#include "qemu/iov.h"
#include "monitor/monitor.h"
+#include "qemu/error-report.h"
#include "qemu/queue.h"
#include "hw/sysbus.h"
#include "trace.h"
#include "hw/virtio/virtio-serial.h"
+#include "hw/virtio/virtio-access.h"
+
+static struct VirtIOSerialDevices {
+ QLIST_HEAD(, VirtIOSerial) devices;
+} vserdevices;
static VirtIOSerialPort *find_port_by_id(VirtIOSerial *vser, uint32_t id)
{
return NULL;
}
+static VirtIOSerialPort *find_port_by_name(char *name)
+{
+ VirtIOSerial *vser;
+
+ QLIST_FOREACH(vser, &vserdevices.devices, next) {
+ VirtIOSerialPort *port;
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ if (port->name && !strcmp(port->name, name)) {
+ return port;
+ }
+ }
+ }
+ return NULL;
+}
+
static bool use_multiport(VirtIOSerial *vser)
{
VirtIODevice *vdev = VIRTIO_DEVICE(vser);
- return vdev->guest_features & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+ return virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT);
}
static size_t write_to_port(VirtIOSerialPort *port,
return 0;
}
- memcpy(elem.in_sg[0].iov_base, buf, len);
+ /* TODO: detect a buffer that's too short, set NEEDS_RESET */
+ iov_from_buf(elem.in_sg, elem.in_num, 0, buf, len);
virtqueue_push(vq, &elem, len);
virtio_notify(VIRTIO_DEVICE(vser), vq);
static size_t send_control_event(VirtIOSerial *vser, uint32_t port_id,
uint16_t event, uint16_t value)
{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vser);
struct virtio_console_control cpkt;
- stl_p(&cpkt.id, port_id);
- stw_p(&cpkt.event, event);
- stw_p(&cpkt.value, value);
+ virtio_stl_p(vdev, &cpkt.id, port_id);
+ virtio_stw_p(vdev, &cpkt.event, event);
+ virtio_stw_p(vdev, &cpkt.value, value);
trace_virtio_serial_send_control_event(port_id, event, value);
return send_control_msg(vser, &cpkt, sizeof(cpkt));
/* Guest wants to notify us of some event */
static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len)
{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vser);
struct VirtIOSerialPort *port;
VirtIOSerialPortClass *vsc;
struct virtio_console_control cpkt, *gcpkt;
return;
}
- cpkt.event = lduw_p(&gcpkt->event);
- cpkt.value = lduw_p(&gcpkt->value);
+ cpkt.event = virtio_lduw_p(vdev, &gcpkt->event);
+ cpkt.value = virtio_lduw_p(vdev, &gcpkt->value);
trace_virtio_serial_handle_control_message(cpkt.event, cpkt.value);
return;
}
- port = find_port_by_id(vser, ldl_p(&gcpkt->id));
+ port = find_port_by_id(vser, virtio_ldl_p(vdev, &gcpkt->id));
if (!port) {
error_report("virtio-serial-bus: Unexpected port id %u for device %s",
- ldl_p(&gcpkt->id), vser->bus.qbus.name);
+ virtio_ldl_p(vdev, &gcpkt->id), vser->bus.qbus.name);
return;
}
}
if (port->name) {
- stl_p(&cpkt.id, port->id);
- stw_p(&cpkt.event, VIRTIO_CONSOLE_PORT_NAME);
- stw_p(&cpkt.value, 1);
+ virtio_stl_p(vdev, &cpkt.id, port->id);
+ virtio_stw_p(vdev, &cpkt.event, VIRTIO_CONSOLE_PORT_NAME);
+ virtio_stw_p(vdev, &cpkt.value, 1);
buffer_len = sizeof(cpkt) + strlen(port->name) + 1;
buffer = g_malloc(buffer_len);
static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
{
+ /*
+ * Users of virtio-serial would like to know when guest becomes
+ * writable again -- i.e. if a vq had stuff queued up and the
+ * guest wasn't reading at all, the host would not be able to
+ * write to the vq anymore. Once the guest reads off something,
+ * we can start queueing things up again. However, this call is
+ * made for each buffer addition by the guest -- even though free
+ * buffers existed prior to the current buffer addition. This is
+ * done so as not to maintain previous state, which will need
+ * additional live-migration-related changes.
+ */
+ VirtIOSerial *vser;
+ VirtIOSerialPort *port;
+ VirtIOSerialPortClass *vsc;
+
+ vser = VIRTIO_SERIAL(vdev);
+ port = find_port_by_vq(vser, vq);
+
+ if (!port) {
+ return;
+ }
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ /*
+ * If guest_connected is false, this call is being made by the
+ * early-boot queueing up of descriptors, which is just noise for
+ * the host apps -- don't disturb them in that case.
+ */
+ if (port->guest_connected && port->host_connected && vsc->guest_writable) {
+ vsc->guest_writable(port);
+ }
}
-static uint32_t get_features(VirtIODevice *vdev, uint32_t features)
+static uint64_t get_features(VirtIODevice *vdev, uint64_t features,
+ Error **errp)
{
VirtIOSerial *vser;
vser = VIRTIO_SERIAL(vdev);
if (vser->bus.max_nr_ports > 1) {
- features |= (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+ virtio_add_feature(&features, VIRTIO_CONSOLE_F_MULTIPORT);
}
return features;
}
/* Guest requested config info */
static void get_config(VirtIODevice *vdev, uint8_t *config_data)
{
- VirtIOSerial *vser;
-
- vser = VIRTIO_SERIAL(vdev);
- memcpy(config_data, &vser->config, sizeof(struct virtio_console_config));
+ VirtIOSerial *vser = VIRTIO_SERIAL(vdev);
+ struct virtio_console_config *config =
+ (struct virtio_console_config *)config_data;
+
+ config->cols = 0;
+ config->rows = 0;
+ config->max_nr_ports = virtio_tswap32(vdev,
+ vser->serial.max_virtserial_ports);
}
static void guest_reset(VirtIOSerial *vser)
static void virtio_serial_save(QEMUFile *f, void *opaque)
{
- VirtIOSerial *s = VIRTIO_SERIAL(opaque);
+ /* The virtio device */
+ virtio_save(VIRTIO_DEVICE(opaque), f);
+}
+
+static void virtio_serial_save_device(VirtIODevice *vdev, QEMUFile *f)
+{
+ VirtIOSerial *s = VIRTIO_SERIAL(vdev);
VirtIOSerialPort *port;
uint32_t nr_active_ports;
unsigned int i, max_nr_ports;
+ struct virtio_console_config config;
- /* The virtio device */
- virtio_save(VIRTIO_DEVICE(s), f);
-
- /* The config space */
- qemu_put_be16s(f, &s->config.cols);
- qemu_put_be16s(f, &s->config.rows);
-
- qemu_put_be32s(f, &s->config.max_nr_ports);
+ /* The config space (ignored on the far end in current versions) */
+ get_config(vdev, (uint8_t *)&config);
+ qemu_put_be16s(f, &config.cols);
+ qemu_put_be16s(f, &config.rows);
+ qemu_put_be32s(f, &config.max_nr_ports);
/* The ports map */
- max_nr_ports = tswap32(s->config.max_nr_ports);
+ max_nr_ports = s->serial.max_virtserial_ports;
for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
qemu_put_be32s(f, &s->ports_map[i]);
}
static int virtio_serial_load(QEMUFile *f, void *opaque, int version_id)
{
- VirtIOSerial *s = VIRTIO_SERIAL(opaque);
- uint32_t max_nr_ports, nr_active_ports, ports_map;
- unsigned int i;
- int ret;
-
if (version_id > 3) {
return -EINVAL;
}
/* The virtio device */
- ret = virtio_load(VIRTIO_DEVICE(s), f);
- if (ret) {
- return ret;
- }
+ return virtio_load(VIRTIO_DEVICE(opaque), f, version_id);
+}
+
+static int virtio_serial_load_device(VirtIODevice *vdev, QEMUFile *f,
+ int version_id)
+{
+ VirtIOSerial *s = VIRTIO_SERIAL(vdev);
+ uint32_t max_nr_ports, nr_active_ports, ports_map;
+ unsigned int i;
+ int ret;
+ uint32_t tmp;
if (version_id < 2) {
return 0;
}
- /* The config space */
- qemu_get_be16s(f, &s->config.cols);
- qemu_get_be16s(f, &s->config.rows);
-
- qemu_get_be32s(f, &max_nr_ports);
- tswap32s(&max_nr_ports);
- if (max_nr_ports > tswap32(s->config.max_nr_ports)) {
- /* Source could have had more ports than us. Fail migration. */
- return -EINVAL;
- }
+ /* Unused */
+ qemu_get_be16s(f, (uint16_t *) &tmp);
+ qemu_get_be16s(f, (uint16_t *) &tmp);
+ qemu_get_be32s(f, &tmp);
+ max_nr_ports = s->serial.max_virtserial_ports;
for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
qemu_get_be32s(f, &ports_map);
{
unsigned int i, max_nr_ports;
- max_nr_ports = tswap32(vser->config.max_nr_ports);
+ max_nr_ports = vser->serial.max_virtserial_ports;
for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
- uint32_t map, bit;
+ uint32_t map, zeroes;
map = vser->ports_map[i];
- bit = ffs(~map);
- if (bit) {
- return (bit - 1) + i * 32;
+ zeroes = ctz32(~map);
+ if (zeroes != 32) {
+ return zeroes + i * 32;
}
}
return VIRTIO_CONSOLE_BAD_ID;
static void remove_port(VirtIOSerial *vser, uint32_t port_id)
{
VirtIOSerialPort *port;
- unsigned int i;
- i = port_id / 32;
- vser->ports_map[i] &= ~(1U << (port_id % 32));
+ /*
+ * Don't mark port 0 removed -- we explicitly reserve it for
+ * backward compat with older guests, ensure a virtconsole device
+ * unplug retains the reservation.
+ */
+ if (port_id) {
+ unsigned int i;
+
+ i = port_id / 32;
+ vser->ports_map[i] &= ~(1U << (port_id % 32));
+ }
port = find_port_by_id(vser, port_id);
/*
return;
}
+ if (port->name != NULL && find_port_by_name(port->name)) {
+ error_setg(errp, "virtio-serial-bus: A port already exists by name %s",
+ port->name);
+ return;
+ }
+
if (port->id == VIRTIO_CONSOLE_BAD_ID) {
if (plugging_port0) {
port->id = 0;
}
}
- max_nr_ports = tswap32(port->vser->config.max_nr_ports);
+ max_nr_ports = port->vser->serial.max_virtserial_ports;
if (port->id >= max_nr_ports) {
error_setg(errp, "virtio-serial-bus: Out-of-range port id specified, "
"max. allowed: %u", max_nr_ports - 1);
}
port->elem.out_num = 0;
+}
+
+static void virtser_port_device_plug(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev);
QTAILQ_INSERT_TAIL(&port->vser->ports, port, next);
port->ivq = port->vser->ivqs[port->id];
add_port(port->vser, port->id);
/* Send an update to the guest about this new port added */
- virtio_notify_config(VIRTIO_DEVICE(port->vser));
+ virtio_notify_config(VIRTIO_DEVICE(hotplug_dev));
}
static void virtser_port_device_unrealize(DeviceState *dev, Error **errp)
{
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtIOSerial *vser = VIRTIO_SERIAL(dev);
- BusState *bus;
uint32_t i, max_supported_ports;
if (!vser->serial.max_virtserial_ports) {
}
/* Each port takes 2 queues, and one pair is for the control queue */
- max_supported_ports = VIRTIO_PCI_QUEUE_MAX / 2 - 1;
+ max_supported_ports = VIRTIO_QUEUE_MAX / 2 - 1;
if (vser->serial.max_virtserial_ports > max_supported_ports) {
error_setg(errp, "maximum ports supported: %u", max_supported_ports);
return;
}
+ /* We don't support emergency write, skip it for now. */
+ /* TODO: cleaner fix, depending on host features. */
virtio_init(vdev, "virtio-serial", VIRTIO_ID_CONSOLE,
- sizeof(struct virtio_console_config));
+ offsetof(struct virtio_console_config, emerg_wr));
/* Spawn a new virtio-serial bus on which the ports will ride as devices */
qbus_create_inplace(&vser->bus, sizeof(vser->bus), TYPE_VIRTIO_SERIAL_BUS,
dev, vdev->bus_name);
- bus = BUS(&vser->bus);
- bus->allow_hotplug = 1;
+ qbus_set_hotplug_handler(BUS(&vser->bus), DEVICE(vser), errp);
vser->bus.vser = vser;
QTAILQ_INIT(&vser->ports);
vser->ovqs[i] = virtio_add_queue(vdev, 128, handle_output);
}
- vser->config.max_nr_ports = tswap32(vser->serial.max_virtserial_ports);
vser->ports_map = g_malloc0(((vser->serial.max_virtserial_ports + 31) / 32)
* sizeof(vser->ports_map[0]));
/*
*/
register_savevm(dev, "virtio-console", -1, 3, virtio_serial_save,
virtio_serial_load, vser);
+
+ QLIST_INSERT_HEAD(&vserdevices.devices, vser, next);
}
static void virtio_serial_port_class_init(ObjectClass *klass, void *data)
k->bus_type = TYPE_VIRTIO_SERIAL_BUS;
k->realize = virtser_port_device_realize;
k->unrealize = virtser_port_device_unrealize;
- k->unplug = qdev_simple_unplug_cb;
k->props = virtser_props;
}
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtIOSerial *vser = VIRTIO_SERIAL(dev);
+ QLIST_REMOVE(vser, next);
+
unregister_savevm(dev, "virtio-console", vser);
g_free(vser->ivqs);
}
static Property virtio_serial_properties[] = {
- DEFINE_VIRTIO_SERIAL_PROPERTIES(VirtIOSerial, serial),
+ DEFINE_PROP_UINT32("max_ports", VirtIOSerial, serial.max_virtserial_ports,
+ 31),
DEFINE_PROP_END_OF_LIST(),
};
{
DeviceClass *dc = DEVICE_CLASS(klass);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+
+ QLIST_INIT(&vserdevices.devices);
dc->props = virtio_serial_properties;
set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
vdc->get_config = get_config;
vdc->set_status = set_status;
vdc->reset = vser_reset;
+ vdc->save = virtio_serial_save_device;
+ vdc->load = virtio_serial_load_device;
+ hc->plug = virtser_port_device_plug;
+ hc->unplug = qdev_simple_device_unplug_cb;
}
static const TypeInfo virtio_device_info = {
.parent = TYPE_VIRTIO_DEVICE,
.instance_size = sizeof(VirtIOSerial),
.class_init = virtio_serial_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ }
};
static void virtio_serial_register_types(void)