diff options
author | Yang Zhang <yang.z.zhang@intel.com> | 2015-08-28 09:58:54 +0800 |
---|---|---|
committer | Yang Zhang <yang.z.zhang@intel.com> | 2015-09-01 12:44:00 +0800 |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/hw/char | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/hw/char')
29 files changed, 11309 insertions, 0 deletions
diff --git a/qemu/hw/char/Makefile.objs b/qemu/hw/char/Makefile.objs new file mode 100644 index 000000000..5931cc840 --- /dev/null +++ b/qemu/hw/char/Makefile.objs @@ -0,0 +1,29 @@ +common-obj-$(CONFIG_IPACK) += ipoctal232.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 +obj-$(CONFIG_DIGIC) += digic-uart.o +obj-$(CONFIG_STM32F2XX_USART) += stm32f2xx_usart.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 sclpconsole-lm.o + +obj-$(CONFIG_VIRTIO) += virtio-serial-bus.o diff --git a/qemu/hw/char/cadence_uart.c b/qemu/hw/char/cadence_uart.c new file mode 100644 index 000000000..9d379e5b1 --- /dev/null +++ b/qemu/hw/char/cadence_uart.c @@ -0,0 +1,536 @@ +/* + * 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/char/cadence_uart.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 +/* somewhat awkwardly, TTRIG is misaligned between SR and ISR */ +#define UART_SR_TTRIG 0x00002000 +#define UART_INTR_TTRIG 0x00000400 +/* 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_INTR_TOVR 0x00001000 + +#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 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) + + +static void uart_update_status(CadenceUARTState *s) +{ + s->r[R_SR] = 0; + + s->r[R_SR] |= s->rx_count == CADENCE_UART_RX_FIFO_SIZE ? UART_SR_INTR_RFUL + : 0; + s->r[R_SR] |= !s->rx_count ? UART_SR_INTR_REMPTY : 0; + s->r[R_SR] |= s->rx_count >= s->r[R_RTRIG] ? UART_SR_INTR_RTRIG : 0; + + s->r[R_SR] |= s->tx_count == CADENCE_UART_TX_FIFO_SIZE ? UART_SR_INTR_TFUL + : 0; + s->r[R_SR] |= !s->tx_count ? UART_SR_INTR_TEMPTY : 0; + s->r[R_SR] |= s->tx_count >= s->r[R_TTRIG] ? UART_SR_TTRIG : 0; + + s->r[R_CISR] |= s->r[R_SR] & UART_SR_TO_CISR_MASK; + s->r[R_CISR] |= s->r[R_SR] & UART_SR_TTRIG ? UART_INTR_TTRIG : 0; + qemu_set_irq(s->irq, !!(s->r[R_IMR] & s->r[R_CISR])); +} + +static void fifo_trigger_update(void *opaque) +{ + CadenceUARTState *s = opaque; + + s->r[R_CISR] |= UART_INTR_TIMEOUT; + + uart_update_status(s); +} + +static void uart_rx_reset(CadenceUARTState *s) +{ + s->rx_wpos = 0; + s->rx_count = 0; + if (s->chr) { + qemu_chr_accept_input(s->chr); + } +} + +static void uart_tx_reset(CadenceUARTState *s) +{ + s->tx_count = 0; +} + +static void uart_send_breaks(CadenceUARTState *s) +{ + int break_enabled = 1; + + if (s->chr) { + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK, + &break_enabled); + } +} + +static void uart_parameters_setup(CadenceUARTState *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; + if (s->chr) { + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); + } +} + +static int uart_can_receive(void *opaque) +{ + CadenceUARTState *s = opaque; + int ret = MAX(CADENCE_UART_RX_FIFO_SIZE, CADENCE_UART_TX_FIFO_SIZE); + uint32_t ch_mode = s->r[R_MR] & UART_MR_CHMODE; + + if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) { + ret = MIN(ret, CADENCE_UART_RX_FIFO_SIZE - s->rx_count); + } + if (ch_mode == REMOTE_LOOPBACK || ch_mode == ECHO_MODE) { + ret = MIN(ret, CADENCE_UART_TX_FIFO_SIZE - s->tx_count); + } + return ret; +} + +static void uart_ctrl_update(CadenceUARTState *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_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) +{ + CadenceUARTState *s = opaque; + uint64_t new_rx_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int i; + + if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) { + return; + } + + if (s->rx_count == CADENCE_UART_RX_FIFO_SIZE) { + s->r[R_CISR] |= UART_INTR_ROVR; + } else { + for (i = 0; i < size; i++) { + s->rx_fifo[s->rx_wpos] = buf[i]; + s->rx_wpos = (s->rx_wpos + 1) % CADENCE_UART_RX_FIFO_SIZE; + s->rx_count++; + } + timer_mod(s->fifo_trigger_handle, new_rx_time + + (s->char_tx_time * 4)); + } + uart_update_status(s); +} + +static gboolean cadence_uart_xmit(GIOChannel *chan, GIOCondition cond, + void *opaque) +{ + CadenceUARTState *s = opaque; + int ret; + + /* instant drain the fifo when there's no back-end */ + if (!s->chr) { + s->tx_count = 0; + return FALSE; + } + + if (!s->tx_count) { + return FALSE; + } + + ret = qemu_chr_fe_write(s->chr, s->tx_fifo, s->tx_count); + s->tx_count -= ret; + memmove(s->tx_fifo, s->tx_fifo + ret, s->tx_count); + + if (s->tx_count) { + int r = qemu_chr_fe_add_watch(s->chr, G_IO_OUT|G_IO_HUP, + cadence_uart_xmit, s); + assert(r); + } + + uart_update_status(s); + return FALSE; +} + +static void uart_write_tx_fifo(CadenceUARTState *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; + } + + if (size > CADENCE_UART_TX_FIFO_SIZE - s->tx_count) { + size = CADENCE_UART_TX_FIFO_SIZE - s->tx_count; + /* + * This can only be a guest error via a bad tx fifo register push, + * as can_receive() should stop remote loop and echo modes ever getting + * us to here. + */ + qemu_log_mask(LOG_GUEST_ERROR, "cadence_uart: TxFIFO overflow"); + s->r[R_CISR] |= UART_INTR_ROVR; + } + + memcpy(s->tx_fifo + s->tx_count, buf, size); + s->tx_count += size; + + cadence_uart_xmit(NULL, G_IO_OUT, s); +} + +static void uart_receive(void *opaque, const uint8_t *buf, int size) +{ + CadenceUARTState *s = 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) +{ + CadenceUARTState *s = 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(CadenceUARTState *s, uint32_t *c) +{ + if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) { + return; + } + + if (s->rx_count) { + uint32_t rx_rpos = (CADENCE_UART_RX_FIFO_SIZE + s->rx_wpos - + s->rx_count) % CADENCE_UART_RX_FIFO_SIZE; + *c = s->rx_fifo[rx_rpos]; + s->rx_count--; + + if (s->chr) { + qemu_chr_accept_input(s->chr); + } + } else { + *c = 0; + } + + uart_update_status(s); +} + +static void uart_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + CadenceUARTState *s = 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; + } + uart_update_status(s); +} + +static uint64_t uart_read(void *opaque, hwaddr offset, + unsigned size) +{ + CadenceUARTState *s = opaque; + uint32_t c = 0; + + offset >>= 2; + if (offset >= CADENCE_UART_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(DeviceState *dev) +{ + CadenceUARTState *s = CADENCE_UART(dev); + + 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); + + uart_update_status(s); +} + +static void cadence_uart_realize(DeviceState *dev, Error **errp) +{ + CadenceUARTState *s = CADENCE_UART(dev); + + s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL, + fifo_trigger_update, s); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + s->chr = qemu_char_get_next_serial(); + + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_receive, uart_receive, + uart_event, s); + } +} + +static void cadence_uart_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + CadenceUARTState *s = CADENCE_UART(obj); + + memory_region_init_io(&s->iomem, obj, &uart_ops, s, "uart", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + + s->char_tx_time = (get_ticks_per_sec() / 9600) * 10; +} + +static int cadence_uart_post_load(void *opaque, int version_id) +{ + CadenceUARTState *s = opaque; + + uart_parameters_setup(s); + uart_update_status(s); + return 0; +} + +static const VMStateDescription vmstate_cadence_uart = { + .name = "cadence_uart", + .version_id = 2, + .minimum_version_id = 2, + .post_load = cadence_uart_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(r, CadenceUARTState, CADENCE_UART_R_MAX), + VMSTATE_UINT8_ARRAY(rx_fifo, CadenceUARTState, + CADENCE_UART_RX_FIFO_SIZE), + VMSTATE_UINT8_ARRAY(tx_fifo, CadenceUARTState, + CADENCE_UART_TX_FIFO_SIZE), + VMSTATE_UINT32(rx_count, CadenceUARTState), + VMSTATE_UINT32(tx_count, CadenceUARTState), + VMSTATE_UINT32(rx_wpos, CadenceUARTState), + VMSTATE_TIMER_PTR(fifo_trigger_handle, CadenceUARTState), + VMSTATE_END_OF_LIST() + } +}; + +static void cadence_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = cadence_uart_realize; + dc->vmsd = &vmstate_cadence_uart; + dc->reset = cadence_uart_reset; + /* Reason: realize() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo cadence_uart_info = { + .name = TYPE_CADENCE_UART, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(CadenceUARTState), + .instance_init = cadence_uart_init, + .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/qemu/hw/char/debugcon.c b/qemu/hw/char/debugcon.c new file mode 100644 index 000000000..36f1c4adb --- /dev/null +++ b/qemu/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_UINT32("iobase", ISADebugconState, iobase, 0xe9), + DEFINE_PROP_CHR("chardev", ISADebugconState, state.chr), + DEFINE_PROP_UINT32("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/qemu/hw/char/digic-uart.c b/qemu/hw/char/digic-uart.c new file mode 100644 index 000000000..6d44576ff --- /dev/null +++ b/qemu/hw/char/digic-uart.c @@ -0,0 +1,197 @@ +/* + * QEMU model of the Canon DIGIC UART block. + * + * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com> + * + * This model is based on reverse engineering efforts + * made by CHDK (http://chdk.wikia.com) and + * Magic Lantern (http://www.magiclantern.fm) projects + * contributors. + * + * See "Serial terminal" docs here: + * http://magiclantern.wikia.com/wiki/Register_Map#Misc_Registers + * + * The QEMU model of the Milkymist UART block by Michael Walle + * is used as a template. + * + * 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. + * + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "sysemu/char.h" + +#include "hw/char/digic-uart.h" + +enum { + ST_RX_RDY = (1 << 0), + ST_TX_RDY = (1 << 1), +}; + +static uint64_t digic_uart_read(void *opaque, hwaddr addr, + unsigned size) +{ + DigicUartState *s = opaque; + uint64_t ret = 0; + + addr >>= 2; + + switch (addr) { + case R_RX: + s->reg_st &= ~(ST_RX_RDY); + ret = s->reg_rx; + break; + + case R_ST: + ret = s->reg_st; + break; + + default: + qemu_log_mask(LOG_UNIMP, + "digic-uart: read access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + } + + return ret; +} + +static void digic_uart_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + DigicUartState *s = opaque; + unsigned char ch = value; + + addr >>= 2; + + switch (addr) { + case R_TX: + if (s->chr) { + qemu_chr_fe_write_all(s->chr, &ch, 1); + } + break; + + case R_ST: + /* + * Ignore write to R_ST. + * + * The point is that this register is actively used + * during receiving and transmitting symbols, + * but we don't know the function of most of bits. + * + * Ignoring writes to R_ST is only a simplification + * of the model. It has no perceptible side effects + * for existing guests. + */ + break; + + default: + qemu_log_mask(LOG_UNIMP, + "digic-uart: write access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + } +} + +static const MemoryRegionOps uart_mmio_ops = { + .read = digic_uart_read, + .write = digic_uart_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int uart_can_rx(void *opaque) +{ + DigicUartState *s = opaque; + + return !(s->reg_st & ST_RX_RDY); +} + +static void uart_rx(void *opaque, const uint8_t *buf, int size) +{ + DigicUartState *s = opaque; + + assert(uart_can_rx(opaque)); + + s->reg_st |= ST_RX_RDY; + s->reg_rx = *buf; +} + +static void uart_event(void *opaque, int event) +{ +} + +static void digic_uart_reset(DeviceState *d) +{ + DigicUartState *s = DIGIC_UART(d); + + s->reg_rx = 0; + s->reg_st = ST_TX_RDY; +} + +static void digic_uart_realize(DeviceState *dev, Error **errp) +{ + DigicUartState *s = DIGIC_UART(dev); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + s->chr = qemu_char_get_next_serial(); + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + } +} + +static void digic_uart_init(Object *obj) +{ + DigicUartState *s = DIGIC_UART(obj); + + memory_region_init_io(&s->regs_region, OBJECT(s), &uart_mmio_ops, s, + TYPE_DIGIC_UART, 0x18); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->regs_region); +} + +static const VMStateDescription vmstate_digic_uart = { + .name = "digic-uart", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(reg_rx, DigicUartState), + VMSTATE_UINT32(reg_st, DigicUartState), + VMSTATE_END_OF_LIST() + } +}; + +static void digic_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = digic_uart_realize; + dc->reset = digic_uart_reset; + dc->vmsd = &vmstate_digic_uart; + /* Reason: realize() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo digic_uart_info = { + .name = TYPE_DIGIC_UART, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DigicUartState), + .instance_init = digic_uart_init, + .class_init = digic_uart_class_init, +}; + +static void digic_uart_register_types(void) +{ + type_register_static(&digic_uart_info); +} + +type_init(digic_uart_register_types) diff --git a/qemu/hw/char/escc.c b/qemu/hw/char/escc.c new file mode 100644 index 000000000..ba653efd6 --- /dev/null +++ b/qemu/hw/char/escc.c @@ -0,0 +1,1052 @@ +/* + * 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 "ui/input.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; + QemuInputHandlerState *hs; +} 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, + .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, + .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 qcode_to_keycode[Q_KEY_CODE_MAX] = { + [Q_KEY_CODE_SHIFT] = 99, + [Q_KEY_CODE_SHIFT_R] = 110, + [Q_KEY_CODE_ALT] = 19, + [Q_KEY_CODE_ALT_R] = 13, + [Q_KEY_CODE_ALTGR] = 13, + [Q_KEY_CODE_CTRL] = 76, + [Q_KEY_CODE_CTRL_R] = 76, + [Q_KEY_CODE_ESC] = 29, + [Q_KEY_CODE_1] = 30, + [Q_KEY_CODE_2] = 31, + [Q_KEY_CODE_3] = 32, + [Q_KEY_CODE_4] = 33, + [Q_KEY_CODE_5] = 34, + [Q_KEY_CODE_6] = 35, + [Q_KEY_CODE_7] = 36, + [Q_KEY_CODE_8] = 37, + [Q_KEY_CODE_9] = 38, + [Q_KEY_CODE_0] = 39, + [Q_KEY_CODE_MINUS] = 40, + [Q_KEY_CODE_EQUAL] = 41, + [Q_KEY_CODE_BACKSPACE] = 43, + [Q_KEY_CODE_TAB] = 53, + [Q_KEY_CODE_Q] = 54, + [Q_KEY_CODE_W] = 55, + [Q_KEY_CODE_E] = 56, + [Q_KEY_CODE_R] = 57, + [Q_KEY_CODE_T] = 58, + [Q_KEY_CODE_Y] = 59, + [Q_KEY_CODE_U] = 60, + [Q_KEY_CODE_I] = 61, + [Q_KEY_CODE_O] = 62, + [Q_KEY_CODE_P] = 63, + [Q_KEY_CODE_BRACKET_LEFT] = 64, + [Q_KEY_CODE_BRACKET_RIGHT] = 65, + [Q_KEY_CODE_RET] = 89, + [Q_KEY_CODE_A] = 77, + [Q_KEY_CODE_S] = 78, + [Q_KEY_CODE_D] = 79, + [Q_KEY_CODE_F] = 80, + [Q_KEY_CODE_G] = 81, + [Q_KEY_CODE_H] = 82, + [Q_KEY_CODE_J] = 83, + [Q_KEY_CODE_K] = 84, + [Q_KEY_CODE_L] = 85, + [Q_KEY_CODE_SEMICOLON] = 86, + [Q_KEY_CODE_APOSTROPHE] = 87, + [Q_KEY_CODE_GRAVE_ACCENT] = 42, + [Q_KEY_CODE_BACKSLASH] = 88, + [Q_KEY_CODE_Z] = 100, + [Q_KEY_CODE_X] = 101, + [Q_KEY_CODE_C] = 102, + [Q_KEY_CODE_V] = 103, + [Q_KEY_CODE_B] = 104, + [Q_KEY_CODE_N] = 105, + [Q_KEY_CODE_M] = 106, + [Q_KEY_CODE_COMMA] = 107, + [Q_KEY_CODE_DOT] = 108, + [Q_KEY_CODE_SLASH] = 109, + [Q_KEY_CODE_ASTERISK] = 47, + [Q_KEY_CODE_SPC] = 121, + [Q_KEY_CODE_CAPS_LOCK] = 119, + [Q_KEY_CODE_F1] = 5, + [Q_KEY_CODE_F2] = 6, + [Q_KEY_CODE_F3] = 8, + [Q_KEY_CODE_F4] = 10, + [Q_KEY_CODE_F5] = 12, + [Q_KEY_CODE_F6] = 14, + [Q_KEY_CODE_F7] = 16, + [Q_KEY_CODE_F8] = 17, + [Q_KEY_CODE_F9] = 18, + [Q_KEY_CODE_F10] = 7, + [Q_KEY_CODE_NUM_LOCK] = 98, + [Q_KEY_CODE_SCROLL_LOCK] = 23, + [Q_KEY_CODE_KP_DIVIDE] = 46, + [Q_KEY_CODE_KP_MULTIPLY] = 47, + [Q_KEY_CODE_KP_SUBTRACT] = 71, + [Q_KEY_CODE_KP_ADD] = 125, + [Q_KEY_CODE_KP_ENTER] = 90, + [Q_KEY_CODE_KP_DECIMAL] = 50, + [Q_KEY_CODE_KP_0] = 94, + [Q_KEY_CODE_KP_1] = 112, + [Q_KEY_CODE_KP_2] = 113, + [Q_KEY_CODE_KP_3] = 114, + [Q_KEY_CODE_KP_4] = 91, + [Q_KEY_CODE_KP_5] = 92, + [Q_KEY_CODE_KP_6] = 93, + [Q_KEY_CODE_KP_7] = 68, + [Q_KEY_CODE_KP_8] = 69, + [Q_KEY_CODE_KP_9] = 70, + [Q_KEY_CODE_LESS] = 124, + [Q_KEY_CODE_F11] = 9, + [Q_KEY_CODE_F12] = 11, + [Q_KEY_CODE_HOME] = 52, + [Q_KEY_CODE_PGUP] = 96, + [Q_KEY_CODE_PGDN] = 123, + [Q_KEY_CODE_END] = 74, + [Q_KEY_CODE_LEFT] = 24, + [Q_KEY_CODE_UP] = 20, + [Q_KEY_CODE_DOWN] = 27, + [Q_KEY_CODE_RIGHT] = 28, + [Q_KEY_CODE_INSERT] = 44, + [Q_KEY_CODE_DELETE] = 66, + [Q_KEY_CODE_STOP] = 1, + [Q_KEY_CODE_AGAIN] = 3, + [Q_KEY_CODE_PROPS] = 25, + [Q_KEY_CODE_UNDO] = 26, + [Q_KEY_CODE_FRONT] = 49, + [Q_KEY_CODE_COPY] = 51, + [Q_KEY_CODE_OPEN] = 72, + [Q_KEY_CODE_PASTE] = 73, + [Q_KEY_CODE_FIND] = 95, + [Q_KEY_CODE_CUT] = 97, + [Q_KEY_CODE_LF] = 111, + [Q_KEY_CODE_HELP] = 118, + [Q_KEY_CODE_META_L] = 120, + [Q_KEY_CODE_META_R] = 122, + [Q_KEY_CODE_COMPOSE] = 67, + [Q_KEY_CODE_PRINT] = 22, + [Q_KEY_CODE_SYSRQ] = 21, +}; + +static void sunkbd_handle_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + ChannelState *s = (ChannelState *)dev; + int qcode, keycode; + + assert(evt->kind == INPUT_EVENT_KIND_KEY); + qcode = qemu_input_key_value_to_qcode(evt->key->key); + trace_escc_sunkbd_event_in(qcode, QKeyCode_lookup[qcode], + evt->key->down); + + if (qcode == Q_KEY_CODE_CAPS_LOCK) { + if (evt->key->down) { + s->caps_lock_mode ^= 1; + if (s->caps_lock_mode == 2) { + return; /* Drop second press */ + } + } else { + s->caps_lock_mode ^= 2; + if (s->caps_lock_mode == 3) { + return; /* Drop first release */ + } + } + } + + if (qcode == Q_KEY_CODE_NUM_LOCK) { + if (evt->key->down) { + s->num_lock_mode ^= 1; + if (s->num_lock_mode == 2) { + return; /* Drop second press */ + } + } else { + s->num_lock_mode ^= 2; + if (s->num_lock_mode == 3) { + return; /* Drop first release */ + } + } + } + + keycode = qcode_to_keycode[qcode]; + if (!evt->key->down) { + keycode |= 0x80; + } + trace_escc_sunkbd_event_out(keycode); + put_queue(s, keycode); +} + +static QemuInputHandler sunkbd_handler = { + .name = "sun keyboard", + .mask = INPUT_EVENT_MASK_KEY, + .event = sunkbd_handle_event, +}; + +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, 0x21); /* en-us 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) { + s->chn[1].hs = qemu_input_handler_register((DeviceState *)(&s->chn[1]), + &sunkbd_handler); + } + + 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/qemu/hw/char/etraxfs_ser.c b/qemu/hw/char/etraxfs_ser.c new file mode 100644 index 000000000..857c13621 --- /dev/null +++ b/qemu/hw/char/etraxfs_ser.c @@ -0,0 +1,255 @@ +/* + * 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); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + 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; + /* Reason: init() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +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/qemu/hw/char/exynos4210_uart.c b/qemu/hw/char/exynos4210_uart.c new file mode 100644 index 000000000..7614e5860 --- /dev/null +++ b/qemu/hw/char/exynos4210_uart.c @@ -0,0 +1,676 @@ +/* + * 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 i; + + for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); 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 i; + + for (i = 0; i < ARRAY_SIZE(exynos4210_uart_regs); 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, + .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, + .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/qemu/hw/char/grlib_apbuart.c b/qemu/hw/char/grlib_apbuart.c new file mode 100644 index 000000000..35ef66177 --- /dev/null +++ b/qemu/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/qemu/hw/char/imx_serial.c b/qemu/hw/char/imx_serial.c new file mode 100644 index 000000000..f3fbc776b --- /dev/null +++ b/qemu/hw/char/imx_serial.c @@ -0,0 +1,472 @@ +/* + * 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, + .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/qemu/hw/char/ipoctal232.c b/qemu/hw/char/ipoctal232.c new file mode 100644 index 000000000..c8d5cdb36 --- /dev/null +++ b/qemu/hw/char/ipoctal232.c @@ -0,0 +1,603 @@ +/* + * 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 "hw/ipack/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 parent_obj; + + 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, + .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, + .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, + .fields = (VMStateField[]) { + VMSTATE_IPACK_DEVICE(parent_obj, 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) +{ + IPackDevice *idev = IPACK_DEVICE(dev); + /* 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(idev->irq[intno]); + } else { + qemu_irq_lower(idev->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 void ipoctal_realize(DeviceState *dev, Error **errp) +{ + IPOctalState *s = IPOCTAL(dev); + 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); + } + } +} + +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->realize = ipoctal_realize; + 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/qemu/hw/char/lm32_juart.c b/qemu/hw/char/lm32_juart.c new file mode 100644 index 000000000..62763f2f4 --- /dev/null +++ b/qemu/hw/char/lm32_juart.c @@ -0,0 +1,164 @@ +/* + * 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_all(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); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + 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, + .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; + /* Reason: init() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +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/qemu/hw/char/lm32_uart.c b/qemu/hw/char/lm32_uart.c new file mode 100644 index 000000000..837a46e8e --- /dev/null +++ b/qemu/hw/char/lm32_uart.c @@ -0,0 +1,304 @@ +/* + * 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_all(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); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + 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, + .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; + /* Reason: init() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +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/qemu/hw/char/mcf_uart.c b/qemu/hw/char/mcf_uart.c new file mode 100644 index 000000000..98fd44e66 --- /dev/null +++ b/qemu/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/qemu/hw/char/milkymist-uart.c b/qemu/hw/char/milkymist-uart.c new file mode 100644 index 000000000..9b89b7e63 --- /dev/null +++ b/qemu/hw/char/milkymist-uart.c @@ -0,0 +1,255 @@ +/* + * 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_all(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 void milkymist_uart_realize(DeviceState *dev, Error **errp) +{ + MilkymistUartState *s = MILKYMIST_UART(dev); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + s->chr = qemu_char_get_next_serial(); + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + } +} + +static void milkymist_uart_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + MilkymistUartState *s = MILKYMIST_UART(obj); + + sysbus_init_irq(sbd, &s->irq); + + memory_region_init_io(&s->regs_region, OBJECT(s), &uart_mmio_ops, s, + "milkymist-uart", R_MAX * 4); + sysbus_init_mmio(sbd, &s->regs_region); +} + +static const VMStateDescription vmstate_milkymist_uart = { + .name = "milkymist-uart", + .version_id = 1, + .minimum_version_id = 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); + + dc->realize = milkymist_uart_realize; + dc->reset = milkymist_uart_reset; + dc->vmsd = &vmstate_milkymist_uart; + /* Reason: realize() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo milkymist_uart_info = { + .name = TYPE_MILKYMIST_UART, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MilkymistUartState), + .instance_init = milkymist_uart_init, + .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/qemu/hw/char/omap_uart.c b/qemu/hw/char/omap_uart.c new file mode 100644 index 000000000..88f20943e --- /dev/null +++ b/qemu/hw/char/omap_uart.c @@ -0,0 +1,188 @@ +/* + * 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) { + omap_badwidth_write8(opaque, addr, value); + return; + } + + 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/qemu/hw/char/parallel.c b/qemu/hw/char/parallel.c new file mode 100644 index 000000000..c2b553f0d --- /dev/null +++ b/qemu/hw/char/parallel.c @@ -0,0 +1,643 @@ +/* + * 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 const VMStateDescription vmstate_parallel_isa = { + .name = "parallel_isa", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(state.dataw, ISAParallelState), + VMSTATE_UINT8(state.datar, ISAParallelState), + VMSTATE_UINT8(state.status, ISAParallelState), + VMSTATE_UINT8(state.control, ISAParallelState), + VMSTATE_INT32(state.irq_pending, ISAParallelState), + VMSTATE_INT32(state.epp_timeout, ISAParallelState), + VMSTATE_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, ¶llel_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_UINT32("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->vmsd = &vmstate_parallel_isa; + 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(¶llel_isa_info); +} + +type_init(parallel_register_types) diff --git a/qemu/hw/char/pl011.c b/qemu/hw/char/pl011.c new file mode 100644 index 000000000..eac6fac09 --- /dev/null +++ b/qemu/hw/char/pl011.c @@ -0,0 +1,342 @@ +/* + * 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 rsr; + 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; + s->rsr = c >> 8; + pl011_update(s); + if (s->chr) { + qemu_chr_accept_input(s->chr); + } + return c; + case 1: /* UARTRSR */ + return s->rsr; + 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: /* UARTRSR/UARTECR */ + s->rsr = 0; + 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 */ + /* Reset the FIFO state on FIFO enable or disable */ + if ((s->lcr ^ value) & 0x10) { + s->read_count = 0; + s->read_pos = 0; + } + 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->lcr & 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 = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32(readbuff, PL011State), + VMSTATE_UINT32(flags, PL011State), + VMSTATE_UINT32(lcr, PL011State), + VMSTATE_UINT32(rsr, 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); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + 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; + /* Reason: realize() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +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/qemu/hw/char/sclpconsole-lm.c b/qemu/hw/char/sclpconsole-lm.c new file mode 100644 index 000000000..02ac80b65 --- /dev/null +++ b/qemu/hw/char/sclpconsole-lm.c @@ -0,0 +1,383 @@ +/* + * SCLP event types + * Operations Command - Line Mode input + * Message - Line Mode output + * + * Copyright IBM, Corp. 2013 + * + * Authors: + * Heinz Graalfs <graalfs@linux.vnet.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 "sysemu/char.h" + +#include "hw/s390x/sclp.h" +#include "hw/s390x/event-facility.h" +#include "hw/s390x/ebcdic.h" + +#define SIZE_BUFFER 4096 +#define NEWLINE "\n" + +typedef struct OprtnsCommand { + EventBufferHeader header; + MDMSU message_unit; + char data[0]; +} QEMU_PACKED OprtnsCommand; + +/* max size for line-mode data in 4K SCCB page */ +#define SIZE_CONSOLE_BUFFER (SCCB_DATA_LEN - sizeof(OprtnsCommand)) + +typedef struct SCLPConsoleLM { + SCLPEvent event; + CharDriverState *chr; + bool echo; /* immediate echo of input if true */ + uint32_t write_errors; /* errors writing to char layer */ + uint32_t length; /* length of byte stream in buffer */ + uint8_t buf[SIZE_CONSOLE_BUFFER]; +} SCLPConsoleLM; + +/* +* Character layer call-back functions + * + * Allow 1 character at a time + * + * Accumulate bytes from character layer in console buffer, + * event_pending is set when a newline character is encountered + * + * The maximum command line length is limited by the maximum + * space available in an SCCB. Line mode console input is sent + * truncated to the guest in case it doesn't fit into the SCCB. + */ + +static int chr_can_read(void *opaque) +{ + SCLPConsoleLM *scon = opaque; + + if (scon->event.event_pending) { + return 0; + } + return 1; +} + +static void chr_read(void *opaque, const uint8_t *buf, int size) +{ + SCLPConsoleLM *scon = opaque; + + assert(size == 1); + + if (*buf == '\r' || *buf == '\n') { + scon->event.event_pending = true; + sclp_service_interrupt(0); + return; + } + if (scon->length == SIZE_CONSOLE_BUFFER) { + /* Eat the character, but still process CR and LF. */ + return; + } + scon->buf[scon->length] = *buf; + scon->length += 1; + if (scon->echo) { + qemu_chr_fe_write(scon->chr, buf, size); + } +} + +/* functions to be called by event facility */ + +static bool can_handle_event(uint8_t type) +{ + return type == SCLP_EVENT_MESSAGE || type == SCLP_EVENT_PMSGCMD; +} + +static unsigned int send_mask(void) +{ + return SCLP_EVENT_MASK_OP_CMD | SCLP_EVENT_MASK_PMSGCMD; +} + +static unsigned int receive_mask(void) +{ + return SCLP_EVENT_MASK_MSG | SCLP_EVENT_MASK_PMSGCMD; +} + +/* + * Triggered by SCLP's read_event_data + * - convert ASCII byte stream to EBCDIC and + * - copy converted data into provided (SCLP) buffer + */ +static int get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size, + int avail) +{ + int len; + + SCLPConsoleLM *cons = DO_UPCAST(SCLPConsoleLM, event, event); + + len = cons->length; + /* data need to fit into provided SCLP buffer */ + if (len > avail) { + return 1; + } + + ebcdic_put(buf, (char *)&cons->buf, len); + *size = len; + cons->length = 0; + /* data provided and no more data pending */ + event->event_pending = false; + qemu_notify_event(); + return 0; +} + +static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr, + int *slen) +{ + int avail, rc; + size_t src_len; + uint8_t *to; + OprtnsCommand *oc = (OprtnsCommand *) evt_buf_hdr; + + if (!event->event_pending) { + /* no data pending */ + return 0; + } + + to = (uint8_t *)&oc->data; + avail = *slen - sizeof(OprtnsCommand); + rc = get_console_data(event, to, &src_len, avail); + if (rc) { + /* data didn't fit, try next SCCB */ + return 1; + } + + oc->message_unit.mdmsu.gds_id = GDS_ID_MDSMU; + oc->message_unit.mdmsu.length = cpu_to_be16(sizeof(struct MDMSU)); + + oc->message_unit.cpmsu.gds_id = GDS_ID_CPMSU; + oc->message_unit.cpmsu.length = + cpu_to_be16(sizeof(struct MDMSU) - sizeof(GdsVector)); + + oc->message_unit.text_command.gds_id = GDS_ID_TEXTCMD; + oc->message_unit.text_command.length = + cpu_to_be16(sizeof(struct MDMSU) - (2 * sizeof(GdsVector))); + + oc->message_unit.self_def_text_message.key = GDS_KEY_SELFDEFTEXTMSG; + oc->message_unit.self_def_text_message.length = + cpu_to_be16(sizeof(struct MDMSU) - (3 * sizeof(GdsVector))); + + oc->message_unit.text_message.key = GDS_KEY_TEXTMSG; + oc->message_unit.text_message.length = + cpu_to_be16(sizeof(GdsSubvector) + src_len); + + oc->header.length = cpu_to_be16(sizeof(OprtnsCommand) + src_len); + oc->header.type = SCLP_EVENT_OPRTNS_COMMAND; + *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 int write_console_data(SCLPEvent *event, const uint8_t *buf, int len) +{ + int ret = 0; + const uint8_t *buf_offset; + + SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event); + + if (!scon->chr) { + /* If there's no backend, we can just say we consumed all data. */ + return len; + } + + buf_offset = buf; + while (len > 0) { + ret = qemu_chr_fe_write(scon->chr, buf, len); + if (ret == 0) { + /* a pty doesn't seem to be connected - no error */ + len = 0; + } else if (ret == -EAGAIN || (ret > 0 && ret < len)) { + len -= ret; + buf_offset += ret; + } else { + len = 0; + } + } + + return ret; +} + +static int process_mdb(SCLPEvent *event, MDBO *mdbo) +{ + int rc; + int len; + uint8_t buffer[SIZE_BUFFER]; + + len = be16_to_cpu(mdbo->length); + len -= sizeof(mdbo->length) + sizeof(mdbo->type) + + sizeof(mdbo->mto.line_type_flags) + + sizeof(mdbo->mto.alarm_control) + + sizeof(mdbo->mto._reserved); + + assert(len <= SIZE_BUFFER); + + /* convert EBCDIC SCLP contents to ASCII console message */ + ascii_put(buffer, mdbo->mto.message, len); + rc = write_console_data(event, (uint8_t *)NEWLINE, 1); + if (rc < 0) { + return rc; + } + return write_console_data(event, buffer, len); +} + +static int write_event_data(SCLPEvent *event, EventBufferHeader *ebh) +{ + int len; + int written; + int errors = 0; + MDBO *mdbo; + SclpMsg *data = (SclpMsg *) ebh; + SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event); + + len = be16_to_cpu(data->mdb.header.length); + if (len < sizeof(data->mdb.header)) { + return SCLP_RC_INCONSISTENT_LENGTHS; + } + len -= sizeof(data->mdb.header); + + /* first check message buffers */ + mdbo = data->mdb.mdbo; + while (len > 0) { + if (be16_to_cpu(mdbo->length) > len + || be16_to_cpu(mdbo->length) == 0) { + return SCLP_RC_INCONSISTENT_LENGTHS; + } + len -= be16_to_cpu(mdbo->length); + mdbo = (void *) mdbo + be16_to_cpu(mdbo->length); + } + + /* then execute */ + len = be16_to_cpu(data->mdb.header.length) - sizeof(data->mdb.header); + mdbo = data->mdb.mdbo; + while (len > 0) { + switch (be16_to_cpu(mdbo->type)) { + case MESSAGE_TEXT: + /* message text object */ + written = process_mdb(event, mdbo); + if (written < 0) { + /* character layer error */ + errors++; + } + break; + default: /* ignore */ + break; + } + len -= be16_to_cpu(mdbo->length); + mdbo = (void *) mdbo + be16_to_cpu(mdbo->length); + } + if (errors) { + scon->write_errors += errors; + } + data->header.flags = SCLP_EVENT_BUFFER_ACCEPTED; + + return SCLP_RC_NORMAL_COMPLETION; +} + +/* functions for live migration */ + +static const VMStateDescription vmstate_sclplmconsole = { + .name = "sclplmconsole", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_BOOL(event.event_pending, SCLPConsoleLM), + VMSTATE_UINT32(write_errors, SCLPConsoleLM), + VMSTATE_UINT32(length, SCLPConsoleLM), + VMSTATE_UINT8_ARRAY(buf, SCLPConsoleLM, SIZE_CONSOLE_BUFFER), + VMSTATE_END_OF_LIST() + } +}; + +/* qemu object creation and initialization functions */ + +/* tell character layer our call-back functions */ + +static int console_init(SCLPEvent *event) +{ + static bool console_available; + + SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event); + + if (console_available) { + error_report("Multiple line-mode operator consoles are not supported"); + return -1; + } + console_available = true; + + if (scon->chr) { + qemu_chr_add_handlers(scon->chr, chr_can_read, chr_read, NULL, scon); + } + + return 0; +} + +static int console_exit(SCLPEvent *event) +{ + return 0; +} + +static void console_reset(DeviceState *dev) +{ + SCLPEvent *event = SCLP_EVENT(dev); + SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event); + + event->event_pending = false; + scon->length = 0; + scon->write_errors = 0; +} + +static Property console_properties[] = { + DEFINE_PROP_CHR("chardev", SCLPConsoleLM, chr), + DEFINE_PROP_UINT32("write_errors", SCLPConsoleLM, write_errors, 0), + DEFINE_PROP_BOOL("echo", SCLPConsoleLM, echo, true), + 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; + dc->reset = console_reset; + dc->vmsd = &vmstate_sclplmconsole; + ec->init = console_init; + ec->exit = console_exit; + ec->get_send_mask = send_mask; + ec->get_receive_mask = receive_mask; + ec->can_handle_event = can_handle_event; + ec->read_event_data = read_event_data; + ec->write_event_data = write_event_data; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo sclp_console_info = { + .name = "sclplmconsole", + .parent = TYPE_SCLP_EVENT, + .instance_size = sizeof(SCLPConsoleLM), + .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/qemu/hw/char/sclpconsole.c b/qemu/hw/char/sclpconsole.c new file mode 100644 index 000000000..b014c7f52 --- /dev/null +++ b/qemu/hw/char/sclpconsole.c @@ -0,0 +1,285 @@ +/* + * 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; + uint8_t iov[SIZE_BUFFER_VT220]; + uint32_t iov_sclp; /* offset in buf for SCLP read operation */ + uint32_t iov_bs; /* offset in buf for char layer read operation */ + uint32_t iov_data_len; /* length of byte stream in buffer */ + uint32_t iov_sclp_rest; /* length of byte stream not read via SCLP */ + bool notify; /* qemu_notify_event() req'd if true */ +} 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; + int avail = SIZE_BUFFER_VT220 - scon->iov_data_len; + + if (avail == 0) { + scon->notify = true; + } + return avail; +} + +/* 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); + /* 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[scon->iov_bs], buf, size); + scon->iov_data_len += size; + scon->iov_sclp_rest += size; + scon->iov_bs += size; + scon->event.event_pending = true; + sclp_service_interrupt(0); +} + +/* functions to be called by event facility */ + +static bool can_handle_event(uint8_t type) +{ + return type == 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[cons->iov_sclp], cons->iov_sclp_rest); + *size = cons->iov_sclp_rest + 1; + cons->iov_sclp = 0; + cons->iov_bs = 0; + 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[cons->iov_sclp], avail); + *size = avail + 1; + cons->iov_sclp_rest -= avail; + cons->iov_sclp += avail; + /* more data pending */ + } + if (cons->notify) { + cons->notify = false; + qemu_notify_event(); + } +} + +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 const VMStateDescription vmstate_sclpconsole = { + .name = "sclpconsole", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_BOOL(event.event_pending, SCLPConsole), + VMSTATE_UINT8_ARRAY(iov, SCLPConsole, SIZE_BUFFER_VT220), + VMSTATE_UINT32(iov_sclp, SCLPConsole), + VMSTATE_UINT32(iov_bs, SCLPConsole), + VMSTATE_UINT32(iov_data_len, SCLPConsole), + VMSTATE_UINT32(iov_sclp_rest, SCLPConsole), + VMSTATE_END_OF_LIST() + } +}; + +/* 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; + if (scon->chr) { + qemu_chr_add_handlers(scon->chr, chr_can_read, + chr_read, NULL, scon); + } + + return 0; +} + +static void console_reset(DeviceState *dev) +{ + SCLPEvent *event = SCLP_EVENT(dev); + SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event); + + event->event_pending = false; + scon->iov_sclp = 0; + scon->iov_bs = 0; + scon->iov_data_len = 0; + scon->iov_sclp_rest = 0; + scon->notify = false; +} + +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; + dc->reset = console_reset; + dc->vmsd = &vmstate_sclpconsole; + ec->init = console_init; + ec->exit = console_exit; + ec->get_send_mask = send_mask; + ec->get_receive_mask = receive_mask; + ec->can_handle_event = can_handle_event; + ec->read_event_data = read_event_data; + ec->write_event_data = write_event_data; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +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/qemu/hw/char/serial-isa.c b/qemu/hw/char/serial-isa.c new file mode 100644 index 000000000..f3db024d0 --- /dev/null +++ b/qemu/hw/char/serial-isa.c @@ -0,0 +1,145 @@ +/* + * 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_UINT32("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) + +static void serial_isa_init(ISABus *bus, int index, CharDriverState *chr) +{ + DeviceState *dev; + ISADevice *isadev; + + isadev = isa_create(bus, TYPE_ISA_SERIAL); + dev = DEVICE(isadev); + qdev_prop_set_uint32(dev, "index", index); + qdev_prop_set_chr(dev, "chardev", chr); + qdev_init_nofail(dev); +} + +void serial_hds_isa_init(ISABus *bus, int n) +{ + int i; + + assert(n <= MAX_SERIAL_PORTS); + + for (i = 0; i < n; ++i) { + if (serial_hds[i]) { + serial_isa_init(bus, i, serial_hds[i]); + } + } +} diff --git a/qemu/hw/char/serial-pci.c b/qemu/hw/char/serial-pci.c new file mode 100644 index 000000000..1c8b9be5c --- /dev/null +++ b/qemu/hw/char/serial-pci.c @@ -0,0 +1,274 @@ +/* + * 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" + +#define PCI_SERIAL_MAX_PORTS 4 + +typedef struct PCISerialState { + PCIDevice dev; + SerialState state; + uint8_t prog_if; +} 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; + uint8_t prog_if; +} PCIMultiSerialState; + +static void multi_serial_pci_exit(PCIDevice *dev); + +static void serial_pci_realize(PCIDevice *dev, Error **errp) +{ + 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) { + error_propagate(errp, err); + return; + } + + pci->dev.config[PCI_CLASS_PROG] = pci->prog_if; + pci->dev.config[PCI_INTERRUPT_PIN] = 0x01; + s->irq = pci_allocate_irq(&pci->dev); + + 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); +} + +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; + } + } + pci_set_irq(&pci->dev, pending); +} + +static void multi_serial_pci_realize(PCIDevice *dev, Error **errp) +{ + PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); + PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev); + SerialState *s; + Error *err = NULL; + int i, nr_ports = 0; + + switch (pc->device_id) { + case 0x0003: + nr_ports = 2; + break; + case 0x0004: + nr_ports = 4; + break; + } + assert(nr_ports > 0); + assert(nr_ports <= PCI_SERIAL_MAX_PORTS); + + pci->dev.config[PCI_CLASS_PROG] = pci->prog_if; + pci->dev.config[PCI_INTERRUPT_PIN] = 0x01; + memory_region_init(&pci->iobar, OBJECT(pci), "multiserial", 8 * nr_ports); + pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->iobar); + pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci, + nr_ports); + + for (i = 0; i < nr_ports; i++) { + s = pci->state + i; + s->baudbase = 115200; + serial_realize_core(s, &err); + if (err != NULL) { + error_propagate(errp, err); + multi_serial_pci_exit(dev); + return; + } + 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); + pci->ports++; + } +} + +static void serial_pci_exit(PCIDevice *dev) +{ + PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev); + SerialState *s = &pci->state; + + serial_exit_core(s); + qemu_free_irq(s->irq); +} + +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_del_subregion(&pci->iobar, &s->io); + g_free(pci->name[i]); + } + qemu_free_irqs(pci->irqs, pci->ports); +} + +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_UINT8("prog_if", PCISerialState, prog_if, 0x02), + 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_UINT8("prog_if", PCIMultiSerialState, prog_if, 0x02), + 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_UINT8("prog_if", PCIMultiSerialState, prog_if, 0x02), + 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->realize = serial_pci_realize; + 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->realize = multi_serial_pci_realize; + 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->realize = multi_serial_pci_realize; + 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/qemu/hw/char/serial.c b/qemu/hw/char/serial.c new file mode 100644 index 000000000..513d73c27 --- /dev/null +++ b/qemu/hw/char/serial.c @@ -0,0 +1,972 @@ +/* + * 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" +#include "qemu/error-report.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; + + timer_del(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) + timer_mod(s->modem_status_poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + get_ticks_per_sec() / 100); +} + +static gboolean serial_xmit(GIOChannel *chan, GIOCondition cond, void *opaque) +{ + SerialState *s = opaque; + + do { + assert(!(s->lsr & UART_LSR_TEMT)); + if (s->tsr_retry <= 0) { + assert(!(s->lsr & UART_LSR_THRE)); + + if (s->fcr & UART_FCR_FE) { + assert(!fifo8_is_empty(&s->xmit_fifo)); + s->tsr = fifo8_pop(&s->xmit_fifo); + if (!s->xmit_fifo.num) { + s->lsr |= UART_LSR_THRE; + } + } else { + s->tsr = s->thr; + s->lsr |= UART_LSR_THRE; + } + if ((s->lsr & UART_LSR_THRE) && !s->thr_ipending) { + s->thr_ipending = 1; + serial_update_irq(s); + } + } + + 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|G_IO_HUP, + serial_xmit, s) > 0) { + s->tsr_retry++; + return FALSE; + } + s->tsr_retry = 0; + } else { + s->tsr_retry = 0; + } + + /* Transmit another byte if it is already available. It is only + possible when FIFO is enabled and not empty. */ + } while (!(s->lsr & UART_LSR_THRE)); + + s->last_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + s->lsr |= UART_LSR_TEMT; + + return FALSE; +} + + +/* Setter for FCR. + is_load flag means, that value is set while loading VM state + and interrupt should not be invoked */ +static void serial_write_fcr(SerialState *s, uint8_t val) +{ + /* Set fcr - val only has the bits that are supposed to "stick" */ + s->fcr = val; + + 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; + } +} + +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->thr_ipending = 0; + s->lsr &= ~UART_LSR_THRE; + s->lsr &= ~UART_LSR_TEMT; + serial_update_irq(s); + if (s->tsr_retry <= 0) { + 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 { + uint8_t changed = (s->ier ^ val) & 0x0f; + 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 ((changed & UART_IER_MSI) && s->poll_msl >= 0) { + if (s->ier & UART_IER_MSI) { + s->poll_msl = 1; + serial_update_msl(s); + } else { + timer_del(s->modem_status_poll); + s->poll_msl = 0; + } + } + + /* Turning on the THRE interrupt on IER can trigger the interrupt + * if LSR.THRE=1, even if it had been masked before by reading IIR. + * This is not in the datasheet, but Windows relies on it. It is + * unclear if THRE has to be resampled every time THRI becomes + * 1, or only on the rising edge. Bochs does the latter, and Windows + * always toggles IER to all zeroes and back to all ones, so do the + * same. + * + * If IER.THRI is zero, thr_ipending is not used. Set it to zero + * so that the thr_ipending subsection is not migrated. + */ + if (changed & UART_IER_THRI) { + if ((s->ier & UART_IER_THRI) && (s->lsr & UART_LSR_THRE)) { + s->thr_ipending = 1; + } else { + s->thr_ipending = 0; + } + } + + if (changed) { + serial_update_irq(s); + } + } + break; + case 2: + /* 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) { + s->lsr &= ~(UART_LSR_DR | UART_LSR_BI); + timer_del(s->fifo_timeout_timer); + s->timeout_ipending = 0; + fifo8_reset(&s->recv_fifo); + } + + if (val & UART_FCR_XFR) { + s->lsr |= UART_LSR_THRE; + s->thr_ipending = 1; + fifo8_reset(&s->xmit_fifo); + } + + serial_write_fcr(s, 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 */ + timer_mod(s->modem_status_poll, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 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 { + timer_mod(s->fifo_timeout_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 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 */ + timer_mod(s->fifo_timeout_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 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_pre_load(void *opaque) +{ + SerialState *s = opaque; + s->thr_ipending = -1; + s->poll_msl = -1; + return 0; +} + +static int serial_post_load(void *opaque, int version_id) +{ + SerialState *s = opaque; + + if (version_id < 3) { + s->fcr_vmstate = 0; + } + if (s->thr_ipending == -1) { + s->thr_ipending = ((s->iir & UART_IIR_ID) == UART_IIR_THRI); + } + s->last_break_enable = (s->lcr >> 6) & 1; + /* Initialize fcr via setter to perform essential side-effects */ + serial_write_fcr(s, s->fcr_vmstate); + serial_update_parameters(s); + return 0; +} + +static bool serial_thr_ipending_needed(void *opaque) +{ + SerialState *s = opaque; + + if (s->ier & UART_IER_THRI) { + bool expected_value = ((s->iir & UART_IIR_ID) == UART_IIR_THRI); + return s->thr_ipending != expected_value; + } else { + /* LSR.THRE will be sampled again when the interrupt is + * enabled. thr_ipending is not used in this case, do + * not migrate it. + */ + return false; + } +} + +static const VMStateDescription vmstate_serial_thr_ipending = { + .name = "serial/thr_ipending", + .version_id = 1, + .minimum_version_id = 1, + .needed = serial_thr_ipending_needed, + .fields = (VMStateField[]) { + VMSTATE_INT32(thr_ipending, SerialState), + VMSTATE_END_OF_LIST() + } +}; + +static bool serial_tsr_needed(void *opaque) +{ + SerialState *s = (SerialState *)opaque; + return s->tsr_retry != 0; +} + +static const VMStateDescription vmstate_serial_tsr = { + .name = "serial/tsr", + .version_id = 1, + .minimum_version_id = 1, + .needed = serial_tsr_needed, + .fields = (VMStateField[]) { + VMSTATE_INT32(tsr_retry, SerialState), + VMSTATE_UINT8(thr, SerialState), + VMSTATE_UINT8(tsr, SerialState), + VMSTATE_END_OF_LIST() + } +}; + +static bool serial_recv_fifo_needed(void *opaque) +{ + SerialState *s = (SerialState *)opaque; + return !fifo8_is_empty(&s->recv_fifo); + +} + +static const VMStateDescription vmstate_serial_recv_fifo = { + .name = "serial/recv_fifo", + .version_id = 1, + .minimum_version_id = 1, + .needed = serial_recv_fifo_needed, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(recv_fifo, SerialState, 1, vmstate_fifo8, Fifo8), + VMSTATE_END_OF_LIST() + } +}; + +static bool serial_xmit_fifo_needed(void *opaque) +{ + SerialState *s = (SerialState *)opaque; + return !fifo8_is_empty(&s->xmit_fifo); +} + +static const VMStateDescription vmstate_serial_xmit_fifo = { + .name = "serial/xmit_fifo", + .version_id = 1, + .minimum_version_id = 1, + .needed = serial_xmit_fifo_needed, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(xmit_fifo, SerialState, 1, vmstate_fifo8, Fifo8), + VMSTATE_END_OF_LIST() + } +}; + +static bool serial_fifo_timeout_timer_needed(void *opaque) +{ + SerialState *s = (SerialState *)opaque; + return timer_pending(s->fifo_timeout_timer); +} + +static const VMStateDescription vmstate_serial_fifo_timeout_timer = { + .name = "serial/fifo_timeout_timer", + .version_id = 1, + .minimum_version_id = 1, + .needed = serial_fifo_timeout_timer_needed, + .fields = (VMStateField[]) { + VMSTATE_TIMER_PTR(fifo_timeout_timer, SerialState), + VMSTATE_END_OF_LIST() + } +}; + +static bool serial_timeout_ipending_needed(void *opaque) +{ + SerialState *s = (SerialState *)opaque; + return s->timeout_ipending != 0; +} + +static const VMStateDescription vmstate_serial_timeout_ipending = { + .name = "serial/timeout_ipending", + .version_id = 1, + .minimum_version_id = 1, + .needed = serial_timeout_ipending_needed, + .fields = (VMStateField[]) { + VMSTATE_INT32(timeout_ipending, SerialState), + VMSTATE_END_OF_LIST() + } +}; + +static bool serial_poll_needed(void *opaque) +{ + SerialState *s = (SerialState *)opaque; + return s->poll_msl >= 0; +} + +static const VMStateDescription vmstate_serial_poll = { + .name = "serial/poll", + .version_id = 1, + .needed = serial_poll_needed, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(poll_msl, SerialState), + VMSTATE_TIMER_PTR(modem_status_poll, SerialState), + VMSTATE_END_OF_LIST() + } +}; + +const VMStateDescription vmstate_serial = { + .name = "serial", + .version_id = 3, + .minimum_version_id = 2, + .pre_save = serial_pre_save, + .pre_load = serial_pre_load, + .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() + }, + .subsections = (const VMStateDescription*[]) { + &vmstate_serial_thr_ipending, + &vmstate_serial_tsr, + &vmstate_serial_recv_fifo, + &vmstate_serial_xmit_fifo, + &vmstate_serial_fifo_timeout_timer, + &vmstate_serial_timeout_ipending, + &vmstate_serial_poll, + NULL + } +}; + +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; + + s->timeout_ipending = 0; + timer_del(s->fifo_timeout_timer); + timer_del(s->modem_status_poll); + + fifo8_reset(&s->recv_fifo); + fifo8_reset(&s->xmit_fifo); + + s->last_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + s->thr_ipending = 0; + s->last_break_enable = 0; + qemu_irq_lower(s->irq); + + serial_update_msl(s); + s->msr &= ~UART_MSR_ANY_DELTA; +} + +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 = timer_new_ns(QEMU_CLOCK_VIRTUAL, (QEMUTimerCB *) serial_update_msl, s); + + s->fifo_timeout_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, (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); + serial_reset(s); +} + +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) { + error_report_err(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) { + error_report_err(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); + return s; +} diff --git a/qemu/hw/char/sh_serial.c b/qemu/hw/char/sh_serial.c new file mode 100644 index 000000000..9328dd1b5 --- /dev/null +++ b/qemu/hw/char/sh_serial.c @@ -0,0 +1,408 @@ +/* + * 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; + case 0x18: + ret = s->fcr; + break; + 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/qemu/hw/char/spapr_vty.c b/qemu/hw/char/spapr_vty.c new file mode 100644 index 000000000..36b328b9a --- /dev/null +++ b/qemu/hw/char/spapr_vty.c @@ -0,0 +1,245 @@ +#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]; + } + + qemu_chr_accept_input(dev->chardev); + + 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 void spapr_vty_realize(VIOsPAPRDevice *sdev, Error **errp) +{ + VIOsPAPRVTYDevice *dev = VIO_SPAPR_VTY_DEVICE(sdev); + + if (!dev->chardev) { + error_setg(errp, "chardev property not set"); + return; + } + + qemu_chr_add_handlers(dev->chardev, vty_can_receive, + vty_receive, NULL, dev); +} + +/* Forward declaration */ +static target_ulong h_put_term_char(PowerPCCPU *cpu, sPAPRMachineState *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, sPAPRMachineState *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, + .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->realize = spapr_vty_realize; + k->dt_name = "vty"; + k->dt_type = "serial"; + k->dt_compatible = "hvterm1"; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + 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), TYPE_VIO_SPAPR_VTY_DEVICE)) { + 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(sPAPRMachineState *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); + } + + if (!object_dynamic_cast(OBJECT(sdev), TYPE_VIO_SPAPR_VTY_DEVICE)) { + return NULL; + } + + 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/qemu/hw/char/stm32f2xx_usart.c b/qemu/hw/char/stm32f2xx_usart.c new file mode 100644 index 000000000..c9d3a1be7 --- /dev/null +++ b/qemu/hw/char/stm32f2xx_usart.c @@ -0,0 +1,232 @@ +/* + * STM32F2XX USART + * + * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me> + * + * 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/stm32f2xx_usart.h" + +#ifndef STM_USART_ERR_DEBUG +#define STM_USART_ERR_DEBUG 0 +#endif + +#define DB_PRINT_L(lvl, fmt, args...) do { \ + if (STM_USART_ERR_DEBUG >= lvl) { \ + qemu_log("%s: " fmt, __func__, ## args); \ + } \ +} while (0); + +#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args) + +static int stm32f2xx_usart_can_receive(void *opaque) +{ + STM32F2XXUsartState *s = opaque; + + if (!(s->usart_sr & USART_SR_RXNE)) { + return 1; + } + + return 0; +} + +static void stm32f2xx_usart_receive(void *opaque, const uint8_t *buf, int size) +{ + STM32F2XXUsartState *s = opaque; + + s->usart_dr = *buf; + + if (!(s->usart_cr1 & USART_CR1_UE && s->usart_cr1 & USART_CR1_RE)) { + /* USART not enabled - drop the chars */ + DB_PRINT("Dropping the chars\n"); + return; + } + + s->usart_sr |= USART_SR_RXNE; + + if (s->usart_cr1 & USART_CR1_RXNEIE) { + qemu_set_irq(s->irq, 1); + } + + DB_PRINT("Receiving: %c\n", s->usart_dr); +} + +static void stm32f2xx_usart_reset(DeviceState *dev) +{ + STM32F2XXUsartState *s = STM32F2XX_USART(dev); + + s->usart_sr = USART_SR_RESET; + s->usart_dr = 0x00000000; + s->usart_brr = 0x00000000; + s->usart_cr1 = 0x00000000; + s->usart_cr2 = 0x00000000; + s->usart_cr3 = 0x00000000; + s->usart_gtpr = 0x00000000; + + qemu_set_irq(s->irq, 0); +} + +static uint64_t stm32f2xx_usart_read(void *opaque, hwaddr addr, + unsigned int size) +{ + STM32F2XXUsartState *s = opaque; + uint64_t retvalue; + + DB_PRINT("Read 0x%"HWADDR_PRIx"\n", addr); + + switch (addr) { + case USART_SR: + retvalue = s->usart_sr; + s->usart_sr &= ~USART_SR_TC; + if (s->chr) { + qemu_chr_accept_input(s->chr); + } + return retvalue; + case USART_DR: + DB_PRINT("Value: 0x%" PRIx32 ", %c\n", s->usart_dr, (char) s->usart_dr); + s->usart_sr |= USART_SR_TXE; + s->usart_sr &= ~USART_SR_RXNE; + if (s->chr) { + qemu_chr_accept_input(s->chr); + } + qemu_set_irq(s->irq, 0); + return s->usart_dr & 0x3FF; + case USART_BRR: + return s->usart_brr; + case USART_CR1: + return s->usart_cr1; + case USART_CR2: + return s->usart_cr2; + case USART_CR3: + return s->usart_cr3; + case USART_GTPR: + return s->usart_gtpr; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr); + return 0; + } + + return 0; +} + +static void stm32f2xx_usart_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + STM32F2XXUsartState *s = opaque; + uint32_t value = val64; + unsigned char ch; + + DB_PRINT("Write 0x%" PRIx32 ", 0x%"HWADDR_PRIx"\n", value, addr); + + switch (addr) { + case USART_SR: + if (value <= 0x3FF) { + s->usart_sr = value; + } else { + s->usart_sr &= value; + } + if (!(s->usart_sr & USART_SR_RXNE)) { + qemu_set_irq(s->irq, 0); + } + return; + case USART_DR: + if (value < 0xF000) { + ch = value; + if (s->chr) { + qemu_chr_fe_write_all(s->chr, &ch, 1); + } + s->usart_sr |= USART_SR_TC; + s->usart_sr &= ~USART_SR_TXE; + } + return; + case USART_BRR: + s->usart_brr = value; + return; + case USART_CR1: + s->usart_cr1 = value; + if (s->usart_cr1 & USART_CR1_RXNEIE && + s->usart_sr & USART_SR_RXNE) { + qemu_set_irq(s->irq, 1); + } + return; + case USART_CR2: + s->usart_cr2 = value; + return; + case USART_CR3: + s->usart_cr3 = value; + return; + case USART_GTPR: + s->usart_gtpr = value; + return; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr); + } +} + +static const MemoryRegionOps stm32f2xx_usart_ops = { + .read = stm32f2xx_usart_read, + .write = stm32f2xx_usart_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void stm32f2xx_usart_init(Object *obj) +{ + STM32F2XXUsartState *s = STM32F2XX_USART(obj); + + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); + + memory_region_init_io(&s->mmio, obj, &stm32f2xx_usart_ops, s, + TYPE_STM32F2XX_USART, 0x2000); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + s->chr = qemu_char_get_next_serial(); + + if (s->chr) { + qemu_chr_add_handlers(s->chr, stm32f2xx_usart_can_receive, + stm32f2xx_usart_receive, NULL, s); + } +} + +static void stm32f2xx_usart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = stm32f2xx_usart_reset; + /* Reason: instance_init() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo stm32f2xx_usart_info = { + .name = TYPE_STM32F2XX_USART, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(STM32F2XXUsartState), + .instance_init = stm32f2xx_usart_init, + .class_init = stm32f2xx_usart_class_init, +}; + +static void stm32f2xx_usart_register_types(void) +{ + type_register_static(&stm32f2xx_usart_info); +} + +type_init(stm32f2xx_usart_register_types) diff --git a/qemu/hw/char/virtio-console.c b/qemu/hw/char/virtio-console.c new file mode 100644 index 000000000..2a867cb4e --- /dev/null +++ b/qemu/hw/char/virtio-console.c @@ -0,0 +1,217 @@ +/* + * 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" +#include "qapi-event.h" + +#define TYPE_VIRTIO_CONSOLE_SERIAL_PORT "virtserialport" +#define VIRTIO_CONSOLE(obj) \ + OBJECT_CHECK(VirtConsole, (obj), TYPE_VIRTIO_CONSOLE_SERIAL_PORT) + +typedef struct VirtConsole { + VirtIOSerialPort parent_obj; + + 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(VIRTIO_SERIAL_PORT(vcon), 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 = VIRTIO_CONSOLE(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|G_IO_HUP, + 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 = VIRTIO_CONSOLE(port); + DeviceState *dev = DEVICE(port); + + if (vcon->chr) { + qemu_chr_fe_set_open(vcon->chr, guest_connected); + } + + if (dev->id) { + qapi_event_send_vserport_change(dev->id, guest_connected, + &error_abort); + } +} + +static void guest_writable(VirtIOSerialPort *port) +{ + VirtConsole *vcon = VIRTIO_CONSOLE(port); + + if (vcon->chr) { + qemu_chr_accept_input(vcon->chr); + } +} + +/* 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(VIRTIO_SERIAL_PORT(vcon)); +} + +/* 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; + VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(vcon); + + trace_virtio_console_chr_read(port->id, size); + virtio_serial_write(port, buf, size); +} + +static void chr_event(void *opaque, int event) +{ + VirtConsole *vcon = opaque; + VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(vcon); + + trace_virtio_console_chr_event(port->id, event); + switch (event) { + case CHR_EVENT_OPENED: + virtio_serial_open(port); + break; + case CHR_EVENT_CLOSED: + if (vcon->watch) { + g_source_remove(vcon->watch); + vcon->watch = 0; + } + virtio_serial_close(port); + break; + } +} + +static void virtconsole_realize(DeviceState *dev, Error **errp) +{ + VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev); + VirtConsole *vcon = VIRTIO_CONSOLE(dev); + VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(dev); + + if (port->id == 0 && !k->is_console) { + error_setg(errp, "Port number 0 on virtio-serial devices reserved " + "for virtconsole devices for backward compatibility."); + return; + } + + if (vcon->chr) { + vcon->chr->explicit_fe_open = 1; + qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event, + vcon); + } +} + +static void virtconsole_unrealize(DeviceState *dev, Error **errp) +{ + VirtConsole *vcon = VIRTIO_CONSOLE(dev); + + if (vcon->watch) { + g_source_remove(vcon->watch); + } +} + +static void virtconsole_class_init(ObjectClass *klass, void *data) +{ + VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass); + + k->is_console = true; +} + +static const TypeInfo virtconsole_info = { + .name = "virtconsole", + .parent = TYPE_VIRTIO_CONSOLE_SERIAL_PORT, + .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->realize = virtconsole_realize; + k->unrealize = virtconsole_unrealize; + k->have_data = flush_buf; + k->set_guest_connected = set_guest_connected; + k->guest_writable = guest_writable; + dc->props = virtserialport_properties; +} + +static const TypeInfo virtserialport_info = { + .name = TYPE_VIRTIO_CONSOLE_SERIAL_PORT, + .parent = TYPE_VIRTIO_SERIAL_PORT, + .instance_size = sizeof(VirtConsole), + .class_init = virtserialport_class_init, +}; + +static void virtconsole_register_types(void) +{ + type_register_static(&virtserialport_info); + type_register_static(&virtconsole_info); +} + +type_init(virtconsole_register_types) diff --git a/qemu/hw/char/virtio-serial-bus.c b/qemu/hw/char/virtio-serial-bus.c new file mode 100644 index 000000000..bc56f5d52 --- /dev/null +++ b/qemu/hw/char/virtio-serial-bus.c @@ -0,0 +1,1134 @@ +/* + * 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/error-report.h" +#include "qemu/queue.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "hw/virtio/virtio-serial.h" +#include "hw/virtio/virtio-access.h" + +static struct VirtIOSerialDevices { + QLIST_HEAD(, VirtIOSerial) devices; +} vserdevices; + +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 VirtIOSerialPort *find_port_by_name(char *name) +{ + VirtIOSerial *vser; + + QLIST_FOREACH(vser, &vserdevices.devices, next) { + VirtIOSerialPort *port; + + QTAILQ_FOREACH(port, &vser->ports, next) { + if (port->name && !strcmp(port->name, name)) { + return port; + } + } + } + return NULL; +} + +static bool use_multiport(VirtIOSerial *vser) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(vser); + return virtio_has_feature(vdev, 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; + } + + /* TODO: detect a buffer that's too short, set NEEDS_RESET */ + iov_from_buf(elem.in_sg, elem.in_num, 0, 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) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(vser); + struct virtio_console_control cpkt; + + virtio_stl_p(vdev, &cpkt.id, port_id); + virtio_stw_p(vdev, &cpkt.event, event); + virtio_stw_p(vdev, &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) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(vser); + 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 = virtio_lduw_p(vdev, &gcpkt->event); + cpkt.value = virtio_lduw_p(vdev, &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, virtio_ldl_p(vdev, &gcpkt->id)); + if (!port) { + error_report("virtio-serial-bus: Unexpected port id %u for device %s", + virtio_ldl_p(vdev, &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) { + virtio_stl_p(vdev, &cpkt.id, port->id); + virtio_stw_p(vdev, &cpkt.event, VIRTIO_CONSOLE_PORT_NAME); + virtio_stw_p(vdev, &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) +{ + /* + * Users of virtio-serial would like to know when guest becomes + * writable again -- i.e. if a vq had stuff queued up and the + * guest wasn't reading at all, the host would not be able to + * write to the vq anymore. Once the guest reads off something, + * we can start queueing things up again. However, this call is + * made for each buffer addition by the guest -- even though free + * buffers existed prior to the current buffer addition. This is + * done so as not to maintain previous state, which will need + * additional live-migration-related changes. + */ + VirtIOSerial *vser; + VirtIOSerialPort *port; + VirtIOSerialPortClass *vsc; + + vser = VIRTIO_SERIAL(vdev); + port = find_port_by_vq(vser, vq); + + if (!port) { + return; + } + vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port); + + /* + * If guest_connected is false, this call is being made by the + * early-boot queueing up of descriptors, which is just noise for + * the host apps -- don't disturb them in that case. + */ + if (port->guest_connected && port->host_connected && vsc->guest_writable) { + vsc->guest_writable(port); + } +} + +static uint64_t get_features(VirtIODevice *vdev, uint64_t features, + Error **errp) +{ + VirtIOSerial *vser; + + vser = VIRTIO_SERIAL(vdev); + + if (vser->bus.max_nr_ports > 1) { + virtio_add_feature(&features, VIRTIO_CONSOLE_F_MULTIPORT); + } + return features; +} + +/* Guest requested config info */ +static void get_config(VirtIODevice *vdev, uint8_t *config_data) +{ + VirtIOSerial *vser = VIRTIO_SERIAL(vdev); + struct virtio_console_config *config = + (struct virtio_console_config *)config_data; + + config->cols = 0; + config->rows = 0; + config->max_nr_ports = virtio_tswap32(vdev, + vser->serial.max_virtserial_ports); +} + +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) +{ + /* The virtio device */ + virtio_save(VIRTIO_DEVICE(opaque), f); +} + +static void virtio_serial_save_device(VirtIODevice *vdev, QEMUFile *f) +{ + VirtIOSerial *s = VIRTIO_SERIAL(vdev); + VirtIOSerialPort *port; + uint32_t nr_active_ports; + unsigned int i, max_nr_ports; + struct virtio_console_config config; + + /* The config space (ignored on the far end in current versions) */ + get_config(vdev, (uint8_t *)&config); + qemu_put_be16s(f, &config.cols); + qemu_put_be16s(f, &config.rows); + qemu_put_be32s(f, &config.max_nr_ports); + + /* The ports map */ + max_nr_ports = s->serial.max_virtserial_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); + timer_free(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 = timer_new_ns(QEMU_CLOCK_VIRTUAL, + 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); + } + } + } + timer_mod(s->post_load->timer, 1); + return 0; +} + +static int virtio_serial_load(QEMUFile *f, void *opaque, int version_id) +{ + if (version_id > 3) { + return -EINVAL; + } + + /* The virtio device */ + return virtio_load(VIRTIO_DEVICE(opaque), f, version_id); +} + +static int virtio_serial_load_device(VirtIODevice *vdev, QEMUFile *f, + int version_id) +{ + VirtIOSerial *s = VIRTIO_SERIAL(vdev); + uint32_t max_nr_ports, nr_active_ports, ports_map; + unsigned int i; + int ret; + uint32_t tmp; + + if (version_id < 2) { + return 0; + } + + /* Unused */ + qemu_get_be16s(f, (uint16_t *) &tmp); + qemu_get_be16s(f, (uint16_t *) &tmp); + qemu_get_be32s(f, &tmp); + + max_nr_ports = s->serial.max_virtserial_ports; + 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 = vser->serial.max_virtserial_ports; + for (i = 0; i < (max_nr_ports + 31) / 32; i++) { + uint32_t map, zeroes; + + map = vser->ports_map[i]; + zeroes = ctz32(~map); + if (zeroes != 32) { + return zeroes + 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; + + /* + * Don't mark port 0 removed -- we explicitly reserve it for + * backward compat with older guests, ensure a virtconsole device + * unplug retains the reservation. + */ + if (port_id) { + 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 void virtser_port_device_realize(DeviceState *dev, Error **errp) +{ + VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev); + VirtIOSerialPortClass *vsc = VIRTIO_SERIAL_PORT_GET_CLASS(port); + VirtIOSerialBus *bus = VIRTIO_SERIAL_BUS(qdev_get_parent_bus(dev)); + int max_nr_ports; + bool plugging_port0; + Error *err = NULL; + + 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_setg(errp, "virtio-serial-bus: A port already exists at id %u", + port->id); + return; + } + + if (port->name != NULL && find_port_by_name(port->name)) { + error_setg(errp, "virtio-serial-bus: A port already exists by name %s", + port->name); + return; + } + + 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_setg(errp, "virtio-serial-bus: Maximum port limit for " + "this device reached"); + return; + } + } + } + + max_nr_ports = port->vser->serial.max_virtserial_ports; + if (port->id >= max_nr_ports) { + error_setg(errp, "virtio-serial-bus: Out-of-range port id specified, " + "max. allowed: %u", max_nr_ports - 1); + return; + } + + vsc->realize(dev, &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + port->elem.out_num = 0; +} + +static void virtser_port_device_plug(HotplugHandler *hotplug_dev, + DeviceState *dev, Error **errp) +{ + VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev); + + 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(hotplug_dev)); +} + +static void virtser_port_device_unrealize(DeviceState *dev, Error **errp) +{ + VirtIOSerialPort *port = VIRTIO_SERIAL_PORT(dev); + VirtIOSerialPortClass *vsc = VIRTIO_SERIAL_PORT_GET_CLASS(dev); + VirtIOSerial *vser = port->vser; + + qemu_bh_delete(port->bh); + remove_port(port->vser, port->id); + + QTAILQ_REMOVE(&vser->ports, port, next); + + if (vsc->unrealize) { + vsc->unrealize(dev, errp); + } +} + +static void virtio_serial_device_realize(DeviceState *dev, Error **errp) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIOSerial *vser = VIRTIO_SERIAL(dev); + uint32_t i, max_supported_ports; + + if (!vser->serial.max_virtserial_ports) { + error_setg(errp, "Maximum number of serial ports not specified"); + return; + } + + /* Each port takes 2 queues, and one pair is for the control queue */ + max_supported_ports = VIRTIO_QUEUE_MAX / 2 - 1; + + if (vser->serial.max_virtserial_ports > max_supported_ports) { + error_setg(errp, "maximum ports supported: %u", max_supported_ports); + return; + } + + /* We don't support emergency write, skip it for now. */ + /* TODO: cleaner fix, depending on host features. */ + virtio_init(vdev, "virtio-serial", VIRTIO_ID_CONSOLE, + offsetof(struct virtio_console_config, emerg_wr)); + + /* Spawn a new virtio-serial bus on which the ports will ride as devices */ + qbus_create_inplace(&vser->bus, sizeof(vser->bus), TYPE_VIRTIO_SERIAL_BUS, + dev, vdev->bus_name); + qbus_set_hotplug_handler(BUS(&vser->bus), DEVICE(vser), errp); + 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->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(dev, "virtio-console", -1, 3, virtio_serial_save, + virtio_serial_load, vser); + + QLIST_INSERT_HEAD(&vserdevices.devices, vser, next); +} + +static void virtio_serial_port_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_INPUT, k->categories); + k->bus_type = TYPE_VIRTIO_SERIAL_BUS; + k->realize = virtser_port_device_realize; + k->unrealize = virtser_port_device_unrealize; + 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 void virtio_serial_device_unrealize(DeviceState *dev, Error **errp) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIOSerial *vser = VIRTIO_SERIAL(dev); + + QLIST_REMOVE(vser, next); + + 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); + timer_del(vser->post_load->timer); + timer_free(vser->post_load->timer); + g_free(vser->post_load); + } + virtio_cleanup(vdev); +} + +static Property virtio_serial_properties[] = { + DEFINE_PROP_UINT32("max_ports", VirtIOSerial, serial.max_virtserial_ports, + 31), + 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); + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass); + + QLIST_INIT(&vserdevices.devices); + + dc->props = virtio_serial_properties; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + vdc->realize = virtio_serial_device_realize; + vdc->unrealize = virtio_serial_device_unrealize; + vdc->get_features = get_features; + vdc->get_config = get_config; + vdc->set_status = set_status; + vdc->reset = vser_reset; + vdc->save = virtio_serial_save_device; + vdc->load = virtio_serial_load_device; + hc->plug = virtser_port_device_plug; + hc->unplug = qdev_simple_device_unplug_cb; +} + +static const TypeInfo virtio_device_info = { + .name = TYPE_VIRTIO_SERIAL, + .parent = TYPE_VIRTIO_DEVICE, + .instance_size = sizeof(VirtIOSerial), + .class_init = virtio_serial_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_HOTPLUG_HANDLER }, + { } + } +}; + +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/qemu/hw/char/xen_console.c b/qemu/hw/char/xen_console.c new file mode 100644 index 000000000..eb7f450ab --- /dev/null +++ b/qemu/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/qemu/hw/char/xilinx_uartlite.c b/qemu/hw/char/xilinx_uartlite.c new file mode 100644 index 000000000..ef883a898 --- /dev/null +++ b/qemu/hw/char/xilinx_uartlite.c @@ -0,0 +1,248 @@ +/* + * 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 void xilinx_uartlite_reset(DeviceState *dev) +{ + uart_update_status(XILINX_UARTLITE(dev)); +} + +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 void xilinx_uartlite_realize(DeviceState *dev, Error **errp) +{ + XilinxUARTLite *s = XILINX_UARTLITE(dev); + + /* FIXME use a qdev chardev prop instead of qemu_char_get_next_serial() */ + s->chr = qemu_char_get_next_serial(); + if (s->chr) + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); +} + +static void xilinx_uartlite_init(Object *obj) +{ + XilinxUARTLite *s = XILINX_UARTLITE(obj); + + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); + + memory_region_init_io(&s->mmio, obj, &uart_ops, s, + "xlnx.xps-uartlite", R_MAX * 4); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio); +} + +static void xilinx_uartlite_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = xilinx_uartlite_reset; + dc->realize = xilinx_uartlite_realize; + /* Reason: realize() method uses qemu_char_get_next_serial() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo xilinx_uartlite_info = { + .name = TYPE_XILINX_UARTLITE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XilinxUARTLite), + .instance_init = xilinx_uartlite_init, + .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) |