Signed-off-by: gilles grimaud <[email protected]>
---
hw/arm/raspi_pico.c | 33 +++++++
hw/arm/rp2040.c | 58 +++++++++++-
hw/char/pl011.c | 86 +++++++++++++++++-
include/hw/arm/rp2040.h | 5 ++
include/hw/char/pl011.h | 6 ++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-uart-test.c | 139 +++++++++++++++++++++++++++++
tests/tcg/arm/system/rp2040-mpu.S | 5 ++
tests/tcg/arm/system/rp2040-uart.S | 5 ++
9 files changed, 333 insertions(+), 5 deletions(-)
create mode 100644 tests/qtest/rp2040-uart-test.c
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
index 1c01a27e89..e361a0dbc9 100644
--- a/hw/arm/raspi_pico.c
+++ b/hw/arm/raspi_pico.c
@@ -30,6 +30,7 @@ struct RaspiPicoMachineState {
MemoryRegion flash;
uint64_t rosc_random_seed;
bool rosc_random_seed_set;
+ bool strict_uart_pins;
};
static void raspi_pico_get_rosc_random_seed(Object *obj, Visitor *v,
@@ -65,6 +66,8 @@ static void raspi_pico_init(MachineState *machine)
object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
+ qdev_prop_set_bit(DEVICE(&s->soc), "strict-uart-pins",
+ s->strict_uart_pins);
qdev_prop_set_uint64(DEVICE(&s->soc.rosc), "random-seed",
s->rosc_random_seed);
qdev_prop_set_bit(DEVICE(&s->soc.rosc), "random-seed-set",
@@ -86,6 +89,28 @@ static void raspi_pico_init(MachineState *machine)
RP2040_XIP_BASE, PICO_FLASH_SIZE);
}
+static bool raspi_pico_get_strict_uart_pins(Object *obj, Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ return s->strict_uart_pins;
+}
+
+static void raspi_pico_set_strict_uart_pins(Object *obj, bool value,
+ Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ s->strict_uart_pins = value;
+}
+
+static void raspi_pico_machine_initfn(Object *obj)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ s->strict_uart_pins = true;
+}
+
static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -107,12 +132,20 @@ static void raspi_pico_machine_class_init(ObjectClass
*oc, const void *data)
"Use a deterministic seed for the "
"ROSC RANDOMBIT stream; if unset, "
"QEMU guest entropy is used");
+ object_class_property_add_bool(oc, "strict-uart-pins",
+ raspi_pico_get_strict_uart_pins,
+ raspi_pico_set_strict_uart_pins);
+ object_class_property_set_description(oc, "strict-uart-pins",
+ "Require the RP2040 IO_BANK0 "
+ "UART pinmux before UART0 or UART1 "
+ "reaches its host serial backend");
}
static const TypeInfo raspi_pico_machine_info = {
.name = TYPE_RASPI_PICO_MACHINE,
.parent = TYPE_MACHINE,
.instance_size = sizeof(RaspiPicoMachineState),
+ .instance_init = raspi_pico_machine_initfn,
.class_init = raspi_pico_machine_class_init,
};
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index aa1407b4dd..b503a4213c 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -50,8 +50,6 @@ static const struct {
hwaddr size;
} rp2040_unimplemented[] = {
{ "rp2040.busctrl", 0x40030000, 0x4000 },
- { "rp2040.uart0_aliases", 0x40035000, 0x3000 },
- { "rp2040.uart1_aliases", 0x40039000, 0x3000 },
{ "rp2040.spi0", 0x4003c000, 0x4000 },
{ "rp2040.spi1", 0x40040000, 0x4000 },
{ "rp2040.i2c0", 0x40044000, 0x4000 },
@@ -102,11 +100,54 @@ static void rp2040_set_irq(void *opaque, int irq, int
level)
rp2040_update_nmi(s);
}
+static void rp2040_update_uart_pins(RP2040State *s)
+{
+ pl011_set_tx_connected(&s->uart[0],
+ !s->strict_uart_pins ||
+ s->uart0_tx_pin_enabled);
+ pl011_set_rx_connected(&s->uart[0],
+ !s->strict_uart_pins ||
+ s->uart0_rx_pin_enabled);
+ pl011_set_tx_connected(&s->uart[1],
+ !s->strict_uart_pins ||
+ s->uart1_tx_pin_enabled);
+ pl011_set_rx_connected(&s->uart[1],
+ !s->strict_uart_pins ||
+ s->uart1_rx_pin_enabled);
+}
+
+static void rp2040_set_uart_pin(void *opaque, int pin, int level)
+{
+ RP2040State *s = opaque;
+
+ switch (pin) {
+ case 0:
+ s->uart0_tx_pin_enabled = level;
+ break;
+ case 1:
+ s->uart0_rx_pin_enabled = level;
+ break;
+ case 2:
+ s->uart1_tx_pin_enabled = level;
+ break;
+ case 3:
+ s->uart1_rx_pin_enabled = level;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ rp2040_update_uart_pins(s);
+}
+
static void rp2040_soc_init(Object *obj)
{
RP2040State *s = RP2040(obj);
int i;
+ qdev_init_gpio_in_named(DEVICE(obj), rp2040_set_uart_pin,
+ "uart-pin", 4);
+
for (i = 0; i < RP2040_NUM_CORES; i++) {
g_autofree char *name = g_strdup_printf("proc%d", i);
@@ -329,6 +370,14 @@ static void rp2040_soc_realize(DeviceState *dev, Error
**errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->iobank0), 0, RP2040_IOBANK0_BASE);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 0,
s->irq[RP2040_IO_IRQ_BANK0]);
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 0,
+ qdev_get_gpio_in_named(dev, "uart-pin", 0));
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 1,
+ qdev_get_gpio_in_named(dev, "uart-pin", 1));
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 0,
+ qdev_get_gpio_in_named(dev, "uart-pin", 2));
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 1,
+ qdev_get_gpio_in_named(dev, "uart-pin", 3));
if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) {
return;
@@ -383,17 +432,20 @@ static void rp2040_soc_realize(DeviceState *dev, Error
**errp)
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 1,
+ uart_base[i] + 0x1000);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
s->irq[uart_irq[i]]);
}
-
rp2040_update_nmi(s);
+ rp2040_update_uart_pins(s);
}
static const Property rp2040_soc_properties[] = {
DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGION,
MemoryRegion *),
DEFINE_PROP_STRING("bootrom-file", RP2040State, bootrom_file),
+ DEFINE_PROP_BOOL("strict-uart-pins", RP2040State, strict_uart_pins, true),
};
static void rp2040_soc_class_init(ObjectClass *klass, const void *data)
diff --git a/hw/char/pl011.c b/hw/char/pl011.c
index 7e982e54bb..828c314100 100644
--- a/hw/char/pl011.c
+++ b/hw/char/pl011.c
@@ -101,6 +101,12 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq,
Chardev *chr)
/* Fractional Baud Rate Divider, UARTFBRD */
#define FBRD_MASK 0x3f
+/* RP2040 APB atomic register aliases. */
+#define PL011_ATOMIC_ALIAS_MASK 0x3000
+#define PL011_ATOMIC_XOR 0x1000
+#define PL011_ATOMIC_SET 0x2000
+#define PL011_ATOMIC_CLR 0x3000
+
static const unsigned char pl011_id_arm[8] =
{ 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
static const unsigned char pl011_id_luminary[8] =
@@ -173,6 +179,21 @@ static void pl011_update_dreq(PL011State *s)
(s->dmacr & DMACR_RXDMAE) && s->read_count > 0);
}
+static uint32_t pl011_apply_atomic_alias(uint32_t old, uint32_t value,
+ hwaddr alias)
+{
+ switch (alias) {
+ case PL011_ATOMIC_XOR:
+ return old ^ value;
+ case PL011_ATOMIC_SET:
+ return old | value;
+ case PL011_ATOMIC_CLR:
+ return old & ~value;
+ default:
+ return value;
+ }
+}
+
static inline void pl011_reset_rx_fifo(PL011State *s)
{
s->read_count = 0;
@@ -271,7 +292,13 @@ static void pl011_write_txdata(PL011State *s, uint8_t data)
* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks
*/
- qemu_chr_fe_write_all(&s->chr, &data, 1);
+ if (s->tx_connected) {
+ qemu_chr_fe_write_all(&s->chr, &data, 1);
+ } else if (!s->logged_disconnected_tx) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PL011 data written while TX pin is disconnected\n");
+ s->logged_disconnected_tx = true;
+ }
pl011_loopback_tx(s, data);
s->int_level |= INT_TX;
pl011_update(s);
@@ -542,6 +569,10 @@ static int pl011_can_receive(void *opaque)
* UART continuously enabled regardless of the enable bits.
*/
+ if (!s->rx_connected) {
+ return 0;
+ }
+
trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available);
return fifo_available;
}
@@ -549,6 +580,9 @@ static int pl011_can_receive(void *opaque)
static void pl011_receive(void *opaque, const uint8_t *buf, int size)
{
trace_pl011_receive(size);
+ if (!PL011(opaque)->rx_connected) {
+ return;
+ }
/*
* In loopback mode, the RX input signal is internally disconnected
* from the entire receiving logics; thus, all inputs are ignored,
@@ -565,11 +599,45 @@ static void pl011_receive(void *opaque, const uint8_t
*buf, int size)
static void pl011_event(void *opaque, QEMUChrEvent event)
{
- if (event == CHR_EVENT_BREAK && !pl011_loopback_enabled(opaque)) {
+ PL011State *s = opaque;
+
+ if (event == CHR_EVENT_BREAK && s->rx_connected &&
+ !pl011_loopback_enabled(opaque)) {
pl011_fifo_rx_put(opaque, DR_BE);
}
}
+void pl011_set_tx_connected(PL011State *s, bool connected)
+{
+ s->tx_connected = connected;
+ if (connected) {
+ s->logged_disconnected_tx = false;
+ }
+}
+
+void pl011_set_rx_connected(PL011State *s, bool connected)
+{
+ s->rx_connected = connected;
+ qemu_chr_fe_accept_input(&s->chr);
+}
+
+static uint64_t pl011_atomic_alias_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ return pl011_read(opaque, offset & 0xfff, size);
+}
+
+static void pl011_atomic_alias_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ hwaddr alias = (offset + PL011_ATOMIC_XOR) & PL011_ATOMIC_ALIAS_MASK;
+ hwaddr reg = offset & 0xfff;
+ uint32_t old = pl011_read(opaque, reg, size);
+ uint32_t new = pl011_apply_atomic_alias(old, value, alias);
+
+ pl011_write(opaque, reg, new, size);
+}
+
static void pl011_clock_update(void *opaque, ClockEvent event)
{
PL011State *s = PL011(opaque);
@@ -585,6 +653,14 @@ static const MemoryRegionOps pl011_ops = {
.impl.max_access_size = 4,
};
+static const MemoryRegionOps pl011_atomic_alias_ops = {
+ .read = pl011_atomic_alias_read,
+ .write = pl011_atomic_alias_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
static bool pl011_clock_needed(void *opaque)
{
PL011State *s = PL011(opaque);
@@ -673,8 +749,14 @@ static void pl011_init(Object *obj)
PL011State *s = PL011(obj);
int i;
+ s->tx_connected = true;
+ s->rx_connected = true;
memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011",
0x1000);
sysbus_init_mmio(sbd, &s->iomem);
+ memory_region_init_io(&s->atomic_alias_iomem, OBJECT(s),
+ &pl011_atomic_alias_ops, s,
+ "pl011-atomic-alias", 0x3000);
+ sysbus_init_mmio(sbd, &s->atomic_alias_iomem);
for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
sysbus_init_irq(sbd, &s->irq[i]);
}
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index fdfcf28f3b..6beb4cafff 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -77,6 +77,11 @@ struct RP2040State {
qemu_irq cpu_irq[RP2040_NUM_CORES][RP2040_NUM_IRQS];
qemu_irq nmi_irq[RP2040_NUM_CORES];
bool irq_level[RP2040_NUM_IRQS];
+ bool strict_uart_pins;
+ bool uart0_tx_pin_enabled;
+ bool uart0_rx_pin_enabled;
+ bool uart1_tx_pin_enabled;
+ bool uart1_rx_pin_enabled;
Clock *sysclk;
};
diff --git a/include/hw/char/pl011.h b/include/hw/char/pl011.h
index c7b5a3a2b3..20c0b5694b 100644
--- a/include/hw/char/pl011.h
+++ b/include/hw/char/pl011.h
@@ -32,6 +32,7 @@ struct PL011State {
SysBusDevice parent_obj;
MemoryRegion iomem;
+ MemoryRegion atomic_alias_iomem;
uint32_t flags;
uint32_t lcr;
uint32_t rsr;
@@ -53,7 +54,10 @@ struct PL011State {
qemu_irq dreq_rx;
Clock *clk;
bool migrate_clk;
+ bool tx_connected;
+ bool rx_connected;
bool logged_disabled_uart;
+ bool logged_disconnected_tx;
const unsigned char *id;
/*
* Since some users embed this struct directly, we must
@@ -63,5 +67,7 @@ struct PL011State {
};
DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr);
+void pl011_set_tx_connected(PL011State *s, bool connected);
+void pl011_set_rx_connected(PL011State *s, bool connected);
#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 8009ec7f8d..dee90c57c8 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -272,6 +272,7 @@ qtests_arm = \
'rp2040-resets-test',
'rp2040-rosc-test',
'rp2040-tbman-test',
+ 'rp2040-uart-test',
'rp2040-vreg-test',
'rp2040-watchdog-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : [])
+ \
diff --git a/tests/qtest/rp2040-uart-test.c b/tests/qtest/rp2040-uart-test.c
new file mode 100644
index 0000000000..05d2f3c763
--- /dev/null
+++ b/tests/qtest/rp2040-uart-test.c
@@ -0,0 +1,139 @@
+/*
+ * QTest testcase for the RP2040 UART pinmux integration.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "hw/misc/rp2040.h"
+
+#define IOBANK0_BASE 0x40014000
+#define GPIO_CTRL(n) (0x004 + (n) * 8)
+#define GPIO_FUNC_UART 2
+
+#define UART0_BASE 0x40034000
+#define UART1_BASE 0x40038000
+#define UART_DR 0x000
+#define UART_FR 0x018
+#define UART_CR 0x030
+#define UART_FR_RXFE 0x10
+
+static char *new_output_path(void)
+{
+ int fd;
+ char *path = NULL;
+
+ fd = g_file_open_tmp("rp2040-uart-XXXXXX", &path, NULL);
+ g_assert_cmpint(fd, >=, 0);
+ close(fd);
+ return path;
+}
+
+static void assert_file_contents(const char *path, const char *expected)
+{
+ g_autofree char *contents = NULL;
+ gsize length;
+
+ g_assert_true(g_file_get_contents(path, &contents, &length, NULL));
+ g_assert_cmpuint(length, ==, strlen(expected));
+ g_assert_cmpmem(contents, length, expected, strlen(expected));
+}
+
+static void test_uart0_pinmux(void)
+{
+ g_autofree char *path = new_output_path();
+ QTestState *qts = qtest_initf("-machine raspi-pico "
+ "-chardev file,id=uart0,path=%s "
+ "-serial chardev:uart0", path);
+
+ qtest_writel(qts, UART0_BASE + UART_DR, 'X');
+ qtest_writel(qts, IOBANK0_BASE + GPIO_CTRL(0), GPIO_FUNC_UART);
+ qtest_writel(qts, UART0_BASE + UART_DR, '0');
+ qtest_quit(qts);
+
+ assert_file_contents(path, "0");
+ unlink(path);
+}
+
+static void test_uart1_pinmux(void)
+{
+ g_autofree char *path = new_output_path();
+ QTestState *qts = qtest_initf("-machine raspi-pico -serial null "
+ "-chardev file,id=uart1,path=%s "
+ "-serial chardev:uart1", path);
+
+ qtest_writel(qts, UART1_BASE + UART_DR, 'X');
+ qtest_writel(qts, IOBANK0_BASE + GPIO_CTRL(4), GPIO_FUNC_UART);
+ qtest_writel(qts, UART1_BASE + UART_DR, '1');
+ qtest_quit(qts);
+
+ assert_file_contents(path, "1");
+ unlink(path);
+}
+
+static void test_non_strict_uart_pins(void)
+{
+ g_autofree char *path = new_output_path();
+ QTestState *qts = qtest_initf("-machine raspi-pico,strict-uart-pins=off "
+ "-chardev file,id=uart0,path=%s "
+ "-serial chardev:uart0", path);
+
+ qtest_writel(qts, UART0_BASE + UART_DR, 'L');
+ qtest_quit(qts);
+
+ assert_file_contents(path, "L");
+ unlink(path);
+}
+
+static void test_uart0_rx_pinmux(void)
+{
+ int sock_fd;
+ int retries;
+ QTestState *qts = qtest_init_with_serial("-machine raspi-pico",
+ &sock_fd);
+
+ qtest_writel(qts, IOBANK0_BASE + GPIO_CTRL(1), GPIO_FUNC_UART);
+ g_assert_cmpint(send(sock_fd, "R", 1, 0), ==, 1);
+ for (retries = 0; retries < 1000; retries++) {
+ if (!(qtest_readl(qts, UART0_BASE + UART_FR) & UART_FR_RXFE)) {
+ break;
+ }
+ g_usleep(1000);
+ }
+ g_assert_cmpint(retries, <, 1000);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_DR), ==, 'R');
+
+ close(sock_fd);
+ qtest_quit(qts);
+}
+
+static void test_atomic_aliases(void)
+{
+ QTestState *qts = qtest_init("-machine raspi-pico");
+ uint32_t reset = qtest_readl(qts, UART0_BASE + UART_CR);
+
+ qtest_writel(qts, UART0_BASE + RP2040_ATOMIC_CLR + UART_CR, 0x100);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), ==,
+ reset & ~0x100);
+ qtest_writel(qts, UART0_BASE + RP2040_ATOMIC_SET + UART_CR, 0x100);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), ==, reset);
+ qtest_writel(qts, UART0_BASE + RP2040_ATOMIC_XOR + UART_CR, 0x200);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), ==,
+ reset ^ 0x200);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-uart/uart0-pinmux", test_uart0_pinmux);
+ qtest_add_func("/rp2040-uart/uart1-pinmux", test_uart1_pinmux);
+ qtest_add_func("/rp2040-uart/non-strict", test_non_strict_uart_pins);
+ qtest_add_func("/rp2040-uart/uart0-rx-pinmux", test_uart0_rx_pinmux);
+ qtest_add_func("/rp2040-uart/atomic-aliases", test_atomic_aliases);
+
+ return g_test_run();
+}
diff --git a/tests/tcg/arm/system/rp2040-mpu.S
b/tests/tcg/arm/system/rp2040-mpu.S
index 5cc98f3d0e..60fa1292e6 100644
--- a/tests/tcg/arm/system/rp2040-mpu.S
+++ b/tests/tcg/arm/system/rp2040-mpu.S
@@ -13,6 +13,8 @@
#define TEST_ADDRESS 0x20001000
#define TEST_VALUE 0x12345678
#define UART0_DR 0x40034000
+#define GPIO0_CTRL 0x40014004
+#define GPIO_FUNC_UART 2
#define MPU_TYPE 0xe000ed90
#define MPU_CTRL 0xe000ed94
#define MPU_RNR 0xe000ed98
@@ -42,6 +44,9 @@ vector_table:
.thumb_func
.global reset_handler
reset_handler:
+ ldr r0, =GPIO0_CTRL
+ movs r1, GPIO_FUNC_UART
+ str r1, [r0]
ldr r0, =start_message
bl puts
diff --git a/tests/tcg/arm/system/rp2040-uart.S
b/tests/tcg/arm/system/rp2040-uart.S
index bdb58cc7f2..6058699200 100644
--- a/tests/tcg/arm/system/rp2040-uart.S
+++ b/tests/tcg/arm/system/rp2040-uart.S
@@ -11,6 +11,8 @@
#define SRAM_END 0x20042000
#define UART0_DR 0x40034000
+#define GPIO0_CTRL 0x40014004
+#define GPIO_FUNC_UART 2
#define SYS_EXIT 0x18
#define ADP_STOPPED_APPLICATION_EXIT 0x20026
@@ -25,6 +27,9 @@ vector_table:
.thumb_func
.global reset_handler
reset_handler:
+ ldr r0, =GPIO0_CTRL
+ movs r1, GPIO_FUNC_UART
+ str r1, [r0]
ldr r0, =UART0_DR
ldr r1, =message
--
2.55.0