[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-arm] [PATCH v2 2/5] bcm2835_aux: add emulation of BCM2835 AUX (aka
From: |
Andrew Baumann |
Subject: |
[Qemu-arm] [PATCH v2 2/5] bcm2835_aux: add emulation of BCM2835 AUX (aka UART1) block |
Date: |
Thu, 3 Mar 2016 10:24:36 -0800 |
At present only the core UART functions (data path for tx/rx) are
implemented, which is enough for UEFI to boot. The following
features/registers are unimplemented:
* Line/modem control
* Scratch register
* Extra control
* Baudrate
* SPI interfaces
Signed-off-by: Andrew Baumann <address@hidden>
---
Notes:
v2:
* document unimplemented features, log unimplemented register accesses
* model read path as 8-bit
* drop incorrect event (break detection) functionality
* implement AUX_IRQ register
* model interrupt enables as a uint8 rather than 2 bools
* corrected bugs in implementation of IIR bits 0-2
* use chardev prop rather than qemu_char_get_next_serial(); the
soc-level (bcm2835_peripherals) handling is still a little messy
however, because uart0 is a pl011 device which still calls
qemu_char_get_next_serial()
hw/arm/bcm2835_peripherals.c | 29 ++++
hw/char/Makefile.objs | 1 +
hw/char/bcm2835_aux.c | 316 +++++++++++++++++++++++++++++++++++
include/hw/arm/bcm2835_peripherals.h | 2 +
include/hw/char/bcm2835_aux.h | 33 ++++
5 files changed, 381 insertions(+)
create mode 100644 hw/char/bcm2835_aux.c
create mode 100644 include/hw/char/bcm2835_aux.h
diff --git a/hw/arm/bcm2835_peripherals.c b/hw/arm/bcm2835_peripherals.c
index 6ce9cd1..375e341 100644
--- a/hw/arm/bcm2835_peripherals.c
+++ b/hw/arm/bcm2835_peripherals.c
@@ -12,6 +12,8 @@
#include "hw/arm/bcm2835_peripherals.h"
#include "hw/misc/bcm2835_mbox_defs.h"
#include "hw/arm/raspi_platform.h"
+#include "sysemu/char.h"
+#include "sysemu/sysemu.h" /* for serial_hds */
/* Peripheral base address on the VC (GPU) system bus */
#define BCM2835_VC_PERI_BASE 0x7e000000
@@ -48,6 +50,11 @@ static void bcm2835_peripherals_init(Object *obj)
object_property_add_child(obj, "uart0", OBJECT(s->uart0), NULL);
qdev_set_parent_bus(DEVICE(s->uart0), sysbus_get_default());
+ /* AUX / UART1 */
+ object_initialize(&s->aux, sizeof(s->aux), TYPE_BCM2835_AUX);
+ object_property_add_child(obj, "aux", OBJECT(&s->aux), NULL);
+ qdev_set_parent_bus(DEVICE(&s->aux), sysbus_get_default());
+
/* Mailboxes */
object_initialize(&s->mboxes, sizeof(s->mboxes), TYPE_BCM2835_MBOX);
object_property_add_child(obj, "mbox", OBJECT(&s->mboxes), NULL);
@@ -79,6 +86,7 @@ static void bcm2835_peripherals_realize(DeviceState *dev,
Error **errp)
MemoryRegion *ram;
Error *err = NULL;
uint32_t ram_size;
+ CharDriverState *chr;
int n;
obj = object_property_get_link(OBJECT(dev), "ram", &err);
@@ -131,6 +139,27 @@ static void bcm2835_peripherals_realize(DeviceState *dev,
Error **errp)
qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ,
INTERRUPT_UART));
+ /* AUX / UART1 */
+ /* XXX: pl011 (uart0) uses qemu_char_get_next_serial(), so at this point it
+ * should have claimed the first serial device (if one exists) */
+ chr = serial_hds[1];
+ if (chr == NULL) {
+ chr = qemu_chr_new("bcm2835.uart1", "null", NULL);
+ }
+ qdev_prop_set_chr(DEVICE(&s->aux), "chardev", chr);
+
+ object_property_set_bool(OBJECT(&s->aux), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ memory_region_add_subregion(&s->peri_mr, UART1_OFFSET,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->aux), 0));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->aux), 0,
+ qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_GPU_IRQ,
+ INTERRUPT_AUX));
+
/* Mailboxes */
object_property_set_bool(OBJECT(&s->mboxes), true, "realized", &err);
if (err) {
diff --git a/hw/char/Makefile.objs b/hw/char/Makefile.objs
index 5931cc8..69a553c 100644
--- a/hw/char/Makefile.objs
+++ b/hw/char/Makefile.objs
@@ -16,6 +16,7 @@ obj-$(CONFIG_SH4) += sh_serial.o
obj-$(CONFIG_PSERIES) += spapr_vty.o
obj-$(CONFIG_DIGIC) += digic-uart.o
obj-$(CONFIG_STM32F2XX_USART) += stm32f2xx_usart.o
+obj-$(CONFIG_RASPI) += bcm2835_aux.o
common-obj-$(CONFIG_ETRAXFS) += etraxfs_ser.o
common-obj-$(CONFIG_ISA_DEBUG) += debugcon.o
diff --git a/hw/char/bcm2835_aux.c b/hw/char/bcm2835_aux.c
new file mode 100644
index 0000000..0394d11
--- /dev/null
+++ b/hw/char/bcm2835_aux.c
@@ -0,0 +1,316 @@
+/*
+ * BCM2835 (Raspberry Pi / Pi 2) Aux block (mini UART and SPI).
+ * Copyright (c) 2015, Microsoft
+ * Written by Andrew Baumann
+ * Based on pl011.c, copyright terms below:
+ *
+ * Arm PrimeCell PL011 UART
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ *
+ * At present only the core UART functions (data path for tx/rx) are
+ * implemented. The following features/registers are unimplemented:
+ * - Line/modem control
+ * - Scratch register
+ * - Extra control
+ * - Baudrate
+ * - SPI interfaces
+ */
+
+#include "qemu/osdep.h"
+#include "hw/char/bcm2835_aux.h"
+
+#define AUX_IRQ 0x0
+#define AUX_ENABLES 0x4
+#define AUX_MU_IO_REG 0x40
+#define AUX_MU_IER_REG 0x44
+#define AUX_MU_IIR_REG 0x48
+#define AUX_MU_LCR_REG 0x4c
+#define AUX_MU_MCR_REG 0x50
+#define AUX_MU_LSR_REG 0x54
+#define AUX_MU_MSR_REG 0x58
+#define AUX_MU_SCRATCH 0x5c
+#define AUX_MU_CNTL_REG 0x60
+#define AUX_MU_STAT_REG 0x64
+#define AUX_MU_BAUD_REG 0x68
+
+/* bits in IER/IIR registers */
+#define TX_INT 0x1
+#define RX_INT 0x2
+
+static void bcm2835_aux_update(BCM2835AuxState *s)
+{
+ /* signal an interrupt if either:
+ * 1. rx interrupt is enabled and we have a non-empty rx fifo, or
+ * 2. the tx interrupt is enabled (since we instantly drain the tx fifo)
+ */
+ s->iir = 0;
+ if ((s->ier & RX_INT) && s->read_count != 0) {
+ s->iir |= RX_INT;
+ }
+ if (s->ier & TX_INT) {
+ s->iir |= TX_INT;
+ }
+ qemu_set_irq(s->irq, s->iir != 0);
+}
+
+static uint64_t bcm2835_aux_read(void *opaque, hwaddr offset, unsigned size)
+{
+ BCM2835AuxState *s = opaque;
+ uint32_t c, res;
+
+ switch (offset) {
+ case AUX_IRQ:
+ return s->iir != 0;
+
+ case AUX_ENABLES:
+ return 1; /* mini UART permanently enabled */
+
+ case AUX_MU_IO_REG:
+ /* "DLAB bit set means access baudrate register" is NYI */
+ c = s->read_fifo[s->read_pos];
+ if (s->read_count > 0) {
+ s->read_count--;
+ if (++s->read_pos == BCM2835_AUX_RX_FIFO_LEN) {
+ s->read_pos = 0;
+ }
+ }
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+ bcm2835_aux_update(s);
+ return c;
+
+ case AUX_MU_IER_REG:
+ /* "DLAB bit set means access baudrate register" is NYI */
+ return 0xc0 | s->ier; /* FIFO enables always read 1 */
+
+ case AUX_MU_IIR_REG:
+ res = 0xc0; /* FIFO enables */
+ /* The spec is unclear on what happens when both tx and rx
+ * interrupts are active, besides that this cannot occur. At
+ * present, we choose to prioritise the rx interrupt, since
+ * the tx fifo is always empty. */
+ if (s->read_count != 0) {
+ res |= 0x4;
+ } else {
+ res |= 0x2;
+ }
+ if (s->iir == 0) {
+ res |= 0x1;
+ }
+ return res;
+
+ case AUX_MU_LCR_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_LCR_REG unsupported\n", __func__);
+ return 0;
+
+ case AUX_MU_MCR_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_MCR_REG unsupported\n", __func__);
+ return 0;
+
+ case AUX_MU_LSR_REG:
+ res = 0x60; /* tx idle, empty */
+ if (s->read_count != 0) {
+ res |= 0x1;
+ }
+ return res;
+
+ case AUX_MU_MSR_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_MSR_REG unsupported\n", __func__);
+ return 0;
+
+ case AUX_MU_SCRATCH:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_SCRATCH unsupported\n", __func__);
+ return 0;
+
+ case AUX_MU_CNTL_REG:
+ return 0x3; /* tx, rx enabled */
+
+ case AUX_MU_STAT_REG:
+ res = 0x30e; /* space in the output buffer, empty tx fifo, idle tx/rx
*/
+ if (s->read_count > 0) {
+ res |= 0x1; /* data in input buffer */
+ assert(s->read_count < BCM2835_AUX_RX_FIFO_LEN);
+ res |= ((uint32_t)s->read_count) << 16; /* rx fifo fill level */
+ }
+ return res;
+
+ case AUX_MU_BAUD_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_BAUD_REG unsupported\n",
__func__);
+ return 0;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+ return 0;
+ }
+}
+
+static void bcm2835_aux_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ BCM2835AuxState *s = opaque;
+ unsigned char ch;
+
+ switch (offset) {
+ case AUX_ENABLES:
+ if (value != 1) {
+ qemu_log_mask(LOG_UNIMP, "%s: unsupported attempt to enable SPI "
+ "or disable UART\n", __func__);
+ }
+ break;
+
+ case AUX_MU_IO_REG:
+ /* "DLAB bit set means access baudrate register" is NYI */
+ ch = value;
+ if (s->chr) {
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+ break;
+
+ case AUX_MU_IER_REG:
+ /* "DLAB bit set means access baudrate register" is NYI */
+ s->ier = value & (TX_INT | RX_INT);
+ bcm2835_aux_update(s);
+ break;
+
+ case AUX_MU_IIR_REG:
+ if (value & 0x2) {
+ s->read_count = 0;
+ }
+ break;
+
+ case AUX_MU_LCR_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_LCR_REG unsupported\n", __func__);
+ break;
+
+ case AUX_MU_MCR_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_MCR_REG unsupported\n", __func__);
+ break;
+
+ case AUX_MU_SCRATCH:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_SCRATCH unsupported\n", __func__);
+ break;
+
+ case AUX_MU_CNTL_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_CNTL_REG unsupported\n",
__func__);
+ break;
+
+ case AUX_MU_BAUD_REG:
+ qemu_log_mask(LOG_UNIMP, "%s: AUX_MU_BAUD_REG unsupported\n",
__func__);
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset %"HWADDR_PRIx"\n",
+ __func__, offset);
+ }
+
+ bcm2835_aux_update(s);
+}
+
+static int bcm2835_aux_can_receive(void *opaque)
+{
+ BCM2835AuxState *s = opaque;
+
+ return s->read_count < BCM2835_AUX_RX_FIFO_LEN;
+}
+
+static void bcm2835_aux_put_fifo(void *opaque, uint8_t value)
+{
+ BCM2835AuxState *s = opaque;
+ int slot;
+
+ slot = s->read_pos + s->read_count;
+ if (slot >= BCM2835_AUX_RX_FIFO_LEN) {
+ slot -= BCM2835_AUX_RX_FIFO_LEN;
+ }
+ s->read_fifo[slot] = value;
+ s->read_count++;
+ if (s->read_count == BCM2835_AUX_RX_FIFO_LEN) {
+ /* buffer full */
+ }
+ bcm2835_aux_update(s);
+}
+
+static void bcm2835_aux_receive(void *opaque, const uint8_t *buf, int size)
+{
+ bcm2835_aux_put_fifo(opaque, *buf);
+}
+
+static const MemoryRegionOps bcm2835_aux_ops = {
+ .read = bcm2835_aux_read,
+ .write = bcm2835_aux_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+};
+
+static const VMStateDescription vmstate_bcm2835_aux = {
+ .name = TYPE_BCM2835_AUX,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(read_fifo, BCM2835AuxState,
+ BCM2835_AUX_RX_FIFO_LEN),
+ VMSTATE_UINT8(read_pos, BCM2835AuxState),
+ VMSTATE_UINT8(read_count, BCM2835AuxState),
+ VMSTATE_UINT8(ier, BCM2835AuxState),
+ VMSTATE_UINT8(iir, BCM2835AuxState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void bcm2835_aux_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ BCM2835AuxState *s = BCM2835_AUX(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &bcm2835_aux_ops, s,
+ TYPE_BCM2835_AUX, 0x100);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+}
+
+static void bcm2835_aux_realize(DeviceState *dev, Error **errp)
+{
+ BCM2835AuxState *s = BCM2835_AUX(dev);
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, bcm2835_aux_can_receive,
+ bcm2835_aux_receive, NULL, s);
+ }
+}
+
+static Property bcm2835_aux_props[] = {
+ DEFINE_PROP_CHR("chardev", BCM2835AuxState, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void bcm2835_aux_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = bcm2835_aux_realize;
+ dc->vmsd = &vmstate_bcm2835_aux;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->props = bcm2835_aux_props;
+}
+
+static const TypeInfo bcm2835_aux_info = {
+ .name = TYPE_BCM2835_AUX,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2835AuxState),
+ .instance_init = bcm2835_aux_init,
+ .class_init = bcm2835_aux_class_init,
+};
+
+static void bcm2835_aux_register_types(void)
+{
+ type_register_static(&bcm2835_aux_info);
+}
+
+type_init(bcm2835_aux_register_types)
diff --git a/include/hw/arm/bcm2835_peripherals.h
b/include/hw/arm/bcm2835_peripherals.h
index 5d888dc..889adf5 100644
--- a/include/hw/arm/bcm2835_peripherals.h
+++ b/include/hw/arm/bcm2835_peripherals.h
@@ -14,6 +14,7 @@
#include "qemu-common.h"
#include "exec/address-spaces.h"
#include "hw/sysbus.h"
+#include "hw/char/bcm2835_aux.h"
#include "hw/intc/bcm2835_ic.h"
#include "hw/misc/bcm2835_property.h"
#include "hw/misc/bcm2835_mbox.h"
@@ -33,6 +34,7 @@ typedef struct BCM2835PeripheralState {
qemu_irq irq, fiq;
SysBusDevice *uart0;
+ BCM2835AuxState aux;
BCM2835ICState ic;
BCM2835PropertyState property;
BCM2835MboxState mboxes;
diff --git a/include/hw/char/bcm2835_aux.h b/include/hw/char/bcm2835_aux.h
new file mode 100644
index 0000000..42f0ee7
--- /dev/null
+++ b/include/hw/char/bcm2835_aux.h
@@ -0,0 +1,33 @@
+/*
+ * Rasperry Pi 2 emulation and refactoring Copyright (c) 2015, Microsoft
+ * Written by Andrew Baumann
+ *
+ * This code is licensed under the GNU GPLv2 and later.
+ */
+
+#ifndef BCM2835_AUX_H
+#define BCM2835_AUX_H
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#define TYPE_BCM2835_AUX "bcm2835-aux"
+#define BCM2835_AUX(obj) OBJECT_CHECK(BCM2835AuxState, (obj), TYPE_BCM2835_AUX)
+
+#define BCM2835_AUX_RX_FIFO_LEN 8
+
+typedef struct {
+ /*< private >*/
+ SysBusDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion iomem;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint8_t read_fifo[BCM2835_AUX_RX_FIFO_LEN];
+ uint8_t read_pos, read_count;
+ uint8_t ier, iir;
+} BCM2835AuxState;
+
+#endif
--
2.5.3