Просмотр исходного кода

[bsp/qemu-virt64-aarch64] Add VirtIO-Console and GPIO driver

GuEe_GUI 3 лет назад
Родитель
Сommit
92b9cfa36a

+ 5 - 1
bsp/qemu-virt64-aarch64/.config

@@ -69,6 +69,7 @@ CONFIG_RT_USING_CONSOLE=y
 CONFIG_RT_CONSOLEBUF_SIZE=256
 CONFIG_RT_CONSOLE_DEVICE_NAME="uart0"
 CONFIG_RT_VER_NUM=0x50000
+# CONFIG_RT_USING_DM is not set
 CONFIG_ARCH_CPU_64BIT=y
 CONFIG_RT_USING_CACHE=y
 # CONFIG_RT_USING_CPU_FFS is not set
@@ -192,9 +193,10 @@ CONFIG_RT_USING_RTC=y
 # CONFIG_RT_USING_WIFI is not set
 CONFIG_RT_USING_VIRTIO=y
 CONFIG_RT_USING_VIRTIO10=y
-CONFIG_RT_USING_VIRTIO_QUEUE_MAX_NR=4
 CONFIG_RT_USING_VIRTIO_BLK=y
 CONFIG_RT_USING_VIRTIO_NET=y
+CONFIG_RT_USING_VIRTIO_CONSOLE=y
+CONFIG_RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR=4
 CONFIG_RT_USING_VIRTIO_GPU=y
 CONFIG_RT_USING_VIRTIO_INPUT=y
 
@@ -736,8 +738,10 @@ CONFIG_BSP_USING_UART=y
 CONFIG_RT_USING_UART0=y
 CONFIG_BSP_USING_RTC=y
 # CONFIG_BSP_USING_ALARM is not set
+CONFIG_BSP_USING_PIN=y
 CONFIG_BSP_USING_VIRTIO_BLK=y
 CONFIG_BSP_USING_VIRTIO_NET=y
+CONFIG_BSP_USING_VIRTIO_CONSOLE=y
 CONFIG_BSP_USING_VIRTIO_GPU=y
 CONFIG_BSP_USING_VIRTIO_INPUT=y
 CONFIG_BSP_USING_GIC=y

+ 7 - 0
bsp/qemu-virt64-aarch64/README.md

@@ -46,14 +46,21 @@ hello rt-thread
 msh />
 ```
 
+如果需要使用VirtIO-Console,请在新终端使用以下命令连接控制台:
+```
+telnet 127.0.0.1 4321
+```
+
 ## 4. 支持情况
 
 | 驱动 | 支持情况  |  备注  |
 | ------ | ----  | :------:  |
 | UART | 支持 | UART0 |
 | RTC  | 支持 | - |
+| GPIO | 支持 | - |
 | VIRTIO BLK | 支持 | - |
 | VIRTIO NET | 支持 | - |
+| VIRTIO Console | 支持 | - |
 | VIRTIO GPU | 支持 | 2D |
 | VIRTIO Input | 支持 | Keyboard, Mouse, Tablet |
 

+ 33 - 0
bsp/qemu-virt64-aarch64/applications/console.c

@@ -0,0 +1,33 @@
+/*
+ * 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 <rtthread.h>
+
+#include <virtio_console.h>
+
+static int console_init()
+{
+    rt_err_t status = RT_EOK;
+    rt_device_t device = rt_device_find("virtio-console0");
+
+    if (device != RT_NULL && rt_device_open(device, 0) == RT_EOK)
+    {
+        /* Create vport0p1 */
+        status = rt_device_control(device, VIRTIO_DEVICE_CTRL_CONSOLE_PORT_CREATE, RT_NULL);
+    }
+
+    if (device != RT_NULL)
+    {
+        rt_device_close(device);
+    }
+
+    return status;
+}
+INIT_ENV_EXPORT(console_init);

+ 33 - 0
bsp/qemu-virt64-aarch64/applications/pin.c

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2022-6-30      GuEe-GUI       first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#ifdef RT_USING_PIN
+
+void qemu_gpio3_key_poweroff(void *args)
+{
+    rt_kprintf("\nYou power off the machine.\n");
+
+    rt_hw_cpu_shutdown();
+}
+
+static int pin_init()
+{
+    rt_pin_attach_irq(3, PIN_IRQ_MODE_FALLING, qemu_gpio3_key_poweroff, RT_NULL);
+    rt_pin_irq_enable(3, RT_TRUE);
+
+    return 0;
+}
+INIT_ENV_EXPORT(pin_init);
+
+#endif /* RT_USING_PIN */

+ 11 - 0
bsp/qemu-virt64-aarch64/drivers/Kconfig

@@ -27,6 +27,11 @@ menu "AARCH64 qemu virt64 configs"
                 default n
         endif
 
+    config BSP_USING_PIN
+        bool "Using PIN"
+        select RT_USING_PIN
+        default y
+
     config BSP_USING_VIRTIO_BLK
         bool "Using VirtIO BLK"
         select RT_USING_VIRTIO
@@ -39,6 +44,12 @@ menu "AARCH64 qemu virt64 configs"
         select RT_USING_VIRTIO_NET
         default y
 
+    config BSP_USING_VIRTIO_CONSOLE
+        bool "Using VirtIO Console"
+        select RT_USING_VIRTIO
+        select RT_USING_VIRTIO_CONSOLE
+        default y
+
     config BSP_USING_VIRTIO_GPU
         bool "Using VirtIO GPU"
         select RT_USING_VIRTIO

+ 320 - 0
bsp/qemu-virt64-aarch64/drivers/drv_gpio.c

@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2022-6-30      GuEe-GUI       first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <board.h>
+
+#include "drv_gpio.h"
+
+#ifdef BSP_USING_PIN
+
+#define GPIODIR 0x400
+#define GPIOIS  0x404
+#define GPIOIBE 0x408
+#define GPIOIEV 0x40c
+#define GPIOIE  0x410
+#define GPIORIS 0x414
+#define GPIOMIS 0x418
+#define GPIOIC  0x41c
+
+#define BIT(x)  (1 << (x))
+
+#define PL061_GPIO_NR   8
+
+static struct pl061
+{
+#ifdef RT_USING_SMP
+    struct rt_spinlock spinlock;
+#endif
+    void (*(hdr[PL061_GPIO_NR]))(void *args);
+    void *args[PL061_GPIO_NR];
+} _pl061;
+
+static rt_ubase_t pl061_gpio_base = PL061_GPIO_BASE;
+
+rt_inline rt_uint8_t pl061_read8(rt_ubase_t offset)
+{
+    return HWREG8(pl061_gpio_base + offset);
+}
+
+rt_inline void pl061_write8(rt_ubase_t offset, rt_uint8_t value)
+{
+    HWREG8(pl061_gpio_base + offset) = value;
+}
+
+static void pl061_pin_mode(struct rt_device *device, rt_base_t pin, rt_base_t mode)
+{
+    int value;
+    rt_uint8_t gpiodir;
+
+#ifdef RT_USING_SMP
+    rt_base_t level;
+#endif
+
+    if (pin < 0 || pin >= PL061_GPIO_NR)
+    {
+        return;
+    }
+
+#ifdef RT_USING_SMP
+    level = rt_spin_lock_irqsave(&_pl061.spinlock);
+#endif
+
+    switch (mode)
+    {
+    case PIN_MODE_OUTPUT:
+
+        value = !!pl061_read8((BIT(pin + 2)));
+
+        pl061_write8(BIT(pin + 2), 0 << pin);
+        gpiodir = pl061_read8(GPIODIR);
+        gpiodir |= BIT(pin);
+        pl061_write8(GPIODIR, gpiodir);
+
+        /*
+         * gpio value is set again, because pl061 doesn't allow to set value of
+         * a gpio pin before configuring it in OUT mode.
+         */
+        pl061_write8((BIT(pin + 2)), value << pin);
+
+        break;
+    case PIN_MODE_INPUT:
+
+        gpiodir = pl061_read8(GPIODIR);
+        gpiodir &= ~(BIT(pin));
+        pl061_write8(GPIODIR, gpiodir);
+
+        break;
+    }
+
+#ifdef RT_USING_SMP
+    rt_spin_unlock_irqrestore(&_pl061.spinlock, level);
+#endif
+}
+
+static void pl061_pin_write(struct rt_device *device, rt_base_t pin, rt_base_t value)
+{
+    pl061_write8(BIT(pin + 2), !!value << pin);
+}
+
+static int pl061_pin_read(struct rt_device *device, rt_base_t pin)
+{
+    return !!pl061_read8((BIT(pin + 2)));
+}
+
+static rt_err_t pl061_pin_attach_irq(struct rt_device *device, rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args)
+{
+    rt_uint8_t gpiois, gpioibe, gpioiev;
+    rt_uint8_t bit = BIT(mode);
+#ifdef RT_USING_SMP
+    rt_base_t level;
+#endif
+
+    if (pin < 0 || pin >= PL061_GPIO_NR)
+    {
+        return -RT_EINVAL;
+    }
+
+#ifdef RT_USING_SMP
+    level = rt_spin_lock_irqsave(&_pl061.spinlock);
+#endif
+
+    gpioiev = pl061_read8(GPIOIEV);
+    gpiois  = pl061_read8(GPIOIS);
+    gpioibe = pl061_read8(GPIOIBE);
+
+    if (mode == PIN_IRQ_MODE_HIGH_LEVEL || pin == PIN_IRQ_MODE_LOW_LEVEL)
+    {
+        rt_bool_t polarity = (mode == PIN_IRQ_MODE_HIGH_LEVEL);
+
+        /* Disable edge detection */
+        gpioibe &= ~bit;
+        /* Enable level detection */
+        gpiois |= bit;
+
+        /* Select polarity */
+        if (polarity)
+        {
+            gpioiev |= bit;
+        }
+        else
+        {
+            gpioiev &= ~bit;
+        }
+    }
+    else if (mode == PIN_IRQ_MODE_RISING_FALLING)
+    {
+        /* Disable level detection */
+        gpiois &= ~bit;
+        /* Select both edges, setting this makes GPIOEV be ignored */
+        gpioibe |= bit;
+    }
+    else if (mode == PIN_IRQ_MODE_RISING || mode == PIN_IRQ_MODE_FALLING)
+    {
+        rt_bool_t rising = (mode == PIN_IRQ_MODE_RISING);
+
+        /* Disable level detection */
+        gpiois &= ~bit;
+        /* Clear detection on both edges */
+        gpioibe &= ~bit;
+
+        /* Select edge */
+        if (rising)
+        {
+            gpioiev |= bit;
+        }
+        else
+        {
+            gpioiev &= ~bit;
+        }
+    }
+    else
+    {
+        /* No trigger: disable everything */
+        gpiois  &= ~bit;
+        gpioibe &= ~bit;
+        gpioiev &= ~bit;
+    }
+
+    pl061_write8(GPIOIS, gpiois);
+    pl061_write8(GPIOIBE, gpioibe);
+    pl061_write8(GPIOIEV, gpioiev);
+
+    _pl061.hdr[pin] = hdr;
+    _pl061.args[pin] = args;
+
+#ifdef RT_USING_SMP
+    rt_spin_unlock_irqrestore(&_pl061.spinlock, level);
+#endif
+
+    return RT_EOK;
+}
+
+static rt_err_t pl061_pin_detach_irq(struct rt_device *device, rt_int32_t pin)
+{
+    if (pin < 0 || pin >= PL061_GPIO_NR)
+    {
+        return -RT_EINVAL;
+    }
+
+    _pl061.hdr[pin] = RT_NULL;
+    _pl061.args[pin] = RT_NULL;
+
+    return RT_EOK;
+}
+
+static rt_err_t pl061_pin_irq_enable(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled)
+{
+    rt_uint8_t mask = BIT(pin);
+    rt_uint8_t gpioie;
+
+#ifdef RT_USING_SMP
+    rt_base_t level;
+#endif
+
+    if (pin < 0 || pin >= PL061_GPIO_NR)
+    {
+        return -RT_EINVAL;
+    }
+
+#ifdef RT_USING_SMP
+    level = rt_spin_lock_irqsave(&_pl061.spinlock);
+#endif
+
+    if (enabled)
+    {
+        gpioie = pl061_read8(GPIOIE) | mask;
+    }
+    else
+    {
+        gpioie = pl061_read8(GPIOIE) & ~mask;
+    }
+
+    pl061_write8(GPIOIE, gpioie);
+
+#ifdef RT_USING_SMP
+    rt_spin_unlock_irqrestore(&_pl061.spinlock, level);
+#endif
+
+    return RT_EOK;
+}
+
+static const struct rt_pin_ops ops =
+{
+    pl061_pin_mode,
+    pl061_pin_write,
+    pl061_pin_read,
+    pl061_pin_attach_irq,
+    pl061_pin_detach_irq,
+    pl061_pin_irq_enable,
+    RT_NULL,
+};
+
+static void rt_hw_gpio_isr(int irqno, void *param)
+{
+    rt_uint8_t mask;
+    unsigned long pending;
+
+#ifdef RT_USING_SMP
+    rt_base_t level;
+#endif
+
+    pending = pl061_read8(GPIOMIS);
+
+    if (pending)
+    {
+        rt_base_t pin;
+
+        for (pin = 0; pin < PL061_GPIO_NR; ++pin)
+        {
+            if (pending & BIT(pin))
+            {
+                mask |= BIT(pin);
+
+                if (_pl061.hdr[pin] != RT_NULL)
+                {
+                    _pl061.hdr[pin](_pl061.args[pin]);
+                }
+            }
+        }
+    }
+
+#ifdef RT_USING_SMP
+    level = rt_spin_lock_irqsave(&_pl061.spinlock);
+#endif
+
+    pl061_write8(GPIOIC, mask);
+
+#ifdef RT_USING_SMP
+    rt_spin_unlock_irqrestore(&_pl061.spinlock, level);
+#endif
+}
+
+int rt_hw_gpio_init(void)
+{
+#ifdef RT_USING_SMP
+    rt_spin_lock_init(&_pl061.spinlock);
+#endif
+
+#ifdef RT_USING_LWP
+    pl061_gpio_base = (rt_size_t)rt_ioremap((void *)pl061_gpio_base, PL061_GPIO_SIZE);
+#endif
+
+    rt_device_pin_register("gpio", &ops, RT_NULL);
+    rt_hw_interrupt_install(PL061_GPIO_IRQNUM, rt_hw_gpio_isr, RT_NULL, "gpio");
+    rt_hw_interrupt_umask(PL061_GPIO_IRQNUM);
+
+    return 0;
+}
+INIT_DEVICE_EXPORT(rt_hw_gpio_init);
+
+#endif /* BSP_USING_PIN */

+ 16 - 0
bsp/qemu-virt64-aarch64/drivers/drv_gpio.h

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2006-2021, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author         Notes
+ * 2022-6-30      GuEe-GUI       first version
+ */
+
+#ifndef DRV_GPIO_H__
+#define DRV_GPIO_H__
+
+int rt_hw_gpio_init(void);
+
+#endif

+ 6 - 0
bsp/qemu-virt64-aarch64/drivers/drv_virtio.c

@@ -17,6 +17,9 @@
 #ifdef BSP_USING_VIRTIO_NET
 #include <virtio_net.h>
 #endif
+#ifdef BSP_USING_VIRTIO_CONSOLE
+#include <virtio_console.h>
+#endif
 #ifdef BSP_USING_VIRTIO_GPU
 #include <virtio_gpu.h>
 #endif
@@ -34,6 +37,9 @@ static virtio_device_init_handler virtio_device_init_handlers[] =
 #ifdef BSP_USING_VIRTIO_NET
     [VIRTIO_DEVICE_ID_NET]      = rt_virtio_net_init,
 #endif
+#ifdef BSP_USING_VIRTIO_CONSOLE
+    [VIRTIO_DEVICE_ID_CONSOLE]  = rt_virtio_console_init,
+#endif
 #ifdef BSP_USING_VIRTIO_GPU
     [VIRTIO_DEVICE_ID_GPU]      = rt_virtio_gpu_init,
 #endif

+ 5 - 0
bsp/qemu-virt64-aarch64/drivers/virt.h

@@ -33,6 +33,11 @@ extern rt_mmu_info mmu_info;
 #define PL031_RTC_SIZE      0x00001000
 #define PL031_RTC_IRQNUM    (32 + 2)
 
+/* GPIO */
+#define PL061_GPIO_BASE     0x09030000
+#define PL061_GPIO_SIZE     0x00001000
+#define PL061_GPIO_IRQNUM   (32 + 7)
+
 /* VirtIO */
 #define VIRTIO_MMIO_BASE    0x0a000000
 #define VIRTIO_MMIO_SIZE    0x00000200

+ 2 - 1
bsp/qemu-virt64-aarch64/qemu-graphic.bat

@@ -9,4 +9,5 @@ qemu-system-aarch64 -M virt,gic-version=2 -cpu cortex-a53 -smp 4 -kernel rtthrea
 -device virtio-gpu-device,xres=800,yres=600,bus=virtio-mmio-bus.2 ^
 -device virtio-keyboard-device,bus=virtio-mmio-bus.3 ^
 -device virtio-mouse-device,bus=virtio-mmio-bus.4 ^
--device virtio-tablet-device,bus=virtio-mmio-bus.5
+-device virtio-tablet-device,bus=virtio-mmio-bus.5 ^
+-device virtio-serial-device -chardev socket,host=127.0.0.1,port=4321,server=on,wait=off,telnet=on,id=console0 -device virtserialport,chardev=console0

+ 2 - 1
bsp/qemu-virt64-aarch64/qemu-graphic.sh

@@ -7,4 +7,5 @@ qemu-system-aarch64 -M virt,gic-version=2 -cpu cortex-a53 -smp 4 -kernel rtthrea
 -device virtio-gpu-device,xres=800,yres=600,bus=virtio-mmio-bus.2 \
 -device virtio-keyboard-device,bus=virtio-mmio-bus.3 \
 -device virtio-mouse-device,bus=virtio-mmio-bus.4 \
--device virtio-tablet-device,bus=virtio-mmio-bus.5
+-device virtio-tablet-device,bus=virtio-mmio-bus.5 \
+-device virtio-serial-device -chardev socket,host=127.0.0.1,port=4321,server=on,wait=off,telnet=on,id=console0 -device virtserialport,chardev=console0

+ 2 - 1
bsp/qemu-virt64-aarch64/qemu.bat

@@ -5,4 +5,5 @@ qemu-img create -f raw sd.bin 64M
 :run
 qemu-system-aarch64 -M virt,gic-version=2 -cpu cortex-a53 -smp 4 -kernel rtthread.bin -nographic ^
 -drive if=none,file=sd.bin,format=raw,id=blk0 -device virtio-blk-device,drive=blk0,bus=virtio-mmio-bus.0 ^
--netdev user,id=net0 -device virtio-net-device,netdev=net0,bus=virtio-mmio-bus.1
+-netdev user,id=net0 -device virtio-net-device,netdev=net0,bus=virtio-mmio-bus.1 ^
+-device virtio-serial-device -chardev socket,host=127.0.0.1,port=4321,server=on,wait=off,telnet=on,id=console0 -device virtserialport,chardev=console0

+ 2 - 1
bsp/qemu-virt64-aarch64/qemu.sh

@@ -3,4 +3,5 @@ dd if=/dev/zero of=sd.bin bs=1024 count=65536
 fi
 qemu-system-aarch64 -M virt,gic-version=2 -cpu cortex-a53 -smp 4 -kernel rtthread.bin -nographic \
 -drive if=none,file=sd.bin,format=raw,id=blk0 -device virtio-blk-device,drive=blk0,bus=virtio-mmio-bus.0 \
--netdev user,id=net0 -device virtio-net-device,netdev=net0,bus=virtio-mmio-bus.1
+-netdev user,id=net0 -device virtio-net-device,netdev=net0,bus=virtio-mmio-bus.1 \
+-device virtio-serial-device -chardev socket,host=127.0.0.1,port=4321,server=on,wait=off,telnet=on,id=console0 -device virtserialport,chardev=console0

+ 4 - 1
bsp/qemu-virt64-aarch64/rtconfig.h

@@ -122,9 +122,10 @@
 #define RT_USING_RTC
 #define RT_USING_VIRTIO
 #define RT_USING_VIRTIO10
-#define RT_USING_VIRTIO_QUEUE_MAX_NR 4
 #define RT_USING_VIRTIO_BLK
 #define RT_USING_VIRTIO_NET
+#define RT_USING_VIRTIO_CONSOLE
+#define RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR 4
 #define RT_USING_VIRTIO_GPU
 #define RT_USING_VIRTIO_INPUT
 
@@ -291,8 +292,10 @@
 #define BSP_USING_UART
 #define RT_USING_UART0
 #define BSP_USING_RTC
+#define BSP_USING_PIN
 #define BSP_USING_VIRTIO_BLK
 #define BSP_USING_VIRTIO_NET
+#define BSP_USING_VIRTIO_CONSOLE
 #define BSP_USING_VIRTIO_GPU
 #define BSP_USING_VIRTIO_INPUT
 #define BSP_USING_GIC

+ 10 - 4
components/drivers/Kconfig

@@ -666,10 +666,6 @@ menuconfig RT_USING_VIRTIO
                 bool "VirtIO v1.0"
         endchoice
 
-        config RT_USING_VIRTIO_QUEUE_MAX_NR
-        int "Number of queues for VirtIO devices"
-        default 4
-
         config RT_USING_VIRTIO_BLK
             bool "Using VirtIO BLK"
             default y
@@ -678,6 +674,16 @@ menuconfig RT_USING_VIRTIO
             bool "Using VirtIO NET"
             default y
 
+        menuconfig RT_USING_VIRTIO_CONSOLE
+            bool "Using VirtIO Console"
+            default y
+
+            if RT_USING_VIRTIO_CONSOLE
+                config RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR
+                    int "Max number of port in VirtIO Console"
+                    default 4
+            endif
+
         config RT_USING_VIRTIO_GPU
             bool "Using VirtIO GPU"
             default y

+ 34 - 2
components/drivers/virtio/virtio.c

@@ -54,6 +54,38 @@ void virtio_interrupt_ack(struct virtio_device *dev)
     }
 }
 
+rt_bool_t virtio_has_feature(struct virtio_device *dev, rt_uint32_t feature_bit)
+{
+    _virtio_dev_check(dev);
+
+    return !!(dev->mmio_config->device_features & (1UL << feature_bit));
+}
+
+rt_err_t virtio_queues_alloc(struct virtio_device *dev, rt_size_t queues_num)
+{
+    _virtio_dev_check(dev);
+
+    dev->queues = rt_malloc(sizeof(struct virtq) * queues_num);
+
+    if (dev->queues != RT_NULL)
+    {
+        dev->queues_num = queues_num;
+
+        return RT_EOK;
+    }
+
+    return -RT_ENOMEM;
+}
+
+void virtio_queues_free(struct virtio_device *dev)
+{
+    if (dev->queues != RT_NULL)
+    {
+        dev->queues_num = 0;
+        rt_free(dev->queues);
+    }
+}
+
 rt_err_t virtio_queue_init(struct virtio_device *dev, rt_uint32_t queue_index, rt_size_t ring_size)
 {
     int i;
@@ -173,7 +205,7 @@ rt_uint16_t virtio_alloc_desc(struct virtio_device *dev, rt_uint32_t queue_index
 
     _virtio_dev_check(dev);
 
-    RT_ASSERT(queue_index < RT_USING_VIRTIO_QUEUE_MAX_NR);
+    RT_ASSERT(queue_index < dev->queues_num);
 
     queue = &dev->queues[queue_index];
 
@@ -204,7 +236,7 @@ void virtio_free_desc(struct virtio_device *dev, rt_uint32_t queue_index, rt_uin
 
     queue = &dev->queues[queue_index];
 
-    RT_ASSERT(queue_index + 1 < RT_USING_VIRTIO_QUEUE_MAX_NR);
+    RT_ASSERT(queue_index < dev->queues_num);
     RT_ASSERT(!queue->free[desc_index]);
 
     queue->desc[desc_index].addr = 0;

+ 6 - 5
components/drivers/virtio/virtio.h

@@ -28,10 +28,6 @@
 #define RT_USING_VIRTIO_VERSION 0x1
 #endif
 
-#ifndef RT_USING_VIRTIO_QUEUE_MAX_NR
-#define RT_USING_VIRTIO_QUEUE_MAX_NR 4
-#endif
-
 #include <virtio_mmio.h>
 #include <virtio_queue.h>
 
@@ -117,7 +113,9 @@ struct virtio_device
 {
     rt_uint32_t irq;
 
-    struct virtq queues[RT_USING_VIRTIO_QUEUE_MAX_NR];
+    struct virtq *queues;
+    rt_size_t queues_num;
+
     union
     {
         rt_ubase_t *mmio_base;
@@ -137,7 +135,10 @@ void virtio_reset_device(struct virtio_device *dev);
 void virtio_status_acknowledge_driver(struct virtio_device *dev);
 void virtio_status_driver_ok(struct virtio_device *dev);
 void virtio_interrupt_ack(struct virtio_device *dev);
+rt_bool_t virtio_has_feature(struct virtio_device *dev, rt_uint32_t feature_bit);
 
+rt_err_t virtio_queues_alloc(struct virtio_device *dev, rt_size_t queues_num);
+void virtio_queues_free(struct virtio_device *dev);
 rt_err_t virtio_queue_init(struct virtio_device *dev, rt_uint32_t queue_index, rt_size_t ring_size);
 void virtio_queue_destroy(struct virtio_device *dev, rt_uint32_t queue_index);
 void virtio_queue_notify(struct virtio_device *dev, rt_uint32_t queue_index);

+ 23 - 8
components/drivers/virtio/virtio_blk.c

@@ -17,10 +17,6 @@
 
 #include <virtio_blk.h>
 
-#if RT_USING_VIRTIO_QUEUE_MAX_NR < 1
-#error "VirtIO BLK uses at least 1 virtio queues"
-#endif
-
 static void virtio_blk_rw(struct virtio_blk_device *virtio_blk_dev, rt_off_t pos, void *buffer, int flags)
 {
     rt_uint16_t idx[3];
@@ -33,6 +29,14 @@ static void virtio_blk_rw(struct virtio_blk_device *virtio_blk_dev, rt_off_t pos
     /* Allocate 3 descriptors */
     while (virtio_alloc_desc_chain(virtio_dev, 0, 3, idx))
     {
+#ifdef RT_USING_SMP
+        rt_spin_unlock_irqrestore(&virtio_dev->spinlock, level);
+#endif
+        rt_thread_yield();
+
+#ifdef RT_USING_SMP
+        level = rt_spin_lock_irqsave(&virtio_dev->spinlock);
+#endif
     }
 
     virtio_blk_dev->info[idx[0]].status = 0xff;
@@ -156,7 +160,6 @@ static void virtio_blk_isr(int irqno, void *param)
 
         /* Done with buffer */
         virtio_blk_dev->info[id].valid = RT_FALSE;
-        rt_thread_yield();
 
         queue->used_idx++;
     }
@@ -206,12 +209,15 @@ rt_err_t rt_virtio_blk_init(rt_ubase_t *mmio_base, rt_uint32_t irq)
     /* Tell device that feature negotiation is complete and we're completely ready */
     virtio_status_driver_ok(virtio_dev);
 
+    if (virtio_queues_alloc(virtio_dev, 1) != RT_EOK)
+    {
+        goto _alloc_fail;
+    }
+
     /* Initialize queue 0 */
     if (virtio_queue_init(virtio_dev, 0, VIRTIO_BLK_QUEUE_RING_SIZE) != RT_EOK)
     {
-        rt_free(virtio_blk_dev);
-
-        return -RT_ENOMEM;
+        goto _alloc_fail;
     }
 
     virtio_blk_dev->parent.type = RT_Device_Class_Block;
@@ -232,5 +238,14 @@ rt_err_t rt_virtio_blk_init(rt_ubase_t *mmio_base, rt_uint32_t irq)
     rt_hw_interrupt_umask(irq);
 
     return rt_device_register((rt_device_t)virtio_blk_dev, dev_name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_REMOVABLE);
+
+_alloc_fail:
+
+    if (virtio_blk_dev != RT_NULL)
+    {
+        virtio_queues_free(virtio_dev);
+        rt_free(virtio_blk_dev);
+    }
+    return -RT_ENOMEM;
 }
 #endif /* RT_USING_VIRTIO_BLK */

+ 737 - 0
components/drivers/virtio/virtio_console.c

@@ -0,0 +1,737 @@
+/*
+ * 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 <rthw.h>
+#include <rtthread.h>
+#include <cpuport.h>
+
+#ifdef RT_USING_VIRTIO_CONSOLE
+
+#include <virtio_console.h>
+
+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
+    {
+        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_create(struct virtio_console_device *virtio_console_dev,
+    const struct rt_device_ops *ops)
+{
+    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;
+    port_dev->parent.ops  = ops;
+
+    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;
+
+    /* Can't use by others, just support only one */
+    if (port_dev->parent.ref_count > 1)
+    {
+        return -RT_EBUSY;
+    }
+
+    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_size_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_size_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_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;
+}
+
+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
+};
+
+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, &virtio_console_port_ops);
+}
+
+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, &virtio_console_port_ops);
+        break;
+    default:
+        status = -RT_EINVAL;
+        break;
+    }
+
+    return status;
+}
+
+const static struct rt_device_ops virtio_console_ops =
+{
+    virtio_console_init,
+    RT_NULL,
+    RT_NULL,
+    RT_NULL,
+    RT_NULL,
+    virtio_console_control
+};
+
+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->parent.rx_indicate != RT_NULL && port_dev->rx_notify)
+            {
+            #ifdef RT_USING_SMP
+                rt_spin_unlock_irqrestore(&port_dev->spinlock_rx, level);
+            #endif
+                /* rx_indicate call virtio_console_port_read to inc used_idx */
+                port_dev->parent.rx_indicate(&port_dev->parent, len);
+
+            #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;
+    virtio_console_dev->parent.ops  = &virtio_console_ops;
+
+    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 */

+ 97 - 0
components/drivers/virtio/virtio_console.h

@@ -0,0 +1,97 @@
+/*
+ * 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
+ */
+
+#ifndef __VIRTIO_CONSOLE_H__
+#define __VIRTIO_CONSOLE_H__
+
+#include <rtdef.h>
+
+#include <virtio.h>
+
+#ifndef RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR
+#define RT_USING_VIRTIO_CONSOLE_PORT_MAX_NR 4
+#endif
+
+#define VIRTIO_CONSOLE_QUEUE_DATA_RX    0
+#define VIRTIO_CONSOLE_QUEUE_DATA_TX    1
+#define VIRTIO_CONSOLE_QUEUE_CTRL_RX    2
+#define VIRTIO_CONSOLE_QUEUE_CTRL_TX    3
+#define VIRTIO_CONSOLE_QUEUE_SIZE       64
+
+/* Every port has data rx & tx, and port0 has ctrl rx & tx in multiport */
+#define VIRTIO_CONSOLE_PORT_QUEUE_INDEX(id, queue)  ((id) * 2 + (!!(id)) * 2 + (queue))
+
+#define VIRTIO_CONSOLE_PORT_BAD_ID      (~(rt_uint32_t)0)
+
+#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? */
+
+struct virtio_console_config
+{
+    rt_uint16_t cols;
+    rt_uint16_t rows;
+    rt_uint32_t max_nr_ports;
+    rt_uint32_t emerg_wr;
+} __attribute__((packed));
+
+struct virtio_console_control
+{
+    rt_uint32_t id;     /* Port number */
+    rt_uint16_t event;  /* The kind of control event */
+    rt_uint16_t value;  /* Extra information for the event */
+};
+
+enum virtio_console_control_event
+{
+    VIRTIO_CONSOLE_DEVICE_READY = 0,
+    VIRTIO_CONSOLE_PORT_ADD,
+    VIRTIO_CONSOLE_PORT_REMOVE,
+    VIRTIO_CONSOLE_PORT_READY,
+    VIRTIO_CONSOLE_CONSOLE_PORT,
+    VIRTIO_CONSOLE_RESIZE,
+    VIRTIO_CONSOLE_PORT_OPEN,
+    VIRTIO_CONSOLE_PORT_NAME,
+};
+
+struct virtio_console_resize
+{
+    rt_uint16_t cols;
+    rt_uint16_t rows;
+};
+
+struct virtio_console_device
+{
+    struct rt_device parent;
+
+    struct virtio_device virtio_dev;
+
+    rt_uint32_t console_id;
+    rt_size_t port_nr;
+    rt_size_t max_port_nr;
+    rt_list_t port_head;
+    struct virtio_console_config *config;
+
+    struct
+    {
+        rt_ubase_t tx_ctrl_paddr;
+        struct virtio_console_control rx_ctrl, tx_ctrl;
+    } info[VIRTIO_CONSOLE_QUEUE_SIZE];
+};
+
+rt_err_t rt_virtio_console_init(rt_ubase_t *mmio_base, rt_uint32_t irq);
+
+enum
+{
+    VIRTIO_DEVICE_CTRL_CONSOLE_PORT_CREATE  = 0x20,
+    VIRTIO_DEVICE_CTRL_CONSOLE_PORT_DESTROY,
+};
+
+#endif /* __VIRTIO_CONSOLE_H__ */

+ 25 - 7
components/drivers/virtio/virtio_gpu.c

@@ -16,10 +16,6 @@
 
 #include <virtio_gpu.h>
 
-#if RT_USING_VIRTIO_QUEUE_MAX_NR < 2
-#error "VirtIO BLK uses at least 2 virtio queues"
-#endif
-
 static struct virtio_gpu_device *_primary_virtio_gpu_dev = RT_NULL;
 
 static rt_ubase_t _pixel_format_convert(rt_ubase_t format, rt_bool_t to_virtio_gpu_format)
@@ -78,6 +74,14 @@ static void virtio_gpu_ctrl_send_command(struct virtio_gpu_device *virtio_gpu_de
 
     while (virtio_alloc_desc_chain(virtio_dev, VIRTIO_GPU_QUEUE_CTRL, 2, idx))
     {
+#ifdef RT_USING_SMP
+        rt_spin_unlock_irqrestore(&virtio_dev->spinlock, level);
+#endif
+        rt_thread_yield();
+
+#ifdef RT_USING_SMP
+        level = rt_spin_lock_irqsave(&virtio_dev->spinlock);
+#endif
     }
 
     rt_hw_dsb();
@@ -130,7 +134,17 @@ static void virtio_gpu_cursor_send_command(struct virtio_gpu_device *virtio_gpu_
     rt_base_t level = rt_spin_lock_irqsave(&virtio_dev->spinlock);
 #endif
 
-    id = virtio_alloc_desc(virtio_dev, VIRTIO_GPU_QUEUE_CURSOR);
+    while ((id = virtio_alloc_desc(virtio_dev, VIRTIO_GPU_QUEUE_CURSOR)) == VIRTQ_INVALID_DESC_ID)
+    {
+#ifdef RT_USING_SMP
+        rt_spin_unlock_irqrestore(&virtio_dev->spinlock, level);
+#endif
+        rt_thread_yield();
+
+#ifdef RT_USING_SMP
+        level = rt_spin_lock_irqsave(&virtio_dev->spinlock);
+#endif
+    }
 
     addr = &virtio_gpu_dev->info[id].cursor_cmd;
     virtio_gpu_dev->info[id].cursor_valid = RT_TRUE;
@@ -812,7 +826,6 @@ static void virtio_gpu_isr(int irqno, void *param)
         id = queue_ctrl->used->ring[queue_ctrl->used_idx % queue_ctrl->num].id;
 
         virtio_gpu_dev->info[id].ctrl_valid = RT_FALSE;
-        rt_thread_yield();
 
         queue_ctrl->used_idx++;
     }
@@ -823,7 +836,6 @@ static void virtio_gpu_isr(int irqno, void *param)
         id = queue_cursor->used->ring[queue_cursor->used_idx % queue_cursor->num].id;
 
         virtio_gpu_dev->info[id].cursor_valid = RT_FALSE;
-        rt_thread_yield();
 
         queue_cursor->used_idx++;
     }
@@ -872,6 +884,11 @@ rt_err_t rt_virtio_gpu_init(rt_ubase_t *mmio_base, rt_uint32_t irq)
 
     virtio_status_driver_ok(virtio_dev);
 
+    if (virtio_queues_alloc(virtio_dev, 2) != RT_EOK)
+    {
+        goto _alloc_fail;
+    }
+
     if (virtio_queue_init(virtio_dev, VIRTIO_GPU_QUEUE_CTRL, VIRTIO_GPU_QUEUE_SIZE) != RT_EOK)
     {
         goto _alloc_fail;
@@ -911,6 +928,7 @@ _alloc_fail:
 
     if (virtio_gpu_dev != RT_NULL)
     {
+        virtio_queues_free(virtio_dev);
         rt_free(virtio_gpu_dev);
     }
     return -RT_ENOMEM;

+ 6 - 4
components/drivers/virtio/virtio_input.c

@@ -16,10 +16,6 @@
 
 #include <virtio_input.h>
 
-#if RT_USING_VIRTIO_QUEUE_MAX_NR < 2
-#error "VirtIO BLK uses at least 2 virtio queues"
-#endif
-
 static void _set_bit(rt_uint32_t nr, volatile rt_ubase_t *addr)
 {
     rt_ubase_t mask = BIT_MASK(nr);
@@ -378,6 +374,11 @@ rt_err_t rt_virtio_input_init(rt_ubase_t *mmio_base, rt_uint32_t irq)
 
     virtio_status_driver_ok(virtio_dev);
 
+    if (virtio_queues_alloc(virtio_dev, 2) != RT_EOK)
+    {
+        goto _alloc_fail;
+    }
+
     if (virtio_queue_init(virtio_dev, VIRTIO_INPUT_QUEUE_EVENT, VIRTIO_INPUT_EVENT_QUEUE_SIZE) != RT_EOK)
     {
         goto _alloc_fail;
@@ -440,6 +441,7 @@ _alloc_fail:
 
     if (virtio_input_dev != RT_NULL)
     {
+        virtio_queues_free(virtio_dev);
         rt_free(virtio_input_dev);
     }
     return -RT_ENOMEM;

+ 6 - 4
components/drivers/virtio/virtio_net.c

@@ -16,10 +16,6 @@
 
 #include <virtio_net.h>
 
-#if RT_USING_VIRTIO_QUEUE_MAX_NR < 2
-#error "VirtIO BLK uses at least 2 virtio queues"
-#endif
-
 static rt_err_t virtio_net_tx(rt_device_t dev, struct pbuf *p)
 {
     rt_uint16_t id;
@@ -275,6 +271,11 @@ rt_err_t rt_virtio_net_init(rt_ubase_t *mmio_base, rt_uint32_t irq)
 
     virtio_status_driver_ok(virtio_dev);
 
+    if (virtio_queues_alloc(virtio_dev, 2) != RT_EOK)
+    {
+        goto _alloc_fail;
+    }
+
     if (virtio_queue_init(virtio_dev, VIRTIO_NET_QUEUE_RX, VIRTIO_NET_RTX_QUEUE_SIZE) != RT_EOK)
     {
         goto _alloc_fail;
@@ -316,6 +317,7 @@ _alloc_fail:
 
     if (virtio_net_dev != RT_NULL)
     {
+        virtio_queues_free(virtio_dev);
         rt_free(virtio_net_dev);
     }
     return -RT_ENOMEM;