3

I was playing around with TTYs, and for qemu-system-x86_64 -M pc, whenever I pass the -serial option it crates a new TTY and I can attach a serial like ttyS0 and ttyS1 to it from Linux.

For ARM however, I can't get past the first ttyAMA0. If I add -serial, it does not show on info qtree and the kernel boot messages only find one:

9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 54, base_baud = 0) is a PL011 rev1 

Is it possible, and if not, is there a specific design reason for that?

I'm on QEMU v3.0.0. From the source it does not seem possible: https://github.com/qemu/qemu/blob/v3.0.0/hw/arm/virt.c#L138 as there is only one UART in the memory map:

[VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 }, [VIRT_UART] = { 0x09000000, 0x00001000 }, [VIRT_RTC] = { 0x09010000, 0x00001000 }, 

I'm doing this because I'm trying to see if connecting from a different serial will make KGDB work. On x86_64 I can connect to ttyS0, but I thought it was worth giving this a try on ARM. See also: https://stackoverflow.com/questions/22004616/how-to-debug-the-linux-kernel-with-qemu-and-kgdb/44197715#44197715

2
  • Hi, you said I can attach a serial like ttyS0 and ttyS1 to it from Linux, how can I do that? Commented Oct 13, 2022 at 14:01
  • @ChanKim -serial telnet::1235,server,nowait on QEMU and telnet localhost 1235 on host to get a shell on guest. Guest must do its own userland setup for that, but it is simple, usually part of init system. Example: cirosantilli.com/linux-kernel-module-cheat/#tty Commented Oct 13, 2022 at 19:24

4 Answers 4

6

this is my multiple serial support patch for virt board in qemu 4.0.1 (official git commit is f9bec78), please enjoy it :-)

diff --git a/hw/arm/virt-acpi-build.c b/hw/arm/virt-acpi-build.c index bf9c0bc..bdc7094 100644 --- a/hw/arm/virt-acpi-build.c +++ b/hw/arm/virt-acpi-build.c @@ -474,11 +474,11 @@ build_iort(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) } static void -build_spcr(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) +build_spcr(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms, int uart) { AcpiSerialPortConsoleRedirection *spcr; - const MemMapEntry *uart_memmap = &vms->memmap[VIRT_UART]; - int irq = vms->irqmap[VIRT_UART] + ARM_SPI_BASE; + const MemMapEntry *uart_memmap = &vms->memmap[uart]; + int irq = vms->irqmap[uart] + ARM_SPI_BASE; int spcr_start = table_data->len; spcr = acpi_data_push(table_data, sizeof(*spcr)); @@ -741,8 +741,14 @@ build_dsdt(GArray *table_data, BIOSLinker *linker, VirtMachineState *vms) */ scope = aml_scope("\\_SB"); acpi_dsdt_add_cpus(scope, vms->smp_cpus); - acpi_dsdt_add_uart(scope, &memmap[VIRT_UART], - (irqmap[VIRT_UART] + ARM_SPI_BASE)); + acpi_dsdt_add_uart(scope, &memmap[VIRT_UART0], + (irqmap[VIRT_UART0] + ARM_SPI_BASE)); + acpi_dsdt_add_uart(scope, &memmap[VIRT_UART1], + (irqmap[VIRT_UART1] + ARM_SPI_BASE)); + acpi_dsdt_add_uart(scope, &memmap[VIRT_UART2], + (irqmap[VIRT_UART2] + ARM_SPI_BASE)); + acpi_dsdt_add_uart(scope, &memmap[VIRT_UART3], + (irqmap[VIRT_UART3] + ARM_SPI_BASE)); acpi_dsdt_add_flash(scope, &memmap[VIRT_FLASH]); acpi_dsdt_add_fw_cfg(scope, &memmap[VIRT_FW_CFG]); acpi_dsdt_add_virtio(scope, &memmap[VIRT_MMIO], @@ -806,7 +812,16 @@ void virt_acpi_build(VirtMachineState *vms, AcpiBuildTables *tables) build_mcfg(tables_blob, tables->linker, vms); acpi_add_table(table_offsets, tables_blob); - build_spcr(tables_blob, tables->linker, vms); + build_spcr(tables_blob, tables->linker, vms, VIRT_UART0); + + acpi_add_table(table_offsets, tables_blob); + build_spcr(tables_blob, tables->linker, vms, VIRT_UART1); + + acpi_add_table(table_offsets, tables_blob); + build_spcr(tables_blob, tables->linker, vms, VIRT_UART2); + + acpi_add_table(table_offsets, tables_blob); + build_spcr(tables_blob, tables->linker, vms, VIRT_UART3); if (nb_numa_nodes > 0) { acpi_add_table(table_offsets, tables_blob); diff --git a/hw/arm/virt.c b/hw/arm/virt.c index ce2664a..e3006c6 100644 --- a/hw/arm/virt.c +++ b/hw/arm/virt.c @@ -125,12 +125,15 @@ static const MemMapEntry base_memmap[] = { [VIRT_GIC_ITS] = { 0x08080000, 0x00020000 }, /* This redistributor space allows up to 2*64kB*123 CPUs */ [VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 }, - [VIRT_UART] = { 0x09000000, 0x00001000 }, + [VIRT_UART0] = { 0x09000000, 0x00001000 }, [VIRT_RTC] = { 0x09010000, 0x00001000 }, [VIRT_FW_CFG] = { 0x09020000, 0x00000018 }, [VIRT_GPIO] = { 0x09030000, 0x00001000 }, [VIRT_SECURE_UART] = { 0x09040000, 0x00001000 }, [VIRT_SMMU] = { 0x09050000, 0x00020000 }, + [VIRT_UART1] = { 0x09080000, 0x00001000 }, + [VIRT_UART2] = { 0x09090000, 0x00001000 }, + [VIRT_UART3] = { 0x090a0000, 0x00001000 }, [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, @@ -161,11 +164,14 @@ static MemMapEntry extended_memmap[] = { }; static const int a15irqmap[] = { - [VIRT_UART] = 1, + [VIRT_UART0] = 1, [VIRT_RTC] = 2, [VIRT_PCIE] = 3, /* ... to 6 */ [VIRT_GPIO] = 7, [VIRT_SECURE_UART] = 8, + [VIRT_UART1] = 9, + [VIRT_UART2] = 10, + [VIRT_UART3] = 11, [VIRT_MMIO] = 16, /* ...to 16 + NUM_VIRTIO_TRANSPORTS - 1 */ [VIRT_GIC_V2M] = 48, /* ...to 48 + NUM_GICV2M_SPIS - 1 */ [VIRT_SMMU] = 74, /* ...to 74 + NUM_SMMU_IRQS - 1 */ @@ -684,11 +690,16 @@ static void create_uart(const VirtMachineState *vms, qemu_irq *pic, int uart, hwaddr base = vms->memmap[uart].base; hwaddr size = vms->memmap[uart].size; int irq = vms->irqmap[uart]; + + if(chr == NULL) + return; + const char compat[] = "arm,pl011\0arm,primecell"; const char clocknames[] = "uartclk\0apb_pclk"; + DeviceState *dev = qdev_create(NULL, "pl011"); SysBusDevice *s = SYS_BUS_DEVICE(dev); - + qdev_prop_set_chr(dev, "chardev", chr); qdev_init_nofail(dev); memory_region_add_subregion(mem, base, @@ -710,9 +721,9 @@ static void create_uart(const VirtMachineState *vms, qemu_irq *pic, int uart, qemu_fdt_setprop(vms->fdt, nodename, "clock-names", clocknames, sizeof(clocknames)); - if (uart == VIRT_UART) { + if (uart == VIRT_UART0) { qemu_fdt_setprop_string(vms->fdt, "/chosen", "stdout-path", nodename); - } else { + } else if ((uart != VIRT_UART1) && (uart != VIRT_UART2) && (uart != VIRT_UART3)) { /* Mark as not usable by the normal world */ qemu_fdt_setprop_string(vms->fdt, nodename, "status", "disabled"); qemu_fdt_setprop_string(vms->fdt, nodename, "secure-status", "okay"); @@ -1616,11 +1627,14 @@ static void machvirt_init(MachineState *machine) fdt_add_pmu_nodes(vms); - create_uart(vms, pic, VIRT_UART, sysmem, serial_hd(0)); + create_uart(vms, pic, VIRT_UART0, sysmem, serial_hd(0)); + create_uart(vms, pic, VIRT_UART1, sysmem, serial_hd(1)); + create_uart(vms, pic, VIRT_UART2, sysmem, serial_hd(2)); + create_uart(vms, pic, VIRT_UART3, sysmem, serial_hd(3)); if (vms->secure) { create_secure_ram(vms, secure_sysmem); - create_uart(vms, pic, VIRT_SECURE_UART, secure_sysmem, serial_hd(1)); + create_uart(vms, pic, VIRT_SECURE_UART, secure_sysmem, serial_hd(4)); } vms->highmem_ecam &= vms->highmem && (!firmware_loaded || aarch64); diff --git a/include/hw/arm/virt.h b/include/hw/arm/virt.h index 507517c..5f6228f 100644 --- a/include/hw/arm/virt.h +++ b/include/hw/arm/virt.h @@ -65,7 +65,10 @@ enum { VIRT_GIC_ITS, VIRT_GIC_REDIST, VIRT_SMMU, - VIRT_UART, + VIRT_UART0, + VIRT_UART1, + VIRT_UART2, + VIRT_UART3, VIRT_MMIO, VIRT_RTC, VIRT_FW_CFG, 

config and build

./configure --enable-kvm --enable-sdl --enable-debug --enable-debug-stack-usage --target-list=aarch64-softmmu,arm-softmmu,x86_64-softmmu,i386-softmmu make && make install 

run

qemu-system-aarch64 \ -cpu cortex-a57 \ -smp 16 \ -m 1G \ -machine virt,gic-version=3,virtualization=on \ -nographic \ -net nic,model=virtio,macaddr=fa:16:3e:4d:58:6f \ -net tap,ifname=tap-jh,script=vm-ifup,downscript=vm-ifdown \ -append "root=/dev/vda mem=768M console=ttyAMA0" \ -kernel arm64-vmlinux.img \ -initrd arm64-initrd.img \ -drive file=arm64-rootfs.img,format=raw,id=disk,if=none \ -device virtio-blk-device,drive=disk \ -monitor tcp::45459,server,nowait \ -serial tcp::45458,server,nowait \ -serial tcp::45457,server,nowait \ -serial tcp::45456,server,nowait \ -serial chardev:s0 -chardev stdio,id=s0,mux=on,logfile=qemu-s0.log,signal=off 
1
  • Awesome.......! Commented Nov 1, 2019 at 7:13
3

Peter has clarified that it is not currently supported and why at http://lists.nongnu.org/archive/html/qemu-discuss/2018-11/msg00001.html :

No, we only have a single UART on the virt board (because at the time I wrote the code I didn't see a reason for having more). We've had requests for a second UART before. The problem with adding it is that it will break UEFI booting, because if you have two UARTS in the dtb then Linux will use the first listed but UEFI will use the second, so commandlines which used to work will stop working.

This is probably handleable by either:

  • only creating the 2nd UART if given a -machine option to specifically ask for it
  • creating the 2nd UART on-demand based on how many -serial options were passed

but the first of those is clunky and I'm a bit worried the second will have unexpected side effects (eg via however libvirt starts QEMU.)

So it kind of went on the "wishlist" feature list. (The actual code required is probably only a dozen or so lines, it's figuring out the best mechanism for determining whether to create the second UART that's hard.)

2

As Ciro Santilli mentioned, the qemu-system-aarch64 has only one serial port: ttyAMA0.

But there is an easier way than patching qemu, we can add a PCI Device that adds more serial ports:

qemu-system-aarch64 \ -machine virt \ -cpu max \ -m 1024 \ -drive file=arch_aarch64.qcow2,format=qcow2 \ - -serial stdio \ + -chardev stdio,mux=on,id=char0 \ + -chardev socket,path=/tmp/qemu_socket.sock,server=on,wait=off,id=gnc0 \ + -mon chardev=char0,mode=readline \ + -serial chardev:char0 \ + -device pci-serial,id=serial0,chardev=gnc0 \ -kernel "<path to linux directory>/arch/arm64/boot/Image.gz" \ - -append "root=/dev/vda2" + -append "root=/dev/vda2 console=ttyAMA0 kgdboc=ttyS0 kgdbwait" 

In this diff we add the pci-serial device, configure which serial port is available where (stdio and socket) and tell the kernel which serial ports to use (console=ttyAMA0 kgdboc=ttyS0).

Then to use KGDB you just have to connect to the socket:

gdb ./vmlinux (gdb) target remote /tmp/qemu_socket.sock 

I've also explained it in more detail here

1

Another solution is to add -device pci-serial-2x,chardev1=ttyS0,chardev2=ttyS1 as an option to qemu-system-aarch64, where ttyS0 and ttyS1 are defined by -chardev option of qemu-system-aarch64. By doing this, /dev/ttyS0 and /dev/ttyS1 will appear in VM in addition to /dev/ttyAMA0.

1
  • Property 'pci-serial-2x.chardev1' can't find value 'ttyS0' Commented Sep 12, 2023 at 5:35

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.