aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boot/android_bootloader_oemlock.c17
-rw-r--r--cuttlefish.fragment5
-rw-r--r--drivers/virtio/virtio_console.c418
-rw-r--r--drivers/virtio/virtio_console.h50
-rw-r--r--include/virtio.h1
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 */