Skip to content

Commit 8fded46

Browse files
author
Jakub Klimczak
committed
drivers: serial: virtio_console: Postpone transmission of control messages
There was a bug in the VIRTIO Console driver which could cause a deadlock by attempting to add buffers to the control-tx virtqueue too fast and with an infinite timeout. This commit fixes it by placing messages that couldn't be sent in a FIFO queue and taking care of them later in a callback function. Signed-off-by: Jakub Klimczak <[email protected]>
1 parent 335e1c2 commit 8fded46

File tree

1 file changed

+62
-10
lines changed

1 file changed

+62
-10
lines changed

drivers/serial/uart_virtio_console.c

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
#define DT_DRV_COMPAT virtio_console
8-
#include <stdio.h>
98
#include <zephyr/device.h>
109
#include <zephyr/drivers/virtio.h>
1110
#include <zephyr/drivers/virtio/virtqueue.h>
@@ -35,6 +34,11 @@ struct _virtio_console_control {
3534
/* VIRTIO_CONSOLE_PORT_NAME immediately followed by a name */
3635
char name[CONFIG_UART_VIRTIO_CONSOLE_NAME_BUFSIZE];
3736
};
37+
struct _fifo_item_virtio_console_control {
38+
void *fifo_reserved;
39+
bool pending; /* true if message is awaiting transmission */
40+
struct _virtio_console_control msg;
41+
};
3842
#endif
3943

4044
enum _flags {
@@ -112,8 +116,10 @@ struct virtconsole_data {
112116
int8_t n_console_ports;
113117
struct k_spinlock ctlrxsl, ctltxsl;
114118
size_t txctlcurrent;
115-
struct _virtio_console_control rx_ctlbuf[CONFIG_UART_VIRTIO_CONSOLE_RX_CONTROL_BUFSIZE],
119+
struct _virtio_console_control rx_ctlbuf[CONFIG_UART_VIRTIO_CONSOLE_RX_CONTROL_BUFSIZE];
120+
struct _fifo_item_virtio_console_control
116121
tx_ctlbuf[CONFIG_UART_VIRTIO_CONSOLE_TX_CONTROL_BUFSIZE];
122+
struct k_fifo tx_ctlfifo;
117123
struct _ctl_cb_data ctl_cb_data[CONFIG_UART_VIRTIO_CONSOLE_RX_CONTROL_BUFSIZE];
118124
struct _rx_cb_data rx_cb_data[VIRTIO_CONSOLE_MAX_PORTS];
119125
#else
@@ -176,13 +182,42 @@ static void virtconsole_recv_setup(const struct device *dev, uint16_t q_no, void
176182
}
177183
struct virtq_buf vqbuf[] = {{.addr = addr, .len = len}};
178184

179-
if (virtq_add_buffer_chain(vq, vqbuf, 1, 0, recv_cb, cb_data, K_FOREVER)) {
185+
if (virtq_add_buffer_chain(vq, vqbuf, 1, 0, recv_cb, cb_data, K_NO_WAIT)) {
180186
LOG_ERR("could not set up virtqueue %u for receiving", q_no);
181187
return;
182188
}
183189
virtio_notify_virtqueue(config->vdev, q_no);
184190
}
185191
#ifdef CONFIG_UART_VIRTIO_CONSOLE_F_MULTIPORT
192+
static void virtconsole_control_tx_flush(void *priv, uint32_t len)
193+
{
194+
struct virtconsole_data *data = priv;
195+
const struct device *dev = data->dev;
196+
const struct virtconsole_config *config = dev->config;
197+
struct _fifo_item_virtio_console_control *item;
198+
struct virtq *vq = virtio_get_virtqueue(config->vdev, VIRTQ_CONTROL_TX);
199+
200+
if (vq == NULL) {
201+
LOG_ERR("could not access virtqueue 3");
202+
return;
203+
}
204+
int i = vq->free_desc_n;
205+
206+
while ((i-- > 0) && (item = k_fifo_get(&data->tx_ctlfifo, K_NO_WAIT))) {
207+
struct virtq_buf vqbuf = {.addr = &item->msg, .len = sizeof(item->msg)};
208+
209+
int ret = virtq_add_buffer_chain(vq, &vqbuf, 1, 1, virtconsole_control_tx_flush,
210+
priv, K_NO_WAIT);
211+
212+
if (ret) {
213+
LOG_ERR("could not send control message");
214+
return;
215+
}
216+
virtio_notify_virtqueue(config->vdev, VIRTQ_CONTROL_TX);
217+
item->pending = false;
218+
}
219+
}
220+
186221
static void virtconsole_send_control_msg(const struct device *dev, uint32_t port, uint16_t event,
187222
uint16_t value)
188223
{
@@ -196,17 +231,33 @@ static void virtconsole_send_control_msg(const struct device *dev, uint32_t port
196231
LOG_ERR("could not access virtqueue 3");
197232
K_SPINLOCK_BREAK;
198233
}
199-
data->tx_ctlbuf[data->txctlcurrent].port = sys_cpu_to_le32(port);
200-
data->tx_ctlbuf[data->txctlcurrent].event = sys_cpu_to_le16(event);
201-
data->tx_ctlbuf[data->txctlcurrent].value = sys_cpu_to_le16(value);
202-
struct virtq_buf vqbuf = {.addr = data->tx_ctlbuf + data->txctlcurrent,
203-
.len = sizeof(data->tx_ctlbuf[0])};
234+
struct _fifo_item_virtio_console_control *item =
235+
&(data->tx_ctlbuf[data->txctlcurrent]);
236+
struct _virtio_console_control *msg = &(item->msg);
204237

205-
if (virtq_add_buffer_chain(vq, &vqbuf, 1, 1, NULL, NULL, K_FOREVER)) {
238+
if (item->pending) {
239+
LOG_ERR("not enough free buffers for control message");
240+
K_SPINLOCK_BREAK;
241+
}
242+
msg->port = sys_cpu_to_le32(port);
243+
msg->event = sys_cpu_to_le16(event);
244+
msg->value = sys_cpu_to_le16(value);
245+
struct virtq_buf vqbuf = {.addr = msg, .len = sizeof(*msg)};
246+
247+
int ret = virtq_add_buffer_chain(vq, &vqbuf, 1, 1, virtconsole_control_tx_flush,
248+
data, K_NO_WAIT);
249+
250+
if (ret == -EBUSY) {
251+
/* put in FIFO to be sent later, mark buffer */
252+
/* as occupied to prevent overwriting */
253+
k_fifo_put(&data->tx_ctlfifo, data->tx_ctlbuf + data->txctlcurrent);
254+
item->pending = true;
255+
} else if (ret) {
206256
LOG_ERR("could not send control message");
207257
K_SPINLOCK_BREAK;
258+
} else {
259+
virtio_notify_virtqueue(config->vdev, VIRTQ_CONTROL_TX);
208260
}
209-
virtio_notify_virtqueue(config->vdev, VIRTQ_CONTROL_TX);
210261
data->txctlcurrent =
211262
(data->txctlcurrent + 1) % CONFIG_UART_VIRTIO_CONSOLE_TX_CONTROL_BUFSIZE;
212263
}
@@ -499,6 +550,7 @@ static int virtconsole_init(const struct device *dev)
499550
virtio_finalize_init(config->vdev);
500551
#ifdef CONFIG_UART_VIRTIO_CONSOLE_F_MULTIPORT
501552
if (multiport) {
553+
k_fifo_init(&data->tx_ctlfifo);
502554
for (int i = 0; i < CONFIG_UART_VIRTIO_CONSOLE_RX_CONTROL_BUFSIZE; i++) {
503555
data->ctl_cb_data[i].data = data;
504556
data->ctl_cb_data[i].buf_no = i;

0 commit comments

Comments
 (0)