فهرست منبع

[dm][serial] add new serial driver for DM

1. 8250 serila family (OFW, PCI, DWC, early)
2. Virtual serial (by graphic and input)
3. HVC early serial
4. ARM PL011 serial

Signed-off-by: GuEe-GUI <2991707448@qq.com>
GuEe-GUI 1 ماه پیش
والد
کامیت
9a6d515e27

+ 351 - 0
components/drivers/serial/device/8250/8250-dw.c

@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2022-11-22     GuEe-GUI     first version
+ */
+
+/*
+ * The Synopsys DesignWare 8250 has an extra feature whereby it detects if the
+ * LCR is written whilst busy. If it is, then a busy detect interrupt is
+ * raised, the LCR needs to be rewritten and the uart status register read.
+ */
+
+#include <rtthread.h>
+
+#include "8250.h"
+
+/* Offsets for the DesignWare specific registers */
+#define DW_UART_USR                     0x1f    /* UART Status Register */
+#define DW_UART_DMASA                   0xa8    /* DMA Software Ack */
+
+#define OCTEON_UART_USR                 0x27    /* UART Status Register */
+
+#define RZN1_UART_TDMACR                0x10c   /* DMA Control Register Transmit Mode */
+#define RZN1_UART_RDMACR                0x110   /* DMA Control Register Receive Mode */
+
+/* DesignWare specific register fields */
+#define DW_UART_MCR_SIRE                RT_BIT(6)
+
+/* Renesas specific register fields */
+#define RZN1_UART_xDMACR_DMA_EN         RT_BIT(0)
+#define RZN1_UART_xDMACR_1_WORD_BURST   (0 << 1)
+#define RZN1_UART_xDMACR_4_WORD_BURST   (1 << 1)
+#define RZN1_UART_xDMACR_8_WORD_BURST   (2 << 1)
+#define RZN1_UART_xDMACR_BLK_SZ(x)      ((x) << 3)
+
+/* Quirks */
+#define DW_UART_QUIRK_OCTEON            RT_BIT(0)
+#define DW_UART_QUIRK_ARMADA_38X        RT_BIT(1)
+#define DW_UART_QUIRK_SKIP_SET_RATE     RT_BIT(2)
+#define DW_UART_QUIRK_IS_DMA_FC         RT_BIT(3)
+
+struct dw8250_platform_data
+{
+    rt_uint8_t usr_reg;
+    rt_uint32_t cpr_val;
+    rt_uint32_t quirks;
+};
+
+struct dw8250
+{
+    struct serial8250 parent;
+    struct rt_spinlock spinlock;
+
+    struct rt_clk *pclk;
+
+    rt_bool_t uart_16550_compatible;
+    struct dw8250_platform_data *platform_data;
+};
+
+#define to_dw8250(serial8250) rt_container_of(serial8250, struct dw8250, parent)
+
+static void dw8250_check_lcr(struct serial8250 *serial, int value)
+{
+    void *offset = (void *)(serial->base + (UART_LCR << serial->regshift));
+    int tries = 1000;
+
+    /* Make sure LCR write wasn't ignored */
+    while (tries--)
+    {
+        rt_uint32_t lcr = serial->serial_in(serial, UART_LCR);
+
+        if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
+        {
+            break;
+        }
+
+        serial->serial_out(serial, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+        serial->serial_in(serial, UART_RX);
+
+        if (serial->iotype == PORT_MMIO32)
+        {
+            HWREG32(offset) = value;
+        }
+        else if (serial->iotype == PORT_MMIO32BE)
+        {
+            HWREG32(offset) = rt_cpu_to_be32(value);
+        }
+        else
+        {
+            HWREG8(offset) = value;
+        }
+    }
+}
+
+static void dw8250_serial_out32(struct serial8250 *serial, int offset, int value)
+{
+    struct dw8250 *dw8250 = to_dw8250(serial);
+
+    HWREG32(serial->base + (offset << serial->regshift)) = value;
+
+    if (offset == UART_LCR && !dw8250->uart_16550_compatible)
+    {
+        dw8250_check_lcr(serial, value);
+    }
+}
+
+static rt_uint32_t dw8250_serial_in32(struct serial8250 *serial, int offset)
+{
+    return HWREG32(serial->base + (offset << serial->regshift));
+}
+
+static rt_err_t dw8250_isr(struct serial8250 *serial, int irq)
+{
+    unsigned int iir, status;
+    struct dw8250 *dw8250 = to_dw8250(serial);
+
+    iir = serial8250_in(serial, UART_IIR);
+
+    /*
+     * If don't do this in non-DMA mode then the "RX TIMEOUT" interrupt will
+     * fire forever.
+     */
+    if ((iir & 0x3f) == UART_IIR_RX_TIMEOUT)
+    {
+        rt_base_t level = rt_spin_lock_irqsave(&dw8250->spinlock);
+
+        status = serial8250_in(serial, UART_LSR);
+
+        if (!(status & (UART_LSR_DR | UART_LSR_BI)))
+        {
+            serial8250_in(serial, UART_RX);
+        }
+
+        rt_spin_unlock_irqrestore(&dw8250->spinlock, level);
+    }
+
+    if (!(iir & UART_IIR_NO_INT))
+    {
+        rt_hw_serial_isr(&serial->parent, RT_SERIAL_EVENT_RX_IND);
+    }
+
+    if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY)
+    {
+        /* Clear the USR */
+        serial8250_in(serial, dw8250->platform_data->usr_reg);
+    }
+
+    return RT_EOK;
+}
+
+static void dw8250_free_resource(struct dw8250 *dw8250)
+{
+    struct serial8250 *serial = &dw8250->parent;
+
+    if (serial->base)
+    {
+        rt_iounmap(serial->base);
+    }
+
+    if (!rt_is_err_or_null(serial->clk))
+    {
+        rt_clk_disable_unprepare(serial->clk);
+        rt_clk_put(serial->clk);
+    }
+
+    if (!rt_is_err_or_null(dw8250->pclk))
+    {
+        rt_clk_disable_unprepare(dw8250->pclk);
+        rt_clk_put(dw8250->pclk);
+    }
+
+    rt_free(dw8250);
+}
+
+static void dw8250_serial_remove(struct serial8250 *serial)
+{
+    struct dw8250 *dw8250 = to_dw8250(serial);
+
+    dw8250_free_resource(dw8250);
+}
+
+static rt_err_t dw8250_probe(struct rt_platform_device *pdev)
+{
+    rt_err_t err;
+    rt_uint32_t val;
+    struct serial8250 *serial;
+    struct rt_device *dev = &pdev->parent;
+    struct dw8250 *dw8250 = serial8250_alloc(dw8250);
+    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
+
+    if (!dw8250)
+    {
+        return -RT_ENOMEM;
+    }
+
+    serial = &dw8250->parent;
+    serial->base = rt_dm_dev_iomap(dev, 0);
+
+    if (!serial->base)
+    {
+        err = -RT_EIO;
+
+        goto _free_res;
+    }
+
+    serial->irq = rt_dm_dev_get_irq(dev, 0);
+
+    if (serial->irq < 0)
+    {
+        err = serial->irq;
+
+        goto _free_res;
+    }
+
+    serial->clk = rt_clk_get_by_name(dev, "baudclk");
+    dw8250->pclk = rt_clk_get_by_name(dev, "apb_pclk");
+
+    if (rt_is_err_or_null(serial->clk))
+    {
+        if ((err = rt_dm_dev_prop_read_u32(dev, "clock-frequency", &serial->freq)))
+        {
+            goto _free_res;
+        }
+    }
+    else
+    {
+        if ((err = rt_clk_prepare_enable(serial->clk)))
+        {
+            goto _free_res;
+        }
+
+        serial->freq = rt_clk_get_rate(serial->clk);
+    }
+
+    if (rt_is_err(dw8250->pclk))
+    {
+        err = rt_ptr_err(dw8250->pclk);
+
+        goto _free_res;
+    }
+
+    if ((err = rt_clk_prepare_enable(dw8250->pclk)))
+    {
+        goto _free_res;
+    }
+
+    if (!rt_dm_dev_prop_read_u32(dev, "reg-io-width", &val) && val == 4)
+    {
+        serial->iotype = PORT_MMIO32;
+        serial->serial_in = &dw8250_serial_in32;
+        serial->serial_out = &dw8250_serial_out32;
+    }
+
+    dw8250->uart_16550_compatible = rt_dm_dev_prop_read_bool(dev, "snps,uart-16550-compatible");
+    dw8250->platform_data = (struct dw8250_platform_data *)pdev->id->data;
+
+    rt_dm_dev_bind_fwdata(&serial->parent.parent, pdev->parent.ofw_node, &serial->parent);
+
+    dev->user_data = serial;
+
+    serial->parent.ops = &serial8250_uart_ops;
+    serial->parent.config = config;
+    serial->handle_irq = &dw8250_isr;
+    serial->remove = &dw8250_serial_remove;
+    serial->data = dw8250;
+
+    rt_spin_lock_init(&dw8250->spinlock);
+
+    if ((err = serial8250_setup(serial)))
+    {
+        goto _free_res;
+    }
+
+    return RT_EOK;
+
+_free_res:
+    dw8250_free_resource(dw8250);
+
+    return err;
+}
+
+static rt_err_t dw8250_remove(struct rt_platform_device *pdev)
+{
+    struct rt_device *dev = &pdev->parent;
+    struct serial8250 *serial = dev->user_data;
+
+    rt_dm_dev_unbind_fwdata(dev, RT_NULL);
+
+    return serial8250_remove(serial);
+}
+
+static const struct dw8250_platform_data dw8250_dw_apb =
+{
+    .usr_reg = DW_UART_USR,
+};
+
+static const struct dw8250_platform_data dw8250_octeon_3860_data =
+{
+    .usr_reg = OCTEON_UART_USR,
+    .quirks = DW_UART_QUIRK_OCTEON,
+};
+
+static const struct dw8250_platform_data dw8250_armada_38x_data =
+{
+    .usr_reg = DW_UART_USR,
+    .quirks = DW_UART_QUIRK_ARMADA_38X,
+};
+
+static const struct dw8250_platform_data dw8250_renesas_rzn1_data =
+{
+    .usr_reg = DW_UART_USR,
+    .cpr_val = 0x00012f32,
+    .quirks = DW_UART_QUIRK_IS_DMA_FC,
+};
+
+static const struct dw8250_platform_data dw8250_starfive_jh7100_data =
+{
+    .usr_reg = DW_UART_USR,
+    .quirks = DW_UART_QUIRK_SKIP_SET_RATE,
+};
+
+static const struct rt_ofw_node_id dw8250_ofw_ids[] =
+{
+    { .type = "ttyS", .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
+    { .type = "ttyS", .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
+    { .type = "ttyS", .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data },
+    { .type = "ttyS", .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data },
+    { .type = "ttyS", .compatible = "starfive,jh7100-uart", .data = &dw8250_starfive_jh7100_data },
+    { /* sentinel */ }
+};
+
+static struct rt_platform_driver dw8250_driver =
+{
+    .name = "dw-apb-uart",
+    .ids = dw8250_ofw_ids,
+
+    .probe = dw8250_probe,
+    .remove = dw8250_remove,
+};
+
+static int dw8250_drv_register(void)
+{
+    rt_platform_driver_register(&dw8250_driver);
+
+    return 0;
+}
+INIT_PLATFORM_EXPORT(dw8250_drv_register);

+ 211 - 0
components/drivers/serial/device/8250/8250-ofw.c

@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2022-11-09     GuEe-GUI     first version
+ */
+
+#include <rtthread.h>
+
+#include "8250.h"
+
+struct ofw_platform_8250
+{
+    struct serial8250 parent;
+
+    struct rt_reset_control *rstc;
+};
+
+#define to_ofw_platform_8250(serial8250) rt_container_of(serial8250, struct ofw_platform_8250, parent)
+
+static void ofw_platform_8250_free_resource(struct ofw_platform_8250 *op8250)
+{
+    struct serial8250 *serial = &op8250->parent;
+
+    if (serial->base)
+    {
+        rt_iounmap(serial->base);
+    }
+
+    if (!rt_is_err_or_null(serial->clk))
+    {
+        rt_clk_disable_unprepare(serial->clk);
+        rt_clk_put(serial->clk);
+    }
+
+    if (!rt_is_err_or_null(op8250->rstc))
+    {
+        rt_reset_control_put(op8250->rstc);
+    }
+
+    rt_free(op8250);
+}
+
+static void op8250_remove(struct serial8250 *serial)
+{
+    struct ofw_platform_8250 *op8250 = to_ofw_platform_8250(serial);
+
+    ofw_platform_8250_free_resource(op8250);
+}
+
+static rt_err_t ofw_platform_8250_probe(struct rt_platform_device *pdev)
+{
+    rt_err_t err;
+    rt_uint32_t val;
+    struct serial8250 *serial;
+    struct ofw_platform_8250 *op8250;
+    struct rt_ofw_node *np = pdev->parent.ofw_node;
+    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
+
+    if (rt_ofw_prop_read_bool(np, "used-by-rtas"))
+    {
+        return -RT_EBUSY;
+    }
+
+    op8250 = serial8250_alloc(op8250);
+
+    if (!op8250)
+    {
+        return -RT_ENOMEM;
+    }
+
+    serial = &op8250->parent;
+    serial->base = rt_ofw_iomap(np, 0);
+
+    if (!serial->base)
+    {
+        err = -RT_EIO;
+
+        goto _fail;
+    }
+
+    serial->irq = rt_ofw_get_irq(np, 0);
+
+    if (serial->irq < 0)
+    {
+        err = serial->irq;
+
+        goto _fail;
+    }
+
+    if (!rt_ofw_prop_read_u32(np, "clock-frequency", &val))
+    {
+        serial->freq = val;
+    }
+    else
+    {
+        serial->clk = rt_ofw_get_clk(np, 0);
+
+        if (rt_is_err(serial->clk))
+        {
+            err = rt_ptr_err(serial->clk);
+            goto _fail;
+        }
+
+        if ((err = rt_clk_prepare_enable(serial->clk)))
+        {
+            goto _fail;
+        }
+
+        serial->freq = rt_clk_get_rate(serial->clk);
+    }
+
+    if (!rt_ofw_prop_read_u32(np, "reg-shift", &val))
+    {
+        serial->regshift = val;
+    }
+
+    serial->iotype = PORT_MMIO;
+    if (!rt_ofw_prop_read_u32(np, "reg-io-width", &val))
+    {
+        switch (val)
+        {
+        case 1:
+            serial->iotype = PORT_MMIO;
+            break;
+
+        case 2:
+            serial->iotype = PORT_MMIO16;
+            break;
+
+        case 4:
+            serial->iotype = rt_ofw_prop_read_bool(np, "big-endian") ?
+                    PORT_MMIO32BE : PORT_MMIO32;
+            break;
+        }
+    }
+
+    op8250->rstc = rt_ofw_get_reset_control_by_index(np, 0);
+
+    if (rt_is_err(op8250->rstc))
+    {
+        err = rt_ptr_err(op8250->rstc);
+        goto _fail;
+    }
+
+    if (op8250->rstc && (err = rt_reset_control_deassert(op8250->rstc)))
+    {
+        goto _fail;
+    }
+
+    rt_dm_dev_bind_fwdata(&serial->parent.parent, pdev->parent.ofw_node, &serial->parent);
+
+    pdev->parent.user_data = serial;
+
+    serial->parent.ops = &serial8250_uart_ops;
+    serial->parent.config = config;
+    serial->remove = &op8250_remove;
+    serial->data = op8250;
+
+    if ((err = serial8250_setup(serial)))
+    {
+        goto _fail;
+    }
+
+    return RT_EOK;
+
+_fail:
+    ofw_platform_8250_free_resource(op8250);
+
+    return err;
+}
+
+static rt_err_t ofw_platform_8250_remove(struct rt_platform_device *pdev)
+{
+    struct rt_device *dev = &pdev->parent;
+    struct serial8250 *serial = dev->user_data;
+
+    rt_dm_dev_unbind_fwdata(dev, RT_NULL);
+
+    return serial8250_remove(serial);
+}
+
+static const struct rt_ofw_node_id ofw_platform_8250_ofw_ids[] =
+{
+    { .type = "ttyS", .compatible = "ns16550a" },
+    { .type = "ttyS", .compatible = "ns16550" },
+#ifndef RT_SERIAL_8250_BCM7271
+    { .type = "ttyS", .compatible = "brcm,bcm7271-uart" },
+#endif
+    { /* sentinel */ }
+};
+
+static struct rt_platform_driver ofw_platform_8250_driver =
+{
+    .name = "8250-ofw",
+    .ids = ofw_platform_8250_ofw_ids,
+
+    .probe = ofw_platform_8250_probe,
+    .remove = ofw_platform_8250_remove,
+};
+
+static int ofw_platform_8250_drv_register(void)
+{
+    rt_platform_driver_register(&ofw_platform_8250_driver);
+
+    return 0;
+}
+INIT_PLATFORM_EXPORT(ofw_platform_8250_drv_register);

+ 169 - 0
components/drivers/serial/device/8250/8250-pci.c

@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2022-11-09     GuEe-GUI     first version
+ */
+
+#include <rtthread.h>
+
+#include "8250.h"
+
+#define IO_PORT_BAR 0
+
+enum
+{
+    PCI_SERIAL  = 0,
+    PCI_SERIAL2 = 2,
+    PCI_SERIAL4 = 4,
+};
+
+enum
+{
+    SERIAL_8250 = 0,
+    SERIAL_16450,
+    SERIAL_16550,
+    SERIAL_16650,
+    SERIAL_16750,
+    SERIAL_16850,
+    SERIAL_16950,
+};
+
+struct pci_serial
+{
+    struct serial8250 parent;
+    struct rt_spinlock spinlock;
+
+    struct rt_pci_device *pci_dev;
+
+    rt_uint8_t type;
+    rt_uint8_t compat;
+};
+
+#define to_pci_serial(raw) rt_container_of(raw, struct pci_serial, parent)
+
+static rt_err_t pci_serial_isr(struct serial8250 *serial, int irq)
+{
+    rt_uint32_t iir;
+    void *base = serial->base;
+
+    iir = HWREG8(base + UART_IIR);
+
+    if (!(iir & UART_IIR_NO_INT))
+    {
+        rt_hw_serial_isr(&serial->parent, RT_SERIAL_EVENT_RX_IND);
+    }
+
+    return RT_EOK;
+}
+
+static rt_ubase_t pci_serial_clock(struct pci_serial *pci_serial)
+{
+    rt_ubase_t clock = 1843200;
+
+    return clock;
+}
+
+static void pci_serial_free_resource(struct pci_serial *pci_serial)
+{
+    struct serial8250 *serial = &pci_serial->parent;
+
+    if (serial->base)
+    {
+        rt_iounmap(serial->base);
+    }
+
+    rt_free(pci_serial);
+}
+
+static void pci_8250serial_remove(struct serial8250 *serial)
+{
+    struct pci_serial *pci_serial = to_pci_serial(serial);
+
+    rt_pci_irq_mask(pci_serial->pci_dev);
+
+    pci_serial_free_resource(pci_serial);
+}
+
+static rt_err_t pci_serial_probe(struct rt_pci_device *pdev)
+{
+    rt_err_t err;
+    struct serial8250 *serial;
+    struct pci_serial *pci_serial = serial8250_alloc(pci_serial);
+    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
+
+    if (!pci_serial)
+    {
+        return -RT_ENOMEM;
+    }
+
+    serial = &pci_serial->parent;
+    serial->size = pdev->resource[IO_PORT_BAR].size;
+    serial->base = rt_pci_iomap(pdev, IO_PORT_BAR);
+
+    if (!serial->base)
+    {
+        err = -RT_EIO;
+
+        goto _free_res;
+    }
+
+    pdev->parent.user_data = serial;
+
+    serial->irq = pdev->irq;
+
+    serial->parent.ops = &serial8250_uart_ops;
+    serial->parent.config = config;
+    serial->freq = pci_serial_clock(pci_serial);
+    serial->handle_irq = &pci_serial_isr;
+    serial->iotype = PORT_MMIO;
+    serial->remove = &pci_8250serial_remove;
+    serial->data = pci_serial;
+
+    pci_serial->pci_dev = pdev;
+    pci_serial->type = (rt_ubase_t)pdev->id->data;
+    rt_spin_lock_init(&pci_serial->spinlock);
+    rt_pci_read_config_u8(pdev, PCIR_PROGIF, &pci_serial->compat);
+
+    if ((err = serial8250_setup(serial)))
+    {
+        goto _free_res;
+    }
+
+    rt_pci_irq_unmask(pci_serial->pci_dev);
+
+    return RT_EOK;
+
+_free_res:
+    pci_serial_free_resource(pci_serial);
+
+    return err;
+}
+
+static rt_err_t pci_serial_remove(struct rt_pci_device *pdev)
+{
+    struct serial8250 *serial = pdev->parent.user_data;
+
+    return serial8250_remove(serial);
+}
+
+static const struct rt_pci_device_id pci_serial_pci_ids[] =
+{
+    { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_REDHAT, 0x0002), .data = (void *)PCI_SERIAL, },
+    { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_REDHAT, 0x0003), .data = (void *)PCI_SERIAL2, },
+    { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_REDHAT, 0x0004), .data = (void *)PCI_SERIAL4, },
+    { /* sentinel */ }
+};
+
+static struct rt_pci_driver pci_serial_driver =
+{
+    .name = "serial-pci",
+
+    .ids = pci_serial_pci_ids,
+    .probe = pci_serial_probe,
+    .remove = pci_serial_remove,
+};
+RT_PCI_DRIVER_EXPORT(pci_serial_driver);

+ 86 - 0
components/drivers/serial/device/8250/8250.h

@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2022-11-16     GuEe-GUI     first version
+ */
+
+#ifndef __SERIAL_8250_H__
+#define __SERIAL_8250_H__
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <drivers/serial_dm.h>
+
+#include <ioremap.h>
+
+#include "regs.h"
+
+enum
+{
+    PORT_UNKNOWN,
+    PORT_IO,
+    PORT_MMIO,
+    PORT_MMIO16,
+    PORT_MMIO32,
+    PORT_MMIO32BE,
+};
+
+struct serial8250
+{
+    struct rt_serial_device parent;
+    struct rt_clk *clk;
+
+    int irq;
+    void *base;
+    rt_size_t size;
+    rt_uint32_t freq;       /* frequency */
+    rt_uint32_t regshift;   /* reg offset shift */
+    rt_uint8_t iotype;      /* io access style */
+
+    struct rt_spinlock spinlock;
+
+    rt_err_t (*serial_ios)(struct serial8250 *, struct serial_configure *ios);
+    rt_uint32_t (*serial_in)(struct serial8250 *, int offset);
+    void (*serial_out)(struct serial8250 *, int offset, int value);
+    rt_err_t (*serial_dma_enable)(struct serial8250 *, rt_bool_t enabled);
+    rt_ssize_t (*serial_dma_tx)(struct serial8250 *, const rt_uint8_t *buf, rt_size_t size);
+    rt_ssize_t (*serial_dma_rx)(struct serial8250 *, rt_uint8_t *buf, rt_size_t size);
+    rt_err_t (*handle_irq)(struct serial8250 *, int irq);
+
+    /* Free all resource (and parent) by child */
+    void (*remove)(struct serial8250 *);
+    void *data;
+};
+
+#define serial8250_alloc(obj) rt_calloc(1, sizeof(typeof(*obj)))
+#define raw_to_serial8250(raw_serial) rt_container_of(raw_serial, struct serial8250, parent)
+
+rt_err_t serial8250_config(struct serial8250 *serial, const char *options);
+rt_err_t serial8250_setup(struct serial8250 *serial);
+rt_err_t serial8250_remove(struct serial8250 *serial);
+
+rt_uint32_t serial8250_in(struct serial8250 *serial, int offset);
+void serial8250_out(struct serial8250 *serial, int offset, int value);
+void serial8250_dma_tx_done(struct serial8250 *serial);
+void serial8250_dma_rx_done(struct serial8250 *serial, int recv_len);
+rt_err_t serial8250_ios(struct serial8250 *serial, struct serial_configure *cfg);
+
+rt_err_t serial8250_uart_configure(struct rt_serial_device *raw_serial, struct serial_configure *cfg);
+rt_err_t serial8250_uart_control(struct rt_serial_device *raw_serial, int cmd, void *arg);
+int serial8250_uart_putc(struct rt_serial_device *raw_serial, char c);
+int serial8250_uart_getc(struct rt_serial_device *raw_serial);
+rt_ssize_t serial8250_uart_dma_transmit(struct rt_serial_device *raw_serial,
+        rt_uint8_t *buf, rt_size_t size, int direction);
+
+int serial8250_early_putc(struct rt_serial_device *raw_serial, char c);
+rt_err_t serial8250_early_fdt_setup(struct serial8250 *serial, struct rt_fdt_earlycon *con, const char *options);
+
+extern struct serial8250 early_serial8250;
+extern const struct rt_uart_ops serial8250_uart_ops;
+
+#endif /* __SERIAL_8250_H__ */

+ 14 - 0
components/drivers/serial/device/8250/Kconfig

@@ -0,0 +1,14 @@
+menuconfig RT_SERIAL_8250
+    bool "8250 Serial Family"
+    default n
+
+config RT_SERIAL_8250_DW
+    bool "Synopsys DesignWare 8250"
+    depends on RT_SERIAL_8250
+    default n
+
+config RT_SERIAL_8250_PCI
+    bool "8250 PCI/2x/4x"
+    depends on RT_SERIAL_8250
+    depends on RT_USING_PCI
+    default n

+ 23 - 0
components/drivers/serial/device/8250/SConscript

@@ -0,0 +1,23 @@
+from building import *
+
+group   = []
+
+if not GetDepend(['RT_SERIAL_8250']):
+    Return('group')
+
+cwd     = GetCurrentDir()
+CPPPATH = [cwd + '/../../include']
+
+src     = ['core.c', 'early.c']
+
+if GetDepend(['RT_SERIAL_8250_DW']):
+    src += ['8250-dw.c']
+
+if GetDepend(['RT_USING_OFW', 'RT_USING_RESET']):
+    src += ['8250-ofw.c']
+
+if GetDepend(['RT_SERIAL_8250_PCI']):
+    src += ['8250-pci.c']
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+Return('group')

+ 510 - 0
components/drivers/serial/device/8250/core.c

@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2022-11-16     GuEe-GUI     first version
+ */
+
+#include "8250.h"
+
+rt_err_t serial8250_config(struct serial8250 *serial, const char *options)
+{
+    rt_err_t ret = -RT_EINVAL;
+
+    if (serial)
+    {
+        char *arg;
+        rt_bool_t has_iotype = RT_FALSE;
+
+        /*
+         *  uart8250,io,<addr>[,options]
+         *  uart8250,mmio,<addr>[,options]
+         *  uart8250,mmio16,<addr>[,options]
+         *  uart8250,mmio32,<addr>[,options]
+         *  uart8250,mmio32be,<addr>[,options]
+         *  uart8250,0x<addr>[,options]
+         */
+        serial_for_each_args(arg, options)
+        {
+            if (!rt_strcmp(arg, "uart8250"))
+            {
+                ret = RT_EOK;
+                continue;
+            }
+            /* User call error */
+            if (ret)
+            {
+                break;
+            }
+            if (!rt_strncmp(arg, "0x", 2))
+            {
+                serial->base = serial_base_from_args(arg);
+                continue;
+            }
+            if (!has_iotype)
+            {
+                const struct
+                {
+                    char *param;
+                    int type;
+                } iotype_table[] =
+                {
+                    { "io", PORT_IO },
+                    { "mmio", PORT_MMIO },
+                    { "mmio16", PORT_MMIO16 },
+                    { "mmio32", PORT_MMIO32 },
+                    { "mmio32be", PORT_MMIO32BE },
+                };
+
+                serial->iotype = PORT_MMIO32;
+
+                for (int i = 0; i < RT_ARRAY_SIZE(iotype_table); ++i)
+                {
+                    if (!rt_strcmp(arg, iotype_table[i].param))
+                    {
+                        serial->iotype = iotype_table[i].type;
+                        break;
+                    }
+                }
+
+                has_iotype = RT_TRUE;
+                continue;
+            }
+
+            serial->parent.config = serial_cfg_from_args(arg);
+            break;
+        }
+
+        if (!serial->size)
+        {
+            serial->size = 0x1000;
+        }
+    }
+
+    return ret;
+}
+
+static void serial8250_isr(int irqno, void *param)
+{
+    struct serial8250 *serial = (struct serial8250 *)param;
+
+    if (serial->handle_irq)
+    {
+        serial->handle_irq(serial, irqno);
+    }
+    else
+    {
+        rt_hw_serial_isr(&serial->parent, RT_SERIAL_EVENT_RX_IND);
+    }
+}
+
+static rt_err_t serial8250_dma_enable_dummy(struct serial8250 *serial, rt_bool_t enabled)
+{
+    return RT_EOK;
+}
+
+static rt_ssize_t serial8250_dma_tx_dummy(struct serial8250 *serial,
+        const rt_uint8_t *buf, rt_size_t size)
+{
+    return 0;
+}
+
+static rt_ssize_t serial8250_dma_rx_dummy(struct serial8250 *serial,
+        rt_uint8_t *buf, rt_size_t size)
+{
+    return 0;
+}
+
+rt_err_t serial8250_setup(struct serial8250 *serial)
+{
+    rt_uint32_t flags;
+    rt_err_t err = RT_EOK;
+    const char *uart_name;
+    char dev_name[RT_NAME_MAX];
+
+    if (serial)
+    {
+        struct rt_device *dev = &serial->parent.parent;
+
+        rt_spin_lock_init(&serial->spinlock);
+        flags = RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX;
+
+        if (!serial->regshift)
+        {
+            rt_dm_dev_prop_read_u32(dev, "reg-shift", &serial->regshift);
+        }
+
+        if (serial->iotype == PORT_UNKNOWN)
+        {
+            rt_uint32_t width = sizeof(rt_uint32_t);
+
+            rt_dm_dev_prop_read_u32(dev, "reg-io-width", &width);
+
+            switch (width)
+            {
+            case sizeof(rt_uint8_t):
+                serial->iotype = PORT_MMIO;
+                break;
+
+            case sizeof(rt_uint16_t):
+                serial->iotype = PORT_MMIO16;
+                break;
+
+            case sizeof(rt_uint32_t):
+            default:
+                serial->iotype = rt_dm_dev_is_big_endian(dev) ? PORT_MMIO32BE : PORT_MMIO32;
+                break;
+            }
+        }
+
+        serial->serial_in = serial->serial_in ? : &serial8250_in;
+        serial->serial_out = serial->serial_out ? : &serial8250_out;
+        serial->serial_dma_enable = serial->serial_dma_enable ? : &serial8250_dma_enable_dummy;
+
+        if (serial->serial_dma_tx)
+        {
+            flags |= RT_DEVICE_FLAG_DMA_TX;
+        }
+        else
+        {
+            serial->serial_dma_tx = &serial8250_dma_tx_dummy;
+        }
+
+        if (serial->serial_dma_rx)
+        {
+            flags |= RT_DEVICE_FLAG_DMA_RX;
+        }
+        else
+        {
+            serial->serial_dma_rx = &serial8250_dma_rx_dummy;
+        }
+
+        serial_dev_set_name(&serial->parent);
+        uart_name = rt_dm_dev_get_name(&serial->parent.parent);
+
+        rt_hw_serial_register(&serial->parent, uart_name, flags, serial->data);
+
+        rt_snprintf(dev_name, sizeof(dev_name), "%s-8250", uart_name);
+        rt_hw_interrupt_install(serial->irq, serial8250_isr, serial, dev_name);
+    }
+    else
+    {
+        err = -RT_EINVAL;
+    }
+
+    return err;
+}
+
+rt_err_t serial8250_remove(struct serial8250 *serial)
+{
+    rt_err_t err;
+
+    rt_iounmap((void *)serial->base);
+    serial->base = RT_NULL;
+
+    rt_hw_interrupt_mask(serial->irq);
+    rt_pic_detach_irq(serial->irq, serial);
+
+    err = rt_device_unregister(&serial->parent.parent);
+
+    if (!err && serial->remove)
+    {
+        serial->remove(serial);
+    }
+
+    return err;
+}
+
+rt_uint32_t serial8250_in(struct serial8250 *serial, int offset)
+{
+    rt_uint32_t ret = 0;
+    offset <<= serial->regshift;
+
+    switch (serial->iotype)
+    {
+    case PORT_MMIO:
+        ret = HWREG8(serial->base + offset);
+        break;
+    case PORT_MMIO16:
+        ret = HWREG16(serial->base + offset);
+        break;
+    case PORT_MMIO32:
+        ret = HWREG32(serial->base + offset);
+        break;
+    case PORT_MMIO32BE:
+        ret = rt_cpu_to_be32(HWREG32(serial->base + offset));
+        break;
+#ifdef ARCH_SUPPORT_PIO
+    case PORT_IO:
+        ret = inb(serial->base + offset);
+        break;
+#endif
+    default:
+        break;
+    }
+
+    return ret;
+}
+
+void serial8250_out(struct serial8250 *serial, int offset, int value)
+{
+    offset <<= serial->regshift;
+
+    switch (serial->iotype)
+    {
+    case PORT_MMIO:
+        HWREG8(serial->base + offset) = value;
+        break;
+    case PORT_MMIO16:
+        HWREG16(serial->base + offset) = value;
+        break;
+    case PORT_MMIO32:
+        HWREG32(serial->base + offset) = value;
+        break;
+    case PORT_MMIO32BE:
+        HWREG32(serial->base + offset) = rt_cpu_to_be32(value);
+        break;
+#ifdef ARCH_SUPPORT_PIO
+    case PORT_IO:
+        outb(serial->base + offset, value);
+        break;
+#endif
+    default:
+        break;
+    }
+}
+
+void serial8250_dma_tx_done(struct serial8250 *serial)
+{
+    rt_hw_serial_isr(&serial->parent, RT_SERIAL_EVENT_TX_DMADONE);
+}
+
+void serial8250_dma_rx_done(struct serial8250 *serial, int recv_len)
+{
+    rt_hw_serial_isr(&serial->parent, RT_SERIAL_EVENT_RX_DMADONE | (recv_len << 8));
+}
+
+rt_inline rt_ubase_t baudrate_to_divisor(rt_ubase_t freq, rt_ubase_t baudrate)
+{
+    return (freq + baudrate * 8) / (16 * baudrate);
+}
+
+static void serial8250_hw_ios(struct serial8250 *serial, struct serial_configure *cfg)
+{
+    rt_uint32_t ier;
+
+    /* Disable RX interrupt */
+    ier = serial->serial_in(serial, UART_IER);
+    ier &= ~UART_IER_RDI;
+    ier |= UART_IER_UUE;
+    serial->serial_out(serial, UART_IER, ier);
+
+    /* Enable FIFO, Clear FIFO */
+    serial->serial_out(serial, UART_FCR, UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+
+    /* DTR + RTS */
+    serial->serial_out(serial, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2 | UART_MCR_DTR | UART_MCR_RTS);
+
+    if (serial->freq)
+    {
+        rt_uint32_t wlen = cfg->data_bits - DATA_BITS_5 + UART_LCR_WLEN5;
+        rt_uint32_t divisor = baudrate_to_divisor(serial->freq, cfg->baud_rate);
+
+        /* Enable access DLL & DLH */
+        serial->serial_out(serial, UART_LCR, serial->serial_in(serial, UART_LCR) | UART_LCR_DLAB);
+        serial->serial_out(serial, UART_DLL, (divisor & 0xff));
+        serial->serial_out(serial, UART_DLM, (divisor >> 8) & 0xff);
+        /* Clear DLAB bit */
+        serial->serial_out(serial, UART_LCR, serial->serial_in(serial, UART_LCR) & (~UART_LCR_DLAB));
+
+        serial->serial_out(serial, UART_LCR, (serial->serial_in(serial, UART_LCR) & (~wlen)) | wlen);
+        serial->serial_out(serial, UART_LCR, serial->serial_in(serial, UART_LCR) & (~UART_LCR_STOP));
+        serial->serial_out(serial, UART_LCR, serial->serial_in(serial, UART_LCR) & (~UART_LCR_PARITY));
+    }
+
+    /* Enable RX interrupt */
+    ier |= UART_IER_RDI;
+    serial->serial_out(serial, UART_IER, ier);
+}
+
+rt_err_t serial8250_ios(struct serial8250 *serial, struct serial_configure *cfg)
+{
+    rt_err_t err = RT_EOK;
+    rt_uint32_t count = 10000;
+    rt_uint32_t divisor = baudrate_to_divisor(serial->freq, cfg->baud_rate);
+
+    /* Flush FIFO */
+    while (!(serial->serial_in(serial, UART_LSR) & UART_LSR_TEMT) && count--)
+    {
+        rt_hw_cpu_relax();
+    }
+
+    /* Need to increase CLK frequency */
+    if (divisor == 0 && serial->freq)
+    {
+        if (serial->clk)
+        {
+            for (rt_ubase_t round = serial->freq, freq = 0; ; round += round)
+            {
+                if (baudrate_to_divisor(round, cfg->baud_rate))
+                {
+                    freq = rt_clk_round_rate(serial->clk, freq);
+
+                    if (freq < 0)
+                    {
+                        continue;
+                    }
+
+                    rt_clk_disable_unprepare(serial->clk);
+
+                    err = rt_clk_set_rate(serial->clk, freq);
+
+                    if (!err)
+                    {
+                        serial->freq = rt_clk_get_rate(serial->clk);
+                    }
+
+                    rt_clk_prepare_enable(serial->clk);
+
+                    break;
+                }
+            }
+        }
+        else
+        {
+            err = -RT_ENOSYS;
+        }
+    }
+
+    if (!err)
+    {
+        serial8250_hw_ios(serial, cfg);
+    }
+
+    return err;
+}
+
+rt_err_t serial8250_uart_configure(struct rt_serial_device *raw_serial, struct serial_configure *cfg)
+{
+    rt_err_t err;
+    struct serial8250 *serial = raw_to_serial8250(raw_serial);
+
+    if (serial->serial_ios)
+    {
+        err = serial->serial_ios(serial, cfg);
+    }
+    else
+    {
+        err = serial8250_ios(serial, cfg);
+    }
+
+    return err;
+}
+
+rt_err_t serial8250_uart_control(struct rt_serial_device *raw_serial, int cmd, void *arg)
+{
+    rt_uint32_t value;
+    rt_err_t err = RT_EOK;
+    rt_ubase_t ctrl = (rt_ubase_t)arg;
+    struct serial8250 *serial = raw_to_serial8250(raw_serial);
+
+    switch (cmd)
+    {
+    case RT_DEVICE_CTRL_CLR_INT:
+        if (ctrl == RT_DEVICE_FLAG_INT_RX)
+        {
+            /* Disable RX irq */
+            value = serial->serial_in(serial, UART_IER);
+            value &= ~UART_IER_RDI;
+            value |= UART_IER_UUE;
+            serial->serial_out(serial, UART_IER, value);
+            rt_hw_interrupt_mask(serial->irq);
+        }
+        else if (ctrl == RT_DEVICE_FLAG_DMA_RX)
+        {
+            err = serial->serial_dma_enable(serial, RT_FALSE);
+        }
+        break;
+
+    case RT_DEVICE_CTRL_SET_INT:
+        if (ctrl == RT_DEVICE_FLAG_INT_RX)
+        {
+            /* Enable RX irq */
+            value = serial->serial_in(serial, UART_IER);
+            serial->serial_out(serial, UART_IER, value | UART_IER_RDI);
+            rt_hw_interrupt_umask(serial->irq);
+        }
+        else if (ctrl == RT_DEVICE_FLAG_DMA_RX)
+        {
+            err = serial->serial_dma_enable(serial, RT_TRUE);
+        }
+        break;
+
+    case RT_DEVICE_CTRL_CLOSE:
+        err = serial->serial_dma_enable(serial, RT_FALSE);
+        break;
+
+    default:
+        err = -RT_EINVAL;
+        break;
+    }
+
+    return err;
+}
+
+int serial8250_uart_putc(struct rt_serial_device *raw_serial, char c)
+{
+    struct serial8250 *serial = raw_to_serial8250(raw_serial);
+
+    while (!(serial->serial_in(serial, UART_LSR) & UART_LSR_THRE))
+    {
+        rt_hw_cpu_relax();
+    }
+
+    serial->serial_out(serial, UART_TX, c);
+
+    return 1;
+}
+
+int serial8250_uart_getc(struct rt_serial_device *raw_serial)
+{
+    int ch = -1;
+    struct serial8250 *serial = raw_to_serial8250(raw_serial);
+
+    if ((serial->serial_in(serial, UART_LSR) & UART_LSR_DR))
+    {
+        ch = serial->serial_in(serial, UART_RX) & 0xff;
+    }
+
+    return ch;
+}
+
+rt_ssize_t serial8250_uart_dma_transmit(struct rt_serial_device *raw_serial,
+        rt_uint8_t *buf, rt_size_t size, int direction)
+{
+    rt_ssize_t res = 0;
+    struct serial8250 *serial = raw_to_serial8250(raw_serial);
+
+    if (direction == RT_SERIAL_DMA_TX)
+    {
+        res = serial->serial_dma_tx(serial, (const rt_uint8_t *)buf, size);
+    }
+    else if (direction == RT_SERIAL_DMA_RX)
+    {
+        res = serial->serial_dma_rx(serial, buf, size);
+    }
+
+    return res;
+}
+
+const struct rt_uart_ops serial8250_uart_ops =
+{
+    .configure = serial8250_uart_configure,
+    .control = serial8250_uart_control,
+    .putc = serial8250_uart_putc,
+    .getc = serial8250_uart_getc,
+    .dma_transmit = serial8250_uart_dma_transmit,
+};

+ 162 - 0
components/drivers/serial/device/8250/early.c

@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2022-11-16     GuEe-GUI     first version
+ */
+
+#include "8250.h"
+
+struct serial8250 early_serial8250 = { 0 };
+
+static rt_uint32_t serial8250_early_in(struct serial8250 *serial, int offset)
+{
+    return serial8250_in(serial, offset);
+}
+
+static void serial8250_early_out(struct serial8250 *serial, int offset, int value)
+{
+    serial8250_out(serial, offset, value);
+}
+
+int serial8250_early_putc(struct rt_serial_device *raw_serial, char c)
+{
+    if (raw_serial)
+    {
+        /* FIFO and shifting register empty */
+        const int uart_lsr_both_empty = (UART_LSR_TEMT | UART_LSR_THRE);
+        struct serial8250 *serial = rt_container_of(raw_serial, struct serial8250, parent);
+
+        serial8250_early_out(serial, UART_TX, c);
+
+        while ((serial8250_early_in(serial, UART_LSR) & uart_lsr_both_empty) != uart_lsr_both_empty)
+        {
+            rt_hw_cpu_relax();
+        }
+    }
+
+    return 1;
+}
+
+static void init_serial(struct serial8250 *serial)
+{
+    unsigned char c;
+    rt_uint32_t ier, divisor;
+
+    serial8250_early_out(serial, UART_LCR, 0x3);    /* 8n1 */
+    ier = serial8250_early_in(serial, UART_IER);
+    serial8250_early_out(serial, UART_IER, ier & UART_IER_UUE); /* No interrupt */
+    serial8250_early_out(serial, UART_FCR, 0);      /* No fifo */
+    serial8250_early_out(serial, UART_MCR, 0x3);    /* DTR + RTS */
+
+    if (serial->freq)
+    {
+        divisor = RT_DIV_ROUND_CLOSEST(serial->freq, 16 * serial->parent.config.baud_rate);
+        c = serial8250_early_in(serial, UART_LCR);
+        serial8250_early_out(serial, UART_LCR, c | UART_LCR_DLAB);
+        serial8250_early_out(serial, UART_DLL, divisor & 0xff);
+        serial8250_early_out(serial, UART_DLM, (divisor >> 8) & 0xff);
+        serial8250_early_out(serial, UART_LCR, c & ~UART_LCR_DLAB);
+    }
+}
+
+static void serial8250_early_kick(struct rt_fdt_earlycon *con, int why)
+{
+    struct serial8250 *serial = raw_to_serial8250(con->data);
+
+    switch (why)
+    {
+    case FDT_EARLYCON_KICK_UPDATE:
+        serial->base = rt_ioremap((void *)con->mmio, con->size);
+        break;
+
+    case FDT_EARLYCON_KICK_COMPLETED:
+        rt_iounmap(serial->base);
+        break;
+
+    default:
+        break;
+    }
+}
+
+rt_err_t serial8250_early_fdt_setup(struct serial8250 *serial, struct rt_fdt_earlycon *con, const char *options)
+{
+    rt_err_t ret = RT_EOK;
+
+    if (!serial->base && con)
+    {
+        serial8250_config(serial, options);
+        con->mmio = (rt_ubase_t)serial->base;
+        con->size = serial->size;
+    }
+
+    if (serial->base && con)
+    {
+        serial->base = rt_ioremap_early((void *)serial->base, serial->size);
+    }
+
+    if (serial->base && con)
+    {
+        con->console_putc = (typeof(con->console_putc))&serial8250_early_putc;
+        con->console_kick = serial8250_early_kick;
+        con->data = &serial->parent;
+
+        if (!serial->parent.config.baud_rate)
+        {
+            /* Assume the device was initialized, only mask interrupts */
+            rt_uint32_t ier = serial8250_early_in(serial, UART_IER);
+            serial8250_early_out(serial, UART_IER, ier & UART_IER_UUE);
+        }
+        else
+        {
+            init_serial(serial);
+        }
+    }
+    else
+    {
+        ret = -RT_ERROR;
+    }
+
+    return ret;
+}
+
+static void common_init(struct serial8250 *serial, struct rt_fdt_earlycon *con)
+{
+    serial->base = (void *)con->mmio;
+    serial->size = con->size;
+    serial->iotype = PORT_MMIO32;
+}
+
+static rt_err_t common_early_setup(struct rt_fdt_earlycon *con, const char *options)
+{
+    struct serial8250 *serial = &early_serial8250;
+
+    common_init(serial, con);
+    serial->regshift = 2;
+    fdt_getprop_u32(con->fdt, con->nodeoffset, "reg-shift", &serial->regshift, RT_NULL);
+
+    return serial8250_early_fdt_setup(serial, con, options);
+}
+RT_FDT_EARLYCON_EXPORT(bcm2835aux, "uart8250", "brcm,bcm2835-aux-uart", common_early_setup);
+RT_FDT_EARLYCON_EXPORT(tegra20, "uart8250", "nvidia,tegra20-uart", common_early_setup);
+RT_FDT_EARLYCON_EXPORT(dw8250, "uart8250", "snps,dw-apb-uart", common_early_setup);
+RT_FDT_EARLYCON_EXPORT(ns16550a, "uart8250", "ns16550a", common_early_setup);
+RT_FDT_EARLYCON_EXPORT(ns16550, "uart8250", "ns16550", common_early_setup);
+
+#ifdef RT_USING_8250_OMAP
+static rt_err_t omap8250_early_setup(struct rt_fdt_earlycon *con, const char *options)
+{
+    struct serial8250 *serial = &early_serial8250;
+
+    common_init(serial, con);
+    serial->regshift = 2;
+
+    return serial8250_early_fdt_setup(serial, con, options);
+}
+RT_FDT_EARLYCON_EXPORT(omap8250, "uart8250", "ti,omap2-uart", omap8250_early_setup);
+RT_FDT_EARLYCON_EXPORT(omap8250, "uart8250", "ti,omap3-uart", omap8250_early_setup);
+RT_FDT_EARLYCON_EXPORT(omap8250, "uart8250", "ti,omap4-uart", omap8250_early_setup);
+#endif /* RT_USING_8250_OMAP */

+ 365 - 0
components/drivers/serial/device/8250/regs.h

@@ -0,0 +1,365 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef __SERIAL_8250_REGS_H__
+#define __SERIAL_8250_REGS_H__
+
+/*
+ * DLAB=0
+ */
+#define UART_RX                 0    /* In:  Receive buffer */
+#define UART_TX                 0    /* Out: Transmit buffer */
+
+#define UART_IER                1    /* Out: Interrupt Enable Register */
+#define UART_IER_MSI            0x08 /* Enable Modem status interrupt */
+#define UART_IER_RLSI           0x04 /* Enable receiver line status interrupt */
+#define UART_IER_THRI           0x02 /* Enable Transmitter holding register int. */
+#define UART_IER_RDI            0x01 /* Enable receiver data interrupt */
+/*
+ * Sleep mode for ST16650 and TI16750.  For the ST16650, EFR[4]=1
+ */
+#define UART_IERX_SLEEP         0x10 /* Enable sleep mode */
+
+#define UART_IIR                2    /* In:  Interrupt ID Register */
+#define UART_IIR_NO_INT         0x01 /* No interrupts pending */
+#define UART_IIR_ID             0x0e /* Mask for the interrupt ID */
+#define UART_IIR_MSI            0x00 /* Modem status interrupt */
+#define UART_IIR_THRI           0x02 /* Transmitter holding register empty */
+#define UART_IIR_RDI            0x04 /* Receiver data interrupt */
+#define UART_IIR_RLSI           0x06 /* Receiver line status interrupt */
+
+#define UART_IIR_BUSY           0x07 /* DesignWare APB Busy Detect */
+
+#define UART_IIR_RX_TIMEOUT     0x0c /* OMAP RX Timeout interrupt */
+#define UART_IIR_XOFF           0x10 /* OMAP XOFF/Special Character */
+#define UART_IIR_CTS_RTS_DSR    0x20 /* OMAP CTS/RTS/DSR Change */
+
+#define UART_FCR                2    /* Out: FIFO Control Register */
+#define UART_FCR_ENABLE_FIFO    0x01 /* Enable the FIFO */
+#define UART_FCR_CLEAR_RCVR     0x02 /* Clear the RCVR FIFO */
+#define UART_FCR_CLEAR_XMIT     0x04 /* Clear the XMIT FIFO */
+#define UART_FCR_DMA_SELECT     0x08 /* For DMA applications */
+/*
+ * Note: The FIFO trigger levels are chip specific:
+ *      RX:76 = 00  01  10  11  TX:54 = 00  01  10  11
+ * PC16550D:     1   4   8  14          xx  xx  xx  xx
+ * TI16C550A:    1   4   8  14          xx  xx  xx  xx
+ * TI16C550C:    1   4   8  14          xx  xx  xx  xx
+ * ST16C550:     1   4   8  14          xx  xx  xx  xx
+ * ST16C650:     8  16  24  28          16   8  24  30  PORT_16650V2
+ * NS16C552:     1   4   8  14          xx  xx  xx  xx
+ * ST16C654:     8  16  56  60           8  16  32  56  PORT_16654
+ * TI16C750:     1  16  32  56          xx  xx  xx  xx  PORT_16750
+ * TI16C752:     8  16  56  60           8  16  32  56
+ * OX16C950:    16  32 112 120          16  32  64 112  PORT_16C950
+ * Tegra:        1   4   8  14          16   8   4   1  PORT_TEGRA
+ */
+#define UART_FCR_R_TRIG_00      0x00
+#define UART_FCR_R_TRIG_01      0x40
+#define UART_FCR_R_TRIG_10      0x80
+#define UART_FCR_R_TRIG_11      0xc0
+#define UART_FCR_T_TRIG_00      0x00
+#define UART_FCR_T_TRIG_01      0x10
+#define UART_FCR_T_TRIG_10      0x20
+#define UART_FCR_T_TRIG_11      0x30
+
+#define UART_FCR_TRIGGER_MASK   0xC0 /* Mask for the FIFO trigger range */
+#define UART_FCR_TRIGGER_1      0x00 /* Mask for trigger set at 1 */
+#define UART_FCR_TRIGGER_4      0x40 /* Mask for trigger set at 4 */
+#define UART_FCR_TRIGGER_8      0x80 /* Mask for trigger set at 8 */
+#define UART_FCR_TRIGGER_14     0xC0 /* Mask for trigger set at 14 */
+/* 16650 definitions */
+#define UART_FCR6_R_TRIGGER_8   0x00 /* Mask for receive trigger set at 1 */
+#define UART_FCR6_R_TRIGGER_16  0x40 /* Mask for receive trigger set at 4 */
+#define UART_FCR6_R_TRIGGER_24  0x80 /* Mask for receive trigger set at 8 */
+#define UART_FCR6_R_TRIGGER_28  0xC0 /* Mask for receive trigger set at 14 */
+#define UART_FCR6_T_TRIGGER_16  0x00 /* Mask for transmit trigger set at 16 */
+#define UART_FCR6_T_TRIGGER_8   0x10 /* Mask for transmit trigger set at 8 */
+#define UART_FCR6_T_TRIGGER_24  0x20 /* Mask for transmit trigger set at 24 */
+#define UART_FCR6_T_TRIGGER_30  0x30 /* Mask for transmit trigger set at 30 */
+#define UART_FCR7_64BYTE        0x20 /* Go into 64 byte mode (TI16C750 and some Freescale UARTs) */
+
+#define UART_FCR_R_TRIG_SHIFT       6
+#define UART_FCR_R_TRIG_BITS(x)     (((x) & UART_FCR_TRIGGER_MASK) >> UART_FCR_R_TRIG_SHIFT)
+#define UART_FCR_R_TRIG_MAX_STATE   4
+
+#define UART_LCR                    3 /* Out: Line Control Register */
+/*
+ * Note: if the word length is 5 bits (UART_LCR_WLEN5), then setting
+ * UART_LCR_STOP will select 1.5 stop bits, not 2 stop bits.
+ */
+#define UART_LCR_DLAB           0x80 /* Divisor latch access bit */
+#define UART_LCR_SBC            0x40 /* Set break control */
+#define UART_LCR_SPAR           0x20 /* Stick parity (?) */
+#define UART_LCR_EPAR           0x10 /* Even parity select */
+#define UART_LCR_PARITY         0x08 /* Parity Enable */
+#define UART_LCR_STOP           0x04 /* Stop bits: 0=1 bit, 1=2 bits */
+#define UART_LCR_WLEN5          0x00 /* Wordlength: 5 bits */
+#define UART_LCR_WLEN6          0x01 /* Wordlength: 6 bits */
+#define UART_LCR_WLEN7          0x02 /* Wordlength: 7 bits */
+#define UART_LCR_WLEN8          0x03 /* Wordlength: 8 bits */
+
+/*
+ * Access to some registers depends on register access / configuration mode.
+ */
+#define UART_LCR_CONF_MODE_A    UART_LCR_DLAB   /* Configutation mode A */
+#define UART_LCR_CONF_MODE_B    0xBF            /* Configutation mode B */
+
+#define UART_MCR                4    /* Out: Modem Control Register */
+#define UART_MCR_CLKSEL         0x80 /* Divide clock by 4 (TI16C752, EFR[4]=1) */
+#define UART_MCR_TCRTLR         0x40 /* Access TCR/TLR (TI16C752, EFR[4]=1) */
+#define UART_MCR_XONANY         0x20 /* Enable Xon Any (TI16C752, EFR[4]=1) */
+#define UART_MCR_AFE            0x20 /* Enable auto-RTS/CTS (TI16C550C/TI16C750) */
+#define UART_MCR_LOOP           0x10 /* Enable loopback test mode */
+#define UART_MCR_OUT2           0x08 /* Out2 complement */
+#define UART_MCR_OUT1           0x04 /* Out1 complement */
+#define UART_MCR_RTS            0x02 /* RTS complement */
+#define UART_MCR_DTR            0x01 /* DTR complement */
+
+#define UART_LSR                5    /* In:  Line Status Register */
+#define UART_LSR_FIFOE          0x80 /* Fifo error */
+#define UART_LSR_TEMT           0x40 /* Transmitter empty */
+#define UART_LSR_THRE           0x20 /* Transmit-hold-register empty */
+#define UART_LSR_BI             0x10 /* Break interrupt indicator */
+#define UART_LSR_FE             0x08 /* Frame error indicator */
+#define UART_LSR_PE             0x04 /* Parity error indicator */
+#define UART_LSR_OE             0x02 /* Overrun error indicator */
+#define UART_LSR_DR             0x01 /* Receiver data ready */
+#define UART_LSR_BRK_ERROR_BITS (UART_LSR_BI|UART_LSR_FE|UART_LSR_PE|UART_LSR_OE)
+
+#define UART_MSR                6    /* In:  Modem Status Register */
+#define UART_MSR_DCD            0x80 /* Data Carrier Detect */
+#define UART_MSR_RI             0x40 /* Ring Indicator */
+#define UART_MSR_DSR            0x20 /* Data Set Ready */
+#define UART_MSR_CTS            0x10 /* Clear to Send */
+#define UART_MSR_DDCD           0x08 /* Delta DCD */
+#define UART_MSR_TERI           0x04 /* Trailing edge ring indicator */
+#define UART_MSR_DDSR           0x02 /* Delta DSR */
+#define UART_MSR_DCTS           0x01 /* Delta CTS */
+#define UART_MSR_ANY_DELTA      (UART_MSR_DDCD|UART_MSR_TERI|UART_MSR_DDSR|UART_MSR_DCTS)
+
+#define UART_SCR                7    /* I/O: Scratch Register */
+
+/*
+ * DLAB=1
+ */
+#define UART_DLL                0       /* Out: Divisor Latch Low */
+#define UART_DLM                1       /* Out: Divisor Latch High */
+#define UART_DIV_MAX            0xFFFF  /* Max divisor value */
+
+/*
+ * LCR=0xBF (or DLAB=1 for 16C660)
+ */
+#define UART_EFR                2    /* I/O: Extended Features Register */
+#define UART_XR_EFR             9    /* I/O: Extended Features Register (XR17D15x) */
+#define UART_EFR_CTS            0x80 /* CTS flow control */
+#define UART_EFR_RTS            0x40 /* RTS flow control */
+#define UART_EFR_SCD            0x20 /* Special character detect */
+#define UART_EFR_ECB            0x10 /* Enhanced control bit */
+/*
+ * the low four bits control software flow control
+ */
+
+/*
+ * LCR=0xBF, TI16C752, ST16650, ST16650A, ST16654
+ */
+#define UART_XON1               4    /* I/O: Xon character 1 */
+#define UART_XON2               5    /* I/O: Xon character 2 */
+#define UART_XOFF1              6    /* I/O: Xoff character 1 */
+#define UART_XOFF2              7    /* I/O: Xoff character 2 */
+
+/*
+ * EFR[4]=1 MCR[6]=1, TI16C752
+ */
+#define UART_TI752_TCR          6    /* I/O: transmission control register */
+#define UART_TI752_TLR          7    /* I/O: trigger level register */
+
+/*
+ * LCR=0xBF, XR16C85x
+ */
+#define UART_TRG                0   /* FCTR bit 7 selects Rx or Tx In: Fifo count Out: Fifo custom trigger levels */
+/*
+ * These are the definitions for the Programmable Trigger Register
+ */
+#define UART_TRG_1              0x01
+#define UART_TRG_4              0x04
+#define UART_TRG_8              0x08
+#define UART_TRG_16             0x10
+#define UART_TRG_32             0x20
+#define UART_TRG_64             0x40
+#define UART_TRG_96             0x60
+#define UART_TRG_120            0x78
+#define UART_TRG_128            0x80
+
+#define UART_FCTR               1    /* Feature Control Register */
+#define UART_FCTR_RTS_NODELAY   0x00 /* RTS flow control delay */
+#define UART_FCTR_RTS_4DELAY    0x01
+#define UART_FCTR_RTS_6DELAY    0x02
+#define UART_FCTR_RTS_8DELAY    0x03
+#define UART_FCTR_IRDA          0x04 /* IrDa data encode select */
+#define UART_FCTR_TX_INT        0x08 /* Tx interrupt type select */
+#define UART_FCTR_TRGA          0x00 /* Tx/Rx 550 trigger table select */
+#define UART_FCTR_TRGB          0x10 /* Tx/Rx 650 trigger table select */
+#define UART_FCTR_TRGC          0x20 /* Tx/Rx 654 trigger table select */
+#define UART_FCTR_TRGD          0x30 /* Tx/Rx 850 programmable trigger select */
+#define UART_FCTR_SCR_SWAP      0x40 /* Scratch pad register swap */
+#define UART_FCTR_RX            0x00 /* Programmable trigger mode select */
+#define UART_FCTR_TX            0x80 /* Programmable trigger mode select */
+
+/*
+ * LCR=0xBF, FCTR[6]=1
+ */
+#define UART_EMSR               7    /* Extended Mode Select Register */
+#define UART_EMSR_FIFO_COUNT    0x01 /* Rx/Tx select */
+#define UART_EMSR_ALT_COUNT     0x02 /* Alternating count select */
+
+/*
+ * The Intel XScale on-chip UARTs define these bits
+ */
+#define UART_IER_DMAE           0x80 /* DMA Requests Enable */
+#define UART_IER_UUE            0x40 /* UART Unit Enable */
+#define UART_IER_NRZE           0x20 /* NRZ coding Enable */
+#define UART_IER_RTOIE          0x10 /* Receiver Time Out Interrupt Enable */
+
+#define UART_IIR_TOD            0x08 /* Character Timeout Indication Detected */
+
+#define UART_FCR_PXAR1          0x00 /* receive FIFO threshold = 1 */
+#define UART_FCR_PXAR8          0x40 /* receive FIFO threshold = 8 */
+#define UART_FCR_PXAR16         0x80 /* receive FIFO threshold = 16 */
+#define UART_FCR_PXAR32         0xc0 /* receive FIFO threshold = 32 */
+
+/*
+ * These register definitions are for the 16C950
+ */
+#define UART_ASR                0x01 /* Additional Status Register */
+#define UART_RFL                0x03 /* Receiver FIFO level */
+#define UART_TFL                0x04 /* Transmitter FIFO level */
+#define UART_ICR                0x05 /* Index Control Register */
+
+/* The 16950 ICR registers */
+#define UART_ACR                0x00 /* Additional Control Register */
+#define UART_CPR                0x01 /* Clock Prescalar Register */
+#define UART_TCR                0x02 /* Times Clock Register */
+#define UART_CKS                0x03 /* Clock Select Register */
+#define UART_TTL                0x04 /* Transmitter Interrupt Trigger Level */
+#define UART_RTL                0x05 /* Receiver Interrupt Trigger Level */
+#define UART_FCL                0x06 /* Flow Control Level Lower */
+#define UART_FCH                0x07 /* Flow Control Level Higher */
+#define UART_ID1                0x08 /* ID #1 */
+#define UART_ID2                0x09 /* ID #2 */
+#define UART_ID3                0x0A /* ID #3 */
+#define UART_REV                0x0B /* Revision */
+#define UART_CSR                0x0C /* Channel Software Reset */
+#define UART_NMR                0x0D /* Nine-bit Mode Register */
+#define UART_CTR                0xFF
+
+/*
+ * The 16C950 Additional Control Register
+ */
+#define UART_ACR_RXDIS          0x01 /* Receiver disable */
+#define UART_ACR_TXDIS          0x02 /* Transmitter disable */
+#define UART_ACR_DSRFC          0x04 /* DSR Flow Control */
+#define UART_ACR_TLENB          0x20 /* 950 trigger levels enable */
+#define UART_ACR_ICRRD          0x40 /* ICR Read enable */
+#define UART_ACR_ASREN          0x80 /* Additional status enable */
+
+/*
+ * These definitions are for the RSA-DV II/S card, from
+ *
+ * Kiyokazu SUTO <suto@ks-and-ks.ne.jp>
+ */
+
+#define UART_RSA_BASE                   (-8)
+
+#define UART_RSA_MSR                    ((UART_RSA_BASE) + 0) /* I/O: Mode Select Register */
+
+#define UART_RSA_MSR_SWAP               (1 << 0) /* Swap low/high 8 bytes in I/O port addr */
+#define UART_RSA_MSR_FIFO               (1 << 2) /* Enable the external FIFO */
+#define UART_RSA_MSR_FLOW               (1 << 3) /* Enable the auto RTS/CTS flow control */
+#define UART_RSA_MSR_ITYP               (1 << 4) /* Level (1) / Edge triger (0) */
+
+#define UART_RSA_IER                    ((UART_RSA_BASE) + 1) /* I/O: Interrupt Enable Register */
+
+#define UART_RSA_IER_Rx_FIFO_H          (1 << 0) /* Enable Rx FIFO half full int. */
+#define UART_RSA_IER_Tx_FIFO_H          (1 << 1) /* Enable Tx FIFO half full int. */
+#define UART_RSA_IER_Tx_FIFO_E          (1 << 2) /* Enable Tx FIFO empty int. */
+#define UART_RSA_IER_Rx_TOUT            (1 << 3) /* Enable char receive timeout int */
+#define UART_RSA_IER_TIMER              (1 << 4) /* Enable timer interrupt */
+
+#define UART_RSA_SRR                    ((UART_RSA_BASE) + 2) /* IN: Status Read Register */
+
+#define UART_RSA_SRR_Tx_FIFO_NEMP       (1 << 0) /* Tx FIFO is not empty (1) */
+#define UART_RSA_SRR_Tx_FIFO_NHFL       (1 << 1) /* Tx FIFO is not half full (1) */
+#define UART_RSA_SRR_Tx_FIFO_NFUL       (1 << 2) /* Tx FIFO is not full (1) */
+#define UART_RSA_SRR_Rx_FIFO_NEMP       (1 << 3) /* Rx FIFO is not empty (1) */
+#define UART_RSA_SRR_Rx_FIFO_NHFL       (1 << 4) /* Rx FIFO is not half full (1) */
+#define UART_RSA_SRR_Rx_FIFO_NFUL       (1 << 5) /* Rx FIFO is not full (1) */
+#define UART_RSA_SRR_Rx_TOUT            (1 << 6) /* Character reception timeout occurred (1) */
+#define UART_RSA_SRR_TIMER              (1 << 7) /* Timer interrupt occurred */
+
+#define UART_RSA_FRR                    ((UART_RSA_BASE) + 2) /* OUT: FIFO Reset Register */
+
+#define UART_RSA_TIVSR                  ((UART_RSA_BASE) + 3) /* I/O: Timer Interval Value Set Register */
+
+#define UART_RSA_TCR                    ((UART_RSA_BASE) + 4) /* OUT: Timer Control Register */
+
+#define UART_RSA_TCR_SWITCH             (1 << 0) /* Timer on */
+
+/*
+ * The RSA DSV/II board has two fixed clock frequencies.  One is the
+ * standard rate, and the other is 8 times faster.
+ */
+#define SERIAL_RSA_BAUD_BASE            (921600)
+#define SERIAL_RSA_BAUD_BASE_LO         (SERIAL_RSA_BAUD_BASE / 8)
+
+/* Extra registers for TI DA8xx/66AK2x */
+#define UART_DA830_PWREMU_MGMT          12
+
+/* PWREMU_MGMT register bits */
+#define UART_DA830_PWREMU_MGMT_FREE     (1 << 0)  /* Free-running mode */
+#define UART_DA830_PWREMU_MGMT_URRST    (1 << 13) /* Receiver reset/enable */
+#define UART_DA830_PWREMU_MGMT_UTRST    (1 << 14) /* Transmitter reset/enable */
+
+/*
+ * Extra serial register definitions for the internal UARTs
+ * in TI OMAP processors.
+ */
+#define OMAP1_UART1_BASE                0xfffb0000
+#define OMAP1_UART2_BASE                0xfffb0800
+#define OMAP1_UART3_BASE                0xfffb9800
+#define UART_OMAP_MDR1                  0x08    /* Mode definition register */
+#define UART_OMAP_MDR2                  0x09    /* Mode definition register 2 */
+#define UART_OMAP_SCR                   0x10    /* Supplementary control register */
+#define UART_OMAP_SSR                   0x11    /* Supplementary status register */
+#define UART_OMAP_EBLR                  0x12    /* BOF length register */
+#define UART_OMAP_OSC_12M_SEL           0x13    /* OMAP1510 12MHz osc select */
+#define UART_OMAP_MVER                  0x14    /* Module version register */
+#define UART_OMAP_SYSC                  0x15    /* System configuration register */
+#define UART_OMAP_SYSS                  0x16    /* System status register */
+#define UART_OMAP_WER                   0x17    /* Wake-up enable register */
+#define UART_OMAP_TX_LVL                0x1a    /* TX FIFO level register */
+
+/*
+ * These are the definitions for the MDR1 register
+ */
+#define UART_OMAP_MDR1_16X_MODE         0x00    /* UART 16x mode */
+#define UART_OMAP_MDR1_SIR_MODE         0x01    /* SIR mode */
+#define UART_OMAP_MDR1_16X_ABAUD_MODE   0x02    /* UART 16x auto-baud */
+#define UART_OMAP_MDR1_13X_MODE         0x03    /* UART 13x mode */
+#define UART_OMAP_MDR1_MIR_MODE         0x04    /* MIR mode */
+#define UART_OMAP_MDR1_FIR_MODE         0x05    /* FIR mode */
+#define UART_OMAP_MDR1_CIR_MODE         0x06    /* CIR mode */
+#define UART_OMAP_MDR1_DISABLE          0x07    /* Disable (default state) */
+
+/*
+ * These are definitions for the Altera ALTR_16550_F32/F64/F128
+ * Normalized from 0x100 to 0x40 because of shift by 2 (32 bit regs).
+ */
+#define UART_ALTR_AFR                   0x40    /* Additional Features Register */
+#define UART_ALTR_EN_TXFIFO_LW          0x01    /* Enable the TX FIFO Low Watermark */
+#define UART_ALTR_TX_LOW                0x41    /* Tx FIFO Low Watermark */
+
+#endif /* __SERIAL_8250_REGS_H__ */

+ 11 - 0
components/drivers/serial/device/Kconfig

@@ -1 +1,12 @@
+config RT_SERIAL_EARLY_HVC
+    bool "Early HVC"
+    default n
+
+config RT_SERIAL_PL011
+    bool "ARM PL011"
+    default n
+
+rsource "8250/Kconfig"
+rsource "virtual/Kconfig"
+
 osource "$(SOC_DM_SERIAL_DIR)/Kconfig"

+ 6 - 0
components/drivers/serial/device/SConscript

@@ -8,6 +8,12 @@ CPPPATH = [cwd + '/../../include']
 
 src = []
 
+if GetDepend(['RT_SERIAL_EARLY_HVC']):
+    src += ['serial-early-hvc.c']
+
+if GetDepend(['RT_SERIAL_PL011']):
+    src += ['serial-pl011.c']
+
 group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
 
 for d in list:

+ 34 - 0
components/drivers/serial/device/serial-early-hvc.c

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-01-30     GuEe-GUI     first version
+ */
+
+#include <rtdevice.h>
+
+#include <hypercall.h>
+
+static void hvc_early_console_putchar(void *data, char c)
+{
+    rt_hv_console(c);
+}
+
+static rt_err_t hvc_early_setup(struct rt_fdt_earlycon *con, const char *options)
+{
+    rt_err_t err;
+    rt_uint32_t version;
+
+    if ((err = rt_hv_version(&version)))
+    {
+        return err;
+    }
+
+    con->console_putc = hvc_early_console_putchar;
+
+    return err;
+}
+RT_FDT_EARLYCON_EXPORT(hvc, "hvc", "vmrt-thread,hvc-console", hvc_early_setup);

+ 417 - 0
components/drivers/serial/device/serial-pl011.c

@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2006-2022, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2018-05-05     Bernard      The first version
+ * 2022-08-24     GuEe-GUI     add OFW support
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <drivers/serial_dm.h>
+
+#include <cpuport.h>
+
+#include <ioremap.h>
+
+#define PL011_OEIM          RT_BIT(10)  /* overrun error interrupt mask */
+#define PL011_BEIM          RT_BIT(9)   /* break error interrupt mask */
+#define PL011_PEIM          RT_BIT(8)   /* parity error interrupt mask */
+#define PL011_FEIM          RT_BIT(7)   /* framing error interrupt mask */
+#define PL011_RTIM          RT_BIT(6)   /* receive timeout interrupt mask */
+#define PL011_TXIM          RT_BIT(5)   /* transmit interrupt mask */
+#define PL011_RXIM          RT_BIT(4)   /* receive interrupt mask */
+#define PL011_DSRMIM        RT_BIT(3)   /* DSR interrupt mask */
+#define PL011_DCDMIM        RT_BIT(2)   /* DCD interrupt mask */
+#define PL011_CTSMIM        RT_BIT(1)   /* CTS interrupt mask */
+#define PL011_RIMIM         RT_BIT(0)   /* RI interrupt mask */
+
+#define PL011_DR            0x000
+#define PL011_FR            0x018
+#define PL011_IBRD          0x024
+#define PL011_FBRD          0x028
+#define PL011_LCR           0x02c
+#define PL011_CR            0x030
+#define PL011_IMSC          0x038
+#define PL011_RIS           0x03c
+#define PL011_DMACR         0x048
+
+#define PL011_LCRH_SPS      (1 << 7)
+#define PL011_LCRH_WLEN_8   (3 << 5)
+#define PL011_LCRH_WLEN_7   (2 << 5)
+#define PL011_LCRH_WLEN_6   (1 << 5)
+#define PL011_LCRH_WLEN_5   (0 << 5)
+#define PL011_LCRH_FEN      (1 << 4)
+#define PL011_LCRH_STP2     (1 << 3)
+#define PL011_LCRH_EPS      (1 << 2)
+#define PL011_LCRH_PEN      (1 << 1)
+#define PL011_LCRH_BRK      (1 << 0)
+
+#define PL011_LCRH_WLEN(n)  ((n - 5) << 5)
+
+#define PL011_CR_CTSEN      RT_BIT(15)
+#define PL011_CR_RTSEN      RT_BIT(14)
+#define PL011_CR_RTS        RT_BIT(11)
+#define PL011_CR_DTR        RT_BIT(10)
+#define PL011_CR_RXE        RT_BIT(9)
+#define PL011_CR_TXE        RT_BIT(8)
+#define PL011_CR_LBE        RT_BIT(7)
+#define PL011_CR_SIRLP      RT_BIT(2)
+#define PL011_CR_SIREN      RT_BIT(1)
+#define PL011_CR_UARTEN     RT_BIT(0)
+
+struct pl011
+{
+    struct rt_serial_device parent;
+
+    int irq;
+    void *base;
+    rt_ubase_t freq;
+    struct rt_clk *clk;
+    struct rt_clk *pclk;
+
+    struct rt_spinlock spinlock;
+};
+
+#define raw_to_pl011(raw) rt_container_of(raw, struct pl011, parent)
+
+rt_inline rt_uint32_t pl011_read(struct pl011 *pl011, int offset)
+{
+    return HWREG32(pl011->base + offset);
+}
+
+rt_inline void pl011_write(struct pl011 *pl011, int offset, rt_uint32_t value)
+{
+    HWREG32(pl011->base + offset) = value;
+}
+
+static void pl011_isr(int irqno, void *param)
+{
+    struct pl011 *pl011 = param;
+
+    /* Check irq */
+    if (pl011_read(pl011, PL011_RIS) & PL011_RXIM)
+    {
+        rt_base_t level = rt_spin_lock_irqsave(&pl011->spinlock);
+
+        rt_hw_serial_isr(&pl011->parent, RT_SERIAL_EVENT_RX_IND);
+
+        rt_spin_unlock_irqrestore(&pl011->spinlock, level);
+    }
+}
+
+static rt_err_t pl011_uart_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
+{
+    rt_ubase_t quot;
+    struct pl011 *pl011 = raw_to_pl011(serial);
+
+    /* Clear UART setting */
+    pl011_write(pl011, PL011_CR, 0);
+    /* Disable FIFO */
+    pl011_write(pl011, PL011_LCR, 0);
+
+    if (cfg->baud_rate > pl011->freq / 16)
+    {
+        quot = RT_DIV_ROUND_CLOSEST(pl011->freq * 8, cfg->baud_rate);
+    }
+    else
+    {
+        quot = RT_DIV_ROUND_CLOSEST(pl011->freq * 4, cfg->baud_rate);
+    }
+
+    pl011_write(pl011, PL011_IBRD, quot >> 6);
+    pl011_write(pl011, PL011_FBRD, quot & 0x3f);
+    /* FIFO */
+    pl011_write(pl011, PL011_LCR, PL011_LCRH_WLEN(cfg->data_bits));
+
+    /* Art enable, TX/RX enable */
+    pl011_write(pl011, PL011_CR, PL011_CR_UARTEN | PL011_CR_TXE | PL011_CR_RXE);
+
+    return RT_EOK;
+}
+
+static rt_err_t pl011_uart_control(struct rt_serial_device *serial, int cmd, void *arg)
+{
+    struct pl011 *pl011 = raw_to_pl011(serial);
+
+    switch (cmd)
+    {
+    case RT_DEVICE_CTRL_CLR_INT:
+        /* Disable rx irq */
+        pl011_write(pl011, PL011_IMSC, pl011_read(pl011, PL011_IMSC) & ~PL011_RXIM);
+
+        rt_hw_interrupt_mask(pl011->irq);
+
+        break;
+
+    case RT_DEVICE_CTRL_SET_INT:
+        /* Enable rx irq */
+        pl011_write(pl011, PL011_IMSC, pl011_read(pl011, PL011_IMSC) | PL011_RXIM);
+
+        rt_hw_interrupt_umask(pl011->irq);
+
+        break;
+    }
+
+    return RT_EOK;
+}
+
+static int pl011_uart_putc(struct rt_serial_device *serial, char c)
+{
+    struct pl011 *pl011 = raw_to_pl011(serial);
+
+    while (pl011_read(pl011, PL011_FR) & PL011_TXIM)
+    {
+        rt_hw_cpu_relax();
+    }
+
+    pl011_write(pl011, PL011_DR, c);
+
+    return 1;
+}
+
+static int pl011_uart_getc(struct rt_serial_device *serial)
+{
+    int ch = -1;
+    struct pl011 *pl011 = raw_to_pl011(serial);
+
+    if (!(pl011_read(pl011, PL011_FR) & PL011_RXIM))
+    {
+        ch = pl011_read(pl011, PL011_DR);
+    }
+
+    return ch;
+}
+
+static const struct rt_uart_ops pl011_uart_ops =
+{
+    .configure = pl011_uart_configure,
+    .control = pl011_uart_control,
+    .putc = pl011_uart_putc,
+    .getc = pl011_uart_getc,
+};
+
+static void pl011_early_kick(struct rt_fdt_earlycon *con, int why)
+{
+    struct pl011 *pl011 = raw_to_pl011(con->data);
+
+    switch (why)
+    {
+    case FDT_EARLYCON_KICK_UPDATE:
+        pl011->base = rt_ioremap((void *)con->mmio, con->size);
+        break;
+
+    case FDT_EARLYCON_KICK_COMPLETED:
+        rt_iounmap(pl011->base);
+        break;
+
+    default:
+        break;
+    }
+}
+
+static rt_err_t pl011_early_setup(struct rt_fdt_earlycon *con, const char *options)
+{
+    rt_err_t err = RT_EOK;
+    static struct pl011 pl011 = { };
+
+    if (options && !con->mmio)
+    {
+        char *arg;
+
+        con->mmio = RT_NULL;
+
+        /*
+         *  The pl011 serial port must already be setup and configured in early.
+         *  Options are not yet supported.
+         *  pl011,<addr>
+         *  pl011,mmio32,<addr>
+         */
+        serial_for_each_args(arg, options)
+        {
+            if (!rt_strcmp(arg, "pl011") || !rt_strcmp(arg, "mmio32"))
+            {
+                continue;
+            }
+            if (!con->mmio)
+            {
+                con->mmio = (rt_ubase_t)serial_base_from_args(arg);
+                break;
+            }
+        }
+    }
+
+    if (!con->size)
+    {
+        con->size = 0x1000;
+    }
+
+    if (con->mmio)
+    {
+        pl011.base = rt_ioremap_early((void *)con->mmio, con->size);
+    }
+
+    if (pl011.base)
+    {
+        con->console_putc = (typeof(con->console_putc))&pl011_uart_putc;
+        con->console_kick = pl011_early_kick;
+        con->data = &pl011.parent;
+        pl011.parent.config = (typeof(pl011.parent.config))RT_SERIAL_CONFIG_DEFAULT;
+    }
+    else
+    {
+        err = -RT_ERROR;
+    }
+
+    return err;
+}
+RT_FDT_EARLYCON_EXPORT(pl011, "pl011", "arm,pl011", pl011_early_setup);
+
+static void pl011_free(struct pl011 *pl011)
+{
+    if (pl011->base)
+    {
+        rt_iounmap(pl011->base);
+    }
+
+    if (!rt_is_err_or_null(pl011->clk))
+    {
+        rt_clk_disable(pl011->clk);
+        rt_clk_put(pl011->clk);
+    }
+
+    if (!rt_is_err_or_null(pl011->pclk))
+    {
+        rt_clk_disable_unprepare(pl011->pclk);
+        rt_clk_put(pl011->pclk);
+    }
+
+    rt_free(pl011);
+}
+
+static rt_err_t pl011_probe(struct rt_platform_device *pdev)
+{
+    rt_err_t err;
+    const char *name;
+    char isr_name[RT_NAME_MAX];
+    struct rt_device *dev = &pdev->parent;
+    struct pl011 *pl011 = rt_calloc(1, sizeof(*pl011));
+    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
+
+    if (!pl011)
+    {
+        return -RT_ENOMEM;
+    }
+
+    pl011->base = rt_dm_dev_iomap(dev, 0);
+
+    if (!pl011->base)
+    {
+        err = -RT_EIO;
+
+        goto _fail;
+    }
+
+    pl011->irq = rt_dm_dev_get_irq(dev, 0);
+
+    if (pl011->irq < 0)
+    {
+        err = pl011->irq;
+
+        goto _fail;
+    }
+
+    pl011->clk = rt_clk_get_by_index(dev, 0);
+
+    if (rt_is_err(pl011->clk))
+    {
+        err = rt_ptr_err(pl011->clk);
+
+        goto _fail;
+    }
+
+    pl011->pclk = rt_clk_get_by_name(dev, "apb_pclk");
+
+    if (rt_is_err(pl011->pclk))
+    {
+        err = rt_ptr_err(pl011->pclk);
+
+        goto _fail;
+    }
+
+    if ((err = rt_clk_prepare_enable(pl011->pclk)))
+    {
+        goto _fail;
+    }
+
+    rt_dm_dev_bind_fwdata(&pl011->parent.parent, dev->ofw_node, &pl011->parent);
+
+    rt_clk_enable(pl011->clk);
+    pl011->freq = rt_clk_get_rate(pl011->clk);
+
+    dev->user_data = pl011;
+
+    pl011->parent.ops = &pl011_uart_ops;
+    pl011->parent.config = config;
+
+    rt_spin_lock_init(&pl011->spinlock);
+
+    serial_dev_set_name(&pl011->parent);
+    name = rt_dm_dev_get_name(&pl011->parent.parent);
+
+    rt_hw_serial_register(&pl011->parent, name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, pl011);
+    rt_snprintf(isr_name, RT_NAME_MAX, "%s-pl011", name);
+    rt_hw_interrupt_install(pl011->irq, pl011_isr, pl011, isr_name);
+
+    return RT_EOK;
+
+_fail:
+    pl011_free(pl011);
+
+    return err;
+}
+
+static rt_err_t pl011_remove(struct rt_platform_device *pdev)
+{
+    struct rt_device *dev = &pdev->parent;
+    struct pl011 *pl011 = dev->user_data;
+
+    rt_dm_dev_unbind_fwdata(dev, RT_NULL);
+
+    rt_hw_interrupt_mask(pl011->irq);
+    rt_pic_detach_irq(pl011->irq, pl011);
+
+    rt_device_unregister(&pl011->parent.parent);
+
+    pl011_free(pl011);
+
+    return RT_EOK;
+}
+
+static const struct rt_ofw_node_id pl011_ofw_ids[] =
+{
+    { .type = "ttyAMA", .compatible = "arm,pl011" },
+    { .type = "ttyAMA", .compatible = "arm,pl011-axi" },
+    { /* sentinel */ }
+};
+
+static struct rt_platform_driver pl011_driver =
+{
+    .name = "serial-pl011",
+    .ids = pl011_ofw_ids,
+
+    .probe = pl011_probe,
+    .remove = pl011_remove,
+};
+
+static int pl011_drv_register(void)
+{
+    rt_platform_driver_register(&pl011_driver);
+
+    return 0;
+}
+INIT_PLATFORM_EXPORT(pl011_drv_register);

+ 1 - 0
components/drivers/serial/device/virtual/.gitignore

@@ -0,0 +1 @@
+psf.inc

+ 17 - 0
components/drivers/serial/device/virtual/Kconfig

@@ -0,0 +1,17 @@
+menuconfig RT_SERIAL_VIRTUAL
+    bool "Virtual Serial in video"
+    depends on RT_GRAPHIC_FB
+    depends on RT_INPUT_KEYBOARD
+    default n
+
+choice
+    prompt "Rendering font(psf)"
+    default RT_SERIAL_VIRTUAL_FONT_UNI2_FIXED16
+    depends on RT_SERIAL_VIRTUAL
+
+    config RT_SERIAL_VIRTUAL_FONT_UNI2_FIXED16
+        bool "uni2 fixed16"
+
+    config RT_SERIAL_VIRTUAL_FONT_UNI2_VGA16
+        bool "uni2 vga16"
+endchoice

+ 39 - 0
components/drivers/serial/device/virtual/SConscript

@@ -0,0 +1,39 @@
+from building import *
+
+group   = []
+
+if not GetDepend(['RT_SERIAL_VIRTUAL']):
+    Return('group')
+
+cwd     = GetCurrentDir()
+CPPPATH = [cwd + '/../../include']
+CPPDEFINES = []
+
+src     = []
+
+font_path = None
+
+if GetDepend(['RT_SERIAL_VIRTUAL_FONT_UNI2_FIXED16']):
+    font_path = cwd + '/font-uni2-fixed16.psf'
+
+if GetDepend(['RT_SERIAL_VIRTUAL_FONT_UNI2_VGA16']):
+    font_path = cwd + '/font-uni2-vga16.psf'
+
+if font_path != None:
+    with open(font_path, 'rb') as psf:
+        psf_data = psf.read()
+
+    first_byte = True
+
+    with open(cwd + '/psf.inc', 'w') as font:
+        for byte in psf_data:
+            if not first_byte:
+                font.write(", ")
+
+            font.write("0x{:02x}".format(byte))
+            first_byte = False
+
+    src += ['psf.c', 'render.c', 'virtual.c']
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
+Return('group')

BIN
components/drivers/serial/device/virtual/font-uni2-fixed16.psf


BIN
components/drivers/serial/device/virtual/font-uni2-vga16.psf


+ 105 - 0
components/drivers/serial/device/virtual/psf.c

@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include "psf.h"
+
+static void psf_read_header(struct psf_font *psf)
+{
+    if (psf->header1->magic[0] == PSF1_MAGIC0 &&
+        psf->header1->magic[1] == PSF1_MAGIC1)
+    {
+        psf->font_map = (void *)&psf->header1[1];
+        psf->type = PSF_TYPE_1;
+
+        if (psf->header1->mode & PSF1_MODE512)
+        {
+            psf->count = 512;
+        }
+        else
+        {
+            psf->count = 256;
+        }
+
+        psf->size = psf->header1->charsize;
+        psf->height = psf->header1->charsize;
+        psf->width = 8;
+    }
+    else if (psf->header2->magic[0] == PSF2_MAGIC0 &&
+            psf->header2->magic[1] == PSF2_MAGIC1 &&
+            psf->header2->magic[2] == PSF2_MAGIC2 &&
+            psf->header2->magic[3] == PSF2_MAGIC3)
+    {
+        psf->font_map = (void *)&psf->header2[1];
+        psf->type = PSF_TYPE_2;
+        psf->count = psf->header2->length;
+        psf->size = psf->header2->charsize;
+        psf->height = psf->header2->height;
+        psf->width = psf->header2->width;
+    }
+    else
+    {
+        psf->type = PSF_TYPE_UNKNOW;
+    }
+
+    psf->glyph = psf->height * psf->width;
+}
+
+rt_err_t psf_initialize(const void *psf_data, struct psf_font *out_psf)
+{
+    struct psf_font *psf = out_psf;
+
+    psf->raw_data = psf_data;
+    psf_read_header(psf);
+
+    if (psf->type == PSF_TYPE_UNKNOW)
+    {
+        return -RT_ENOSYS;
+    }
+
+    return RT_EOK;
+}
+
+rt_err_t psf_parse(struct psf_font *psf, const rt_uint8_t *font_data,
+        rt_uint8_t *tmpglyph, rt_uint32_t color_size,
+        rt_uint32_t foreground, rt_uint32_t background)
+{
+    rt_uint8_t *font = (void *)font_data, *map = (void *)psf->font_map;
+
+    psf->font_data = font_data;
+
+    for (int n = 0; n < psf->count; ++n, map += psf->size)
+    {
+        rt_memcpy(tmpglyph, map, psf->size);
+
+        for (int i = 0; i < psf->size; ++i)
+        {
+            for (int j = 0; j < 8; ++j)
+            {
+                if (i % (psf->size / psf->height) * 8 + j < psf->width)
+                {
+                    if (tmpglyph[i] & 0x80)
+                    {
+                        rt_memcpy(font, &foreground, color_size);
+                    }
+                    else
+                    {
+                        rt_memcpy(font, &background, color_size);
+                    }
+
+                    font += color_size;
+                }
+
+                tmpglyph[i] = tmpglyph[i] << 1;
+            }
+        }
+    }
+
+    return RT_EOK;
+}

+ 92 - 0
components/drivers/serial/device/virtual/psf.h

@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#ifndef __PSF_H__
+#define __PSF_H__
+
+#include <rtthread.h>
+
+#define PSF1_MAGIC0             0x36
+#define PSF1_MAGIC1             0x04
+
+#define PSF1_MODE512            0x01
+#define PSF1_MODEHASTAB         0x02
+#define PSF1_MODEHASSEQ         0x04
+#define PSF1_MAXMODE            0x05
+
+#define PSF1_SEPARATOR          0xffff
+#define PSF1_STARTSEQ           0xfffe
+
+struct psf1_header
+{
+    rt_uint8_t magic[2];        /* Magic number */
+    rt_uint8_t mode;            /* PSF font mode */
+    rt_uint8_t charsize;        /* Character size */
+};
+
+#define PSF2_MAGIC0             0x72
+#define PSF2_MAGIC1             0xb5
+#define PSF2_MAGIC2             0x4a
+#define PSF2_MAGIC3             0x86
+
+/* bits used in flags */
+#define PSF2_HAS_UNICODE_TABLE  0x01
+
+/* max version recognized so far */
+#define PSF2_MAXVERSION         0
+
+/* UTF8 separators */
+#define PSF2_SEPARATOR          0xff
+#define PSF2_STARTSEQ           0xfe
+
+struct psf2_header
+{
+    rt_uint8_t magic[4];
+    rt_uint32_t version;
+    rt_uint32_t headersize;     /* offset of bitmaps in file */
+    rt_uint32_t flags;
+    rt_uint32_t length;         /* number of glyphs */
+    rt_uint32_t charsize;       /* number of bytes for each character, charsize = height * ((width + 7) / 8) */
+    rt_uint32_t height, width;  /* max dimensions of glyphs */
+};
+
+enum psf_type
+{
+    PSF_TYPE_1,
+    PSF_TYPE_2,
+    PSF_TYPE_UNKNOW,
+};
+
+struct psf_font
+{
+    union
+    {
+        const rt_uint8_t *raw_data;
+        struct psf1_header *header1;
+        struct psf2_header *header2;
+    };
+    const rt_uint8_t *font_map;
+    const rt_uint8_t *font_data;
+
+    enum psf_type type;
+
+    rt_uint32_t count;          /* fonts count */
+    rt_uint32_t size;
+    rt_uint32_t height;
+    rt_uint32_t width;
+    rt_uint32_t glyph;          /* height * width */
+};
+
+rt_err_t psf_initialize(const void *psf_data, struct psf_font *out_psf);
+rt_err_t psf_parse(struct psf_font *psf, const rt_uint8_t *font_data,
+        rt_uint8_t *tmpglyph, rt_uint32_t color_size,
+        rt_uint32_t foreground, rt_uint32_t background);
+
+#endif /* __PSF_H__ */

+ 661 - 0
components/drivers/serial/device/virtual/render.c

@@ -0,0 +1,661 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+
+#include "psf.h"
+#include "render.h"
+
+struct render
+{
+    struct rt_device *fbdev;
+    struct rt_device_graphic_info info;
+    struct fb_var_screeninfo var;
+    struct fb_fix_screeninfo fix;
+
+    struct psf_font psf;
+    rt_uint32_t xlate;
+    rt_uint32_t font_line_length;
+    rt_uint32_t buffer_idx;
+    rt_size_t buffer_size;
+    rt_size_t line_size;
+    rt_size_t screen_size;
+    rt_size_t font_size;
+    rt_uint8_t color_size;
+    rt_uint8_t *cursor, *cursor_backend;
+
+    struct render_point position, start_point, end_point;
+
+    rt_uint32_t foreground, background;
+    rt_uint32_t raw_foreground, raw_background;
+    rt_uint32_t red_off, green_off, blue_off, alpha_off;
+    rt_uint32_t red_mask, green_mask, blue_mask, alpha_mask;
+
+    void *hook_ptr;
+
+    void (*set_pixel)(void *fb, rt_uint32_t color);
+    rt_uint32_t (*get_pixel)(void *fb);
+
+    void *(*last_buffer)(void);
+    void *(*next_buffer)(void);
+    void (*move_buffer)(void *dst, void *src, rt_size_t size);
+    void (*pan_display)(void);
+};
+
+static struct render _render = {};
+
+rt_err_t render_load_fbdev(struct rt_device *fbdev)
+{
+    rt_err_t err = RT_EOK;
+    struct render *rd = &_render;
+
+    if ((err = rt_device_open(fbdev, 0)))
+    {
+        return err;
+    }
+
+    rd->fbdev = fbdev;
+
+    err |= rt_device_control(fbdev, FBIOGET_VSCREENINFO, &rd->var);
+    err |= rt_device_control(fbdev, FBIOGET_FSCREENINFO, &rd->fix);
+    err |= rt_device_control(fbdev, RTGRAPHIC_CTRL_GET_INFO, &rd->info);
+
+    rt_device_close(fbdev);
+
+    return err;
+}
+
+static rt_uint32_t parse_color(struct render_color *color)
+{
+    rt_uint32_t color_value;
+    struct render *rd = &_render;
+
+    color_value = ((color->red & rd->red_mask) << rd->red_off) |
+                  ((color->green & rd->green_mask) << rd->green_off) |
+                  ((color->blue & rd->blue_mask) << rd->blue_off);
+
+    if (rd->var.transp.length)
+    {
+        color_value |= (color->alpha & rd->alpha_mask) << rd->alpha_off;
+    }
+
+    return color_value;
+}
+
+static void set_pixel16(void *fb, rt_uint32_t color)
+{
+    rt_memcpy(fb, &color, 2);
+}
+
+static rt_uint32_t get_pixel16(void *fb)
+{
+    rt_uint16_t color;
+
+    rt_memcpy(&color, fb, 2);
+
+    return color;
+}
+
+static void set_pixel24(void *fb, rt_uint32_t color)
+{
+    rt_memcpy(fb, &color, 3);
+}
+
+static rt_uint32_t get_pixel24(void *fb)
+{
+    rt_uint32_t color;
+
+    rt_memcpy(&color, fb, 3);
+
+    return color;
+}
+
+static void set_pixel32(void *fb, rt_uint32_t color)
+{
+    *(rt_uint32_t *)fb = color;
+}
+
+static rt_uint32_t get_pixel32(void *fb)
+{
+    return *(rt_uint32_t *)fb;
+}
+
+static void *last_buffer_single(void)
+{
+    struct render *rd = &_render;
+
+    return (void *)rd->info.framebuffer;
+}
+
+static void *fb_next_buffer_dummy(void)
+{
+    return last_buffer_single();
+}
+
+static void fb_move_buffer_single(void *dst, void *src, rt_size_t size)
+{
+    rt_memmove(dst, src, size);
+}
+
+static void fb_pan_display_dummy(void)
+{
+}
+
+static void *last_buffer_multi(void)
+{
+    struct render *rd = &_render;
+
+    return (void *)rd->info.framebuffer + rd->buffer_idx * rd->line_size;
+}
+
+static void *fb_next_buffer(void)
+{
+    struct render *rd = &_render;
+
+    if (rd->buffer_idx < rd->buffer_size)
+    {
+        ++rd->buffer_idx;
+    }
+    else
+    {
+        rd->buffer_idx = 0;
+    }
+
+    return last_buffer_multi();
+}
+
+static void fb_move_buffer_multi(void *dst, void *src, rt_size_t size)
+{
+    struct render *rd = &_render;
+
+    if (rd->buffer_idx == 0)
+    {
+        fb_move_buffer_single(dst, src, size);
+    }
+}
+
+static void fb_pan_display(void)
+{
+    struct render *rd = &_render;
+
+    rd->var.yoffset = rd->buffer_idx * rd->psf.height;
+
+    rt_device_control(rd->fbdev, FBIOPAN_DISPLAY, &rd->var);
+}
+
+static void color_hook_dummy(void *, rt_uint32_t *);
+
+rt_err_t render_load_font(const char *psf_data, rt_size_t size,
+        struct render_color *foreground, struct render_color *background,
+        struct render_point *out_start_point, struct render_point *out_end_point)
+{
+    rt_err_t err;
+    rt_uint8_t *font_data, *cursor, *cursor_backend;
+    rt_uint32_t foreground_color, background_color;
+    struct psf_font new_psf;
+    struct render *rd = &_render;
+
+    if ((err = psf_initialize(psf_data, &new_psf)))
+    {
+        return err;
+    }
+
+    if (!new_psf.count)
+    {
+        return -RT_EEMPTY;
+    }
+
+    /* Font + Cursor + Cursor backend */
+    rd->color_size = rd->var.bits_per_pixel / 8;
+    font_data = rt_malloc_align(rd->color_size * new_psf.glyph * (new_psf.count + 2), sizeof(rt_ubase_t));
+
+    if (!font_data)
+    {
+        return -RT_ENOMEM;
+    }
+
+    cursor = font_data + rd->color_size * new_psf.glyph * new_psf.count;
+    cursor_backend = cursor + rd->color_size * new_psf.glyph * new_psf.count;
+
+    rd->red_off = rd->var.red.offset;
+    rd->red_mask = RT_GENMASK(rd->var.red.length, 0);
+    rd->green_off = rd->var.green.offset;
+    rd->green_mask = RT_GENMASK(rd->var.green.length, 0);
+    rd->blue_off = rd->var.blue.offset;
+    rd->blue_mask = RT_GENMASK(rd->var.blue.length, 0);
+    rd->alpha_off = rd->var.transp.offset;
+    rd->alpha_mask = RT_GENMASK(rd->var.transp.length, 0);
+
+    foreground_color = parse_color(foreground);
+    background_color = parse_color(background);
+
+    if ((err = psf_parse(&new_psf, font_data, cursor_backend,
+            rd->color_size, foreground_color, background_color)))
+    {
+        rt_free(font_data);
+
+        return err;
+    }
+
+    if (rd->psf.font_data)
+    {
+        rt_free((void *)rd->psf.font_data);
+    }
+
+    rd->foreground = rd->raw_foreground = foreground_color;
+    rd->background = rd->raw_background = background_color;
+    rd->hook_ptr = &color_hook_dummy;
+
+    rt_memcpy(&rd->psf, &new_psf, sizeof(rd->psf));
+
+    rd->font_line_length = rd->psf.width * rd->color_size;
+    rd->xlate = rd->fix.line_length - rd->font_line_length;
+
+    rd->line_size = rd->psf.height * rd->fix.line_length;
+    rd->screen_size = rd->var.xres * rd->var.yres * rd->color_size;
+    rd->font_size = rd->psf.glyph * rd->color_size;
+    rd->buffer_idx = 0;
+    rd->buffer_size = (rd->fix.smem_len - rd->screen_size) / rd->line_size;
+
+    if (rd->buffer_size)
+    {
+        rd->var.yres_virtual = (rd->fix.smem_len / rd->screen_size) * rd->var.yres;
+
+        if (rt_device_control(rd->fbdev, FBIOPUT_VSCREENINFO, &rd->var))
+        {
+            /* Enable double buffer fail */
+            rd->buffer_size = 0;
+        }
+    }
+
+    if (rd->buffer_size)
+    {
+        rd->last_buffer = last_buffer_multi;
+        rd->next_buffer = fb_next_buffer;
+        rd->move_buffer = fb_move_buffer_single;
+        rd->pan_display = fb_pan_display;
+    }
+    else
+    {
+        rd->last_buffer = last_buffer_single;
+        rd->next_buffer = fb_next_buffer_dummy;
+        rd->move_buffer = fb_move_buffer_multi;
+        rd->pan_display = fb_pan_display_dummy;
+    }
+
+    rd->cursor = cursor;
+    rd->cursor_backend = cursor_backend;
+
+    rd->start_point.row = rd->var.yoffset / rd->psf.height;
+    rd->start_point.col = rd->var.xoffset / rd->psf.width;
+    rd->end_point.row = rd->var.yres / rd->psf.height - 1;
+    rd->end_point.col = rd->var.xres / rd->psf.width - 1;
+
+    if (out_start_point)
+    {
+        rt_memcpy(out_start_point, &rd->start_point, sizeof(*out_start_point));
+    }
+
+    if (out_end_point)
+    {
+        rt_memcpy(out_end_point, &rd->end_point, sizeof(*out_end_point));
+    }
+
+    switch (rd->var.bits_per_pixel)
+    {
+    case 32:
+        rd->set_pixel = set_pixel32;
+        rd->get_pixel = get_pixel32;
+        break;
+
+    case 24:
+        rd->set_pixel = set_pixel24;
+        rd->get_pixel = get_pixel24;
+        break;
+
+    case 16:
+        rd->set_pixel = set_pixel16;
+        rd->get_pixel = get_pixel16;
+        break;
+
+    default: break;
+    }
+
+    return RT_EOK;
+}
+
+void render_clear_display(void)
+{
+    void *fb, *fb_end;
+    rt_uint32_t color;
+    rt_size_t color_size;
+    struct render *rd = &_render;
+    typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel;
+
+    fb = rd->next_buffer();
+    fb_end = fb + rd->screen_size;
+
+    color = rd->background;
+    color_size = rd->color_size;
+
+    while (fb < fb_end)
+    {
+        set_pixel_handler(fb, color);
+
+        fb += color_size;
+    }
+
+    rd->pan_display();
+    render_move_cursor(RT_NULL);
+}
+
+static void roll_display(void)
+{
+    rt_uint32_t color;
+    rt_size_t screen_size, flush_size, color_size;
+    void *old_fb, *fb, *fb_end;
+    struct render *rd = &_render;
+    typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel;
+
+    color = rd->background;
+    color_size = rd->color_size;
+    screen_size = rd->screen_size;
+    flush_size = screen_size - rd->line_size;
+
+    old_fb = rd->last_buffer();
+    fb = rd->next_buffer();
+    fb_end = fb + screen_size;
+
+    rd->move_buffer(fb, old_fb + rd->line_size, flush_size);
+    /* The last line */
+    fb += flush_size;
+
+    while (fb < fb_end)
+    {
+        set_pixel_handler(fb, color);
+
+        fb += color_size;
+    }
+
+    rd->pan_display();
+    rt_device_control(rd->fbdev, FBIO_WAITFORVSYNC, RT_NULL);
+}
+
+static void color_hook_dummy(void *fb, rt_uint32_t *out_color)
+{
+}
+
+static void color_hook_transform(void *fb, rt_uint32_t *out_color)
+{
+    rt_uint32_t color;
+    struct render *rd = &_render;
+
+    rt_memcpy(&color, out_color, rd->color_size);
+
+    if (color == rd->raw_foreground)
+    {
+        *out_color = rd->foreground;
+    }
+    else if (color == rd->raw_background)
+    {
+        *out_color = rd->background;
+    }
+}
+
+void render_set_foreground(struct render_color *foreground)
+{
+    struct render *rd = &_render;
+
+    rd->foreground = parse_color(foreground);
+
+    if (rd->foreground != rd->raw_foreground)
+    {
+        rd->hook_ptr = color_hook_transform;
+    }
+    else
+    {
+        rd->hook_ptr = color_hook_dummy;
+    }
+}
+
+void render_set_background(struct render_color *background)
+{
+    struct render *rd = &_render;
+
+    rd->background = parse_color(background);
+
+    if (rd->foreground != rd->raw_foreground)
+    {
+        rd->hook_ptr = color_hook_transform;
+    }
+    else
+    {
+        rd->hook_ptr = color_hook_dummy;
+    }
+}
+
+static void color_hook_invert(void *fb, rt_uint32_t *out_color)
+{
+    struct render *rd = &_render;
+
+    *out_color ^= rd->get_pixel(fb) & ~(rd->alpha_mask << rd->alpha_off);
+}
+
+static void draw_block(void *block, void (*color_hook)(void *fb, rt_uint32_t *out_color))
+{
+    void *fb;
+    rt_size_t color_size;
+    int font_width, font_height, xlate;
+    struct render *rd = &_render;
+    typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel;
+
+    color_size = rd->color_size;
+    font_width = rd->psf.width;
+    font_height = rd->psf.height;
+    xlate = rd->xlate;
+
+    fb = rd->last_buffer();
+    fb += rd->position.col * rd->font_line_length + rd->position.row * rd->line_size;
+
+    for (int y = 0; y < font_height; ++y)
+    {
+        for (int x = 0; x < font_width; ++x)
+        {
+            rt_uint32_t color = *(rt_uint32_t *)block;
+
+            color_hook(fb, &color);
+            set_pixel_handler(fb, color);
+
+            fb += color_size;
+            block += color_size;
+        }
+
+        fb += xlate;
+    }
+}
+
+static void cursor_leave(void)
+{
+    struct render *rd = &_render;
+
+    draw_block(rd->cursor, &color_hook_invert);
+}
+
+static void cursor_update(void)
+{
+    struct render *rd = &_render;
+
+    draw_block(rd->cursor, &color_hook_invert);
+
+    rt_device_control(rd->fbdev, FBIO_WAITFORVSYNC, RT_NULL);
+}
+
+void render_select_cursor(enum cursor shape)
+{
+    void *cursor;
+    rt_size_t color_size;
+    int font_width, font_height;
+    rt_uint32_t foreground, background;
+    struct render *rd = &_render;
+    typeof(rd->set_pixel) set_pixel_handler = rd->set_pixel;
+
+    cursor = rd->cursor;
+    font_width = rd->psf.width;
+    font_height = rd->psf.height;
+    foreground = rd->foreground;
+    background = rd->background;
+    color_size = rd->color_size;
+
+    switch (shape)
+    {
+    case CURSOR_HLINE:
+        for (int y = 0; y < font_height; ++y)
+        {
+            for (int x = 0; x < font_width; ++x)
+            {
+                if (y + 1 < font_height)
+                {
+                    set_pixel_handler(cursor, background);
+                }
+                else
+                {
+                    set_pixel_handler(cursor, foreground);
+                }
+
+                cursor += color_size;
+            }
+        }
+        break;
+
+    case CURSOR_VLINE:
+        for (int i = 0; i < font_height; ++i)
+        {
+            set_pixel_handler(cursor, foreground);
+            cursor += color_size;
+
+            for (int x = 1; x < font_width; ++x)
+            {
+                set_pixel_handler(cursor, background);
+
+                cursor += color_size;
+            }
+        }
+        break;
+
+    case CURSOR_BLOCK:
+        for (int y = 0; y < font_height; ++y)
+        {
+            for (int x = 0; x < font_width; ++x)
+            {
+                set_pixel_handler(cursor, foreground);
+
+                cursor += color_size;
+            }
+        }
+        break;
+
+    default:
+        return;
+    }
+
+    cursor_update();
+}
+
+void render_move_cursor(struct render_point *position)
+{
+    struct render *rd = &_render;
+
+    if (position)
+    {
+        cursor_leave();
+
+        if (position->row > rd->end_point.row || position->col > rd->end_point.col)
+        {
+            return;
+        }
+
+        rt_memcpy(&rd->position, position, sizeof(rd->position));
+    }
+    else
+    {
+        rt_memset(&rd->position, 0, sizeof(rd->position));
+    }
+
+    cursor_update();
+}
+
+void render_reset_cursor(struct render_point *out_position)
+{
+    struct render *rd = &_render;
+
+    cursor_leave();
+
+    rd->position.col = rd->start_point.col;
+
+    cursor_update();
+    render_current_cursor(out_position);
+}
+
+void render_return_cursor(struct render_point *out_position)
+{
+    struct render *rd = &_render;
+
+    cursor_leave();
+
+    rd->position.col = rd->start_point.col;
+
+    if (rd->position.row >= rd->end_point.row)
+    {
+        roll_display();
+    }
+    else
+    {
+        ++rd->position.row;
+    }
+
+    cursor_update();
+    render_current_cursor(out_position);
+}
+
+void render_current_cursor(struct render_point *out_position)
+{
+    struct render *rd = &_render;
+
+    if (out_position)
+    {
+        rt_memcpy(out_position, &rd->position, sizeof(rd->position));
+    }
+}
+
+void render_put_char(char ch)
+{
+    struct render *rd = &_render;
+
+    draw_block((void *)rd->psf.font_data + ch * rd->font_size, rd->hook_ptr);
+
+    ++rd->position.col;
+
+    if (rd->position.col > rd->end_point.col)
+    {
+        rd->position.col = rd->start_point.col;
+
+        if (rd->position.row >= rd->end_point.row)
+        {
+            roll_display();
+        }
+        else
+        {
+            ++rd->position.row;
+        }
+    }
+
+    cursor_update();
+}

+ 60 - 0
components/drivers/serial/device/virtual/render.h

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#ifndef __VIRTUAL_RENDER_H__
+#define __VIRTUAL_RENDER_H__
+
+#include <rtdef.h>
+
+struct render_color
+{
+    rt_uint32_t red;
+    rt_uint32_t green;
+    rt_uint32_t blue;
+    rt_uint32_t alpha;
+};
+
+struct render_point
+{
+    rt_uint32_t row;
+    rt_uint32_t col;
+};
+
+enum cursor
+{
+    CURSOR_HLINE,
+    CURSOR_VLINE,
+    CURSOR_BLOCK,
+};
+
+rt_err_t render_load_fbdev(struct rt_device *fbdev);
+rt_err_t render_load_font(const char *psf_data, rt_size_t size,
+        struct render_color *foreground, struct render_color *background,
+        struct render_point *out_start_point, struct render_point *out_end_point);
+
+void render_clear_display(void);
+
+void render_set_foreground(struct render_color *foreground);
+void render_set_background(struct render_color *background);
+rt_inline void render_set_color(struct render_color *foreground, struct render_color *background)
+{
+    render_set_foreground(foreground);
+    render_set_background(background);
+}
+
+void render_select_cursor(enum cursor shape);
+void render_move_cursor(struct render_point *position);
+void render_reset_cursor(struct render_point *out_position);
+void render_return_cursor(struct render_point *out_position);
+void render_current_cursor(struct render_point *out_position);
+
+void render_put_char(char ch);
+
+#endif /* __VIRTUAL_RENDER_H__ */

+ 789 - 0
components/drivers/serial/device/virtual/virtual.c

@@ -0,0 +1,789 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2023-02-25     GuEe-GUI     the first version
+ */
+
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <dt-bindings/input/event-codes.h>
+
+#define DBG_TAG "serial.virtual"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#include "render.h"
+
+struct virtual_serial
+{
+    struct rt_serial_device parent;
+
+    struct rt_device *fbdev;
+    struct rt_thread *render_task;
+
+    struct rt_device *idev;
+    struct rt_input_handler input_handler;
+
+    int param;
+    rt_bool_t input_int;
+    struct render_point start, end;
+
+    struct rt_spinlock lock;
+
+    rt_uint8_t in[RT_CONSOLEBUF_SIZE];
+    rt_uint16_t in_head, in_tail;
+    rt_bool_t shift, ctrl, alt, caps;
+    rt_bool_t is_escape, is_bracket;
+    rt_bool_t in_pending;
+
+    rt_uint8_t out[RT_CONSOLEBUF_SIZE];
+    rt_uint16_t out_head, out_tail;
+    rt_bool_t out_pending;
+};
+
+#define raw_to_virtual_serial(raw)  rt_container_of(raw, struct virtual_serial, parent)
+
+static rt_uint8_t virtual_font[] =
+{
+#include "psf.inc"
+};
+
+enum
+{
+    COLOR_BLACK,
+    COLOR_RED,
+    COLOR_GREEN,
+    COLOR_YELLOW,
+    COLOR_BLUE,
+    COLOR_MAGENTA,
+    COLOR_CYAN,
+    COLOR_LIGHT_GRAY,
+    COLOR_DARK_GRAY,
+    COLOR_LIGHT_RED,
+    COLOR_LIGHT_GREEN,
+    COLOR_LIGHT_YELLOW,
+    COLOR_LIGHT_BLUE,
+    COLOR_LIGHT_MAGENTA,
+    COLOR_LIGHT_CYAN,
+    COLOR_WHITE,
+};
+
+static struct render_color font_colors[] =
+{                             /*  R    G    B    A */
+    [COLOR_BLACK]           = {   0,   0,   0, 255 },
+    [COLOR_RED]             = { 205,   0,   0, 255 },
+    [COLOR_GREEN]           = {   0, 205,   0, 255 },
+    [COLOR_YELLOW]          = { 205, 205,   0, 255 },
+    [COLOR_BLUE]            = {   0,   0, 238, 255 },
+    [COLOR_MAGENTA]         = { 205,   0, 205, 255 },
+    [COLOR_CYAN]            = {   0, 205, 205, 255 },
+    [COLOR_LIGHT_GRAY]      = { 229, 229, 229, 255 },
+    [COLOR_DARK_GRAY]       = { 127, 127, 127, 255 },
+    [COLOR_LIGHT_RED]       = { 255,   0,   0, 255 },
+    [COLOR_LIGHT_GREEN]     = {   0, 255,   0, 255 },
+    [COLOR_LIGHT_YELLOW]    = { 255, 255,   0, 255 },
+    [COLOR_LIGHT_BLUE]      = {  92,  92, 255, 255 },
+    [COLOR_LIGHT_MAGENTA]   = { 255,   0, 255, 255 },
+    [COLOR_LIGHT_CYAN]      = {   0, 255, 255, 255 },
+    [COLOR_WHITE]           = { 255, 255, 255, 255 },
+};
+
+static const rt_uint8_t unix_color_map[] =
+{
+    /* \033[Xm */
+    [0]  = COLOR_WHITE,
+    [30] = COLOR_BLACK,
+    [31] = COLOR_RED,
+    [32] = COLOR_GREEN,
+    [33] = COLOR_YELLOW,
+    [34] = COLOR_BLUE,
+    [35] = COLOR_MAGENTA,
+    [36] = COLOR_CYAN,
+    [37] = COLOR_LIGHT_GRAY,
+    [90] = COLOR_DARK_GRAY,
+    [91] = COLOR_LIGHT_RED,
+    [92] = COLOR_LIGHT_GREEN,
+    [93] = COLOR_LIGHT_YELLOW,
+    [94] = COLOR_LIGHT_BLUE,
+    [95] = COLOR_LIGHT_MAGENTA,
+    [96] = COLOR_LIGHT_CYAN,
+    [97] = COLOR_WHITE,
+};
+
+
+static char key_map[] =
+{
+    [KEY_1] = '1',
+    [KEY_2] = '2',
+    [KEY_3] = '3',
+    [KEY_4] = '4',
+    [KEY_5] = '5',
+    [KEY_6] = '6',
+    [KEY_7] = '7',
+    [KEY_8] = '8',
+    [KEY_9] = '9',
+    [KEY_0] = '0',
+    [KEY_MINUS] = '-',
+    [KEY_EQUAL] = '=',
+    [KEY_Q] = 'q',
+    [KEY_W] = 'w',
+    [KEY_E] = 'e',
+    [KEY_R] = 'r',
+    [KEY_T] = 't',
+    [KEY_Y] = 'y',
+    [KEY_U] = 'u',
+    [KEY_I] = 'i',
+    [KEY_O] = 'o',
+    [KEY_P] = 'p',
+    [KEY_LEFTBRACE] = '[',
+    [KEY_RIGHTBRACE] = ']',
+    [KEY_A] = 'a',
+    [KEY_S] = 's',
+    [KEY_D] = 'd',
+    [KEY_F] = 'f',
+    [KEY_G] = 'g',
+    [KEY_H] = 'h',
+    [KEY_J] = 'j',
+    [KEY_K] = 'k',
+    [KEY_L] = 'l',
+    [KEY_SEMICOLON] = ';',
+    [KEY_APOSTROPHE] = '\'',
+    [KEY_BACKSLASH] = '\\',
+    [KEY_Z] = 'z',
+    [KEY_X] = 'x',
+    [KEY_C] = 'c',
+    [KEY_V] = 'v',
+    [KEY_B] = 'b',
+    [KEY_N] = 'n',
+    [KEY_M] = 'm',
+    [KEY_COMMA] = ',',
+    [KEY_DOT] = '.',
+    [KEY_SLASH] = '/',
+    [KEY_SPACE] = ' ',
+};
+
+static char key_shift_map[] =
+{
+    [KEY_1] = '!',
+    [KEY_2] = '@',
+    [KEY_3] = '#',
+    [KEY_4] = '$',
+    [KEY_5] = '%',
+    [KEY_6] = '^',
+    [KEY_7] = '&',
+    [KEY_8] = '*',
+    [KEY_9] = '(',
+    [KEY_0] = ')',
+    [KEY_MINUS] = '_',
+    [KEY_EQUAL] = '+',
+    [KEY_Q] = 'Q',
+    [KEY_W] = 'W',
+    [KEY_E] = 'E',
+    [KEY_R] = 'R',
+    [KEY_T] = 'T',
+    [KEY_Y] = 'Y',
+    [KEY_U] = 'U',
+    [KEY_I] = 'I',
+    [KEY_O] = 'O',
+    [KEY_P] = 'P',
+    [KEY_LEFTBRACE] = '{',
+    [KEY_RIGHTBRACE] = '}',
+    [KEY_A] = 'A',
+    [KEY_S] = 'S',
+    [KEY_D] = 'D',
+    [KEY_F] = 'F',
+    [KEY_G] = 'G',
+    [KEY_H] = 'H',
+    [KEY_J] = 'J',
+    [KEY_K] = 'K',
+    [KEY_L] = 'L',
+    [KEY_SEMICOLON] = ':',
+    [KEY_APOSTROPHE] = '\"',
+    [KEY_BACKSLASH] = '|',
+    [KEY_Z] = 'Z',
+    [KEY_X] = 'X',
+    [KEY_C] = 'C',
+    [KEY_V] = 'V',
+    [KEY_B] = 'B',
+    [KEY_N] = 'N',
+    [KEY_M] = 'M',
+    [KEY_COMMA] = '<',
+    [KEY_DOT] = '>',
+    [KEY_SLASH] = '?',
+    [KEY_SPACE] = ' ',
+};
+
+static enum cursor cursor_shape = CURSOR_BLOCK;
+
+static struct virtual_serial _vs = {};
+
+static void push_out_char(struct virtual_serial *vs, char c);
+static void virtual_serial_render_char(struct virtual_serial *vs, char ch);
+static int pop_in_char(struct virtual_serial *vs);
+
+static rt_err_t virtual_serial_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
+{
+    return RT_EOK;
+}
+
+static rt_err_t virtual_serial_control(struct rt_serial_device *serial, int cmd, void *arg)
+{
+    struct virtual_serial *vs = raw_to_virtual_serial(serial);
+
+    switch (cmd)
+    {
+    case RT_DEVICE_CTRL_SUSPEND:
+    case RT_DEVICE_CTRL_CLR_INT:
+        vs->input_int = RT_FALSE;
+        break;
+
+    case RT_DEVICE_CTRL_RESUME:
+    case RT_DEVICE_CTRL_SET_INT:
+        vs->input_int = RT_TRUE;
+        break;
+
+    default:
+        return -RT_ENOSYS;
+    }
+
+    return RT_EOK;
+}
+
+static int virtual_serial_putc(struct rt_serial_device *serial, char c)
+{
+    struct virtual_serial *vs = raw_to_virtual_serial(serial);
+
+    rt_spin_lock(&vs->lock);
+
+    if (!rt_critical_level())
+    {
+        virtual_serial_render_char(vs, c);
+    }
+    else
+    {
+        push_out_char(vs, c);
+        rt_thread_resume(vs->render_task);
+    }
+
+    rt_spin_unlock(&vs->lock);
+
+    return 1;
+}
+
+static int virtual_serial_getc(struct rt_serial_device *serial)
+{
+    struct virtual_serial *vs = raw_to_virtual_serial(serial);
+
+    return pop_in_char(vs);
+}
+
+static const struct rt_uart_ops virtual_serial_ops =
+{
+    .configure = virtual_serial_configure,
+    .control = virtual_serial_control,
+    .putc = virtual_serial_putc,
+    .getc = virtual_serial_getc,
+};
+
+static rt_bool_t parse_ctrl(struct virtual_serial *vs, char c)
+{
+    if (vs->is_bracket && c == 'm')
+    {
+        render_set_foreground(&font_colors[unix_color_map[vs->param]]);
+
+        return RT_TRUE;
+    }
+
+    if (c == 'J' && vs->param == 2)
+    {
+        render_clear_display();
+
+        return RT_TRUE;
+    }
+
+    if (c == 'K' && vs->param == 2)
+    {
+        rt_uint32_t old_col;
+        struct render_point point;
+
+        render_current_cursor(&point);
+        old_col = point.col;
+
+        for (int i = vs->start.col; i < old_col; ++i)
+        {
+            --point.col;
+            render_move_cursor(&point);
+            render_put_char(' ');
+        }
+
+        point.col = old_col;
+        render_move_cursor(&point);
+
+        return RT_TRUE;
+    }
+
+    if (c == 'H')
+    {
+        render_move_cursor(&vs->start);
+
+        return RT_TRUE;
+    }
+
+    return RT_FALSE;
+}
+
+static void push_out_char(struct virtual_serial *vs, char c)
+{
+    rt_uint16_t next = (vs->out_head + 1) % sizeof(vs->out);
+
+    if (next == vs->out_tail)
+    {
+        /* Full */
+        return;
+    }
+
+    vs->out[vs->out_head] = c;
+    vs->out_head = next;
+    vs->out_pending = RT_TRUE;
+}
+
+static int pop_out_char(struct virtual_serial *vs)
+{
+    int c;
+
+    if (vs->out_head == vs->out_tail)
+    {
+        return -1;
+    }
+
+    c = vs->out[vs->out_tail];
+    vs->out_tail = (vs->out_tail + 1) % sizeof(vs->out);
+
+    return c;
+}
+
+static void push_in_char(struct virtual_serial *vs, char c)
+{
+    rt_uint16_t next = (vs->in_head + 1) % sizeof(vs->in);
+
+    if (next == vs->in_tail)
+    {
+        /* Full, drop */
+        return;
+    }
+
+    vs->in[vs->in_head] = (rt_uint8_t)c;
+    vs->in_head = next;
+    vs->in_pending = RT_TRUE;
+}
+
+static int pop_in_char(struct virtual_serial *vs)
+{
+    int ch;
+
+    if (vs->in_head == vs->in_tail)
+    {
+        return -1;
+    }
+
+    ch = vs->in[vs->in_tail];
+    vs->in_tail = (vs->in_tail + 1) % sizeof(vs->in);
+
+    return ch;
+}
+
+static char key_to_char(struct virtual_serial *vs, int code)
+{
+    char base, shiftc;
+
+    base = key_map[code];
+    shiftc = key_shift_map[code];
+
+    if (!base && !shiftc)
+    {
+        return 0;
+    }
+
+    if (code >= KEY_A && code <= KEY_Z)
+    {
+        rt_bool_t upper = (vs->shift ^ vs->caps);
+
+        return upper ? (shiftc ? shiftc : base - 'a' + 'A')
+                     : (base   ? base   : shiftc + 'a' - 'A');
+    }
+
+    if (vs->shift && shiftc)
+    {
+        return shiftc;
+    }
+
+    return base;
+}
+
+static void virtual_serial_render_char(struct virtual_serial *vs, char ch)
+{
+    if (vs->is_escape)
+    {
+        if (ch == '[')
+        {
+            vs->param = 0;
+            vs->is_bracket = RT_TRUE;
+            return;
+        }
+        else if (vs->is_bracket && (ch >= '0' && ch <= '9'))
+        {
+            vs->param = vs->param * 10 + (ch - '0');
+            return;
+        }
+        else
+        {
+            if (!parse_ctrl(vs, ch))
+            {
+                goto _render_char;
+            }
+
+            vs->is_bracket = RT_FALSE;
+            vs->is_escape = RT_FALSE;
+            return;
+        }
+    }
+
+_render_char:
+    if (ch >= ' ')
+    {
+        render_put_char(ch);
+    }
+    else
+    {
+        struct render_point point;
+
+        switch (ch)
+        {
+        case '\n':
+            render_return_cursor(RT_NULL);
+            break;
+
+        case '\033':
+            vs->is_escape = RT_TRUE;
+            break;
+
+        case '\t':
+            render_current_cursor(&point);
+            point.col = (point.col / 4 + 1) * 4;
+            if (point.col > vs->end.col)
+            {
+                point.col -= vs->end.col;
+                point.row++;
+            }
+            render_move_cursor(&point);
+            break;
+
+        case '\r':
+            render_reset_cursor(RT_NULL);
+            break;
+
+        case '\b':
+            render_current_cursor(&point);
+            if (point.col > 0)
+            {
+                point.col--;
+                render_move_cursor(&point);
+            }
+            break;
+
+        default:
+            break;
+        }
+    }
+}
+
+static void virtual_serial_render_task(void *param)
+{
+    struct virtual_serial *vs = param;
+
+    while (RT_TRUE)
+    {
+        rt_spin_lock(&vs->lock);
+
+        if (vs->out_head == vs->out_tail)
+        {
+            rt_spin_unlock(&vs->lock);
+            rt_thread_suspend(rt_thread_self());
+            rt_schedule();
+
+            continue;
+        }
+
+        virtual_serial_render_char(vs, pop_out_char(vs));
+
+        rt_spin_unlock(&vs->lock);
+    }
+}
+
+static rt_bool_t virtual_serial_input_test_cap(struct rt_input_handler *handler,
+        struct rt_input_device *idev)
+{
+    return rt_bitmap_test_bit(idev->key_map, KEY_ENTER);
+}
+
+static rt_bool_t virtual_serial_input_callback(struct rt_input_handler *handler,
+        struct rt_input_event *ev)
+{
+    char ch;
+    struct virtual_serial *vs = handler->priv;
+
+    if (ev->type == EV_KEY)
+    {
+        if (!vs->input_int)
+        {
+            return RT_TRUE;
+        }
+
+        if (ev->value == 0)
+        {
+            switch (ev->code)
+            {
+            case KEY_LEFTSHIFT:
+            case KEY_RIGHTSHIFT:
+                vs->shift = RT_FALSE;
+                break;
+
+            case KEY_LEFTCTRL:
+            case KEY_RIGHTCTRL:
+                vs->ctrl = RT_FALSE;
+                break;
+
+            case KEY_LEFTALT:
+            case KEY_RIGHTALT:
+                vs->alt = RT_FALSE;
+                break;
+
+            default:
+                break;
+            }
+
+            return RT_TRUE;
+        }
+
+        if (ev->value != 1)
+        {
+            return RT_TRUE;
+        }
+
+        switch (ev->code)
+        {
+        case KEY_LEFTSHIFT:
+        case KEY_RIGHTSHIFT:
+            vs->shift = RT_TRUE;
+            break;
+
+        case KEY_LEFTCTRL:
+        case KEY_RIGHTCTRL:
+            vs->ctrl = RT_TRUE;
+            break;
+
+        case KEY_LEFTALT:
+        case KEY_RIGHTALT:
+            vs->alt = RT_TRUE;
+            break;
+
+        case KEY_CAPSLOCK:
+            vs->caps = !vs->caps;
+            break;
+
+        case KEY_ENTER:
+        case KEY_KPENTER:
+            push_in_char(vs, '\r');
+            break;
+
+        case KEY_BACKSPACE:
+            push_in_char(vs, '\b');
+            break;
+
+        case KEY_TAB:
+            push_in_char(vs, '\t');
+            break;
+
+        case KEY_UP:
+            push_in_char(vs, 0x1b);
+            push_in_char(vs, '[');
+            push_in_char(vs, 'A');
+            break;
+
+        case KEY_DOWN:
+            push_in_char(vs, 0x1b);
+            push_in_char(vs, '[');
+            push_in_char(vs, 'B');
+            break;
+
+        case KEY_LEFT:
+            push_in_char(vs, 0x1b);
+            push_in_char(vs, '[');
+            push_in_char(vs, 'D');
+            break;
+
+        case KEY_RIGHT:
+            push_in_char(vs, 0x1b);
+            push_in_char(vs, '[');
+            push_in_char(vs, 'C');
+            break;
+
+        default:
+            if ((ch = key_to_char(vs, ev->code)))
+            {
+                push_in_char(vs, ch);
+            }
+            break;
+        }
+
+        return RT_TRUE;
+    }
+    else if (ev->type == EV_SYN)
+    {
+        if (vs->input_int && vs->in_pending)
+        {
+            vs->in_pending = RT_FALSE;
+            rt_hw_serial_isr(&vs->parent, RT_SERIAL_EVENT_RX_IND);
+        }
+
+        return RT_TRUE;
+    }
+
+    return RT_FALSE;
+}
+
+static int virtual_serial_setup(void)
+{
+    rt_err_t err;
+
+    for (int id = 0; id < RT_DM_IDA_NUM; ++id)
+    {
+        if ((_vs.fbdev = rt_dm_device_find(MASTER_ID_GRAPHIC_FRAMEBUFFER, id)))
+        {
+            break;
+        }
+    }
+
+    if (!_vs.fbdev)
+    {
+        return (int)-RT_ENOSYS;
+    }
+
+    _vs.input_handler.idev = RT_NULL;
+    _vs.input_handler.identify = &virtual_serial_input_test_cap;
+    _vs.input_handler.callback = &virtual_serial_input_callback;
+    _vs.input_handler.priv = &_vs;
+
+    if ((err = rt_input_add_handler(&_vs.input_handler)))
+    {
+        return (int)err;
+    }
+
+    if (!(_vs.render_task = rt_thread_create("vuart", virtual_serial_render_task, &_vs,
+            DM_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 2, 32)))
+    {
+        goto _fail;
+    }
+
+    if ((err = render_load_fbdev(_vs.fbdev)))
+    {
+        LOG_E("Load fbdev error = %s", rt_strerror(err));
+
+        goto _fail;
+    }
+
+    if ((err = render_load_font((void *)virtual_font, sizeof(virtual_font),
+            &font_colors[COLOR_WHITE], &font_colors[COLOR_BLACK],
+            &_vs.start, &_vs.end)))
+    {
+        LOG_E("Load PSF font error = %s", rt_strerror(err));
+
+        goto _fail;
+    }
+
+    render_select_cursor(cursor_shape);
+
+    rt_device_open(_vs.fbdev, 0);
+    rt_spin_lock_init(&_vs.lock);
+
+    rt_thread_startup(_vs.render_task);
+
+    _vs.input_int = RT_TRUE;
+    _vs.parent.ops = &virtual_serial_ops;
+    _vs.parent.config = (struct serial_configure)RT_SERIAL_CONFIG_DEFAULT;
+    rt_hw_serial_register(&_vs.parent, "vuart", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, &_vs);
+
+    return 0;
+
+_fail:
+    rt_input_del_handler(&_vs.input_handler);
+
+    return (int)err;
+}
+INIT_EXPORT(virtual_serial_setup, "3.end");
+
+#if defined(RT_USING_CONSOLE) && defined(RT_USING_MSH)
+static int virtual_serial_cmd_color(int argc, char**argv)
+{
+    const char *color;
+    static const char * const color_names[] =
+    {
+        [COLOR_BLACK]           = "black",
+        [COLOR_RED]             = "red",
+        [COLOR_GREEN]           = "green",
+        [COLOR_YELLOW]          = "yellow",
+        [COLOR_BLUE]            = "blue",
+        [COLOR_MAGENTA]         = "magenta",
+        [COLOR_CYAN]            = "cyan",
+        [COLOR_LIGHT_GRAY]      = "light gray",
+        [COLOR_DARK_GRAY]       = "dark gray",
+        [COLOR_LIGHT_RED]       = "light red",
+        [COLOR_LIGHT_GREEN]     = "light green",
+        [COLOR_LIGHT_YELLOW]    = "light yellow",
+        [COLOR_LIGHT_BLUE]      = "light blue",
+        [COLOR_LIGHT_MAGENTA]   = "light magenta",
+        [COLOR_LIGHT_CYAN]      = "light cyan",
+        [COLOR_WHITE]           = "white",
+    };
+
+    if (argc != 2)
+    {
+        goto _help;
+    }
+
+    color = argv[1];
+
+    if (!((color[0] >= '0' && color[0] <= '9') || (color[0] >= 'a' && color[0] <= 'f')) ||
+        !((color[1] >= '0' && color[1] <= '9') || (color[1] >= 'a' && color[1] <= 'f')))
+    {
+        goto _help;
+    }
+
+    if (color[0] == color[1])
+    {
+        rt_kprintf("foreground cannot equal background\n");
+
+        return (int)-RT_EINVAL;
+    }
+
+    render_set_foreground(&font_colors[color[0] - (color[0] >= 'a' ? ('a' - 10) : '0')]);
+    render_set_background(&font_colors[color[1] - (color[1] >= 'a' ? ('a' - 10) : '0')]);
+
+    return 0;
+
+_help:
+    rt_kprintf("Usage: color [attr]\nattr:\n");
+
+    for (int i = 0; i < RT_ARRAY_SIZE(font_colors); ++i)
+    {
+        rt_kprintf("\t%x = %s\n", i, color_names[i]);
+    }
+
+    return (int)-RT_EINVAL;
+}
+MSH_CMD_EXPORT_ALIAS(virtual_serial_cmd_color, color, set virtual serial foreground and background);
+#endif /* RT_USING_CONSOLE && RT_USING_MSH */