summaryrefslogtreecommitdiff
path: root/hw/char
diff options
context:
space:
mode:
authorGui Chen <gui.chen@intel.com>2014-08-01 02:25:56 -0400
committerGui Chen <gui.chen@intel.com>2014-08-01 02:25:56 -0400
commit6bedc38620a0358406b933725234f6615eed96be (patch)
treeac3085313c069ba8d035984fa5754675d46c718c /hw/char
downloadqemu-arm-static-6bedc38620a0358406b933725234f6615eed96be.tar.gz
qemu-arm-static-6bedc38620a0358406b933725234f6615eed96be.tar.bz2
qemu-arm-static-6bedc38620a0358406b933725234f6615eed96be.zip
Signed-off-by: Gui Chen <gui.chen@intel.com>
Diffstat (limited to 'hw/char')
-rw-r--r--hw/char/Makefile.objs27
-rw-r--r--hw/char/cadence_uart.c522
-rw-r--r--hw/char/debugcon.c140
-rw-r--r--hw/char/escc.c941
-rw-r--r--hw/char/etraxfs_ser.c252
-rw-r--r--hw/char/exynos4210_uart.c680
-rw-r--r--hw/char/grlib_apbuart.c298
-rw-r--r--hw/char/imx_serial.c473
-rw-r--r--hw/char/ipack.c116
-rw-r--r--hw/char/ipack.h79
-rw-r--r--hw/char/ipoctal232.c606
-rw-r--r--hw/char/lm32_juart.c162
-rw-r--r--hw/char/lm32_uart.c302
-rw-r--r--hw/char/mcf_uart.c307
-rw-r--r--hw/char/milkymist-uart.c249
-rw-r--r--hw/char/omap_uart.c187
-rw-r--r--hw/char/parallel.c625
-rw-r--r--hw/char/pl011.c332
-rw-r--r--hw/char/sclpconsole.c289
-rw-r--r--hw/char/serial-isa.c138
-rw-r--r--hw/char/serial-pci.c269
-rw-r--r--hw/char/serial.c775
-rw-r--r--hw/char/sh_serial.c410
-rw-r--r--hw/char/spapr_vty.c241
-rw-r--r--hw/char/tpci200.c672
-rw-r--r--hw/char/virtio-console.c207
-rw-r--r--hw/char/virtio-serial-bus.c1044
-rw-r--r--hw/char/xen_console.c305
-rw-r--r--hw/char/xilinx_uartlite.c235
29 files changed, 10883 insertions, 0 deletions
diff --git a/hw/char/Makefile.objs b/hw/char/Makefile.objs
new file mode 100644
index 000000000..f8f3dbca3
--- /dev/null
+++ b/hw/char/Makefile.objs
@@ -0,0 +1,27 @@
+common-obj-$(CONFIG_IPACK) += tpci200.o ipoctal232.o ipack.o
+common-obj-$(CONFIG_ESCC) += escc.o
+common-obj-$(CONFIG_PARALLEL) += parallel.o
+common-obj-$(CONFIG_PL011) += pl011.o
+common-obj-$(CONFIG_SERIAL) += serial.o serial-isa.o
+common-obj-$(CONFIG_SERIAL_PCI) += serial-pci.o
+common-obj-$(CONFIG_VIRTIO) += virtio-console.o
+common-obj-$(CONFIG_XILINX) += xilinx_uartlite.o
+common-obj-$(CONFIG_XEN_BACKEND) += xen_console.o
+common-obj-$(CONFIG_CADENCE) += cadence_uart.o
+
+obj-$(CONFIG_EXYNOS4) += exynos4210_uart.o
+obj-$(CONFIG_COLDFIRE) += mcf_uart.o
+obj-$(CONFIG_OMAP) += omap_uart.o
+obj-$(CONFIG_SH4) += sh_serial.o
+obj-$(CONFIG_PSERIES) += spapr_vty.o
+
+common-obj-$(CONFIG_ETRAXFS) += etraxfs_ser.o
+common-obj-$(CONFIG_ISA_DEBUG) += debugcon.o
+common-obj-$(CONFIG_GRLIB) += grlib_apbuart.o
+common-obj-$(CONFIG_IMX) += imx_serial.o
+common-obj-$(CONFIG_LM32) += lm32_juart.o
+common-obj-$(CONFIG_LM32) += lm32_uart.o
+common-obj-$(CONFIG_MILKYMIST) += milkymist-uart.o
+common-obj-$(CONFIG_SCLPCONSOLE) += sclpconsole.o
+
+obj-$(CONFIG_VIRTIO) += virtio-serial-bus.o
diff --git a/hw/char/cadence_uart.c b/hw/char/cadence_uart.c
new file mode 100644
index 000000000..3c2e96097
--- /dev/null
+++ b/hw/char/cadence_uart.c
@@ -0,0 +1,522 @@
+/*
+ * Device model for Cadence UART
+ *
+ * Copyright (c) 2010 Xilinx Inc.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
+ * Written by Haibing Ma
+ * M.Habib
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+#include "qemu/timer.h"
+
+#ifdef CADENCE_UART_ERR_DEBUG
+#define DB_PRINT(...) do { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } while (0);
+#else
+ #define DB_PRINT(...)
+#endif
+
+#define UART_SR_INTR_RTRIG 0x00000001
+#define UART_SR_INTR_REMPTY 0x00000002
+#define UART_SR_INTR_RFUL 0x00000004
+#define UART_SR_INTR_TEMPTY 0x00000008
+#define UART_SR_INTR_TFUL 0x00000010
+/* bits fields in CSR that correlate to CISR. If any of these bits are set in
+ * SR, then the same bit in CISR is set high too */
+#define UART_SR_TO_CISR_MASK 0x0000001F
+
+#define UART_INTR_ROVR 0x00000020
+#define UART_INTR_FRAME 0x00000040
+#define UART_INTR_PARE 0x00000080
+#define UART_INTR_TIMEOUT 0x00000100
+#define UART_INTR_DMSI 0x00000200
+
+#define UART_SR_RACTIVE 0x00000400
+#define UART_SR_TACTIVE 0x00000800
+#define UART_SR_FDELT 0x00001000
+
+#define UART_CR_RXRST 0x00000001
+#define UART_CR_TXRST 0x00000002
+#define UART_CR_RX_EN 0x00000004
+#define UART_CR_RX_DIS 0x00000008
+#define UART_CR_TX_EN 0x00000010
+#define UART_CR_TX_DIS 0x00000020
+#define UART_CR_RST_TO 0x00000040
+#define UART_CR_STARTBRK 0x00000080
+#define UART_CR_STOPBRK 0x00000100
+
+#define UART_MR_CLKS 0x00000001
+#define UART_MR_CHRL 0x00000006
+#define UART_MR_CHRL_SH 1
+#define UART_MR_PAR 0x00000038
+#define UART_MR_PAR_SH 3
+#define UART_MR_NBSTOP 0x000000C0
+#define UART_MR_NBSTOP_SH 6
+#define UART_MR_CHMODE 0x00000300
+#define UART_MR_CHMODE_SH 8
+#define UART_MR_UCLKEN 0x00000400
+#define UART_MR_IRMODE 0x00000800
+
+#define UART_DATA_BITS_6 (0x3 << UART_MR_CHRL_SH)
+#define UART_DATA_BITS_7 (0x2 << UART_MR_CHRL_SH)
+#define UART_PARITY_ODD (0x1 << UART_MR_PAR_SH)
+#define UART_PARITY_EVEN (0x0 << UART_MR_PAR_SH)
+#define UART_STOP_BITS_1 (0x3 << UART_MR_NBSTOP_SH)
+#define UART_STOP_BITS_2 (0x2 << UART_MR_NBSTOP_SH)
+#define NORMAL_MODE (0x0 << UART_MR_CHMODE_SH)
+#define ECHO_MODE (0x1 << UART_MR_CHMODE_SH)
+#define LOCAL_LOOPBACK (0x2 << UART_MR_CHMODE_SH)
+#define REMOTE_LOOPBACK (0x3 << UART_MR_CHMODE_SH)
+
+#define RX_FIFO_SIZE 16
+#define TX_FIFO_SIZE 16
+#define UART_INPUT_CLK 50000000
+
+#define R_CR (0x00/4)
+#define R_MR (0x04/4)
+#define R_IER (0x08/4)
+#define R_IDR (0x0C/4)
+#define R_IMR (0x10/4)
+#define R_CISR (0x14/4)
+#define R_BRGR (0x18/4)
+#define R_RTOR (0x1C/4)
+#define R_RTRIG (0x20/4)
+#define R_MCR (0x24/4)
+#define R_MSR (0x28/4)
+#define R_SR (0x2C/4)
+#define R_TX_RX (0x30/4)
+#define R_BDIV (0x34/4)
+#define R_FDEL (0x38/4)
+#define R_PMIN (0x3C/4)
+#define R_PWID (0x40/4)
+#define R_TTRIG (0x44/4)
+
+#define R_MAX (R_TTRIG + 1)
+
+#define TYPE_CADENCE_UART "cadence_uart"
+#define CADENCE_UART(obj) OBJECT_CHECK(UartState, (obj), TYPE_CADENCE_UART)
+
+typedef struct {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t r[R_MAX];
+ uint8_t r_fifo[RX_FIFO_SIZE];
+ uint32_t rx_wpos;
+ uint32_t rx_count;
+ uint64_t char_tx_time;
+ CharDriverState *chr;
+ qemu_irq irq;
+ struct QEMUTimer *fifo_trigger_handle;
+ struct QEMUTimer *tx_time_handle;
+} UartState;
+
+static void uart_update_status(UartState *s)
+{
+ s->r[R_CISR] |= s->r[R_SR] & UART_SR_TO_CISR_MASK;
+ qemu_set_irq(s->irq, !!(s->r[R_IMR] & s->r[R_CISR]));
+}
+
+static void fifo_trigger_update(void *opaque)
+{
+ UartState *s = (UartState *)opaque;
+
+ s->r[R_CISR] |= UART_INTR_TIMEOUT;
+
+ uart_update_status(s);
+}
+
+static void uart_tx_redo(UartState *s)
+{
+ uint64_t new_tx_time = qemu_get_clock_ns(vm_clock);
+
+ qemu_mod_timer(s->tx_time_handle, new_tx_time + s->char_tx_time);
+
+ s->r[R_SR] |= UART_SR_INTR_TEMPTY;
+
+ uart_update_status(s);
+}
+
+static void uart_tx_write(void *opaque)
+{
+ UartState *s = (UartState *)opaque;
+
+ uart_tx_redo(s);
+}
+
+static void uart_rx_reset(UartState *s)
+{
+ s->rx_wpos = 0;
+ s->rx_count = 0;
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+
+ s->r[R_SR] |= UART_SR_INTR_REMPTY;
+ s->r[R_SR] &= ~UART_SR_INTR_RFUL;
+}
+
+static void uart_tx_reset(UartState *s)
+{
+ s->r[R_SR] |= UART_SR_INTR_TEMPTY;
+ s->r[R_SR] &= ~UART_SR_INTR_TFUL;
+}
+
+static void uart_send_breaks(UartState *s)
+{
+ int break_enabled = 1;
+
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
+ &break_enabled);
+}
+
+static void uart_parameters_setup(UartState *s)
+{
+ QEMUSerialSetParams ssp;
+ unsigned int baud_rate, packet_size;
+
+ baud_rate = (s->r[R_MR] & UART_MR_CLKS) ?
+ UART_INPUT_CLK / 8 : UART_INPUT_CLK;
+
+ ssp.speed = baud_rate / (s->r[R_BRGR] * (s->r[R_BDIV] + 1));
+ packet_size = 1;
+
+ switch (s->r[R_MR] & UART_MR_PAR) {
+ case UART_PARITY_EVEN:
+ ssp.parity = 'E';
+ packet_size++;
+ break;
+ case UART_PARITY_ODD:
+ ssp.parity = 'O';
+ packet_size++;
+ break;
+ default:
+ ssp.parity = 'N';
+ break;
+ }
+
+ switch (s->r[R_MR] & UART_MR_CHRL) {
+ case UART_DATA_BITS_6:
+ ssp.data_bits = 6;
+ break;
+ case UART_DATA_BITS_7:
+ ssp.data_bits = 7;
+ break;
+ default:
+ ssp.data_bits = 8;
+ break;
+ }
+
+ switch (s->r[R_MR] & UART_MR_NBSTOP) {
+ case UART_STOP_BITS_1:
+ ssp.stop_bits = 1;
+ break;
+ default:
+ ssp.stop_bits = 2;
+ break;
+ }
+
+ packet_size += ssp.data_bits + ssp.stop_bits;
+ s->char_tx_time = (get_ticks_per_sec() / ssp.speed) * packet_size;
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+}
+
+static int uart_can_receive(void *opaque)
+{
+ UartState *s = (UartState *)opaque;
+
+ return RX_FIFO_SIZE - s->rx_count;
+}
+
+static void uart_ctrl_update(UartState *s)
+{
+ if (s->r[R_CR] & UART_CR_TXRST) {
+ uart_tx_reset(s);
+ }
+
+ if (s->r[R_CR] & UART_CR_RXRST) {
+ uart_rx_reset(s);
+ }
+
+ s->r[R_CR] &= ~(UART_CR_TXRST | UART_CR_RXRST);
+
+ if ((s->r[R_CR] & UART_CR_TX_EN) && !(s->r[R_CR] & UART_CR_TX_DIS)) {
+ uart_tx_redo(s);
+ }
+
+ if (s->r[R_CR] & UART_CR_STARTBRK && !(s->r[R_CR] & UART_CR_STOPBRK)) {
+ uart_send_breaks(s);
+ }
+}
+
+static void uart_write_rx_fifo(void *opaque, const uint8_t *buf, int size)
+{
+ UartState *s = (UartState *)opaque;
+ uint64_t new_rx_time = qemu_get_clock_ns(vm_clock);
+ int i;
+
+ if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) {
+ return;
+ }
+
+ s->r[R_SR] &= ~UART_SR_INTR_REMPTY;
+
+ if (s->rx_count == RX_FIFO_SIZE) {
+ s->r[R_CISR] |= UART_INTR_ROVR;
+ } else {
+ for (i = 0; i < size; i++) {
+ s->r_fifo[s->rx_wpos] = buf[i];
+ s->rx_wpos = (s->rx_wpos + 1) % RX_FIFO_SIZE;
+ s->rx_count++;
+
+ if (s->rx_count == RX_FIFO_SIZE) {
+ s->r[R_SR] |= UART_SR_INTR_RFUL;
+ break;
+ }
+
+ if (s->rx_count >= s->r[R_RTRIG]) {
+ s->r[R_SR] |= UART_SR_INTR_RTRIG;
+ }
+ }
+ qemu_mod_timer(s->fifo_trigger_handle, new_rx_time +
+ (s->char_tx_time * 4));
+ }
+ uart_update_status(s);
+}
+
+static void uart_write_tx_fifo(UartState *s, const uint8_t *buf, int size)
+{
+ if ((s->r[R_CR] & UART_CR_TX_DIS) || !(s->r[R_CR] & UART_CR_TX_EN)) {
+ return;
+ }
+
+ qemu_chr_fe_write_all(s->chr, buf, size);
+}
+
+static void uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ UartState *s = (UartState *)opaque;
+ uint32_t ch_mode = s->r[R_MR] & UART_MR_CHMODE;
+
+ if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) {
+ uart_write_rx_fifo(opaque, buf, size);
+ }
+ if (ch_mode == REMOTE_LOOPBACK || ch_mode == ECHO_MODE) {
+ uart_write_tx_fifo(s, buf, size);
+ }
+}
+
+static void uart_event(void *opaque, int event)
+{
+ UartState *s = (UartState *)opaque;
+ uint8_t buf = '\0';
+
+ if (event == CHR_EVENT_BREAK) {
+ uart_write_rx_fifo(opaque, &buf, 1);
+ }
+
+ uart_update_status(s);
+}
+
+static void uart_read_rx_fifo(UartState *s, uint32_t *c)
+{
+ if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) {
+ return;
+ }
+
+ s->r[R_SR] &= ~UART_SR_INTR_RFUL;
+
+ if (s->rx_count) {
+ uint32_t rx_rpos =
+ (RX_FIFO_SIZE + s->rx_wpos - s->rx_count) % RX_FIFO_SIZE;
+ *c = s->r_fifo[rx_rpos];
+ s->rx_count--;
+
+ if (!s->rx_count) {
+ s->r[R_SR] |= UART_SR_INTR_REMPTY;
+ }
+ qemu_chr_accept_input(s->chr);
+ } else {
+ *c = 0;
+ s->r[R_SR] |= UART_SR_INTR_REMPTY;
+ }
+
+ if (s->rx_count < s->r[R_RTRIG]) {
+ s->r[R_SR] &= ~UART_SR_INTR_RTRIG;
+ }
+ uart_update_status(s);
+}
+
+static void uart_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ UartState *s = (UartState *)opaque;
+
+ DB_PRINT(" offset:%x data:%08x\n", (unsigned)offset, (unsigned)value);
+ offset >>= 2;
+ switch (offset) {
+ case R_IER: /* ier (wts imr) */
+ s->r[R_IMR] |= value;
+ break;
+ case R_IDR: /* idr (wtc imr) */
+ s->r[R_IMR] &= ~value;
+ break;
+ case R_IMR: /* imr (read only) */
+ break;
+ case R_CISR: /* cisr (wtc) */
+ s->r[R_CISR] &= ~value;
+ break;
+ case R_TX_RX: /* UARTDR */
+ switch (s->r[R_MR] & UART_MR_CHMODE) {
+ case NORMAL_MODE:
+ uart_write_tx_fifo(s, (uint8_t *) &value, 1);
+ break;
+ case LOCAL_LOOPBACK:
+ uart_write_rx_fifo(opaque, (uint8_t *) &value, 1);
+ break;
+ }
+ break;
+ default:
+ s->r[offset] = value;
+ }
+
+ switch (offset) {
+ case R_CR:
+ uart_ctrl_update(s);
+ break;
+ case R_MR:
+ uart_parameters_setup(s);
+ break;
+ }
+}
+
+static uint64_t uart_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ UartState *s = (UartState *)opaque;
+ uint32_t c = 0;
+
+ offset >>= 2;
+ if (offset >= R_MAX) {
+ c = 0;
+ } else if (offset == R_TX_RX) {
+ uart_read_rx_fifo(s, &c);
+ } else {
+ c = s->r[offset];
+ }
+
+ DB_PRINT(" offset:%x data:%08x\n", (unsigned)(offset << 2), (unsigned)c);
+ return c;
+}
+
+static const MemoryRegionOps uart_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void cadence_uart_reset(UartState *s)
+{
+ s->r[R_CR] = 0x00000128;
+ s->r[R_IMR] = 0;
+ s->r[R_CISR] = 0;
+ s->r[R_RTRIG] = 0x00000020;
+ s->r[R_BRGR] = 0x0000000F;
+ s->r[R_TTRIG] = 0x00000020;
+
+ uart_rx_reset(s);
+ uart_tx_reset(s);
+
+ s->rx_count = 0;
+ s->rx_wpos = 0;
+}
+
+static int cadence_uart_init(SysBusDevice *dev)
+{
+ UartState *s = CADENCE_UART(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &uart_ops, s, "uart", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+
+ s->fifo_trigger_handle = qemu_new_timer_ns(vm_clock,
+ (QEMUTimerCB *)fifo_trigger_update, s);
+
+ s->tx_time_handle = qemu_new_timer_ns(vm_clock,
+ (QEMUTimerCB *)uart_tx_write, s);
+
+ s->char_tx_time = (get_ticks_per_sec() / 9600) * 10;
+
+ s->chr = qemu_char_get_next_serial();
+
+ cadence_uart_reset(s);
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, uart_can_receive, uart_receive,
+ uart_event, s);
+ }
+
+ return 0;
+}
+
+static int cadence_uart_post_load(void *opaque, int version_id)
+{
+ UartState *s = opaque;
+
+ uart_parameters_setup(s);
+ uart_update_status(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_cadence_uart = {
+ .name = "cadence_uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .post_load = cadence_uart_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(r, UartState, R_MAX),
+ VMSTATE_UINT8_ARRAY(r_fifo, UartState, RX_FIFO_SIZE),
+ VMSTATE_UINT32(rx_count, UartState),
+ VMSTATE_UINT32(rx_wpos, UartState),
+ VMSTATE_TIMER(fifo_trigger_handle, UartState),
+ VMSTATE_TIMER(tx_time_handle, UartState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cadence_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = cadence_uart_init;
+ dc->vmsd = &vmstate_cadence_uart;
+}
+
+static const TypeInfo cadence_uart_info = {
+ .name = TYPE_CADENCE_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(UartState),
+ .class_init = cadence_uart_class_init,
+};
+
+static void cadence_uart_register_types(void)
+{
+ type_register_static(&cadence_uart_info);
+}
+
+type_init(cadence_uart_register_types)
diff --git a/hw/char/debugcon.c b/hw/char/debugcon.c
new file mode 100644
index 000000000..02d0d57a7
--- /dev/null
+++ b/hw/char/debugcon.c
@@ -0,0 +1,140 @@
+/*
+ * QEMU Bochs-style debug console ("port E9") emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ * Copyright (c) Intel Corporation; author: H. Peter Anvin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/char.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+
+#define TYPE_ISA_DEBUGCON_DEVICE "isa-debugcon"
+#define ISA_DEBUGCON_DEVICE(obj) \
+ OBJECT_CHECK(ISADebugconState, (obj), TYPE_ISA_DEBUGCON_DEVICE)
+
+//#define DEBUG_DEBUGCON
+
+typedef struct DebugconState {
+ MemoryRegion io;
+ CharDriverState *chr;
+ uint32_t readback;
+} DebugconState;
+
+typedef struct ISADebugconState {
+ ISADevice parent_obj;
+
+ uint32_t iobase;
+ DebugconState state;
+} ISADebugconState;
+
+static void debugcon_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ DebugconState *s = opaque;
+ unsigned char ch = val;
+
+#ifdef DEBUG_DEBUGCON
+ printf(" [debugcon: write addr=0x%04" HWADDR_PRIx " val=0x%02" PRIx64 "]\n", addr, val);
+#endif
+
+ qemu_chr_fe_write(s->chr, &ch, 1);
+}
+
+
+static uint64_t debugcon_ioport_read(void *opaque, hwaddr addr, unsigned width)
+{
+ DebugconState *s = opaque;
+
+#ifdef DEBUG_DEBUGCON
+ printf("debugcon: read addr=0x%04" HWADDR_PRIx "\n", addr);
+#endif
+
+ return s->readback;
+}
+
+static const MemoryRegionOps debugcon_ops = {
+ .read = debugcon_ioport_read,
+ .write = debugcon_ioport_write,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void debugcon_realize_core(DebugconState *s, Error **errp)
+{
+ if (!s->chr) {
+ error_setg(errp, "Can't create debugcon device, empty char device");
+ return;
+ }
+
+ qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, s);
+}
+
+static void debugcon_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ ISADevice *d = ISA_DEVICE(dev);
+ ISADebugconState *isa = ISA_DEBUGCON_DEVICE(dev);
+ DebugconState *s = &isa->state;
+ Error *err = NULL;
+
+ debugcon_realize_core(s, &err);
+ if (err != NULL) {
+ error_propagate(errp, err);
+ return;
+ }
+ memory_region_init_io(&s->io, OBJECT(dev), &debugcon_ops, s,
+ TYPE_ISA_DEBUGCON_DEVICE, 1);
+ memory_region_add_subregion(isa_address_space_io(d),
+ isa->iobase, &s->io);
+}
+
+static Property debugcon_isa_properties[] = {
+ DEFINE_PROP_HEX32("iobase", ISADebugconState, iobase, 0xe9),
+ DEFINE_PROP_CHR("chardev", ISADebugconState, state.chr),
+ DEFINE_PROP_HEX32("readback", ISADebugconState, state.readback, 0xe9),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void debugcon_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = debugcon_isa_realizefn;
+ dc->props = debugcon_isa_properties;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo debugcon_isa_info = {
+ .name = TYPE_ISA_DEBUGCON_DEVICE,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISADebugconState),
+ .class_init = debugcon_isa_class_initfn,
+};
+
+static void debugcon_register_types(void)
+{
+ type_register_static(&debugcon_isa_info);
+}
+
+type_init(debugcon_register_types)
diff --git a/hw/char/escc.c b/hw/char/escc.c
new file mode 100644
index 000000000..6397f6f28
--- /dev/null
+++ b/hw/char/escc.c
@@ -0,0 +1,941 @@
+/*
+ * QEMU ESCC (Z8030/Z8530/Z85C30/SCC/ESCC) serial port emulation
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "hw/char/escc.h"
+#include "sysemu/char.h"
+#include "ui/console.h"
+#include "trace.h"
+
+/*
+ * Chipset docs:
+ * "Z80C30/Z85C30/Z80230/Z85230/Z85233 SCC/ESCC User Manual",
+ * http://www.zilog.com/docs/serial/scc_escc_um.pdf
+ *
+ * On Sparc32 this is the serial port, mouse and keyboard part of chip STP2001
+ * (Slave I/O), also produced as NCR89C105. See
+ * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
+ *
+ * The serial ports implement full AMD AM8530 or Zilog Z8530 chips,
+ * mouse and keyboard ports don't implement all functions and they are
+ * only asynchronous. There is no DMA.
+ *
+ * Z85C30 is also used on PowerMacs. There are some small differences
+ * between Sparc version (sunzilog) and PowerMac (pmac):
+ * Offset between control and data registers
+ * There is some kind of lockup bug, but we can ignore it
+ * CTS is inverted
+ * DMA on pmac using DBDMA chip
+ * pmac can do IRDA and faster rates, sunzilog can only do 38400
+ * pmac baud rate generator clock is 3.6864 MHz, sunzilog 4.9152 MHz
+ */
+
+/*
+ * Modifications:
+ * 2006-Aug-10 Igor Kovalenko : Renamed KBDQueue to SERIOQueue, implemented
+ * serial mouse queue.
+ * Implemented serial mouse protocol.
+ *
+ * 2010-May-23 Artyom Tarasenko: Reworked IUS logic
+ */
+
+typedef enum {
+ chn_a, chn_b,
+} ChnID;
+
+#define CHN_C(s) ((s)->chn == chn_b? 'b' : 'a')
+
+typedef enum {
+ ser, kbd, mouse,
+} ChnType;
+
+#define SERIO_QUEUE_SIZE 256
+
+typedef struct {
+ uint8_t data[SERIO_QUEUE_SIZE];
+ int rptr, wptr, count;
+} SERIOQueue;
+
+#define SERIAL_REGS 16
+typedef struct ChannelState {
+ qemu_irq irq;
+ uint32_t rxint, txint, rxint_under_svc, txint_under_svc;
+ struct ChannelState *otherchn;
+ uint32_t reg;
+ uint8_t wregs[SERIAL_REGS], rregs[SERIAL_REGS];
+ SERIOQueue queue;
+ CharDriverState *chr;
+ int e0_mode, led_mode, caps_lock_mode, num_lock_mode;
+ int disabled;
+ int clock;
+ uint32_t vmstate_dummy;
+ ChnID chn; // this channel, A (base+4) or B (base+0)
+ ChnType type;
+ uint8_t rx, tx;
+} ChannelState;
+
+#define ESCC(obj) OBJECT_CHECK(ESCCState, (obj), TYPE_ESCC)
+
+typedef struct ESCCState {
+ SysBusDevice parent_obj;
+
+ struct ChannelState chn[2];
+ uint32_t it_shift;
+ MemoryRegion mmio;
+ uint32_t disabled;
+ uint32_t frequency;
+} ESCCState;
+
+#define SERIAL_CTRL 0
+#define SERIAL_DATA 1
+
+#define W_CMD 0
+#define CMD_PTR_MASK 0x07
+#define CMD_CMD_MASK 0x38
+#define CMD_HI 0x08
+#define CMD_CLR_TXINT 0x28
+#define CMD_CLR_IUS 0x38
+#define W_INTR 1
+#define INTR_INTALL 0x01
+#define INTR_TXINT 0x02
+#define INTR_RXMODEMSK 0x18
+#define INTR_RXINT1ST 0x08
+#define INTR_RXINTALL 0x10
+#define W_IVEC 2
+#define W_RXCTRL 3
+#define RXCTRL_RXEN 0x01
+#define W_TXCTRL1 4
+#define TXCTRL1_PAREN 0x01
+#define TXCTRL1_PAREV 0x02
+#define TXCTRL1_1STOP 0x04
+#define TXCTRL1_1HSTOP 0x08
+#define TXCTRL1_2STOP 0x0c
+#define TXCTRL1_STPMSK 0x0c
+#define TXCTRL1_CLK1X 0x00
+#define TXCTRL1_CLK16X 0x40
+#define TXCTRL1_CLK32X 0x80
+#define TXCTRL1_CLK64X 0xc0
+#define TXCTRL1_CLKMSK 0xc0
+#define W_TXCTRL2 5
+#define TXCTRL2_TXEN 0x08
+#define TXCTRL2_BITMSK 0x60
+#define TXCTRL2_5BITS 0x00
+#define TXCTRL2_7BITS 0x20
+#define TXCTRL2_6BITS 0x40
+#define TXCTRL2_8BITS 0x60
+#define W_SYNC1 6
+#define W_SYNC2 7
+#define W_TXBUF 8
+#define W_MINTR 9
+#define MINTR_STATUSHI 0x10
+#define MINTR_RST_MASK 0xc0
+#define MINTR_RST_B 0x40
+#define MINTR_RST_A 0x80
+#define MINTR_RST_ALL 0xc0
+#define W_MISC1 10
+#define W_CLOCK 11
+#define CLOCK_TRXC 0x08
+#define W_BRGLO 12
+#define W_BRGHI 13
+#define W_MISC2 14
+#define MISC2_PLLDIS 0x30
+#define W_EXTINT 15
+#define EXTINT_DCD 0x08
+#define EXTINT_SYNCINT 0x10
+#define EXTINT_CTSINT 0x20
+#define EXTINT_TXUNDRN 0x40
+#define EXTINT_BRKINT 0x80
+
+#define R_STATUS 0
+#define STATUS_RXAV 0x01
+#define STATUS_ZERO 0x02
+#define STATUS_TXEMPTY 0x04
+#define STATUS_DCD 0x08
+#define STATUS_SYNC 0x10
+#define STATUS_CTS 0x20
+#define STATUS_TXUNDRN 0x40
+#define STATUS_BRK 0x80
+#define R_SPEC 1
+#define SPEC_ALLSENT 0x01
+#define SPEC_BITS8 0x06
+#define R_IVEC 2
+#define IVEC_TXINTB 0x00
+#define IVEC_LONOINT 0x06
+#define IVEC_LORXINTA 0x0c
+#define IVEC_LORXINTB 0x04
+#define IVEC_LOTXINTA 0x08
+#define IVEC_HINOINT 0x60
+#define IVEC_HIRXINTA 0x30
+#define IVEC_HIRXINTB 0x20
+#define IVEC_HITXINTA 0x10
+#define R_INTR 3
+#define INTR_EXTINTB 0x01
+#define INTR_TXINTB 0x02
+#define INTR_RXINTB 0x04
+#define INTR_EXTINTA 0x08
+#define INTR_TXINTA 0x10
+#define INTR_RXINTA 0x20
+#define R_IPEN 4
+#define R_TXCTRL1 5
+#define R_TXCTRL2 6
+#define R_BC 7
+#define R_RXBUF 8
+#define R_RXCTRL 9
+#define R_MISC 10
+#define R_MISC1 11
+#define R_BRGLO 12
+#define R_BRGHI 13
+#define R_MISC1I 14
+#define R_EXTINT 15
+
+static void handle_kbd_command(ChannelState *s, int val);
+static int serial_can_receive(void *opaque);
+static void serial_receive_byte(ChannelState *s, int ch);
+
+static void clear_queue(void *opaque)
+{
+ ChannelState *s = opaque;
+ SERIOQueue *q = &s->queue;
+ q->rptr = q->wptr = q->count = 0;
+}
+
+static void put_queue(void *opaque, int b)
+{
+ ChannelState *s = opaque;
+ SERIOQueue *q = &s->queue;
+
+ trace_escc_put_queue(CHN_C(s), b);
+ if (q->count >= SERIO_QUEUE_SIZE)
+ return;
+ q->data[q->wptr] = b;
+ if (++q->wptr == SERIO_QUEUE_SIZE)
+ q->wptr = 0;
+ q->count++;
+ serial_receive_byte(s, 0);
+}
+
+static uint32_t get_queue(void *opaque)
+{
+ ChannelState *s = opaque;
+ SERIOQueue *q = &s->queue;
+ int val;
+
+ if (q->count == 0) {
+ return 0;
+ } else {
+ val = q->data[q->rptr];
+ if (++q->rptr == SERIO_QUEUE_SIZE)
+ q->rptr = 0;
+ q->count--;
+ }
+ trace_escc_get_queue(CHN_C(s), val);
+ if (q->count > 0)
+ serial_receive_byte(s, 0);
+ return val;
+}
+
+static int escc_update_irq_chn(ChannelState *s)
+{
+ if ((((s->wregs[W_INTR] & INTR_TXINT) && (s->txint == 1)) ||
+ // tx ints enabled, pending
+ ((((s->wregs[W_INTR] & INTR_RXMODEMSK) == INTR_RXINT1ST) ||
+ ((s->wregs[W_INTR] & INTR_RXMODEMSK) == INTR_RXINTALL)) &&
+ s->rxint == 1) || // rx ints enabled, pending
+ ((s->wregs[W_EXTINT] & EXTINT_BRKINT) &&
+ (s->rregs[R_STATUS] & STATUS_BRK)))) { // break int e&p
+ return 1;
+ }
+ return 0;
+}
+
+static void escc_update_irq(ChannelState *s)
+{
+ int irq;
+
+ irq = escc_update_irq_chn(s);
+ irq |= escc_update_irq_chn(s->otherchn);
+
+ trace_escc_update_irq(irq);
+ qemu_set_irq(s->irq, irq);
+}
+
+static void escc_reset_chn(ChannelState *s)
+{
+ int i;
+
+ s->reg = 0;
+ for (i = 0; i < SERIAL_REGS; i++) {
+ s->rregs[i] = 0;
+ s->wregs[i] = 0;
+ }
+ s->wregs[W_TXCTRL1] = TXCTRL1_1STOP; // 1X divisor, 1 stop bit, no parity
+ s->wregs[W_MINTR] = MINTR_RST_ALL;
+ s->wregs[W_CLOCK] = CLOCK_TRXC; // Synch mode tx clock = TRxC
+ s->wregs[W_MISC2] = MISC2_PLLDIS; // PLL disabled
+ s->wregs[W_EXTINT] = EXTINT_DCD | EXTINT_SYNCINT | EXTINT_CTSINT |
+ EXTINT_TXUNDRN | EXTINT_BRKINT; // Enable most interrupts
+ if (s->disabled)
+ s->rregs[R_STATUS] = STATUS_TXEMPTY | STATUS_DCD | STATUS_SYNC |
+ STATUS_CTS | STATUS_TXUNDRN;
+ else
+ s->rregs[R_STATUS] = STATUS_TXEMPTY | STATUS_TXUNDRN;
+ s->rregs[R_SPEC] = SPEC_BITS8 | SPEC_ALLSENT;
+
+ s->rx = s->tx = 0;
+ s->rxint = s->txint = 0;
+ s->rxint_under_svc = s->txint_under_svc = 0;
+ s->e0_mode = s->led_mode = s->caps_lock_mode = s->num_lock_mode = 0;
+ clear_queue(s);
+}
+
+static void escc_reset(DeviceState *d)
+{
+ ESCCState *s = ESCC(d);
+
+ escc_reset_chn(&s->chn[0]);
+ escc_reset_chn(&s->chn[1]);
+}
+
+static inline void set_rxint(ChannelState *s)
+{
+ s->rxint = 1;
+ /* XXX: missing daisy chainnig: chn_b rx should have a lower priority
+ than chn_a rx/tx/special_condition service*/
+ s->rxint_under_svc = 1;
+ if (s->chn == chn_a) {
+ s->rregs[R_INTR] |= INTR_RXINTA;
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HIRXINTA;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LORXINTA;
+ } else {
+ s->otherchn->rregs[R_INTR] |= INTR_RXINTB;
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->rregs[R_IVEC] = IVEC_HIRXINTB;
+ else
+ s->rregs[R_IVEC] = IVEC_LORXINTB;
+ }
+ escc_update_irq(s);
+}
+
+static inline void set_txint(ChannelState *s)
+{
+ s->txint = 1;
+ if (!s->rxint_under_svc) {
+ s->txint_under_svc = 1;
+ if (s->chn == chn_a) {
+ if (s->wregs[W_INTR] & INTR_TXINT) {
+ s->rregs[R_INTR] |= INTR_TXINTA;
+ }
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HITXINTA;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LOTXINTA;
+ } else {
+ s->rregs[R_IVEC] = IVEC_TXINTB;
+ if (s->wregs[W_INTR] & INTR_TXINT) {
+ s->otherchn->rregs[R_INTR] |= INTR_TXINTB;
+ }
+ }
+ escc_update_irq(s);
+ }
+}
+
+static inline void clr_rxint(ChannelState *s)
+{
+ s->rxint = 0;
+ s->rxint_under_svc = 0;
+ if (s->chn == chn_a) {
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LONOINT;
+ s->rregs[R_INTR] &= ~INTR_RXINTA;
+ } else {
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->rregs[R_IVEC] = IVEC_LONOINT;
+ s->otherchn->rregs[R_INTR] &= ~INTR_RXINTB;
+ }
+ if (s->txint)
+ set_txint(s);
+ escc_update_irq(s);
+}
+
+static inline void clr_txint(ChannelState *s)
+{
+ s->txint = 0;
+ s->txint_under_svc = 0;
+ if (s->chn == chn_a) {
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->otherchn->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->otherchn->rregs[R_IVEC] = IVEC_LONOINT;
+ s->rregs[R_INTR] &= ~INTR_TXINTA;
+ } else {
+ s->otherchn->rregs[R_INTR] &= ~INTR_TXINTB;
+ if (s->wregs[W_MINTR] & MINTR_STATUSHI)
+ s->rregs[R_IVEC] = IVEC_HINOINT;
+ else
+ s->rregs[R_IVEC] = IVEC_LONOINT;
+ s->otherchn->rregs[R_INTR] &= ~INTR_TXINTB;
+ }
+ if (s->rxint)
+ set_rxint(s);
+ escc_update_irq(s);
+}
+
+static void escc_update_parameters(ChannelState *s)
+{
+ int speed, parity, data_bits, stop_bits;
+ QEMUSerialSetParams ssp;
+
+ if (!s->chr || s->type != ser)
+ return;
+
+ if (s->wregs[W_TXCTRL1] & TXCTRL1_PAREN) {
+ if (s->wregs[W_TXCTRL1] & TXCTRL1_PAREV)
+ parity = 'E';
+ else
+ parity = 'O';
+ } else {
+ parity = 'N';
+ }
+ if ((s->wregs[W_TXCTRL1] & TXCTRL1_STPMSK) == TXCTRL1_2STOP)
+ stop_bits = 2;
+ else
+ stop_bits = 1;
+ switch (s->wregs[W_TXCTRL2] & TXCTRL2_BITMSK) {
+ case TXCTRL2_5BITS:
+ data_bits = 5;
+ break;
+ case TXCTRL2_7BITS:
+ data_bits = 7;
+ break;
+ case TXCTRL2_6BITS:
+ data_bits = 6;
+ break;
+ default:
+ case TXCTRL2_8BITS:
+ data_bits = 8;
+ break;
+ }
+ speed = s->clock / ((s->wregs[W_BRGLO] | (s->wregs[W_BRGHI] << 8)) + 2);
+ switch (s->wregs[W_TXCTRL1] & TXCTRL1_CLKMSK) {
+ case TXCTRL1_CLK1X:
+ break;
+ case TXCTRL1_CLK16X:
+ speed /= 16;
+ break;
+ case TXCTRL1_CLK32X:
+ speed /= 32;
+ break;
+ default:
+ case TXCTRL1_CLK64X:
+ speed /= 64;
+ break;
+ }
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+ trace_escc_update_parameters(CHN_C(s), speed, parity, data_bits, stop_bits);
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+}
+
+static void escc_mem_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ ESCCState *serial = opaque;
+ ChannelState *s;
+ uint32_t saddr;
+ int newreg, channel;
+
+ val &= 0xff;
+ saddr = (addr >> serial->it_shift) & 1;
+ channel = (addr >> (serial->it_shift + 1)) & 1;
+ s = &serial->chn[channel];
+ switch (saddr) {
+ case SERIAL_CTRL:
+ trace_escc_mem_writeb_ctrl(CHN_C(s), s->reg, val & 0xff);
+ newreg = 0;
+ switch (s->reg) {
+ case W_CMD:
+ newreg = val & CMD_PTR_MASK;
+ val &= CMD_CMD_MASK;
+ switch (val) {
+ case CMD_HI:
+ newreg |= CMD_HI;
+ break;
+ case CMD_CLR_TXINT:
+ clr_txint(s);
+ break;
+ case CMD_CLR_IUS:
+ if (s->rxint_under_svc) {
+ s->rxint_under_svc = 0;
+ if (s->txint) {
+ set_txint(s);
+ }
+ } else if (s->txint_under_svc) {
+ s->txint_under_svc = 0;
+ }
+ escc_update_irq(s);
+ break;
+ default:
+ break;
+ }
+ break;
+ case W_INTR ... W_RXCTRL:
+ case W_SYNC1 ... W_TXBUF:
+ case W_MISC1 ... W_CLOCK:
+ case W_MISC2 ... W_EXTINT:
+ s->wregs[s->reg] = val;
+ break;
+ case W_TXCTRL1:
+ case W_TXCTRL2:
+ s->wregs[s->reg] = val;
+ escc_update_parameters(s);
+ break;
+ case W_BRGLO:
+ case W_BRGHI:
+ s->wregs[s->reg] = val;
+ s->rregs[s->reg] = val;
+ escc_update_parameters(s);
+ break;
+ case W_MINTR:
+ switch (val & MINTR_RST_MASK) {
+ case 0:
+ default:
+ break;
+ case MINTR_RST_B:
+ escc_reset_chn(&serial->chn[0]);
+ return;
+ case MINTR_RST_A:
+ escc_reset_chn(&serial->chn[1]);
+ return;
+ case MINTR_RST_ALL:
+ escc_reset(DEVICE(serial));
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ if (s->reg == 0)
+ s->reg = newreg;
+ else
+ s->reg = 0;
+ break;
+ case SERIAL_DATA:
+ trace_escc_mem_writeb_data(CHN_C(s), val);
+ s->tx = val;
+ if (s->wregs[W_TXCTRL2] & TXCTRL2_TXEN) { // tx enabled
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, &s->tx, 1);
+ else if (s->type == kbd && !s->disabled) {
+ handle_kbd_command(s, val);
+ }
+ }
+ s->rregs[R_STATUS] |= STATUS_TXEMPTY; // Tx buffer empty
+ s->rregs[R_SPEC] |= SPEC_ALLSENT; // All sent
+ set_txint(s);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t escc_mem_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ ESCCState *serial = opaque;
+ ChannelState *s;
+ uint32_t saddr;
+ uint32_t ret;
+ int channel;
+
+ saddr = (addr >> serial->it_shift) & 1;
+ channel = (addr >> (serial->it_shift + 1)) & 1;
+ s = &serial->chn[channel];
+ switch (saddr) {
+ case SERIAL_CTRL:
+ trace_escc_mem_readb_ctrl(CHN_C(s), s->reg, s->rregs[s->reg]);
+ ret = s->rregs[s->reg];
+ s->reg = 0;
+ return ret;
+ case SERIAL_DATA:
+ s->rregs[R_STATUS] &= ~STATUS_RXAV;
+ clr_rxint(s);
+ if (s->type == kbd || s->type == mouse)
+ ret = get_queue(s);
+ else
+ ret = s->rx;
+ trace_escc_mem_readb_data(CHN_C(s), ret);
+ if (s->chr)
+ qemu_chr_accept_input(s->chr);
+ return ret;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const MemoryRegionOps escc_mem_ops = {
+ .read = escc_mem_read,
+ .write = escc_mem_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static int serial_can_receive(void *opaque)
+{
+ ChannelState *s = opaque;
+ int ret;
+
+ if (((s->wregs[W_RXCTRL] & RXCTRL_RXEN) == 0) // Rx not enabled
+ || ((s->rregs[R_STATUS] & STATUS_RXAV) == STATUS_RXAV))
+ // char already available
+ ret = 0;
+ else
+ ret = 1;
+ return ret;
+}
+
+static void serial_receive_byte(ChannelState *s, int ch)
+{
+ trace_escc_serial_receive_byte(CHN_C(s), ch);
+ s->rregs[R_STATUS] |= STATUS_RXAV;
+ s->rx = ch;
+ set_rxint(s);
+}
+
+static void serial_receive_break(ChannelState *s)
+{
+ s->rregs[R_STATUS] |= STATUS_BRK;
+ escc_update_irq(s);
+}
+
+static void serial_receive1(void *opaque, const uint8_t *buf, int size)
+{
+ ChannelState *s = opaque;
+ serial_receive_byte(s, buf[0]);
+}
+
+static void serial_event(void *opaque, int event)
+{
+ ChannelState *s = opaque;
+ if (event == CHR_EVENT_BREAK)
+ serial_receive_break(s);
+}
+
+static const VMStateDescription vmstate_escc_chn = {
+ .name ="escc_chn",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT32(vmstate_dummy, ChannelState),
+ VMSTATE_UINT32(reg, ChannelState),
+ VMSTATE_UINT32(rxint, ChannelState),
+ VMSTATE_UINT32(txint, ChannelState),
+ VMSTATE_UINT32(rxint_under_svc, ChannelState),
+ VMSTATE_UINT32(txint_under_svc, ChannelState),
+ VMSTATE_UINT8(rx, ChannelState),
+ VMSTATE_UINT8(tx, ChannelState),
+ VMSTATE_BUFFER(wregs, ChannelState),
+ VMSTATE_BUFFER(rregs, ChannelState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_escc = {
+ .name ="escc",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField []) {
+ VMSTATE_STRUCT_ARRAY(chn, ESCCState, 2, 2, vmstate_escc_chn,
+ ChannelState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+MemoryRegion *escc_init(hwaddr base, qemu_irq irqA, qemu_irq irqB,
+ CharDriverState *chrA, CharDriverState *chrB,
+ int clock, int it_shift)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+ ESCCState *d;
+
+ dev = qdev_create(NULL, TYPE_ESCC);
+ qdev_prop_set_uint32(dev, "disabled", 0);
+ qdev_prop_set_uint32(dev, "frequency", clock);
+ qdev_prop_set_uint32(dev, "it_shift", it_shift);
+ qdev_prop_set_chr(dev, "chrB", chrB);
+ qdev_prop_set_chr(dev, "chrA", chrA);
+ qdev_prop_set_uint32(dev, "chnBtype", ser);
+ qdev_prop_set_uint32(dev, "chnAtype", ser);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irqB);
+ sysbus_connect_irq(s, 1, irqA);
+ if (base) {
+ sysbus_mmio_map(s, 0, base);
+ }
+
+ d = ESCC(s);
+ return &d->mmio;
+}
+
+static const uint8_t keycodes[128] = {
+ 127, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 89, 76, 77, 78,
+ 79, 80, 81, 82, 83, 84, 85, 86, 87, 42, 99, 88, 100, 101, 102, 103,
+ 104, 105, 106, 107, 108, 109, 110, 47, 19, 121, 119, 5, 6, 8, 10, 12,
+ 14, 16, 17, 18, 7, 98, 23, 68, 69, 70, 71, 91, 92, 93, 125, 112,
+ 113, 114, 94, 50, 0, 0, 124, 9, 11, 0, 0, 0, 0, 0, 0, 0,
+ 90, 0, 46, 22, 13, 111, 52, 20, 96, 24, 28, 74, 27, 123, 44, 66,
+ 0, 45, 2, 4, 48, 0, 0, 21, 0, 0, 0, 0, 0, 120, 122, 67,
+};
+
+static const uint8_t e0_keycodes[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 109, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 68, 69, 70, 0, 91, 0, 93, 0, 112,
+ 113, 114, 94, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 3, 25, 26, 49, 52, 72, 73, 97, 99, 111, 118, 120, 122, 67, 0,
+};
+
+static void sunkbd_event(void *opaque, int ch)
+{
+ ChannelState *s = opaque;
+ int release = ch & 0x80;
+
+ trace_escc_sunkbd_event_in(ch);
+ switch (ch) {
+ case 58: // Caps lock press
+ s->caps_lock_mode ^= 1;
+ if (s->caps_lock_mode == 2)
+ return; // Drop second press
+ break;
+ case 69: // Num lock press
+ s->num_lock_mode ^= 1;
+ if (s->num_lock_mode == 2)
+ return; // Drop second press
+ break;
+ case 186: // Caps lock release
+ s->caps_lock_mode ^= 2;
+ if (s->caps_lock_mode == 3)
+ return; // Drop first release
+ break;
+ case 197: // Num lock release
+ s->num_lock_mode ^= 2;
+ if (s->num_lock_mode == 3)
+ return; // Drop first release
+ break;
+ case 0xe0:
+ s->e0_mode = 1;
+ return;
+ default:
+ break;
+ }
+ if (s->e0_mode) {
+ s->e0_mode = 0;
+ ch = e0_keycodes[ch & 0x7f];
+ } else {
+ ch = keycodes[ch & 0x7f];
+ }
+ trace_escc_sunkbd_event_out(ch);
+ put_queue(s, ch | release);
+}
+
+static void handle_kbd_command(ChannelState *s, int val)
+{
+ trace_escc_kbd_command(val);
+ if (s->led_mode) { // Ignore led byte
+ s->led_mode = 0;
+ return;
+ }
+ switch (val) {
+ case 1: // Reset, return type code
+ clear_queue(s);
+ put_queue(s, 0xff);
+ put_queue(s, 4); // Type 4
+ put_queue(s, 0x7f);
+ break;
+ case 0xe: // Set leds
+ s->led_mode = 1;
+ break;
+ case 7: // Query layout
+ case 0xf:
+ clear_queue(s);
+ put_queue(s, 0xfe);
+ put_queue(s, 0); // XXX, layout?
+ break;
+ default:
+ break;
+ }
+}
+
+static void sunmouse_event(void *opaque,
+ int dx, int dy, int dz, int buttons_state)
+{
+ ChannelState *s = opaque;
+ int ch;
+
+ trace_escc_sunmouse_event(dx, dy, buttons_state);
+ ch = 0x80 | 0x7; /* protocol start byte, no buttons pressed */
+
+ if (buttons_state & MOUSE_EVENT_LBUTTON)
+ ch ^= 0x4;
+ if (buttons_state & MOUSE_EVENT_MBUTTON)
+ ch ^= 0x2;
+ if (buttons_state & MOUSE_EVENT_RBUTTON)
+ ch ^= 0x1;
+
+ put_queue(s, ch);
+
+ ch = dx;
+
+ if (ch > 127)
+ ch = 127;
+ else if (ch < -127)
+ ch = -127;
+
+ put_queue(s, ch & 0xff);
+
+ ch = -dy;
+
+ if (ch > 127)
+ ch = 127;
+ else if (ch < -127)
+ ch = -127;
+
+ put_queue(s, ch & 0xff);
+
+ // MSC protocol specify two extra motion bytes
+
+ put_queue(s, 0);
+ put_queue(s, 0);
+}
+
+void slavio_serial_ms_kbd_init(hwaddr base, qemu_irq irq,
+ int disabled, int clock, int it_shift)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_create(NULL, TYPE_ESCC);
+ qdev_prop_set_uint32(dev, "disabled", disabled);
+ qdev_prop_set_uint32(dev, "frequency", clock);
+ qdev_prop_set_uint32(dev, "it_shift", it_shift);
+ qdev_prop_set_chr(dev, "chrB", NULL);
+ qdev_prop_set_chr(dev, "chrA", NULL);
+ qdev_prop_set_uint32(dev, "chnBtype", mouse);
+ qdev_prop_set_uint32(dev, "chnAtype", kbd);
+ qdev_init_nofail(dev);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_connect_irq(s, 0, irq);
+ sysbus_connect_irq(s, 1, irq);
+ sysbus_mmio_map(s, 0, base);
+}
+
+static int escc_init1(SysBusDevice *dev)
+{
+ ESCCState *s = ESCC(dev);
+ unsigned int i;
+
+ s->chn[0].disabled = s->disabled;
+ s->chn[1].disabled = s->disabled;
+ for (i = 0; i < 2; i++) {
+ sysbus_init_irq(dev, &s->chn[i].irq);
+ s->chn[i].chn = 1 - i;
+ s->chn[i].clock = s->frequency / 2;
+ if (s->chn[i].chr) {
+ qemu_chr_add_handlers(s->chn[i].chr, serial_can_receive,
+ serial_receive1, serial_event, &s->chn[i]);
+ }
+ }
+ s->chn[0].otherchn = &s->chn[1];
+ s->chn[1].otherchn = &s->chn[0];
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &escc_mem_ops, s, "escc",
+ ESCC_SIZE << s->it_shift);
+ sysbus_init_mmio(dev, &s->mmio);
+
+ if (s->chn[0].type == mouse) {
+ qemu_add_mouse_event_handler(sunmouse_event, &s->chn[0], 0,
+ "QEMU Sun Mouse");
+ }
+ if (s->chn[1].type == kbd) {
+ qemu_add_kbd_event_handler(sunkbd_event, &s->chn[1]);
+ }
+
+ return 0;
+}
+
+static Property escc_properties[] = {
+ DEFINE_PROP_UINT32("frequency", ESCCState, frequency, 0),
+ DEFINE_PROP_UINT32("it_shift", ESCCState, it_shift, 0),
+ DEFINE_PROP_UINT32("disabled", ESCCState, disabled, 0),
+ DEFINE_PROP_UINT32("chnBtype", ESCCState, chn[0].type, 0),
+ DEFINE_PROP_UINT32("chnAtype", ESCCState, chn[1].type, 0),
+ DEFINE_PROP_CHR("chrB", ESCCState, chn[0].chr),
+ DEFINE_PROP_CHR("chrA", ESCCState, chn[1].chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void escc_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = escc_init1;
+ dc->reset = escc_reset;
+ dc->vmsd = &vmstate_escc;
+ dc->props = escc_properties;
+}
+
+static const TypeInfo escc_info = {
+ .name = TYPE_ESCC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ESCCState),
+ .class_init = escc_class_init,
+};
+
+static void escc_register_types(void)
+{
+ type_register_static(&escc_info);
+}
+
+type_init(escc_register_types)
diff --git a/hw/char/etraxfs_ser.c b/hw/char/etraxfs_ser.c
new file mode 100644
index 000000000..460094e79
--- /dev/null
+++ b/hw/char/etraxfs_ser.c
@@ -0,0 +1,252 @@
+/*
+ * QEMU ETRAX System Emulator
+ *
+ * Copyright (c) 2007 Edgar E. Iglesias, Axis Communications AB.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+#include "qemu/log.h"
+
+#define D(x)
+
+#define RW_TR_CTRL (0x00 / 4)
+#define RW_TR_DMA_EN (0x04 / 4)
+#define RW_REC_CTRL (0x08 / 4)
+#define RW_DOUT (0x1c / 4)
+#define RS_STAT_DIN (0x20 / 4)
+#define R_STAT_DIN (0x24 / 4)
+#define RW_INTR_MASK (0x2c / 4)
+#define RW_ACK_INTR (0x30 / 4)
+#define R_INTR (0x34 / 4)
+#define R_MASKED_INTR (0x38 / 4)
+#define R_MAX (0x3c / 4)
+
+#define STAT_DAV 16
+#define STAT_TR_IDLE 22
+#define STAT_TR_RDY 24
+
+#define TYPE_ETRAX_FS_SERIAL "etraxfs,serial"
+#define ETRAX_SERIAL(obj) \
+ OBJECT_CHECK(ETRAXSerial, (obj), TYPE_ETRAX_FS_SERIAL)
+
+typedef struct ETRAXSerial {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ int pending_tx;
+
+ uint8_t rx_fifo[16];
+ unsigned int rx_fifo_pos;
+ unsigned int rx_fifo_len;
+
+ /* Control registers. */
+ uint32_t regs[R_MAX];
+} ETRAXSerial;
+
+static void ser_update_irq(ETRAXSerial *s)
+{
+
+ if (s->rx_fifo_len) {
+ s->regs[R_INTR] |= 8;
+ } else {
+ s->regs[R_INTR] &= ~8;
+ }
+
+ s->regs[R_MASKED_INTR] = s->regs[R_INTR] & s->regs[RW_INTR_MASK];
+ qemu_set_irq(s->irq, !!s->regs[R_MASKED_INTR]);
+}
+
+static uint64_t
+ser_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ ETRAXSerial *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_STAT_DIN:
+ r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 15];
+ if (s->rx_fifo_len) {
+ r |= 1 << STAT_DAV;
+ }
+ r |= 1 << STAT_TR_RDY;
+ r |= 1 << STAT_TR_IDLE;
+ break;
+ case RS_STAT_DIN:
+ r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 15];
+ if (s->rx_fifo_len) {
+ r |= 1 << STAT_DAV;
+ s->rx_fifo_len--;
+ }
+ r |= 1 << STAT_TR_RDY;
+ r |= 1 << STAT_TR_IDLE;
+ break;
+ default:
+ r = s->regs[addr];
+ D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr, r));
+ break;
+ }
+ return r;
+}
+
+static void
+ser_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ ETRAXSerial *s = opaque;
+ uint32_t value = val64;
+ unsigned char ch = val64;
+
+ D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr, value));
+ addr >>= 2;
+ switch (addr)
+ {
+ case RW_DOUT:
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ s->regs[R_INTR] |= 3;
+ s->pending_tx = 1;
+ s->regs[addr] = value;
+ break;
+ case RW_ACK_INTR:
+ if (s->pending_tx) {
+ value &= ~1;
+ s->pending_tx = 0;
+ D(qemu_log("fixedup value=%x r_intr=%x\n",
+ value, s->regs[R_INTR]));
+ }
+ s->regs[addr] = value;
+ s->regs[R_INTR] &= ~value;
+ D(printf("r_intr=%x\n", s->regs[R_INTR]));
+ break;
+ default:
+ s->regs[addr] = value;
+ break;
+ }
+ ser_update_irq(s);
+}
+
+static const MemoryRegionOps ser_ops = {
+ .read = ser_read,
+ .write = ser_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void serial_receive(void *opaque, const uint8_t *buf, int size)
+{
+ ETRAXSerial *s = opaque;
+ int i;
+
+ /* Got a byte. */
+ if (s->rx_fifo_len >= 16) {
+ qemu_log("WARNING: UART dropped char.\n");
+ return;
+ }
+
+ for (i = 0; i < size; i++) {
+ s->rx_fifo[s->rx_fifo_pos] = buf[i];
+ s->rx_fifo_pos++;
+ s->rx_fifo_pos &= 15;
+ s->rx_fifo_len++;
+ }
+
+ ser_update_irq(s);
+}
+
+static int serial_can_receive(void *opaque)
+{
+ ETRAXSerial *s = opaque;
+ int r;
+
+ /* Is the receiver enabled? */
+ if (!(s->regs[RW_REC_CTRL] & (1 << 3))) {
+ return 0;
+ }
+
+ r = sizeof(s->rx_fifo) - s->rx_fifo_len;
+ return r;
+}
+
+static void serial_event(void *opaque, int event)
+{
+
+}
+
+static void etraxfs_ser_reset(DeviceState *d)
+{
+ ETRAXSerial *s = ETRAX_SERIAL(d);
+
+ /* transmitter begins ready and idle. */
+ s->regs[RS_STAT_DIN] |= (1 << STAT_TR_RDY);
+ s->regs[RS_STAT_DIN] |= (1 << STAT_TR_IDLE);
+
+ s->regs[RW_REC_CTRL] = 0x10000;
+
+}
+
+static int etraxfs_ser_init(SysBusDevice *dev)
+{
+ ETRAXSerial *s = ETRAX_SERIAL(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+ memory_region_init_io(&s->mmio, OBJECT(s), &ser_ops, s,
+ "etraxfs-serial", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->mmio);
+
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr,
+ serial_can_receive, serial_receive,
+ serial_event, s);
+ }
+ return 0;
+}
+
+static void etraxfs_ser_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = etraxfs_ser_init;
+ dc->reset = etraxfs_ser_reset;
+}
+
+static const TypeInfo etraxfs_ser_info = {
+ .name = TYPE_ETRAX_FS_SERIAL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(ETRAXSerial),
+ .class_init = etraxfs_ser_class_init,
+};
+
+static void etraxfs_serial_register_types(void)
+{
+ type_register_static(&etraxfs_ser_info);
+}
+
+type_init(etraxfs_serial_register_types)
diff --git a/hw/char/exynos4210_uart.c b/hw/char/exynos4210_uart.c
new file mode 100644
index 000000000..eef23a0cc
--- /dev/null
+++ b/hw/char/exynos4210_uart.c
@@ -0,0 +1,680 @@
+/*
+ * Exynos4210 UART Emulation
+ *
+ * Copyright (C) 2011 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/char.h"
+
+#include "hw/arm/exynos4210.h"
+
+#undef DEBUG_UART
+#undef DEBUG_UART_EXTEND
+#undef DEBUG_IRQ
+#undef DEBUG_Rx_DATA
+#undef DEBUG_Tx_DATA
+
+#define DEBUG_UART 0
+#define DEBUG_UART_EXTEND 0
+#define DEBUG_IRQ 0
+#define DEBUG_Rx_DATA 0
+#define DEBUG_Tx_DATA 0
+
+#if DEBUG_UART
+#define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#if DEBUG_UART_EXTEND
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+#else
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do {} while (0)
+#endif /* EXTEND */
+
+#else
+#define PRINT_DEBUG(fmt, args...) \
+ do {} while (0)
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do {} while (0)
+#endif
+
+#define PRINT_ERROR(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+/*
+ * Offsets for UART registers relative to SFR base address
+ * for UARTn
+ *
+ */
+#define ULCON 0x0000 /* Line Control */
+#define UCON 0x0004 /* Control */
+#define UFCON 0x0008 /* FIFO Control */
+#define UMCON 0x000C /* Modem Control */
+#define UTRSTAT 0x0010 /* Tx/Rx Status */
+#define UERSTAT 0x0014 /* UART Error Status */
+#define UFSTAT 0x0018 /* FIFO Status */
+#define UMSTAT 0x001C /* Modem Status */
+#define UTXH 0x0020 /* Transmit Buffer */
+#define URXH 0x0024 /* Receive Buffer */
+#define UBRDIV 0x0028 /* Baud Rate Divisor */
+#define UFRACVAL 0x002C /* Divisor Fractional Value */
+#define UINTP 0x0030 /* Interrupt Pending */
+#define UINTSP 0x0034 /* Interrupt Source Pending */
+#define UINTM 0x0038 /* Interrupt Mask */
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+typedef struct Exynos4210UartReg {
+ const char *name; /* the only reason is the debug output */
+ hwaddr offset;
+ uint32_t reset_value;
+} Exynos4210UartReg;
+
+static Exynos4210UartReg exynos4210_uart_regs[] = {
+ {"ULCON", ULCON, 0x00000000},
+ {"UCON", UCON, 0x00003000},
+ {"UFCON", UFCON, 0x00000000},
+ {"UMCON", UMCON, 0x00000000},
+ {"UTRSTAT", UTRSTAT, 0x00000006}, /* RO */
+ {"UERSTAT", UERSTAT, 0x00000000}, /* RO */
+ {"UFSTAT", UFSTAT, 0x00000000}, /* RO */
+ {"UMSTAT", UMSTAT, 0x00000000}, /* RO */
+ {"UTXH", UTXH, 0x5c5c5c5c}, /* WO, undefined reset value*/
+ {"URXH", URXH, 0x00000000}, /* RO */
+ {"UBRDIV", UBRDIV, 0x00000000},
+ {"UFRACVAL", UFRACVAL, 0x00000000},
+ {"UINTP", UINTP, 0x00000000},
+ {"UINTSP", UINTSP, 0x00000000},
+ {"UINTM", UINTM, 0x00000000},
+};
+
+#define EXYNOS4210_UART_REGS_MEM_SIZE 0x3C
+
+/* UART FIFO Control */
+#define UFCON_FIFO_ENABLE 0x1
+#define UFCON_Rx_FIFO_RESET 0x2
+#define UFCON_Tx_FIFO_RESET 0x4
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT 8
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT)
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT 4
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT)
+
+/* Uart FIFO Status */
+#define UFSTAT_Rx_FIFO_COUNT 0xff
+#define UFSTAT_Rx_FIFO_FULL 0x100
+#define UFSTAT_Rx_FIFO_ERROR 0x200
+#define UFSTAT_Tx_FIFO_COUNT_SHIFT 16
+#define UFSTAT_Tx_FIFO_COUNT (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT)
+#define UFSTAT_Tx_FIFO_FULL_SHIFT 24
+#define UFSTAT_Tx_FIFO_FULL (1 << UFSTAT_Tx_FIFO_FULL_SHIFT)
+
+/* UART Interrupt Source Pending */
+#define UINTSP_RXD 0x1 /* Receive interrupt */
+#define UINTSP_ERROR 0x2 /* Error interrupt */
+#define UINTSP_TXD 0x4 /* Transmit interrupt */
+#define UINTSP_MODEM 0x8 /* Modem interrupt */
+
+/* UART Line Control */
+#define ULCON_IR_MODE_SHIFT 6
+#define ULCON_PARITY_SHIFT 3
+#define ULCON_STOP_BIT_SHIFT 1
+
+/* UART Tx/Rx Status */
+#define UTRSTAT_TRANSMITTER_EMPTY 0x4
+#define UTRSTAT_Tx_BUFFER_EMPTY 0x2
+#define UTRSTAT_Rx_BUFFER_DATA_READY 0x1
+
+/* UART Error Status */
+#define UERSTAT_OVERRUN 0x1
+#define UERSTAT_PARITY 0x2
+#define UERSTAT_FRAME 0x4
+#define UERSTAT_BREAK 0x8
+
+typedef struct {
+ uint8_t *data;
+ uint32_t sp, rp; /* store and retrieve pointers */
+ uint32_t size;
+} Exynos4210UartFIFO;
+
+#define TYPE_EXYNOS4210_UART "exynos4210.uart"
+#define EXYNOS4210_UART(obj) \
+ OBJECT_CHECK(Exynos4210UartState, (obj), TYPE_EXYNOS4210_UART)
+
+typedef struct Exynos4210UartState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t reg[EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)];
+ Exynos4210UartFIFO rx;
+ Exynos4210UartFIFO tx;
+
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t channel;
+
+} Exynos4210UartState;
+
+
+#if DEBUG_UART
+/* Used only for debugging inside PRINT_DEBUG_... macros */
+static const char *exynos4210_uart_regname(hwaddr offset)
+{
+
+ int regs_number = sizeof(exynos4210_uart_regs) / sizeof(Exynos4210UartReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ if (offset == exynos4210_uart_regs[i].offset) {
+ return exynos4210_uart_regs[i].name;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+
+static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch)
+{
+ q->data[q->sp] = ch;
+ q->sp = (q->sp + 1) % q->size;
+}
+
+static uint8_t fifo_retrieve(Exynos4210UartFIFO *q)
+{
+ uint8_t ret = q->data[q->rp];
+ q->rp = (q->rp + 1) % q->size;
+ return ret;
+}
+
+static int fifo_elements_number(Exynos4210UartFIFO *q)
+{
+ if (q->sp < q->rp) {
+ return q->size - q->rp + q->sp;
+ }
+
+ return q->sp - q->rp;
+}
+
+static int fifo_empty_elements_number(Exynos4210UartFIFO *q)
+{
+ return q->size - fifo_elements_number(q);
+}
+
+static void fifo_reset(Exynos4210UartFIFO *q)
+{
+ if (q->data != NULL) {
+ g_free(q->data);
+ q->data = NULL;
+ }
+
+ q->data = (uint8_t *)g_malloc0(q->size);
+
+ q->sp = 0;
+ q->rp = 0;
+}
+
+static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(Exynos4210UartState *s)
+{
+ uint32_t level = 0;
+ uint32_t reg;
+
+ reg = (s->reg[I_(UFCON)] & UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
+ UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
+
+ switch (s->channel) {
+ case 0:
+ level = reg * 32;
+ break;
+ case 1:
+ case 4:
+ level = reg * 8;
+ break;
+ case 2:
+ case 3:
+ level = reg * 2;
+ break;
+ default:
+ level = 0;
+ PRINT_ERROR("Wrong UART channel number: %d\n", s->channel);
+ }
+
+ return level;
+}
+
+static void exynos4210_uart_update_irq(Exynos4210UartState *s)
+{
+ /*
+ * The Tx interrupt is always requested if the number of data in the
+ * transmit FIFO is smaller than the trigger level.
+ */
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+
+ uint32_t count = (s->reg[I_(UFSTAT)] & UFSTAT_Tx_FIFO_COUNT) >>
+ UFSTAT_Tx_FIFO_COUNT_SHIFT;
+
+ if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) {
+ s->reg[I_(UINTSP)] |= UINTSP_TXD;
+ }
+ }
+
+ s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)];
+
+ if (s->reg[I_(UINTP)]) {
+ qemu_irq_raise(s->irq);
+
+#if DEBUG_IRQ
+ fprintf(stderr, "UART%d: IRQ has been raised: %08x\n",
+ s->channel, s->reg[I_(UINTP)]);
+#endif
+
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
+{
+ int speed, parity, data_bits, stop_bits, frame_size;
+ QEMUSerialSetParams ssp;
+ uint64_t uclk_rate;
+
+ if (s->reg[I_(UBRDIV)] == 0) {
+ return;
+ }
+
+ frame_size = 1; /* start bit */
+ if (s->reg[I_(ULCON)] & 0x20) {
+ frame_size++; /* parity bit */
+ if (s->reg[I_(ULCON)] & 0x28) {
+ parity = 'E';
+ } else {
+ parity = 'O';
+ }
+ } else {
+ parity = 'N';
+ }
+
+ if (s->reg[I_(ULCON)] & 0x4) {
+ stop_bits = 2;
+ } else {
+ stop_bits = 1;
+ }
+
+ data_bits = (s->reg[I_(ULCON)] & 0x3) + 5;
+
+ frame_size += data_bits + stop_bits;
+
+ uclk_rate = 24000000;
+
+ speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) +
+ (s->reg[I_(UFRACVAL)] & 0x7) + 16);
+
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+
+ PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n",
+ s->channel, speed, parity, data_bits, stop_bits);
+}
+
+static void exynos4210_uart_write(void *opaque, hwaddr offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ uint8_t ch;
+
+ PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n", s->channel,
+ offset, exynos4210_uart_regname(offset), (long long unsigned int)val);
+
+ switch (offset) {
+ case ULCON:
+ case UBRDIV:
+ case UFRACVAL:
+ s->reg[I_(offset)] = val;
+ exynos4210_uart_update_parameters(s);
+ break;
+ case UFCON:
+ s->reg[I_(UFCON)] = val;
+ if (val & UFCON_Rx_FIFO_RESET) {
+ fifo_reset(&s->rx);
+ s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET;
+ PRINT_DEBUG("UART%d: Rx FIFO Reset\n", s->channel);
+ }
+ if (val & UFCON_Tx_FIFO_RESET) {
+ fifo_reset(&s->tx);
+ s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET;
+ PRINT_DEBUG("UART%d: Tx FIFO Reset\n", s->channel);
+ }
+ break;
+
+ case UTXH:
+ if (s->chr) {
+ s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY |
+ UTRSTAT_Tx_BUFFER_EMPTY);
+ ch = (uint8_t)val;
+ qemu_chr_fe_write(s->chr, &ch, 1);
+#if DEBUG_Tx_DATA
+ fprintf(stderr, "%c", ch);
+#endif
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY |
+ UTRSTAT_Tx_BUFFER_EMPTY;
+ s->reg[I_(UINTSP)] |= UINTSP_TXD;
+ exynos4210_uart_update_irq(s);
+ }
+ break;
+
+ case UINTP:
+ s->reg[I_(UINTP)] &= ~val;
+ s->reg[I_(UINTSP)] &= ~val;
+ PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n",
+ s->channel, offset, s->reg[I_(UINTP)]);
+ exynos4210_uart_update_irq(s);
+ break;
+ case UTRSTAT:
+ case UERSTAT:
+ case UFSTAT:
+ case UMSTAT:
+ case URXH:
+ PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n",
+ s->channel, exynos4210_uart_regname(offset), offset);
+ break;
+ case UINTSP:
+ s->reg[I_(UINTSP)] &= ~val;
+ break;
+ case UINTM:
+ s->reg[I_(UINTM)] = val;
+ exynos4210_uart_update_irq(s);
+ break;
+ case UCON:
+ case UMCON:
+ default:
+ s->reg[I_(offset)] = val;
+ break;
+ }
+}
+static uint64_t exynos4210_uart_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ uint32_t res;
+
+ switch (offset) {
+ case UERSTAT: /* Read Only */
+ res = s->reg[I_(UERSTAT)];
+ s->reg[I_(UERSTAT)] = 0;
+ return res;
+ case UFSTAT: /* Read Only */
+ s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff;
+ if (fifo_empty_elements_number(&s->rx) == 0) {
+ s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL;
+ s->reg[I_(UFSTAT)] &= ~0xff;
+ }
+ return s->reg[I_(UFSTAT)];
+ case URXH:
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+ if (fifo_elements_number(&s->rx)) {
+ res = fifo_retrieve(&s->rx);
+#if DEBUG_Rx_DATA
+ fprintf(stderr, "%c", res);
+#endif
+ if (!fifo_elements_number(&s->rx)) {
+ s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+ } else {
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+ } else {
+ s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+ exynos4210_uart_update_irq(s);
+ res = 0;
+ }
+ } else {
+ s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+ res = s->reg[I_(URXH)];
+ }
+ return res;
+ case UTXH:
+ PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n",
+ s->channel, exynos4210_uart_regname(offset), offset);
+ break;
+ default:
+ return s->reg[I_(offset)];
+ }
+
+ return 0;
+}
+
+static const MemoryRegionOps exynos4210_uart_ops = {
+ .read = exynos4210_uart_read,
+ .write = exynos4210_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .max_access_size = 4,
+ .unaligned = false
+ },
+};
+
+static int exynos4210_uart_can_receive(void *opaque)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+ return fifo_empty_elements_number(&s->rx);
+}
+
+
+static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ int i;
+
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+ if (fifo_empty_elements_number(&s->rx) < size) {
+ for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) {
+ fifo_store(&s->rx, buf[i]);
+ }
+ s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ } else {
+ for (i = 0; i < size; i++) {
+ fifo_store(&s->rx, buf[i]);
+ }
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+ /* XXX: Around here we maybe should check Rx trigger level */
+ s->reg[I_(UINTSP)] |= UINTSP_RXD;
+ } else {
+ s->reg[I_(URXH)] = buf[0];
+ s->reg[I_(UINTSP)] |= UINTSP_RXD;
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+
+ exynos4210_uart_update_irq(s);
+}
+
+
+static void exynos4210_uart_event(void *opaque, int event)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+ if (event == CHR_EVENT_BREAK) {
+ /* When the RxDn is held in logic 0, then a null byte is pushed into the
+ * fifo */
+ fifo_store(&s->rx, '\0');
+ s->reg[I_(UERSTAT)] |= UERSTAT_BREAK;
+ exynos4210_uart_update_irq(s);
+ }
+}
+
+
+static void exynos4210_uart_reset(DeviceState *dev)
+{
+ Exynos4210UartState *s = EXYNOS4210_UART(dev);
+ int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ s->reg[I_(exynos4210_uart_regs[i].offset)] =
+ exynos4210_uart_regs[i].reset_value;
+ }
+
+ fifo_reset(&s->rx);
+ fifo_reset(&s->tx);
+
+ PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
+}
+
+static const VMStateDescription vmstate_exynos4210_uart_fifo = {
+ .name = "exynos4210.uart.fifo",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(sp, Exynos4210UartFIFO),
+ VMSTATE_UINT32(rp, Exynos4210UartFIFO),
+ VMSTATE_VBUFFER_UINT32(data, Exynos4210UartFIFO, 1, NULL, 0, size),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_exynos4210_uart = {
+ .name = "exynos4210.uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(rx, Exynos4210UartState, 1,
+ vmstate_exynos4210_uart_fifo, Exynos4210UartFIFO),
+ VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState,
+ EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+DeviceState *exynos4210_uart_create(hwaddr addr,
+ int fifo_size,
+ int channel,
+ CharDriverState *chr,
+ qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *bus;
+
+ const char chr_name[] = "serial";
+ char label[ARRAY_SIZE(chr_name) + 1];
+
+ dev = qdev_create(NULL, TYPE_EXYNOS4210_UART);
+
+ if (!chr) {
+ if (channel >= MAX_SERIAL_PORTS) {
+ hw_error("Only %d serial ports are supported by QEMU.\n",
+ MAX_SERIAL_PORTS);
+ }
+ chr = serial_hds[channel];
+ if (!chr) {
+ snprintf(label, ARRAY_SIZE(label), "%s%d", chr_name, channel);
+ chr = qemu_chr_new(label, "null", NULL);
+ if (!(chr)) {
+ hw_error("Can't assign serial port to UART%d.\n", channel);
+ }
+ }
+ }
+
+ qdev_prop_set_chr(dev, "chardev", chr);
+ qdev_prop_set_uint32(dev, "channel", channel);
+ qdev_prop_set_uint32(dev, "rx-size", fifo_size);
+ qdev_prop_set_uint32(dev, "tx-size", fifo_size);
+
+ bus = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ if (addr != (hwaddr)-1) {
+ sysbus_mmio_map(bus, 0, addr);
+ }
+ sysbus_connect_irq(bus, 0, irq);
+
+ return dev;
+}
+
+static int exynos4210_uart_init(SysBusDevice *dev)
+{
+ Exynos4210UartState *s = EXYNOS4210_UART(dev);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_uart_ops, s,
+ "exynos4210.uart", EXYNOS4210_UART_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ qemu_chr_add_handlers(s->chr, exynos4210_uart_can_receive,
+ exynos4210_uart_receive, exynos4210_uart_event, s);
+
+ return 0;
+}
+
+static Property exynos4210_uart_properties[] = {
+ DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr),
+ DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0),
+ DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16),
+ DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void exynos4210_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = exynos4210_uart_init;
+ dc->reset = exynos4210_uart_reset;
+ dc->props = exynos4210_uart_properties;
+ dc->vmsd = &vmstate_exynos4210_uart;
+}
+
+static const TypeInfo exynos4210_uart_info = {
+ .name = TYPE_EXYNOS4210_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Exynos4210UartState),
+ .class_init = exynos4210_uart_class_init,
+};
+
+static void exynos4210_uart_register(void)
+{
+ type_register_static(&exynos4210_uart_info);
+}
+
+type_init(exynos4210_uart_register)
diff --git a/hw/char/grlib_apbuart.c b/hw/char/grlib_apbuart.c
new file mode 100644
index 000000000..35ef66177
--- /dev/null
+++ b/hw/char/grlib_apbuart.c
@@ -0,0 +1,298 @@
+/*
+ * QEMU GRLIB APB UART Emulator
+ *
+ * Copyright (c) 2010-2011 AdaCore
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#include "trace.h"
+
+#define UART_REG_SIZE 20 /* Size of memory mapped registers */
+
+/* UART status register fields */
+#define UART_DATA_READY (1 << 0)
+#define UART_TRANSMIT_SHIFT_EMPTY (1 << 1)
+#define UART_TRANSMIT_FIFO_EMPTY (1 << 2)
+#define UART_BREAK_RECEIVED (1 << 3)
+#define UART_OVERRUN (1 << 4)
+#define UART_PARITY_ERROR (1 << 5)
+#define UART_FRAMING_ERROR (1 << 6)
+#define UART_TRANSMIT_FIFO_HALF (1 << 7)
+#define UART_RECEIVE_FIFO_HALF (1 << 8)
+#define UART_TRANSMIT_FIFO_FULL (1 << 9)
+#define UART_RECEIVE_FIFO_FULL (1 << 10)
+
+/* UART control register fields */
+#define UART_RECEIVE_ENABLE (1 << 0)
+#define UART_TRANSMIT_ENABLE (1 << 1)
+#define UART_RECEIVE_INTERRUPT (1 << 2)
+#define UART_TRANSMIT_INTERRUPT (1 << 3)
+#define UART_PARITY_SELECT (1 << 4)
+#define UART_PARITY_ENABLE (1 << 5)
+#define UART_FLOW_CONTROL (1 << 6)
+#define UART_LOOPBACK (1 << 7)
+#define UART_EXTERNAL_CLOCK (1 << 8)
+#define UART_RECEIVE_FIFO_INTERRUPT (1 << 9)
+#define UART_TRANSMIT_FIFO_INTERRUPT (1 << 10)
+#define UART_FIFO_DEBUG_MODE (1 << 11)
+#define UART_OUTPUT_ENABLE (1 << 12)
+#define UART_FIFO_AVAILABLE (1 << 31)
+
+/* Memory mapped register offsets */
+#define DATA_OFFSET 0x00
+#define STATUS_OFFSET 0x04
+#define CONTROL_OFFSET 0x08
+#define SCALER_OFFSET 0x0C /* not supported */
+#define FIFO_DEBUG_OFFSET 0x10 /* not supported */
+
+#define FIFO_LENGTH 1024
+
+#define TYPE_GRLIB_APB_UART "grlib,apbuart"
+#define GRLIB_APB_UART(obj) \
+ OBJECT_CHECK(UART, (obj), TYPE_GRLIB_APB_UART)
+
+typedef struct UART {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ CharDriverState *chr;
+
+ /* registers */
+ uint32_t status;
+ uint32_t control;
+
+ /* FIFO */
+ char buffer[FIFO_LENGTH];
+ int len;
+ int current;
+} UART;
+
+static int uart_data_to_read(UART *uart)
+{
+ return uart->current < uart->len;
+}
+
+static char uart_pop(UART *uart)
+{
+ char ret;
+
+ if (uart->len == 0) {
+ uart->status &= ~UART_DATA_READY;
+ return 0;
+ }
+
+ ret = uart->buffer[uart->current++];
+
+ if (uart->current >= uart->len) {
+ /* Flush */
+ uart->len = 0;
+ uart->current = 0;
+ }
+
+ if (!uart_data_to_read(uart)) {
+ uart->status &= ~UART_DATA_READY;
+ }
+
+ return ret;
+}
+
+static void uart_add_to_fifo(UART *uart,
+ const uint8_t *buffer,
+ int length)
+{
+ if (uart->len + length > FIFO_LENGTH) {
+ abort();
+ }
+ memcpy(uart->buffer + uart->len, buffer, length);
+ uart->len += length;
+}
+
+static int grlib_apbuart_can_receive(void *opaque)
+{
+ UART *uart = opaque;
+
+ return FIFO_LENGTH - uart->len;
+}
+
+static void grlib_apbuart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ UART *uart = opaque;
+
+ if (uart->control & UART_RECEIVE_ENABLE) {
+ uart_add_to_fifo(uart, buf, size);
+
+ uart->status |= UART_DATA_READY;
+
+ if (uart->control & UART_RECEIVE_INTERRUPT) {
+ qemu_irq_pulse(uart->irq);
+ }
+ }
+}
+
+static void grlib_apbuart_event(void *opaque, int event)
+{
+ trace_grlib_apbuart_event(event);
+}
+
+
+static uint64_t grlib_apbuart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ UART *uart = opaque;
+
+ addr &= 0xff;
+
+ /* Unit registers */
+ switch (addr) {
+ case DATA_OFFSET:
+ case DATA_OFFSET + 3: /* when only one byte read */
+ return uart_pop(uart);
+
+ case STATUS_OFFSET:
+ /* Read Only */
+ return uart->status;
+
+ case CONTROL_OFFSET:
+ return uart->control;
+
+ case SCALER_OFFSET:
+ /* Not supported */
+ return 0;
+
+ default:
+ trace_grlib_apbuart_readl_unknown(addr);
+ return 0;
+ }
+}
+
+static void grlib_apbuart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ UART *uart = opaque;
+ unsigned char c = 0;
+
+ addr &= 0xff;
+
+ /* Unit registers */
+ switch (addr) {
+ case DATA_OFFSET:
+ case DATA_OFFSET + 3: /* When only one byte write */
+ /* Transmit when character device available and transmitter enabled */
+ if ((uart->chr) && (uart->control & UART_TRANSMIT_ENABLE)) {
+ c = value & 0xFF;
+ qemu_chr_fe_write(uart->chr, &c, 1);
+ /* Generate interrupt */
+ if (uart->control & UART_TRANSMIT_INTERRUPT) {
+ qemu_irq_pulse(uart->irq);
+ }
+ }
+ return;
+
+ case STATUS_OFFSET:
+ /* Read Only */
+ return;
+
+ case CONTROL_OFFSET:
+ uart->control = value;
+ return;
+
+ case SCALER_OFFSET:
+ /* Not supported */
+ return;
+
+ default:
+ break;
+ }
+
+ trace_grlib_apbuart_writel_unknown(addr, value);
+}
+
+static const MemoryRegionOps grlib_apbuart_ops = {
+ .write = grlib_apbuart_write,
+ .read = grlib_apbuart_read,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int grlib_apbuart_init(SysBusDevice *dev)
+{
+ UART *uart = GRLIB_APB_UART(dev);
+
+ qemu_chr_add_handlers(uart->chr,
+ grlib_apbuart_can_receive,
+ grlib_apbuart_receive,
+ grlib_apbuart_event,
+ uart);
+
+ sysbus_init_irq(dev, &uart->irq);
+
+ memory_region_init_io(&uart->iomem, OBJECT(uart), &grlib_apbuart_ops, uart,
+ "uart", UART_REG_SIZE);
+
+ sysbus_init_mmio(dev, &uart->iomem);
+
+ return 0;
+}
+
+static void grlib_apbuart_reset(DeviceState *d)
+{
+ UART *uart = GRLIB_APB_UART(d);
+
+ /* Transmitter FIFO and shift registers are always empty in QEMU */
+ uart->status = UART_TRANSMIT_FIFO_EMPTY | UART_TRANSMIT_SHIFT_EMPTY;
+ /* Everything is off */
+ uart->control = 0;
+ /* Flush receive FIFO */
+ uart->len = 0;
+ uart->current = 0;
+}
+
+static Property grlib_apbuart_properties[] = {
+ DEFINE_PROP_CHR("chrdev", UART, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void grlib_apbuart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = grlib_apbuart_init;
+ dc->reset = grlib_apbuart_reset;
+ dc->props = grlib_apbuart_properties;
+}
+
+static const TypeInfo grlib_apbuart_info = {
+ .name = TYPE_GRLIB_APB_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(UART),
+ .class_init = grlib_apbuart_class_init,
+};
+
+static void grlib_apbuart_register_types(void)
+{
+ type_register_static(&grlib_apbuart_info);
+}
+
+type_init(grlib_apbuart_register_types)
diff --git a/hw/char/imx_serial.c b/hw/char/imx_serial.c
new file mode 100644
index 000000000..7f16835ae
--- /dev/null
+++ b/hw/char/imx_serial.c
@@ -0,0 +1,473 @@
+/*
+ * IMX31 UARTS
+ *
+ * Copyright (c) 2008 OKL
+ * Originally Written by Hans Jiang
+ * Copyright (c) 2011 NICTA Pty Ltd.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * This is a `bare-bones' implementation of the IMX series serial ports.
+ * TODO:
+ * -- implement FIFOs. The real hardware has 32 word transmit
+ * and receive FIFOs; we currently use a 1-char buffer
+ * -- implement DMA
+ * -- implement BAUD-rate and modem lines, for when the backend
+ * is a real serial device.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/char.h"
+#include "hw/arm/imx.h"
+
+//#define DEBUG_SERIAL 1
+#ifdef DEBUG_SERIAL
+#define DPRINTF(fmt, args...) \
+do { printf("imx_serial: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+/*
+ * Define to 1 for messages about attempts to
+ * access unimplemented registers or similar.
+ */
+//#define DEBUG_IMPLEMENTATION 1
+#ifdef DEBUG_IMPLEMENTATION
+# define IPRINTF(fmt, args...) \
+ do { fprintf(stderr, "imx_serial: " fmt, ##args); } while (0)
+#else
+# define IPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define TYPE_IMX_SERIAL "imx-serial"
+#define IMX_SERIAL(obj) OBJECT_CHECK(IMXSerialState, (obj), TYPE_IMX_SERIAL)
+
+typedef struct IMXSerialState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ int32_t readbuff;
+
+ uint32_t usr1;
+ uint32_t usr2;
+ uint32_t ucr1;
+ uint32_t ucr2;
+ uint32_t uts1;
+
+ /*
+ * The registers below are implemented just so that the
+ * guest OS sees what it has written
+ */
+ uint32_t onems;
+ uint32_t ufcr;
+ uint32_t ubmr;
+ uint32_t ubrc;
+ uint32_t ucr3;
+
+ qemu_irq irq;
+ CharDriverState *chr;
+} IMXSerialState;
+
+static const VMStateDescription vmstate_imx_serial = {
+ .name = "imx-serial",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(readbuff, IMXSerialState),
+ VMSTATE_UINT32(usr1, IMXSerialState),
+ VMSTATE_UINT32(usr2, IMXSerialState),
+ VMSTATE_UINT32(ucr1, IMXSerialState),
+ VMSTATE_UINT32(uts1, IMXSerialState),
+ VMSTATE_UINT32(onems, IMXSerialState),
+ VMSTATE_UINT32(ufcr, IMXSerialState),
+ VMSTATE_UINT32(ubmr, IMXSerialState),
+ VMSTATE_UINT32(ubrc, IMXSerialState),
+ VMSTATE_UINT32(ucr3, IMXSerialState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+
+#define URXD_CHARRDY (1<<15) /* character read is valid */
+#define URXD_ERR (1<<14) /* Character has error */
+#define URXD_BRK (1<<11) /* Break received */
+
+#define USR1_PARTYER (1<<15) /* Parity Error */
+#define USR1_RTSS (1<<14) /* RTS pin status */
+#define USR1_TRDY (1<<13) /* Tx ready */
+#define USR1_RTSD (1<<12) /* RTS delta: pin changed state */
+#define USR1_ESCF (1<<11) /* Escape sequence interrupt */
+#define USR1_FRAMERR (1<<10) /* Framing error */
+#define USR1_RRDY (1<<9) /* receiver ready */
+#define USR1_AGTIM (1<<8) /* Aging timer interrupt */
+#define USR1_DTRD (1<<7) /* DTR changed */
+#define USR1_RXDS (1<<6) /* Receiver is idle */
+#define USR1_AIRINT (1<<5) /* Aysnch IR interrupt */
+#define USR1_AWAKE (1<<4) /* Falling edge detected on RXd pin */
+
+#define USR2_ADET (1<<15) /* Autobaud complete */
+#define USR2_TXFE (1<<14) /* Transmit FIFO empty */
+#define USR2_DTRF (1<<13) /* DTR/DSR transition */
+#define USR2_IDLE (1<<12) /* UART has been idle for too long */
+#define USR2_ACST (1<<11) /* Autobaud counter stopped */
+#define USR2_RIDELT (1<<10) /* Ring Indicator delta */
+#define USR2_RIIN (1<<9) /* Ring Indicator Input */
+#define USR2_IRINT (1<<8) /* Serial Infrared Interrupt */
+#define USR2_WAKE (1<<7) /* Start bit detected */
+#define USR2_DCDDELT (1<<6) /* Data Carrier Detect delta */
+#define USR2_DCDIN (1<<5) /* Data Carrier Detect Input */
+#define USR2_RTSF (1<<4) /* RTS transition */
+#define USR2_TXDC (1<<3) /* Transmission complete */
+#define USR2_BRCD (1<<2) /* Break condition detected */
+#define USR2_ORE (1<<1) /* Overrun error */
+#define USR2_RDR (1<<0) /* Receive data ready */
+
+#define UCR1_TRDYEN (1<<13) /* Tx Ready Interrupt Enable */
+#define UCR1_RRDYEN (1<<9) /* Rx Ready Interrupt Enable */
+#define UCR1_TXMPTYEN (1<<6) /* Tx Empty Interrupt Enable */
+#define UCR1_UARTEN (1<<0) /* UART Enable */
+
+#define UCR2_TXEN (1<<2) /* Transmitter enable */
+#define UCR2_RXEN (1<<1) /* Receiver enable */
+#define UCR2_SRST (1<<0) /* Reset complete */
+
+#define UTS1_TXEMPTY (1<<6)
+#define UTS1_RXEMPTY (1<<5)
+#define UTS1_TXFULL (1<<4)
+#define UTS1_RXFULL (1<<3)
+
+static void imx_update(IMXSerialState *s)
+{
+ uint32_t flags;
+
+ flags = (s->usr1 & s->ucr1) & (USR1_TRDY|USR1_RRDY);
+ if (!(s->ucr1 & UCR1_TXMPTYEN)) {
+ flags &= ~USR1_TRDY;
+ }
+
+ qemu_set_irq(s->irq, !!flags);
+}
+
+static void imx_serial_reset(IMXSerialState *s)
+{
+
+ s->usr1 = USR1_TRDY | USR1_RXDS;
+ /*
+ * Fake attachment of a terminal: assert RTS.
+ */
+ s->usr1 |= USR1_RTSS;
+ s->usr2 = USR2_TXFE | USR2_TXDC | USR2_DCDIN;
+ s->uts1 = UTS1_RXEMPTY | UTS1_TXEMPTY;
+ s->ucr1 = 0;
+ s->ucr2 = UCR2_SRST;
+ s->ucr3 = 0x700;
+ s->ubmr = 0;
+ s->ubrc = 4;
+ s->readbuff = URXD_ERR;
+}
+
+static void imx_serial_reset_at_boot(DeviceState *dev)
+{
+ IMXSerialState *s = IMX_SERIAL(dev);
+
+ imx_serial_reset(s);
+
+ /*
+ * enable the uart on boot, so messages from the linux decompresser
+ * are visible. On real hardware this is done by the boot rom
+ * before anything else is loaded.
+ */
+ s->ucr1 = UCR1_UARTEN;
+ s->ucr2 = UCR2_TXEN;
+
+}
+
+static uint64_t imx_serial_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ uint32_t c;
+
+ DPRINTF("read(offset=%x)\n", offset >> 2);
+ switch (offset >> 2) {
+ case 0x0: /* URXD */
+ c = s->readbuff;
+ if (!(s->uts1 & UTS1_RXEMPTY)) {
+ /* Character is valid */
+ c |= URXD_CHARRDY;
+ s->usr1 &= ~USR1_RRDY;
+ s->usr2 &= ~USR2_RDR;
+ s->uts1 |= UTS1_RXEMPTY;
+ imx_update(s);
+ qemu_chr_accept_input(s->chr);
+ }
+ return c;
+
+ case 0x20: /* UCR1 */
+ return s->ucr1;
+
+ case 0x21: /* UCR2 */
+ return s->ucr2;
+
+ case 0x25: /* USR1 */
+ return s->usr1;
+
+ case 0x26: /* USR2 */
+ return s->usr2;
+
+ case 0x2A: /* BRM Modulator */
+ return s->ubmr;
+
+ case 0x2B: /* Baud Rate Count */
+ return s->ubrc;
+
+ case 0x2d: /* Test register */
+ return s->uts1;
+
+ case 0x24: /* UFCR */
+ return s->ufcr;
+
+ case 0x2c:
+ return s->onems;
+
+ case 0x22: /* UCR3 */
+ return s->ucr3;
+
+ case 0x23: /* UCR4 */
+ case 0x29: /* BRM Incremental */
+ return 0x0; /* TODO */
+
+ default:
+ IPRINTF("imx_serial_read: bad offset: 0x%x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void imx_serial_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ unsigned char ch;
+
+ DPRINTF("write(offset=%x, value = %x) to %s\n",
+ offset >> 2,
+ (unsigned int)value, s->chr ? s->chr->label : "NODEV");
+
+ switch (offset >> 2) {
+ case 0x10: /* UTXD */
+ ch = value;
+ if (s->ucr2 & UCR2_TXEN) {
+ if (s->chr) {
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+ s->usr1 &= ~USR1_TRDY;
+ imx_update(s);
+ s->usr1 |= USR1_TRDY;
+ imx_update(s);
+ }
+ break;
+
+ case 0x20: /* UCR1 */
+ s->ucr1 = value & 0xffff;
+ DPRINTF("write(ucr1=%x)\n", (unsigned int)value);
+ imx_update(s);
+ break;
+
+ case 0x21: /* UCR2 */
+ /*
+ * Only a few bits in control register 2 are implemented as yet.
+ * If it's intended to use a real serial device as a back-end, this
+ * register will have to be implemented more fully.
+ */
+ if (!(value & UCR2_SRST)) {
+ imx_serial_reset(s);
+ imx_update(s);
+ value |= UCR2_SRST;
+ }
+ if (value & UCR2_RXEN) {
+ if (!(s->ucr2 & UCR2_RXEN)) {
+ qemu_chr_accept_input(s->chr);
+ }
+ }
+ s->ucr2 = value & 0xffff;
+ break;
+
+ case 0x25: /* USR1 */
+ value &= USR1_AWAKE | USR1_AIRINT | USR1_DTRD | USR1_AGTIM |
+ USR1_FRAMERR | USR1_ESCF | USR1_RTSD | USR1_PARTYER;
+ s->usr1 &= ~value;
+ break;
+
+ case 0x26: /* USR2 */
+ /*
+ * Writing 1 to some bits clears them; all other
+ * values are ignored
+ */
+ value &= USR2_ADET | USR2_DTRF | USR2_IDLE | USR2_ACST |
+ USR2_RIDELT | USR2_IRINT | USR2_WAKE |
+ USR2_DCDDELT | USR2_RTSF | USR2_BRCD | USR2_ORE;
+ s->usr2 &= ~value;
+ break;
+
+ /*
+ * Linux expects to see what it writes to these registers
+ * We don't currently alter the baud rate
+ */
+ case 0x29: /* UBIR */
+ s->ubrc = value & 0xffff;
+ break;
+
+ case 0x2a: /* UBMR */
+ s->ubmr = value & 0xffff;
+ break;
+
+ case 0x2c: /* One ms reg */
+ s->onems = value & 0xffff;
+ break;
+
+ case 0x24: /* FIFO control register */
+ s->ufcr = value & 0xffff;
+ break;
+
+ case 0x22: /* UCR3 */
+ s->ucr3 = value & 0xffff;
+ break;
+
+ case 0x2d: /* UTS1 */
+ case 0x23: /* UCR4 */
+ IPRINTF("Unimplemented Register %x written to\n", offset >> 2);
+ /* TODO */
+ break;
+
+ default:
+ IPRINTF("imx_serial_write: Bad offset 0x%x\n", (int)offset);
+ }
+}
+
+static int imx_can_receive(void *opaque)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ return !(s->usr1 & USR1_RRDY);
+}
+
+static void imx_put_data(void *opaque, uint32_t value)
+{
+ IMXSerialState *s = (IMXSerialState *)opaque;
+ DPRINTF("received char\n");
+ s->usr1 |= USR1_RRDY;
+ s->usr2 |= USR2_RDR;
+ s->uts1 &= ~UTS1_RXEMPTY;
+ s->readbuff = value;
+ imx_update(s);
+}
+
+static void imx_receive(void *opaque, const uint8_t *buf, int size)
+{
+ imx_put_data(opaque, *buf);
+}
+
+static void imx_event(void *opaque, int event)
+{
+ if (event == CHR_EVENT_BREAK) {
+ imx_put_data(opaque, URXD_BRK);
+ }
+}
+
+
+static const struct MemoryRegionOps imx_serial_ops = {
+ .read = imx_serial_read,
+ .write = imx_serial_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int imx_serial_init(SysBusDevice *dev)
+{
+ IMXSerialState *s = IMX_SERIAL(dev);
+
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &imx_serial_ops, s,
+ "imx-serial", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ sysbus_init_irq(dev, &s->irq);
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, imx_can_receive, imx_receive,
+ imx_event, s);
+ } else {
+ DPRINTF("No char dev for uart at 0x%lx\n",
+ (unsigned long)s->iomem.ram_addr);
+ }
+
+ return 0;
+}
+
+void imx_serial_create(int uart, const hwaddr addr, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *bus;
+ CharDriverState *chr;
+ const char chr_name[] = "serial";
+ char label[ARRAY_SIZE(chr_name) + 1];
+
+ dev = qdev_create(NULL, TYPE_IMX_SERIAL);
+
+ if (uart >= MAX_SERIAL_PORTS) {
+ hw_error("Cannot assign uart %d: QEMU supports only %d ports\n",
+ uart, MAX_SERIAL_PORTS);
+ }
+ chr = serial_hds[uart];
+ if (!chr) {
+ snprintf(label, ARRAY_SIZE(label), "%s%d", chr_name, uart);
+ chr = qemu_chr_new(label, "null", NULL);
+ if (!(chr)) {
+ hw_error("Can't assign serial port to imx-uart%d.\n", uart);
+ }
+ }
+
+ qdev_prop_set_chr(dev, "chardev", chr);
+ bus = SYS_BUS_DEVICE(dev);
+ qdev_init_nofail(dev);
+ if (addr != (hwaddr)-1) {
+ sysbus_mmio_map(bus, 0, addr);
+ }
+ sysbus_connect_irq(bus, 0, irq);
+
+}
+
+
+static Property imx32_serial_properties[] = {
+ DEFINE_PROP_CHR("chardev", IMXSerialState, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void imx_serial_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = imx_serial_init;
+ dc->vmsd = &vmstate_imx_serial;
+ dc->reset = imx_serial_reset_at_boot;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "i.MX series UART";
+ dc->props = imx32_serial_properties;
+}
+
+static const TypeInfo imx_serial_info = {
+ .name = TYPE_IMX_SERIAL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(IMXSerialState),
+ .class_init = imx_serial_class_init,
+};
+
+static void imx_serial_register_types(void)
+{
+ type_register_static(&imx_serial_info);
+}
+
+type_init(imx_serial_register_types)
diff --git a/hw/char/ipack.c b/hw/char/ipack.c
new file mode 100644
index 000000000..f890471db
--- /dev/null
+++ b/hw/char/ipack.c
@@ -0,0 +1,116 @@
+/*
+ * QEMU IndustryPack emulation
+ *
+ * Copyright (C) 2012 Igalia, S.L.
+ * Author: Alberto Garcia <agarcia@igalia.com>
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any
+ * later version.
+ */
+
+#include "ipack.h"
+
+IPackDevice *ipack_device_find(IPackBus *bus, int32_t slot)
+{
+ BusChild *kid;
+
+ QTAILQ_FOREACH(kid, &BUS(bus)->children, sibling) {
+ DeviceState *qdev = kid->child;
+ IPackDevice *ip = IPACK_DEVICE(qdev);
+ if (ip->slot == slot) {
+ return ip;
+ }
+ }
+ return NULL;
+}
+
+void ipack_bus_new_inplace(IPackBus *bus, DeviceState *parent,
+ const char *name, uint8_t n_slots,
+ qemu_irq_handler handler)
+{
+ qbus_create_inplace(&bus->qbus, TYPE_IPACK_BUS, parent, name);
+ bus->n_slots = n_slots;
+ bus->set_irq = handler;
+}
+
+static int ipack_device_dev_init(DeviceState *qdev)
+{
+ IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(qdev));
+ IPackDevice *dev = IPACK_DEVICE(qdev);
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev);
+
+ if (dev->slot < 0) {
+ dev->slot = bus->free_slot;
+ }
+ if (dev->slot >= bus->n_slots) {
+ return -1;
+ }
+ bus->free_slot = dev->slot + 1;
+
+ dev->irq = qemu_allocate_irqs(bus->set_irq, dev, 2);
+
+ return k->init(dev);
+}
+
+static int ipack_device_dev_exit(DeviceState *qdev)
+{
+ IPackDevice *dev = IPACK_DEVICE(qdev);
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev);
+
+ if (k->exit) {
+ k->exit(dev);
+ }
+
+ qemu_free_irqs(dev->irq);
+
+ return 0;
+}
+
+static Property ipack_device_props[] = {
+ DEFINE_PROP_INT32("slot", IPackDevice, slot, -1),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void ipack_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ set_bit(DEVICE_CATEGORY_INPUT, k->categories);
+ k->bus_type = TYPE_IPACK_BUS;
+ k->init = ipack_device_dev_init;
+ k->exit = ipack_device_dev_exit;
+ k->props = ipack_device_props;
+}
+
+const VMStateDescription vmstate_ipack_device = {
+ .name = "ipack_device",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(slot, IPackDevice),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const TypeInfo ipack_device_info = {
+ .name = TYPE_IPACK_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(IPackDevice),
+ .class_size = sizeof(IPackDeviceClass),
+ .class_init = ipack_device_class_init,
+ .abstract = true,
+};
+
+static const TypeInfo ipack_bus_info = {
+ .name = TYPE_IPACK_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(IPackBus),
+};
+
+static void ipack_register_types(void)
+{
+ type_register_static(&ipack_device_info);
+ type_register_static(&ipack_bus_info);
+}
+
+type_init(ipack_register_types)
diff --git a/hw/char/ipack.h b/hw/char/ipack.h
new file mode 100644
index 000000000..f2b7a12e0
--- /dev/null
+++ b/hw/char/ipack.h
@@ -0,0 +1,79 @@
+/*
+ * QEMU IndustryPack emulation
+ *
+ * Copyright (C) 2012 Igalia, S.L.
+ * Author: Alberto Garcia <agarcia@igalia.com>
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any
+ * later version.
+ */
+
+#ifndef QEMU_IPACK_H
+#define QEMU_IPACK_H
+
+#include "hw/qdev.h"
+
+typedef struct IPackBus IPackBus;
+
+#define TYPE_IPACK_BUS "IndustryPack"
+#define IPACK_BUS(obj) OBJECT_CHECK(IPackBus, (obj), TYPE_IPACK_BUS)
+
+struct IPackBus {
+ BusState qbus;
+ /* All fields are private */
+ uint8_t n_slots;
+ uint8_t free_slot;
+ qemu_irq_handler set_irq;
+};
+
+typedef struct IPackDevice IPackDevice;
+typedef struct IPackDeviceClass IPackDeviceClass;
+
+#define TYPE_IPACK_DEVICE "ipack-device"
+#define IPACK_DEVICE(obj) \
+ OBJECT_CHECK(IPackDevice, (obj), TYPE_IPACK_DEVICE)
+#define IPACK_DEVICE_CLASS(klass) \
+ OBJECT_CLASS_CHECK(IPackDeviceClass, (klass), TYPE_IPACK_DEVICE)
+#define IPACK_DEVICE_GET_CLASS(obj) \
+ OBJECT_GET_CLASS(IPackDeviceClass, (obj), TYPE_IPACK_DEVICE)
+
+struct IPackDeviceClass {
+ DeviceClass parent_class;
+
+ int (*init)(IPackDevice *dev);
+ int (*exit)(IPackDevice *dev);
+
+ uint16_t (*io_read)(IPackDevice *dev, uint8_t addr);
+ void (*io_write)(IPackDevice *dev, uint8_t addr, uint16_t val);
+
+ uint16_t (*id_read)(IPackDevice *dev, uint8_t addr);
+ void (*id_write)(IPackDevice *dev, uint8_t addr, uint16_t val);
+
+ uint16_t (*int_read)(IPackDevice *dev, uint8_t addr);
+ void (*int_write)(IPackDevice *dev, uint8_t addr, uint16_t val);
+
+ uint16_t (*mem_read16)(IPackDevice *dev, uint32_t addr);
+ void (*mem_write16)(IPackDevice *dev, uint32_t addr, uint16_t val);
+
+ uint8_t (*mem_read8)(IPackDevice *dev, uint32_t addr);
+ void (*mem_write8)(IPackDevice *dev, uint32_t addr, uint8_t val);
+};
+
+struct IPackDevice {
+ DeviceState qdev;
+ int32_t slot;
+ /* IRQ objects for the IndustryPack INT0# and INT1# */
+ qemu_irq *irq;
+};
+
+extern const VMStateDescription vmstate_ipack_device;
+
+#define VMSTATE_IPACK_DEVICE(_field, _state) \
+ VMSTATE_STRUCT(_field, _state, 1, vmstate_ipack_device, IPackDevice)
+
+IPackDevice *ipack_device_find(IPackBus *bus, int32_t slot);
+void ipack_bus_new_inplace(IPackBus *bus, DeviceState *parent,
+ const char *name, uint8_t n_slots,
+ qemu_irq_handler handler);
+
+#endif
diff --git a/hw/char/ipoctal232.c b/hw/char/ipoctal232.c
new file mode 100644
index 000000000..88e2ccae7
--- /dev/null
+++ b/hw/char/ipoctal232.c
@@ -0,0 +1,606 @@
+/*
+ * QEMU GE IP-Octal 232 IndustryPack emulation
+ *
+ * Copyright (C) 2012 Igalia, S.L.
+ * Author: Alberto Garcia <agarcia@igalia.com>
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any
+ * later version.
+ */
+
+#include "ipack.h"
+#include "qemu/bitops.h"
+#include "sysemu/char.h"
+
+/* #define DEBUG_IPOCTAL */
+
+#ifdef DEBUG_IPOCTAL
+#define DPRINTF2(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF2(fmt, ...) do { } while (0)
+#endif
+
+#define DPRINTF(fmt, ...) DPRINTF2("IP-Octal: " fmt, ## __VA_ARGS__)
+
+#define RX_FIFO_SIZE 3
+
+/* The IP-Octal has 8 channels (a-h)
+ divided into 4 blocks (A-D) */
+#define N_CHANNELS 8
+#define N_BLOCKS 4
+
+#define REG_MRa 0x01
+#define REG_MRb 0x11
+#define REG_SRa 0x03
+#define REG_SRb 0x13
+#define REG_CSRa 0x03
+#define REG_CSRb 0x13
+#define REG_CRa 0x05
+#define REG_CRb 0x15
+#define REG_RHRa 0x07
+#define REG_RHRb 0x17
+#define REG_THRa 0x07
+#define REG_THRb 0x17
+#define REG_ACR 0x09
+#define REG_ISR 0x0B
+#define REG_IMR 0x0B
+#define REG_OPCR 0x1B
+
+#define CR_ENABLE_RX BIT(0)
+#define CR_DISABLE_RX BIT(1)
+#define CR_ENABLE_TX BIT(2)
+#define CR_DISABLE_TX BIT(3)
+#define CR_CMD(cr) ((cr) >> 4)
+#define CR_NO_OP 0
+#define CR_RESET_MR 1
+#define CR_RESET_RX 2
+#define CR_RESET_TX 3
+#define CR_RESET_ERR 4
+#define CR_RESET_BRKINT 5
+#define CR_START_BRK 6
+#define CR_STOP_BRK 7
+#define CR_ASSERT_RTSN 8
+#define CR_NEGATE_RTSN 9
+#define CR_TIMEOUT_ON 10
+#define CR_TIMEOUT_OFF 12
+
+#define SR_RXRDY BIT(0)
+#define SR_FFULL BIT(1)
+#define SR_TXRDY BIT(2)
+#define SR_TXEMT BIT(3)
+#define SR_OVERRUN BIT(4)
+#define SR_PARITY BIT(5)
+#define SR_FRAMING BIT(6)
+#define SR_BREAK BIT(7)
+
+#define ISR_TXRDYA BIT(0)
+#define ISR_RXRDYA BIT(1)
+#define ISR_BREAKA BIT(2)
+#define ISR_CNTRDY BIT(3)
+#define ISR_TXRDYB BIT(4)
+#define ISR_RXRDYB BIT(5)
+#define ISR_BREAKB BIT(6)
+#define ISR_MPICHG BIT(7)
+#define ISR_TXRDY(CH) (((CH) & 1) ? BIT(4) : BIT(0))
+#define ISR_RXRDY(CH) (((CH) & 1) ? BIT(5) : BIT(1))
+#define ISR_BREAK(CH) (((CH) & 1) ? BIT(6) : BIT(2))
+
+typedef struct IPOctalState IPOctalState;
+typedef struct SCC2698Channel SCC2698Channel;
+typedef struct SCC2698Block SCC2698Block;
+
+struct SCC2698Channel {
+ IPOctalState *ipoctal;
+ CharDriverState *dev;
+ bool rx_enabled;
+ uint8_t mr[2];
+ uint8_t mr_idx;
+ uint8_t sr;
+ uint8_t rhr[RX_FIFO_SIZE];
+ uint8_t rhr_idx;
+ uint8_t rx_pending;
+};
+
+struct SCC2698Block {
+ uint8_t imr;
+ uint8_t isr;
+};
+
+struct IPOctalState {
+ IPackDevice dev;
+ SCC2698Channel ch[N_CHANNELS];
+ SCC2698Block blk[N_BLOCKS];
+ uint8_t irq_vector;
+};
+
+#define TYPE_IPOCTAL "ipoctal232"
+
+#define IPOCTAL(obj) \
+ OBJECT_CHECK(IPOctalState, (obj), TYPE_IPOCTAL)
+
+static const VMStateDescription vmstate_scc2698_channel = {
+ .name = "scc2698_channel",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(rx_enabled, SCC2698Channel),
+ VMSTATE_UINT8_ARRAY(mr, SCC2698Channel, 2),
+ VMSTATE_UINT8(mr_idx, SCC2698Channel),
+ VMSTATE_UINT8(sr, SCC2698Channel),
+ VMSTATE_UINT8_ARRAY(rhr, SCC2698Channel, RX_FIFO_SIZE),
+ VMSTATE_UINT8(rhr_idx, SCC2698Channel),
+ VMSTATE_UINT8(rx_pending, SCC2698Channel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_scc2698_block = {
+ .name = "scc2698_block",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(imr, SCC2698Block),
+ VMSTATE_UINT8(isr, SCC2698Block),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ipoctal = {
+ .name = "ipoctal232",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_IPACK_DEVICE(dev, IPOctalState),
+ VMSTATE_STRUCT_ARRAY(ch, IPOctalState, N_CHANNELS, 1,
+ vmstate_scc2698_channel, SCC2698Channel),
+ VMSTATE_STRUCT_ARRAY(blk, IPOctalState, N_BLOCKS, 1,
+ vmstate_scc2698_block, SCC2698Block),
+ VMSTATE_UINT8(irq_vector, IPOctalState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* data[10] is 0x0C, not 0x0B as the doc says */
+static const uint8_t id_prom_data[] = {
+ 0x49, 0x50, 0x41, 0x43, 0xF0, 0x22,
+ 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xCC
+};
+
+static void update_irq(IPOctalState *dev, unsigned block)
+{
+ /* Blocks A and B interrupt on INT0#, C and D on INT1#.
+ Thus, to get the status we have to check two blocks. */
+ SCC2698Block *blk0 = &dev->blk[block];
+ SCC2698Block *blk1 = &dev->blk[block^1];
+ unsigned intno = block / 2;
+
+ if ((blk0->isr & blk0->imr) || (blk1->isr & blk1->imr)) {
+ qemu_irq_raise(dev->dev.irq[intno]);
+ } else {
+ qemu_irq_lower(dev->dev.irq[intno]);
+ }
+}
+
+static void write_cr(IPOctalState *dev, unsigned channel, uint8_t val)
+{
+ SCC2698Channel *ch = &dev->ch[channel];
+ SCC2698Block *blk = &dev->blk[channel / 2];
+
+ DPRINTF("Write CR%c %u: ", channel + 'a', val);
+
+ /* The lower 4 bits are used to enable and disable Tx and Rx */
+ if (val & CR_ENABLE_RX) {
+ DPRINTF2("Rx on, ");
+ ch->rx_enabled = true;
+ }
+ if (val & CR_DISABLE_RX) {
+ DPRINTF2("Rx off, ");
+ ch->rx_enabled = false;
+ }
+ if (val & CR_ENABLE_TX) {
+ DPRINTF2("Tx on, ");
+ ch->sr |= SR_TXRDY | SR_TXEMT;
+ blk->isr |= ISR_TXRDY(channel);
+ }
+ if (val & CR_DISABLE_TX) {
+ DPRINTF2("Tx off, ");
+ ch->sr &= ~(SR_TXRDY | SR_TXEMT);
+ blk->isr &= ~ISR_TXRDY(channel);
+ }
+
+ DPRINTF2("cmd: ");
+
+ /* The rest of the bits implement different commands */
+ switch (CR_CMD(val)) {
+ case CR_NO_OP:
+ DPRINTF2("none");
+ break;
+ case CR_RESET_MR:
+ DPRINTF2("reset MR");
+ ch->mr_idx = 0;
+ break;
+ case CR_RESET_RX:
+ DPRINTF2("reset Rx");
+ ch->rx_enabled = false;
+ ch->rx_pending = 0;
+ ch->sr &= ~SR_RXRDY;
+ blk->isr &= ~ISR_RXRDY(channel);
+ break;
+ case CR_RESET_TX:
+ DPRINTF2("reset Tx");
+ ch->sr &= ~(SR_TXRDY | SR_TXEMT);
+ blk->isr &= ~ISR_TXRDY(channel);
+ break;
+ case CR_RESET_ERR:
+ DPRINTF2("reset err");
+ ch->sr &= ~(SR_OVERRUN | SR_PARITY | SR_FRAMING | SR_BREAK);
+ break;
+ case CR_RESET_BRKINT:
+ DPRINTF2("reset brk ch int");
+ blk->isr &= ~(ISR_BREAKA | ISR_BREAKB);
+ break;
+ default:
+ DPRINTF2("unsupported 0x%x", CR_CMD(val));
+ }
+
+ DPRINTF2("\n");
+}
+
+static uint16_t io_read(IPackDevice *ip, uint8_t addr)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ uint16_t ret = 0;
+ /* addr[7:6]: block (A-D)
+ addr[7:5]: channel (a-h)
+ addr[5:0]: register */
+ unsigned block = addr >> 5;
+ unsigned channel = addr >> 4;
+ /* Big endian, accessed using 8-bit bytes at odd locations */
+ unsigned offset = (addr & 0x1F) ^ 1;
+ SCC2698Channel *ch = &dev->ch[channel];
+ SCC2698Block *blk = &dev->blk[block];
+ uint8_t old_isr = blk->isr;
+
+ switch (offset) {
+
+ case REG_MRa:
+ case REG_MRb:
+ ret = ch->mr[ch->mr_idx];
+ DPRINTF("Read MR%u%c: 0x%x\n", ch->mr_idx + 1, channel + 'a', ret);
+ ch->mr_idx = 1;
+ break;
+
+ case REG_SRa:
+ case REG_SRb:
+ ret = ch->sr;
+ DPRINTF("Read SR%c: 0x%x\n", channel + 'a', ret);
+ break;
+
+ case REG_RHRa:
+ case REG_RHRb:
+ ret = ch->rhr[ch->rhr_idx];
+ if (ch->rx_pending > 0) {
+ ch->rx_pending--;
+ if (ch->rx_pending == 0) {
+ ch->sr &= ~SR_RXRDY;
+ blk->isr &= ~ISR_RXRDY(channel);
+ if (ch->dev) {
+ qemu_chr_accept_input(ch->dev);
+ }
+ } else {
+ ch->rhr_idx = (ch->rhr_idx + 1) % RX_FIFO_SIZE;
+ }
+ if (ch->sr & SR_BREAK) {
+ ch->sr &= ~SR_BREAK;
+ blk->isr |= ISR_BREAK(channel);
+ }
+ }
+ DPRINTF("Read RHR%c (0x%x)\n", channel + 'a', ret);
+ break;
+
+ case REG_ISR:
+ ret = blk->isr;
+ DPRINTF("Read ISR%c: 0x%x\n", block + 'A', ret);
+ break;
+
+ default:
+ DPRINTF("Read unknown/unsupported register 0x%02x\n", offset);
+ }
+
+ if (old_isr != blk->isr) {
+ update_irq(dev, block);
+ }
+
+ return ret;
+}
+
+static void io_write(IPackDevice *ip, uint8_t addr, uint16_t val)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ unsigned reg = val & 0xFF;
+ /* addr[7:6]: block (A-D)
+ addr[7:5]: channel (a-h)
+ addr[5:0]: register */
+ unsigned block = addr >> 5;
+ unsigned channel = addr >> 4;
+ /* Big endian, accessed using 8-bit bytes at odd locations */
+ unsigned offset = (addr & 0x1F) ^ 1;
+ SCC2698Channel *ch = &dev->ch[channel];
+ SCC2698Block *blk = &dev->blk[block];
+ uint8_t old_isr = blk->isr;
+ uint8_t old_imr = blk->imr;
+
+ switch (offset) {
+
+ case REG_MRa:
+ case REG_MRb:
+ ch->mr[ch->mr_idx] = reg;
+ DPRINTF("Write MR%u%c 0x%x\n", ch->mr_idx + 1, channel + 'a', reg);
+ ch->mr_idx = 1;
+ break;
+
+ /* Not implemented */
+ case REG_CSRa:
+ case REG_CSRb:
+ DPRINTF("Write CSR%c: 0x%x\n", channel + 'a', reg);
+ break;
+
+ case REG_CRa:
+ case REG_CRb:
+ write_cr(dev, channel, reg);
+ break;
+
+ case REG_THRa:
+ case REG_THRb:
+ if (ch->sr & SR_TXRDY) {
+ DPRINTF("Write THR%c (0x%x)\n", channel + 'a', reg);
+ if (ch->dev) {
+ uint8_t thr = reg;
+ qemu_chr_fe_write(ch->dev, &thr, 1);
+ }
+ } else {
+ DPRINTF("Write THR%c (0x%x), Tx disabled\n", channel + 'a', reg);
+ }
+ break;
+
+ /* Not implemented */
+ case REG_ACR:
+ DPRINTF("Write ACR%c 0x%x\n", block + 'A', val);
+ break;
+
+ case REG_IMR:
+ DPRINTF("Write IMR%c 0x%x\n", block + 'A', val);
+ blk->imr = reg;
+ break;
+
+ /* Not implemented */
+ case REG_OPCR:
+ DPRINTF("Write OPCR%c 0x%x\n", block + 'A', val);
+ break;
+
+ default:
+ DPRINTF("Write unknown/unsupported register 0x%02x %u\n", offset, val);
+ }
+
+ if (old_isr != blk->isr || old_imr != blk->imr) {
+ update_irq(dev, block);
+ }
+}
+
+static uint16_t id_read(IPackDevice *ip, uint8_t addr)
+{
+ uint16_t ret = 0;
+ unsigned pos = addr / 2; /* The ID PROM data is stored every other byte */
+
+ if (pos < ARRAY_SIZE(id_prom_data)) {
+ ret = id_prom_data[pos];
+ } else {
+ DPRINTF("Attempt to read unavailable PROM data at 0x%x\n", addr);
+ }
+
+ return ret;
+}
+
+static void id_write(IPackDevice *ip, uint8_t addr, uint16_t val)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ if (addr == 1) {
+ DPRINTF("Write IRQ vector: %u\n", (unsigned) val);
+ dev->irq_vector = val; /* Undocumented, but the hw works like that */
+ } else {
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+ }
+}
+
+static uint16_t int_read(IPackDevice *ip, uint8_t addr)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ /* Read address 0 to ACK INT0# and address 2 to ACK INT1# */
+ if (addr != 0 && addr != 2) {
+ DPRINTF("Attempt to read from 0x%x\n", addr);
+ return 0;
+ } else {
+ /* Update interrupts if necessary */
+ update_irq(dev, addr);
+ return dev->irq_vector;
+ }
+}
+
+static void int_write(IPackDevice *ip, uint8_t addr, uint16_t val)
+{
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+}
+
+static uint16_t mem_read16(IPackDevice *ip, uint32_t addr)
+{
+ DPRINTF("Attempt to read from 0x%x\n", addr);
+ return 0;
+}
+
+static void mem_write16(IPackDevice *ip, uint32_t addr, uint16_t val)
+{
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+}
+
+static uint8_t mem_read8(IPackDevice *ip, uint32_t addr)
+{
+ DPRINTF("Attempt to read from 0x%x\n", addr);
+ return 0;
+}
+
+static void mem_write8(IPackDevice *ip, uint32_t addr, uint8_t val)
+{
+ IPOctalState *dev = IPOCTAL(ip);
+ if (addr == 1) {
+ DPRINTF("Write IRQ vector: %u\n", (unsigned) val);
+ dev->irq_vector = val;
+ } else {
+ DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr);
+ }
+}
+
+static int hostdev_can_receive(void *opaque)
+{
+ SCC2698Channel *ch = opaque;
+ int available_bytes = RX_FIFO_SIZE - ch->rx_pending;
+ return ch->rx_enabled ? available_bytes : 0;
+}
+
+static void hostdev_receive(void *opaque, const uint8_t *buf, int size)
+{
+ SCC2698Channel *ch = opaque;
+ IPOctalState *dev = ch->ipoctal;
+ unsigned pos = ch->rhr_idx + ch->rx_pending;
+ int i;
+
+ assert(size + ch->rx_pending <= RX_FIFO_SIZE);
+
+ /* Copy data to the RxFIFO */
+ for (i = 0; i < size; i++) {
+ pos %= RX_FIFO_SIZE;
+ ch->rhr[pos++] = buf[i];
+ }
+
+ ch->rx_pending += size;
+
+ /* If the RxFIFO was empty raise an interrupt */
+ if (!(ch->sr & SR_RXRDY)) {
+ unsigned block, channel = 0;
+ /* Find channel number to update the ISR register */
+ while (&dev->ch[channel] != ch) {
+ channel++;
+ }
+ block = channel / 2;
+ dev->blk[block].isr |= ISR_RXRDY(channel);
+ ch->sr |= SR_RXRDY;
+ update_irq(dev, block);
+ }
+}
+
+static void hostdev_event(void *opaque, int event)
+{
+ SCC2698Channel *ch = opaque;
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ DPRINTF("Device %s opened\n", ch->dev->label);
+ break;
+ case CHR_EVENT_BREAK: {
+ uint8_t zero = 0;
+ DPRINTF("Device %s received break\n", ch->dev->label);
+
+ if (!(ch->sr & SR_BREAK)) {
+ IPOctalState *dev = ch->ipoctal;
+ unsigned block, channel = 0;
+
+ while (&dev->ch[channel] != ch) {
+ channel++;
+ }
+ block = channel / 2;
+
+ ch->sr |= SR_BREAK;
+ dev->blk[block].isr |= ISR_BREAK(channel);
+ }
+
+ /* Put a zero character in the buffer */
+ hostdev_receive(ch, &zero, 1);
+ }
+ break;
+ default:
+ DPRINTF("Device %s received event %d\n", ch->dev->label, event);
+ }
+}
+
+static int ipoctal_init(IPackDevice *ip)
+{
+ IPOctalState *s = IPOCTAL(ip);
+ unsigned i;
+
+ for (i = 0; i < N_CHANNELS; i++) {
+ SCC2698Channel *ch = &s->ch[i];
+ ch->ipoctal = s;
+
+ /* Redirect IP-Octal channels to host character devices */
+ if (ch->dev) {
+ qemu_chr_add_handlers(ch->dev, hostdev_can_receive,
+ hostdev_receive, hostdev_event, ch);
+ DPRINTF("Redirecting channel %u to %s\n", i, ch->dev->label);
+ } else {
+ DPRINTF("Could not redirect channel %u, no chardev set\n", i);
+ }
+ }
+
+ return 0;
+}
+
+static Property ipoctal_properties[] = {
+ DEFINE_PROP_CHR("chardev0", IPOctalState, ch[0].dev),
+ DEFINE_PROP_CHR("chardev1", IPOctalState, ch[1].dev),
+ DEFINE_PROP_CHR("chardev2", IPOctalState, ch[2].dev),
+ DEFINE_PROP_CHR("chardev3", IPOctalState, ch[3].dev),
+ DEFINE_PROP_CHR("chardev4", IPOctalState, ch[4].dev),
+ DEFINE_PROP_CHR("chardev5", IPOctalState, ch[5].dev),
+ DEFINE_PROP_CHR("chardev6", IPOctalState, ch[6].dev),
+ DEFINE_PROP_CHR("chardev7", IPOctalState, ch[7].dev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ipoctal_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ IPackDeviceClass *ic = IPACK_DEVICE_CLASS(klass);
+
+ ic->init = ipoctal_init;
+ ic->io_read = io_read;
+ ic->io_write = io_write;
+ ic->id_read = id_read;
+ ic->id_write = id_write;
+ ic->int_read = int_read;
+ ic->int_write = int_write;
+ ic->mem_read16 = mem_read16;
+ ic->mem_write16 = mem_write16;
+ ic->mem_read8 = mem_read8;
+ ic->mem_write8 = mem_write8;
+
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "GE IP-Octal 232 8-channel RS-232 IndustryPack";
+ dc->props = ipoctal_properties;
+ dc->vmsd = &vmstate_ipoctal;
+}
+
+static const TypeInfo ipoctal_info = {
+ .name = TYPE_IPOCTAL,
+ .parent = TYPE_IPACK_DEVICE,
+ .instance_size = sizeof(IPOctalState),
+ .class_init = ipoctal_class_init,
+};
+
+static void ipoctal_register_types(void)
+{
+ type_register_static(&ipoctal_info);
+}
+
+type_init(ipoctal_register_types)
diff --git a/hw/char/lm32_juart.c b/hw/char/lm32_juart.c
new file mode 100644
index 000000000..252fe46da
--- /dev/null
+++ b/hw/char/lm32_juart.c
@@ -0,0 +1,162 @@
+/*
+ * LatticeMico32 JTAG UART model.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "sysemu/char.h"
+
+#include "hw/char/lm32_juart.h"
+
+enum {
+ LM32_JUART_MIN_SAVE_VERSION = 0,
+ LM32_JUART_CURRENT_SAVE_VERSION = 0,
+ LM32_JUART_MAX_SAVE_VERSION = 0,
+};
+
+enum {
+ JTX_FULL = (1<<8),
+};
+
+enum {
+ JRX_FULL = (1<<8),
+};
+
+#define LM32_JUART(obj) OBJECT_CHECK(LM32JuartState, (obj), TYPE_LM32_JUART)
+
+struct LM32JuartState {
+ SysBusDevice parent_obj;
+
+ CharDriverState *chr;
+
+ uint32_t jtx;
+ uint32_t jrx;
+};
+typedef struct LM32JuartState LM32JuartState;
+
+uint32_t lm32_juart_get_jtx(DeviceState *d)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ trace_lm32_juart_get_jtx(s->jtx);
+ return s->jtx;
+}
+
+uint32_t lm32_juart_get_jrx(DeviceState *d)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ trace_lm32_juart_get_jrx(s->jrx);
+ return s->jrx;
+}
+
+void lm32_juart_set_jtx(DeviceState *d, uint32_t jtx)
+{
+ LM32JuartState *s = LM32_JUART(d);
+ unsigned char ch = jtx & 0xff;
+
+ trace_lm32_juart_set_jtx(s->jtx);
+
+ s->jtx = jtx;
+ if (s->chr) {
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+}
+
+void lm32_juart_set_jrx(DeviceState *d, uint32_t jtx)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ trace_lm32_juart_set_jrx(s->jrx);
+ s->jrx &= ~JRX_FULL;
+}
+
+static void juart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ LM32JuartState *s = opaque;
+
+ s->jrx = *buf | JRX_FULL;
+}
+
+static int juart_can_rx(void *opaque)
+{
+ LM32JuartState *s = opaque;
+
+ return !(s->jrx & JRX_FULL);
+}
+
+static void juart_event(void *opaque, int event)
+{
+}
+
+static void juart_reset(DeviceState *d)
+{
+ LM32JuartState *s = LM32_JUART(d);
+
+ s->jtx = 0;
+ s->jrx = 0;
+}
+
+static int lm32_juart_init(SysBusDevice *dev)
+{
+ LM32JuartState *s = LM32_JUART(dev);
+
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, juart_can_rx, juart_rx, juart_event, s);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm32_juart = {
+ .name = "lm32-juart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(jtx, LM32JuartState),
+ VMSTATE_UINT32(jrx, LM32JuartState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void lm32_juart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lm32_juart_init;
+ dc->reset = juart_reset;
+ dc->vmsd = &vmstate_lm32_juart;
+}
+
+static const TypeInfo lm32_juart_info = {
+ .name = TYPE_LM32_JUART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LM32JuartState),
+ .class_init = lm32_juart_class_init,
+};
+
+static void lm32_juart_register_types(void)
+{
+ type_register_static(&lm32_juart_info);
+}
+
+type_init(lm32_juart_register_types)
diff --git a/hw/char/lm32_uart.c b/hw/char/lm32_uart.c
new file mode 100644
index 000000000..85d726508
--- /dev/null
+++ b/hw/char/lm32_uart.c
@@ -0,0 +1,302 @@
+/*
+ * QEMU model of the LatticeMico32 UART block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.latticesemi.com/documents/mico32uart.pdf
+ */
+
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "sysemu/char.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_RXTX = 0,
+ R_IER,
+ R_IIR,
+ R_LCR,
+ R_MCR,
+ R_LSR,
+ R_MSR,
+ R_DIV,
+ R_MAX
+};
+
+enum {
+ IER_RBRI = (1<<0),
+ IER_THRI = (1<<1),
+ IER_RLSI = (1<<2),
+ IER_MSI = (1<<3),
+};
+
+enum {
+ IIR_STAT = (1<<0),
+ IIR_ID0 = (1<<1),
+ IIR_ID1 = (1<<2),
+};
+
+enum {
+ LCR_WLS0 = (1<<0),
+ LCR_WLS1 = (1<<1),
+ LCR_STB = (1<<2),
+ LCR_PEN = (1<<3),
+ LCR_EPS = (1<<4),
+ LCR_SP = (1<<5),
+ LCR_SB = (1<<6),
+};
+
+enum {
+ MCR_DTR = (1<<0),
+ MCR_RTS = (1<<1),
+};
+
+enum {
+ LSR_DR = (1<<0),
+ LSR_OE = (1<<1),
+ LSR_PE = (1<<2),
+ LSR_FE = (1<<3),
+ LSR_BI = (1<<4),
+ LSR_THRE = (1<<5),
+ LSR_TEMT = (1<<6),
+};
+
+enum {
+ MSR_DCTS = (1<<0),
+ MSR_DDSR = (1<<1),
+ MSR_TERI = (1<<2),
+ MSR_DDCD = (1<<3),
+ MSR_CTS = (1<<4),
+ MSR_DSR = (1<<5),
+ MSR_RI = (1<<6),
+ MSR_DCD = (1<<7),
+};
+
+#define TYPE_LM32_UART "lm32-uart"
+#define LM32_UART(obj) OBJECT_CHECK(LM32UartState, (obj), TYPE_LM32_UART)
+
+struct LM32UartState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct LM32UartState LM32UartState;
+
+static void uart_update_irq(LM32UartState *s)
+{
+ unsigned int irq;
+
+ if ((s->regs[R_LSR] & (LSR_OE | LSR_PE | LSR_FE | LSR_BI))
+ && (s->regs[R_IER] & IER_RLSI)) {
+ irq = 1;
+ s->regs[R_IIR] = IIR_ID1 | IIR_ID0;
+ } else if ((s->regs[R_LSR] & LSR_DR) && (s->regs[R_IER] & IER_RBRI)) {
+ irq = 1;
+ s->regs[R_IIR] = IIR_ID1;
+ } else if ((s->regs[R_LSR] & LSR_THRE) && (s->regs[R_IER] & IER_THRI)) {
+ irq = 1;
+ s->regs[R_IIR] = IIR_ID0;
+ } else if ((s->regs[R_MSR] & 0x0f) && (s->regs[R_IER] & IER_MSI)) {
+ irq = 1;
+ s->regs[R_IIR] = 0;
+ } else {
+ irq = 0;
+ s->regs[R_IIR] = IIR_STAT;
+ }
+
+ trace_lm32_uart_irq_state(irq);
+ qemu_set_irq(s->irq, irq);
+}
+
+static uint64_t uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ LM32UartState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ r = s->regs[R_RXTX];
+ s->regs[R_LSR] &= ~LSR_DR;
+ uart_update_irq(s);
+ qemu_chr_accept_input(s->chr);
+ break;
+ case R_IIR:
+ case R_LSR:
+ case R_MSR:
+ r = s->regs[addr];
+ break;
+ case R_IER:
+ case R_LCR:
+ case R_MCR:
+ case R_DIV:
+ error_report("lm32_uart: read access to write only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ default:
+ error_report("lm32_uart: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_lm32_uart_memory_read(addr << 2, r);
+ return r;
+}
+
+static void uart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ LM32UartState *s = opaque;
+ unsigned char ch = value;
+
+ trace_lm32_uart_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ if (s->chr) {
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+ break;
+ case R_IER:
+ case R_LCR:
+ case R_MCR:
+ case R_DIV:
+ s->regs[addr] = value;
+ break;
+ case R_IIR:
+ case R_LSR:
+ case R_MSR:
+ error_report("lm32_uart: write access to read only register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ default:
+ error_report("lm32_uart: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+ uart_update_irq(s);
+}
+
+static const MemoryRegionOps uart_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ LM32UartState *s = opaque;
+
+ if (s->regs[R_LSR] & LSR_DR) {
+ s->regs[R_LSR] |= LSR_OE;
+ }
+
+ s->regs[R_LSR] |= LSR_DR;
+ s->regs[R_RXTX] = *buf;
+
+ uart_update_irq(s);
+}
+
+static int uart_can_rx(void *opaque)
+{
+ LM32UartState *s = opaque;
+
+ return !(s->regs[R_LSR] & LSR_DR);
+}
+
+static void uart_event(void *opaque, int event)
+{
+}
+
+static void uart_reset(DeviceState *d)
+{
+ LM32UartState *s = LM32_UART(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ /* defaults */
+ s->regs[R_LSR] = LSR_THRE | LSR_TEMT;
+}
+
+static int lm32_uart_init(SysBusDevice *dev)
+{
+ LM32UartState *s = LM32_UART(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &uart_ops, s,
+ "uart", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_lm32_uart = {
+ .name = "lm32-uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, LM32UartState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void lm32_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = lm32_uart_init;
+ dc->reset = uart_reset;
+ dc->vmsd = &vmstate_lm32_uart;
+}
+
+static const TypeInfo lm32_uart_info = {
+ .name = TYPE_LM32_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(LM32UartState),
+ .class_init = lm32_uart_class_init,
+};
+
+static void lm32_uart_register_types(void)
+{
+ type_register_static(&lm32_uart_info);
+}
+
+type_init(lm32_uart_register_types)
diff --git a/hw/char/mcf_uart.c b/hw/char/mcf_uart.c
new file mode 100644
index 000000000..98fd44e66
--- /dev/null
+++ b/hw/char/mcf_uart.c
@@ -0,0 +1,307 @@
+/*
+ * ColdFire UART emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ *
+ * This code is licensed under the GPL
+ */
+#include "hw/hw.h"
+#include "hw/m68k/mcf.h"
+#include "sysemu/char.h"
+#include "exec/address-spaces.h"
+
+typedef struct {
+ MemoryRegion iomem;
+ uint8_t mr[2];
+ uint8_t sr;
+ uint8_t isr;
+ uint8_t imr;
+ uint8_t bg1;
+ uint8_t bg2;
+ uint8_t fifo[4];
+ uint8_t tb;
+ int current_mr;
+ int fifo_len;
+ int tx_enabled;
+ int rx_enabled;
+ qemu_irq irq;
+ CharDriverState *chr;
+} mcf_uart_state;
+
+/* UART Status Register bits. */
+#define MCF_UART_RxRDY 0x01
+#define MCF_UART_FFULL 0x02
+#define MCF_UART_TxRDY 0x04
+#define MCF_UART_TxEMP 0x08
+#define MCF_UART_OE 0x10
+#define MCF_UART_PE 0x20
+#define MCF_UART_FE 0x40
+#define MCF_UART_RB 0x80
+
+/* Interrupt flags. */
+#define MCF_UART_TxINT 0x01
+#define MCF_UART_RxINT 0x02
+#define MCF_UART_DBINT 0x04
+#define MCF_UART_COSINT 0x80
+
+/* UMR1 flags. */
+#define MCF_UART_BC0 0x01
+#define MCF_UART_BC1 0x02
+#define MCF_UART_PT 0x04
+#define MCF_UART_PM0 0x08
+#define MCF_UART_PM1 0x10
+#define MCF_UART_ERR 0x20
+#define MCF_UART_RxIRQ 0x40
+#define MCF_UART_RxRTS 0x80
+
+static void mcf_uart_update(mcf_uart_state *s)
+{
+ s->isr &= ~(MCF_UART_TxINT | MCF_UART_RxINT);
+ if (s->sr & MCF_UART_TxRDY)
+ s->isr |= MCF_UART_TxINT;
+ if ((s->sr & ((s->mr[0] & MCF_UART_RxIRQ)
+ ? MCF_UART_FFULL : MCF_UART_RxRDY)) != 0)
+ s->isr |= MCF_UART_RxINT;
+
+ qemu_set_irq(s->irq, (s->isr & s->imr) != 0);
+}
+
+uint64_t mcf_uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+ switch (addr & 0x3f) {
+ case 0x00:
+ return s->mr[s->current_mr];
+ case 0x04:
+ return s->sr;
+ case 0x0c:
+ {
+ uint8_t val;
+ int i;
+
+ if (s->fifo_len == 0)
+ return 0;
+
+ val = s->fifo[0];
+ s->fifo_len--;
+ for (i = 0; i < s->fifo_len; i++)
+ s->fifo[i] = s->fifo[i + 1];
+ s->sr &= ~MCF_UART_FFULL;
+ if (s->fifo_len == 0)
+ s->sr &= ~MCF_UART_RxRDY;
+ mcf_uart_update(s);
+ qemu_chr_accept_input(s->chr);
+ return val;
+ }
+ case 0x10:
+ /* TODO: Implement IPCR. */
+ return 0;
+ case 0x14:
+ return s->isr;
+ case 0x18:
+ return s->bg1;
+ case 0x1c:
+ return s->bg2;
+ default:
+ return 0;
+ }
+}
+
+/* Update TxRDY flag and set data if present and enabled. */
+static void mcf_uart_do_tx(mcf_uart_state *s)
+{
+ if (s->tx_enabled && (s->sr & MCF_UART_TxEMP) == 0) {
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, (unsigned char *)&s->tb, 1);
+ s->sr |= MCF_UART_TxEMP;
+ }
+ if (s->tx_enabled) {
+ s->sr |= MCF_UART_TxRDY;
+ } else {
+ s->sr &= ~MCF_UART_TxRDY;
+ }
+}
+
+static void mcf_do_command(mcf_uart_state *s, uint8_t cmd)
+{
+ /* Misc command. */
+ switch ((cmd >> 4) & 3) {
+ case 0: /* No-op. */
+ break;
+ case 1: /* Reset mode register pointer. */
+ s->current_mr = 0;
+ break;
+ case 2: /* Reset receiver. */
+ s->rx_enabled = 0;
+ s->fifo_len = 0;
+ s->sr &= ~(MCF_UART_RxRDY | MCF_UART_FFULL);
+ break;
+ case 3: /* Reset transmitter. */
+ s->tx_enabled = 0;
+ s->sr |= MCF_UART_TxEMP;
+ s->sr &= ~MCF_UART_TxRDY;
+ break;
+ case 4: /* Reset error status. */
+ break;
+ case 5: /* Reset break-change interrupt. */
+ s->isr &= ~MCF_UART_DBINT;
+ break;
+ case 6: /* Start break. */
+ case 7: /* Stop break. */
+ break;
+ }
+
+ /* Transmitter command. */
+ switch ((cmd >> 2) & 3) {
+ case 0: /* No-op. */
+ break;
+ case 1: /* Enable. */
+ s->tx_enabled = 1;
+ mcf_uart_do_tx(s);
+ break;
+ case 2: /* Disable. */
+ s->tx_enabled = 0;
+ mcf_uart_do_tx(s);
+ break;
+ case 3: /* Reserved. */
+ fprintf(stderr, "mcf_uart: Bad TX command\n");
+ break;
+ }
+
+ /* Receiver command. */
+ switch (cmd & 3) {
+ case 0: /* No-op. */
+ break;
+ case 1: /* Enable. */
+ s->rx_enabled = 1;
+ break;
+ case 2:
+ s->rx_enabled = 0;
+ break;
+ case 3: /* Reserved. */
+ fprintf(stderr, "mcf_uart: Bad RX command\n");
+ break;
+ }
+}
+
+void mcf_uart_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+ switch (addr & 0x3f) {
+ case 0x00:
+ s->mr[s->current_mr] = val;
+ s->current_mr = 1;
+ break;
+ case 0x04:
+ /* CSR is ignored. */
+ break;
+ case 0x08: /* Command Register. */
+ mcf_do_command(s, val);
+ break;
+ case 0x0c: /* Transmit Buffer. */
+ s->sr &= ~MCF_UART_TxEMP;
+ s->tb = val;
+ mcf_uart_do_tx(s);
+ break;
+ case 0x10:
+ /* ACR is ignored. */
+ break;
+ case 0x14:
+ s->imr = val;
+ break;
+ default:
+ break;
+ }
+ mcf_uart_update(s);
+}
+
+static void mcf_uart_reset(mcf_uart_state *s)
+{
+ s->fifo_len = 0;
+ s->mr[0] = 0;
+ s->mr[1] = 0;
+ s->sr = MCF_UART_TxEMP;
+ s->tx_enabled = 0;
+ s->rx_enabled = 0;
+ s->isr = 0;
+ s->imr = 0;
+}
+
+static void mcf_uart_push_byte(mcf_uart_state *s, uint8_t data)
+{
+ /* Break events overwrite the last byte if the fifo is full. */
+ if (s->fifo_len == 4)
+ s->fifo_len--;
+
+ s->fifo[s->fifo_len] = data;
+ s->fifo_len++;
+ s->sr |= MCF_UART_RxRDY;
+ if (s->fifo_len == 4)
+ s->sr |= MCF_UART_FFULL;
+
+ mcf_uart_update(s);
+}
+
+static void mcf_uart_event(void *opaque, int event)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+
+ switch (event) {
+ case CHR_EVENT_BREAK:
+ s->isr |= MCF_UART_DBINT;
+ mcf_uart_push_byte(s, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+static int mcf_uart_can_receive(void *opaque)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+
+ return s->rx_enabled && (s->sr & MCF_UART_FFULL) == 0;
+}
+
+static void mcf_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ mcf_uart_state *s = (mcf_uart_state *)opaque;
+
+ mcf_uart_push_byte(s, buf[0]);
+}
+
+void *mcf_uart_init(qemu_irq irq, CharDriverState *chr)
+{
+ mcf_uart_state *s;
+
+ s = g_malloc0(sizeof(mcf_uart_state));
+ s->chr = chr;
+ s->irq = irq;
+ if (chr) {
+ qemu_chr_fe_claim_no_fail(chr);
+ qemu_chr_add_handlers(chr, mcf_uart_can_receive, mcf_uart_receive,
+ mcf_uart_event, s);
+ }
+ mcf_uart_reset(s);
+ return s;
+}
+
+static const MemoryRegionOps mcf_uart_ops = {
+ .read = mcf_uart_read,
+ .write = mcf_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void mcf_uart_mm_init(MemoryRegion *sysmem,
+ hwaddr base,
+ qemu_irq irq,
+ CharDriverState *chr)
+{
+ mcf_uart_state *s;
+
+ s = mcf_uart_init(irq, chr);
+ memory_region_init_io(&s->iomem, NULL, &mcf_uart_ops, s, "uart", 0x40);
+ memory_region_add_subregion(sysmem, base, &s->iomem);
+}
diff --git a/hw/char/milkymist-uart.c b/hw/char/milkymist-uart.c
new file mode 100644
index 000000000..2e4b5c58b
--- /dev/null
+++ b/hw/char/milkymist-uart.c
@@ -0,0 +1,249 @@
+/*
+ * QEMU model of the Milkymist UART block.
+ *
+ * Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ * http://www.milkymist.org/socdoc/uart.pdf
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "sysemu/char.h"
+#include "qemu/error-report.h"
+
+enum {
+ R_RXTX = 0,
+ R_DIV,
+ R_STAT,
+ R_CTRL,
+ R_DBG,
+ R_MAX
+};
+
+enum {
+ STAT_THRE = (1<<0),
+ STAT_RX_EVT = (1<<1),
+ STAT_TX_EVT = (1<<2),
+};
+
+enum {
+ CTRL_RX_IRQ_EN = (1<<0),
+ CTRL_TX_IRQ_EN = (1<<1),
+ CTRL_THRU_EN = (1<<2),
+};
+
+enum {
+ DBG_BREAK_EN = (1<<0),
+};
+
+#define TYPE_MILKYMIST_UART "milkymist-uart"
+#define MILKYMIST_UART(obj) \
+ OBJECT_CHECK(MilkymistUartState, (obj), TYPE_MILKYMIST_UART)
+
+struct MilkymistUartState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion regs_region;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t regs[R_MAX];
+};
+typedef struct MilkymistUartState MilkymistUartState;
+
+static void uart_update_irq(MilkymistUartState *s)
+{
+ int rx_event = s->regs[R_STAT] & STAT_RX_EVT;
+ int tx_event = s->regs[R_STAT] & STAT_TX_EVT;
+ int rx_irq_en = s->regs[R_CTRL] & CTRL_RX_IRQ_EN;
+ int tx_irq_en = s->regs[R_CTRL] & CTRL_TX_IRQ_EN;
+
+ if ((rx_irq_en && rx_event) || (tx_irq_en && tx_event)) {
+ trace_milkymist_uart_raise_irq();
+ qemu_irq_raise(s->irq);
+ } else {
+ trace_milkymist_uart_lower_irq();
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static uint64_t uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ MilkymistUartState *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ r = s->regs[addr];
+ break;
+ case R_DIV:
+ case R_STAT:
+ case R_CTRL:
+ case R_DBG:
+ r = s->regs[addr];
+ break;
+
+ default:
+ error_report("milkymist_uart: read access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ trace_milkymist_uart_memory_read(addr << 2, r);
+
+ return r;
+}
+
+static void uart_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MilkymistUartState *s = opaque;
+ unsigned char ch = value;
+
+ trace_milkymist_uart_memory_write(addr, value);
+
+ addr >>= 2;
+ switch (addr) {
+ case R_RXTX:
+ if (s->chr) {
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+ s->regs[R_STAT] |= STAT_TX_EVT;
+ break;
+ case R_DIV:
+ case R_CTRL:
+ case R_DBG:
+ s->regs[addr] = value;
+ break;
+
+ case R_STAT:
+ /* write one to clear bits */
+ s->regs[addr] &= ~(value & (STAT_RX_EVT | STAT_TX_EVT));
+ qemu_chr_accept_input(s->chr);
+ break;
+
+ default:
+ error_report("milkymist_uart: write access to unknown register 0x"
+ TARGET_FMT_plx, addr << 2);
+ break;
+ }
+
+ uart_update_irq(s);
+}
+
+static const MemoryRegionOps uart_mmio_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ MilkymistUartState *s = opaque;
+
+ assert(!(s->regs[R_STAT] & STAT_RX_EVT));
+
+ s->regs[R_STAT] |= STAT_RX_EVT;
+ s->regs[R_RXTX] = *buf;
+
+ uart_update_irq(s);
+}
+
+static int uart_can_rx(void *opaque)
+{
+ MilkymistUartState *s = opaque;
+
+ return !(s->regs[R_STAT] & STAT_RX_EVT);
+}
+
+static void uart_event(void *opaque, int event)
+{
+}
+
+static void milkymist_uart_reset(DeviceState *d)
+{
+ MilkymistUartState *s = MILKYMIST_UART(d);
+ int i;
+
+ for (i = 0; i < R_MAX; i++) {
+ s->regs[i] = 0;
+ }
+
+ /* THRE is always set */
+ s->regs[R_STAT] = STAT_THRE;
+}
+
+static int milkymist_uart_init(SysBusDevice *dev)
+{
+ MilkymistUartState *s = MILKYMIST_UART(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->regs_region, OBJECT(s), &uart_mmio_ops, s,
+ "milkymist-uart", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->regs_region);
+
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
+ }
+
+ return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_uart = {
+ .name = "milkymist-uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MilkymistUartState, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void milkymist_uart_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = milkymist_uart_init;
+ dc->reset = milkymist_uart_reset;
+ dc->vmsd = &vmstate_milkymist_uart;
+}
+
+static const TypeInfo milkymist_uart_info = {
+ .name = TYPE_MILKYMIST_UART,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MilkymistUartState),
+ .class_init = milkymist_uart_class_init,
+};
+
+static void milkymist_uart_register_types(void)
+{
+ type_register_static(&milkymist_uart_info);
+}
+
+type_init(milkymist_uart_register_types)
diff --git a/hw/char/omap_uart.c b/hw/char/omap_uart.c
new file mode 100644
index 000000000..0b9169360
--- /dev/null
+++ b/hw/char/omap_uart.c
@@ -0,0 +1,187 @@
+/*
+ * TI OMAP processors UART emulation.
+ *
+ * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
+ * Copyright (C) 2007-2009 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "sysemu/char.h"
+#include "hw/hw.h"
+#include "hw/arm/omap.h"
+#include "hw/char/serial.h"
+#include "exec/address-spaces.h"
+
+/* UARTs */
+struct omap_uart_s {
+ MemoryRegion iomem;
+ hwaddr base;
+ SerialState *serial; /* TODO */
+ struct omap_target_agent_s *ta;
+ omap_clk fclk;
+ qemu_irq irq;
+
+ uint8_t eblr;
+ uint8_t syscontrol;
+ uint8_t wkup;
+ uint8_t cfps;
+ uint8_t mdr[2];
+ uint8_t scr;
+ uint8_t clksel;
+};
+
+void omap_uart_reset(struct omap_uart_s *s)
+{
+ s->eblr = 0x00;
+ s->syscontrol = 0;
+ s->wkup = 0x3f;
+ s->cfps = 0x69;
+ s->clksel = 0;
+}
+
+struct omap_uart_s *omap_uart_init(hwaddr base,
+ qemu_irq irq, omap_clk fclk, omap_clk iclk,
+ qemu_irq txdma, qemu_irq rxdma,
+ const char *label, CharDriverState *chr)
+{
+ struct omap_uart_s *s = (struct omap_uart_s *)
+ g_malloc0(sizeof(struct omap_uart_s));
+
+ s->base = base;
+ s->fclk = fclk;
+ s->irq = irq;
+ s->serial = serial_mm_init(get_system_memory(), base, 2, irq,
+ omap_clk_getrate(fclk)/16,
+ chr ?: qemu_chr_new(label, "null", NULL),
+ DEVICE_NATIVE_ENDIAN);
+ return s;
+}
+
+static uint64_t omap_uart_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ struct omap_uart_s *s = (struct omap_uart_s *) opaque;
+
+ if (size == 4) {
+ return omap_badwidth_read8(opaque, addr);
+ }
+
+ switch (addr) {
+ case 0x20: /* MDR1 */
+ return s->mdr[0];
+ case 0x24: /* MDR2 */
+ return s->mdr[1];
+ case 0x40: /* SCR */
+ return s->scr;
+ case 0x44: /* SSR */
+ return 0x0;
+ case 0x48: /* EBLR (OMAP2) */
+ return s->eblr;
+ case 0x4C: /* OSC_12M_SEL (OMAP1) */
+ return s->clksel;
+ case 0x50: /* MVR */
+ return 0x30;
+ case 0x54: /* SYSC (OMAP2) */
+ return s->syscontrol;
+ case 0x58: /* SYSS (OMAP2) */
+ return 1;
+ case 0x5c: /* WER (OMAP2) */
+ return s->wkup;
+ case 0x60: /* CFPS (OMAP2) */
+ return s->cfps;
+ }
+
+ OMAP_BAD_REG(addr);
+ return 0;
+}
+
+static void omap_uart_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ struct omap_uart_s *s = (struct omap_uart_s *) opaque;
+
+ if (size == 4) {
+ return omap_badwidth_write8(opaque, addr, value);
+ }
+
+ switch (addr) {
+ case 0x20: /* MDR1 */
+ s->mdr[0] = value & 0x7f;
+ break;
+ case 0x24: /* MDR2 */
+ s->mdr[1] = value & 0xff;
+ break;
+ case 0x40: /* SCR */
+ s->scr = value & 0xff;
+ break;
+ case 0x48: /* EBLR (OMAP2) */
+ s->eblr = value & 0xff;
+ break;
+ case 0x4C: /* OSC_12M_SEL (OMAP1) */
+ s->clksel = value & 1;
+ break;
+ case 0x44: /* SSR */
+ case 0x50: /* MVR */
+ case 0x58: /* SYSS (OMAP2) */
+ OMAP_RO_REG(addr);
+ break;
+ case 0x54: /* SYSC (OMAP2) */
+ s->syscontrol = value & 0x1d;
+ if (value & 2)
+ omap_uart_reset(s);
+ break;
+ case 0x5c: /* WER (OMAP2) */
+ s->wkup = value & 0x7f;
+ break;
+ case 0x60: /* CFPS (OMAP2) */
+ s->cfps = value & 0xff;
+ break;
+ default:
+ OMAP_BAD_REG(addr);
+ }
+}
+
+static const MemoryRegionOps omap_uart_ops = {
+ .read = omap_uart_read,
+ .write = omap_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+struct omap_uart_s *omap2_uart_init(MemoryRegion *sysmem,
+ struct omap_target_agent_s *ta,
+ qemu_irq irq, omap_clk fclk, omap_clk iclk,
+ qemu_irq txdma, qemu_irq rxdma,
+ const char *label, CharDriverState *chr)
+{
+ hwaddr base = omap_l4_attach(ta, 0, NULL);
+ struct omap_uart_s *s = omap_uart_init(base, irq,
+ fclk, iclk, txdma, rxdma, label, chr);
+
+ memory_region_init_io(&s->iomem, NULL, &omap_uart_ops, s, "omap.uart", 0x100);
+
+ s->ta = ta;
+
+ memory_region_add_subregion(sysmem, base + 0x20, &s->iomem);
+
+ return s;
+}
+
+void omap_uart_attach(struct omap_uart_s *s, CharDriverState *chr)
+{
+ /* TODO: Should reuse or destroy current s->serial */
+ s->serial = serial_mm_init(get_system_memory(), s->base, 2, s->irq,
+ omap_clk_getrate(s->fclk) / 16,
+ chr ?: qemu_chr_new("null", "null", NULL),
+ DEVICE_NATIVE_ENDIAN);
+}
diff --git a/hw/char/parallel.c b/hw/char/parallel.c
new file mode 100644
index 000000000..7a3b2647c
--- /dev/null
+++ b/hw/char/parallel.c
@@ -0,0 +1,625 @@
+/*
+ * QEMU Parallel PORT emulation
+ *
+ * Copyright (c) 2003-2005 Fabrice Bellard
+ * Copyright (c) 2007 Marko Kohtala
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "hw/hw.h"
+#include "sysemu/char.h"
+#include "hw/isa/isa.h"
+#include "hw/i386/pc.h"
+#include "sysemu/sysemu.h"
+
+//#define DEBUG_PARALLEL
+
+#ifdef DEBUG_PARALLEL
+#define pdebug(fmt, ...) printf("pp: " fmt, ## __VA_ARGS__)
+#else
+#define pdebug(fmt, ...) ((void)0)
+#endif
+
+#define PARA_REG_DATA 0
+#define PARA_REG_STS 1
+#define PARA_REG_CTR 2
+#define PARA_REG_EPP_ADDR 3
+#define PARA_REG_EPP_DATA 4
+
+/*
+ * These are the definitions for the Printer Status Register
+ */
+#define PARA_STS_BUSY 0x80 /* Busy complement */
+#define PARA_STS_ACK 0x40 /* Acknowledge */
+#define PARA_STS_PAPER 0x20 /* Out of paper */
+#define PARA_STS_ONLINE 0x10 /* Online */
+#define PARA_STS_ERROR 0x08 /* Error complement */
+#define PARA_STS_TMOUT 0x01 /* EPP timeout */
+
+/*
+ * These are the definitions for the Printer Control Register
+ */
+#define PARA_CTR_DIR 0x20 /* Direction (1=read, 0=write) */
+#define PARA_CTR_INTEN 0x10 /* IRQ Enable */
+#define PARA_CTR_SELECT 0x08 /* Select In complement */
+#define PARA_CTR_INIT 0x04 /* Initialize Printer complement */
+#define PARA_CTR_AUTOLF 0x02 /* Auto linefeed complement */
+#define PARA_CTR_STROBE 0x01 /* Strobe complement */
+
+#define PARA_CTR_SIGNAL (PARA_CTR_SELECT|PARA_CTR_INIT|PARA_CTR_AUTOLF|PARA_CTR_STROBE)
+
+typedef struct ParallelState {
+ MemoryRegion iomem;
+ uint8_t dataw;
+ uint8_t datar;
+ uint8_t status;
+ uint8_t control;
+ qemu_irq irq;
+ int irq_pending;
+ CharDriverState *chr;
+ int hw_driver;
+ int epp_timeout;
+ uint32_t last_read_offset; /* For debugging */
+ /* Memory-mapped interface */
+ int it_shift;
+} ParallelState;
+
+#define TYPE_ISA_PARALLEL "isa-parallel"
+#define ISA_PARALLEL(obj) \
+ OBJECT_CHECK(ISAParallelState, (obj), TYPE_ISA_PARALLEL)
+
+typedef struct ISAParallelState {
+ ISADevice parent_obj;
+
+ uint32_t index;
+ uint32_t iobase;
+ uint32_t isairq;
+ ParallelState state;
+} ISAParallelState;
+
+static void parallel_update_irq(ParallelState *s)
+{
+ if (s->irq_pending)
+ qemu_irq_raise(s->irq);
+ else
+ qemu_irq_lower(s->irq);
+}
+
+static void
+parallel_ioport_write_sw(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+
+ pdebug("write addr=0x%02x val=0x%02x\n", addr, val);
+
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ s->dataw = val;
+ parallel_update_irq(s);
+ break;
+ case PARA_REG_CTR:
+ val |= 0xc0;
+ if ((val & PARA_CTR_INIT) == 0 ) {
+ s->status = PARA_STS_BUSY;
+ s->status |= PARA_STS_ACK;
+ s->status |= PARA_STS_ONLINE;
+ s->status |= PARA_STS_ERROR;
+ }
+ else if (val & PARA_CTR_SELECT) {
+ if (val & PARA_CTR_STROBE) {
+ s->status &= ~PARA_STS_BUSY;
+ if ((s->control & PARA_CTR_STROBE) == 0)
+ qemu_chr_fe_write(s->chr, &s->dataw, 1);
+ } else {
+ if (s->control & PARA_CTR_INTEN) {
+ s->irq_pending = 1;
+ }
+ }
+ }
+ parallel_update_irq(s);
+ s->control = val;
+ break;
+ }
+}
+
+static void parallel_ioport_write_hw(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+ uint8_t parm = val;
+ int dir;
+
+ /* Sometimes programs do several writes for timing purposes on old
+ HW. Take care not to waste time on writes that do nothing. */
+
+ s->last_read_offset = ~0U;
+
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ if (s->dataw == val)
+ return;
+ pdebug("wd%02x\n", val);
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_WRITE_DATA, &parm);
+ s->dataw = val;
+ break;
+ case PARA_REG_STS:
+ pdebug("ws%02x\n", val);
+ if (val & PARA_STS_TMOUT)
+ s->epp_timeout = 0;
+ break;
+ case PARA_REG_CTR:
+ val |= 0xc0;
+ if (s->control == val)
+ return;
+ pdebug("wc%02x\n", val);
+
+ if ((val & PARA_CTR_DIR) != (s->control & PARA_CTR_DIR)) {
+ if (val & PARA_CTR_DIR) {
+ dir = 1;
+ } else {
+ dir = 0;
+ }
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_DATA_DIR, &dir);
+ parm &= ~PARA_CTR_DIR;
+ }
+
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_WRITE_CONTROL, &parm);
+ s->control = val;
+ break;
+ case PARA_REG_EPP_ADDR:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
+ /* Controls not correct for EPP address cycle, so do nothing */
+ pdebug("wa%02x s\n", val);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE_ADDR, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("wa%02x t\n", val);
+ }
+ else
+ pdebug("wa%02x\n", val);
+ }
+ break;
+ case PARA_REG_EPP_DATA:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT)
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("we%02x s\n", val);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &parm, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("we%02x t\n", val);
+ }
+ else
+ pdebug("we%02x\n", val);
+ }
+ break;
+ }
+}
+
+static void
+parallel_ioport_eppdata_write_hw2(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+ uint16_t eppdata = cpu_to_le16(val);
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("we%04x s\n", val);
+ return;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("we%04x t\n", val);
+ }
+ else
+ pdebug("we%04x\n", val);
+}
+
+static void
+parallel_ioport_eppdata_write_hw4(void *opaque, uint32_t addr, uint32_t val)
+{
+ ParallelState *s = opaque;
+ uint32_t eppdata = cpu_to_le32(val);
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != PARA_CTR_INIT) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("we%08x s\n", val);
+ return;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_WRITE, &ioarg);
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("we%08x t\n", val);
+ }
+ else
+ pdebug("we%08x\n", val);
+}
+
+static uint32_t parallel_ioport_read_sw(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint32_t ret = 0xff;
+
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ if (s->control & PARA_CTR_DIR)
+ ret = s->datar;
+ else
+ ret = s->dataw;
+ break;
+ case PARA_REG_STS:
+ ret = s->status;
+ s->irq_pending = 0;
+ if ((s->status & PARA_STS_BUSY) == 0 && (s->control & PARA_CTR_STROBE) == 0) {
+ /* XXX Fixme: wait 5 microseconds */
+ if (s->status & PARA_STS_ACK)
+ s->status &= ~PARA_STS_ACK;
+ else {
+ /* XXX Fixme: wait 5 microseconds */
+ s->status |= PARA_STS_ACK;
+ s->status |= PARA_STS_BUSY;
+ }
+ }
+ parallel_update_irq(s);
+ break;
+ case PARA_REG_CTR:
+ ret = s->control;
+ break;
+ }
+ pdebug("read addr=0x%02x val=0x%02x\n", addr, ret);
+ return ret;
+}
+
+static uint32_t parallel_ioport_read_hw(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint8_t ret = 0xff;
+ addr &= 7;
+ switch(addr) {
+ case PARA_REG_DATA:
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_DATA, &ret);
+ if (s->last_read_offset != addr || s->datar != ret)
+ pdebug("rd%02x\n", ret);
+ s->datar = ret;
+ break;
+ case PARA_REG_STS:
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_STATUS, &ret);
+ ret &= ~PARA_STS_TMOUT;
+ if (s->epp_timeout)
+ ret |= PARA_STS_TMOUT;
+ if (s->last_read_offset != addr || s->status != ret)
+ pdebug("rs%02x\n", ret);
+ s->status = ret;
+ break;
+ case PARA_REG_CTR:
+ /* s->control has some bits fixed to 1. It is zero only when
+ it has not been yet written to. */
+ if (s->control == 0) {
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_CONTROL, &ret);
+ if (s->last_read_offset != addr)
+ pdebug("rc%02x\n", ret);
+ s->control = ret;
+ }
+ else {
+ ret = s->control;
+ if (s->last_read_offset != addr)
+ pdebug("rc%02x\n", ret);
+ }
+ break;
+ case PARA_REG_EPP_ADDR:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT))
+ /* Controls not correct for EPP addr cycle, so do nothing */
+ pdebug("ra%02x s\n", ret);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ_ADDR, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("ra%02x t\n", ret);
+ }
+ else
+ pdebug("ra%02x\n", ret);
+ }
+ break;
+ case PARA_REG_EPP_DATA:
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT))
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("re%02x s\n", ret);
+ else {
+ struct ParallelIOArg ioarg = { .buffer = &ret, .count = 1 };
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg)) {
+ s->epp_timeout = 1;
+ pdebug("re%02x t\n", ret);
+ }
+ else
+ pdebug("re%02x\n", ret);
+ }
+ break;
+ }
+ s->last_read_offset = addr;
+ return ret;
+}
+
+static uint32_t
+parallel_ioport_eppdata_read_hw2(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint32_t ret;
+ uint16_t eppdata = ~0;
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("re%04x s\n", eppdata);
+ return eppdata;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
+ ret = le16_to_cpu(eppdata);
+
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("re%04x t\n", ret);
+ }
+ else
+ pdebug("re%04x\n", ret);
+ return ret;
+}
+
+static uint32_t
+parallel_ioport_eppdata_read_hw4(void *opaque, uint32_t addr)
+{
+ ParallelState *s = opaque;
+ uint32_t ret;
+ uint32_t eppdata = ~0U;
+ int err;
+ struct ParallelIOArg ioarg = {
+ .buffer = &eppdata, .count = sizeof(eppdata)
+ };
+ if ((s->control & (PARA_CTR_DIR|PARA_CTR_SIGNAL)) != (PARA_CTR_DIR|PARA_CTR_INIT)) {
+ /* Controls not correct for EPP data cycle, so do nothing */
+ pdebug("re%08x s\n", eppdata);
+ return eppdata;
+ }
+ err = qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_EPP_READ, &ioarg);
+ ret = le32_to_cpu(eppdata);
+
+ if (err) {
+ s->epp_timeout = 1;
+ pdebug("re%08x t\n", ret);
+ }
+ else
+ pdebug("re%08x\n", ret);
+ return ret;
+}
+
+static void parallel_ioport_ecp_write(void *opaque, uint32_t addr, uint32_t val)
+{
+ pdebug("wecp%d=%02x\n", addr & 7, val);
+}
+
+static uint32_t parallel_ioport_ecp_read(void *opaque, uint32_t addr)
+{
+ uint8_t ret = 0xff;
+
+ pdebug("recp%d:%02x\n", addr & 7, ret);
+ return ret;
+}
+
+static void parallel_reset(void *opaque)
+{
+ ParallelState *s = opaque;
+
+ s->datar = ~0;
+ s->dataw = ~0;
+ s->status = PARA_STS_BUSY;
+ s->status |= PARA_STS_ACK;
+ s->status |= PARA_STS_ONLINE;
+ s->status |= PARA_STS_ERROR;
+ s->status |= PARA_STS_TMOUT;
+ s->control = PARA_CTR_SELECT;
+ s->control |= PARA_CTR_INIT;
+ s->control |= 0xc0;
+ s->irq_pending = 0;
+ s->hw_driver = 0;
+ s->epp_timeout = 0;
+ s->last_read_offset = ~0U;
+}
+
+static const int isa_parallel_io[MAX_PARALLEL_PORTS] = { 0x378, 0x278, 0x3bc };
+
+static const MemoryRegionPortio isa_parallel_portio_hw_list[] = {
+ { 0, 8, 1,
+ .read = parallel_ioport_read_hw,
+ .write = parallel_ioport_write_hw },
+ { 4, 1, 2,
+ .read = parallel_ioport_eppdata_read_hw2,
+ .write = parallel_ioport_eppdata_write_hw2 },
+ { 4, 1, 4,
+ .read = parallel_ioport_eppdata_read_hw4,
+ .write = parallel_ioport_eppdata_write_hw4 },
+ { 0x400, 8, 1,
+ .read = parallel_ioport_ecp_read,
+ .write = parallel_ioport_ecp_write },
+ PORTIO_END_OF_LIST(),
+};
+
+static const MemoryRegionPortio isa_parallel_portio_sw_list[] = {
+ { 0, 8, 1,
+ .read = parallel_ioport_read_sw,
+ .write = parallel_ioport_write_sw },
+ PORTIO_END_OF_LIST(),
+};
+
+static void parallel_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ static int index;
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISAParallelState *isa = ISA_PARALLEL(dev);
+ ParallelState *s = &isa->state;
+ int base;
+ uint8_t dummy;
+
+ if (!s->chr) {
+ error_setg(errp, "Can't create parallel device, empty char device");
+ return;
+ }
+
+ if (isa->index == -1) {
+ isa->index = index;
+ }
+ if (isa->index >= MAX_PARALLEL_PORTS) {
+ error_setg(errp, "Max. supported number of parallel ports is %d.",
+ MAX_PARALLEL_PORTS);
+ return;
+ }
+ if (isa->iobase == -1) {
+ isa->iobase = isa_parallel_io[isa->index];
+ }
+ index++;
+
+ base = isa->iobase;
+ isa_init_irq(isadev, &s->irq, isa->isairq);
+ qemu_register_reset(parallel_reset, s);
+
+ if (qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_PP_READ_STATUS, &dummy) == 0) {
+ s->hw_driver = 1;
+ s->status = dummy;
+ }
+
+ isa_register_portio_list(isadev, base,
+ (s->hw_driver
+ ? &isa_parallel_portio_hw_list[0]
+ : &isa_parallel_portio_sw_list[0]),
+ s, "parallel");
+}
+
+/* Memory mapped interface */
+static uint32_t parallel_mm_readb (void *opaque, hwaddr addr)
+{
+ ParallelState *s = opaque;
+
+ return parallel_ioport_read_sw(s, addr >> s->it_shift) & 0xFF;
+}
+
+static void parallel_mm_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ParallelState *s = opaque;
+
+ parallel_ioport_write_sw(s, addr >> s->it_shift, value & 0xFF);
+}
+
+static uint32_t parallel_mm_readw (void *opaque, hwaddr addr)
+{
+ ParallelState *s = opaque;
+
+ return parallel_ioport_read_sw(s, addr >> s->it_shift) & 0xFFFF;
+}
+
+static void parallel_mm_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ParallelState *s = opaque;
+
+ parallel_ioport_write_sw(s, addr >> s->it_shift, value & 0xFFFF);
+}
+
+static uint32_t parallel_mm_readl (void *opaque, hwaddr addr)
+{
+ ParallelState *s = opaque;
+
+ return parallel_ioport_read_sw(s, addr >> s->it_shift);
+}
+
+static void parallel_mm_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ParallelState *s = opaque;
+
+ parallel_ioport_write_sw(s, addr >> s->it_shift, value);
+}
+
+static const MemoryRegionOps parallel_mm_ops = {
+ .old_mmio = {
+ .read = { parallel_mm_readb, parallel_mm_readw, parallel_mm_readl },
+ .write = { parallel_mm_writeb, parallel_mm_writew, parallel_mm_writel },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* If fd is zero, it means that the parallel device uses the console */
+bool parallel_mm_init(MemoryRegion *address_space,
+ hwaddr base, int it_shift, qemu_irq irq,
+ CharDriverState *chr)
+{
+ ParallelState *s;
+
+ s = g_malloc0(sizeof(ParallelState));
+ s->irq = irq;
+ s->chr = chr;
+ s->it_shift = it_shift;
+ qemu_register_reset(parallel_reset, s);
+
+ memory_region_init_io(&s->iomem, NULL, &parallel_mm_ops, s,
+ "parallel", 8 << it_shift);
+ memory_region_add_subregion(address_space, base, &s->iomem);
+ return true;
+}
+
+static Property parallel_isa_properties[] = {
+ DEFINE_PROP_UINT32("index", ISAParallelState, index, -1),
+ DEFINE_PROP_HEX32("iobase", ISAParallelState, iobase, -1),
+ DEFINE_PROP_UINT32("irq", ISAParallelState, isairq, 7),
+ DEFINE_PROP_CHR("chardev", ISAParallelState, state.chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void parallel_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = parallel_isa_realizefn;
+ dc->props = parallel_isa_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo parallel_isa_info = {
+ .name = TYPE_ISA_PARALLEL,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISAParallelState),
+ .class_init = parallel_isa_class_initfn,
+};
+
+static void parallel_register_types(void)
+{
+ type_register_static(&parallel_isa_info);
+}
+
+type_init(parallel_register_types)
diff --git a/hw/char/pl011.c b/hw/char/pl011.c
new file mode 100644
index 000000000..a8ae6f470
--- /dev/null
+++ b/hw/char/pl011.c
@@ -0,0 +1,332 @@
+/*
+ * Arm PrimeCell PL011 UART
+ *
+ * Copyright (c) 2006 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#define TYPE_PL011 "pl011"
+#define PL011(obj) OBJECT_CHECK(PL011State, (obj), TYPE_PL011)
+
+typedef struct PL011State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t readbuff;
+ uint32_t flags;
+ uint32_t lcr;
+ uint32_t cr;
+ uint32_t dmacr;
+ uint32_t int_enabled;
+ uint32_t int_level;
+ uint32_t read_fifo[16];
+ uint32_t ilpr;
+ uint32_t ibrd;
+ uint32_t fbrd;
+ uint32_t ifl;
+ int read_pos;
+ int read_count;
+ int read_trigger;
+ CharDriverState *chr;
+ qemu_irq irq;
+ const unsigned char *id;
+} PL011State;
+
+#define PL011_INT_TX 0x20
+#define PL011_INT_RX 0x10
+
+#define PL011_FLAG_TXFE 0x80
+#define PL011_FLAG_RXFF 0x40
+#define PL011_FLAG_TXFF 0x20
+#define PL011_FLAG_RXFE 0x10
+
+static const unsigned char pl011_id_arm[8] =
+ { 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
+static const unsigned char pl011_id_luminary[8] =
+ { 0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 };
+
+static void pl011_update(PL011State *s)
+{
+ uint32_t flags;
+
+ flags = s->int_level & s->int_enabled;
+ qemu_set_irq(s->irq, flags != 0);
+}
+
+static uint64_t pl011_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ PL011State *s = (PL011State *)opaque;
+ uint32_t c;
+
+ if (offset >= 0xfe0 && offset < 0x1000) {
+ return s->id[(offset - 0xfe0) >> 2];
+ }
+ switch (offset >> 2) {
+ case 0: /* UARTDR */
+ s->flags &= ~PL011_FLAG_RXFF;
+ c = s->read_fifo[s->read_pos];
+ if (s->read_count > 0) {
+ s->read_count--;
+ if (++s->read_pos == 16)
+ s->read_pos = 0;
+ }
+ if (s->read_count == 0) {
+ s->flags |= PL011_FLAG_RXFE;
+ }
+ if (s->read_count == s->read_trigger - 1)
+ s->int_level &= ~ PL011_INT_RX;
+ pl011_update(s);
+ if (s->chr) {
+ qemu_chr_accept_input(s->chr);
+ }
+ return c;
+ case 1: /* UARTCR */
+ return 0;
+ case 6: /* UARTFR */
+ return s->flags;
+ case 8: /* UARTILPR */
+ return s->ilpr;
+ case 9: /* UARTIBRD */
+ return s->ibrd;
+ case 10: /* UARTFBRD */
+ return s->fbrd;
+ case 11: /* UARTLCR_H */
+ return s->lcr;
+ case 12: /* UARTCR */
+ return s->cr;
+ case 13: /* UARTIFLS */
+ return s->ifl;
+ case 14: /* UARTIMSC */
+ return s->int_enabled;
+ case 15: /* UARTRIS */
+ return s->int_level;
+ case 16: /* UARTMIS */
+ return s->int_level & s->int_enabled;
+ case 18: /* UARTDMACR */
+ return s->dmacr;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl011_read: Bad offset %x\n", (int)offset);
+ return 0;
+ }
+}
+
+static void pl011_set_read_trigger(PL011State *s)
+{
+#if 0
+ /* The docs say the RX interrupt is triggered when the FIFO exceeds
+ the threshold. However linux only reads the FIFO in response to an
+ interrupt. Triggering the interrupt when the FIFO is non-empty seems
+ to make things work. */
+ if (s->lcr & 0x10)
+ s->read_trigger = (s->ifl >> 1) & 0x1c;
+ else
+#endif
+ s->read_trigger = 1;
+}
+
+static void pl011_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ PL011State *s = (PL011State *)opaque;
+ unsigned char ch;
+
+ switch (offset >> 2) {
+ case 0: /* UARTDR */
+ /* ??? Check if transmitter is enabled. */
+ ch = value;
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ s->int_level |= PL011_INT_TX;
+ pl011_update(s);
+ break;
+ case 1: /* UARTCR */
+ s->cr = value;
+ break;
+ case 6: /* UARTFR */
+ /* Writes to Flag register are ignored. */
+ break;
+ case 8: /* UARTUARTILPR */
+ s->ilpr = value;
+ break;
+ case 9: /* UARTIBRD */
+ s->ibrd = value;
+ break;
+ case 10: /* UARTFBRD */
+ s->fbrd = value;
+ break;
+ case 11: /* UARTLCR_H */
+ s->lcr = value;
+ pl011_set_read_trigger(s);
+ break;
+ case 12: /* UARTCR */
+ /* ??? Need to implement the enable and loopback bits. */
+ s->cr = value;
+ break;
+ case 13: /* UARTIFS */
+ s->ifl = value;
+ pl011_set_read_trigger(s);
+ break;
+ case 14: /* UARTIMSC */
+ s->int_enabled = value;
+ pl011_update(s);
+ break;
+ case 17: /* UARTICR */
+ s->int_level &= ~value;
+ pl011_update(s);
+ break;
+ case 18: /* UARTDMACR */
+ s->dmacr = value;
+ if (value & 3) {
+ qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "pl011_write: Bad offset %x\n", (int)offset);
+ }
+}
+
+static int pl011_can_receive(void *opaque)
+{
+ PL011State *s = (PL011State *)opaque;
+
+ if (s->lcr & 0x10)
+ return s->read_count < 16;
+ else
+ return s->read_count < 1;
+}
+
+static void pl011_put_fifo(void *opaque, uint32_t value)
+{
+ PL011State *s = (PL011State *)opaque;
+ int slot;
+
+ slot = s->read_pos + s->read_count;
+ if (slot >= 16)
+ slot -= 16;
+ s->read_fifo[slot] = value;
+ s->read_count++;
+ s->flags &= ~PL011_FLAG_RXFE;
+ if (s->cr & 0x10 || s->read_count == 16) {
+ s->flags |= PL011_FLAG_RXFF;
+ }
+ if (s->read_count == s->read_trigger) {
+ s->int_level |= PL011_INT_RX;
+ pl011_update(s);
+ }
+}
+
+static void pl011_receive(void *opaque, const uint8_t *buf, int size)
+{
+ pl011_put_fifo(opaque, *buf);
+}
+
+static void pl011_event(void *opaque, int event)
+{
+ if (event == CHR_EVENT_BREAK)
+ pl011_put_fifo(opaque, 0x400);
+}
+
+static const MemoryRegionOps pl011_ops = {
+ .read = pl011_read,
+ .write = pl011_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_pl011 = {
+ .name = "pl011",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(readbuff, PL011State),
+ VMSTATE_UINT32(flags, PL011State),
+ VMSTATE_UINT32(lcr, PL011State),
+ VMSTATE_UINT32(cr, PL011State),
+ VMSTATE_UINT32(dmacr, PL011State),
+ VMSTATE_UINT32(int_enabled, PL011State),
+ VMSTATE_UINT32(int_level, PL011State),
+ VMSTATE_UINT32_ARRAY(read_fifo, PL011State, 16),
+ VMSTATE_UINT32(ilpr, PL011State),
+ VMSTATE_UINT32(ibrd, PL011State),
+ VMSTATE_UINT32(fbrd, PL011State),
+ VMSTATE_UINT32(ifl, PL011State),
+ VMSTATE_INT32(read_pos, PL011State),
+ VMSTATE_INT32(read_count, PL011State),
+ VMSTATE_INT32(read_trigger, PL011State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void pl011_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ PL011State *s = PL011(obj);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->read_trigger = 1;
+ s->ifl = 0x12;
+ s->cr = 0x300;
+ s->flags = 0x90;
+
+ s->id = pl011_id_arm;
+}
+
+static void pl011_realize(DeviceState *dev, Error **errp)
+{
+ PL011State *s = PL011(dev);
+
+ s->chr = qemu_char_get_next_serial();
+
+ if (s->chr) {
+ qemu_chr_add_handlers(s->chr, pl011_can_receive, pl011_receive,
+ pl011_event, s);
+ }
+}
+
+static void pl011_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = pl011_realize;
+ dc->vmsd = &vmstate_pl011;
+}
+
+static const TypeInfo pl011_arm_info = {
+ .name = TYPE_PL011,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PL011State),
+ .instance_init = pl011_init,
+ .class_init = pl011_class_init,
+};
+
+static void pl011_luminary_init(Object *obj)
+{
+ PL011State *s = PL011(obj);
+
+ s->id = pl011_id_luminary;
+}
+
+static const TypeInfo pl011_luminary_info = {
+ .name = "pl011_luminary",
+ .parent = TYPE_PL011,
+ .instance_init = pl011_luminary_init,
+};
+
+static void pl011_register_types(void)
+{
+ type_register_static(&pl011_arm_info);
+ type_register_static(&pl011_luminary_info);
+}
+
+type_init(pl011_register_types)
diff --git a/hw/char/sclpconsole.c b/hw/char/sclpconsole.c
new file mode 100644
index 000000000..eb3988c2e
--- /dev/null
+++ b/hw/char/sclpconsole.c
@@ -0,0 +1,289 @@
+/*
+ * SCLP event type
+ * Ascii Console Data (VT220 Console)
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Heinz Graalfs <graalfs@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version. See the COPYING file in the top-level directory.
+ *
+ */
+
+#include <hw/qdev.h>
+#include "qemu/thread.h"
+#include "qemu/error-report.h"
+
+#include "hw/s390x/sclp.h"
+#include "hw/s390x/event-facility.h"
+#include "sysemu/char.h"
+
+typedef struct ASCIIConsoleData {
+ EventBufferHeader ebh;
+ char data[0];
+} QEMU_PACKED ASCIIConsoleData;
+
+/* max size for ASCII data in 4K SCCB page */
+#define SIZE_BUFFER_VT220 4080
+
+typedef struct SCLPConsole {
+ SCLPEvent event;
+ CharDriverState *chr;
+ /* io vector */
+ uint8_t *iov; /* iov buffer pointer */
+ uint8_t *iov_sclp; /* pointer to SCLP read offset */
+ uint8_t *iov_bs; /* pointer byte stream read offset */
+ uint32_t iov_data_len; /* length of byte stream in buffer */
+ uint32_t iov_sclp_rest; /* length of byte stream not read via SCLP */
+ qemu_irq irq_read_vt220;
+} SCLPConsole;
+
+/* character layer call-back functions */
+
+/* Return number of bytes that fit into iov buffer */
+static int chr_can_read(void *opaque)
+{
+ SCLPConsole *scon = opaque;
+
+ return scon->iov ? SIZE_BUFFER_VT220 - scon->iov_data_len : 0;
+}
+
+/* Receive n bytes from character layer, save in iov buffer,
+ * and set event pending */
+static void receive_from_chr_layer(SCLPConsole *scon, const uint8_t *buf,
+ int size)
+{
+ assert(scon->iov);
+
+ /* read data must fit into current buffer */
+ assert(size <= SIZE_BUFFER_VT220 - scon->iov_data_len);
+
+ /* put byte-stream from character layer into buffer */
+ memcpy(scon->iov_bs, buf, size);
+ scon->iov_data_len += size;
+ scon->iov_sclp_rest += size;
+ scon->iov_bs += size;
+ scon->event.event_pending = true;
+}
+
+/* Send data from a char device over to the guest */
+static void chr_read(void *opaque, const uint8_t *buf, int size)
+{
+ SCLPConsole *scon = opaque;
+
+ assert(scon);
+
+ receive_from_chr_layer(scon, buf, size);
+ /* trigger SCLP read operation */
+ qemu_irq_raise(scon->irq_read_vt220);
+}
+
+static void chr_event(void *opaque, int event)
+{
+ SCLPConsole *scon = opaque;
+
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ if (!scon->iov) {
+ scon->iov = g_malloc0(SIZE_BUFFER_VT220);
+ scon->iov_sclp = scon->iov;
+ scon->iov_bs = scon->iov;
+ scon->iov_data_len = 0;
+ scon->iov_sclp_rest = 0;
+ }
+ break;
+ case CHR_EVENT_CLOSED:
+ if (scon->iov) {
+ g_free(scon->iov);
+ scon->iov = NULL;
+ }
+ break;
+ }
+}
+
+/* functions to be called by event facility */
+
+static int event_type(void)
+{
+ return SCLP_EVENT_ASCII_CONSOLE_DATA;
+}
+
+static unsigned int send_mask(void)
+{
+ return SCLP_EVENT_MASK_MSG_ASCII;
+}
+
+static unsigned int receive_mask(void)
+{
+ return SCLP_EVENT_MASK_MSG_ASCII;
+}
+
+/* triggered by SCLP's read_event_data -
+ * copy console data byte-stream into provided (SCLP) buffer
+ */
+static void get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size,
+ int avail)
+{
+ SCLPConsole *cons = DO_UPCAST(SCLPConsole, event, event);
+
+ /* first byte is hex 0 saying an ascii string follows */
+ *buf++ = '\0';
+ avail--;
+ /* if all data fit into provided SCLP buffer */
+ if (avail >= cons->iov_sclp_rest) {
+ /* copy character byte-stream to SCLP buffer */
+ memcpy(buf, cons->iov_sclp, cons->iov_sclp_rest);
+ *size = cons->iov_sclp_rest + 1;
+ cons->iov_sclp = cons->iov;
+ cons->iov_bs = cons->iov;
+ cons->iov_data_len = 0;
+ cons->iov_sclp_rest = 0;
+ event->event_pending = false;
+ /* data provided and no more data pending */
+ } else {
+ /* if provided buffer is too small, just copy part */
+ memcpy(buf, cons->iov_sclp, avail);
+ *size = avail + 1;
+ cons->iov_sclp_rest -= avail;
+ cons->iov_sclp += avail;
+ /* more data pending */
+ }
+}
+
+static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
+ int *slen)
+{
+ int avail;
+ size_t src_len;
+ uint8_t *to;
+ ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr;
+
+ if (!event->event_pending) {
+ /* no data pending */
+ return 0;
+ }
+
+ to = (uint8_t *)&acd->data;
+ avail = *slen - sizeof(ASCIIConsoleData);
+ get_console_data(event, to, &src_len, avail);
+
+ acd->ebh.length = cpu_to_be16(sizeof(ASCIIConsoleData) + src_len);
+ acd->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA;
+ acd->ebh.flags |= SCLP_EVENT_BUFFER_ACCEPTED;
+ *slen = avail - src_len;
+
+ return 1;
+}
+
+/* triggered by SCLP's write_event_data
+ * - write console data to character layer
+ * returns < 0 if an error occurred
+ */
+static ssize_t write_console_data(SCLPEvent *event, const uint8_t *buf,
+ size_t len)
+{
+ SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event);
+
+ if (!scon->chr) {
+ /* If there's no backend, we can just say we consumed all data. */
+ return len;
+ }
+
+ return qemu_chr_fe_write_all(scon->chr, buf, len);
+}
+
+static int write_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr)
+{
+ int rc;
+ int length;
+ ssize_t written;
+ ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr;
+
+ length = be16_to_cpu(evt_buf_hdr->length) - sizeof(EventBufferHeader);
+ written = write_console_data(event, (uint8_t *)acd->data, length);
+
+ rc = SCLP_RC_NORMAL_COMPLETION;
+ /* set event buffer accepted flag */
+ evt_buf_hdr->flags |= SCLP_EVENT_BUFFER_ACCEPTED;
+
+ /* written will be zero if a pty is not connected - don't treat as error */
+ if (written < 0) {
+ /* event buffer not accepted due to error in character layer */
+ evt_buf_hdr->flags &= ~(SCLP_EVENT_BUFFER_ACCEPTED);
+ rc = SCLP_RC_CONTAINED_EQUIPMENT_CHECK;
+ }
+
+ return rc;
+}
+
+static void trigger_ascii_console_data(void *opaque, int n, int level)
+{
+ sclp_service_interrupt(0);
+}
+
+/* qemu object creation and initialization functions */
+
+/* tell character layer our call-back functions */
+static int console_init(SCLPEvent *event)
+{
+ static bool console_available;
+
+ SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event);
+
+ if (console_available) {
+ error_report("Multiple VT220 operator consoles are not supported");
+ return -1;
+ }
+ console_available = true;
+ event->event_type = SCLP_EVENT_ASCII_CONSOLE_DATA;
+ if (scon->chr) {
+ qemu_chr_add_handlers(scon->chr, chr_can_read,
+ chr_read, chr_event, scon);
+ }
+ scon->irq_read_vt220 = *qemu_allocate_irqs(trigger_ascii_console_data,
+ NULL, 1);
+
+ return 0;
+}
+
+static int console_exit(SCLPEvent *event)
+{
+ return 0;
+}
+
+static Property console_properties[] = {
+ DEFINE_PROP_CHR("chardev", SCLPConsole, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void console_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SCLPEventClass *ec = SCLP_EVENT_CLASS(klass);
+
+ dc->props = console_properties;
+ ec->init = console_init;
+ ec->exit = console_exit;
+ ec->get_send_mask = send_mask;
+ ec->get_receive_mask = receive_mask;
+ ec->event_type = event_type;
+ ec->read_event_data = read_event_data;
+ ec->write_event_data = write_event_data;
+}
+
+static const TypeInfo sclp_console_info = {
+ .name = "sclpconsole",
+ .parent = TYPE_SCLP_EVENT,
+ .instance_size = sizeof(SCLPConsole),
+ .class_init = console_class_init,
+ .class_size = sizeof(SCLPEventClass),
+};
+
+static void register_types(void)
+{
+ type_register_static(&sclp_console_info);
+}
+
+type_init(register_types)
diff --git a/hw/char/serial-isa.c b/hw/char/serial-isa.c
new file mode 100644
index 000000000..5cb77b311
--- /dev/null
+++ b/hw/char/serial-isa.c
@@ -0,0 +1,138 @@
+/*
+ * QEMU 16550A UART emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw/char/serial.h"
+#include "hw/isa/isa.h"
+
+#define ISA_SERIAL(obj) OBJECT_CHECK(ISASerialState, (obj), TYPE_ISA_SERIAL)
+
+typedef struct ISASerialState {
+ ISADevice parent_obj;
+
+ uint32_t index;
+ uint32_t iobase;
+ uint32_t isairq;
+ SerialState state;
+} ISASerialState;
+
+static const int isa_serial_io[MAX_SERIAL_PORTS] = {
+ 0x3f8, 0x2f8, 0x3e8, 0x2e8
+};
+static const int isa_serial_irq[MAX_SERIAL_PORTS] = {
+ 4, 3, 4, 3
+};
+
+static void serial_isa_realizefn(DeviceState *dev, Error **errp)
+{
+ static int index;
+ ISADevice *isadev = ISA_DEVICE(dev);
+ ISASerialState *isa = ISA_SERIAL(dev);
+ SerialState *s = &isa->state;
+
+ if (isa->index == -1) {
+ isa->index = index;
+ }
+ if (isa->index >= MAX_SERIAL_PORTS) {
+ error_setg(errp, "Max. supported number of ISA serial ports is %d.",
+ MAX_SERIAL_PORTS);
+ return;
+ }
+ if (isa->iobase == -1) {
+ isa->iobase = isa_serial_io[isa->index];
+ }
+ if (isa->isairq == -1) {
+ isa->isairq = isa_serial_irq[isa->index];
+ }
+ index++;
+
+ s->baudbase = 115200;
+ isa_init_irq(isadev, &s->irq, isa->isairq);
+ serial_realize_core(s, errp);
+ qdev_set_legacy_instance_id(dev, isa->iobase, 3);
+
+ memory_region_init_io(&s->io, OBJECT(isa), &serial_io_ops, s, "serial", 8);
+ isa_register_ioport(isadev, &s->io, isa->iobase);
+}
+
+static const VMStateDescription vmstate_isa_serial = {
+ .name = "serial",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, ISASerialState, 0, vmstate_serial, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property serial_isa_properties[] = {
+ DEFINE_PROP_UINT32("index", ISASerialState, index, -1),
+ DEFINE_PROP_HEX32("iobase", ISASerialState, iobase, -1),
+ DEFINE_PROP_UINT32("irq", ISASerialState, isairq, -1),
+ DEFINE_PROP_CHR("chardev", ISASerialState, state.chr),
+ DEFINE_PROP_UINT32("wakeup", ISASerialState, state.wakeup, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void serial_isa_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = serial_isa_realizefn;
+ dc->vmsd = &vmstate_isa_serial;
+ dc->props = serial_isa_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo serial_isa_info = {
+ .name = TYPE_ISA_SERIAL,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(ISASerialState),
+ .class_init = serial_isa_class_initfn,
+};
+
+static void serial_register_types(void)
+{
+ type_register_static(&serial_isa_info);
+}
+
+type_init(serial_register_types)
+
+bool serial_isa_init(ISABus *bus, int index, CharDriverState *chr)
+{
+ DeviceState *dev;
+ ISADevice *isadev;
+
+ isadev = isa_try_create(bus, TYPE_ISA_SERIAL);
+ if (!isadev) {
+ return false;
+ }
+ dev = DEVICE(isadev);
+ qdev_prop_set_uint32(dev, "index", index);
+ qdev_prop_set_chr(dev, "chardev", chr);
+ if (qdev_init(dev) < 0) {
+ return false;
+ }
+ return true;
+}
diff --git a/hw/char/serial-pci.c b/hw/char/serial-pci.c
new file mode 100644
index 000000000..aec6705a0
--- /dev/null
+++ b/hw/char/serial-pci.c
@@ -0,0 +1,269 @@
+/*
+ * QEMU 16550A UART emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* see docs/specs/pci-serial.txt */
+
+#include "hw/char/serial.h"
+#include "hw/pci/pci.h"
+#include "qapi/qmp/qerror.h"
+
+#define PCI_SERIAL_MAX_PORTS 4
+
+typedef struct PCISerialState {
+ PCIDevice dev;
+ SerialState state;
+} PCISerialState;
+
+typedef struct PCIMultiSerialState {
+ PCIDevice dev;
+ MemoryRegion iobar;
+ uint32_t ports;
+ char *name[PCI_SERIAL_MAX_PORTS];
+ SerialState state[PCI_SERIAL_MAX_PORTS];
+ uint32_t level[PCI_SERIAL_MAX_PORTS];
+ qemu_irq *irqs;
+} PCIMultiSerialState;
+
+static int serial_pci_init(PCIDevice *dev)
+{
+ PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev);
+ SerialState *s = &pci->state;
+ Error *err = NULL;
+
+ s->baudbase = 115200;
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ qerror_report_err(err);
+ error_free(err);
+ return -1;
+ }
+
+ pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
+ s->irq = pci->dev.irq[0];
+
+ memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s, "serial", 8);
+ pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+ return 0;
+}
+
+static void multi_serial_irq_mux(void *opaque, int n, int level)
+{
+ PCIMultiSerialState *pci = opaque;
+ int i, pending = 0;
+
+ pci->level[n] = level;
+ for (i = 0; i < pci->ports; i++) {
+ if (pci->level[i]) {
+ pending = 1;
+ }
+ }
+ qemu_set_irq(pci->dev.irq[0], pending);
+}
+
+static int multi_serial_pci_init(PCIDevice *dev)
+{
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev);
+ SerialState *s;
+ Error *err = NULL;
+ int i;
+
+ switch (pc->device_id) {
+ case 0x0003:
+ pci->ports = 2;
+ break;
+ case 0x0004:
+ pci->ports = 4;
+ break;
+ }
+ assert(pci->ports > 0);
+ assert(pci->ports <= PCI_SERIAL_MAX_PORTS);
+
+ pci->dev.config[PCI_INTERRUPT_PIN] = 0x01;
+ memory_region_init(&pci->iobar, OBJECT(pci), "multiserial", 8 * pci->ports);
+ pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->iobar);
+ pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci,
+ pci->ports);
+
+ for (i = 0; i < pci->ports; i++) {
+ s = pci->state + i;
+ s->baudbase = 115200;
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ qerror_report_err(err);
+ error_free(err);
+ return -1;
+ }
+ s->irq = pci->irqs[i];
+ pci->name[i] = g_strdup_printf("uart #%d", i+1);
+ memory_region_init_io(&s->io, OBJECT(pci), &serial_io_ops, s,
+ pci->name[i], 8);
+ memory_region_add_subregion(&pci->iobar, 8 * i, &s->io);
+ }
+ return 0;
+}
+
+static void serial_pci_exit(PCIDevice *dev)
+{
+ PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev);
+ SerialState *s = &pci->state;
+
+ serial_exit_core(s);
+ memory_region_destroy(&s->io);
+}
+
+static void multi_serial_pci_exit(PCIDevice *dev)
+{
+ PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev);
+ SerialState *s;
+ int i;
+
+ for (i = 0; i < pci->ports; i++) {
+ s = pci->state + i;
+ serial_exit_core(s);
+ memory_region_destroy(&s->io);
+ g_free(pci->name[i]);
+ }
+ memory_region_destroy(&pci->iobar);
+ qemu_free_irqs(pci->irqs);
+}
+
+static const VMStateDescription vmstate_pci_serial = {
+ .name = "pci-serial",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCISerialState),
+ VMSTATE_STRUCT(state, PCISerialState, 0, vmstate_serial, SerialState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_multi_serial = {
+ .name = "pci-serial-multi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, PCIMultiSerialState),
+ VMSTATE_STRUCT_ARRAY(state, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS,
+ 0, vmstate_serial, SerialState),
+ VMSTATE_UINT32_ARRAY(level, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property serial_pci_properties[] = {
+ DEFINE_PROP_CHR("chardev", PCISerialState, state.chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property multi_2x_serial_pci_properties[] = {
+ DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr),
+ DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static Property multi_4x_serial_pci_properties[] = {
+ DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr),
+ DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr),
+ DEFINE_PROP_CHR("chardev3", PCIMultiSerialState, state[2].chr),
+ DEFINE_PROP_CHR("chardev4", PCIMultiSerialState, state[3].chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void serial_pci_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+ pc->init = serial_pci_init;
+ pc->exit = serial_pci_exit;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL;
+ pc->revision = 1;
+ pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
+ dc->vmsd = &vmstate_pci_serial;
+ dc->props = serial_pci_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static void multi_2x_serial_pci_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+ pc->init = multi_serial_pci_init;
+ pc->exit = multi_serial_pci_exit;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL2;
+ pc->revision = 1;
+ pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
+ dc->vmsd = &vmstate_pci_multi_serial;
+ dc->props = multi_2x_serial_pci_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static void multi_4x_serial_pci_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
+ pc->init = multi_serial_pci_init;
+ pc->exit = multi_serial_pci_exit;
+ pc->vendor_id = PCI_VENDOR_ID_REDHAT;
+ pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL4;
+ pc->revision = 1;
+ pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL;
+ dc->vmsd = &vmstate_pci_multi_serial;
+ dc->props = multi_4x_serial_pci_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+}
+
+static const TypeInfo serial_pci_info = {
+ .name = "pci-serial",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCISerialState),
+ .class_init = serial_pci_class_initfn,
+};
+
+static const TypeInfo multi_2x_serial_pci_info = {
+ .name = "pci-serial-2x",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIMultiSerialState),
+ .class_init = multi_2x_serial_pci_class_initfn,
+};
+
+static const TypeInfo multi_4x_serial_pci_info = {
+ .name = "pci-serial-4x",
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PCIMultiSerialState),
+ .class_init = multi_4x_serial_pci_class_initfn,
+};
+
+static void serial_pci_register_types(void)
+{
+ type_register_static(&serial_pci_info);
+ type_register_static(&multi_2x_serial_pci_info);
+ type_register_static(&multi_4x_serial_pci_info);
+}
+
+type_init(serial_pci_register_types)
diff --git a/hw/char/serial.c b/hw/char/serial.c
new file mode 100644
index 000000000..602559254
--- /dev/null
+++ b/hw/char/serial.c
@@ -0,0 +1,775 @@
+/*
+ * QEMU 16550A UART emulation
+ *
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ * Copyright (c) 2008 Citrix Systems, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw/char/serial.h"
+#include "sysemu/char.h"
+#include "qemu/timer.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_SERIAL
+
+#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */
+
+#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 */
+
+#define UART_IIR_NO_INT 0x01 /* No interrupts pending */
+#define UART_IIR_ID 0x06 /* 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_CTI 0x0C /* Character Timeout Indication */
+
+#define UART_IIR_FENF 0x80 /* Fifo enabled, but not functionning */
+#define UART_IIR_FE 0xC0 /* Fifo enabled */
+
+/*
+ * These are the definitions for the Modem Control Register
+ */
+#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 */
+
+/*
+ * These are the definitions for the 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 0x0F /* Any of the delta bits! */
+
+#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_INT_ANY 0x1E /* Any of the lsr-interrupt-triggering status bits */
+
+/* Interrupt trigger levels. The byte-counts are for 16550A - in newer UARTs the byte-count for each ITL is higher. */
+
+#define UART_FCR_ITL_1 0x00 /* 1 byte ITL */
+#define UART_FCR_ITL_2 0x40 /* 4 bytes ITL */
+#define UART_FCR_ITL_3 0x80 /* 8 bytes ITL */
+#define UART_FCR_ITL_4 0xC0 /* 14 bytes ITL */
+
+#define UART_FCR_DMS 0x08 /* DMA Mode Select */
+#define UART_FCR_XFR 0x04 /* XMIT Fifo Reset */
+#define UART_FCR_RFR 0x02 /* RCVR Fifo Reset */
+#define UART_FCR_FE 0x01 /* FIFO Enable */
+
+#define MAX_XMIT_RETRY 4
+
+#ifdef DEBUG_SERIAL
+#define DPRINTF(fmt, ...) \
+do { fprintf(stderr, "serial: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+do {} while (0)
+#endif
+
+static void serial_receive1(void *opaque, const uint8_t *buf, int size);
+
+static inline void recv_fifo_put(SerialState *s, uint8_t chr)
+{
+ /* Receive overruns do not overwrite FIFO contents. */
+ if (!fifo8_is_full(&s->recv_fifo)) {
+ fifo8_push(&s->recv_fifo, chr);
+ } else {
+ s->lsr |= UART_LSR_OE;
+ }
+}
+
+static void serial_update_irq(SerialState *s)
+{
+ uint8_t tmp_iir = UART_IIR_NO_INT;
+
+ if ((s->ier & UART_IER_RLSI) && (s->lsr & UART_LSR_INT_ANY)) {
+ tmp_iir = UART_IIR_RLSI;
+ } else if ((s->ier & UART_IER_RDI) && s->timeout_ipending) {
+ /* Note that(s->ier & UART_IER_RDI) can mask this interrupt,
+ * this is not in the specification but is observed on existing
+ * hardware. */
+ tmp_iir = UART_IIR_CTI;
+ } else if ((s->ier & UART_IER_RDI) && (s->lsr & UART_LSR_DR) &&
+ (!(s->fcr & UART_FCR_FE) ||
+ s->recv_fifo.num >= s->recv_fifo_itl)) {
+ tmp_iir = UART_IIR_RDI;
+ } else if ((s->ier & UART_IER_THRI) && s->thr_ipending) {
+ tmp_iir = UART_IIR_THRI;
+ } else if ((s->ier & UART_IER_MSI) && (s->msr & UART_MSR_ANY_DELTA)) {
+ tmp_iir = UART_IIR_MSI;
+ }
+
+ s->iir = tmp_iir | (s->iir & 0xF0);
+
+ if (tmp_iir != UART_IIR_NO_INT) {
+ qemu_irq_raise(s->irq);
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void serial_update_parameters(SerialState *s)
+{
+ int speed, parity, data_bits, stop_bits, frame_size;
+ QEMUSerialSetParams ssp;
+
+ if (s->divider == 0)
+ return;
+
+ /* Start bit. */
+ frame_size = 1;
+ if (s->lcr & 0x08) {
+ /* Parity bit. */
+ frame_size++;
+ if (s->lcr & 0x10)
+ parity = 'E';
+ else
+ parity = 'O';
+ } else {
+ parity = 'N';
+ }
+ if (s->lcr & 0x04)
+ stop_bits = 2;
+ else
+ stop_bits = 1;
+
+ data_bits = (s->lcr & 0x03) + 5;
+ frame_size += data_bits + stop_bits;
+ speed = s->baudbase / s->divider;
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+ s->char_transmit_time = (get_ticks_per_sec() / speed) * frame_size;
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+
+ DPRINTF("speed=%d parity=%c data=%d stop=%d\n",
+ speed, parity, data_bits, stop_bits);
+}
+
+static void serial_update_msl(SerialState *s)
+{
+ uint8_t omsr;
+ int flags;
+
+ qemu_del_timer(s->modem_status_poll);
+
+ if (qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP) {
+ s->poll_msl = -1;
+ return;
+ }
+
+ omsr = s->msr;
+
+ s->msr = (flags & CHR_TIOCM_CTS) ? s->msr | UART_MSR_CTS : s->msr & ~UART_MSR_CTS;
+ s->msr = (flags & CHR_TIOCM_DSR) ? s->msr | UART_MSR_DSR : s->msr & ~UART_MSR_DSR;
+ s->msr = (flags & CHR_TIOCM_CAR) ? s->msr | UART_MSR_DCD : s->msr & ~UART_MSR_DCD;
+ s->msr = (flags & CHR_TIOCM_RI) ? s->msr | UART_MSR_RI : s->msr & ~UART_MSR_RI;
+
+ if (s->msr != omsr) {
+ /* Set delta bits */
+ s->msr = s->msr | ((s->msr >> 4) ^ (omsr >> 4));
+ /* UART_MSR_TERI only if change was from 1 -> 0 */
+ if ((s->msr & UART_MSR_TERI) && !(omsr & UART_MSR_RI))
+ s->msr &= ~UART_MSR_TERI;
+ serial_update_irq(s);
+ }
+
+ /* The real 16550A apparently has a 250ns response latency to line status changes.
+ We'll be lazy and poll only every 10ms, and only poll it at all if MSI interrupts are turned on */
+
+ if (s->poll_msl)
+ qemu_mod_timer(s->modem_status_poll, qemu_get_clock_ns(vm_clock) + get_ticks_per_sec() / 100);
+}
+
+static gboolean serial_xmit(GIOChannel *chan, GIOCondition cond, void *opaque)
+{
+ SerialState *s = opaque;
+
+ if (s->tsr_retry <= 0) {
+ if (s->fcr & UART_FCR_FE) {
+ s->tsr = fifo8_is_full(&s->xmit_fifo) ?
+ 0 : fifo8_pop(&s->xmit_fifo);
+ if (!s->xmit_fifo.num) {
+ s->lsr |= UART_LSR_THRE;
+ }
+ } else if ((s->lsr & UART_LSR_THRE)) {
+ return FALSE;
+ } else {
+ s->tsr = s->thr;
+ s->lsr |= UART_LSR_THRE;
+ s->lsr &= ~UART_LSR_TEMT;
+ }
+ }
+
+ if (s->mcr & UART_MCR_LOOP) {
+ /* in loopback mode, say that we just received a char */
+ serial_receive1(s, &s->tsr, 1);
+ } else if (qemu_chr_fe_write(s->chr, &s->tsr, 1) != 1) {
+ if (s->tsr_retry >= 0 && s->tsr_retry < MAX_XMIT_RETRY &&
+ qemu_chr_fe_add_watch(s->chr, G_IO_OUT, serial_xmit, s) > 0) {
+ s->tsr_retry++;
+ return FALSE;
+ }
+ s->tsr_retry = 0;
+ } else {
+ s->tsr_retry = 0;
+ }
+
+ s->last_xmit_ts = qemu_get_clock_ns(vm_clock);
+
+ if (s->lsr & UART_LSR_THRE) {
+ s->lsr |= UART_LSR_TEMT;
+ s->thr_ipending = 1;
+ serial_update_irq(s);
+ }
+
+ return FALSE;
+}
+
+
+static void serial_ioport_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ SerialState *s = opaque;
+
+ addr &= 7;
+ DPRINTF("write addr=0x%" HWADDR_PRIx " val=0x%" PRIx64 "\n", addr, val);
+ switch(addr) {
+ default:
+ case 0:
+ if (s->lcr & UART_LCR_DLAB) {
+ s->divider = (s->divider & 0xff00) | val;
+ serial_update_parameters(s);
+ } else {
+ s->thr = (uint8_t) val;
+ if(s->fcr & UART_FCR_FE) {
+ /* xmit overruns overwrite data, so make space if needed */
+ if (fifo8_is_full(&s->xmit_fifo)) {
+ fifo8_pop(&s->xmit_fifo);
+ }
+ fifo8_push(&s->xmit_fifo, s->thr);
+ s->lsr &= ~UART_LSR_TEMT;
+ }
+ s->thr_ipending = 0;
+ s->lsr &= ~UART_LSR_THRE;
+ serial_update_irq(s);
+ serial_xmit(NULL, G_IO_OUT, s);
+ }
+ break;
+ case 1:
+ if (s->lcr & UART_LCR_DLAB) {
+ s->divider = (s->divider & 0x00ff) | (val << 8);
+ serial_update_parameters(s);
+ } else {
+ s->ier = val & 0x0f;
+ /* If the backend device is a real serial port, turn polling of the modem
+ status lines on physical port on or off depending on UART_IER_MSI state */
+ if (s->poll_msl >= 0) {
+ if (s->ier & UART_IER_MSI) {
+ s->poll_msl = 1;
+ serial_update_msl(s);
+ } else {
+ qemu_del_timer(s->modem_status_poll);
+ s->poll_msl = 0;
+ }
+ }
+ if (s->lsr & UART_LSR_THRE) {
+ s->thr_ipending = 1;
+ serial_update_irq(s);
+ }
+ }
+ break;
+ case 2:
+ val = val & 0xFF;
+
+ if (s->fcr == val)
+ break;
+
+ /* Did the enable/disable flag change? If so, make sure FIFOs get flushed */
+ if ((val ^ s->fcr) & UART_FCR_FE)
+ val |= UART_FCR_XFR | UART_FCR_RFR;
+
+ /* FIFO clear */
+
+ if (val & UART_FCR_RFR) {
+ qemu_del_timer(s->fifo_timeout_timer);
+ s->timeout_ipending=0;
+ fifo8_reset(&s->recv_fifo);
+ }
+
+ if (val & UART_FCR_XFR) {
+ fifo8_reset(&s->xmit_fifo);
+ }
+
+ if (val & UART_FCR_FE) {
+ s->iir |= UART_IIR_FE;
+ /* Set recv_fifo trigger Level */
+ switch (val & 0xC0) {
+ case UART_FCR_ITL_1:
+ s->recv_fifo_itl = 1;
+ break;
+ case UART_FCR_ITL_2:
+ s->recv_fifo_itl = 4;
+ break;
+ case UART_FCR_ITL_3:
+ s->recv_fifo_itl = 8;
+ break;
+ case UART_FCR_ITL_4:
+ s->recv_fifo_itl = 14;
+ break;
+ }
+ } else
+ s->iir &= ~UART_IIR_FE;
+
+ /* Set fcr - or at least the bits in it that are supposed to "stick" */
+ s->fcr = val & 0xC9;
+ serial_update_irq(s);
+ break;
+ case 3:
+ {
+ int break_enable;
+ s->lcr = val;
+ serial_update_parameters(s);
+ break_enable = (val >> 6) & 1;
+ if (break_enable != s->last_break_enable) {
+ s->last_break_enable = break_enable;
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
+ &break_enable);
+ }
+ }
+ break;
+ case 4:
+ {
+ int flags;
+ int old_mcr = s->mcr;
+ s->mcr = val & 0x1f;
+ if (val & UART_MCR_LOOP)
+ break;
+
+ if (s->poll_msl >= 0 && old_mcr != s->mcr) {
+
+ qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_GET_TIOCM, &flags);
+
+ flags &= ~(CHR_TIOCM_RTS | CHR_TIOCM_DTR);
+
+ if (val & UART_MCR_RTS)
+ flags |= CHR_TIOCM_RTS;
+ if (val & UART_MCR_DTR)
+ flags |= CHR_TIOCM_DTR;
+
+ qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_SET_TIOCM, &flags);
+ /* Update the modem status after a one-character-send wait-time, since there may be a response
+ from the device/computer at the other end of the serial line */
+ qemu_mod_timer(s->modem_status_poll, qemu_get_clock_ns(vm_clock) + s->char_transmit_time);
+ }
+ }
+ break;
+ case 5:
+ break;
+ case 6:
+ break;
+ case 7:
+ s->scr = val;
+ break;
+ }
+}
+
+static uint64_t serial_ioport_read(void *opaque, hwaddr addr, unsigned size)
+{
+ SerialState *s = opaque;
+ uint32_t ret;
+
+ addr &= 7;
+ switch(addr) {
+ default:
+ case 0:
+ if (s->lcr & UART_LCR_DLAB) {
+ ret = s->divider & 0xff;
+ } else {
+ if(s->fcr & UART_FCR_FE) {
+ ret = fifo8_is_empty(&s->recv_fifo) ?
+ 0 : fifo8_pop(&s->recv_fifo);
+ if (s->recv_fifo.num == 0) {
+ s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
+ } else {
+ qemu_mod_timer(s->fifo_timeout_timer, qemu_get_clock_ns (vm_clock) + s->char_transmit_time * 4);
+ }
+ s->timeout_ipending = 0;
+ } else {
+ ret = s->rbr;
+ s->lsr &= ~(UART_LSR_DR | UART_LSR_BI);
+ }
+ serial_update_irq(s);
+ if (!(s->mcr & UART_MCR_LOOP)) {
+ /* in loopback mode, don't receive any data */
+ qemu_chr_accept_input(s->chr);
+ }
+ }
+ break;
+ case 1:
+ if (s->lcr & UART_LCR_DLAB) {
+ ret = (s->divider >> 8) & 0xff;
+ } else {
+ ret = s->ier;
+ }
+ break;
+ case 2:
+ ret = s->iir;
+ if ((ret & UART_IIR_ID) == UART_IIR_THRI) {
+ s->thr_ipending = 0;
+ serial_update_irq(s);
+ }
+ break;
+ case 3:
+ ret = s->lcr;
+ break;
+ case 4:
+ ret = s->mcr;
+ break;
+ case 5:
+ ret = s->lsr;
+ /* Clear break and overrun interrupts */
+ if (s->lsr & (UART_LSR_BI|UART_LSR_OE)) {
+ s->lsr &= ~(UART_LSR_BI|UART_LSR_OE);
+ serial_update_irq(s);
+ }
+ break;
+ case 6:
+ if (s->mcr & UART_MCR_LOOP) {
+ /* in loopback, the modem output pins are connected to the
+ inputs */
+ ret = (s->mcr & 0x0c) << 4;
+ ret |= (s->mcr & 0x02) << 3;
+ ret |= (s->mcr & 0x01) << 5;
+ } else {
+ if (s->poll_msl >= 0)
+ serial_update_msl(s);
+ ret = s->msr;
+ /* Clear delta bits & msr int after read, if they were set */
+ if (s->msr & UART_MSR_ANY_DELTA) {
+ s->msr &= 0xF0;
+ serial_update_irq(s);
+ }
+ }
+ break;
+ case 7:
+ ret = s->scr;
+ break;
+ }
+ DPRINTF("read addr=0x%" HWADDR_PRIx " val=0x%02x\n", addr, ret);
+ return ret;
+}
+
+static int serial_can_receive(SerialState *s)
+{
+ if(s->fcr & UART_FCR_FE) {
+ if (s->recv_fifo.num < UART_FIFO_LENGTH) {
+ /*
+ * Advertise (fifo.itl - fifo.count) bytes when count < ITL, and 1
+ * if above. If UART_FIFO_LENGTH - fifo.count is advertised the
+ * effect will be to almost always fill the fifo completely before
+ * the guest has a chance to respond, effectively overriding the ITL
+ * that the guest has set.
+ */
+ return (s->recv_fifo.num <= s->recv_fifo_itl) ?
+ s->recv_fifo_itl - s->recv_fifo.num : 1;
+ } else {
+ return 0;
+ }
+ } else {
+ return !(s->lsr & UART_LSR_DR);
+ }
+}
+
+static void serial_receive_break(SerialState *s)
+{
+ s->rbr = 0;
+ /* When the LSR_DR is set a null byte is pushed into the fifo */
+ recv_fifo_put(s, '\0');
+ s->lsr |= UART_LSR_BI | UART_LSR_DR;
+ serial_update_irq(s);
+}
+
+/* There's data in recv_fifo and s->rbr has not been read for 4 char transmit times */
+static void fifo_timeout_int (void *opaque) {
+ SerialState *s = opaque;
+ if (s->recv_fifo.num) {
+ s->timeout_ipending = 1;
+ serial_update_irq(s);
+ }
+}
+
+static int serial_can_receive1(void *opaque)
+{
+ SerialState *s = opaque;
+ return serial_can_receive(s);
+}
+
+static void serial_receive1(void *opaque, const uint8_t *buf, int size)
+{
+ SerialState *s = opaque;
+
+ if (s->wakeup) {
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER);
+ }
+ if(s->fcr & UART_FCR_FE) {
+ int i;
+ for (i = 0; i < size; i++) {
+ recv_fifo_put(s, buf[i]);
+ }
+ s->lsr |= UART_LSR_DR;
+ /* call the timeout receive callback in 4 char transmit time */
+ qemu_mod_timer(s->fifo_timeout_timer, qemu_get_clock_ns (vm_clock) + s->char_transmit_time * 4);
+ } else {
+ if (s->lsr & UART_LSR_DR)
+ s->lsr |= UART_LSR_OE;
+ s->rbr = buf[0];
+ s->lsr |= UART_LSR_DR;
+ }
+ serial_update_irq(s);
+}
+
+static void serial_event(void *opaque, int event)
+{
+ SerialState *s = opaque;
+ DPRINTF("event %x\n", event);
+ if (event == CHR_EVENT_BREAK)
+ serial_receive_break(s);
+}
+
+static void serial_pre_save(void *opaque)
+{
+ SerialState *s = opaque;
+ s->fcr_vmstate = s->fcr;
+}
+
+static int serial_post_load(void *opaque, int version_id)
+{
+ SerialState *s = opaque;
+
+ if (version_id < 3) {
+ s->fcr_vmstate = 0;
+ }
+ /* Initialize fcr via setter to perform essential side-effects */
+ serial_ioport_write(s, 0x02, s->fcr_vmstate, 1);
+ serial_update_parameters(s);
+ return 0;
+}
+
+const VMStateDescription vmstate_serial = {
+ .name = "serial",
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .pre_save = serial_pre_save,
+ .post_load = serial_post_load,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT16_V(divider, SerialState, 2),
+ VMSTATE_UINT8(rbr, SerialState),
+ VMSTATE_UINT8(ier, SerialState),
+ VMSTATE_UINT8(iir, SerialState),
+ VMSTATE_UINT8(lcr, SerialState),
+ VMSTATE_UINT8(mcr, SerialState),
+ VMSTATE_UINT8(lsr, SerialState),
+ VMSTATE_UINT8(msr, SerialState),
+ VMSTATE_UINT8(scr, SerialState),
+ VMSTATE_UINT8_V(fcr_vmstate, SerialState, 3),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void serial_reset(void *opaque)
+{
+ SerialState *s = opaque;
+
+ s->rbr = 0;
+ s->ier = 0;
+ s->iir = UART_IIR_NO_INT;
+ s->lcr = 0;
+ s->lsr = UART_LSR_TEMT | UART_LSR_THRE;
+ s->msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
+ /* Default to 9600 baud, 1 start bit, 8 data bits, 1 stop bit, no parity. */
+ s->divider = 0x0C;
+ s->mcr = UART_MCR_OUT2;
+ s->scr = 0;
+ s->tsr_retry = 0;
+ s->char_transmit_time = (get_ticks_per_sec() / 9600) * 10;
+ s->poll_msl = 0;
+
+ fifo8_reset(&s->recv_fifo);
+ fifo8_reset(&s->xmit_fifo);
+
+ s->last_xmit_ts = qemu_get_clock_ns(vm_clock);
+
+ s->thr_ipending = 0;
+ s->last_break_enable = 0;
+ qemu_irq_lower(s->irq);
+}
+
+void serial_realize_core(SerialState *s, Error **errp)
+{
+ if (!s->chr) {
+ error_setg(errp, "Can't create serial device, empty char device");
+ return;
+ }
+
+ s->modem_status_poll = qemu_new_timer_ns(vm_clock, (QEMUTimerCB *) serial_update_msl, s);
+
+ s->fifo_timeout_timer = qemu_new_timer_ns(vm_clock, (QEMUTimerCB *) fifo_timeout_int, s);
+ qemu_register_reset(serial_reset, s);
+
+ qemu_chr_add_handlers(s->chr, serial_can_receive1, serial_receive1,
+ serial_event, s);
+ fifo8_create(&s->recv_fifo, UART_FIFO_LENGTH);
+ fifo8_create(&s->xmit_fifo, UART_FIFO_LENGTH);
+}
+
+void serial_exit_core(SerialState *s)
+{
+ qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, NULL);
+ qemu_unregister_reset(serial_reset, s);
+}
+
+/* Change the main reference oscillator frequency. */
+void serial_set_frequency(SerialState *s, uint32_t frequency)
+{
+ s->baudbase = frequency;
+ serial_update_parameters(s);
+}
+
+const MemoryRegionOps serial_io_ops = {
+ .read = serial_ioport_read,
+ .write = serial_ioport_write,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+SerialState *serial_init(int base, qemu_irq irq, int baudbase,
+ CharDriverState *chr, MemoryRegion *system_io)
+{
+ SerialState *s;
+ Error *err = NULL;
+
+ s = g_malloc0(sizeof(SerialState));
+
+ s->irq = irq;
+ s->baudbase = baudbase;
+ s->chr = chr;
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ fprintf(stderr, "%s\n", error_get_pretty(err));
+ error_free(err);
+ exit(1);
+ }
+
+ vmstate_register(NULL, base, &vmstate_serial, s);
+
+ memory_region_init_io(&s->io, NULL, &serial_io_ops, s, "serial", 8);
+ memory_region_add_subregion(system_io, base, &s->io);
+
+ return s;
+}
+
+/* Memory mapped interface */
+static uint64_t serial_mm_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ SerialState *s = opaque;
+ return serial_ioport_read(s, addr >> s->it_shift, 1);
+}
+
+static void serial_mm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ SerialState *s = opaque;
+ value &= ~0u >> (32 - (size * 8));
+ serial_ioport_write(s, addr >> s->it_shift, value, 1);
+}
+
+static const MemoryRegionOps serial_mm_ops[3] = {
+ [DEVICE_NATIVE_ENDIAN] = {
+ .read = serial_mm_read,
+ .write = serial_mm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ },
+ [DEVICE_LITTLE_ENDIAN] = {
+ .read = serial_mm_read,
+ .write = serial_mm_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ },
+ [DEVICE_BIG_ENDIAN] = {
+ .read = serial_mm_read,
+ .write = serial_mm_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ },
+};
+
+SerialState *serial_mm_init(MemoryRegion *address_space,
+ hwaddr base, int it_shift,
+ qemu_irq irq, int baudbase,
+ CharDriverState *chr, enum device_endian end)
+{
+ SerialState *s;
+ Error *err = NULL;
+
+ s = g_malloc0(sizeof(SerialState));
+
+ s->it_shift = it_shift;
+ s->irq = irq;
+ s->baudbase = baudbase;
+ s->chr = chr;
+
+ serial_realize_core(s, &err);
+ if (err != NULL) {
+ fprintf(stderr, "%s\n", error_get_pretty(err));
+ error_free(err);
+ exit(1);
+ }
+ vmstate_register(NULL, base, &vmstate_serial, s);
+
+ memory_region_init_io(&s->io, NULL, &serial_mm_ops[end], s,
+ "serial", 8 << it_shift);
+ memory_region_add_subregion(address_space, base, &s->io);
+
+ serial_update_msl(s);
+ return s;
+}
diff --git a/hw/char/sh_serial.c b/hw/char/sh_serial.c
new file mode 100644
index 000000000..6223a557b
--- /dev/null
+++ b/hw/char/sh_serial.c
@@ -0,0 +1,410 @@
+/*
+ * QEMU SCI/SCIF serial port emulation
+ *
+ * Copyright (c) 2007 Magnus Damm
+ *
+ * Based on serial.c - QEMU 16450 UART emulation
+ * Copyright (c) 2003-2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "hw/hw.h"
+#include "hw/sh4/sh.h"
+#include "sysemu/char.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_SERIAL
+
+#define SH_SERIAL_FLAG_TEND (1 << 0)
+#define SH_SERIAL_FLAG_TDE (1 << 1)
+#define SH_SERIAL_FLAG_RDF (1 << 2)
+#define SH_SERIAL_FLAG_BRK (1 << 3)
+#define SH_SERIAL_FLAG_DR (1 << 4)
+
+#define SH_RX_FIFO_LENGTH (16)
+
+typedef struct {
+ MemoryRegion iomem;
+ MemoryRegion iomem_p4;
+ MemoryRegion iomem_a7;
+ uint8_t smr;
+ uint8_t brr;
+ uint8_t scr;
+ uint8_t dr; /* ftdr / tdr */
+ uint8_t sr; /* fsr / ssr */
+ uint16_t fcr;
+ uint8_t sptr;
+
+ uint8_t rx_fifo[SH_RX_FIFO_LENGTH]; /* frdr / rdr */
+ uint8_t rx_cnt;
+ uint8_t rx_tail;
+ uint8_t rx_head;
+
+ int freq;
+ int feat;
+ int flags;
+ int rtrg;
+
+ CharDriverState *chr;
+
+ qemu_irq eri;
+ qemu_irq rxi;
+ qemu_irq txi;
+ qemu_irq tei;
+ qemu_irq bri;
+} sh_serial_state;
+
+static void sh_serial_clear_fifo(sh_serial_state * s)
+{
+ memset(s->rx_fifo, 0, SH_RX_FIFO_LENGTH);
+ s->rx_cnt = 0;
+ s->rx_head = 0;
+ s->rx_tail = 0;
+}
+
+static void sh_serial_write(void *opaque, hwaddr offs,
+ uint64_t val, unsigned size)
+{
+ sh_serial_state *s = opaque;
+ unsigned char ch;
+
+#ifdef DEBUG_SERIAL
+ printf("sh_serial: write offs=0x%02x val=0x%02x\n",
+ offs, val);
+#endif
+ switch(offs) {
+ case 0x00: /* SMR */
+ s->smr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0x7b : 0xff);
+ return;
+ case 0x04: /* BRR */
+ s->brr = val;
+ return;
+ case 0x08: /* SCR */
+ /* TODO : For SH7751, SCIF mask should be 0xfb. */
+ s->scr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0xfa : 0xff);
+ if (!(val & (1 << 5)))
+ s->flags |= SH_SERIAL_FLAG_TEND;
+ if ((s->feat & SH_SERIAL_FEAT_SCIF) && s->txi) {
+ qemu_set_irq(s->txi, val & (1 << 7));
+ }
+ if (!(val & (1 << 6))) {
+ qemu_set_irq(s->rxi, 0);
+ }
+ return;
+ case 0x0c: /* FTDR / TDR */
+ if (s->chr) {
+ ch = val;
+ qemu_chr_fe_write(s->chr, &ch, 1);
+ }
+ s->dr = val;
+ s->flags &= ~SH_SERIAL_FLAG_TDE;
+ return;
+#if 0
+ case 0x14: /* FRDR / RDR */
+ ret = 0;
+ break;
+#endif
+ }
+ if (s->feat & SH_SERIAL_FEAT_SCIF) {
+ switch(offs) {
+ case 0x10: /* FSR */
+ if (!(val & (1 << 6)))
+ s->flags &= ~SH_SERIAL_FLAG_TEND;
+ if (!(val & (1 << 5)))
+ s->flags &= ~SH_SERIAL_FLAG_TDE;
+ if (!(val & (1 << 4)))
+ s->flags &= ~SH_SERIAL_FLAG_BRK;
+ if (!(val & (1 << 1)))
+ s->flags &= ~SH_SERIAL_FLAG_RDF;
+ if (!(val & (1 << 0)))
+ s->flags &= ~SH_SERIAL_FLAG_DR;
+
+ if (!(val & (1 << 1)) || !(val & (1 << 0))) {
+ if (s->rxi) {
+ qemu_set_irq(s->rxi, 0);
+ }
+ }
+ return;
+ case 0x18: /* FCR */
+ s->fcr = val;
+ switch ((val >> 6) & 3) {
+ case 0:
+ s->rtrg = 1;
+ break;
+ case 1:
+ s->rtrg = 4;
+ break;
+ case 2:
+ s->rtrg = 8;
+ break;
+ case 3:
+ s->rtrg = 14;
+ break;
+ }
+ if (val & (1 << 1)) {
+ sh_serial_clear_fifo(s);
+ s->sr &= ~(1 << 1);
+ }
+
+ return;
+ case 0x20: /* SPTR */
+ s->sptr = val & 0xf3;
+ return;
+ case 0x24: /* LSR */
+ return;
+ }
+ }
+ else {
+ switch(offs) {
+#if 0
+ case 0x0c:
+ ret = s->dr;
+ break;
+ case 0x10:
+ ret = 0;
+ break;
+#endif
+ case 0x1c:
+ s->sptr = val & 0x8f;
+ return;
+ }
+ }
+
+ fprintf(stderr, "sh_serial: unsupported write to 0x%02"
+ HWADDR_PRIx "\n", offs);
+ abort();
+}
+
+static uint64_t sh_serial_read(void *opaque, hwaddr offs,
+ unsigned size)
+{
+ sh_serial_state *s = opaque;
+ uint32_t ret = ~0;
+
+#if 0
+ switch(offs) {
+ case 0x00:
+ ret = s->smr;
+ break;
+ case 0x04:
+ ret = s->brr;
+ break;
+ case 0x08:
+ ret = s->scr;
+ break;
+ case 0x14:
+ ret = 0;
+ break;
+ }
+#endif
+ if (s->feat & SH_SERIAL_FEAT_SCIF) {
+ switch(offs) {
+ case 0x00: /* SMR */
+ ret = s->smr;
+ break;
+ case 0x08: /* SCR */
+ ret = s->scr;
+ break;
+ case 0x10: /* FSR */
+ ret = 0;
+ if (s->flags & SH_SERIAL_FLAG_TEND)
+ ret |= (1 << 6);
+ if (s->flags & SH_SERIAL_FLAG_TDE)
+ ret |= (1 << 5);
+ if (s->flags & SH_SERIAL_FLAG_BRK)
+ ret |= (1 << 4);
+ if (s->flags & SH_SERIAL_FLAG_RDF)
+ ret |= (1 << 1);
+ if (s->flags & SH_SERIAL_FLAG_DR)
+ ret |= (1 << 0);
+
+ if (s->scr & (1 << 5))
+ s->flags |= SH_SERIAL_FLAG_TDE | SH_SERIAL_FLAG_TEND;
+
+ break;
+ case 0x14:
+ if (s->rx_cnt > 0) {
+ ret = s->rx_fifo[s->rx_tail++];
+ s->rx_cnt--;
+ if (s->rx_tail == SH_RX_FIFO_LENGTH)
+ s->rx_tail = 0;
+ if (s->rx_cnt < s->rtrg)
+ s->flags &= ~SH_SERIAL_FLAG_RDF;
+ }
+ break;
+#if 0
+ case 0x18:
+ ret = s->fcr;
+ break;
+#endif
+ case 0x1c:
+ ret = s->rx_cnt;
+ break;
+ case 0x20:
+ ret = s->sptr;
+ break;
+ case 0x24:
+ ret = 0;
+ break;
+ }
+ }
+ else {
+ switch(offs) {
+#if 0
+ case 0x0c:
+ ret = s->dr;
+ break;
+ case 0x10:
+ ret = 0;
+ break;
+ case 0x14:
+ ret = s->rx_fifo[0];
+ break;
+#endif
+ case 0x1c:
+ ret = s->sptr;
+ break;
+ }
+ }
+#ifdef DEBUG_SERIAL
+ printf("sh_serial: read offs=0x%02x val=0x%x\n",
+ offs, ret);
+#endif
+
+ if (ret & ~((1 << 16) - 1)) {
+ fprintf(stderr, "sh_serial: unsupported read from 0x%02"
+ HWADDR_PRIx "\n", offs);
+ abort();
+ }
+
+ return ret;
+}
+
+static int sh_serial_can_receive(sh_serial_state *s)
+{
+ return s->scr & (1 << 4);
+}
+
+static void sh_serial_receive_break(sh_serial_state *s)
+{
+ if (s->feat & SH_SERIAL_FEAT_SCIF)
+ s->sr |= (1 << 4);
+}
+
+static int sh_serial_can_receive1(void *opaque)
+{
+ sh_serial_state *s = opaque;
+ return sh_serial_can_receive(s);
+}
+
+static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size)
+{
+ sh_serial_state *s = opaque;
+
+ if (s->feat & SH_SERIAL_FEAT_SCIF) {
+ int i;
+ for (i = 0; i < size; i++) {
+ if (s->rx_cnt < SH_RX_FIFO_LENGTH) {
+ s->rx_fifo[s->rx_head++] = buf[i];
+ if (s->rx_head == SH_RX_FIFO_LENGTH) {
+ s->rx_head = 0;
+ }
+ s->rx_cnt++;
+ if (s->rx_cnt >= s->rtrg) {
+ s->flags |= SH_SERIAL_FLAG_RDF;
+ if (s->scr & (1 << 6) && s->rxi) {
+ qemu_set_irq(s->rxi, 1);
+ }
+ }
+ }
+ }
+ } else {
+ s->rx_fifo[0] = buf[0];
+ }
+}
+
+static void sh_serial_event(void *opaque, int event)
+{
+ sh_serial_state *s = opaque;
+ if (event == CHR_EVENT_BREAK)
+ sh_serial_receive_break(s);
+}
+
+static const MemoryRegionOps sh_serial_ops = {
+ .read = sh_serial_read,
+ .write = sh_serial_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+void sh_serial_init(MemoryRegion *sysmem,
+ hwaddr base, int feat,
+ uint32_t freq, CharDriverState *chr,
+ qemu_irq eri_source,
+ qemu_irq rxi_source,
+ qemu_irq txi_source,
+ qemu_irq tei_source,
+ qemu_irq bri_source)
+{
+ sh_serial_state *s;
+
+ s = g_malloc0(sizeof(sh_serial_state));
+
+ s->feat = feat;
+ s->flags = SH_SERIAL_FLAG_TEND | SH_SERIAL_FLAG_TDE;
+ s->rtrg = 1;
+
+ s->smr = 0;
+ s->brr = 0xff;
+ s->scr = 1 << 5; /* pretend that TX is enabled so early printk works */
+ s->sptr = 0;
+
+ if (feat & SH_SERIAL_FEAT_SCIF) {
+ s->fcr = 0;
+ }
+ else {
+ s->dr = 0xff;
+ }
+
+ sh_serial_clear_fifo(s);
+
+ memory_region_init_io(&s->iomem, NULL, &sh_serial_ops, s,
+ "serial", 0x100000000ULL);
+
+ memory_region_init_alias(&s->iomem_p4, NULL, "serial-p4", &s->iomem,
+ 0, 0x28);
+ memory_region_add_subregion(sysmem, P4ADDR(base), &s->iomem_p4);
+
+ memory_region_init_alias(&s->iomem_a7, NULL, "serial-a7", &s->iomem,
+ 0, 0x28);
+ memory_region_add_subregion(sysmem, A7ADDR(base), &s->iomem_a7);
+
+ s->chr = chr;
+
+ if (chr) {
+ qemu_chr_fe_claim_no_fail(chr);
+ qemu_chr_add_handlers(chr, sh_serial_can_receive1, sh_serial_receive1,
+ sh_serial_event, s);
+ }
+
+ s->eri = eri_source;
+ s->rxi = rxi_source;
+ s->txi = txi_source;
+ s->tei = tei_source;
+ s->bri = bri_source;
+}
diff --git a/hw/char/spapr_vty.c b/hw/char/spapr_vty.c
new file mode 100644
index 000000000..a7997213b
--- /dev/null
+++ b/hw/char/spapr_vty.c
@@ -0,0 +1,241 @@
+#include "hw/qdev.h"
+#include "sysemu/char.h"
+#include "hw/ppc/spapr.h"
+#include "hw/ppc/spapr_vio.h"
+
+#define VTERM_BUFSIZE 16
+
+typedef struct VIOsPAPRVTYDevice {
+ VIOsPAPRDevice sdev;
+ CharDriverState *chardev;
+ uint32_t in, out;
+ uint8_t buf[VTERM_BUFSIZE];
+} VIOsPAPRVTYDevice;
+
+#define TYPE_VIO_SPAPR_VTY_DEVICE "spapr-vty"
+#define VIO_SPAPR_VTY_DEVICE(obj) \
+ OBJECT_CHECK(VIOsPAPRVTYDevice, (obj), TYPE_VIO_SPAPR_VTY_DEVICE)
+
+static int vty_can_receive(void *opaque)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(opaque);
+
+ return (dev->in - dev->out) < VTERM_BUFSIZE;
+}
+
+static void vty_receive(void *opaque, const uint8_t *buf, int size)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(opaque);
+ int i;
+
+ if ((dev->in == dev->out) && size) {
+ /* toggle line to simulate edge interrupt */
+ qemu_irq_pulse(spapr_vio_qirq(&dev->sdev));
+ }
+ for (i = 0; i < size; i++) {
+ assert((dev->in - dev->out) < VTERM_BUFSIZE);
+ dev->buf[dev->in++ % VTERM_BUFSIZE] = buf[i];
+ }
+}
+
+static int vty_getchars(VIOsPAPRDevice *sdev, uint8_t *buf, int max)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(sdev);
+ int n = 0;
+
+ while ((n < max) && (dev->out != dev->in)) {
+ buf[n++] = dev->buf[dev->out++ % VTERM_BUFSIZE];
+ }
+
+ return n;
+}
+
+void vty_putchars(VIOsPAPRDevice *sdev, uint8_t *buf, int len)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(sdev);
+
+ /* FIXME: should check the qemu_chr_fe_write() return value */
+ qemu_chr_fe_write(dev->chardev, buf, len);
+}
+
+static int spapr_vty_init(VIOsPAPRDevice *sdev)
+{
+ VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(sdev);
+
+ if (!dev->chardev) {
+ fprintf(stderr, "spapr-vty: Can't create vty without a chardev!\n");
+ exit(1);
+ }
+
+ qemu_chr_add_handlers(dev->chardev, vty_can_receive,
+ vty_receive, NULL, dev);
+
+ return 0;
+}
+
+/* Forward declaration */
+static target_ulong h_put_term_char(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong len = args[1];
+ target_ulong char0_7 = args[2];
+ target_ulong char8_15 = args[3];
+ VIOsPAPRDevice *sdev;
+ uint8_t buf[16];
+
+ sdev = vty_lookup(spapr, reg);
+ if (!sdev) {
+ return H_PARAMETER;
+ }
+
+ if (len > 16) {
+ return H_PARAMETER;
+ }
+
+ *((uint64_t *)buf) = cpu_to_be64(char0_7);
+ *((uint64_t *)buf + 1) = cpu_to_be64(char8_15);
+
+ vty_putchars(sdev, buf, len);
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_get_term_char(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong *len = args + 0;
+ target_ulong *char0_7 = args + 1;
+ target_ulong *char8_15 = args + 2;
+ VIOsPAPRDevice *sdev;
+ uint8_t buf[16];
+
+ sdev = vty_lookup(spapr, reg);
+ if (!sdev) {
+ return H_PARAMETER;
+ }
+
+ *len = vty_getchars(sdev, buf, sizeof(buf));
+ if (*len < 16) {
+ memset(buf + *len, 0, 16 - *len);
+ }
+
+ *char0_7 = be64_to_cpu(*((uint64_t *)buf));
+ *char8_15 = be64_to_cpu(*((uint64_t *)buf + 1));
+
+ return H_SUCCESS;
+}
+
+void spapr_vty_create(VIOsPAPRBus *bus, CharDriverState *chardev)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->bus, "spapr-vty");
+ qdev_prop_set_chr(dev, "chardev", chardev);
+ qdev_init_nofail(dev);
+}
+
+static Property spapr_vty_properties[] = {
+ DEFINE_SPAPR_PROPERTIES(VIOsPAPRVTYDevice, sdev),
+ DEFINE_PROP_CHR("chardev", VIOsPAPRVTYDevice, chardev),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_spapr_vty = {
+ .name = "spapr_vty",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField []) {
+ VMSTATE_SPAPR_VIO(sdev, VIOsPAPRVTYDevice),
+
+ VMSTATE_UINT32(in, VIOsPAPRVTYDevice),
+ VMSTATE_UINT32(out, VIOsPAPRVTYDevice),
+ VMSTATE_BUFFER(buf, VIOsPAPRVTYDevice),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void spapr_vty_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VIOsPAPRDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass);
+
+ k->init = spapr_vty_init;
+ k->dt_name = "vty";
+ k->dt_type = "serial";
+ k->dt_compatible = "hvterm1";
+ dc->props = spapr_vty_properties;
+ dc->vmsd = &vmstate_spapr_vty;
+}
+
+static const TypeInfo spapr_vty_info = {
+ .name = TYPE_VIO_SPAPR_VTY_DEVICE,
+ .parent = TYPE_VIO_SPAPR_DEVICE,
+ .instance_size = sizeof(VIOsPAPRVTYDevice),
+ .class_init = spapr_vty_class_init,
+};
+
+VIOsPAPRDevice *spapr_vty_get_default(VIOsPAPRBus *bus)
+{
+ VIOsPAPRDevice *sdev, *selected;
+ BusChild *kid;
+
+ /*
+ * To avoid the console bouncing around we want one VTY to be
+ * the "default". We haven't really got anything to go on, so
+ * arbitrarily choose the one with the lowest reg value.
+ */
+
+ selected = NULL;
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ DeviceState *iter = kid->child;
+
+ /* Only look at VTY devices */
+ if (!object_dynamic_cast(OBJECT(iter), "spapr-vty")) {
+ continue;
+ }
+
+ sdev = VIO_SPAPR_DEVICE(iter);
+
+ /* First VTY we've found, so it is selected for now */
+ if (!selected) {
+ selected = sdev;
+ continue;
+ }
+
+ /* Choose VTY with lowest reg value */
+ if (sdev->reg < selected->reg) {
+ selected = sdev;
+ }
+ }
+
+ return selected;
+}
+
+VIOsPAPRDevice *vty_lookup(sPAPREnvironment *spapr, target_ulong reg)
+{
+ VIOsPAPRDevice *sdev;
+
+ sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ if (!sdev && reg == 0) {
+ /* Hack for kernel early debug, which always specifies reg==0.
+ * We search all VIO devices, and grab the vty with the lowest
+ * reg. This attempts to mimic existing PowerVM behaviour
+ * (early debug does work there, despite having no vty with
+ * reg==0. */
+ return spapr_vty_get_default(spapr->vio_bus);
+ }
+
+ return sdev;
+}
+
+static void spapr_vty_register_types(void)
+{
+ spapr_register_hypercall(H_PUT_TERM_CHAR, h_put_term_char);
+ spapr_register_hypercall(H_GET_TERM_CHAR, h_get_term_char);
+ type_register_static(&spapr_vty_info);
+}
+
+type_init(spapr_vty_register_types)
diff --git a/hw/char/tpci200.c b/hw/char/tpci200.c
new file mode 100644
index 000000000..d9e17b295
--- /dev/null
+++ b/hw/char/tpci200.c
@@ -0,0 +1,672 @@
+/*
+ * QEMU TEWS TPCI200 IndustryPack carrier emulation
+ *
+ * Copyright (C) 2012 Igalia, S.L.
+ * Author: Alberto Garcia <agarcia@igalia.com>
+ *
+ * This code is licensed under the GNU GPL v2 or (at your option) any
+ * later version.
+ */
+
+#include "ipack.h"
+#include "hw/pci/pci.h"
+#include "qemu/bitops.h"
+#include <stdio.h>
+
+/* #define DEBUG_TPCI */
+
+#ifdef DEBUG_TPCI
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stderr, "TPCI200: " fmt, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do { } while (0)
+#endif
+
+#define N_MODULES 4
+
+#define IP_ID_SPACE 2
+#define IP_INT_SPACE 3
+#define IP_IO_SPACE_ADDR_MASK 0x7F
+#define IP_ID_SPACE_ADDR_MASK 0x3F
+#define IP_INT_SPACE_ADDR_MASK 0x3F
+
+#define STATUS_INT(IP, INTNO) BIT((IP) * 2 + (INTNO))
+#define STATUS_TIME(IP) BIT((IP) + 12)
+#define STATUS_ERR_ANY 0xF00
+
+#define CTRL_CLKRATE BIT(0)
+#define CTRL_RECOVER BIT(1)
+#define CTRL_TIME_INT BIT(2)
+#define CTRL_ERR_INT BIT(3)
+#define CTRL_INT_EDGE(INTNO) BIT(4 + (INTNO))
+#define CTRL_INT(INTNO) BIT(6 + (INTNO))
+
+#define REG_REV_ID 0x00
+#define REG_IP_A_CTRL 0x02
+#define REG_IP_B_CTRL 0x04
+#define REG_IP_C_CTRL 0x06
+#define REG_IP_D_CTRL 0x08
+#define REG_RESET 0x0A
+#define REG_STATUS 0x0C
+#define IP_N_FROM_REG(REG) ((REG) / 2 - 1)
+
+typedef struct {
+ PCIDevice dev;
+ IPackBus bus;
+ MemoryRegion mmio;
+ MemoryRegion io;
+ MemoryRegion las0;
+ MemoryRegion las1;
+ MemoryRegion las2;
+ MemoryRegion las3;
+ bool big_endian[3];
+ uint8_t ctrl[N_MODULES];
+ uint16_t status;
+ uint8_t int_set;
+} TPCI200State;
+
+#define TYPE_TPCI200 "tpci200"
+
+#define TPCI200(obj) \
+ OBJECT_CHECK(TPCI200State, (obj), TYPE_TPCI200)
+
+static const uint8_t local_config_regs[] = {
+ 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xFC, 0xFF, 0x0F, 0x00, 0x00, 0x00,
+ 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x08, 0x01, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x60, 0x41, 0xD4,
+ 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x01,
+ 0x14, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x08, 0x01, 0x02,
+ 0x00, 0x04, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x80, 0x02, 0x41,
+ 0x00, 0x00, 0x00, 0x00, 0x40, 0x7A, 0x00, 0x52, 0x92, 0x24, 0x02
+};
+
+static void adjust_addr(bool big_endian, hwaddr *addr, unsigned size)
+{
+ /* During 8 bit access in big endian mode,
+ odd and even addresses are swapped */
+ if (big_endian && size == 1) {
+ *addr ^= 1;
+ }
+}
+
+static uint64_t adjust_value(bool big_endian, uint64_t *val, unsigned size)
+{
+ /* Local spaces only support 8/16 bit access,
+ * so there's no need to care for sizes > 2 */
+ if (big_endian && size == 2) {
+ *val = bswap16(*val);
+ }
+ return *val;
+}
+
+static void tpci200_set_irq(void *opaque, int intno, int level)
+{
+ IPackDevice *ip = opaque;
+ IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(DEVICE(ip)));
+ PCIDevice *pcidev = PCI_DEVICE(BUS(bus)->parent);
+ TPCI200State *dev = TPCI200(pcidev);
+ unsigned ip_n = ip->slot;
+ uint16_t prev_status = dev->status;
+
+ assert(ip->slot >= 0 && ip->slot < N_MODULES);
+
+ /* The requested interrupt must be enabled in the IP CONTROL
+ * register */
+ if (!(dev->ctrl[ip_n] & CTRL_INT(intno))) {
+ return;
+ }
+
+ /* Update the interrupt status in the IP STATUS register */
+ if (level) {
+ dev->status |= STATUS_INT(ip_n, intno);
+ } else {
+ dev->status &= ~STATUS_INT(ip_n, intno);
+ }
+
+ /* Return if there are no changes */
+ if (dev->status == prev_status) {
+ return;
+ }
+
+ DPRINTF("IP %u INT%u#: %u\n", ip_n, intno, level);
+
+ /* Check if the interrupt is edge sensitive */
+ if (dev->ctrl[ip_n] & CTRL_INT_EDGE(intno)) {
+ if (level) {
+ qemu_set_irq(dev->dev.irq[0], !dev->int_set);
+ qemu_set_irq(dev->dev.irq[0], dev->int_set);
+ }
+ } else {
+ unsigned i, j;
+ uint16_t level_status = dev->status;
+
+ /* Check if there are any level sensitive interrupts set by
+ removing the ones that are edge sensitive from the status
+ register */
+ for (i = 0; i < N_MODULES; i++) {
+ for (j = 0; j < 2; j++) {
+ if (dev->ctrl[i] & CTRL_INT_EDGE(j)) {
+ level_status &= ~STATUS_INT(i, j);
+ }
+ }
+ }
+
+ if (level_status && !dev->int_set) {
+ qemu_irq_raise(dev->dev.irq[0]);
+ dev->int_set = 1;
+ } else if (!level_status && dev->int_set) {
+ qemu_irq_lower(dev->dev.irq[0]);
+ dev->int_set = 0;
+ }
+ }
+}
+
+static uint64_t tpci200_read_cfg(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ uint8_t ret = 0;
+ if (addr < ARRAY_SIZE(local_config_regs)) {
+ ret = local_config_regs[addr];
+ }
+ /* Endianness is stored in the first bit of these registers */
+ if ((addr == 0x2b && s->big_endian[0]) ||
+ (addr == 0x2f && s->big_endian[1]) ||
+ (addr == 0x33 && s->big_endian[2])) {
+ ret |= 1;
+ }
+ DPRINTF("Read from LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) ret);
+ return ret;
+}
+
+static void tpci200_write_cfg(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ /* Endianness is stored in the first bit of these registers */
+ if (addr == 0x2b || addr == 0x2f || addr == 0x33) {
+ unsigned las = (addr - 0x2b) / 4;
+ s->big_endian[las] = val & 1;
+ DPRINTF("LAS%u big endian mode: %u\n", las, (unsigned) val & 1);
+ } else {
+ DPRINTF("Write to LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) val);
+ }
+}
+
+static uint64_t tpci200_read_las0(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ uint64_t ret = 0;
+
+ switch (addr) {
+
+ case REG_REV_ID:
+ DPRINTF("Read REVISION ID\n"); /* Current value is 0x00 */
+ break;
+
+ case REG_IP_A_CTRL:
+ case REG_IP_B_CTRL:
+ case REG_IP_C_CTRL:
+ case REG_IP_D_CTRL:
+ {
+ unsigned ip_n = IP_N_FROM_REG(addr);
+ ret = s->ctrl[ip_n];
+ DPRINTF("Read IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) ret);
+ }
+ break;
+
+ case REG_RESET:
+ DPRINTF("Read RESET\n"); /* Not implemented */
+ break;
+
+ case REG_STATUS:
+ ret = s->status;
+ DPRINTF("Read STATUS: 0x%x\n", (unsigned) ret);
+ break;
+
+ /* Reserved */
+ default:
+ DPRINTF("Unsupported read from LAS0 0x%x\n", (unsigned) addr);
+ break;
+ }
+
+ return adjust_value(s->big_endian[0], &ret, size);
+}
+
+static void tpci200_write_las0(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+
+ adjust_value(s->big_endian[0], &val, size);
+
+ switch (addr) {
+
+ case REG_REV_ID:
+ DPRINTF("Write Revision ID: 0x%x\n", (unsigned) val); /* No effect */
+ break;
+
+ case REG_IP_A_CTRL:
+ case REG_IP_B_CTRL:
+ case REG_IP_C_CTRL:
+ case REG_IP_D_CTRL:
+ {
+ unsigned ip_n = IP_N_FROM_REG(addr);
+ s->ctrl[ip_n] = val;
+ DPRINTF("Write IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) val);
+ }
+ break;
+
+ case REG_RESET:
+ DPRINTF("Write RESET: 0x%x\n", (unsigned) val); /* Not implemented */
+ break;
+
+ case REG_STATUS:
+ {
+ unsigned i;
+
+ for (i = 0; i < N_MODULES; i++) {
+ IPackDevice *ip = ipack_device_find(&s->bus, i);
+
+ if (ip != NULL) {
+ if (val & STATUS_INT(i, 0)) {
+ DPRINTF("Clear IP %c INT0# status\n", 'A' + i);
+ qemu_irq_lower(ip->irq[0]);
+ }
+ if (val & STATUS_INT(i, 1)) {
+ DPRINTF("Clear IP %c INT1# status\n", 'A' + i);
+ qemu_irq_lower(ip->irq[1]);
+ }
+ }
+
+ if (val & STATUS_TIME(i)) {
+ DPRINTF("Clear IP %c timeout\n", 'A' + i);
+ s->status &= ~STATUS_TIME(i);
+ }
+ }
+
+ if (val & STATUS_ERR_ANY) {
+ DPRINTF("Unexpected write to STATUS register: 0x%x\n",
+ (unsigned) val);
+ }
+ }
+ break;
+
+ /* Reserved */
+ default:
+ DPRINTF("Unsupported write to LAS0 0x%x: 0x%x\n",
+ (unsigned) addr, (unsigned) val);
+ break;
+ }
+}
+
+static uint64_t tpci200_read_las1(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ uint64_t ret = 0;
+ unsigned ip_n, space;
+ uint8_t offset;
+
+ adjust_addr(s->big_endian[1], &addr, size);
+
+ /*
+ * The address is divided into the IP module number (0-4), the IP
+ * address space (I/O, ID, INT) and the offset within that space.
+ */
+ ip_n = addr >> 8;
+ space = (addr >> 6) & 3;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Read LAS1: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ switch (space) {
+
+ case IP_ID_SPACE:
+ offset = addr & IP_ID_SPACE_ADDR_MASK;
+ if (k->id_read) {
+ ret = k->id_read(ip, offset);
+ }
+ break;
+
+ case IP_INT_SPACE:
+ offset = addr & IP_INT_SPACE_ADDR_MASK;
+
+ /* Read address 0 to ACK IP INT0# and address 2 to ACK IP INT1# */
+ if (offset == 0 || offset == 2) {
+ unsigned intno = offset / 2;
+ bool int_set = s->status & STATUS_INT(ip_n, intno);
+ bool int_edge_sensitive = s->ctrl[ip_n] & CTRL_INT_EDGE(intno);
+ if (int_set && !int_edge_sensitive) {
+ qemu_irq_lower(ip->irq[intno]);
+ }
+ }
+
+ if (k->int_read) {
+ ret = k->int_read(ip, offset);
+ }
+ break;
+
+ default:
+ offset = addr & IP_IO_SPACE_ADDR_MASK;
+ if (k->io_read) {
+ ret = k->io_read(ip, offset);
+ }
+ break;
+ }
+ }
+
+ return adjust_value(s->big_endian[1], &ret, size);
+}
+
+static void tpci200_write_las1(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ unsigned ip_n, space;
+ uint8_t offset;
+
+ adjust_addr(s->big_endian[1], &addr, size);
+ adjust_value(s->big_endian[1], &val, size);
+
+ /*
+ * The address is divided into the IP module number, the IP
+ * address space (I/O, ID, INT) and the offset within that space.
+ */
+ ip_n = addr >> 8;
+ space = (addr >> 6) & 3;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Write LAS1: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ switch (space) {
+
+ case IP_ID_SPACE:
+ offset = addr & IP_ID_SPACE_ADDR_MASK;
+ if (k->id_write) {
+ k->id_write(ip, offset, val);
+ }
+ break;
+
+ case IP_INT_SPACE:
+ offset = addr & IP_INT_SPACE_ADDR_MASK;
+ if (k->int_write) {
+ k->int_write(ip, offset, val);
+ }
+ break;
+
+ default:
+ offset = addr & IP_IO_SPACE_ADDR_MASK;
+ if (k->io_write) {
+ k->io_write(ip, offset, val);
+ }
+ break;
+ }
+ }
+}
+
+static uint64_t tpci200_read_las2(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ uint64_t ret = 0;
+ unsigned ip_n;
+ uint32_t offset;
+
+ adjust_addr(s->big_endian[2], &addr, size);
+
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ ip_n = addr >> 23;
+ offset = addr & 0x7fffff;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Read LAS2: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_read16) {
+ ret = k->mem_read16(ip, offset);
+ }
+ }
+
+ return adjust_value(s->big_endian[2], &ret, size);
+}
+
+static void tpci200_write_las2(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ unsigned ip_n;
+ uint32_t offset;
+
+ adjust_addr(s->big_endian[2], &addr, size);
+ adjust_value(s->big_endian[2], &val, size);
+
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ ip_n = addr >> 23;
+ offset = addr & 0x7fffff;
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Write LAS2: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_write16) {
+ k->mem_write16(ip, offset, val);
+ }
+ }
+}
+
+static uint64_t tpci200_read_las3(void *opaque, hwaddr addr, unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ uint64_t ret = 0;
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ unsigned ip_n = addr >> 22;
+ uint32_t offset = addr & 0x3fffff;
+
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Read LAS3: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_read8) {
+ ret = k->mem_read8(ip, offset);
+ }
+ }
+
+ return ret;
+}
+
+static void tpci200_write_las3(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ TPCI200State *s = opaque;
+ IPackDevice *ip;
+ /*
+ * The address is divided into the IP module number and the offset
+ * within the IP module MEM space.
+ */
+ unsigned ip_n = addr >> 22;
+ uint32_t offset = addr & 0x3fffff;
+
+ ip = ipack_device_find(&s->bus, ip_n);
+
+ if (ip == NULL) {
+ DPRINTF("Write LAS3: IP module %u not installed\n", ip_n);
+ } else {
+ IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip);
+ if (k->mem_write8) {
+ k->mem_write8(ip, offset, val);
+ }
+ }
+}
+
+static const MemoryRegionOps tpci200_cfg_ops = {
+ .read = tpci200_read_cfg,
+ .write = tpci200_write_cfg,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4
+ },
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1
+ }
+};
+
+static const MemoryRegionOps tpci200_las0_ops = {
+ .read = tpci200_read_las0,
+ .write = tpci200_write_las0,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 2,
+ .max_access_size = 2
+ }
+};
+
+static const MemoryRegionOps tpci200_las1_ops = {
+ .read = tpci200_read_las1,
+ .write = tpci200_write_las1,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 2
+ }
+};
+
+static const MemoryRegionOps tpci200_las2_ops = {
+ .read = tpci200_read_las2,
+ .write = tpci200_write_las2,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 2
+ }
+};
+
+static const MemoryRegionOps tpci200_las3_ops = {
+ .read = tpci200_read_las3,
+ .write = tpci200_write_las3,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1
+ }
+};
+
+static int tpci200_initfn(PCIDevice *pci_dev)
+{
+ TPCI200State *s = TPCI200(pci_dev);
+ uint8_t *c = s->dev.config;
+
+ pci_set_word(c + PCI_COMMAND, 0x0003);
+ pci_set_word(c + PCI_STATUS, 0x0280);
+
+ pci_set_byte(c + PCI_INTERRUPT_PIN, 0x01); /* Interrupt pin A */
+
+ pci_set_byte(c + PCI_CAPABILITY_LIST, 0x40);
+ pci_set_long(c + 0x40, 0x48014801);
+ pci_set_long(c + 0x48, 0x00024C06);
+ pci_set_long(c + 0x4C, 0x00000003);
+
+ memory_region_init_io(&s->mmio, OBJECT(s), &tpci200_cfg_ops,
+ s, "tpci200_mmio", 128);
+ memory_region_init_io(&s->io, OBJECT(s), &tpci200_cfg_ops,
+ s, "tpci200_io", 128);
+ memory_region_init_io(&s->las0, OBJECT(s), &tpci200_las0_ops,
+ s, "tpci200_las0", 256);
+ memory_region_init_io(&s->las1, OBJECT(s), &tpci200_las1_ops,
+ s, "tpci200_las1", 1024);
+ memory_region_init_io(&s->las2, OBJECT(s), &tpci200_las2_ops,
+ s, "tpci200_las2", 1024*1024*32);
+ memory_region_init_io(&s->las3, OBJECT(s), &tpci200_las3_ops,
+ s, "tpci200_las3", 1024*1024*16);
+ pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio);
+ pci_register_bar(&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io);
+ pci_register_bar(&s->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las0);
+ pci_register_bar(&s->dev, 3, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las1);
+ pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las2);
+ pci_register_bar(&s->dev, 5, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las3);
+
+ ipack_bus_new_inplace(&s->bus, DEVICE(&s->dev), NULL,
+ N_MODULES, tpci200_set_irq);
+
+ return 0;
+}
+
+static void tpci200_exitfn(PCIDevice *pci_dev)
+{
+ TPCI200State *s = TPCI200(pci_dev);
+
+ memory_region_destroy(&s->mmio);
+ memory_region_destroy(&s->io);
+ memory_region_destroy(&s->las0);
+ memory_region_destroy(&s->las1);
+ memory_region_destroy(&s->las2);
+ memory_region_destroy(&s->las3);
+}
+
+static const VMStateDescription vmstate_tpci200 = {
+ .name = "tpci200",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, TPCI200State),
+ VMSTATE_BOOL_ARRAY(big_endian, TPCI200State, 3),
+ VMSTATE_UINT8_ARRAY(ctrl, TPCI200State, N_MODULES),
+ VMSTATE_UINT16(status, TPCI200State),
+ VMSTATE_UINT8(int_set, TPCI200State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void tpci200_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->init = tpci200_initfn;
+ k->exit = tpci200_exitfn;
+ k->vendor_id = PCI_VENDOR_ID_TEWS;
+ k->device_id = PCI_DEVICE_ID_TEWS_TPCI200;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+ k->subsystem_vendor_id = PCI_VENDOR_ID_TEWS;
+ k->subsystem_id = 0x300A;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ dc->desc = "TEWS TPCI200 IndustryPack carrier";
+ dc->vmsd = &vmstate_tpci200;
+}
+
+static const TypeInfo tpci200_info = {
+ .name = TYPE_TPCI200,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(TPCI200State),
+ .class_init = tpci200_class_init,
+};
+
+static void tpci200_register_types(void)
+{
+ type_register_static(&tpci200_info);
+}
+
+type_init(tpci200_register_types)
diff --git a/hw/char/virtio-console.c b/hw/char/virtio-console.c
new file mode 100644
index 000000000..2e00ad2a7
--- /dev/null
+++ b/hw/char/virtio-console.c
@@ -0,0 +1,207 @@
+/*
+ * Virtio Console and Generic Serial Port Devices
+ *
+ * Copyright Red Hat, Inc. 2009, 2010
+ *
+ * Authors:
+ * Amit Shah <amit.shah@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "sysemu/char.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+#include "hw/virtio/virtio-serial.h"
+
+typedef struct VirtConsole {
+ VirtIOSerialPort port;
+ CharDriverState *chr;
+ guint watch;
+} VirtConsole;
+
+/*
+ * Callback function that's called from chardevs when backend becomes
+ * writable.
+ */
+static gboolean chr_write_unblocked(GIOChannel *chan, GIOCondition cond,
+ void *opaque)
+{
+ VirtConsole *vcon = opaque;
+
+ vcon->watch = 0;
+ virtio_serial_throttle_port(&vcon->port, false);
+ return FALSE;
+}
+
+/* Callback function that's called when the guest sends us data */
+static ssize_t flush_buf(VirtIOSerialPort *port,
+ const uint8_t *buf, ssize_t len)
+{
+ VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port);
+ ssize_t ret;
+
+ if (!vcon->chr) {
+ /* If there's no backend, we can just say we consumed all data. */
+ return len;
+ }
+
+ ret = qemu_chr_fe_write(vcon->chr, buf, len);
+ trace_virtio_console_flush_buf(port->id, len, ret);
+
+ if (ret < len) {
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ /*
+ * Ideally we'd get a better error code than just -1, but
+ * that's what the chardev interface gives us right now. If
+ * we had a finer-grained message, like -EPIPE, we could close
+ * this connection.
+ */
+ if (ret < 0)
+ ret = 0;
+ if (!k->is_console) {
+ virtio_serial_throttle_port(port, true);
+ if (!vcon->watch) {
+ vcon->watch = qemu_chr_fe_add_watch(vcon->chr, G_IO_OUT,
+ chr_write_unblocked, vcon);
+ }
+ }
+ }
+ return ret;
+}
+
+/* Callback function that's called when the guest opens/closes the port */
+static void set_guest_connected(VirtIOSerialPort *port, int guest_connected)
+{
+ VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port);
+
+ if (!vcon->chr) {
+ return;
+ }
+ qemu_chr_fe_set_open(vcon->chr, guest_connected);
+}
+
+/* Readiness of the guest to accept data on a port */
+static int chr_can_read(void *opaque)
+{
+ VirtConsole *vcon = opaque;
+
+ return virtio_serial_guest_ready(&vcon->port);
+}
+
+/* Send data from a char device over to the guest */
+static void chr_read(void *opaque, const uint8_t *buf, int size)
+{
+ VirtConsole *vcon = opaque;
+
+ trace_virtio_console_chr_read(vcon->port.id, size);
+ virtio_serial_write(&vcon->port, buf, size);
+}
+
+static void chr_event(void *opaque, int event)
+{
+ VirtConsole *vcon = opaque;
+
+ trace_virtio_console_chr_event(vcon->port.id, event);
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ virtio_serial_open(&vcon->port);
+ break;
+ case CHR_EVENT_CLOSED:
+ if (vcon->watch) {
+ g_source_remove(vcon->watch);
+ vcon->watch = 0;
+ }
+ virtio_serial_close(&vcon->port);
+ break;
+ }
+}
+
+static int virtconsole_initfn(VirtIOSerialPort *port)
+{
+ VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port);
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ if (port->id == 0 && !k->is_console) {
+ error_report("Port number 0 on virtio-serial devices reserved for virtconsole devices for backward compatibility.");
+ return -1;
+ }
+
+ if (vcon->chr) {
+ vcon->chr->explicit_fe_open = 1;
+ qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event,
+ vcon);
+ }
+
+ return 0;
+}
+
+static int virtconsole_exitfn(VirtIOSerialPort *port)
+{
+ VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port);
+
+ if (vcon->watch) {
+ g_source_remove(vcon->watch);
+ }
+
+ return 0;
+}
+
+static Property virtconsole_properties[] = {
+ DEFINE_PROP_CHR("chardev", VirtConsole, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtconsole_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
+
+ k->is_console = true;
+ k->init = virtconsole_initfn;
+ k->exit = virtconsole_exitfn;
+ k->have_data = flush_buf;
+ k->set_guest_connected = set_guest_connected;
+ dc->props = virtconsole_properties;
+}
+
+static const TypeInfo virtconsole_info = {
+ .name = "virtconsole",
+ .parent = TYPE_VIRTIO_SERIAL_PORT,
+ .instance_size = sizeof(VirtConsole),
+ .class_init = virtconsole_class_init,
+};
+
+static Property virtserialport_properties[] = {
+ DEFINE_PROP_CHR("chardev", VirtConsole, chr),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtserialport_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass);
+
+ k->init = virtconsole_initfn;
+ k->exit = virtconsole_exitfn;
+ k->have_data = flush_buf;
+ k->set_guest_connected = set_guest_connected;
+ dc->props = virtserialport_properties;
+}
+
+static const TypeInfo virtserialport_info = {
+ .name = "virtserialport",
+ .parent = TYPE_VIRTIO_SERIAL_PORT,
+ .instance_size = sizeof(VirtConsole),
+ .class_init = virtserialport_class_init,
+};
+
+static void virtconsole_register_types(void)
+{
+ type_register_static(&virtconsole_info);
+ type_register_static(&virtserialport_info);
+}
+
+type_init(virtconsole_register_types)
diff --git a/hw/char/virtio-serial-bus.c b/hw/char/virtio-serial-bus.c
new file mode 100644
index 000000000..da417c701
--- /dev/null
+++ b/hw/char/virtio-serial-bus.c
@@ -0,0 +1,1044 @@
+/*
+ * A bus for connecting virtio serial and console ports
+ *
+ * Copyright (C) 2009, 2010 Red Hat, Inc.
+ *
+ * Author(s):
+ * Amit Shah <amit.shah@redhat.com>
+ *
+ * Some earlier parts are:
+ * Copyright IBM, Corp. 2008
+ * authored by
+ * Christian Ehrhardt <ehrhardt@linux.vnet.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2. See
+ * the COPYING file in the top-level directory.
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/iov.h"
+#include "monitor/monitor.h"
+#include "qemu/queue.h"
+#include "hw/sysbus.h"
+#include "trace.h"
+#include "hw/virtio/virtio-serial.h"
+
+static VirtIOSerialPort *find_port_by_id(VirtIOSerial *vser, uint32_t id)
+{
+ VirtIOSerialPort *port;
+
+ if (id == VIRTIO_CONSOLE_BAD_ID) {
+ return NULL;
+ }
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ if (port->id == id)
+ return port;
+ }
+ return NULL;
+}
+
+static VirtIOSerialPort *find_port_by_vq(VirtIOSerial *vser, VirtQueue *vq)
+{
+ VirtIOSerialPort *port;
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ if (port->ivq == vq || port->ovq == vq)
+ return port;
+ }
+ return NULL;
+}
+
+static bool use_multiport(VirtIOSerial *vser)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(vser);
+ return vdev->guest_features & (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+}
+
+static size_t write_to_port(VirtIOSerialPort *port,
+ const uint8_t *buf, size_t size)
+{
+ VirtQueueElement elem;
+ VirtQueue *vq;
+ size_t offset;
+
+ vq = port->ivq;
+ if (!virtio_queue_ready(vq)) {
+ return 0;
+ }
+
+ offset = 0;
+ while (offset < size) {
+ size_t len;
+
+ if (!virtqueue_pop(vq, &elem)) {
+ break;
+ }
+
+ len = iov_from_buf(elem.in_sg, elem.in_num, 0,
+ buf + offset, size - offset);
+ offset += len;
+
+ virtqueue_push(vq, &elem, len);
+ }
+
+ virtio_notify(VIRTIO_DEVICE(port->vser), vq);
+ return offset;
+}
+
+static void discard_vq_data(VirtQueue *vq, VirtIODevice *vdev)
+{
+ VirtQueueElement elem;
+
+ if (!virtio_queue_ready(vq)) {
+ return;
+ }
+ while (virtqueue_pop(vq, &elem)) {
+ virtqueue_push(vq, &elem, 0);
+ }
+ virtio_notify(vdev, vq);
+}
+
+static void do_flush_queued_data(VirtIOSerialPort *port, VirtQueue *vq,
+ VirtIODevice *vdev)
+{
+ VirtIOSerialPortClass *vsc;
+
+ assert(port);
+ assert(virtio_queue_ready(vq));
+
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ while (!port->throttled) {
+ unsigned int i;
+
+ /* Pop an elem only if we haven't left off a previous one mid-way */
+ if (!port->elem.out_num) {
+ if (!virtqueue_pop(vq, &port->elem)) {
+ break;
+ }
+ port->iov_idx = 0;
+ port->iov_offset = 0;
+ }
+
+ for (i = port->iov_idx; i < port->elem.out_num; i++) {
+ size_t buf_size;
+ ssize_t ret;
+
+ buf_size = port->elem.out_sg[i].iov_len - port->iov_offset;
+ ret = vsc->have_data(port,
+ port->elem.out_sg[i].iov_base
+ + port->iov_offset,
+ buf_size);
+ if (port->throttled) {
+ port->iov_idx = i;
+ if (ret > 0) {
+ port->iov_offset += ret;
+ }
+ break;
+ }
+ port->iov_offset = 0;
+ }
+ if (port->throttled) {
+ break;
+ }
+ virtqueue_push(vq, &port->elem, 0);
+ port->elem.out_num = 0;
+ }
+ virtio_notify(vdev, vq);
+}
+
+static void flush_queued_data(VirtIOSerialPort *port)
+{
+ assert(port);
+
+ if (!virtio_queue_ready(port->ovq)) {
+ return;
+ }
+ do_flush_queued_data(port, port->ovq, VIRTIO_DEVICE(port->vser));
+}
+
+static size_t send_control_msg(VirtIOSerial *vser, void *buf, size_t len)
+{
+ VirtQueueElement elem;
+ VirtQueue *vq;
+
+ vq = vser->c_ivq;
+ if (!virtio_queue_ready(vq)) {
+ return 0;
+ }
+ if (!virtqueue_pop(vq, &elem)) {
+ return 0;
+ }
+
+ memcpy(elem.in_sg[0].iov_base, buf, len);
+
+ virtqueue_push(vq, &elem, len);
+ virtio_notify(VIRTIO_DEVICE(vser), vq);
+ return len;
+}
+
+static size_t send_control_event(VirtIOSerial *vser, uint32_t port_id,
+ uint16_t event, uint16_t value)
+{
+ struct virtio_console_control cpkt;
+
+ stl_p(&cpkt.id, port_id);
+ stw_p(&cpkt.event, event);
+ stw_p(&cpkt.value, value);
+
+ trace_virtio_serial_send_control_event(port_id, event, value);
+ return send_control_msg(vser, &cpkt, sizeof(cpkt));
+}
+
+/* Functions for use inside qemu to open and read from/write to ports */
+int virtio_serial_open(VirtIOSerialPort *port)
+{
+ /* Don't allow opening an already-open port */
+ if (port->host_connected) {
+ return 0;
+ }
+ /* Send port open notification to the guest */
+ port->host_connected = true;
+ send_control_event(port->vser, port->id, VIRTIO_CONSOLE_PORT_OPEN, 1);
+
+ return 0;
+}
+
+int virtio_serial_close(VirtIOSerialPort *port)
+{
+ port->host_connected = false;
+ /*
+ * If there's any data the guest sent which the app didn't
+ * consume, reset the throttling flag and discard the data.
+ */
+ port->throttled = false;
+ discard_vq_data(port->ovq, VIRTIO_DEVICE(port->vser));
+
+ send_control_event(port->vser, port->id, VIRTIO_CONSOLE_PORT_OPEN, 0);
+
+ return 0;
+}
+
+/* Individual ports/apps call this function to write to the guest. */
+ssize_t virtio_serial_write(VirtIOSerialPort *port, const uint8_t *buf,
+ size_t size)
+{
+ if (!port || !port->host_connected || !port->guest_connected) {
+ return 0;
+ }
+ return write_to_port(port, buf, size);
+}
+
+/*
+ * Readiness of the guest to accept data on a port.
+ * Returns max. data the guest can receive
+ */
+size_t virtio_serial_guest_ready(VirtIOSerialPort *port)
+{
+ VirtIODevice *vdev = VIRTIO_DEVICE(port->vser);
+ VirtQueue *vq = port->ivq;
+ unsigned int bytes;
+
+ if (!virtio_queue_ready(vq) ||
+ !(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK) ||
+ virtio_queue_empty(vq)) {
+ return 0;
+ }
+ if (use_multiport(port->vser) && !port->guest_connected) {
+ return 0;
+ }
+ virtqueue_get_avail_bytes(vq, &bytes, NULL, 4096, 0);
+ return bytes;
+}
+
+static void flush_queued_data_bh(void *opaque)
+{
+ VirtIOSerialPort *port = opaque;
+
+ flush_queued_data(port);
+}
+
+void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle)
+{
+ if (!port) {
+ return;
+ }
+
+ trace_virtio_serial_throttle_port(port->id, throttle);
+ port->throttled = throttle;
+ if (throttle) {
+ return;
+ }
+ qemu_bh_schedule(port->bh);
+}
+
+/* Guest wants to notify us of some event */
+static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len)
+{
+ struct VirtIOSerialPort *port;
+ VirtIOSerialPortClass *vsc;
+ struct virtio_console_control cpkt, *gcpkt;
+ uint8_t *buffer;
+ size_t buffer_len;
+
+ gcpkt = buf;
+
+ if (len < sizeof(cpkt)) {
+ /* The guest sent an invalid control packet */
+ return;
+ }
+
+ cpkt.event = lduw_p(&gcpkt->event);
+ cpkt.value = lduw_p(&gcpkt->value);
+
+ trace_virtio_serial_handle_control_message(cpkt.event, cpkt.value);
+
+ if (cpkt.event == VIRTIO_CONSOLE_DEVICE_READY) {
+ if (!cpkt.value) {
+ error_report("virtio-serial-bus: Guest failure in adding device %s",
+ vser->bus.qbus.name);
+ return;
+ }
+ /*
+ * The device is up, we can now tell the device about all the
+ * ports we have here.
+ */
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_PORT_ADD, 1);
+ }
+ return;
+ }
+
+ port = find_port_by_id(vser, ldl_p(&gcpkt->id));
+ if (!port) {
+ error_report("virtio-serial-bus: Unexpected port id %u for device %s",
+ ldl_p(&gcpkt->id), vser->bus.qbus.name);
+ return;
+ }
+
+ trace_virtio_serial_handle_control_message_port(port->id);
+
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+
+ switch(cpkt.event) {
+ case VIRTIO_CONSOLE_PORT_READY:
+ if (!cpkt.value) {
+ error_report("virtio-serial-bus: Guest failure in adding port %u for device %s",
+ port->id, vser->bus.qbus.name);
+ break;
+ }
+ /*
+ * Now that we know the guest asked for the port name, we're
+ * sure the guest has initialised whatever state is necessary
+ * for this port. Now's a good time to let the guest know if
+ * this port is a console port so that the guest can hook it
+ * up to hvc.
+ */
+ if (vsc->is_console) {
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_CONSOLE_PORT, 1);
+ }
+
+ if (port->name) {
+ stl_p(&cpkt.id, port->id);
+ stw_p(&cpkt.event, VIRTIO_CONSOLE_PORT_NAME);
+ stw_p(&cpkt.value, 1);
+
+ buffer_len = sizeof(cpkt) + strlen(port->name) + 1;
+ buffer = g_malloc(buffer_len);
+
+ memcpy(buffer, &cpkt, sizeof(cpkt));
+ memcpy(buffer + sizeof(cpkt), port->name, strlen(port->name));
+ buffer[buffer_len - 1] = 0;
+
+ send_control_msg(vser, buffer, buffer_len);
+ g_free(buffer);
+ }
+
+ if (port->host_connected) {
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_PORT_OPEN, 1);
+ }
+
+ /*
+ * When the guest has asked us for this information it means
+ * the guest is all setup and has its virtqueues
+ * initialised. If some app is interested in knowing about
+ * this event, let it know.
+ */
+ if (vsc->guest_ready) {
+ vsc->guest_ready(port);
+ }
+ break;
+
+ case VIRTIO_CONSOLE_PORT_OPEN:
+ port->guest_connected = cpkt.value;
+ if (vsc->set_guest_connected) {
+ /* Send the guest opened notification if an app is interested */
+ vsc->set_guest_connected(port, cpkt.value);
+ }
+ break;
+ }
+}
+
+static void control_in(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+static void control_out(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtQueueElement elem;
+ VirtIOSerial *vser;
+ uint8_t *buf;
+ size_t len;
+
+ vser = VIRTIO_SERIAL(vdev);
+
+ len = 0;
+ buf = NULL;
+ while (virtqueue_pop(vq, &elem)) {
+ size_t cur_len;
+
+ cur_len = iov_size(elem.out_sg, elem.out_num);
+ /*
+ * Allocate a new buf only if we didn't have one previously or
+ * if the size of the buf differs
+ */
+ if (cur_len > len) {
+ g_free(buf);
+
+ buf = g_malloc(cur_len);
+ len = cur_len;
+ }
+ iov_to_buf(elem.out_sg, elem.out_num, 0, buf, cur_len);
+
+ handle_control_message(vser, buf, cur_len);
+ virtqueue_push(vq, &elem, 0);
+ }
+ g_free(buf);
+ virtio_notify(vdev, vq);
+}
+
+/* Guest wrote something to some port. */
+static void handle_output(VirtIODevice *vdev, VirtQueue *vq)
+{
+ VirtIOSerial *vser;
+ VirtIOSerialPort *port;
+
+ vser = VIRTIO_SERIAL(vdev);
+ port = find_port_by_vq(vser, vq);
+
+ if (!port || !port->host_connected) {
+ discard_vq_data(vq, vdev);
+ return;
+ }
+
+ if (!port->throttled) {
+ do_flush_queued_data(port, vq, vdev);
+ return;
+ }
+}
+
+static void handle_input(VirtIODevice *vdev, VirtQueue *vq)
+{
+}
+
+static uint32_t get_features(VirtIODevice *vdev, uint32_t features)
+{
+ VirtIOSerial *vser;
+
+ vser = VIRTIO_SERIAL(vdev);
+
+ if (vser->bus.max_nr_ports > 1) {
+ features |= (1 << VIRTIO_CONSOLE_F_MULTIPORT);
+ }
+ return features;
+}
+
+/* Guest requested config info */
+static void get_config(VirtIODevice *vdev, uint8_t *config_data)
+{
+ VirtIOSerial *vser;
+
+ vser = VIRTIO_SERIAL(vdev);
+ memcpy(config_data, &vser->config, sizeof(struct virtio_console_config));
+}
+
+static void set_config(VirtIODevice *vdev, const uint8_t *config_data)
+{
+ struct virtio_console_config config;
+
+ memcpy(&config, config_data, sizeof(config));
+}
+
+static void guest_reset(VirtIOSerial *vser)
+{
+ VirtIOSerialPort *port;
+ VirtIOSerialPortClass *vsc;
+
+ QTAILQ_FOREACH(port, &vser->ports, next) {
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+ if (port->guest_connected) {
+ port->guest_connected = false;
+ if (vsc->set_guest_connected) {
+ vsc->set_guest_connected(port, false);
+ }
+ }
+ }
+}
+
+static void set_status(VirtIODevice *vdev, uint8_t status)
+{
+ VirtIOSerial *vser;
+ VirtIOSerialPort *port;
+
+ vser = VIRTIO_SERIAL(vdev);
+ port = find_port_by_id(vser, 0);
+
+ if (port && !use_multiport(port->vser)
+ && (status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ /*
+ * Non-multiport guests won't be able to tell us guest
+ * open/close status. Such guests can only have a port at id
+ * 0, so set guest_connected for such ports as soon as guest
+ * is up.
+ */
+ port->guest_connected = true;
+ }
+ if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) {
+ guest_reset(vser);
+ }
+}
+
+static void vser_reset(VirtIODevice *vdev)
+{
+ VirtIOSerial *vser;
+
+ vser = VIRTIO_SERIAL(vdev);
+ guest_reset(vser);
+}
+
+static void virtio_serial_save(QEMUFile *f, void *opaque)
+{
+ VirtIOSerial *s = VIRTIO_SERIAL(opaque);
+ VirtIOSerialPort *port;
+ uint32_t nr_active_ports;
+ unsigned int i, max_nr_ports;
+
+ /* The virtio device */
+ virtio_save(VIRTIO_DEVICE(s), f);
+
+ /* The config space */
+ qemu_put_be16s(f, &s->config.cols);
+ qemu_put_be16s(f, &s->config.rows);
+
+ qemu_put_be32s(f, &s->config.max_nr_ports);
+
+ /* The ports map */
+ max_nr_ports = tswap32(s->config.max_nr_ports);
+ for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
+ qemu_put_be32s(f, &s->ports_map[i]);
+ }
+
+ /* Ports */
+
+ nr_active_ports = 0;
+ QTAILQ_FOREACH(port, &s->ports, next) {
+ nr_active_ports++;
+ }
+
+ qemu_put_be32s(f, &nr_active_ports);
+
+ /*
+ * Items in struct VirtIOSerialPort.
+ */
+ QTAILQ_FOREACH(port, &s->ports, next) {
+ uint32_t elem_popped;
+
+ qemu_put_be32s(f, &port->id);
+ qemu_put_byte(f, port->guest_connected);
+ qemu_put_byte(f, port->host_connected);
+
+ elem_popped = 0;
+ if (port->elem.out_num) {
+ elem_popped = 1;
+ }
+ qemu_put_be32s(f, &elem_popped);
+ if (elem_popped) {
+ qemu_put_be32s(f, &port->iov_idx);
+ qemu_put_be64s(f, &port->iov_offset);
+
+ qemu_put_buffer(f, (unsigned char *)&port->elem,
+ sizeof(port->elem));
+ }
+ }
+}
+
+static void virtio_serial_post_load_timer_cb(void *opaque)
+{
+ uint32_t i;
+ VirtIOSerial *s = VIRTIO_SERIAL(opaque);
+ VirtIOSerialPort *port;
+ uint8_t host_connected;
+ VirtIOSerialPortClass *vsc;
+
+ if (!s->post_load) {
+ return;
+ }
+ for (i = 0 ; i < s->post_load->nr_active_ports; ++i) {
+ port = s->post_load->connected[i].port;
+ host_connected = s->post_load->connected[i].host_connected;
+ if (host_connected != port->host_connected) {
+ /*
+ * We have to let the guest know of the host connection
+ * status change
+ */
+ send_control_event(s, port->id, VIRTIO_CONSOLE_PORT_OPEN,
+ port->host_connected);
+ }
+ vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+ if (vsc->set_guest_connected) {
+ vsc->set_guest_connected(port, port->guest_connected);
+ }
+ }
+ g_free(s->post_load->connected);
+ qemu_free_timer(s->post_load->timer);
+ g_free(s->post_load);
+ s->post_load = NULL;
+}
+
+static int fetch_active_ports_list(QEMUFile *f, int version_id,
+ VirtIOSerial *s, uint32_t nr_active_ports)
+{
+ uint32_t i;
+
+ s->post_load = g_malloc0(sizeof(*s->post_load));
+ s->post_load->nr_active_ports = nr_active_ports;
+ s->post_load->connected =
+ g_malloc0(sizeof(*s->post_load->connected) * nr_active_ports);
+
+ s->post_load->timer = qemu_new_timer_ns(vm_clock,
+ virtio_serial_post_load_timer_cb,
+ s);
+
+ /* Items in struct VirtIOSerialPort */
+ for (i = 0; i < nr_active_ports; i++) {
+ VirtIOSerialPort *port;
+ uint32_t id;
+
+ id = qemu_get_be32(f);
+ port = find_port_by_id(s, id);
+ if (!port) {
+ return -EINVAL;
+ }
+
+ port->guest_connected = qemu_get_byte(f);
+ s->post_load->connected[i].port = port;
+ s->post_load->connected[i].host_connected = qemu_get_byte(f);
+
+ if (version_id > 2) {
+ uint32_t elem_popped;
+
+ qemu_get_be32s(f, &elem_popped);
+ if (elem_popped) {
+ qemu_get_be32s(f, &port->iov_idx);
+ qemu_get_be64s(f, &port->iov_offset);
+
+ qemu_get_buffer(f, (unsigned char *)&port->elem,
+ sizeof(port->elem));
+ virtqueue_map_sg(port->elem.in_sg, port->elem.in_addr,
+ port->elem.in_num, 1);
+ virtqueue_map_sg(port->elem.out_sg, port->elem.out_addr,
+ port->elem.out_num, 1);
+
+ /*
+ * Port was throttled on source machine. Let's
+ * unthrottle it here so data starts flowing again.
+ */
+ virtio_serial_throttle_port(port, false);
+ }
+ }
+ }
+ qemu_mod_timer(s->post_load->timer, 1);
+ return 0;
+}
+
+static int virtio_serial_load(QEMUFile *f, void *opaque, int version_id)
+{
+ VirtIOSerial *s = VIRTIO_SERIAL(opaque);
+ uint32_t max_nr_ports, nr_active_ports, ports_map;
+ unsigned int i;
+ int ret;
+
+ if (version_id > 3) {
+ return -EINVAL;
+ }
+
+ /* The virtio device */
+ ret = virtio_load(VIRTIO_DEVICE(s), f);
+ if (ret) {
+ return ret;
+ }
+
+ if (version_id < 2) {
+ return 0;
+ }
+
+ /* The config space */
+ qemu_get_be16s(f, &s->config.cols);
+ qemu_get_be16s(f, &s->config.rows);
+
+ qemu_get_be32s(f, &max_nr_ports);
+ tswap32s(&max_nr_ports);
+ if (max_nr_ports > tswap32(s->config.max_nr_ports)) {
+ /* Source could have had more ports than us. Fail migration. */
+ return -EINVAL;
+ }
+
+ for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
+ qemu_get_be32s(f, &ports_map);
+
+ if (ports_map != s->ports_map[i]) {
+ /*
+ * Ports active on source and destination don't
+ * match. Fail migration.
+ */
+ return -EINVAL;
+ }
+ }
+
+ qemu_get_be32s(f, &nr_active_ports);
+
+ if (nr_active_ports) {
+ ret = fetch_active_ports_list(f, version_id, s, nr_active_ports);
+ if (ret) {
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void virtser_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent);
+
+static Property virtser_props[] = {
+ DEFINE_PROP_UINT32("nr", VirtIOSerialPort, id, VIRTIO_CONSOLE_BAD_ID),
+ DEFINE_PROP_STRING("name", VirtIOSerialPort, name),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+#define TYPE_VIRTIO_SERIAL_BUS "virtio-serial-bus"
+#define VIRTIO_SERIAL_BUS(obj) \
+ OBJECT_CHECK(VirtIOSerialBus, (obj), TYPE_VIRTIO_SERIAL_BUS)
+
+static void virtser_bus_class_init(ObjectClass *klass, void *data)
+{
+ BusClass *k = BUS_CLASS(klass);
+ k->print_dev = virtser_bus_dev_print;
+}
+
+static const TypeInfo virtser_bus_info = {
+ .name = TYPE_VIRTIO_SERIAL_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(VirtIOSerialBus),
+ .class_init = virtser_bus_class_init,
+};
+
+static void virtser_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent)
+{
+ VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, qdev);
+
+ monitor_printf(mon, "%*sport %d, guest %s, host %s, throttle %s\n",
+ indent, "", port->id,
+ port->guest_connected ? "on" : "off",
+ port->host_connected ? "on" : "off",
+ port->throttled ? "on" : "off");
+}
+
+/* This function is only used if a port id is not provided by the user */
+static uint32_t find_free_port_id(VirtIOSerial *vser)
+{
+ unsigned int i, max_nr_ports;
+
+ max_nr_ports = tswap32(vser->config.max_nr_ports);
+ for (i = 0; i < (max_nr_ports + 31) / 32; i++) {
+ uint32_t map, bit;
+
+ map = vser->ports_map[i];
+ bit = ffs(~map);
+ if (bit) {
+ return (bit - 1) + i * 32;
+ }
+ }
+ return VIRTIO_CONSOLE_BAD_ID;
+}
+
+static void mark_port_added(VirtIOSerial *vser, uint32_t port_id)
+{
+ unsigned int i;
+
+ i = port_id / 32;
+ vser->ports_map[i] |= 1U << (port_id % 32);
+}
+
+static void add_port(VirtIOSerial *vser, uint32_t port_id)
+{
+ mark_port_added(vser, port_id);
+ send_control_event(vser, port_id, VIRTIO_CONSOLE_PORT_ADD, 1);
+}
+
+static void remove_port(VirtIOSerial *vser, uint32_t port_id)
+{
+ VirtIOSerialPort *port;
+ unsigned int i;
+
+ i = port_id / 32;
+ vser->ports_map[i] &= ~(1U << (port_id % 32));
+
+ port = find_port_by_id(vser, port_id);
+ /*
+ * This function is only called from qdev's unplug callback; if we
+ * get a NULL port here, we're in trouble.
+ */
+ assert(port);
+
+ /* Flush out any unconsumed buffers first */
+ discard_vq_data(port->ovq, VIRTIO_DEVICE(port->vser));
+
+ send_control_event(vser, port->id, VIRTIO_CONSOLE_PORT_REMOVE, 1);
+}
+
+static int virtser_port_qdev_init(DeviceState *qdev)
+{
+ VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, qdev);
+ VirtIOSerialPortClass *vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+ VirtIOSerialBus *bus = DO_UPCAST(VirtIOSerialBus, qbus, qdev->parent_bus);
+ int ret, max_nr_ports;
+ bool plugging_port0;
+
+ port->vser = bus->vser;
+ port->bh = qemu_bh_new(flush_queued_data_bh, port);
+
+ assert(vsc->have_data);
+
+ /*
+ * Is the first console port we're seeing? If so, put it up at
+ * location 0. This is done for backward compatibility (old
+ * kernel, new qemu).
+ */
+ plugging_port0 = vsc->is_console && !find_port_by_id(port->vser, 0);
+
+ if (find_port_by_id(port->vser, port->id)) {
+ error_report("virtio-serial-bus: A port already exists at id %u",
+ port->id);
+ return -1;
+ }
+
+ if (port->id == VIRTIO_CONSOLE_BAD_ID) {
+ if (plugging_port0) {
+ port->id = 0;
+ } else {
+ port->id = find_free_port_id(port->vser);
+ if (port->id == VIRTIO_CONSOLE_BAD_ID) {
+ error_report("virtio-serial-bus: Maximum port limit for this device reached");
+ return -1;
+ }
+ }
+ }
+
+ max_nr_ports = tswap32(port->vser->config.max_nr_ports);
+ if (port->id >= max_nr_ports) {
+ error_report("virtio-serial-bus: Out-of-range port id specified, max. allowed: %u",
+ max_nr_ports - 1);
+ return -1;
+ }
+
+ ret = vsc->init(port);
+ if (ret) {
+ return ret;
+ }
+
+ port->elem.out_num = 0;
+
+ QTAILQ_INSERT_TAIL(&port->vser->ports, port, next);
+ port->ivq = port->vser->ivqs[port->id];
+ port->ovq = port->vser->ovqs[port->id];
+
+ add_port(port->vser, port->id);
+
+ /* Send an update to the guest about this new port added */
+ virtio_notify_config(VIRTIO_DEVICE(port->vser));
+
+ return ret;
+}
+
+static int virtser_port_qdev_exit(DeviceState *qdev)
+{
+ VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, qdev);
+ VirtIOSerialPortClass *vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port);
+ VirtIOSerial *vser = port->vser;
+
+ qemu_bh_delete(port->bh);
+ remove_port(port->vser, port->id);
+
+ QTAILQ_REMOVE(&vser->ports, port, next);
+
+ if (vsc->exit) {
+ vsc->exit(port);
+ }
+ return 0;
+}
+
+static int virtio_serial_device_init(VirtIODevice *vdev)
+{
+ DeviceState *qdev = DEVICE(vdev);
+ VirtIOSerial *vser = VIRTIO_SERIAL(vdev);
+ uint32_t i, max_supported_ports;
+
+ if (!vser->serial.max_virtserial_ports) {
+ return -1;
+ }
+
+ /* Each port takes 2 queues, and one pair is for the control queue */
+ max_supported_ports = VIRTIO_PCI_QUEUE_MAX / 2 - 1;
+
+ if (vser->serial.max_virtserial_ports > max_supported_ports) {
+ error_report("maximum ports supported: %u", max_supported_ports);
+ return -1;
+ }
+
+ virtio_init(vdev, "virtio-serial", VIRTIO_ID_CONSOLE,
+ sizeof(struct virtio_console_config));
+
+ /* Spawn a new virtio-serial bus on which the ports will ride as devices */
+ qbus_create_inplace(&vser->bus.qbus, TYPE_VIRTIO_SERIAL_BUS, qdev,
+ vdev->bus_name);
+ vser->bus.qbus.allow_hotplug = 1;
+ vser->bus.vser = vser;
+ QTAILQ_INIT(&vser->ports);
+
+ vser->bus.max_nr_ports = vser->serial.max_virtserial_ports;
+ vser->ivqs = g_malloc(vser->serial.max_virtserial_ports
+ * sizeof(VirtQueue *));
+ vser->ovqs = g_malloc(vser->serial.max_virtserial_ports
+ * sizeof(VirtQueue *));
+
+ /* Add a queue for host to guest transfers for port 0 (backward compat) */
+ vser->ivqs[0] = virtio_add_queue(vdev, 128, handle_input);
+ /* Add a queue for guest to host transfers for port 0 (backward compat) */
+ vser->ovqs[0] = virtio_add_queue(vdev, 128, handle_output);
+
+ /* TODO: host to guest notifications can get dropped
+ * if the queue fills up. Implement queueing in host,
+ * this might also make it possible to reduce the control
+ * queue size: as guest preposts buffers there,
+ * this will save 4Kbyte of guest memory per entry. */
+
+ /* control queue: host to guest */
+ vser->c_ivq = virtio_add_queue(vdev, 32, control_in);
+ /* control queue: guest to host */
+ vser->c_ovq = virtio_add_queue(vdev, 32, control_out);
+
+ for (i = 1; i < vser->bus.max_nr_ports; i++) {
+ /* Add a per-port queue for host to guest transfers */
+ vser->ivqs[i] = virtio_add_queue(vdev, 128, handle_input);
+ /* Add a per-per queue for guest to host transfers */
+ vser->ovqs[i] = virtio_add_queue(vdev, 128, handle_output);
+ }
+
+ vser->config.max_nr_ports = tswap32(vser->serial.max_virtserial_ports);
+ vser->ports_map = g_malloc0(((vser->serial.max_virtserial_ports + 31) / 32)
+ * sizeof(vser->ports_map[0]));
+ /*
+ * Reserve location 0 for a console port for backward compat
+ * (old kernel, new qemu)
+ */
+ mark_port_added(vser, 0);
+
+ vser->post_load = NULL;
+
+ /*
+ * Register for the savevm section with the virtio-console name
+ * to preserve backward compat
+ */
+ register_savevm(qdev, "virtio-console", -1, 3, virtio_serial_save,
+ virtio_serial_load, vser);
+
+ return 0;
+}
+
+static void virtio_serial_port_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = virtser_port_qdev_init;
+ set_bit(DEVICE_CATEGORY_INPUT, k->categories);
+ k->bus_type = TYPE_VIRTIO_SERIAL_BUS;
+ k->exit = virtser_port_qdev_exit;
+ k->unplug = qdev_simple_unplug_cb;
+ k->props = virtser_props;
+}
+
+static const TypeInfo virtio_serial_port_type_info = {
+ .name = TYPE_VIRTIO_SERIAL_PORT,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VirtIOSerialPort),
+ .abstract = true,
+ .class_size = sizeof(VirtIOSerialPortClass),
+ .class_init = virtio_serial_port_class_init,
+};
+
+static int virtio_serial_device_exit(DeviceState *dev)
+{
+ VirtIOSerial *vser = VIRTIO_SERIAL(dev);
+ VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+
+ unregister_savevm(dev, "virtio-console", vser);
+
+ g_free(vser->ivqs);
+ g_free(vser->ovqs);
+ g_free(vser->ports_map);
+ if (vser->post_load) {
+ g_free(vser->post_load->connected);
+ qemu_del_timer(vser->post_load->timer);
+ qemu_free_timer(vser->post_load->timer);
+ g_free(vser->post_load);
+ }
+ virtio_cleanup(vdev);
+ return 0;
+}
+
+static Property virtio_serial_properties[] = {
+ DEFINE_VIRTIO_SERIAL_PROPERTIES(VirtIOSerial, serial),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void virtio_serial_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
+ dc->exit = virtio_serial_device_exit;
+ dc->props = virtio_serial_properties;
+ set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
+ vdc->init = virtio_serial_device_init;
+ vdc->get_features = get_features;
+ vdc->get_config = get_config;
+ vdc->set_config = set_config;
+ vdc->set_status = set_status;
+ vdc->reset = vser_reset;
+}
+
+static const TypeInfo virtio_device_info = {
+ .name = TYPE_VIRTIO_SERIAL,
+ .parent = TYPE_VIRTIO_DEVICE,
+ .instance_size = sizeof(VirtIOSerial),
+ .class_init = virtio_serial_class_init,
+};
+
+static void virtio_serial_register_types(void)
+{
+ type_register_static(&virtser_bus_info);
+ type_register_static(&virtio_serial_port_type_info);
+ type_register_static(&virtio_device_info);
+}
+
+type_init(virtio_serial_register_types)
diff --git a/hw/char/xen_console.c b/hw/char/xen_console.c
new file mode 100644
index 000000000..eb7f450ab
--- /dev/null
+++ b/hw/char/xen_console.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) International Business Machines Corp., 2005
+ * Author(s): Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * Copyright (C) Red Hat 2007
+ *
+ * Xen Console
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <termios.h>
+#include <stdarg.h>
+#include <sys/mman.h>
+
+#include "hw/hw.h"
+#include "sysemu/char.h"
+#include "hw/xen/xen_backend.h"
+
+#include <xen/io/console.h>
+
+struct buffer {
+ uint8_t *data;
+ size_t consumed;
+ size_t size;
+ size_t capacity;
+ size_t max_capacity;
+};
+
+struct XenConsole {
+ struct XenDevice xendev; /* must be first */
+ struct buffer buffer;
+ char console[XEN_BUFSIZE];
+ int ring_ref;
+ void *sring;
+ CharDriverState *chr;
+ int backlog;
+};
+
+static void buffer_append(struct XenConsole *con)
+{
+ struct buffer *buffer = &con->buffer;
+ XENCONS_RING_IDX cons, prod, size;
+ struct xencons_interface *intf = con->sring;
+
+ cons = intf->out_cons;
+ prod = intf->out_prod;
+ xen_mb();
+
+ size = prod - cons;
+ if ((size == 0) || (size > sizeof(intf->out)))
+ return;
+
+ if ((buffer->capacity - buffer->size) < size) {
+ buffer->capacity += (size + 1024);
+ buffer->data = g_realloc(buffer->data, buffer->capacity);
+ }
+
+ while (cons != prod)
+ buffer->data[buffer->size++] = intf->out[
+ MASK_XENCONS_IDX(cons++, intf->out)];
+
+ xen_mb();
+ intf->out_cons = cons;
+ xen_be_send_notify(&con->xendev);
+
+ if (buffer->max_capacity &&
+ buffer->size > buffer->max_capacity) {
+ /* Discard the middle of the data. */
+
+ size_t over = buffer->size - buffer->max_capacity;
+ uint8_t *maxpos = buffer->data + buffer->max_capacity;
+
+ memmove(maxpos - over, maxpos, over);
+ buffer->data = g_realloc(buffer->data, buffer->max_capacity);
+ buffer->size = buffer->capacity = buffer->max_capacity;
+
+ if (buffer->consumed > buffer->max_capacity - over)
+ buffer->consumed = buffer->max_capacity - over;
+ }
+}
+
+static void buffer_advance(struct buffer *buffer, size_t len)
+{
+ buffer->consumed += len;
+ if (buffer->consumed == buffer->size) {
+ buffer->consumed = 0;
+ buffer->size = 0;
+ }
+}
+
+static int ring_free_bytes(struct XenConsole *con)
+{
+ struct xencons_interface *intf = con->sring;
+ XENCONS_RING_IDX cons, prod, space;
+
+ cons = intf->in_cons;
+ prod = intf->in_prod;
+ xen_mb();
+
+ space = prod - cons;
+ if (space > sizeof(intf->in))
+ return 0; /* ring is screwed: ignore it */
+
+ return (sizeof(intf->in) - space);
+}
+
+static int xencons_can_receive(void *opaque)
+{
+ struct XenConsole *con = opaque;
+ return ring_free_bytes(con);
+}
+
+static void xencons_receive(void *opaque, const uint8_t *buf, int len)
+{
+ struct XenConsole *con = opaque;
+ struct xencons_interface *intf = con->sring;
+ XENCONS_RING_IDX prod;
+ int i, max;
+
+ max = ring_free_bytes(con);
+ /* The can_receive() func limits this, but check again anyway */
+ if (max < len)
+ len = max;
+
+ prod = intf->in_prod;
+ for (i = 0; i < len; i++) {
+ intf->in[MASK_XENCONS_IDX(prod++, intf->in)] =
+ buf[i];
+ }
+ xen_wmb();
+ intf->in_prod = prod;
+ xen_be_send_notify(&con->xendev);
+}
+
+static void xencons_send(struct XenConsole *con)
+{
+ ssize_t len, size;
+
+ size = con->buffer.size - con->buffer.consumed;
+ if (con->chr)
+ len = qemu_chr_fe_write(con->chr, con->buffer.data + con->buffer.consumed,
+ size);
+ else
+ len = size;
+ if (len < 1) {
+ if (!con->backlog) {
+ con->backlog = 1;
+ xen_be_printf(&con->xendev, 1, "backlog piling up, nobody listening?\n");
+ }
+ } else {
+ buffer_advance(&con->buffer, len);
+ if (con->backlog && len == size) {
+ con->backlog = 0;
+ xen_be_printf(&con->xendev, 1, "backlog is gone\n");
+ }
+ }
+}
+
+/* -------------------------------------------------------------------- */
+
+static int con_init(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+ char *type, *dom, label[32];
+ int ret = 0;
+ const char *output;
+
+ /* setup */
+ dom = xs_get_domain_path(xenstore, con->xendev.dom);
+ if (!xendev->dev) {
+ snprintf(con->console, sizeof(con->console), "%s/console", dom);
+ } else {
+ snprintf(con->console, sizeof(con->console), "%s/device/console/%d", dom, xendev->dev);
+ }
+ free(dom);
+
+ type = xenstore_read_str(con->console, "type");
+ if (!type || strcmp(type, "ioemu") != 0) {
+ xen_be_printf(xendev, 1, "not for me (type=%s)\n", type);
+ ret = -1;
+ goto out;
+ }
+
+ output = xenstore_read_str(con->console, "output");
+
+ /* no Xen override, use qemu output device */
+ if (output == NULL) {
+ con->chr = serial_hds[con->xendev.dev];
+ } else {
+ snprintf(label, sizeof(label), "xencons%d", con->xendev.dev);
+ con->chr = qemu_chr_new(label, output, NULL);
+ }
+
+ xenstore_store_pv_console_info(con->xendev.dev, con->chr);
+
+out:
+ g_free(type);
+ return ret;
+}
+
+static int con_initialise(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+ int limit;
+
+ if (xenstore_read_int(con->console, "ring-ref", &con->ring_ref) == -1)
+ return -1;
+ if (xenstore_read_int(con->console, "port", &con->xendev.remote_port) == -1)
+ return -1;
+ if (xenstore_read_int(con->console, "limit", &limit) == 0)
+ con->buffer.max_capacity = limit;
+
+ if (!xendev->dev) {
+ con->sring = xc_map_foreign_range(xen_xc, con->xendev.dom,
+ XC_PAGE_SIZE,
+ PROT_READ|PROT_WRITE,
+ con->ring_ref);
+ } else {
+ con->sring = xc_gnttab_map_grant_ref(xendev->gnttabdev, con->xendev.dom,
+ con->ring_ref,
+ PROT_READ|PROT_WRITE);
+ }
+ if (!con->sring)
+ return -1;
+
+ xen_be_bind_evtchn(&con->xendev);
+ if (con->chr) {
+ if (qemu_chr_fe_claim(con->chr) == 0) {
+ qemu_chr_add_handlers(con->chr, xencons_can_receive,
+ xencons_receive, NULL, con);
+ } else {
+ xen_be_printf(xendev, 0,
+ "xen_console_init error chardev %s already used\n",
+ con->chr->label);
+ con->chr = NULL;
+ }
+ }
+
+ xen_be_printf(xendev, 1, "ring mfn %d, remote port %d, local port %d, limit %zd\n",
+ con->ring_ref,
+ con->xendev.remote_port,
+ con->xendev.local_port,
+ con->buffer.max_capacity);
+ return 0;
+}
+
+static void con_disconnect(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+
+ if (!xendev->dev) {
+ return;
+ }
+ if (con->chr) {
+ qemu_chr_add_handlers(con->chr, NULL, NULL, NULL, NULL);
+ qemu_chr_fe_release(con->chr);
+ }
+ xen_be_unbind_evtchn(&con->xendev);
+
+ if (con->sring) {
+ if (!xendev->gnttabdev) {
+ munmap(con->sring, XC_PAGE_SIZE);
+ } else {
+ xc_gnttab_munmap(xendev->gnttabdev, con->sring, 1);
+ }
+ con->sring = NULL;
+ }
+}
+
+static void con_event(struct XenDevice *xendev)
+{
+ struct XenConsole *con = container_of(xendev, struct XenConsole, xendev);
+
+ buffer_append(con);
+ if (con->buffer.size - con->buffer.consumed)
+ xencons_send(con);
+}
+
+/* -------------------------------------------------------------------- */
+
+struct XenDevOps xen_console_ops = {
+ .size = sizeof(struct XenConsole),
+ .flags = DEVOPS_FLAG_IGNORE_STATE|DEVOPS_FLAG_NEED_GNTDEV,
+ .init = con_init,
+ .initialise = con_initialise,
+ .event = con_event,
+ .disconnect = con_disconnect,
+};
diff --git a/hw/char/xilinx_uartlite.c b/hw/char/xilinx_uartlite.c
new file mode 100644
index 000000000..b0d1d04af
--- /dev/null
+++ b/hw/char/xilinx_uartlite.c
@@ -0,0 +1,235 @@
+/*
+ * QEMU model of Xilinx uartlite.
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw/sysbus.h"
+#include "sysemu/char.h"
+
+#define DUART(x)
+
+#define R_RX 0
+#define R_TX 1
+#define R_STATUS 2
+#define R_CTRL 3
+#define R_MAX 4
+
+#define STATUS_RXVALID 0x01
+#define STATUS_RXFULL 0x02
+#define STATUS_TXEMPTY 0x04
+#define STATUS_TXFULL 0x08
+#define STATUS_IE 0x10
+#define STATUS_OVERRUN 0x20
+#define STATUS_FRAME 0x40
+#define STATUS_PARITY 0x80
+
+#define CONTROL_RST_TX 0x01
+#define CONTROL_RST_RX 0x02
+#define CONTROL_IE 0x10
+
+#define TYPE_XILINX_UARTLITE "xlnx.xps-uartlite"
+#define XILINX_UARTLITE(obj) \
+ OBJECT_CHECK(XilinxUARTLite, (obj), TYPE_XILINX_UARTLITE)
+
+typedef struct XilinxUARTLite {
+ SysBusDevice parent_obj;
+
+ MemoryRegion mmio;
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint8_t rx_fifo[8];
+ unsigned int rx_fifo_pos;
+ unsigned int rx_fifo_len;
+
+ uint32_t regs[R_MAX];
+} XilinxUARTLite;
+
+static void uart_update_irq(XilinxUARTLite *s)
+{
+ unsigned int irq;
+
+ if (s->rx_fifo_len)
+ s->regs[R_STATUS] |= STATUS_IE;
+
+ irq = (s->regs[R_STATUS] & STATUS_IE) && (s->regs[R_CTRL] & CONTROL_IE);
+ qemu_set_irq(s->irq, irq);
+}
+
+static void uart_update_status(XilinxUARTLite *s)
+{
+ uint32_t r;
+
+ r = s->regs[R_STATUS];
+ r &= ~7;
+ r |= 1 << 2; /* Tx fifo is always empty. We are fast :) */
+ r |= (s->rx_fifo_len == sizeof (s->rx_fifo)) << 1;
+ r |= (!!s->rx_fifo_len);
+ s->regs[R_STATUS] = r;
+}
+
+static uint64_t
+uart_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ XilinxUARTLite *s = opaque;
+ uint32_t r = 0;
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_RX:
+ r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 7];
+ if (s->rx_fifo_len)
+ s->rx_fifo_len--;
+ uart_update_status(s);
+ uart_update_irq(s);
+ qemu_chr_accept_input(s->chr);
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(s->regs))
+ r = s->regs[addr];
+ DUART(qemu_log("%s addr=%x v=%x\n", __func__, addr, r));
+ break;
+ }
+ return r;
+}
+
+static void
+uart_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ XilinxUARTLite *s = opaque;
+ uint32_t value = val64;
+ unsigned char ch = value;
+
+ addr >>= 2;
+ switch (addr)
+ {
+ case R_STATUS:
+ hw_error("write to UART STATUS?\n");
+ break;
+
+ case R_CTRL:
+ if (value & CONTROL_RST_RX) {
+ s->rx_fifo_pos = 0;
+ s->rx_fifo_len = 0;
+ }
+ s->regs[addr] = value;
+ break;
+
+ case R_TX:
+ if (s->chr)
+ qemu_chr_fe_write(s->chr, &ch, 1);
+
+ s->regs[addr] = value;
+
+ /* hax. */
+ s->regs[R_STATUS] |= STATUS_IE;
+ break;
+
+ default:
+ DUART(printf("%s addr=%x v=%x\n", __func__, addr, value));
+ if (addr < ARRAY_SIZE(s->regs))
+ s->regs[addr] = value;
+ break;
+ }
+ uart_update_status(s);
+ uart_update_irq(s);
+}
+
+static const MemoryRegionOps uart_ops = {
+ .read = uart_read,
+ .write = uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4
+ }
+};
+
+static void uart_rx(void *opaque, const uint8_t *buf, int size)
+{
+ XilinxUARTLite *s = opaque;
+
+ /* Got a byte. */
+ if (s->rx_fifo_len >= 8) {
+ printf("WARNING: UART dropped char.\n");
+ return;
+ }
+ s->rx_fifo[s->rx_fifo_pos] = *buf;
+ s->rx_fifo_pos++;
+ s->rx_fifo_pos &= 0x7;
+ s->rx_fifo_len++;
+
+ uart_update_status(s);
+ uart_update_irq(s);
+}
+
+static int uart_can_rx(void *opaque)
+{
+ XilinxUARTLite *s = opaque;
+
+ return s->rx_fifo_len < sizeof(s->rx_fifo);
+}
+
+static void uart_event(void *opaque, int event)
+{
+
+}
+
+static int xilinx_uartlite_init(SysBusDevice *dev)
+{
+ XilinxUARTLite *s = XILINX_UARTLITE(dev);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ uart_update_status(s);
+ memory_region_init_io(&s->mmio, OBJECT(s), &uart_ops, s,
+ "xlnx.xps-uartlite", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->mmio);
+
+ s->chr = qemu_char_get_next_serial();
+ if (s->chr)
+ qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s);
+ return 0;
+}
+
+static void xilinx_uartlite_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+ sdc->init = xilinx_uartlite_init;
+}
+
+static const TypeInfo xilinx_uartlite_info = {
+ .name = TYPE_XILINX_UARTLITE,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxUARTLite),
+ .class_init = xilinx_uartlite_class_init,
+};
+
+static void xilinx_uart_register_types(void)
+{
+ type_register_static(&xilinx_uartlite_info);
+}
+
+type_init(xilinx_uart_register_types)