From: Filip Hejsek <[email protected]> Implement the part of the virtio spec that allows to notify the virtio driver about terminal resizes. Gate the new resize support behind the console-size property, which defaults to off. To implement the property, restore VirtIOSerial::host_features (previously removed by commit d0660e5b7fc7e97926232d13d2500a16df32b310).
The virtio spec contains two methods to inform the guest about console size: For legacy drivers, we have only one port and we put the terminal size in the config space and inject the config changed interrupt. For multiport devices, we use the control virtqueue to send a packet containing the terminal size. Unfortunately, old versions of the Linux kernel used an incorrect order for the fields (rows then cols instead of cols then rows), until it was fixed by commit 5326ab737a47278dbd16ed3ee7380b26c7056ddd. As a result, when running an old guest kernel, the number of rows and columns will be swapped. Based on a patch originally written by Szymon Lukasz <[email protected]>, but partially rewritten to fix various corner cases. Signed-off-by: Szymon Lukasz <[email protected]> Signed-off-by: Filip Hejsek <[email protected]> Acked-by: Michael S. Tsirkin <[email protected]> Reviewed-by: Marc-André Lureau <[email protected]> Message-ID: <[email protected]> --- hw/char/trace-events | 1 + hw/char/virtio-serial-bus.c | 81 ++++++++++++++++++++++++++++++++++++++- include/hw/virtio/virtio-serial.h | 7 ++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/hw/char/trace-events b/hw/char/trace-events index a3fcc772877a..e387c8853fcf 100644 --- a/hw/char/trace-events +++ b/hw/char/trace-events @@ -11,6 +11,7 @@ serial_update_parameters(uint64_t baudrate, char parity, int data_bits, int stop # virtio-serial-bus.c virtio_serial_send_control_event(unsigned int port, uint16_t event, uint16_t value) "port %u, event %u, value %u" +virtio_serial_send_console_resize(unsigned int port, uint16_t cols, uint16_t rows) "port %u, cols %u, rows %u" virtio_serial_throttle_port(unsigned int port, bool throttle) "port %u, throttle %d" virtio_serial_handle_control_message(uint16_t event, uint16_t value) "event %u, value %u" virtio_serial_handle_control_message_port(unsigned int port) "port %u" diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c index c36e18a4cfb5..882765ac88ab 100644 --- a/hw/char/virtio-serial-bus.c +++ b/hw/char/virtio-serial-bus.c @@ -261,6 +261,68 @@ static size_t send_control_event(VirtIOSerial *vser, uint32_t port_id, return send_control_msg(vser, &cpkt, sizeof(cpkt)); } +/* + * This struct should be added to the Linux kernel uapi headers + * and later imported to standard-headers/linux/virtio_console.h + */ +struct virtio_console_resize { + __virtio16 cols; + __virtio16 rows; +}; + +static void send_console_resize(VirtIOSerialPort *port) +{ + VirtIOSerial *vser = port->vser; + VirtIODevice *vdev = VIRTIO_DEVICE(vser); + + if (!virtio_has_feature(vser->host_features, VIRTIO_CONSOLE_F_SIZE)) { + return; + } + + trace_virtio_serial_send_console_resize(port->id, port->cols, port->rows); + + if (use_multiport(vser)) { + struct { + struct virtio_console_control control; + struct virtio_console_resize resize; + } buffer = {}; + + virtio_stl_p(vdev, &buffer.control.id, port->id); + virtio_stw_p(vdev, &buffer.control.event, VIRTIO_CONSOLE_RESIZE); + virtio_stw_p(vdev, &buffer.resize.cols, port->cols); + virtio_stw_p(vdev, &buffer.resize.rows, port->rows); + + send_control_msg(vser, &buffer, sizeof(buffer)); + } +} + +void virtio_serial_resize_console(VirtIOSerialPort *port, + uint16_t cols, uint16_t rows) +{ + VirtIOSerial *vser = port->vser; + VirtIODevice *vdev = VIRTIO_DEVICE(vser); + + if (port->cols == cols && port->rows == rows) { + return; + } + + port->cols = cols; + port->rows = rows; + + if (port->id == 0 && !use_multiport(vser) && + virtio_vdev_has_feature(vdev, VIRTIO_CONSOLE_F_SIZE)) { + virtio_notify_config(vdev); + } + + /* + * We will send these messages even before we told the guest that + * it is a console port (by sending VIRTIO_CONSOLE_CONSOLE_PORT + * message), but that should be fine as the guest will likely + * ignore them. + */ + send_console_resize(port); +} + /* Functions for use inside qemu to open and read from/write to ports */ int virtio_serial_open(VirtIOSerialPort *port) { @@ -403,6 +465,7 @@ static void handle_control_message(VirtIOSerial *vser, */ if (vsc->is_console) { send_control_event(vser, port->id, VIRTIO_CONSOLE_CONSOLE_PORT, 1); + send_console_resize(port); } if (port->name) { @@ -538,6 +601,7 @@ static uint64_t get_features(VirtIODevice *vdev, uint64_t features, vser = VIRTIO_SERIAL(vdev); + features |= vser->host_features; features |= BIT_ULL(VIRTIO_CONSOLE_F_EMERG_WRITE); if (vser->bus.max_nr_ports > 1) { virtio_add_feature(&features, VIRTIO_CONSOLE_F_MULTIPORT); @@ -549,11 +613,18 @@ static uint64_t get_features(VirtIODevice *vdev, uint64_t features, static void get_config(VirtIODevice *vdev, uint8_t *config_data) { VirtIOSerial *vser = VIRTIO_SERIAL(vdev); + VirtIOSerialPort *port; struct virtio_console_config *config = (struct virtio_console_config *)config_data; - config->cols = 0; - config->rows = 0; + port = find_port_by_id(vser, 0); + if (port) { + config->cols = virtio_tswap16(vdev, port->cols); + config->rows = virtio_tswap16(vdev, port->rows); + } else { + config->cols = 0; + config->rows = 0; + } config->max_nr_ports = virtio_tswap32(vdev, vser->serial.max_virtserial_ports); } @@ -716,6 +787,10 @@ static void virtio_serial_post_load_timer_cb(void *opaque) port->host_connected); } vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port); + /* + * TODO: let the guest know about new console size if it has changed + * (this will require sending old size in the migration stream) + */ if (vsc->set_guest_connected) { vsc->set_guest_connected(port, port->guest_connected); } @@ -1142,6 +1217,8 @@ static const VMStateDescription vmstate_virtio_console = { static const Property virtio_serial_properties[] = { DEFINE_PROP_UINT32("max_ports", VirtIOSerial, serial.max_virtserial_ports, 31), + DEFINE_PROP_BIT64("console-size", VirtIOSerial, host_features, + VIRTIO_CONSOLE_F_SIZE, false), }; static void virtio_serial_class_init(ObjectClass *klass, const void *data) diff --git a/include/hw/virtio/virtio-serial.h b/include/hw/virtio/virtio-serial.h index da0c91e1a403..bda6d5312af6 100644 --- a/include/hw/virtio/virtio-serial.h +++ b/include/hw/virtio/virtio-serial.h @@ -145,6 +145,9 @@ struct VirtIOSerialPort { bool host_connected; /* Do apps not want to receive data? */ bool throttled; + + /* Terminal size reported to the guest. Only used for consoles. */ + uint16_t cols, rows; }; /* The virtio-serial bus on top of which the ports will ride as devices */ @@ -186,6 +189,8 @@ struct VirtIOSerial { struct VirtIOSerialPostLoad *post_load; virtio_serial_conf serial; + + uint64_t host_features; }; /* Interface to the virtio-serial bus */ @@ -220,5 +225,7 @@ size_t virtio_serial_guest_ready(VirtIOSerialPort *port); */ void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle); +void virtio_serial_resize_console(VirtIOSerialPort *port, + uint16_t cols, uint16_t rows); #endif -- 2.56.0.116.g6de20f6092dc
