riscv/virt: Add optional UART1
This adds optional UART1 to RiscV virt board if required at runtime to simplify multicore development. Note that UART0 remains default serial_hd(0) and it is: - the lowest address UART - first serial in DTB - behind /aliases/serial0 in DTB - the /chosen/stdout-path in DTB Note that UART1 is placed at different page from UART0 to support page level isolation. Signed-off-by: Yanfeng Liu <yfliu2008@qq.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Message-ID: <tencent_4EEBFC805F3E59863BEDC38094EF5A109206@qq.com> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
Yanfeng Liu committed
Jul 18, 2026 at 10:16 UTC
2a99140258feaf8bd05df5b16698dde6aec1148c
4 files changed
+45
-15
docs/system/riscv/virt.rst
+4
-1
@@ -16,7 +16,7 @@ The ``virt`` machine supports the following devices:
16
* Core Local Interruptor (CLINT)
17
* Platform-Level Interrupt Controller (PLIC)
18
* CFI parallel NOR flash memory
19
-* 1 NS16550 compatible UART
19
+* Either 1 or 2 NS16550 compatible UARTs
20
* 1 Google Goldfish RTC
21
* 1 SiFive Test device
22
* 8 virtio-mmio transport devices
@@ -27,6 +27,9 @@ The hypervisor extension has been enabled for the default CPU, so virtual
27
machines with hypervisor extension can simply be used without explicitly
28
declaring.
29
30
+The second UART only exists if a backend is configured explicitly (e.g.
31
+with a second ``-serial`` command line option).
32
+
33
Hardware configuration information
34
----------------------------------
35
hw/riscv/virt-acpi-build.c
+8
-4
@@ -177,11 +177,11 @@ static void acpi_dsdt_add_plic_aplic(Aml *scope, uint8_t socket_count,
177
178
static void
179
acpi_dsdt_add_uart(Aml *scope, const MemMapEntry *uart_memmap,
180
- uint32_t uart_irq)
180
+ uint32_t uart_irq, int uartidx)
181
{
182
- Aml *dev = aml_device("COM0");
182
+ Aml *dev = aml_device("COM%d", uartidx);
183
aml_append(dev, aml_name_decl("_HID", aml_string("RSCV0003")));
184
- aml_append(dev, aml_name_decl("_UID", aml_int(0)));
184
+ aml_append(dev, aml_name_decl("_UID", aml_int(uartidx)));
185
186
Aml *crs = aml_resource_template();
187
aml_append(crs, aml_memory32_fixed(uart_memmap->base,
@@ -490,7 +490,11 @@ static void build_dsdt(GArray *table_data,
490
memmap[VIRT_APLIC_S].size, "RSCV0002");
491
}
492
493
- acpi_dsdt_add_uart(scope, &memmap[VIRT_UART0], UART0_IRQ);
493
+ acpi_dsdt_add_uart(scope, &memmap[VIRT_UART0], UART0_IRQ, 0);
494
+ if (s->uart1_present) {
495
+ acpi_dsdt_add_uart(scope, &memmap[VIRT_UART1], UART1_IRQ, 1);
496
+ }
497
+
498
if (virt_is_iommu_sys_enabled(s)) {
499
acpi_dsdt_add_iommu_sys(scope, &memmap[VIRT_IOMMU_SYS], IOMMU_SYS_IRQ);
500
}
hw/riscv/virt.c
+30
-10
@@ -97,6 +97,8 @@ static const MemMapEntry virt_memmap[] = {
97
[VIRT_APLIC_S] = { 0xd000000, APLIC_SIZE(VIRT_CPUS_MAX) },
98
[VIRT_UART0] = { 0x10000000, 0x100 },
99
[VIRT_VIRTIO] = { 0x10001000, 0x1000 },
100
+ /* UART1 supports page isolation from UART0 */
101
+ [VIRT_UART1] = { 0x1000a000, 0x100 },
102
[VIRT_FW_CFG] = { 0x10100000, 0x18 },
103
[VIRT_FLASH] = { 0x20000000, 0x4000000 },
104
[VIRT_IMSIC_M] = { 0x24000000, VIRT_IMSIC_MAX_SIZE },
@@ -187,7 +189,8 @@ static void create_pcie_irq_map(RISCVVirtState *s, void *fdt, char *nodename,
189
FDT_MAX_INT_MAP_WIDTH] = {};
190
uint32_t *irq_map = full_irq_map;
191
190
- /* This code creates a standard swizzle of interrupts such that
192
+ /*
193
+ * This code creates a standard swizzle of interrupts such that
194
* each device's first interrupt is based on it's PCI_SLOT number.
195
* (See pci_swizzle_map_irq_fn())
196
*
@@ -805,28 +808,38 @@ static void create_fdt_reset(RISCVVirtState *s, uint32_t *phandle)
808
}
809
810
static void create_fdt_uart(RISCVVirtState *s,
808
- uint32_t irq_mmio_phandle)
811
+ uint32_t irq_mmio_phandle, int memId, int irqNo)
812
{
813
g_autofree char *name = NULL;
814
MachineState *ms = MACHINE(s);
815
816
name = g_strdup_printf("/soc/serial@%"HWADDR_PRIx,
814
- s->memmap[VIRT_UART0].base);
817
+ s->memmap[memId].base);
818
qemu_fdt_add_subnode(ms->fdt, name);
819
qemu_fdt_setprop_string(ms->fdt, name, "compatible", "ns16550a");
820
qemu_fdt_setprop_sized_cells(ms->fdt, name, "reg",
818
- 2, s->memmap[VIRT_UART0].base,
819
- 2, s->memmap[VIRT_UART0].size);
821
+ 2, s->memmap[memId].base,
822
+ 2, s->memmap[memId].size);
823
qemu_fdt_setprop_cell(ms->fdt, name, "clock-frequency", 3686400);
824
qemu_fdt_setprop_cell(ms->fdt, name, "interrupt-parent", irq_mmio_phandle);
825
if (s->aia_type == VIRT_AIA_TYPE_NONE) {
823
- qemu_fdt_setprop_cell(ms->fdt, name, "interrupts", UART0_IRQ);
826
+ qemu_fdt_setprop_cell(ms->fdt, name, "interrupts", irqNo);
827
} else {
825
- qemu_fdt_setprop_cells(ms->fdt, name, "interrupts", UART0_IRQ, 0x4);
828
+ qemu_fdt_setprop_cells(ms->fdt, name, "interrupts", irqNo, 0x4);
829
+ }
830
+
831
+ if (VIRT_UART0 == memId) {
832
+ qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", name);
833
+ qemu_fdt_setprop_string(ms->fdt, "/aliases", "serial0", name);
834
}
835
+}
836
828
- qemu_fdt_setprop_string(ms->fdt, "/chosen", "stdout-path", name);
829
- qemu_fdt_setprop_string(ms->fdt, "/aliases", "serial0", name);
837
+static void create_fdt_uarts(RISCVVirtState *s, uint32_t irq_mmio_phandle)
838
+{
839
+ if (s->uart1_present) {
840
+ create_fdt_uart(s, irq_mmio_phandle, VIRT_UART1, UART1_IRQ);
841
+ }
842
+ create_fdt_uart(s, irq_mmio_phandle, VIRT_UART0, UART0_IRQ);
843
}
844
845
static void create_fdt_rtc(RISCVVirtState *s,
@@ -996,7 +1009,7 @@ static void finalize_fdt(RISCVVirtState *s)
1009
1010
create_fdt_reset(s, &phandle);
1011
999
- create_fdt_uart(s, irq_mmio_phandle);
1012
+ create_fdt_uarts(s, irq_mmio_phandle);
1013
1014
create_fdt_rtc(s, irq_mmio_phandle);
1015
}
@@ -1486,6 +1499,13 @@ static void virt_machine_init(MachineState *machine)
1499
0, qdev_get_gpio_in(mmio_irqchip, UART0_IRQ), 399193,
1500
serial_hd(0), DEVICE_LITTLE_ENDIAN);
1501
1502
+ if (serial_hd(1)) {
1503
+ serial_mm_init(system_memory, s->memmap[VIRT_UART1].base,
1504
+ 0, qdev_get_gpio_in(mmio_irqchip, UART1_IRQ), 399193,
1505
+ serial_hd(1), DEVICE_LITTLE_ENDIAN);
1506
+ s->uart1_present = true;
1507
+ }
1508
+
1509
sysbus_create_simple("goldfish_rtc", s->memmap[VIRT_RTC].base,
1510
qdev_get_gpio_in(mmio_irqchip, RTC_IRQ));
1511
include/hw/riscv/virt.h
+3
@@ -59,6 +59,7 @@ struct RISCVVirtState {
59
int aia_guests;
60
char *oem_id;
61
char *oem_table_id;
62
+ bool uart1_present;
63
OnOffAuto acpi;
64
const MemMapEntry *memmap;
65
struct GPEXHost *gpex_host;
@@ -79,6 +80,7 @@ enum {
80
VIRT_APLIC_S,
81
VIRT_UART0,
82
VIRT_VIRTIO,
83
+ VIRT_UART1,
84
VIRT_FW_CFG,
85
VIRT_IMSIC_M,
86
VIRT_IMSIC_S,
@@ -94,6 +96,7 @@ enum {
96
enum {
97
UART0_IRQ = 10,
98
RTC_IRQ = 11,
99
+ UART1_IRQ = 12,
100
VIRTIO_IRQ = 1, /* 1 to 8 */
101
VIRTIO_COUNT = 8,
102
PCIE_IRQ = 0x20, /* 32 to 35 */