diff options
-rw-r--r-- | boot/android_bootloader_oemlock.c | 17 | ||||
-rw-r--r-- | cuttlefish.fragment | 5 | ||||
-rw-r--r-- | drivers/virtio/virtio_console.c | 418 | ||||
-rw-r--r-- | drivers/virtio/virtio_console.h | 50 | ||||
-rw-r--r-- | include/virtio.h | 1 |
5 files changed, 413 insertions, 78 deletions
diff --git a/boot/android_bootloader_oemlock.c b/boot/android_bootloader_oemlock.c index eac9ba5cb9..0e5dbea900 100644 --- a/boot/android_bootloader_oemlock.c +++ b/boot/android_bootloader_oemlock.c @@ -6,6 +6,7 @@ #include <android_bootloader_oemlock.h> #include <android_bootloader_transport.h> #include <dm/device.h> +#include <dm/device-internal.h> #include <dm/uclass.h> #include <serial.h> #include <virtio.h> @@ -23,11 +24,19 @@ static struct udevice* get_console(void) { static struct udevice *console = NULL; if (console == NULL) { - if (uclass_get_nth_device_by_driver_name(UCLASS_SERIAL, console_index, - VIRTIO_CONSOLE_DRV_NAME, &console)) { - log_err("Failed to initialize oemlock console\n"); - return NULL; + int single_port = uclass_get_nth_device_by_driver_name( + UCLASS_SERIAL, console_index, VIRTIO_CONSOLE_DRV_NAME, &console); + if (single_port == 0) { + return console; } + // TODO(schuffelen): Do this by console name + int multi_port = uclass_get_nth_device_by_driver_name( + UCLASS_SERIAL, console_index - 1, VIRTIO_CONSOLE_PORT_DRV_NAME, &console); + if (multi_port == 0) { + return console; + } + log_err("Failed to initialize oemlock console: %d, %d\n", single_port, multi_port); + return NULL; } return console; } diff --git a/cuttlefish.fragment b/cuttlefish.fragment index 57039504e7..5033e59562 100644 --- a/cuttlefish.fragment +++ b/cuttlefish.fragment @@ -64,5 +64,10 @@ CONFIG_CMD_BCB=y # Used by Android boot loader (oemlock/avb) CONFIG_ANDROID_BOOTLOADER_OEMLOCK_VIRTIO_CONSOLE_INDEX=10 +# Enable tracing of error codes +CONFIG_LOG=y +CONFIG_LOG_CONSOLE=y +CONFIG_LOG_ERROR_RETURN=y + # Must not use RNG from host. # CONFIG_VIRTIO_RNG is not set diff --git a/drivers/virtio/virtio_console.c b/drivers/virtio/virtio_console.c index 74bf371c62..02e66a45c2 100644 --- a/drivers/virtio/virtio_console.c +++ b/drivers/virtio/virtio_console.c @@ -9,75 +9,235 @@ * Copyright (C) 2021, Google LLC, schuffelen@google.com (A. Cody Schuffelen) */ -#include <common.h> #include <blk.h> #include <dm.h> +#include <dm/device-internal.h> +#include <dm/devres.h> #include <part.h> #include <serial.h> #include <virtio_types.h> #include <virtio.h> #include <virtio_ring.h> -#include "virtio_blk.h" +#include "dm/device.h" +#include "virtio_console.h" +static const size_t CONTROL_BUFFER_SIZE = 64; +static const size_t CONTROL_QUEUE_SIZE = 32; + +static const u32 features[] = { + VIRTIO_CONSOLE_F_MULTIPORT, +}; + +// Private data struct for the top-level virtio-console udevice. struct virtio_console_priv { - struct virtqueue *receiveq_port0; - struct virtqueue *transmitq_port0; - unsigned char inbuf[1] __aligned(4); + struct virtqueue *receiveq_control; + struct virtqueue *transmitq_control; + char control_buffers[CONTROL_QUEUE_SIZE][CONTROL_BUFFER_SIZE]; }; -static int virtio_console_bind(struct udevice *dev) +// "Platform data struct", both the top-level and every child port device +// contain one of these. +struct virtio_console_port_plat { + struct virtio_console_priv *console_priv; + struct virtqueue *receiveq; + struct virtqueue *transmitq; + int port_num; + unsigned char char_inbuf[1] __aligned(4); +}; + +static int virtqueue_blocking_send(struct virtqueue *queue, void *data, + size_t size) { - struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent); + struct virtio_sg sg = { + .addr = data, + .length = size, + }; - /* Indicate what driver features we support */ - virtio_driver_features_init(uc_priv, NULL, 0, NULL, 0); + struct virtio_sg *sgs[] = {&sg}; + int ret = virtqueue_add(queue, sgs, 1, 0); + + if (ret) + return log_msg_ret("failed to add buffer", ret); + + virtqueue_kick(queue); + + while (!virtqueue_get_buf(queue, NULL)) + ; return 0; } -/* - * Create a scatter-gather list representing our input buffer and put - * it in the queue. - */ -static void add_inbuf(struct virtio_console_priv *priv) +static int virtio_console_send_control_message(struct virtio_console_priv *priv, + u32 id, u16 event, u16 value) { - struct virtio_sg sg; - struct virtio_sg *sgs[1]; + struct virtio_console_control message = { + .id = id, + .event = event, + .value = value, + }; + return virtqueue_blocking_send(priv->transmitq_control, &message, sizeof(message)); +} - sgs[0] = &sg; - sg.addr = priv->inbuf; - sg.length = sizeof(priv->inbuf); +static int fill_control_inbuf(struct virtio_console_priv *priv) +{ + // The QEMU host implementation of this will drop control messages if + // there's no space in the guest. Preemptively try to keep the control + // queue well-provisioned with messages that can store either a control + // message or a name (from VIRTIO_CONSOLE_PORT_NAME). + struct virtio_sg sgs[ARRAY_SIZE(priv->control_buffers)]; + struct virtio_sg *sg_addrs[ARRAY_SIZE(sgs)]; + int i; + int ret; - /* We should always be able to add one buffer to an empty queue. */ - if (virtqueue_add(priv->receiveq_port0, sgs, 0, 1) < 0) { - debug("%s: virtqueue_add failed\n", __func__); - BUG(); + for (i = 0; i < ARRAY_SIZE(sgs); i++) { + sgs[i].addr = priv->control_buffers[i]; + sgs[i].length = CONTROL_BUFFER_SIZE; + sg_addrs[i] = &sgs[i]; } - virtqueue_kick(priv->receiveq_port0); + + ret = virtqueue_add(priv->receiveq_control, sg_addrs, 0, ARRAY_SIZE(sgs)); + + if (ret) + return log_ret(ret); + + virtqueue_kick(priv->receiveq_control); + + return 0; } -static int virtio_console_probe(struct udevice *dev) +static int return_control_buffer(struct virtio_console_priv *priv, void *data) { - struct virtio_console_priv *priv = dev_get_priv(dev); + struct virtio_sg sg = { + .addr = data, + .length = CONTROL_BUFFER_SIZE, + }; + struct virtio_sg *sgs[1] = { &sg }; + int ret = virtqueue_add(priv->receiveq_control, sgs, 0, 1); + + if (ret) + return log_msg_ret("Adding control receive buffer", ret); + + virtqueue_kick(priv->receiveq_control); + + return 0; +} + +static int virtio_console_control_messsage_pending(struct virtio_console_priv *priv) +{ + return virtqueue_poll(priv->receiveq_control, priv->receiveq_control->last_used_idx); +} + +static int virtio_console_process_control_message(struct virtio_console_priv *priv) +{ + unsigned int len = 0; + struct virtio_console_control *control_ptr; + struct virtio_console_control control; int ret; - struct virtqueue *virtqueues[2]; + if (!virtio_console_control_messsage_pending(priv)) + return 0; // Nothing to process - ret = virtio_find_vqs(dev, 2, virtqueues); - if (ret < 0) { - debug("%s: virtio_find_vqs failed\n", __func__); - return ret; - } + control_ptr = virtqueue_get_buf(priv->receiveq_control, &len); + + if (!control_ptr) + return log_msg_ret("No buffers", -EINVAL); + else if (len != sizeof(*control_ptr)) + return log_msg_ret("Unexpected buffer size", -EINVAL); + + control = *control_ptr; + ret = return_control_buffer(priv, control_ptr); - priv->receiveq_port0 = virtqueues[0]; - priv->transmitq_port0 = virtqueues[1]; + if (ret) + log_msg_ret("returning control buffer", ret); - /* Register the input buffer the first time. */ - add_inbuf(priv); + switch (control.event) { + case VIRTIO_CONSOLE_PORT_ADD: + ret = virtio_console_send_control_message(priv, control.id, + VIRTIO_CONSOLE_PORT_READY, 1); + if (ret) + return log_msg_ret("sending port ready message", ret); + return 0; + case VIRTIO_CONSOLE_CONSOLE_PORT: + ret = virtio_console_send_control_message(priv, control.id, + VIRTIO_CONSOLE_PORT_OPEN, 1); + + if (ret) + return log_msg_ret("sending port open message", ret); + return 0; + case VIRTIO_CONSOLE_PORT_REMOVE: + case VIRTIO_CONSOLE_RESIZE: + case VIRTIO_CONSOLE_PORT_OPEN: + return 0; + case VIRTIO_CONSOLE_PORT_NAME: { + // Since this command is always followed by the name, we have to + // read the name to avoid interpreting it as another control + // command. + while (!virtio_console_control_messsage_pending(priv)) + ; + unsigned int len = 0; + void *buf = virtqueue_get_buf(priv->receiveq_control, &len); + + if (!buf) + return log_msg_ret("expected port name string", -EINVAL); + + ret = return_control_buffer(priv, buf); + if (ret) + return log_msg_ret("returning name buffer", ret); + return 0; + } + default: + return log_msg_ret("unexpected control message event", -EINVAL); + } +} + +static int virtio_console_exhaust_control_queue(struct virtio_console_priv *priv) +{ + while (virtio_console_control_messsage_pending(priv)) { + int ret = virtio_console_process_control_message(priv); + + if (ret) + return log_ret(ret); + } return 0; } +/* + * Create a scatter-gather list representing our input buffer and put + * it in the queue. + */ +static int add_char_inbuf(struct virtio_console_port_plat *plat) +{ + struct virtio_sg sg = { + .addr = plat->char_inbuf, + .length = sizeof(plat->char_inbuf), + }; + struct virtio_sg *sgs[] = {&sg}; + + int ret = virtqueue_add(plat->receiveq, sgs, 0, 1); + + if (ret) + return log_msg_ret("Failed to add to virtqueue", ret); + + virtqueue_kick(plat->receiveq); +} + +static int virtio_console_port_probe(struct udevice *dev) +{ + struct virtio_console_port_plat *plat = dev_get_plat(dev); + + add_char_inbuf(plat); + // QEMU will accept output on ports at any time, but will not pass + // through input until it receives a VIRTIO_CONSOLE_PORT_OPEN on that + // port number. It doesn't seem to produce a VIRTIO_CONSOLE_DEVICE_ADD + // for each port it already has on startup, so here we pre-emptively + // `OPEN` every port when we probe. + int ret = virtio_console_send_control_message(plat->console_priv, plat->port_num, + VIRTIO_CONSOLE_PORT_OPEN, 1); + + return log_msg_ret("failed to send port open message", ret); +} + static int virtio_console_serial_setbrg(struct udevice *dev, int baudrate) { return 0; @@ -85,74 +245,184 @@ static int virtio_console_serial_setbrg(struct udevice *dev, int baudrate) static int virtio_console_serial_pending(struct udevice *dev, bool input) { - struct virtio_console_priv *priv = dev_get_priv(dev); - return virtqueue_poll(priv->receiveq_port0, - priv->receiveq_port0->last_used_idx); + struct virtio_console_port_plat *plat = dev_get_plat(dev); + + return virtqueue_poll(plat->receiveq, + plat->receiveq->last_used_idx); } -static int virtio_console_serial_getc(struct udevice *dev) +static int virtio_console_port_serial_getc(struct udevice *dev) { - struct virtio_console_priv *priv = dev_get_priv(dev); - unsigned char *in; - int ch; + struct virtio_console_port_plat *plat = dev_get_plat(dev); + + virtio_console_exhaust_control_queue(plat->console_priv); unsigned int len = 0; - in = virtqueue_get_buf(priv->receiveq_port0, &len); - if (!in) { + unsigned char *in = virtqueue_get_buf(plat->receiveq, &len); + + if (!in) return -EAGAIN; - } else if (len != 1) { - debug("%s: too much data: %d\n", __func__, len); - BUG(); - } + else if (len != 1) + log_err("%s: too much data: %d\n", __func__, len); - ch = *in; + int ch = *in; - add_inbuf(priv); + add_char_inbuf(plat); return ch; } -static int virtio_console_serial_putc(struct udevice *dev, const char ch) +static int virtio_console_port_serial_putc(struct udevice *dev, const char ch) { - struct virtio_console_priv *priv = dev_get_priv(dev); - struct virtio_sg sg; - struct virtio_sg *sgs[1]; - unsigned char buf[1] __aligned(4); + struct virtio_console_port_plat *plat = dev_get_plat(dev); + + virtio_console_exhaust_control_queue(plat->console_priv); + + return log_ret(virtqueue_blocking_send(plat->transmitq, (void *)&ch, 1)); +} + +static ssize_t virtio_console_port_serial_puts(struct udevice *dev, const char *s, size_t len) +{ + struct virtio_console_port_plat *plat = dev_get_plat(dev); + + virtio_console_exhaust_control_queue(plat->console_priv); + + return log_ret(virtqueue_blocking_send(plat->transmitq, (void *)s, len)); +} + +static const struct dm_serial_ops virtio_console_port_serial_ops = { + .putc = virtio_console_port_serial_putc, + .puts = virtio_console_port_serial_puts, + .pending = virtio_console_serial_pending, + .getc = virtio_console_port_serial_getc, + .setbrg = virtio_console_serial_setbrg, +}; + +static int virtio_console_bind(struct udevice *dev) +{ + struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent); + + /* Indicate what driver features we support */ + virtio_driver_features_init(uc_priv, features, ARRAY_SIZE(features), + NULL, 0); + + return 0; +} + +U_BOOT_DRIVER(virtio_console_port) = { + .name = VIRTIO_CONSOLE_PORT_DRV_NAME, + .id = UCLASS_SERIAL, + .ops = &virtio_console_port_serial_ops, + .probe = virtio_console_port_probe, + .flags = DM_FLAG_ACTIVE_DMA, +}; + +static int virtio_console_create_port(struct udevice *dev, + struct virtqueue **queues, + int port_num) +{ + struct virtio_console_port_plat *plat = + devm_kmalloc(dev, sizeof(struct virtio_console_port_plat), GFP_KERNEL); + struct udevice *created_dev = NULL; int ret = 0; - sg.addr = buf; - sg.length = sizeof(buf); - sgs[0] = &sg; - buf[0] = ch; + if (!plat) + return log_msg_ret("Can't allocate virtio_console_port_plat", -ENOMEM); + + *plat = (struct virtio_console_port_plat) { + .console_priv = dev_get_priv(dev), + .receiveq = queues[(port_num * 2) + 2], + .transmitq = queues[(port_num * 2) + 3], + .port_num = port_num, + }; - ret = virtqueue_add(priv->transmitq_port0, sgs, 1, 0); + ret = device_bind(dev, DM_DRIVER_REF(virtio_console_port), + "virtio_console_port", plat, ofnode_null(), + &created_dev); if (ret) { - debug("%s: virtqueue_add failed\n", __func__); - return ret; + free(plat); + return log_msg_ret("Can't create port device", ret); + } + + return log_msg_ret("Failed to probe device", device_probe(created_dev)); +} + +static int virtio_console_probe(struct udevice *dev) +{ + struct virtio_console_priv *priv = dev_get_priv(dev); + struct virtio_console_port_plat *plat = dev_get_plat(dev); + int is_multiport = 0; + u32 max_ports = 1; + int num_queues = 2; + int ret = 0; + struct virtqueue *virtqueues[64]; // max size + + if (virtio_has_feature(dev, VIRTIO_CONSOLE_F_MULTIPORT)) { + virtio_cread(dev, struct virtio_console_config, max_nr_ports, + &max_ports); + is_multiport = 1; + num_queues = (1 + max_ports) * 2; // In/out per port + control } + if (num_queues > 64) + return log_msg_ret("Too many queues", -ENOMEM); - virtqueue_kick(priv->transmitq_port0); + ret = virtio_find_vqs(dev, num_queues, virtqueues); - while (!virtqueue_get_buf(priv->transmitq_port0, NULL)) { + if (ret) + return log_msg_ret("Can't find virtqueues", ret); + + *plat = (struct virtio_console_port_plat) { + .console_priv = priv, + .receiveq = virtqueues[0], + .transmitq = virtqueues[1], + .port_num = 0, + }; + add_char_inbuf(plat); + + if (is_multiport == 0) { + priv->receiveq_control = NULL; + priv->transmitq_control = NULL; + return 0; + } + + priv->receiveq_control = virtqueues[2]; + priv->transmitq_control = virtqueues[3]; + + ret = fill_control_inbuf(priv); + if (ret) + return log_ret(ret); + + ret = virtio_console_send_control_message(priv, 0, VIRTIO_CONSOLE_DEVICE_READY, 1); + if (ret) + return log_msg_ret("Failed to send ready message", ret); + + ret = virtio_console_exhaust_control_queue(priv); + if (ret) + return log_msg_ret("Failed to handle control message", ret); + + ret = virtio_console_send_control_message(priv, 0, VIRTIO_CONSOLE_PORT_OPEN, 1); + if (ret) + return log_msg_ret("Failed to send port open message", ret); + + add_char_inbuf(plat); + + for (int i = 1; i < max_ports; i++) { + ret = virtio_console_create_port(dev, virtqueues, i); + if (ret) + return log_msg_ret("Failed to create port", ret); } return 0; } -static const struct dm_serial_ops virtio_console_serial_ops = { - .putc = virtio_console_serial_putc, - .pending = virtio_console_serial_pending, - .getc = virtio_console_serial_getc, - .setbrg = virtio_console_serial_setbrg, -}; - U_BOOT_DRIVER(virtio_console) = { .name = VIRTIO_CONSOLE_DRV_NAME, .id = UCLASS_SERIAL, - .ops = &virtio_console_serial_ops, + .ops = &virtio_console_port_serial_ops, .bind = virtio_console_bind, .probe = virtio_console_probe, .remove = virtio_reset, .priv_auto = sizeof(struct virtio_console_priv), - .flags = DM_FLAG_ACTIVE_DMA, + .plat_auto = sizeof(struct virtio_console_port_plat), + .flags = DM_FLAG_ACTIVE_DMA | DM_FLAG_ALLOC_PDATA, }; diff --git a/drivers/virtio/virtio_console.h b/drivers/virtio/virtio_console.h new file mode 100644 index 0000000000..a4fe56c51c --- /dev/null +++ b/drivers/virtio/virtio_console.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (C) Red Hat, Inc., 2009, 2010, 2011 + * Copyright (C) Amit Shah <amit.shah@redhat.com>, 2009, 2010, 2011 + * + * From Linux kernel include/uapi/linux/virtio_console.h + */ +#ifndef _LINUX_VIRTIO_CONSOLE_H +#define _LINUX_VIRTIO_CONSOLE_H + +/* Feature bits */ +#define VIRTIO_CONSOLE_F_SIZE 0 /* Does host provide console size? */ +#define VIRTIO_CONSOLE_F_MULTIPORT 1 /* Does host provide multiple ports? */ +#define VIRTIO_CONSOLE_F_EMERG_WRITE 2 /* Does host support emergency write? */ + +#define VIRTIO_CONSOLE_BAD_ID (~(__u32)0) + +struct virtio_console_config { + /* colums of the screens */ + __u16 cols; + /* rows of the screens */ + __u16 rows; + /* max. number of ports this device can hold */ + __u32 max_nr_ports; + /* emergency write register */ + __u32 emerg_wr; +} __attribute__((packed)); + +/* + * A message that's passed between the Host and the Guest for a + * particular port. + */ +struct virtio_console_control { + __u32 id; /* Port number */ + __u16 event; /* The kind of control event (see below) */ + __u16 value; /* Extra information for the key */ +}; + +/* Some events for control messages */ +#define VIRTIO_CONSOLE_DEVICE_READY 0 +#define VIRTIO_CONSOLE_PORT_ADD 1 +#define VIRTIO_CONSOLE_PORT_REMOVE 2 +#define VIRTIO_CONSOLE_PORT_READY 3 +#define VIRTIO_CONSOLE_CONSOLE_PORT 4 +#define VIRTIO_CONSOLE_RESIZE 5 +#define VIRTIO_CONSOLE_PORT_OPEN 6 +#define VIRTIO_CONSOLE_PORT_NAME 7 + + +#endif diff --git a/include/virtio.h b/include/virtio.h index e36dd41fd1..545a5c3a56 100644 --- a/include/virtio.h +++ b/include/virtio.h @@ -32,6 +32,7 @@ #define VIRTIO_NET_DRV_NAME "virtio-net" #define VIRTIO_BLK_DRV_NAME "virtio-blk" #define VIRTIO_CONSOLE_DRV_NAME "virtio-console" +#define VIRTIO_CONSOLE_PORT_DRV_NAME "virtio-console-port" #define VIRTIO_RNG_DRV_NAME "virtio-rng" /* Status byte for guest to report progress, and synchronize features */ |