/* * Copyright (c) 2006-2021, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2021-11-11 GuEe-GUI the first version */ #include #include #include #ifdef RT_USING_VIRTIO_CONSOLE #include struct port_device { struct rt_device parent; rt_list_t node; rt_uint32_t port_id; rt_bool_t rx_notify; rt_bool_t need_destroy; struct virtio_console_device *console; struct virtq *queue_rx, *queue_tx; rt_uint32_t queue_rx_index, queue_tx_index; #ifdef RT_USING_SMP struct rt_spinlock spinlock_rx, spinlock_tx; #endif struct rt_device_notify rx_notify_helper; struct { char rx_char, tx_char; } info[VIRTIO_CONSOLE_QUEUE_SIZE]; }; static void virtio_console_send_ctrl(struct virtio_console_device *virtio_console_dev, struct virtio_console_control *ctrl) { rt_uint16_t id; struct virtio_device *virtio_dev = &virtio_console_dev->virtio_dev; struct virtq *queue_ctrl_tx; #ifdef RT_USING_SMP rt_base_t level = rt_spin_lock_irqsave(&virtio_dev->spinlock); #endif queue_ctrl_tx = &virtio_dev->queues[VIRTIO_CONSOLE_QUEUE_CTRL_TX]; id = queue_ctrl_tx->avail->idx % queue_ctrl_tx->num; rt_memcpy(&virtio_console_dev->info[id].tx_ctrl, ctrl, sizeof(struct virtio_console_control)); virtio_free_desc(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_TX, id); virtio_fill_desc(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_TX, id, virtio_console_dev->info[id].tx_ctrl_paddr, sizeof(struct virtio_console_control), 0, 0); virtio_submit_chain(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_TX, id); virtio_queue_notify(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_TX); virtio_alloc_desc(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_TX); #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&virtio_dev->spinlock, level); #endif } static rt_err_t virtio_console_port_init(rt_device_t dev); static rt_err_t virtio_console_port_open(rt_device_t dev, rt_uint16_t oflag); static rt_err_t virtio_console_port_close(rt_device_t dev); static rt_ssize_t virtio_console_port_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); static rt_ssize_t virtio_console_port_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); static rt_err_t virtio_console_port_control(rt_device_t dev, int cmd, void *args); #ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops virtio_console_port_ops = { virtio_console_port_init, virtio_console_port_open, virtio_console_port_close, virtio_console_port_read, virtio_console_port_write, virtio_console_port_control }; #endif static rt_err_t virtio_console_port_create(struct virtio_console_device *virtio_console_dev) { rt_uint32_t port_id; char dev_name[RT_NAME_MAX]; struct port_device *port_dev, *prev_port_dev = RT_NULL; struct virtio_device *virtio_dev = &virtio_console_dev->virtio_dev; if (virtio_console_dev->port_nr > 0 && !virtio_has_feature(virtio_dev, VIRTIO_CONSOLE_F_MULTIPORT)) { return -RT_ENOSYS; } if (virtio_console_dev->port_nr >= virtio_console_dev->max_port_nr) { return -RT_EFULL; } port_id = 0; /* The port device list is always ordered, so just find next number for id */ rt_list_for_each_entry(port_dev, &virtio_console_dev->port_head, node) { if (port_dev->port_id != port_id) { break; } ++port_id; prev_port_dev = port_dev; } port_dev = rt_malloc(sizeof(struct port_device)); if (port_dev == RT_NULL) { return -RT_ENOMEM; } port_dev->parent.type = RT_Device_Class_Char; #ifdef RT_USING_DEVICE_OPS port_dev->parent.ops = &virtio_console_port_ops; #else port_dev->parent.init = virtio_console_port_init; port_dev->parent.open = virtio_console_port_open; port_dev->parent.close = virtio_console_port_close; port_dev->parent.read = virtio_console_port_read; port_dev->parent.write = virtio_console_port_write; port_dev->parent.control = virtio_console_port_control; #endif port_dev->parent.rx_indicate = RT_NULL; port_dev->parent.tx_complete = RT_NULL; rt_list_init(&port_dev->node); port_dev->port_id = port_id; port_dev->need_destroy = RT_FALSE; port_dev->rx_notify = RT_TRUE; port_dev->console = virtio_console_dev; port_dev->queue_rx_index = VIRTIO_CONSOLE_PORT_QUEUE_INDEX(port_dev->port_id, VIRTIO_CONSOLE_QUEUE_DATA_RX); port_dev->queue_tx_index = VIRTIO_CONSOLE_PORT_QUEUE_INDEX(port_dev->port_id, VIRTIO_CONSOLE_QUEUE_DATA_TX); port_dev->queue_rx = &virtio_dev->queues[port_dev->queue_rx_index]; port_dev->queue_tx = &virtio_dev->queues[port_dev->queue_tx_index]; #ifdef RT_USING_SMP rt_spin_lock_init(&port_dev->spinlock_rx); rt_spin_lock_init(&port_dev->spinlock_tx); #endif rt_snprintf(dev_name, RT_NAME_MAX, "vport%dp%d", virtio_console_dev->console_id, port_id); if (rt_device_register((rt_device_t)port_dev, dev_name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX) != RT_EOK) { rt_free(port_dev); return -RT_ERROR; } if (prev_port_dev != RT_NULL) { rt_list_insert_after(&prev_port_dev->node, &port_dev->node); } else { /* Port0 */ rt_list_insert_after(&virtio_console_dev->port_head, &port_dev->node); } virtio_console_dev->port_nr++; return RT_EOK; } static void virtio_console_port_destroy(struct virtio_console_device *virtio_console_dev, struct port_device *port_dev) { struct virtio_console_control set_ctrl; set_ctrl.id = port_dev->port_id; set_ctrl.event = VIRTIO_CONSOLE_PORT_OPEN; set_ctrl.value = 0; virtio_console_send_ctrl(virtio_console_dev, &set_ctrl); virtio_console_dev->port_nr--; rt_list_remove(&port_dev->node); rt_device_unregister((rt_device_t)port_dev); rt_free(port_dev); } static rt_err_t virtio_console_port_init(rt_device_t dev) { rt_uint16_t id; rt_uint16_t idx[VIRTIO_CONSOLE_QUEUE_SIZE]; rt_uint16_t rx_queue_index, tx_queue_index; struct port_device *port_dev = (struct port_device *)dev; struct virtio_console_device *virtio_console_dev = port_dev->console; struct virtio_device *virtio_dev = &virtio_console_dev->virtio_dev; struct virtq *queue_rx, *queue_tx; rx_queue_index = VIRTIO_CONSOLE_PORT_QUEUE_INDEX(port_dev->port_id, VIRTIO_CONSOLE_QUEUE_DATA_RX); tx_queue_index = VIRTIO_CONSOLE_PORT_QUEUE_INDEX(port_dev->port_id, VIRTIO_CONSOLE_QUEUE_DATA_TX); queue_rx = &virtio_dev->queues[rx_queue_index]; queue_tx = &virtio_dev->queues[tx_queue_index]; virtio_alloc_desc_chain(virtio_dev, rx_queue_index, queue_rx->num, idx); virtio_alloc_desc_chain(virtio_dev, tx_queue_index, queue_tx->num, idx); for (id = 0; id < queue_rx->num; ++id) { void *addr = &port_dev->info[id].rx_char; virtio_fill_desc(virtio_dev, rx_queue_index, id, VIRTIO_VA2PA(addr), sizeof(char), VIRTQ_DESC_F_WRITE, 0); queue_rx->avail->ring[id] = id; } rt_hw_dsb(); queue_rx->avail->flags = 0; queue_rx->avail->idx = queue_rx->num; queue_rx->used_idx = queue_rx->used->idx; queue_tx->avail->flags = VIRTQ_AVAIL_F_NO_INTERRUPT; queue_tx->avail->idx = 0; virtio_queue_notify(virtio_dev, rx_queue_index); if (virtio_has_feature(virtio_dev, VIRTIO_CONSOLE_F_MULTIPORT)) { struct virtio_console_control set_ctrl; set_ctrl.id = VIRTIO_CONSOLE_PORT_BAD_ID; set_ctrl.event = VIRTIO_CONSOLE_DEVICE_READY; set_ctrl.value = 1; virtio_console_send_ctrl(virtio_console_dev, &set_ctrl); } return RT_EOK; } static rt_err_t virtio_console_port_open(rt_device_t dev, rt_uint16_t oflag) { struct port_device *port_dev = (struct port_device *)dev; if (port_dev->port_id == 0 && virtio_has_feature(&port_dev->console->virtio_dev, VIRTIO_CONSOLE_F_MULTIPORT)) { /* Port0 is reserve in multiport */ return -RT_ERROR; } port_dev->rx_notify = RT_TRUE; return RT_EOK; } static rt_err_t virtio_console_port_close(rt_device_t dev) { struct port_device *port_dev = (struct port_device *)dev; if (port_dev->need_destroy) { virtio_console_port_destroy(port_dev->console, port_dev); /* * We released the device memory in virtio_console_port_destroy, * rt_device_close has not finished yet, make the return value * to empty so that rt_device_close will not access the device memory. */ return -RT_EEMPTY; } port_dev->rx_notify = RT_FALSE; return RT_EOK; } static rt_ssize_t virtio_console_port_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { rt_off_t i = 0; rt_uint16_t id; rt_uint32_t len; struct port_device *port_dev = (struct port_device *)dev; struct virtio_device *virtio_dev = &port_dev->console->virtio_dev; rt_uint32_t queue_rx_index = port_dev->queue_rx_index; struct virtq *queue_rx = port_dev->queue_rx; #ifdef RT_USING_SMP rt_base_t level = rt_spin_lock_irqsave(&port_dev->spinlock_rx); #endif while (i < size) { if (queue_rx->used_idx == queue_rx->used->idx) { break; } rt_hw_dsb(); id = queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].id; len = queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].len; if (len > sizeof(char)) { rt_kprintf("%s: Receive buffer's size = %u is too big!\n", port_dev->parent.parent.name, len); len = sizeof(char); } *((char *)buffer + i) = port_dev->info[id].rx_char; queue_rx->used_idx++; virtio_submit_chain(virtio_dev, queue_rx_index, id); virtio_queue_notify(virtio_dev, queue_rx_index); i += len; } #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&port_dev->spinlock_rx, level); #endif size = i; return size; } static rt_ssize_t virtio_console_port_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { char ch = 0; rt_off_t i = 0; rt_uint16_t id; struct port_device *port_dev = (struct port_device *)dev; struct virtio_device *virtio_dev = &port_dev->console->virtio_dev; rt_uint32_t queue_tx_index = port_dev->queue_tx_index; struct virtq *queue_tx = port_dev->queue_tx; #ifdef RT_USING_SMP rt_base_t level = rt_spin_lock_irqsave(&port_dev->spinlock_tx); #endif while (i < size || ch == '\r') { id = queue_tx->avail->idx % queue_tx->num; /* Keep the way until 'new line' are unified */ if (ch != '\r') { ch = *((const char *)buffer + i); } else { i -= sizeof(char); } port_dev->info[id].tx_char = ch; ch = (ch == '\n' ? '\r' : 0); virtio_free_desc(virtio_dev, queue_tx_index, id); virtio_fill_desc(virtio_dev, queue_tx_index, id, VIRTIO_VA2PA(&port_dev->info[id].tx_char), sizeof(char), 0, 0); virtio_submit_chain(virtio_dev, queue_tx_index, id); virtio_queue_notify(virtio_dev, queue_tx_index); virtio_alloc_desc(virtio_dev, queue_tx_index); i += sizeof(char); } #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&port_dev->spinlock_tx, level); #endif return size; } static rt_err_t virtio_console_port_control(rt_device_t dev, int cmd, void *args) { rt_err_t status = RT_EOK; struct port_device *port_dev = (struct port_device *)dev; switch (cmd) { case RT_DEVICE_CTRL_NOTIFY_SET: if (args == RT_NULL) { status = -RT_ERROR; break; } rt_memcpy(&port_dev->rx_notify_helper, args, sizeof(port_dev->rx_notify_helper)); break; case RT_DEVICE_CTRL_CLR_INT: /* Disable RX */ port_dev->rx_notify = RT_FALSE; break; case RT_DEVICE_CTRL_SET_INT: /* Enable RX */ port_dev->rx_notify = RT_TRUE; break; case VIRTIO_DEVICE_CTRL_CONSOLE_PORT_DESTROY: { port_dev->need_destroy = RT_TRUE; port_dev->rx_notify = RT_FALSE; } break; default: status = -RT_EINVAL; break; } return status; } static rt_err_t virtio_console_init(rt_device_t dev) { struct virtio_console_device *virtio_console_dev = (struct virtio_console_device *)dev; struct virtio_device *virtio_dev = &virtio_console_dev->virtio_dev; if (virtio_has_feature(virtio_dev, VIRTIO_CONSOLE_F_MULTIPORT)) { rt_uint16_t id; rt_uint16_t idx[VIRTIO_CONSOLE_QUEUE_SIZE]; struct virtq *queue_ctrl_rx, *queue_ctrl_tx; queue_ctrl_rx = &virtio_dev->queues[VIRTIO_CONSOLE_QUEUE_CTRL_RX]; queue_ctrl_tx = &virtio_dev->queues[VIRTIO_CONSOLE_QUEUE_CTRL_TX]; virtio_alloc_desc_chain(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_RX, queue_ctrl_rx->num, idx); virtio_alloc_desc_chain(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_TX, queue_ctrl_tx->num, idx); for (id = 0; id < queue_ctrl_rx->num; ++id) { void *addr = &virtio_console_dev->info[id].rx_ctrl; virtio_fill_desc(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_RX, id, VIRTIO_VA2PA(addr), sizeof(struct virtio_console_control), VIRTQ_DESC_F_WRITE, 0); queue_ctrl_rx->avail->ring[id] = id; } rt_hw_dsb(); for (id = 0; id < queue_ctrl_tx->num; ++id) { virtio_console_dev->info[id].tx_ctrl_paddr = VIRTIO_VA2PA(&virtio_console_dev->info[id].tx_ctrl); } queue_ctrl_rx->avail->flags = 0; queue_ctrl_rx->avail->idx = queue_ctrl_rx->num; queue_ctrl_rx->used_idx = queue_ctrl_rx->used->idx; queue_ctrl_tx->avail->flags = VIRTQ_AVAIL_F_NO_INTERRUPT; queue_ctrl_tx->avail->idx = 0; virtio_queue_notify(virtio_dev, VIRTIO_CONSOLE_QUEUE_CTRL_RX); } return virtio_console_port_create(virtio_console_dev); } static rt_err_t virtio_console_control(rt_device_t dev, int cmd, void *args) { rt_err_t status = RT_EOK; struct virtio_console_device *virtio_console_dev = (struct virtio_console_device *)dev; switch (cmd) { case VIRTIO_DEVICE_CTRL_CONSOLE_PORT_CREATE: status = virtio_console_port_create(virtio_console_dev); break; default: status = -RT_EINVAL; break; } return status; } #ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops virtio_console_ops = { virtio_console_init, RT_NULL, RT_NULL, RT_NULL, RT_NULL, virtio_console_control }; #endif static void virtio_console_isr(int irqno, void *param) { rt_uint32_t id; rt_uint32_t len; struct port_device *port_dev; struct virtio_console_device *virtio_console_dev = (struct virtio_console_device *)param; struct virtio_device *virtio_dev = &virtio_console_dev->virtio_dev; const char *dev_name = virtio_console_dev->parent.parent.name; #ifdef RT_USING_SMP rt_base_t level = rt_spin_lock_irqsave(&virtio_dev->spinlock); #endif virtio_interrupt_ack(virtio_dev); rt_hw_dsb(); do { struct virtq *queue_rx; struct virtio_console_control *ctrl, set_ctrl; if (!virtio_has_feature(virtio_dev, VIRTIO_CONSOLE_F_MULTIPORT)) { break; } queue_rx = &virtio_dev->queues[VIRTIO_CONSOLE_QUEUE_CTRL_RX]; if (queue_rx->used_idx == queue_rx->used->idx) { break; } rt_hw_dsb(); id = queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].id; len = queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].len; queue_rx->used_idx++; if (len != sizeof(struct virtio_console_control)) { rt_kprintf("%s: Invalid ctrl!\n", dev_name); break; } ctrl = &virtio_console_dev->info[id].rx_ctrl; switch (ctrl->event) { case VIRTIO_CONSOLE_PORT_ADD: { set_ctrl.id = ctrl->id; set_ctrl.event = VIRTIO_CONSOLE_PORT_READY; set_ctrl.value = 1; #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&virtio_dev->spinlock, level); #endif virtio_console_send_ctrl(virtio_console_dev, &set_ctrl); #ifdef RT_USING_SMP level = rt_spin_lock_irqsave(&virtio_dev->spinlock); #endif } break; case VIRTIO_CONSOLE_PORT_REMOVE: break; case VIRTIO_CONSOLE_RESIZE: break; case VIRTIO_CONSOLE_PORT_OPEN: { set_ctrl.id = ctrl->id; set_ctrl.event = VIRTIO_CONSOLE_PORT_OPEN; set_ctrl.value = 1; #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&virtio_dev->spinlock, level); #endif virtio_console_send_ctrl(virtio_console_dev, &set_ctrl); #ifdef RT_USING_SMP level = rt_spin_lock_irqsave(&virtio_dev->spinlock); #endif } break; case VIRTIO_CONSOLE_PORT_NAME: break; default: rt_kprintf("%s: Unsupport ctrl[id: %d, event: %d, value: %d]!\n", dev_name, ctrl->id, ctrl->event, ctrl->value); break; } } while (0); #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&virtio_dev->spinlock, level); #endif rt_list_for_each_entry(port_dev, &virtio_console_dev->port_head, node) { rt_uint32_t queue_rx_index = port_dev->queue_rx_index; struct virtq *queue_rx = port_dev->queue_rx; #ifdef RT_USING_SMP rt_base_t level = rt_spin_lock_irqsave(&port_dev->spinlock_rx); #endif if (queue_rx->used_idx != queue_rx->used->idx) { rt_hw_dsb(); id = queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].id; len = queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].len; if (port_dev->rx_notify) { #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&port_dev->spinlock_rx, level); #endif /* Will call virtio_console_port_read to inc used_idx */ if (port_dev->parent.rx_indicate != RT_NULL) { port_dev->parent.rx_indicate(&port_dev->parent, len); } if (port_dev->rx_notify_helper.notify != RT_NULL) { port_dev->rx_notify_helper.notify(port_dev->rx_notify_helper.dev); } #ifdef RT_USING_SMP level = rt_spin_lock_irqsave(&port_dev->spinlock_rx); #endif } else { queue_rx->used_idx++; virtio_submit_chain(virtio_dev, queue_rx_index, id); virtio_queue_notify(virtio_dev, queue_rx_index); } } #ifdef RT_USING_SMP rt_spin_unlock_irqrestore(&port_dev->spinlock_rx, level); #endif } } rt_err_t rt_virtio_console_init(rt_ubase_t *mmio_base, rt_uint32_t irq) { int i; rt_size_t queues_num; static int dev_no = 0; char dev_name[RT_NAME_MAX]; struct virtio_device *virtio_dev; struct virtio_console_device *virtio_console_dev; RT_ASSERT(RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR > 0); virtio_console_dev = rt_malloc(sizeof(struct virtio_console_device)); if (virtio_console_dev == RT_NULL) { goto _alloc_fail; } virtio_dev = &virtio_console_dev->virtio_dev; virtio_dev->irq = irq; virtio_dev->mmio_base = mmio_base; virtio_console_dev->config = (struct virtio_console_config *)virtio_dev->mmio_config->config; #ifdef RT_USING_SMP rt_spin_lock_init(&virtio_dev->spinlock); #endif virtio_reset_device(virtio_dev); virtio_status_acknowledge_driver(virtio_dev); virtio_dev->mmio_config->driver_features = virtio_dev->mmio_config->device_features & ~( (1 << VIRTIO_F_RING_EVENT_IDX) | (1 << VIRTIO_F_RING_INDIRECT_DESC)); virtio_status_driver_ok(virtio_dev); if (!virtio_has_feature(virtio_dev, VIRTIO_CONSOLE_F_MULTIPORT)) { virtio_console_dev->max_port_nr = 1; queues_num = 2; } else { if (virtio_console_dev->config->max_nr_ports > RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR) { virtio_console_dev->max_port_nr = RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR; virtio_console_dev->config->max_nr_ports = virtio_console_dev->max_port_nr; } else { virtio_console_dev->max_port_nr = virtio_console_dev->config->max_nr_ports; } queues_num = VIRTIO_CONSOLE_PORT_QUEUE_INDEX(virtio_console_dev->max_port_nr, VIRTIO_CONSOLE_QUEUE_DATA_RX); } if (virtio_queues_alloc(virtio_dev, queues_num) != RT_EOK) { goto _alloc_fail; } for (i = 0; i < virtio_dev->queues_num; ++i) { if (virtio_queue_init(virtio_dev, i, VIRTIO_CONSOLE_QUEUE_SIZE) != RT_EOK) { for (; i >= 0; --i) { virtio_queue_destroy(virtio_dev, i); } goto _alloc_fail; } } virtio_console_dev->parent.type = RT_Device_Class_Char; #ifdef RT_USING_DEVICE_OPS virtio_console_dev->parent.ops = &virtio_console_ops; #else virtio_console_dev->parent.init = virtio_console_init; virtio_console_dev->parent.open = RT_NULL; virtio_console_dev->parent.close = RT_NULL; virtio_console_dev->parent.read = RT_NULL; virtio_console_dev->parent.write = RT_NULL; virtio_console_dev->parent.control = virtio_console_control; #endif virtio_console_dev->parent.rx_indicate = RT_NULL; virtio_console_dev->parent.tx_complete = RT_NULL; virtio_console_dev->console_id = dev_no; virtio_console_dev->port_nr = 0; rt_list_init(&virtio_console_dev->port_head); rt_snprintf(dev_name, RT_NAME_MAX, "virtio-console%d", dev_no++); rt_hw_interrupt_install(irq, virtio_console_isr, virtio_console_dev, dev_name); rt_hw_interrupt_umask(irq); return rt_device_register((rt_device_t)virtio_console_dev, dev_name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX); _alloc_fail: if (virtio_console_dev != RT_NULL) { virtio_queues_free(virtio_dev); rt_free(virtio_console_dev); } return -RT_ENOMEM; } #endif /* RT_USING_VIRTIO_CONSOLE */