diff options
Diffstat (limited to 'hw/char')
-rw-r--r-- | hw/char/Makefile.objs | 10 | ||||
-rw-r--r-- | hw/char/cadence_uart.c | 518 | ||||
-rw-r--r-- | hw/char/escc.c | 938 | ||||
-rw-r--r-- | hw/char/ipack.c | 115 | ||||
-rw-r--r-- | hw/char/ipoctal232.c | 605 | ||||
-rw-r--r-- | hw/char/parallel.c | 614 | ||||
-rw-r--r-- | hw/char/pl011.c | 330 | ||||
-rw-r--r-- | hw/char/serial-isa.c | 130 | ||||
-rw-r--r-- | hw/char/serial-pci.c | 252 | ||||
-rw-r--r-- | hw/char/serial.c | 789 | ||||
-rw-r--r-- | hw/char/tpci200.c | 671 | ||||
-rw-r--r-- | hw/char/virtio-console.c | 184 | ||||
-rw-r--r-- | hw/char/xen_console.c | 305 | ||||
-rw-r--r-- | hw/char/xilinx_uartlite.c | 231 |
14 files changed, 5692 insertions, 0 deletions
diff --git a/hw/char/Makefile.objs b/hw/char/Makefile.objs index e69de29bb2..eee23ff637 100644 --- a/hw/char/Makefile.objs +++ b/hw/char/Makefile.objs @@ -0,0 +1,10 @@ +common-obj-$(CONFIG_IPACK) += tpci200.o ipoctal232.o ipack.o +common-obj-$(CONFIG_ESCC) += escc.o +common-obj-$(CONFIG_PARALLEL) += parallel.o +common-obj-$(CONFIG_PL011) += pl011.o +common-obj-$(CONFIG_SERIAL) += serial.o serial-isa.o +common-obj-$(CONFIG_SERIAL_PCI) += serial-pci.o +common-obj-$(CONFIG_VIRTIO) += virtio-console.o +common-obj-$(CONFIG_XILINX) += xilinx_uartlite.o +common-obj-$(CONFIG_XEN_BACKEND) += xen_console.o +common-obj-$(CONFIG_CADENCE) += cadence_uart.o diff --git a/hw/char/cadence_uart.c b/hw/char/cadence_uart.c new file mode 100644 index 0000000000..421ec998d6 --- /dev/null +++ b/hw/char/cadence_uart.c @@ -0,0 +1,518 @@ +/* + * Device model for Cadence UART + * + * Copyright (c) 2010 Xilinx Inc. + * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com) + * Copyright (c) 2012 PetaLogix Pty Ltd. + * Written by Haibing Ma + * M.Habib + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/sysbus.h" +#include "char/char.h" +#include "qemu/timer.h" + +#ifdef CADENCE_UART_ERR_DEBUG +#define DB_PRINT(...) do { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } while (0); +#else + #define DB_PRINT(...) +#endif + +#define UART_SR_INTR_RTRIG 0x00000001 +#define UART_SR_INTR_REMPTY 0x00000002 +#define UART_SR_INTR_RFUL 0x00000004 +#define UART_SR_INTR_TEMPTY 0x00000008 +#define UART_SR_INTR_TFUL 0x00000010 +/* bits fields in CSR that correlate to CISR. If any of these bits are set in + * SR, then the same bit in CISR is set high too */ +#define UART_SR_TO_CISR_MASK 0x0000001F + +#define UART_INTR_ROVR 0x00000020 +#define UART_INTR_FRAME 0x00000040 +#define UART_INTR_PARE 0x00000080 +#define UART_INTR_TIMEOUT 0x00000100 +#define UART_INTR_DMSI 0x00000200 + +#define UART_SR_RACTIVE 0x00000400 +#define UART_SR_TACTIVE 0x00000800 +#define UART_SR_FDELT 0x00001000 + +#define UART_CR_RXRST 0x00000001 +#define UART_CR_TXRST 0x00000002 +#define UART_CR_RX_EN 0x00000004 +#define UART_CR_RX_DIS 0x00000008 +#define UART_CR_TX_EN 0x00000010 +#define UART_CR_TX_DIS 0x00000020 +#define UART_CR_RST_TO 0x00000040 +#define UART_CR_STARTBRK 0x00000080 +#define UART_CR_STOPBRK 0x00000100 + +#define UART_MR_CLKS 0x00000001 +#define UART_MR_CHRL 0x00000006 +#define UART_MR_CHRL_SH 1 +#define UART_MR_PAR 0x00000038 +#define UART_MR_PAR_SH 3 +#define UART_MR_NBSTOP 0x000000C0 +#define UART_MR_NBSTOP_SH 6 +#define UART_MR_CHMODE 0x00000300 +#define UART_MR_CHMODE_SH 8 +#define UART_MR_UCLKEN 0x00000400 +#define UART_MR_IRMODE 0x00000800 + +#define UART_DATA_BITS_6 (0x3 << UART_MR_CHRL_SH) +#define UART_DATA_BITS_7 (0x2 << UART_MR_CHRL_SH) +#define UART_PARITY_ODD (0x1 << UART_MR_PAR_SH) +#define UART_PARITY_EVEN (0x0 << UART_MR_PAR_SH) +#define UART_STOP_BITS_1 (0x3 << UART_MR_NBSTOP_SH) +#define UART_STOP_BITS_2 (0x2 << UART_MR_NBSTOP_SH) +#define NORMAL_MODE (0x0 << UART_MR_CHMODE_SH) +#define ECHO_MODE (0x1 << UART_MR_CHMODE_SH) +#define LOCAL_LOOPBACK (0x2 << UART_MR_CHMODE_SH) +#define REMOTE_LOOPBACK (0x3 << UART_MR_CHMODE_SH) + +#define RX_FIFO_SIZE 16 +#define TX_FIFO_SIZE 16 +#define UART_INPUT_CLK 50000000 + +#define R_CR (0x00/4) +#define R_MR (0x04/4) +#define R_IER (0x08/4) +#define R_IDR (0x0C/4) +#define R_IMR (0x10/4) +#define R_CISR (0x14/4) +#define R_BRGR (0x18/4) +#define R_RTOR (0x1C/4) +#define R_RTRIG (0x20/4) +#define R_MCR (0x24/4) +#define R_MSR (0x28/4) +#define R_SR (0x2C/4) +#define R_TX_RX (0x30/4) +#define R_BDIV (0x34/4) +#define R_FDEL (0x38/4) +#define R_PMIN (0x3C/4) +#define R_PWID (0x40/4) +#define R_TTRIG (0x44/4) + +#define R_MAX (R_TTRIG + 1) + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + uint32_t r[R_MAX]; + uint8_t r_fifo[RX_FIFO_SIZE]; + uint32_t rx_wpos; + uint32_t rx_count; + uint64_t char_tx_time; + CharDriverState *chr; + qemu_irq irq; + struct QEMUTimer *fifo_trigger_handle; + struct QEMUTimer *tx_time_handle; +} UartState; + +static void uart_update_status(UartState *s) +{ + s->r[R_CISR] |= s->r[R_SR] & UART_SR_TO_CISR_MASK; + qemu_set_irq(s->irq, !!(s->r[R_IMR] & s->r[R_CISR])); +} + +static void fifo_trigger_update(void *opaque) +{ + UartState *s = (UartState *)opaque; + + s->r[R_CISR] |= UART_INTR_TIMEOUT; + + uart_update_status(s); +} + +static void uart_tx_redo(UartState *s) +{ + uint64_t new_tx_time = qemu_get_clock_ns(vm_clock); + + qemu_mod_timer(s->tx_time_handle, new_tx_time + s->char_tx_time); + + s->r[R_SR] |= UART_SR_INTR_TEMPTY; + + uart_update_status(s); +} + +static void uart_tx_write(void *opaque) +{ + UartState *s = (UartState *)opaque; + + uart_tx_redo(s); +} + +static void uart_rx_reset(UartState *s) +{ + s->rx_wpos = 0; + s->rx_count = 0; + qemu_chr_accept_input(s->chr); + + s->r[R_SR] |= UART_SR_INTR_REMPTY; + s->r[R_SR] &= ~UART_SR_INTR_RFUL; +} + +static void uart_tx_reset(UartState *s) +{ + s->r[R_SR] |= UART_SR_INTR_TEMPTY; + s->r[R_SR] &= ~UART_SR_INTR_TFUL; +} + +static void uart_send_breaks(UartState *s) +{ + int break_enabled = 1; + + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK, + &break_enabled); +} + +static void uart_parameters_setup(UartState *s) +{ + QEMUSerialSetParams ssp; + unsigned int baud_rate, packet_size; + + baud_rate = (s->r[R_MR] & UART_MR_CLKS) ? + UART_INPUT_CLK / 8 : UART_INPUT_CLK; + + ssp.speed = baud_rate / (s->r[R_BRGR] * (s->r[R_BDIV] + 1)); + packet_size = 1; + + switch (s->r[R_MR] & UART_MR_PAR) { + case UART_PARITY_EVEN: + ssp.parity = 'E'; + packet_size++; + break; + case UART_PARITY_ODD: + ssp.parity = 'O'; + packet_size++; + break; + default: + ssp.parity = 'N'; + break; + } + + switch (s->r[R_MR] & UART_MR_CHRL) { + case UART_DATA_BITS_6: + ssp.data_bits = 6; + break; + case UART_DATA_BITS_7: + ssp.data_bits = 7; + break; + default: + ssp.data_bits = 8; + break; + } + + switch (s->r[R_MR] & UART_MR_NBSTOP) { + case UART_STOP_BITS_1: + ssp.stop_bits = 1; + break; + default: + ssp.stop_bits = 2; + break; + } + + packet_size += ssp.data_bits + ssp.stop_bits; + s->char_tx_time = (get_ticks_per_sec() / ssp.speed) * packet_size; + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); +} + +static int uart_can_receive(void *opaque) +{ + UartState *s = (UartState *)opaque; + + return RX_FIFO_SIZE - s->rx_count; +} + +static void uart_ctrl_update(UartState *s) +{ + if (s->r[R_CR] & UART_CR_TXRST) { + uart_tx_reset(s); + } + + if (s->r[R_CR] & UART_CR_RXRST) { + uart_rx_reset(s); + } + + s->r[R_CR] &= ~(UART_CR_TXRST | UART_CR_RXRST); + + if ((s->r[R_CR] & UART_CR_TX_EN) && !(s->r[R_CR] & UART_CR_TX_DIS)) { + uart_tx_redo(s); + } + + if (s->r[R_CR] & UART_CR_STARTBRK && !(s->r[R_CR] & UART_CR_STOPBRK)) { + uart_send_breaks(s); + } +} + +static void uart_write_rx_fifo(void *opaque, const uint8_t *buf, int size) +{ + UartState *s = (UartState *)opaque; + uint64_t new_rx_time = qemu_get_clock_ns(vm_clock); + int i; + + if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) { + return; + } + + s->r[R_SR] &= ~UART_SR_INTR_REMPTY; + + if (s->rx_count == RX_FIFO_SIZE) { + s->r[R_CISR] |= UART_INTR_ROVR; + } else { + for (i = 0; i < size; i++) { + s->r_fifo[s->rx_wpos] = buf[i]; + s->rx_wpos = (s->rx_wpos + 1) % RX_FIFO_SIZE; + s->rx_count++; + + if (s->rx_count == RX_FIFO_SIZE) { + s->r[R_SR] |= UART_SR_INTR_RFUL; + break; + } + + if (s->rx_count >= s->r[R_RTRIG]) { + s->r[R_SR] |= UART_SR_INTR_RTRIG; + } + } + qemu_mod_timer(s->fifo_trigger_handle, new_rx_time + + (s->char_tx_time * 4)); + } + uart_update_status(s); +} + +static void uart_write_tx_fifo(UartState *s, const uint8_t *buf, int size) +{ + if ((s->r[R_CR] & UART_CR_TX_DIS) || !(s->r[R_CR] & UART_CR_TX_EN)) { + return; + } + + while (size) { + size -= qemu_chr_fe_write(s->chr, buf, size); + } +} + +static void uart_receive(void *opaque, const uint8_t *buf, int size) +{ + UartState *s = (UartState *)opaque; + uint32_t ch_mode = s->r[R_MR] & UART_MR_CHMODE; + + if (ch_mode == NORMAL_MODE || ch_mode == ECHO_MODE) { + uart_write_rx_fifo(opaque, buf, size); + } + if (ch_mode == REMOTE_LOOPBACK || ch_mode == ECHO_MODE) { + uart_write_tx_fifo(s, buf, size); + } +} + +static void uart_event(void *opaque, int event) +{ + UartState *s = (UartState *)opaque; + uint8_t buf = '\0'; + + if (event == CHR_EVENT_BREAK) { + uart_write_rx_fifo(opaque, &buf, 1); + } + + uart_update_status(s); +} + +static void uart_read_rx_fifo(UartState *s, uint32_t *c) +{ + if ((s->r[R_CR] & UART_CR_RX_DIS) || !(s->r[R_CR] & UART_CR_RX_EN)) { + return; + } + + s->r[R_SR] &= ~UART_SR_INTR_RFUL; + + if (s->rx_count) { + uint32_t rx_rpos = + (RX_FIFO_SIZE + s->rx_wpos - s->rx_count) % RX_FIFO_SIZE; + *c = s->r_fifo[rx_rpos]; + s->rx_count--; + + if (!s->rx_count) { + s->r[R_SR] |= UART_SR_INTR_REMPTY; + } + qemu_chr_accept_input(s->chr); + } else { + *c = 0; + s->r[R_SR] |= UART_SR_INTR_REMPTY; + } + + if (s->rx_count < s->r[R_RTRIG]) { + s->r[R_SR] &= ~UART_SR_INTR_RTRIG; + } + uart_update_status(s); +} + +static void uart_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + UartState *s = (UartState *)opaque; + + DB_PRINT(" offset:%x data:%08x\n", (unsigned)offset, (unsigned)value); + offset >>= 2; + switch (offset) { + case R_IER: /* ier (wts imr) */ + s->r[R_IMR] |= value; + break; + case R_IDR: /* idr (wtc imr) */ + s->r[R_IMR] &= ~value; + break; + case R_IMR: /* imr (read only) */ + break; + case R_CISR: /* cisr (wtc) */ + s->r[R_CISR] &= ~value; + break; + case R_TX_RX: /* UARTDR */ + switch (s->r[R_MR] & UART_MR_CHMODE) { + case NORMAL_MODE: + uart_write_tx_fifo(s, (uint8_t *) &value, 1); + break; + case LOCAL_LOOPBACK: + uart_write_rx_fifo(opaque, (uint8_t *) &value, 1); + break; + } + break; + default: + s->r[offset] = value; + } + + switch (offset) { + case R_CR: + uart_ctrl_update(s); + break; + case R_MR: + uart_parameters_setup(s); + break; + } +} + +static uint64_t uart_read(void *opaque, hwaddr offset, + unsigned size) +{ + UartState *s = (UartState *)opaque; + uint32_t c = 0; + + offset >>= 2; + if (offset >= R_MAX) { + c = 0; + } else if (offset == R_TX_RX) { + uart_read_rx_fifo(s, &c); + } else { + c = s->r[offset]; + } + + DB_PRINT(" offset:%x data:%08x\n", (unsigned)(offset << 2), (unsigned)c); + return c; +} + +static const MemoryRegionOps uart_ops = { + .read = uart_read, + .write = uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void cadence_uart_reset(UartState *s) +{ + s->r[R_CR] = 0x00000128; + s->r[R_IMR] = 0; + s->r[R_CISR] = 0; + s->r[R_RTRIG] = 0x00000020; + s->r[R_BRGR] = 0x0000000F; + s->r[R_TTRIG] = 0x00000020; + + uart_rx_reset(s); + uart_tx_reset(s); + + s->rx_count = 0; + s->rx_wpos = 0; +} + +static int cadence_uart_init(SysBusDevice *dev) +{ + UartState *s = FROM_SYSBUS(UartState, dev); + + memory_region_init_io(&s->iomem, &uart_ops, s, "uart", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + + s->fifo_trigger_handle = qemu_new_timer_ns(vm_clock, + (QEMUTimerCB *)fifo_trigger_update, s); + + s->tx_time_handle = qemu_new_timer_ns(vm_clock, + (QEMUTimerCB *)uart_tx_write, s); + + s->char_tx_time = (get_ticks_per_sec() / 9600) * 10; + + s->chr = qemu_char_get_next_serial(); + + cadence_uart_reset(s); + + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_receive, uart_receive, + uart_event, s); + } + + return 0; +} + +static int cadence_uart_post_load(void *opaque, int version_id) +{ + UartState *s = opaque; + + uart_parameters_setup(s); + uart_update_status(s); + return 0; +} + +static const VMStateDescription vmstate_cadence_uart = { + .name = "cadence_uart", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = cadence_uart_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(r, UartState, R_MAX), + VMSTATE_UINT8_ARRAY(r_fifo, UartState, RX_FIFO_SIZE), + VMSTATE_UINT32(rx_count, UartState), + VMSTATE_UINT32(rx_wpos, UartState), + VMSTATE_TIMER(fifo_trigger_handle, UartState), + VMSTATE_TIMER(tx_time_handle, UartState), + VMSTATE_END_OF_LIST() + } +}; + +static void cadence_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = cadence_uart_init; + dc->vmsd = &vmstate_cadence_uart; +} + +static const TypeInfo cadence_uart_info = { + .name = "cadence_uart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(UartState), + .class_init = cadence_uart_class_init, +}; + +static void cadence_uart_register_types(void) +{ + type_register_static(&cadence_uart_info); +} + +type_init(cadence_uart_register_types) diff --git a/hw/char/escc.c b/hw/char/escc.c new file mode 100644 index 0000000000..067b055fee --- /dev/null +++ b/hw/char/escc.c @@ -0,0 +1,938 @@ +/* + * 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 "char/char.h" +#include "ui/console.h" +#include "trace.h" + +/* + * Chipset docs: + * "Z80C30/Z85C30/Z80230/Z85230/Z85233 SCC/ESCC User Manual", + * http://www.zilog.com/docs/serial/scc_escc_um.pdf + * + * On Sparc32 this is the serial port, mouse and keyboard part of chip STP2001 + * (Slave I/O), also produced as NCR89C105. See + * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt + * + * The serial ports implement full AMD AM8530 or Zilog Z8530 chips, + * mouse and keyboard ports don't implement all functions and they are + * only asynchronous. There is no DMA. + * + * Z85C30 is also used on PowerMacs. There are some small differences + * between Sparc version (sunzilog) and PowerMac (pmac): + * Offset between control and data registers + * There is some kind of lockup bug, but we can ignore it + * CTS is inverted + * DMA on pmac using DBDMA chip + * pmac can do IRDA and faster rates, sunzilog can only do 38400 + * pmac baud rate generator clock is 3.6864 MHz, sunzilog 4.9152 MHz + */ + +/* + * Modifications: + * 2006-Aug-10 Igor Kovalenko : Renamed KBDQueue to SERIOQueue, implemented + * serial mouse queue. + * Implemented serial mouse protocol. + * + * 2010-May-23 Artyom Tarasenko: Reworked IUS logic + */ + +typedef enum { + chn_a, chn_b, +} ChnID; + +#define CHN_C(s) ((s)->chn == chn_b? 'b' : 'a') + +typedef enum { + ser, kbd, mouse, +} ChnType; + +#define SERIO_QUEUE_SIZE 256 + +typedef struct { + uint8_t data[SERIO_QUEUE_SIZE]; + int rptr, wptr, count; +} SERIOQueue; + +#define SERIAL_REGS 16 +typedef struct ChannelState { + qemu_irq irq; + uint32_t rxint, txint, rxint_under_svc, txint_under_svc; + struct ChannelState *otherchn; + uint32_t reg; + uint8_t wregs[SERIAL_REGS], rregs[SERIAL_REGS]; + SERIOQueue queue; + CharDriverState *chr; + int e0_mode, led_mode, caps_lock_mode, num_lock_mode; + int disabled; + int clock; + uint32_t vmstate_dummy; + ChnID chn; // this channel, A (base+4) or B (base+0) + ChnType type; + uint8_t rx, tx; +} ChannelState; + +struct SerialState { + SysBusDevice busdev; + struct ChannelState chn[2]; + uint32_t it_shift; + MemoryRegion mmio; + uint32_t disabled; + uint32_t frequency; +}; + +#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) +{ + SerialState *s = container_of(d, SerialState, busdev.qdev); + + 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) +{ + SerialState *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(&serial->busdev.qdev); + 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) +{ + SerialState *serial = opaque; + ChannelState *s; + uint32_t saddr; + uint32_t ret; + int channel; + + saddr = (addr >> serial->it_shift) & 1; + channel = (addr >> (serial->it_shift + 1)) & 1; + s = &serial->chn[channel]; + switch (saddr) { + case SERIAL_CTRL: + trace_escc_mem_readb_ctrl(CHN_C(s), s->reg, s->rregs[s->reg]); + ret = s->rregs[s->reg]; + s->reg = 0; + return ret; + case SERIAL_DATA: + s->rregs[R_STATUS] &= ~STATUS_RXAV; + clr_rxint(s); + if (s->type == kbd || s->type == mouse) + ret = get_queue(s); + else + ret = s->rx; + trace_escc_mem_readb_data(CHN_C(s), ret); + if (s->chr) + qemu_chr_accept_input(s->chr); + return ret; + default: + break; + } + return 0; +} + +static const MemoryRegionOps escc_mem_ops = { + .read = escc_mem_read, + .write = escc_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static int serial_can_receive(void *opaque) +{ + ChannelState *s = opaque; + int ret; + + if (((s->wregs[W_RXCTRL] & RXCTRL_RXEN) == 0) // Rx not enabled + || ((s->rregs[R_STATUS] & STATUS_RXAV) == STATUS_RXAV)) + // char already available + ret = 0; + else + ret = 1; + return ret; +} + +static void serial_receive_byte(ChannelState *s, int ch) +{ + trace_escc_serial_receive_byte(CHN_C(s), ch); + s->rregs[R_STATUS] |= STATUS_RXAV; + s->rx = ch; + set_rxint(s); +} + +static void serial_receive_break(ChannelState *s) +{ + s->rregs[R_STATUS] |= STATUS_BRK; + escc_update_irq(s); +} + +static void serial_receive1(void *opaque, const uint8_t *buf, int size) +{ + ChannelState *s = opaque; + serial_receive_byte(s, buf[0]); +} + +static void serial_event(void *opaque, int event) +{ + ChannelState *s = opaque; + if (event == CHR_EVENT_BREAK) + serial_receive_break(s); +} + +static const VMStateDescription vmstate_escc_chn = { + .name ="escc_chn", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT32(vmstate_dummy, ChannelState), + VMSTATE_UINT32(reg, ChannelState), + VMSTATE_UINT32(rxint, ChannelState), + VMSTATE_UINT32(txint, ChannelState), + VMSTATE_UINT32(rxint_under_svc, ChannelState), + VMSTATE_UINT32(txint_under_svc, ChannelState), + VMSTATE_UINT8(rx, ChannelState), + VMSTATE_UINT8(tx, ChannelState), + VMSTATE_BUFFER(wregs, ChannelState), + VMSTATE_BUFFER(rregs, ChannelState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_escc = { + .name ="escc", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_STRUCT_ARRAY(chn, SerialState, 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; + SerialState *d; + + dev = qdev_create(NULL, "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 = FROM_SYSBUS(SerialState, s); + return &d->mmio; +} + +static const uint8_t keycodes[128] = { + 127, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 89, 76, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 86, 87, 42, 99, 88, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 47, 19, 121, 119, 5, 6, 8, 10, 12, + 14, 16, 17, 18, 7, 98, 23, 68, 69, 70, 71, 91, 92, 93, 125, 112, + 113, 114, 94, 50, 0, 0, 124, 9, 11, 0, 0, 0, 0, 0, 0, 0, + 90, 0, 46, 22, 13, 111, 52, 20, 96, 24, 28, 74, 27, 123, 44, 66, + 0, 45, 2, 4, 48, 0, 0, 21, 0, 0, 0, 0, 0, 120, 122, 67, +}; + +static const uint8_t e0_keycodes[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 109, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 68, 69, 70, 0, 91, 0, 93, 0, 112, + 113, 114, 94, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 3, 25, 26, 49, 52, 72, 73, 97, 99, 111, 118, 120, 122, 67, 0, +}; + +static void sunkbd_event(void *opaque, int ch) +{ + ChannelState *s = opaque; + int release = ch & 0x80; + + trace_escc_sunkbd_event_in(ch); + switch (ch) { + case 58: // Caps lock press + s->caps_lock_mode ^= 1; + if (s->caps_lock_mode == 2) + return; // Drop second press + break; + case 69: // Num lock press + s->num_lock_mode ^= 1; + if (s->num_lock_mode == 2) + return; // Drop second press + break; + case 186: // Caps lock release + s->caps_lock_mode ^= 2; + if (s->caps_lock_mode == 3) + return; // Drop first release + break; + case 197: // Num lock release + s->num_lock_mode ^= 2; + if (s->num_lock_mode == 3) + return; // Drop first release + break; + case 0xe0: + s->e0_mode = 1; + return; + default: + break; + } + if (s->e0_mode) { + s->e0_mode = 0; + ch = e0_keycodes[ch & 0x7f]; + } else { + ch = keycodes[ch & 0x7f]; + } + trace_escc_sunkbd_event_out(ch); + put_queue(s, ch | release); +} + +static void handle_kbd_command(ChannelState *s, int val) +{ + trace_escc_kbd_command(val); + if (s->led_mode) { // Ignore led byte + s->led_mode = 0; + return; + } + switch (val) { + case 1: // Reset, return type code + clear_queue(s); + put_queue(s, 0xff); + put_queue(s, 4); // Type 4 + put_queue(s, 0x7f); + break; + case 0xe: // Set leds + s->led_mode = 1; + break; + case 7: // Query layout + case 0xf: + clear_queue(s); + put_queue(s, 0xfe); + put_queue(s, 0); // XXX, layout? + break; + default: + break; + } +} + +static void sunmouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + ChannelState *s = opaque; + int ch; + + trace_escc_sunmouse_event(dx, dy, buttons_state); + ch = 0x80 | 0x7; /* protocol start byte, no buttons pressed */ + + if (buttons_state & MOUSE_EVENT_LBUTTON) + ch ^= 0x4; + if (buttons_state & MOUSE_EVENT_MBUTTON) + ch ^= 0x2; + if (buttons_state & MOUSE_EVENT_RBUTTON) + ch ^= 0x1; + + put_queue(s, ch); + + ch = dx; + + if (ch > 127) + ch = 127; + else if (ch < -127) + ch = -127; + + put_queue(s, ch & 0xff); + + ch = -dy; + + if (ch > 127) + ch = 127; + else if (ch < -127) + ch = -127; + + put_queue(s, ch & 0xff); + + // MSC protocol specify two extra motion bytes + + put_queue(s, 0); + put_queue(s, 0); +} + +void slavio_serial_ms_kbd_init(hwaddr base, qemu_irq irq, + int disabled, int clock, int it_shift) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "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) +{ + SerialState *s = FROM_SYSBUS(SerialState, 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, &escc_mem_ops, s, "escc", + ESCC_SIZE << s->it_shift); + sysbus_init_mmio(dev, &s->mmio); + + if (s->chn[0].type == mouse) { + qemu_add_mouse_event_handler(sunmouse_event, &s->chn[0], 0, + "QEMU Sun Mouse"); + } + if (s->chn[1].type == kbd) { + qemu_add_kbd_event_handler(sunkbd_event, &s->chn[1]); + } + + return 0; +} + +static Property escc_properties[] = { + DEFINE_PROP_UINT32("frequency", SerialState, frequency, 0), + DEFINE_PROP_UINT32("it_shift", SerialState, it_shift, 0), + DEFINE_PROP_UINT32("disabled", SerialState, disabled, 0), + DEFINE_PROP_UINT32("chnBtype", SerialState, chn[0].type, 0), + DEFINE_PROP_UINT32("chnAtype", SerialState, chn[1].type, 0), + DEFINE_PROP_CHR("chrB", SerialState, chn[0].chr), + DEFINE_PROP_CHR("chrA", SerialState, 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 = "escc", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SerialState), + .class_init = escc_class_init, +}; + +static void escc_register_types(void) +{ + type_register_static(&escc_info); +} + +type_init(escc_register_types) diff --git a/hw/char/ipack.c b/hw/char/ipack.c new file mode 100644 index 0000000000..b1f46c10a4 --- /dev/null +++ b/hw/char/ipack.c @@ -0,0 +1,115 @@ +/* + * QEMU IndustryPack emulation + * + * Copyright (C) 2012 Igalia, S.L. + * Author: Alberto Garcia <agarcia@igalia.com> + * + * This code is licensed under the GNU GPL v2 or (at your option) any + * later version. + */ + +#include "hw/ipack.h" + +IPackDevice *ipack_device_find(IPackBus *bus, int32_t slot) +{ + BusChild *kid; + + QTAILQ_FOREACH(kid, &BUS(bus)->children, sibling) { + DeviceState *qdev = kid->child; + IPackDevice *ip = IPACK_DEVICE(qdev); + if (ip->slot == slot) { + return ip; + } + } + return NULL; +} + +void ipack_bus_new_inplace(IPackBus *bus, DeviceState *parent, + const char *name, uint8_t n_slots, + qemu_irq_handler handler) +{ + qbus_create_inplace(&bus->qbus, TYPE_IPACK_BUS, parent, name); + bus->n_slots = n_slots; + bus->set_irq = handler; +} + +static int ipack_device_dev_init(DeviceState *qdev) +{ + IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(qdev)); + IPackDevice *dev = IPACK_DEVICE(qdev); + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev); + + if (dev->slot < 0) { + dev->slot = bus->free_slot; + } + if (dev->slot >= bus->n_slots) { + return -1; + } + bus->free_slot = dev->slot + 1; + + dev->irq = qemu_allocate_irqs(bus->set_irq, dev, 2); + + return k->init(dev); +} + +static int ipack_device_dev_exit(DeviceState *qdev) +{ + IPackDevice *dev = IPACK_DEVICE(qdev); + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(dev); + + if (k->exit) { + k->exit(dev); + } + + qemu_free_irqs(dev->irq); + + return 0; +} + +static Property ipack_device_props[] = { + DEFINE_PROP_INT32("slot", IPackDevice, slot, -1), + DEFINE_PROP_END_OF_LIST() +}; + +static void ipack_device_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->bus_type = TYPE_IPACK_BUS; + k->init = ipack_device_dev_init; + k->exit = ipack_device_dev_exit; + k->props = ipack_device_props; +} + +const VMStateDescription vmstate_ipack_device = { + .name = "ipack_device", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(slot, IPackDevice), + VMSTATE_END_OF_LIST() + } +}; + +static const TypeInfo ipack_device_info = { + .name = TYPE_IPACK_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(IPackDevice), + .class_size = sizeof(IPackDeviceClass), + .class_init = ipack_device_class_init, + .abstract = true, +}; + +static const TypeInfo ipack_bus_info = { + .name = TYPE_IPACK_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(IPackBus), +}; + +static void ipack_register_types(void) +{ + type_register_static(&ipack_device_info); + type_register_static(&ipack_bus_info); +} + +type_init(ipack_register_types) diff --git a/hw/char/ipoctal232.c b/hw/char/ipoctal232.c new file mode 100644 index 0000000000..685fee2d2e --- /dev/null +++ b/hw/char/ipoctal232.c @@ -0,0 +1,605 @@ +/* + * 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.h" +#include "qemu/bitops.h" +#include "char/char.h" + +/* #define DEBUG_IPOCTAL */ + +#ifdef DEBUG_IPOCTAL +#define DPRINTF2(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF2(fmt, ...) do { } while (0) +#endif + +#define DPRINTF(fmt, ...) DPRINTF2("IP-Octal: " fmt, ## __VA_ARGS__) + +#define RX_FIFO_SIZE 3 + +/* The IP-Octal has 8 channels (a-h) + divided into 4 blocks (A-D) */ +#define N_CHANNELS 8 +#define N_BLOCKS 4 + +#define REG_MRa 0x01 +#define REG_MRb 0x11 +#define REG_SRa 0x03 +#define REG_SRb 0x13 +#define REG_CSRa 0x03 +#define REG_CSRb 0x13 +#define REG_CRa 0x05 +#define REG_CRb 0x15 +#define REG_RHRa 0x07 +#define REG_RHRb 0x17 +#define REG_THRa 0x07 +#define REG_THRb 0x17 +#define REG_ACR 0x09 +#define REG_ISR 0x0B +#define REG_IMR 0x0B +#define REG_OPCR 0x1B + +#define CR_ENABLE_RX BIT(0) +#define CR_DISABLE_RX BIT(1) +#define CR_ENABLE_TX BIT(2) +#define CR_DISABLE_TX BIT(3) +#define CR_CMD(cr) ((cr) >> 4) +#define CR_NO_OP 0 +#define CR_RESET_MR 1 +#define CR_RESET_RX 2 +#define CR_RESET_TX 3 +#define CR_RESET_ERR 4 +#define CR_RESET_BRKINT 5 +#define CR_START_BRK 6 +#define CR_STOP_BRK 7 +#define CR_ASSERT_RTSN 8 +#define CR_NEGATE_RTSN 9 +#define CR_TIMEOUT_ON 10 +#define CR_TIMEOUT_OFF 12 + +#define SR_RXRDY BIT(0) +#define SR_FFULL BIT(1) +#define SR_TXRDY BIT(2) +#define SR_TXEMT BIT(3) +#define SR_OVERRUN BIT(4) +#define SR_PARITY BIT(5) +#define SR_FRAMING BIT(6) +#define SR_BREAK BIT(7) + +#define ISR_TXRDYA BIT(0) +#define ISR_RXRDYA BIT(1) +#define ISR_BREAKA BIT(2) +#define ISR_CNTRDY BIT(3) +#define ISR_TXRDYB BIT(4) +#define ISR_RXRDYB BIT(5) +#define ISR_BREAKB BIT(6) +#define ISR_MPICHG BIT(7) +#define ISR_TXRDY(CH) (((CH) & 1) ? BIT(4) : BIT(0)) +#define ISR_RXRDY(CH) (((CH) & 1) ? BIT(5) : BIT(1)) +#define ISR_BREAK(CH) (((CH) & 1) ? BIT(6) : BIT(2)) + +typedef struct IPOctalState IPOctalState; +typedef struct SCC2698Channel SCC2698Channel; +typedef struct SCC2698Block SCC2698Block; + +struct SCC2698Channel { + IPOctalState *ipoctal; + CharDriverState *dev; + bool rx_enabled; + uint8_t mr[2]; + uint8_t mr_idx; + uint8_t sr; + uint8_t rhr[RX_FIFO_SIZE]; + uint8_t rhr_idx; + uint8_t rx_pending; +}; + +struct SCC2698Block { + uint8_t imr; + uint8_t isr; +}; + +struct IPOctalState { + IPackDevice dev; + SCC2698Channel ch[N_CHANNELS]; + SCC2698Block blk[N_BLOCKS]; + uint8_t irq_vector; +}; + +#define TYPE_IPOCTAL "ipoctal232" + +#define IPOCTAL(obj) \ + OBJECT_CHECK(IPOctalState, (obj), TYPE_IPOCTAL) + +static const VMStateDescription vmstate_scc2698_channel = { + .name = "scc2698_channel", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_BOOL(rx_enabled, SCC2698Channel), + VMSTATE_UINT8_ARRAY(mr, SCC2698Channel, 2), + VMSTATE_UINT8(mr_idx, SCC2698Channel), + VMSTATE_UINT8(sr, SCC2698Channel), + VMSTATE_UINT8_ARRAY(rhr, SCC2698Channel, RX_FIFO_SIZE), + VMSTATE_UINT8(rhr_idx, SCC2698Channel), + VMSTATE_UINT8(rx_pending, SCC2698Channel), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_scc2698_block = { + .name = "scc2698_block", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(imr, SCC2698Block), + VMSTATE_UINT8(isr, SCC2698Block), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_ipoctal = { + .name = "ipoctal232", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_IPACK_DEVICE(dev, IPOctalState), + VMSTATE_STRUCT_ARRAY(ch, IPOctalState, N_CHANNELS, 1, + vmstate_scc2698_channel, SCC2698Channel), + VMSTATE_STRUCT_ARRAY(blk, IPOctalState, N_BLOCKS, 1, + vmstate_scc2698_block, SCC2698Block), + VMSTATE_UINT8(irq_vector, IPOctalState), + VMSTATE_END_OF_LIST() + } +}; + +/* data[10] is 0x0C, not 0x0B as the doc says */ +static const uint8_t id_prom_data[] = { + 0x49, 0x50, 0x41, 0x43, 0xF0, 0x22, + 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xCC +}; + +static void update_irq(IPOctalState *dev, unsigned block) +{ + /* Blocks A and B interrupt on INT0#, C and D on INT1#. + Thus, to get the status we have to check two blocks. */ + SCC2698Block *blk0 = &dev->blk[block]; + SCC2698Block *blk1 = &dev->blk[block^1]; + unsigned intno = block / 2; + + if ((blk0->isr & blk0->imr) || (blk1->isr & blk1->imr)) { + qemu_irq_raise(dev->dev.irq[intno]); + } else { + qemu_irq_lower(dev->dev.irq[intno]); + } +} + +static void write_cr(IPOctalState *dev, unsigned channel, uint8_t val) +{ + SCC2698Channel *ch = &dev->ch[channel]; + SCC2698Block *blk = &dev->blk[channel / 2]; + + DPRINTF("Write CR%c %u: ", channel + 'a', val); + + /* The lower 4 bits are used to enable and disable Tx and Rx */ + if (val & CR_ENABLE_RX) { + DPRINTF2("Rx on, "); + ch->rx_enabled = true; + } + if (val & CR_DISABLE_RX) { + DPRINTF2("Rx off, "); + ch->rx_enabled = false; + } + if (val & CR_ENABLE_TX) { + DPRINTF2("Tx on, "); + ch->sr |= SR_TXRDY | SR_TXEMT; + blk->isr |= ISR_TXRDY(channel); + } + if (val & CR_DISABLE_TX) { + DPRINTF2("Tx off, "); + ch->sr &= ~(SR_TXRDY | SR_TXEMT); + blk->isr &= ~ISR_TXRDY(channel); + } + + DPRINTF2("cmd: "); + + /* The rest of the bits implement different commands */ + switch (CR_CMD(val)) { + case CR_NO_OP: + DPRINTF2("none"); + break; + case CR_RESET_MR: + DPRINTF2("reset MR"); + ch->mr_idx = 0; + break; + case CR_RESET_RX: + DPRINTF2("reset Rx"); + ch->rx_enabled = false; + ch->rx_pending = 0; + ch->sr &= ~SR_RXRDY; + blk->isr &= ~ISR_RXRDY(channel); + break; + case CR_RESET_TX: + DPRINTF2("reset Tx"); + ch->sr &= ~(SR_TXRDY | SR_TXEMT); + blk->isr &= ~ISR_TXRDY(channel); + break; + case CR_RESET_ERR: + DPRINTF2("reset err"); + ch->sr &= ~(SR_OVERRUN | SR_PARITY | SR_FRAMING | SR_BREAK); + break; + case CR_RESET_BRKINT: + DPRINTF2("reset brk ch int"); + blk->isr &= ~(ISR_BREAKA | ISR_BREAKB); + break; + default: + DPRINTF2("unsupported 0x%x", CR_CMD(val)); + } + + DPRINTF2("\n"); +} + +static uint16_t io_read(IPackDevice *ip, uint8_t addr) +{ + IPOctalState *dev = IPOCTAL(ip); + uint16_t ret = 0; + /* addr[7:6]: block (A-D) + addr[7:5]: channel (a-h) + addr[5:0]: register */ + unsigned block = addr >> 5; + unsigned channel = addr >> 4; + /* Big endian, accessed using 8-bit bytes at odd locations */ + unsigned offset = (addr & 0x1F) ^ 1; + SCC2698Channel *ch = &dev->ch[channel]; + SCC2698Block *blk = &dev->blk[block]; + uint8_t old_isr = blk->isr; + + switch (offset) { + + case REG_MRa: + case REG_MRb: + ret = ch->mr[ch->mr_idx]; + DPRINTF("Read MR%u%c: 0x%x\n", ch->mr_idx + 1, channel + 'a', ret); + ch->mr_idx = 1; + break; + + case REG_SRa: + case REG_SRb: + ret = ch->sr; + DPRINTF("Read SR%c: 0x%x\n", channel + 'a', ret); + break; + + case REG_RHRa: + case REG_RHRb: + ret = ch->rhr[ch->rhr_idx]; + if (ch->rx_pending > 0) { + ch->rx_pending--; + if (ch->rx_pending == 0) { + ch->sr &= ~SR_RXRDY; + blk->isr &= ~ISR_RXRDY(channel); + if (ch->dev) { + qemu_chr_accept_input(ch->dev); + } + } else { + ch->rhr_idx = (ch->rhr_idx + 1) % RX_FIFO_SIZE; + } + if (ch->sr & SR_BREAK) { + ch->sr &= ~SR_BREAK; + blk->isr |= ISR_BREAK(channel); + } + } + DPRINTF("Read RHR%c (0x%x)\n", channel + 'a', ret); + break; + + case REG_ISR: + ret = blk->isr; + DPRINTF("Read ISR%c: 0x%x\n", block + 'A', ret); + break; + + default: + DPRINTF("Read unknown/unsupported register 0x%02x\n", offset); + } + + if (old_isr != blk->isr) { + update_irq(dev, block); + } + + return ret; +} + +static void io_write(IPackDevice *ip, uint8_t addr, uint16_t val) +{ + IPOctalState *dev = IPOCTAL(ip); + unsigned reg = val & 0xFF; + /* addr[7:6]: block (A-D) + addr[7:5]: channel (a-h) + addr[5:0]: register */ + unsigned block = addr >> 5; + unsigned channel = addr >> 4; + /* Big endian, accessed using 8-bit bytes at odd locations */ + unsigned offset = (addr & 0x1F) ^ 1; + SCC2698Channel *ch = &dev->ch[channel]; + SCC2698Block *blk = &dev->blk[block]; + uint8_t old_isr = blk->isr; + uint8_t old_imr = blk->imr; + + switch (offset) { + + case REG_MRa: + case REG_MRb: + ch->mr[ch->mr_idx] = reg; + DPRINTF("Write MR%u%c 0x%x\n", ch->mr_idx + 1, channel + 'a', reg); + ch->mr_idx = 1; + break; + + /* Not implemented */ + case REG_CSRa: + case REG_CSRb: + DPRINTF("Write CSR%c: 0x%x\n", channel + 'a', reg); + break; + + case REG_CRa: + case REG_CRb: + write_cr(dev, channel, reg); + break; + + case REG_THRa: + case REG_THRb: + if (ch->sr & SR_TXRDY) { + DPRINTF("Write THR%c (0x%x)\n", channel + 'a', reg); + if (ch->dev) { + uint8_t thr = reg; + qemu_chr_fe_write(ch->dev, &thr, 1); + } + } else { + DPRINTF("Write THR%c (0x%x), Tx disabled\n", channel + 'a', reg); + } + break; + + /* Not implemented */ + case REG_ACR: + DPRINTF("Write ACR%c 0x%x\n", block + 'A', val); + break; + + case REG_IMR: + DPRINTF("Write IMR%c 0x%x\n", block + 'A', val); + blk->imr = reg; + break; + + /* Not implemented */ + case REG_OPCR: + DPRINTF("Write OPCR%c 0x%x\n", block + 'A', val); + break; + + default: + DPRINTF("Write unknown/unsupported register 0x%02x %u\n", offset, val); + } + + if (old_isr != blk->isr || old_imr != blk->imr) { + update_irq(dev, block); + } +} + +static uint16_t id_read(IPackDevice *ip, uint8_t addr) +{ + uint16_t ret = 0; + unsigned pos = addr / 2; /* The ID PROM data is stored every other byte */ + + if (pos < ARRAY_SIZE(id_prom_data)) { + ret = id_prom_data[pos]; + } else { + DPRINTF("Attempt to read unavailable PROM data at 0x%x\n", addr); + } + + return ret; +} + +static void id_write(IPackDevice *ip, uint8_t addr, uint16_t val) +{ + IPOctalState *dev = IPOCTAL(ip); + if (addr == 1) { + DPRINTF("Write IRQ vector: %u\n", (unsigned) val); + dev->irq_vector = val; /* Undocumented, but the hw works like that */ + } else { + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); + } +} + +static uint16_t int_read(IPackDevice *ip, uint8_t addr) +{ + IPOctalState *dev = IPOCTAL(ip); + /* Read address 0 to ACK INT0# and address 2 to ACK INT1# */ + if (addr != 0 && addr != 2) { + DPRINTF("Attempt to read from 0x%x\n", addr); + return 0; + } else { + /* Update interrupts if necessary */ + update_irq(dev, addr); + return dev->irq_vector; + } +} + +static void int_write(IPackDevice *ip, uint8_t addr, uint16_t val) +{ + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); +} + +static uint16_t mem_read16(IPackDevice *ip, uint32_t addr) +{ + DPRINTF("Attempt to read from 0x%x\n", addr); + return 0; +} + +static void mem_write16(IPackDevice *ip, uint32_t addr, uint16_t val) +{ + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); +} + +static uint8_t mem_read8(IPackDevice *ip, uint32_t addr) +{ + DPRINTF("Attempt to read from 0x%x\n", addr); + return 0; +} + +static void mem_write8(IPackDevice *ip, uint32_t addr, uint8_t val) +{ + IPOctalState *dev = IPOCTAL(ip); + if (addr == 1) { + DPRINTF("Write IRQ vector: %u\n", (unsigned) val); + dev->irq_vector = val; + } else { + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); + } +} + +static int hostdev_can_receive(void *opaque) +{ + SCC2698Channel *ch = opaque; + int available_bytes = RX_FIFO_SIZE - ch->rx_pending; + return ch->rx_enabled ? available_bytes : 0; +} + +static void hostdev_receive(void *opaque, const uint8_t *buf, int size) +{ + SCC2698Channel *ch = opaque; + IPOctalState *dev = ch->ipoctal; + unsigned pos = ch->rhr_idx + ch->rx_pending; + int i; + + assert(size + ch->rx_pending <= RX_FIFO_SIZE); + + /* Copy data to the RxFIFO */ + for (i = 0; i < size; i++) { + pos %= RX_FIFO_SIZE; + ch->rhr[pos++] = buf[i]; + } + + ch->rx_pending += size; + + /* If the RxFIFO was empty raise an interrupt */ + if (!(ch->sr & SR_RXRDY)) { + unsigned block, channel = 0; + /* Find channel number to update the ISR register */ + while (&dev->ch[channel] != ch) { + channel++; + } + block = channel / 2; + dev->blk[block].isr |= ISR_RXRDY(channel); + ch->sr |= SR_RXRDY; + update_irq(dev, block); + } +} + +static void hostdev_event(void *opaque, int event) +{ + SCC2698Channel *ch = opaque; + switch (event) { + case CHR_EVENT_OPENED: + DPRINTF("Device %s opened\n", ch->dev->label); + break; + case CHR_EVENT_BREAK: { + uint8_t zero = 0; + DPRINTF("Device %s received break\n", ch->dev->label); + + if (!(ch->sr & SR_BREAK)) { + IPOctalState *dev = ch->ipoctal; + unsigned block, channel = 0; + + while (&dev->ch[channel] != ch) { + channel++; + } + block = channel / 2; + + ch->sr |= SR_BREAK; + dev->blk[block].isr |= ISR_BREAK(channel); + } + + /* Put a zero character in the buffer */ + hostdev_receive(ch, &zero, 1); + } + break; + default: + DPRINTF("Device %s received event %d\n", ch->dev->label, event); + } +} + +static int ipoctal_init(IPackDevice *ip) +{ + IPOctalState *s = IPOCTAL(ip); + unsigned i; + + for (i = 0; i < N_CHANNELS; i++) { + SCC2698Channel *ch = &s->ch[i]; + ch->ipoctal = s; + + /* Redirect IP-Octal channels to host character devices */ + if (ch->dev) { + qemu_chr_add_handlers(ch->dev, hostdev_can_receive, + hostdev_receive, hostdev_event, ch); + DPRINTF("Redirecting channel %u to %s\n", i, ch->dev->label); + } else { + DPRINTF("Could not redirect channel %u, no chardev set\n", i); + } + } + + return 0; +} + +static Property ipoctal_properties[] = { + DEFINE_PROP_CHR("chardev0", IPOctalState, ch[0].dev), + DEFINE_PROP_CHR("chardev1", IPOctalState, ch[1].dev), + DEFINE_PROP_CHR("chardev2", IPOctalState, ch[2].dev), + DEFINE_PROP_CHR("chardev3", IPOctalState, ch[3].dev), + DEFINE_PROP_CHR("chardev4", IPOctalState, ch[4].dev), + DEFINE_PROP_CHR("chardev5", IPOctalState, ch[5].dev), + DEFINE_PROP_CHR("chardev6", IPOctalState, ch[6].dev), + DEFINE_PROP_CHR("chardev7", IPOctalState, ch[7].dev), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ipoctal_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + IPackDeviceClass *ic = IPACK_DEVICE_CLASS(klass); + + ic->init = ipoctal_init; + ic->io_read = io_read; + ic->io_write = io_write; + ic->id_read = id_read; + ic->id_write = id_write; + ic->int_read = int_read; + ic->int_write = int_write; + ic->mem_read16 = mem_read16; + ic->mem_write16 = mem_write16; + ic->mem_read8 = mem_read8; + ic->mem_write8 = mem_write8; + + dc->desc = "GE IP-Octal 232 8-channel RS-232 IndustryPack"; + dc->props = ipoctal_properties; + dc->vmsd = &vmstate_ipoctal; +} + +static const TypeInfo ipoctal_info = { + .name = TYPE_IPOCTAL, + .parent = TYPE_IPACK_DEVICE, + .instance_size = sizeof(IPOctalState), + .class_init = ipoctal_class_init, +}; + +static void ipoctal_register_types(void) +{ + type_register_static(&ipoctal_info); +} + +type_init(ipoctal_register_types) diff --git a/hw/char/parallel.c b/hw/char/parallel.c new file mode 100644 index 0000000000..863a6fb4a9 --- /dev/null +++ b/hw/char/parallel.c @@ -0,0 +1,614 @@ +/* + * 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 "char/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; + +typedef struct ISAParallelState { + ISADevice dev; + 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 int parallel_isa_initfn(ISADevice *dev) +{ + static int index; + ISAParallelState *isa = DO_UPCAST(ISAParallelState, dev, dev); + ParallelState *s = &isa->state; + int base; + uint8_t dummy; + + if (!s->chr) { + fprintf(stderr, "Can't create parallel device, empty char device\n"); + exit(1); + } + + if (isa->index == -1) + isa->index = index; + if (isa->index >= MAX_PARALLEL_PORTS) + return -1; + if (isa->iobase == -1) + isa->iobase = isa_parallel_io[isa->index]; + index++; + + base = isa->iobase; + isa_init_irq(dev, &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(dev, base, + (s->hw_driver + ? &isa_parallel_portio_hw_list[0] + : &isa_parallel_portio_sw_list[0]), + s, "parallel"); + return 0; +} + +/* 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, ¶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_HEX32("iobase", ISAParallelState, iobase, -1), + DEFINE_PROP_UINT32("irq", ISAParallelState, isairq, 7), + DEFINE_PROP_CHR("chardev", ISAParallelState, state.chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void parallel_isa_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = parallel_isa_initfn; + dc->props = parallel_isa_properties; +} + +static const TypeInfo parallel_isa_info = { + .name = "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/hw/char/pl011.c b/hw/char/pl011.c new file mode 100644 index 0000000000..332d5b970c --- /dev/null +++ b/hw/char/pl011.c @@ -0,0 +1,330 @@ +/* + * Arm PrimeCell PL011 UART + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" +#include "char/char.h" + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + uint32_t readbuff; + uint32_t flags; + uint32_t lcr; + uint32_t cr; + uint32_t dmacr; + uint32_t int_enabled; + uint32_t int_level; + uint32_t read_fifo[16]; + uint32_t ilpr; + uint32_t ibrd; + uint32_t fbrd; + uint32_t ifl; + int read_pos; + int read_count; + int read_trigger; + CharDriverState *chr; + qemu_irq irq; + const unsigned char *id; +} pl011_state; + +#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(pl011_state *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) +{ + pl011_state *s = (pl011_state *)opaque; + uint32_t c; + + if (offset >= 0xfe0 && offset < 0x1000) { + return s->id[(offset - 0xfe0) >> 2]; + } + switch (offset >> 2) { + case 0: /* UARTDR */ + s->flags &= ~PL011_FLAG_RXFF; + c = s->read_fifo[s->read_pos]; + if (s->read_count > 0) { + s->read_count--; + if (++s->read_pos == 16) + s->read_pos = 0; + } + if (s->read_count == 0) { + s->flags |= PL011_FLAG_RXFE; + } + if (s->read_count == s->read_trigger - 1) + s->int_level &= ~ PL011_INT_RX; + pl011_update(s); + if (s->chr) { + qemu_chr_accept_input(s->chr); + } + return c; + case 1: /* UARTCR */ + return 0; + case 6: /* UARTFR */ + return s->flags; + case 8: /* UARTILPR */ + return s->ilpr; + case 9: /* UARTIBRD */ + return s->ibrd; + case 10: /* UARTFBRD */ + return s->fbrd; + case 11: /* UARTLCR_H */ + return s->lcr; + case 12: /* UARTCR */ + return s->cr; + case 13: /* UARTIFLS */ + return s->ifl; + case 14: /* UARTIMSC */ + return s->int_enabled; + case 15: /* UARTRIS */ + return s->int_level; + case 16: /* UARTMIS */ + return s->int_level & s->int_enabled; + case 18: /* UARTDMACR */ + return s->dmacr; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl011_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl011_set_read_trigger(pl011_state *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) +{ + pl011_state *s = (pl011_state *)opaque; + unsigned char ch; + + switch (offset >> 2) { + case 0: /* UARTDR */ + /* ??? Check if transmitter is enabled. */ + ch = value; + if (s->chr) + qemu_chr_fe_write(s->chr, &ch, 1); + s->int_level |= PL011_INT_TX; + pl011_update(s); + break; + case 1: /* UARTCR */ + s->cr = value; + break; + case 6: /* UARTFR */ + /* Writes to Flag register are ignored. */ + break; + case 8: /* UARTUARTILPR */ + s->ilpr = value; + break; + case 9: /* UARTIBRD */ + s->ibrd = value; + break; + case 10: /* UARTFBRD */ + s->fbrd = value; + break; + case 11: /* UARTLCR_H */ + s->lcr = value; + pl011_set_read_trigger(s); + break; + case 12: /* UARTCR */ + /* ??? Need to implement the enable and loopback bits. */ + s->cr = value; + break; + case 13: /* UARTIFS */ + s->ifl = value; + pl011_set_read_trigger(s); + break; + case 14: /* UARTIMSC */ + s->int_enabled = value; + pl011_update(s); + break; + case 17: /* UARTICR */ + s->int_level &= ~value; + pl011_update(s); + break; + case 18: /* UARTDMACR */ + s->dmacr = value; + if (value & 3) { + qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n"); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl011_write: Bad offset %x\n", (int)offset); + } +} + +static int pl011_can_receive(void *opaque) +{ + pl011_state *s = (pl011_state *)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) +{ + pl011_state *s = (pl011_state *)opaque; + int slot; + + slot = s->read_pos + s->read_count; + if (slot >= 16) + slot -= 16; + s->read_fifo[slot] = value; + s->read_count++; + s->flags &= ~PL011_FLAG_RXFE; + if (s->cr & 0x10 || s->read_count == 16) { + s->flags |= PL011_FLAG_RXFF; + } + if (s->read_count == s->read_trigger) { + s->int_level |= PL011_INT_RX; + pl011_update(s); + } +} + +static void pl011_receive(void *opaque, const uint8_t *buf, int size) +{ + pl011_put_fifo(opaque, *buf); +} + +static void pl011_event(void *opaque, int event) +{ + if (event == CHR_EVENT_BREAK) + pl011_put_fifo(opaque, 0x400); +} + +static const MemoryRegionOps pl011_ops = { + .read = pl011_read, + .write = pl011_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_pl011 = { + .name = "pl011", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(readbuff, pl011_state), + VMSTATE_UINT32(flags, pl011_state), + VMSTATE_UINT32(lcr, pl011_state), + VMSTATE_UINT32(cr, pl011_state), + VMSTATE_UINT32(dmacr, pl011_state), + VMSTATE_UINT32(int_enabled, pl011_state), + VMSTATE_UINT32(int_level, pl011_state), + VMSTATE_UINT32_ARRAY(read_fifo, pl011_state, 16), + VMSTATE_UINT32(ilpr, pl011_state), + VMSTATE_UINT32(ibrd, pl011_state), + VMSTATE_UINT32(fbrd, pl011_state), + VMSTATE_UINT32(ifl, pl011_state), + VMSTATE_INT32(read_pos, pl011_state), + VMSTATE_INT32(read_count, pl011_state), + VMSTATE_INT32(read_trigger, pl011_state), + VMSTATE_END_OF_LIST() + } +}; + +static int pl011_init(SysBusDevice *dev, const unsigned char *id) +{ + pl011_state *s = FROM_SYSBUS(pl011_state, dev); + + memory_region_init_io(&s->iomem, &pl011_ops, s, "pl011", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + s->id = id; + s->chr = qemu_char_get_next_serial(); + + s->read_trigger = 1; + s->ifl = 0x12; + s->cr = 0x300; + s->flags = 0x90; + if (s->chr) { + qemu_chr_add_handlers(s->chr, pl011_can_receive, pl011_receive, + pl011_event, s); + } + vmstate_register(&dev->qdev, -1, &vmstate_pl011, s); + return 0; +} + +static int pl011_arm_init(SysBusDevice *dev) +{ + return pl011_init(dev, pl011_id_arm); +} + +static int pl011_luminary_init(SysBusDevice *dev) +{ + return pl011_init(dev, pl011_id_luminary); +} + +static void pl011_arm_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = pl011_arm_init; +} + +static const TypeInfo pl011_arm_info = { + .name = "pl011", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl011_state), + .class_init = pl011_arm_class_init, +}; + +static void pl011_luminary_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = pl011_luminary_init; +} + +static const TypeInfo pl011_luminary_info = { + .name = "pl011_luminary", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(pl011_state), + .class_init = pl011_luminary_class_init, +}; + +static void pl011_register_types(void) +{ + type_register_static(&pl011_arm_info); + type_register_static(&pl011_luminary_info); +} + +type_init(pl011_register_types) diff --git a/hw/char/serial-isa.c b/hw/char/serial-isa.c new file mode 100644 index 0000000000..ed140d04a6 --- /dev/null +++ b/hw/char/serial-isa.c @@ -0,0 +1,130 @@ +/* + * 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" + +typedef struct ISASerialState { + ISADevice dev; + 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 int serial_isa_initfn(ISADevice *dev) +{ + static int index; + ISASerialState *isa = DO_UPCAST(ISASerialState, dev, dev); + SerialState *s = &isa->state; + + if (isa->index == -1) { + isa->index = index; + } + if (isa->index >= MAX_SERIAL_PORTS) { + return -1; + } + 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(dev, &s->irq, isa->isairq); + serial_init_core(s); + qdev_set_legacy_instance_id(&dev->qdev, isa->iobase, 3); + + memory_region_init_io(&s->io, &serial_io_ops, s, "serial", 8); + isa_register_ioport(dev, &s->io, isa->iobase); + return 0; +} + +static const VMStateDescription vmstate_isa_serial = { + .name = "serial", + .version_id = 3, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(state, ISASerialState, 0, vmstate_serial, SerialState), + VMSTATE_END_OF_LIST() + } +}; + +static Property serial_isa_properties[] = { + DEFINE_PROP_UINT32("index", ISASerialState, index, -1), + DEFINE_PROP_HEX32("iobase", ISASerialState, iobase, -1), + DEFINE_PROP_UINT32("irq", ISASerialState, isairq, -1), + DEFINE_PROP_CHR("chardev", ISASerialState, state.chr), + DEFINE_PROP_UINT32("wakeup", ISASerialState, state.wakeup, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void serial_isa_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = serial_isa_initfn; + dc->vmsd = &vmstate_isa_serial; + dc->props = serial_isa_properties; +} + +static const TypeInfo serial_isa_info = { + .name = "isa-serial", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISASerialState), + .class_init = serial_isa_class_initfn, +}; + +static void serial_register_types(void) +{ + type_register_static(&serial_isa_info); +} + +type_init(serial_register_types) + +bool serial_isa_init(ISABus *bus, int index, CharDriverState *chr) +{ + ISADevice *dev; + + dev = isa_try_create(bus, "isa-serial"); + if (!dev) { + return false; + } + qdev_prop_set_uint32(&dev->qdev, "index", index); + qdev_prop_set_chr(&dev->qdev, "chardev", chr); + if (qdev_init(&dev->qdev) < 0) { + return false; + } + return true; +} diff --git a/hw/char/serial-pci.c b/hw/char/serial-pci.c new file mode 100644 index 0000000000..2138e35851 --- /dev/null +++ b/hw/char/serial-pci.c @@ -0,0 +1,252 @@ +/* + * 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; +} PCISerialState; + +typedef struct PCIMultiSerialState { + PCIDevice dev; + MemoryRegion iobar; + uint32_t ports; + char *name[PCI_SERIAL_MAX_PORTS]; + SerialState state[PCI_SERIAL_MAX_PORTS]; + uint32_t level[PCI_SERIAL_MAX_PORTS]; + qemu_irq *irqs; +} PCIMultiSerialState; + +static int serial_pci_init(PCIDevice *dev) +{ + PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev); + SerialState *s = &pci->state; + + s->baudbase = 115200; + serial_init_core(s); + + pci->dev.config[PCI_INTERRUPT_PIN] = 0x01; + s->irq = pci->dev.irq[0]; + + memory_region_init_io(&s->io, &serial_io_ops, s, "serial", 8); + pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->io); + return 0; +} + +static void multi_serial_irq_mux(void *opaque, int n, int level) +{ + PCIMultiSerialState *pci = opaque; + int i, pending = 0; + + pci->level[n] = level; + for (i = 0; i < pci->ports; i++) { + if (pci->level[i]) { + pending = 1; + } + } + qemu_set_irq(pci->dev.irq[0], pending); +} + +static int multi_serial_pci_init(PCIDevice *dev) +{ + PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev); + PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev); + SerialState *s; + int i; + + switch (pc->device_id) { + case 0x0003: + pci->ports = 2; + break; + case 0x0004: + pci->ports = 4; + break; + } + assert(pci->ports > 0); + assert(pci->ports <= PCI_SERIAL_MAX_PORTS); + + pci->dev.config[PCI_INTERRUPT_PIN] = 0x01; + memory_region_init(&pci->iobar, "multiserial", 8 * pci->ports); + pci_register_bar(&pci->dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &pci->iobar); + pci->irqs = qemu_allocate_irqs(multi_serial_irq_mux, pci, + pci->ports); + + for (i = 0; i < pci->ports; i++) { + s = pci->state + i; + s->baudbase = 115200; + serial_init_core(s); + s->irq = pci->irqs[i]; + pci->name[i] = g_strdup_printf("uart #%d", i+1); + memory_region_init_io(&s->io, &serial_io_ops, s, pci->name[i], 8); + memory_region_add_subregion(&pci->iobar, 8 * i, &s->io); + } + return 0; +} + +static void serial_pci_exit(PCIDevice *dev) +{ + PCISerialState *pci = DO_UPCAST(PCISerialState, dev, dev); + SerialState *s = &pci->state; + + serial_exit_core(s); + memory_region_destroy(&s->io); +} + +static void multi_serial_pci_exit(PCIDevice *dev) +{ + PCIMultiSerialState *pci = DO_UPCAST(PCIMultiSerialState, dev, dev); + SerialState *s; + int i; + + for (i = 0; i < pci->ports; i++) { + s = pci->state + i; + serial_exit_core(s); + memory_region_destroy(&s->io); + g_free(pci->name[i]); + } + memory_region_destroy(&pci->iobar); + qemu_free_irqs(pci->irqs); +} + +static const VMStateDescription vmstate_pci_serial = { + .name = "pci-serial", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, PCISerialState), + VMSTATE_STRUCT(state, PCISerialState, 0, vmstate_serial, SerialState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pci_multi_serial = { + .name = "pci-serial-multi", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, PCIMultiSerialState), + VMSTATE_STRUCT_ARRAY(state, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS, + 0, vmstate_serial, SerialState), + VMSTATE_UINT32_ARRAY(level, PCIMultiSerialState, PCI_SERIAL_MAX_PORTS), + VMSTATE_END_OF_LIST() + } +}; + +static Property serial_pci_properties[] = { + DEFINE_PROP_CHR("chardev", PCISerialState, state.chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static Property multi_2x_serial_pci_properties[] = { + DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr), + DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static Property multi_4x_serial_pci_properties[] = { + DEFINE_PROP_CHR("chardev1", PCIMultiSerialState, state[0].chr), + DEFINE_PROP_CHR("chardev2", PCIMultiSerialState, state[1].chr), + DEFINE_PROP_CHR("chardev3", PCIMultiSerialState, state[2].chr), + DEFINE_PROP_CHR("chardev4", PCIMultiSerialState, state[3].chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void serial_pci_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); + pc->init = serial_pci_init; + pc->exit = serial_pci_exit; + pc->vendor_id = PCI_VENDOR_ID_REDHAT; + pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL; + pc->revision = 1; + pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL; + dc->vmsd = &vmstate_pci_serial; + dc->props = serial_pci_properties; +} + +static void multi_2x_serial_pci_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); + pc->init = multi_serial_pci_init; + pc->exit = multi_serial_pci_exit; + pc->vendor_id = PCI_VENDOR_ID_REDHAT; + pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL2; + pc->revision = 1; + pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL; + dc->vmsd = &vmstate_pci_multi_serial; + dc->props = multi_2x_serial_pci_properties; +} + +static void multi_4x_serial_pci_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); + pc->init = multi_serial_pci_init; + pc->exit = multi_serial_pci_exit; + pc->vendor_id = PCI_VENDOR_ID_REDHAT; + pc->device_id = PCI_DEVICE_ID_REDHAT_SERIAL4; + pc->revision = 1; + pc->class_id = PCI_CLASS_COMMUNICATION_SERIAL; + dc->vmsd = &vmstate_pci_multi_serial; + dc->props = multi_4x_serial_pci_properties; +} + +static const TypeInfo serial_pci_info = { + .name = "pci-serial", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCISerialState), + .class_init = serial_pci_class_initfn, +}; + +static const TypeInfo multi_2x_serial_pci_info = { + .name = "pci-serial-2x", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIMultiSerialState), + .class_init = multi_2x_serial_pci_class_initfn, +}; + +static const TypeInfo multi_4x_serial_pci_info = { + .name = "pci-serial-4x", + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(PCIMultiSerialState), + .class_init = multi_4x_serial_pci_class_initfn, +}; + +static void serial_pci_register_types(void) +{ + type_register_static(&serial_pci_info); + type_register_static(&multi_2x_serial_pci_info); + type_register_static(&multi_4x_serial_pci_info); +} + +type_init(serial_pci_register_types) diff --git a/hw/char/serial.c b/hw/char/serial.c new file mode 100644 index 0000000000..1151bf1bab --- /dev/null +++ b/hw/char/serial.c @@ -0,0 +1,789 @@ +/* + * 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 "char/char.h" +#include "qemu/timer.h" +#include "exec/address-spaces.h" + +//#define DEBUG_SERIAL + +#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */ + +#define UART_IER_MSI 0x08 /* Enable Modem status interrupt */ +#define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */ +#define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */ +#define UART_IER_RDI 0x01 /* Enable receiver data interrupt */ + +#define UART_IIR_NO_INT 0x01 /* No interrupts pending */ +#define UART_IIR_ID 0x06 /* Mask for the interrupt ID */ + +#define UART_IIR_MSI 0x00 /* Modem status interrupt */ +#define UART_IIR_THRI 0x02 /* Transmitter holding register empty */ +#define UART_IIR_RDI 0x04 /* Receiver data interrupt */ +#define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */ +#define UART_IIR_CTI 0x0C /* Character Timeout Indication */ + +#define UART_IIR_FENF 0x80 /* Fifo enabled, but not functionning */ +#define UART_IIR_FE 0xC0 /* Fifo enabled */ + +/* + * These are the definitions for the Modem Control Register + */ +#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */ +#define UART_MCR_OUT2 0x08 /* Out2 complement */ +#define UART_MCR_OUT1 0x04 /* Out1 complement */ +#define UART_MCR_RTS 0x02 /* RTS complement */ +#define UART_MCR_DTR 0x01 /* DTR complement */ + +/* + * These are the definitions for the Modem Status Register + */ +#define UART_MSR_DCD 0x80 /* Data Carrier Detect */ +#define UART_MSR_RI 0x40 /* Ring Indicator */ +#define UART_MSR_DSR 0x20 /* Data Set Ready */ +#define UART_MSR_CTS 0x10 /* Clear to Send */ +#define UART_MSR_DDCD 0x08 /* Delta DCD */ +#define UART_MSR_TERI 0x04 /* Trailing edge ring indicator */ +#define UART_MSR_DDSR 0x02 /* Delta DSR */ +#define UART_MSR_DCTS 0x01 /* Delta CTS */ +#define UART_MSR_ANY_DELTA 0x0F /* Any of the delta bits! */ + +#define UART_LSR_TEMT 0x40 /* Transmitter empty */ +#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */ +#define UART_LSR_BI 0x10 /* Break interrupt indicator */ +#define UART_LSR_FE 0x08 /* Frame error indicator */ +#define UART_LSR_PE 0x04 /* Parity error indicator */ +#define UART_LSR_OE 0x02 /* Overrun error indicator */ +#define UART_LSR_DR 0x01 /* Receiver data ready */ +#define UART_LSR_INT_ANY 0x1E /* Any of the lsr-interrupt-triggering status bits */ + +/* Interrupt trigger levels. The byte-counts are for 16550A - in newer UARTs the byte-count for each ITL is higher. */ + +#define UART_FCR_ITL_1 0x00 /* 1 byte ITL */ +#define UART_FCR_ITL_2 0x40 /* 4 bytes ITL */ +#define UART_FCR_ITL_3 0x80 /* 8 bytes ITL */ +#define UART_FCR_ITL_4 0xC0 /* 14 bytes ITL */ + +#define UART_FCR_DMS 0x08 /* DMA Mode Select */ +#define UART_FCR_XFR 0x04 /* XMIT Fifo Reset */ +#define UART_FCR_RFR 0x02 /* RCVR Fifo Reset */ +#define UART_FCR_FE 0x01 /* FIFO Enable */ + +#define XMIT_FIFO 0 +#define RECV_FIFO 1 +#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 void fifo_clear(SerialState *s, int fifo) +{ + SerialFIFO *f = (fifo) ? &s->recv_fifo : &s->xmit_fifo; + memset(f->data, 0, UART_FIFO_LENGTH); + f->count = 0; + f->head = 0; + f->tail = 0; +} + +static int fifo_put(SerialState *s, int fifo, uint8_t chr) +{ + SerialFIFO *f = (fifo) ? &s->recv_fifo : &s->xmit_fifo; + + /* Receive overruns do not overwrite FIFO contents. */ + if (fifo == XMIT_FIFO || f->count < UART_FIFO_LENGTH) { + + f->data[f->head++] = chr; + + if (f->head == UART_FIFO_LENGTH) + f->head = 0; + } + + if (f->count < UART_FIFO_LENGTH) + f->count++; + else if (fifo == RECV_FIFO) + s->lsr |= UART_LSR_OE; + + return 1; +} + +static uint8_t fifo_get(SerialState *s, int fifo) +{ + SerialFIFO *f = (fifo) ? &s->recv_fifo : &s->xmit_fifo; + uint8_t c; + + if(f->count == 0) + return 0; + + c = f->data[f->tail++]; + if (f->tail == UART_FIFO_LENGTH) + f->tail = 0; + f->count--; + + return c; +} + +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.count >= s->recv_fifo.itl)) { + tmp_iir = UART_IIR_RDI; + } else if ((s->ier & UART_IER_THRI) && s->thr_ipending) { + tmp_iir = UART_IIR_THRI; + } else if ((s->ier & UART_IER_MSI) && (s->msr & UART_MSR_ANY_DELTA)) { + tmp_iir = UART_IIR_MSI; + } + + s->iir = tmp_iir | (s->iir & 0xF0); + + if (tmp_iir != UART_IIR_NO_INT) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + +static void serial_update_parameters(SerialState *s) +{ + int speed, parity, data_bits, stop_bits, frame_size; + QEMUSerialSetParams ssp; + + if (s->divider == 0) + return; + + /* Start bit. */ + frame_size = 1; + if (s->lcr & 0x08) { + /* Parity bit. */ + frame_size++; + if (s->lcr & 0x10) + parity = 'E'; + else + parity = 'O'; + } else { + parity = 'N'; + } + if (s->lcr & 0x04) + stop_bits = 2; + else + stop_bits = 1; + + data_bits = (s->lcr & 0x03) + 5; + frame_size += data_bits + stop_bits; + speed = s->baudbase / s->divider; + ssp.speed = speed; + ssp.parity = parity; + ssp.data_bits = data_bits; + ssp.stop_bits = stop_bits; + s->char_transmit_time = (get_ticks_per_sec() / speed) * frame_size; + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); + + DPRINTF("speed=%d parity=%c data=%d stop=%d\n", + speed, parity, data_bits, stop_bits); +} + +static void serial_update_msl(SerialState *s) +{ + uint8_t omsr; + int flags; + + qemu_del_timer(s->modem_status_poll); + + if (qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP) { + s->poll_msl = -1; + return; + } + + omsr = s->msr; + + s->msr = (flags & CHR_TIOCM_CTS) ? s->msr | UART_MSR_CTS : s->msr & ~UART_MSR_CTS; + s->msr = (flags & CHR_TIOCM_DSR) ? s->msr | UART_MSR_DSR : s->msr & ~UART_MSR_DSR; + s->msr = (flags & CHR_TIOCM_CAR) ? s->msr | UART_MSR_DCD : s->msr & ~UART_MSR_DCD; + s->msr = (flags & CHR_TIOCM_RI) ? s->msr | UART_MSR_RI : s->msr & ~UART_MSR_RI; + + if (s->msr != omsr) { + /* Set delta bits */ + s->msr = s->msr | ((s->msr >> 4) ^ (omsr >> 4)); + /* UART_MSR_TERI only if change was from 1 -> 0 */ + if ((s->msr & UART_MSR_TERI) && !(omsr & UART_MSR_RI)) + s->msr &= ~UART_MSR_TERI; + serial_update_irq(s); + } + + /* The real 16550A apparently has a 250ns response latency to line status changes. + We'll be lazy and poll only every 10ms, and only poll it at all if MSI interrupts are turned on */ + + if (s->poll_msl) + qemu_mod_timer(s->modem_status_poll, qemu_get_clock_ns(vm_clock) + get_ticks_per_sec() / 100); +} + +static gboolean serial_xmit(GIOChannel *chan, GIOCondition cond, void *opaque) +{ + SerialState *s = opaque; + + if (s->tsr_retry <= 0) { + if (s->fcr & UART_FCR_FE) { + s->tsr = fifo_get(s,XMIT_FIFO); + if (!s->xmit_fifo.count) + s->lsr |= UART_LSR_THRE; + } else if ((s->lsr & UART_LSR_THRE)) { + return FALSE; + } else { + s->tsr = s->thr; + s->lsr |= UART_LSR_THRE; + s->lsr &= ~UART_LSR_TEMT; + } + } + + if (s->mcr & UART_MCR_LOOP) { + /* in loopback mode, say that we just received a char */ + serial_receive1(s, &s->tsr, 1); + } else if (qemu_chr_fe_write(s->chr, &s->tsr, 1) != 1) { + if (s->tsr_retry >= 0 && s->tsr_retry < MAX_XMIT_RETRY && + qemu_chr_fe_add_watch(s->chr, G_IO_OUT, serial_xmit, s) > 0) { + s->tsr_retry++; + return FALSE; + } + s->tsr_retry = 0; + } else { + s->tsr_retry = 0; + } + + s->last_xmit_ts = qemu_get_clock_ns(vm_clock); + + if (s->lsr & UART_LSR_THRE) { + s->lsr |= UART_LSR_TEMT; + s->thr_ipending = 1; + serial_update_irq(s); + } + + return FALSE; +} + + +static void serial_ioport_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + SerialState *s = opaque; + + addr &= 7; + DPRINTF("write addr=0x%" HWADDR_PRIx " val=0x%" PRIx64 "\n", addr, val); + switch(addr) { + default: + case 0: + if (s->lcr & UART_LCR_DLAB) { + s->divider = (s->divider & 0xff00) | val; + serial_update_parameters(s); + } else { + s->thr = (uint8_t) val; + if(s->fcr & UART_FCR_FE) { + fifo_put(s, XMIT_FIFO, s->thr); + s->thr_ipending = 0; + s->lsr &= ~UART_LSR_TEMT; + s->lsr &= ~UART_LSR_THRE; + serial_update_irq(s); + } else { + s->thr_ipending = 0; + s->lsr &= ~UART_LSR_THRE; + serial_update_irq(s); + } + serial_xmit(NULL, G_IO_OUT, s); + } + break; + case 1: + if (s->lcr & UART_LCR_DLAB) { + s->divider = (s->divider & 0x00ff) | (val << 8); + serial_update_parameters(s); + } else { + s->ier = val & 0x0f; + /* If the backend device is a real serial port, turn polling of the modem + status lines on physical port on or off depending on UART_IER_MSI state */ + if (s->poll_msl >= 0) { + if (s->ier & UART_IER_MSI) { + s->poll_msl = 1; + serial_update_msl(s); + } else { + qemu_del_timer(s->modem_status_poll); + s->poll_msl = 0; + } + } + if (s->lsr & UART_LSR_THRE) { + s->thr_ipending = 1; + serial_update_irq(s); + } + } + break; + case 2: + val = val & 0xFF; + + if (s->fcr == val) + break; + + /* Did the enable/disable flag change? If so, make sure FIFOs get flushed */ + if ((val ^ s->fcr) & UART_FCR_FE) + val |= UART_FCR_XFR | UART_FCR_RFR; + + /* FIFO clear */ + + if (val & UART_FCR_RFR) { + qemu_del_timer(s->fifo_timeout_timer); + s->timeout_ipending=0; + fifo_clear(s,RECV_FIFO); + } + + if (val & UART_FCR_XFR) { + fifo_clear(s,XMIT_FIFO); + } + + if (val & UART_FCR_FE) { + s->iir |= UART_IIR_FE; + /* Set RECV_FIFO trigger Level */ + switch (val & 0xC0) { + case UART_FCR_ITL_1: + s->recv_fifo.itl = 1; + break; + case UART_FCR_ITL_2: + s->recv_fifo.itl = 4; + break; + case UART_FCR_ITL_3: + s->recv_fifo.itl = 8; + break; + case UART_FCR_ITL_4: + s->recv_fifo.itl = 14; + break; + } + } else + s->iir &= ~UART_IIR_FE; + + /* Set fcr - or at least the bits in it that are supposed to "stick" */ + s->fcr = val & 0xC9; + serial_update_irq(s); + break; + case 3: + { + int break_enable; + s->lcr = val; + serial_update_parameters(s); + break_enable = (val >> 6) & 1; + if (break_enable != s->last_break_enable) { + s->last_break_enable = break_enable; + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK, + &break_enable); + } + } + break; + case 4: + { + int flags; + int old_mcr = s->mcr; + s->mcr = val & 0x1f; + if (val & UART_MCR_LOOP) + break; + + if (s->poll_msl >= 0 && old_mcr != s->mcr) { + + qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_GET_TIOCM, &flags); + + flags &= ~(CHR_TIOCM_RTS | CHR_TIOCM_DTR); + + if (val & UART_MCR_RTS) + flags |= CHR_TIOCM_RTS; + if (val & UART_MCR_DTR) + flags |= CHR_TIOCM_DTR; + + qemu_chr_fe_ioctl(s->chr,CHR_IOCTL_SERIAL_SET_TIOCM, &flags); + /* Update the modem status after a one-character-send wait-time, since there may be a response + from the device/computer at the other end of the serial line */ + qemu_mod_timer(s->modem_status_poll, qemu_get_clock_ns(vm_clock) + s->char_transmit_time); + } + } + break; + case 5: + break; + case 6: + break; + case 7: + s->scr = val; + break; + } +} + +static uint64_t serial_ioport_read(void *opaque, hwaddr addr, unsigned size) +{ + SerialState *s = opaque; + uint32_t ret; + + addr &= 7; + switch(addr) { + default: + case 0: + if (s->lcr & UART_LCR_DLAB) { + ret = s->divider & 0xff; + } else { + if(s->fcr & UART_FCR_FE) { + ret = fifo_get(s,RECV_FIFO); + if (s->recv_fifo.count == 0) + s->lsr &= ~(UART_LSR_DR | UART_LSR_BI); + else + qemu_mod_timer(s->fifo_timeout_timer, qemu_get_clock_ns (vm_clock) + s->char_transmit_time * 4); + s->timeout_ipending = 0; + } else { + ret = s->rbr; + s->lsr &= ~(UART_LSR_DR | UART_LSR_BI); + } + serial_update_irq(s); + if (!(s->mcr & UART_MCR_LOOP)) { + /* in loopback mode, don't receive any data */ + qemu_chr_accept_input(s->chr); + } + } + break; + case 1: + if (s->lcr & UART_LCR_DLAB) { + ret = (s->divider >> 8) & 0xff; + } else { + ret = s->ier; + } + break; + case 2: + ret = s->iir; + if ((ret & UART_IIR_ID) == UART_IIR_THRI) { + s->thr_ipending = 0; + serial_update_irq(s); + } + break; + case 3: + ret = s->lcr; + break; + case 4: + ret = s->mcr; + break; + case 5: + ret = s->lsr; + /* Clear break and overrun interrupts */ + if (s->lsr & (UART_LSR_BI|UART_LSR_OE)) { + s->lsr &= ~(UART_LSR_BI|UART_LSR_OE); + serial_update_irq(s); + } + break; + case 6: + if (s->mcr & UART_MCR_LOOP) { + /* in loopback, the modem output pins are connected to the + inputs */ + ret = (s->mcr & 0x0c) << 4; + ret |= (s->mcr & 0x02) << 3; + ret |= (s->mcr & 0x01) << 5; + } else { + if (s->poll_msl >= 0) + serial_update_msl(s); + ret = s->msr; + /* Clear delta bits & msr int after read, if they were set */ + if (s->msr & UART_MSR_ANY_DELTA) { + s->msr &= 0xF0; + serial_update_irq(s); + } + } + break; + case 7: + ret = s->scr; + break; + } + DPRINTF("read addr=0x%" HWADDR_PRIx " val=0x%02x\n", addr, ret); + return ret; +} + +static int serial_can_receive(SerialState *s) +{ + if(s->fcr & UART_FCR_FE) { + if(s->recv_fifo.count < 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.count <= s->recv_fifo.itl) ? s->recv_fifo.itl - s->recv_fifo.count : 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 */ + fifo_put(s, RECV_FIFO, '\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.count) { + 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++) { + fifo_put(s, RECV_FIFO, buf[i]); + } + s->lsr |= UART_LSR_DR; + /* call the timeout receive callback in 4 char transmit time */ + qemu_mod_timer(s->fifo_timeout_timer, qemu_get_clock_ns (vm_clock) + s->char_transmit_time * 4); + } else { + if (s->lsr & UART_LSR_DR) + s->lsr |= UART_LSR_OE; + s->rbr = buf[0]; + s->lsr |= UART_LSR_DR; + } + serial_update_irq(s); +} + +static void serial_event(void *opaque, int event) +{ + SerialState *s = opaque; + DPRINTF("event %x\n", event); + if (event == CHR_EVENT_BREAK) + serial_receive_break(s); +} + +static void serial_pre_save(void *opaque) +{ + SerialState *s = opaque; + s->fcr_vmstate = s->fcr; +} + +static int serial_post_load(void *opaque, int version_id) +{ + SerialState *s = opaque; + + if (version_id < 3) { + s->fcr_vmstate = 0; + } + /* Initialize fcr via setter to perform essential side-effects */ + serial_ioport_write(s, 0x02, s->fcr_vmstate, 1); + serial_update_parameters(s); + return 0; +} + +const VMStateDescription vmstate_serial = { + .name = "serial", + .version_id = 3, + .minimum_version_id = 2, + .pre_save = serial_pre_save, + .post_load = serial_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT16_V(divider, SerialState, 2), + VMSTATE_UINT8(rbr, SerialState), + VMSTATE_UINT8(ier, SerialState), + VMSTATE_UINT8(iir, SerialState), + VMSTATE_UINT8(lcr, SerialState), + VMSTATE_UINT8(mcr, SerialState), + VMSTATE_UINT8(lsr, SerialState), + VMSTATE_UINT8(msr, SerialState), + VMSTATE_UINT8(scr, SerialState), + VMSTATE_UINT8_V(fcr_vmstate, SerialState, 3), + VMSTATE_END_OF_LIST() + } +}; + +static void serial_reset(void *opaque) +{ + SerialState *s = opaque; + + s->rbr = 0; + s->ier = 0; + s->iir = UART_IIR_NO_INT; + s->lcr = 0; + s->lsr = UART_LSR_TEMT | UART_LSR_THRE; + s->msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS; + /* Default to 9600 baud, 1 start bit, 8 data bits, 1 stop bit, no parity. */ + s->divider = 0x0C; + s->mcr = UART_MCR_OUT2; + s->scr = 0; + s->tsr_retry = 0; + s->char_transmit_time = (get_ticks_per_sec() / 9600) * 10; + s->poll_msl = 0; + + fifo_clear(s,RECV_FIFO); + fifo_clear(s,XMIT_FIFO); + + s->last_xmit_ts = qemu_get_clock_ns(vm_clock); + + s->thr_ipending = 0; + s->last_break_enable = 0; + qemu_irq_lower(s->irq); +} + +void serial_init_core(SerialState *s) +{ + if (!s->chr) { + fprintf(stderr, "Can't create serial device, empty char device\n"); + exit(1); + } + + s->modem_status_poll = qemu_new_timer_ns(vm_clock, (QEMUTimerCB *) serial_update_msl, s); + + s->fifo_timeout_timer = qemu_new_timer_ns(vm_clock, (QEMUTimerCB *) fifo_timeout_int, s); + qemu_register_reset(serial_reset, s); + + qemu_chr_add_handlers(s->chr, serial_can_receive1, serial_receive1, + serial_event, s); +} + +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; + + s = g_malloc0(sizeof(SerialState)); + + s->irq = irq; + s->baudbase = baudbase; + s->chr = chr; + serial_init_core(s); + + vmstate_register(NULL, base, &vmstate_serial, s); + + memory_region_init_io(&s->io, &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; + + s = g_malloc0(sizeof(SerialState)); + + s->it_shift = it_shift; + s->irq = irq; + s->baudbase = baudbase; + s->chr = chr; + + serial_init_core(s); + vmstate_register(NULL, base, &vmstate_serial, s); + + memory_region_init_io(&s->io, &serial_mm_ops[end], s, + "serial", 8 << it_shift); + memory_region_add_subregion(address_space, base, &s->io); + + serial_update_msl(s); + return s; +} diff --git a/hw/char/tpci200.c b/hw/char/tpci200.c new file mode 100644 index 0000000000..e3408ef4ba --- /dev/null +++ b/hw/char/tpci200.c @@ -0,0 +1,671 @@ +/* + * QEMU TEWS TPCI200 IndustryPack carrier emulation + * + * Copyright (C) 2012 Igalia, S.L. + * Author: Alberto Garcia <agarcia@igalia.com> + * + * This code is licensed under the GNU GPL v2 or (at your option) any + * later version. + */ + +#include "hw/ipack.h" +#include "hw/pci/pci.h" +#include "qemu/bitops.h" +#include <stdio.h> + +/* #define DEBUG_TPCI */ + +#ifdef DEBUG_TPCI +#define DPRINTF(fmt, ...) \ + do { fprintf(stderr, "TPCI200: " fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + +#define N_MODULES 4 + +#define IP_ID_SPACE 2 +#define IP_INT_SPACE 3 +#define IP_IO_SPACE_ADDR_MASK 0x7F +#define IP_ID_SPACE_ADDR_MASK 0x3F +#define IP_INT_SPACE_ADDR_MASK 0x3F + +#define STATUS_INT(IP, INTNO) BIT((IP) * 2 + (INTNO)) +#define STATUS_TIME(IP) BIT((IP) + 12) +#define STATUS_ERR_ANY 0xF00 + +#define CTRL_CLKRATE BIT(0) +#define CTRL_RECOVER BIT(1) +#define CTRL_TIME_INT BIT(2) +#define CTRL_ERR_INT BIT(3) +#define CTRL_INT_EDGE(INTNO) BIT(4 + (INTNO)) +#define CTRL_INT(INTNO) BIT(6 + (INTNO)) + +#define REG_REV_ID 0x00 +#define REG_IP_A_CTRL 0x02 +#define REG_IP_B_CTRL 0x04 +#define REG_IP_C_CTRL 0x06 +#define REG_IP_D_CTRL 0x08 +#define REG_RESET 0x0A +#define REG_STATUS 0x0C +#define IP_N_FROM_REG(REG) ((REG) / 2 - 1) + +typedef struct { + PCIDevice dev; + IPackBus bus; + MemoryRegion mmio; + MemoryRegion io; + MemoryRegion las0; + MemoryRegion las1; + MemoryRegion las2; + MemoryRegion las3; + bool big_endian[3]; + uint8_t ctrl[N_MODULES]; + uint16_t status; + uint8_t int_set; +} TPCI200State; + +#define TYPE_TPCI200 "tpci200" + +#define TPCI200(obj) \ + OBJECT_CHECK(TPCI200State, (obj), TYPE_TPCI200) + +static const uint8_t local_config_regs[] = { + 0x00, 0xFF, 0xFF, 0x0F, 0x00, 0xFC, 0xFF, 0x0F, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x08, 0x01, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x60, 0x41, 0xD4, + 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x41, 0x14, 0xA2, 0x20, 0x01, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x08, 0x01, 0x02, + 0x00, 0x04, 0x01, 0x00, 0x00, 0x01, 0x01, 0x00, 0x80, 0x02, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x7A, 0x00, 0x52, 0x92, 0x24, 0x02 +}; + +static void adjust_addr(bool big_endian, hwaddr *addr, unsigned size) +{ + /* During 8 bit access in big endian mode, + odd and even addresses are swapped */ + if (big_endian && size == 1) { + *addr ^= 1; + } +} + +static uint64_t adjust_value(bool big_endian, uint64_t *val, unsigned size) +{ + /* Local spaces only support 8/16 bit access, + * so there's no need to care for sizes > 2 */ + if (big_endian && size == 2) { + *val = bswap16(*val); + } + return *val; +} + +static void tpci200_set_irq(void *opaque, int intno, int level) +{ + IPackDevice *ip = opaque; + IPackBus *bus = IPACK_BUS(qdev_get_parent_bus(DEVICE(ip))); + PCIDevice *pcidev = PCI_DEVICE(BUS(bus)->parent); + TPCI200State *dev = TPCI200(pcidev); + unsigned ip_n = ip->slot; + uint16_t prev_status = dev->status; + + assert(ip->slot >= 0 && ip->slot < N_MODULES); + + /* The requested interrupt must be enabled in the IP CONTROL + * register */ + if (!(dev->ctrl[ip_n] & CTRL_INT(intno))) { + return; + } + + /* Update the interrupt status in the IP STATUS register */ + if (level) { + dev->status |= STATUS_INT(ip_n, intno); + } else { + dev->status &= ~STATUS_INT(ip_n, intno); + } + + /* Return if there are no changes */ + if (dev->status == prev_status) { + return; + } + + DPRINTF("IP %u INT%u#: %u\n", ip_n, intno, level); + + /* Check if the interrupt is edge sensitive */ + if (dev->ctrl[ip_n] & CTRL_INT_EDGE(intno)) { + if (level) { + qemu_set_irq(dev->dev.irq[0], !dev->int_set); + qemu_set_irq(dev->dev.irq[0], dev->int_set); + } + } else { + unsigned i, j; + uint16_t level_status = dev->status; + + /* Check if there are any level sensitive interrupts set by + removing the ones that are edge sensitive from the status + register */ + for (i = 0; i < N_MODULES; i++) { + for (j = 0; j < 2; j++) { + if (dev->ctrl[i] & CTRL_INT_EDGE(j)) { + level_status &= ~STATUS_INT(i, j); + } + } + } + + if (level_status && !dev->int_set) { + qemu_irq_raise(dev->dev.irq[0]); + dev->int_set = 1; + } else if (!level_status && dev->int_set) { + qemu_irq_lower(dev->dev.irq[0]); + dev->int_set = 0; + } + } +} + +static uint64_t tpci200_read_cfg(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + uint8_t ret = 0; + if (addr < ARRAY_SIZE(local_config_regs)) { + ret = local_config_regs[addr]; + } + /* Endianness is stored in the first bit of these registers */ + if ((addr == 0x2b && s->big_endian[0]) || + (addr == 0x2f && s->big_endian[1]) || + (addr == 0x33 && s->big_endian[2])) { + ret |= 1; + } + DPRINTF("Read from LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) ret); + return ret; +} + +static void tpci200_write_cfg(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + /* Endianness is stored in the first bit of these registers */ + if (addr == 0x2b || addr == 0x2f || addr == 0x33) { + unsigned las = (addr - 0x2b) / 4; + s->big_endian[las] = val & 1; + DPRINTF("LAS%u big endian mode: %u\n", las, (unsigned) val & 1); + } else { + DPRINTF("Write to LCR 0x%x: 0x%x\n", (unsigned) addr, (unsigned) val); + } +} + +static uint64_t tpci200_read_las0(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + uint64_t ret = 0; + + switch (addr) { + + case REG_REV_ID: + DPRINTF("Read REVISION ID\n"); /* Current value is 0x00 */ + break; + + case REG_IP_A_CTRL: + case REG_IP_B_CTRL: + case REG_IP_C_CTRL: + case REG_IP_D_CTRL: + { + unsigned ip_n = IP_N_FROM_REG(addr); + ret = s->ctrl[ip_n]; + DPRINTF("Read IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) ret); + } + break; + + case REG_RESET: + DPRINTF("Read RESET\n"); /* Not implemented */ + break; + + case REG_STATUS: + ret = s->status; + DPRINTF("Read STATUS: 0x%x\n", (unsigned) ret); + break; + + /* Reserved */ + default: + DPRINTF("Unsupported read from LAS0 0x%x\n", (unsigned) addr); + break; + } + + return adjust_value(s->big_endian[0], &ret, size); +} + +static void tpci200_write_las0(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + + adjust_value(s->big_endian[0], &val, size); + + switch (addr) { + + case REG_REV_ID: + DPRINTF("Write Revision ID: 0x%x\n", (unsigned) val); /* No effect */ + break; + + case REG_IP_A_CTRL: + case REG_IP_B_CTRL: + case REG_IP_C_CTRL: + case REG_IP_D_CTRL: + { + unsigned ip_n = IP_N_FROM_REG(addr); + s->ctrl[ip_n] = val; + DPRINTF("Write IP %c CONTROL: 0x%x\n", 'A' + ip_n, (unsigned) val); + } + break; + + case REG_RESET: + DPRINTF("Write RESET: 0x%x\n", (unsigned) val); /* Not implemented */ + break; + + case REG_STATUS: + { + unsigned i; + + for (i = 0; i < N_MODULES; i++) { + IPackDevice *ip = ipack_device_find(&s->bus, i); + + if (ip != NULL) { + if (val & STATUS_INT(i, 0)) { + DPRINTF("Clear IP %c INT0# status\n", 'A' + i); + qemu_irq_lower(ip->irq[0]); + } + if (val & STATUS_INT(i, 1)) { + DPRINTF("Clear IP %c INT1# status\n", 'A' + i); + qemu_irq_lower(ip->irq[1]); + } + } + + if (val & STATUS_TIME(i)) { + DPRINTF("Clear IP %c timeout\n", 'A' + i); + s->status &= ~STATUS_TIME(i); + } + } + + if (val & STATUS_ERR_ANY) { + DPRINTF("Unexpected write to STATUS register: 0x%x\n", + (unsigned) val); + } + } + break; + + /* Reserved */ + default: + DPRINTF("Unsupported write to LAS0 0x%x: 0x%x\n", + (unsigned) addr, (unsigned) val); + break; + } +} + +static uint64_t tpci200_read_las1(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + uint64_t ret = 0; + unsigned ip_n, space; + uint8_t offset; + + adjust_addr(s->big_endian[1], &addr, size); + + /* + * The address is divided into the IP module number (0-4), the IP + * address space (I/O, ID, INT) and the offset within that space. + */ + ip_n = addr >> 8; + space = (addr >> 6) & 3; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Read LAS1: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + switch (space) { + + case IP_ID_SPACE: + offset = addr & IP_ID_SPACE_ADDR_MASK; + if (k->id_read) { + ret = k->id_read(ip, offset); + } + break; + + case IP_INT_SPACE: + offset = addr & IP_INT_SPACE_ADDR_MASK; + + /* Read address 0 to ACK IP INT0# and address 2 to ACK IP INT1# */ + if (offset == 0 || offset == 2) { + unsigned intno = offset / 2; + bool int_set = s->status & STATUS_INT(ip_n, intno); + bool int_edge_sensitive = s->ctrl[ip_n] & CTRL_INT_EDGE(intno); + if (int_set && !int_edge_sensitive) { + qemu_irq_lower(ip->irq[intno]); + } + } + + if (k->int_read) { + ret = k->int_read(ip, offset); + } + break; + + default: + offset = addr & IP_IO_SPACE_ADDR_MASK; + if (k->io_read) { + ret = k->io_read(ip, offset); + } + break; + } + } + + return adjust_value(s->big_endian[1], &ret, size); +} + +static void tpci200_write_las1(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + unsigned ip_n, space; + uint8_t offset; + + adjust_addr(s->big_endian[1], &addr, size); + adjust_value(s->big_endian[1], &val, size); + + /* + * The address is divided into the IP module number, the IP + * address space (I/O, ID, INT) and the offset within that space. + */ + ip_n = addr >> 8; + space = (addr >> 6) & 3; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Write LAS1: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + switch (space) { + + case IP_ID_SPACE: + offset = addr & IP_ID_SPACE_ADDR_MASK; + if (k->id_write) { + k->id_write(ip, offset, val); + } + break; + + case IP_INT_SPACE: + offset = addr & IP_INT_SPACE_ADDR_MASK; + if (k->int_write) { + k->int_write(ip, offset, val); + } + break; + + default: + offset = addr & IP_IO_SPACE_ADDR_MASK; + if (k->io_write) { + k->io_write(ip, offset, val); + } + break; + } + } +} + +static uint64_t tpci200_read_las2(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + uint64_t ret = 0; + unsigned ip_n; + uint32_t offset; + + adjust_addr(s->big_endian[2], &addr, size); + + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + ip_n = addr >> 23; + offset = addr & 0x7fffff; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Read LAS2: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_read16) { + ret = k->mem_read16(ip, offset); + } + } + + return adjust_value(s->big_endian[2], &ret, size); +} + +static void tpci200_write_las2(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + unsigned ip_n; + uint32_t offset; + + adjust_addr(s->big_endian[2], &addr, size); + adjust_value(s->big_endian[2], &val, size); + + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + ip_n = addr >> 23; + offset = addr & 0x7fffff; + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Write LAS2: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_write16) { + k->mem_write16(ip, offset, val); + } + } +} + +static uint64_t tpci200_read_las3(void *opaque, hwaddr addr, unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + uint64_t ret = 0; + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + unsigned ip_n = addr >> 22; + uint32_t offset = addr & 0x3fffff; + + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Read LAS3: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_read8) { + ret = k->mem_read8(ip, offset); + } + } + + return ret; +} + +static void tpci200_write_las3(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + TPCI200State *s = opaque; + IPackDevice *ip; + /* + * The address is divided into the IP module number and the offset + * within the IP module MEM space. + */ + unsigned ip_n = addr >> 22; + uint32_t offset = addr & 0x3fffff; + + ip = ipack_device_find(&s->bus, ip_n); + + if (ip == NULL) { + DPRINTF("Write LAS3: IP module %u not installed\n", ip_n); + } else { + IPackDeviceClass *k = IPACK_DEVICE_GET_CLASS(ip); + if (k->mem_write8) { + k->mem_write8(ip, offset, val); + } + } +} + +static const MemoryRegionOps tpci200_cfg_ops = { + .read = tpci200_read_cfg, + .write = tpci200_write_cfg, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4 + }, + .impl = { + .min_access_size = 1, + .max_access_size = 1 + } +}; + +static const MemoryRegionOps tpci200_las0_ops = { + .read = tpci200_read_las0, + .write = tpci200_write_las0, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 2, + .max_access_size = 2 + } +}; + +static const MemoryRegionOps tpci200_las1_ops = { + .read = tpci200_read_las1, + .write = tpci200_write_las1, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 2 + } +}; + +static const MemoryRegionOps tpci200_las2_ops = { + .read = tpci200_read_las2, + .write = tpci200_write_las2, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 2 + } +}; + +static const MemoryRegionOps tpci200_las3_ops = { + .read = tpci200_read_las3, + .write = tpci200_write_las3, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 1 + } +}; + +static int tpci200_initfn(PCIDevice *pci_dev) +{ + TPCI200State *s = TPCI200(pci_dev); + uint8_t *c = s->dev.config; + + pci_set_word(c + PCI_COMMAND, 0x0003); + pci_set_word(c + PCI_STATUS, 0x0280); + + pci_set_byte(c + PCI_INTERRUPT_PIN, 0x01); /* Interrupt pin A */ + + pci_set_byte(c + PCI_CAPABILITY_LIST, 0x40); + pci_set_long(c + 0x40, 0x48014801); + pci_set_long(c + 0x48, 0x00024C06); + pci_set_long(c + 0x4C, 0x00000003); + + memory_region_init_io(&s->mmio, &tpci200_cfg_ops, + s, "tpci200_mmio", 128); + memory_region_init_io(&s->io, &tpci200_cfg_ops, + s, "tpci200_io", 128); + memory_region_init_io(&s->las0, &tpci200_las0_ops, + s, "tpci200_las0", 256); + memory_region_init_io(&s->las1, &tpci200_las1_ops, + s, "tpci200_las1", 1024); + memory_region_init_io(&s->las2, &tpci200_las2_ops, + s, "tpci200_las2", 1024*1024*32); + memory_region_init_io(&s->las3, &tpci200_las3_ops, + s, "tpci200_las3", 1024*1024*16); + pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mmio); + pci_register_bar(&s->dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->io); + pci_register_bar(&s->dev, 2, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las0); + pci_register_bar(&s->dev, 3, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las1); + pci_register_bar(&s->dev, 4, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las2); + pci_register_bar(&s->dev, 5, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->las3); + + ipack_bus_new_inplace(&s->bus, DEVICE(&s->dev), NULL, + N_MODULES, tpci200_set_irq); + + return 0; +} + +static void tpci200_exitfn(PCIDevice *pci_dev) +{ + TPCI200State *s = TPCI200(pci_dev); + + memory_region_destroy(&s->mmio); + memory_region_destroy(&s->io); + memory_region_destroy(&s->las0); + memory_region_destroy(&s->las1); + memory_region_destroy(&s->las2); + memory_region_destroy(&s->las3); +} + +static const VMStateDescription vmstate_tpci200 = { + .name = "tpci200", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, TPCI200State), + VMSTATE_BOOL_ARRAY(big_endian, TPCI200State, 3), + VMSTATE_UINT8_ARRAY(ctrl, TPCI200State, N_MODULES), + VMSTATE_UINT16(status, TPCI200State), + VMSTATE_UINT8(int_set, TPCI200State), + VMSTATE_END_OF_LIST() + } +}; + +static void tpci200_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *k = PCI_DEVICE_CLASS(klass); + + k->init = tpci200_initfn; + k->exit = tpci200_exitfn; + k->vendor_id = PCI_VENDOR_ID_TEWS; + k->device_id = PCI_DEVICE_ID_TEWS_TPCI200; + k->class_id = PCI_CLASS_BRIDGE_OTHER; + k->subsystem_vendor_id = PCI_VENDOR_ID_TEWS; + k->subsystem_id = 0x300A; + dc->desc = "TEWS TPCI200 IndustryPack carrier"; + dc->vmsd = &vmstate_tpci200; +} + +static const TypeInfo tpci200_info = { + .name = TYPE_TPCI200, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(TPCI200State), + .class_init = tpci200_class_init, +}; + +static void tpci200_register_types(void) +{ + type_register_static(&tpci200_info); +} + +type_init(tpci200_register_types) diff --git a/hw/char/virtio-console.c b/hw/char/virtio-console.c new file mode 100644 index 0000000000..31f672c9a3 --- /dev/null +++ b/hw/char/virtio-console.c @@ -0,0 +1,184 @@ +/* + * 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 "char/char.h" +#include "qemu/error-report.h" +#include "trace.h" +#include "hw/virtio/virtio-serial.h" + +typedef struct VirtConsole { + VirtIOSerialPort port; + CharDriverState *chr; +} 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; + + virtio_serial_throttle_port(&vcon->port, false); + return FALSE; +} + +/* Callback function that's called when the guest sends us data */ +static ssize_t flush_buf(VirtIOSerialPort *port, const uint8_t *buf, size_t len) +{ + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + ssize_t ret; + + if (!vcon->chr) { + /* If there's no backend, we can just say we consumed all data. */ + return len; + } + + ret = qemu_chr_fe_write(vcon->chr, buf, len); + trace_virtio_console_flush_buf(port->id, len, ret); + + if (ret <= 0) { + 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. + */ + ret = 0; + if (!k->is_console) { + virtio_serial_throttle_port(port, true); + qemu_chr_fe_add_watch(vcon->chr, G_IO_OUT, chr_write_unblocked, + vcon); + } + } + return ret; +} + +/* Callback function that's called when the guest opens/closes the port */ +static void set_guest_connected(VirtIOSerialPort *port, int guest_connected) +{ + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + + if (!vcon->chr) { + return; + } + qemu_chr_fe_set_open(vcon->chr, guest_connected); +} + +/* Readiness of the guest to accept data on a port */ +static int chr_can_read(void *opaque) +{ + VirtConsole *vcon = opaque; + + return virtio_serial_guest_ready(&vcon->port); +} + +/* Send data from a char device over to the guest */ +static void chr_read(void *opaque, const uint8_t *buf, int size) +{ + VirtConsole *vcon = opaque; + + trace_virtio_console_chr_read(vcon->port.id, size); + virtio_serial_write(&vcon->port, buf, size); +} + +static void chr_event(void *opaque, int event) +{ + VirtConsole *vcon = opaque; + + trace_virtio_console_chr_event(vcon->port.id, event); + switch (event) { + case CHR_EVENT_OPENED: + virtio_serial_open(&vcon->port); + break; + case CHR_EVENT_CLOSED: + virtio_serial_close(&vcon->port); + break; + } +} + +static int virtconsole_initfn(VirtIOSerialPort *port) +{ + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_GET_CLASS(port); + + if (port->id == 0 && !k->is_console) { + error_report("Port number 0 on virtio-serial devices reserved for virtconsole devices for backward compatibility."); + return -1; + } + + if (vcon->chr) { + vcon->chr->explicit_fe_open = 1; + qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event, + vcon); + } + + return 0; +} + +static Property virtconsole_properties[] = { + DEFINE_PROP_CHR("chardev", VirtConsole, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtconsole_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass); + + k->is_console = true; + k->init = virtconsole_initfn; + k->have_data = flush_buf; + k->set_guest_connected = set_guest_connected; + dc->props = virtconsole_properties; +} + +static const TypeInfo virtconsole_info = { + .name = "virtconsole", + .parent = TYPE_VIRTIO_SERIAL_PORT, + .instance_size = sizeof(VirtConsole), + .class_init = virtconsole_class_init, +}; + +static Property virtserialport_properties[] = { + DEFINE_PROP_CHR("chardev", VirtConsole, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtserialport_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtIOSerialPortClass *k = VIRTIO_SERIAL_PORT_CLASS(klass); + + k->init = virtconsole_initfn; + k->have_data = flush_buf; + k->set_guest_connected = set_guest_connected; + dc->props = virtserialport_properties; +} + +static const TypeInfo virtserialport_info = { + .name = "virtserialport", + .parent = TYPE_VIRTIO_SERIAL_PORT, + .instance_size = sizeof(VirtConsole), + .class_init = virtserialport_class_init, +}; + +static void virtconsole_register_types(void) +{ + type_register_static(&virtconsole_info); + type_register_static(&virtserialport_info); +} + +type_init(virtconsole_register_types) diff --git a/hw/char/xen_console.c b/hw/char/xen_console.c new file mode 100644 index 0000000000..efc32320fa --- /dev/null +++ b/hw/char/xen_console.c @@ -0,0 +1,305 @@ +/* + * Copyright (C) International Business Machines Corp., 2005 + * Author(s): Anthony Liguori <aliguori@us.ibm.com> + * + * Copyright (C) Red Hat 2007 + * + * Xen Console + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; under version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <sys/select.h> +#include <fcntl.h> +#include <unistd.h> +#include <termios.h> +#include <stdarg.h> +#include <sys/mman.h> + +#include "hw/hw.h" +#include "char/char.h" +#include "hw/xen/xen_backend.h" + +#include <xen/io/console.h> + +struct buffer { + uint8_t *data; + size_t consumed; + size_t size; + size_t capacity; + size_t max_capacity; +}; + +struct XenConsole { + struct XenDevice xendev; /* must be first */ + struct buffer buffer; + char console[XEN_BUFSIZE]; + int ring_ref; + void *sring; + CharDriverState *chr; + int backlog; +}; + +static void buffer_append(struct XenConsole *con) +{ + struct buffer *buffer = &con->buffer; + XENCONS_RING_IDX cons, prod, size; + struct xencons_interface *intf = con->sring; + + cons = intf->out_cons; + prod = intf->out_prod; + xen_mb(); + + size = prod - cons; + if ((size == 0) || (size > sizeof(intf->out))) + return; + + if ((buffer->capacity - buffer->size) < size) { + buffer->capacity += (size + 1024); + buffer->data = g_realloc(buffer->data, buffer->capacity); + } + + while (cons != prod) + buffer->data[buffer->size++] = intf->out[ + MASK_XENCONS_IDX(cons++, intf->out)]; + + xen_mb(); + intf->out_cons = cons; + xen_be_send_notify(&con->xendev); + + if (buffer->max_capacity && + buffer->size > buffer->max_capacity) { + /* Discard the middle of the data. */ + + size_t over = buffer->size - buffer->max_capacity; + uint8_t *maxpos = buffer->data + buffer->max_capacity; + + memmove(maxpos - over, maxpos, over); + buffer->data = g_realloc(buffer->data, buffer->max_capacity); + buffer->size = buffer->capacity = buffer->max_capacity; + + if (buffer->consumed > buffer->max_capacity - over) + buffer->consumed = buffer->max_capacity - over; + } +} + +static void buffer_advance(struct buffer *buffer, size_t len) +{ + buffer->consumed += len; + if (buffer->consumed == buffer->size) { + buffer->consumed = 0; + buffer->size = 0; + } +} + +static int ring_free_bytes(struct XenConsole *con) +{ + struct xencons_interface *intf = con->sring; + XENCONS_RING_IDX cons, prod, space; + + cons = intf->in_cons; + prod = intf->in_prod; + xen_mb(); + + space = prod - cons; + if (space > sizeof(intf->in)) + return 0; /* ring is screwed: ignore it */ + + return (sizeof(intf->in) - space); +} + +static int xencons_can_receive(void *opaque) +{ + struct XenConsole *con = opaque; + return ring_free_bytes(con); +} + +static void xencons_receive(void *opaque, const uint8_t *buf, int len) +{ + struct XenConsole *con = opaque; + struct xencons_interface *intf = con->sring; + XENCONS_RING_IDX prod; + int i, max; + + max = ring_free_bytes(con); + /* The can_receive() func limits this, but check again anyway */ + if (max < len) + len = max; + + prod = intf->in_prod; + for (i = 0; i < len; i++) { + intf->in[MASK_XENCONS_IDX(prod++, intf->in)] = + buf[i]; + } + xen_wmb(); + intf->in_prod = prod; + xen_be_send_notify(&con->xendev); +} + +static void xencons_send(struct XenConsole *con) +{ + ssize_t len, size; + + size = con->buffer.size - con->buffer.consumed; + if (con->chr) + len = qemu_chr_fe_write(con->chr, con->buffer.data + con->buffer.consumed, + size); + else + len = size; + if (len < 1) { + if (!con->backlog) { + con->backlog = 1; + xen_be_printf(&con->xendev, 1, "backlog piling up, nobody listening?\n"); + } + } else { + buffer_advance(&con->buffer, len); + if (con->backlog && len == size) { + con->backlog = 0; + xen_be_printf(&con->xendev, 1, "backlog is gone\n"); + } + } +} + +/* -------------------------------------------------------------------- */ + +static int con_init(struct XenDevice *xendev) +{ + struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); + char *type, *dom, label[32]; + int ret = 0; + const char *output; + + /* setup */ + dom = xs_get_domain_path(xenstore, con->xendev.dom); + if (!xendev->dev) { + snprintf(con->console, sizeof(con->console), "%s/console", dom); + } else { + snprintf(con->console, sizeof(con->console), "%s/device/console/%d", dom, xendev->dev); + } + free(dom); + + type = xenstore_read_str(con->console, "type"); + if (!type || strcmp(type, "ioemu") != 0) { + xen_be_printf(xendev, 1, "not for me (type=%s)\n", type); + ret = -1; + goto out; + } + + output = xenstore_read_str(con->console, "output"); + + /* no Xen override, use qemu output device */ + if (output == NULL) { + con->chr = serial_hds[con->xendev.dev]; + } else { + snprintf(label, sizeof(label), "xencons%d", con->xendev.dev); + con->chr = qemu_chr_new(label, output, NULL); + } + + xenstore_store_pv_console_info(con->xendev.dev, con->chr); + +out: + g_free(type); + return ret; +} + +static int con_initialise(struct XenDevice *xendev) +{ + struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); + int limit; + + if (xenstore_read_int(con->console, "ring-ref", &con->ring_ref) == -1) + return -1; + if (xenstore_read_int(con->console, "port", &con->xendev.remote_port) == -1) + return -1; + if (xenstore_read_int(con->console, "limit", &limit) == 0) + con->buffer.max_capacity = limit; + + if (!xendev->dev) { + con->sring = xc_map_foreign_range(xen_xc, con->xendev.dom, + XC_PAGE_SIZE, + PROT_READ|PROT_WRITE, + con->ring_ref); + } else { + con->sring = xc_gnttab_map_grant_ref(xendev->gnttabdev, con->xendev.dom, + con->ring_ref, + PROT_READ|PROT_WRITE); + } + if (!con->sring) + return -1; + + xen_be_bind_evtchn(&con->xendev); + if (con->chr) { + if (qemu_chr_fe_claim(con->chr) == 0) { + qemu_chr_add_handlers(con->chr, xencons_can_receive, + xencons_receive, NULL, con); + } else { + xen_be_printf(xendev, 0, + "xen_console_init error chardev %s already used\n", + con->chr->label); + con->chr = NULL; + } + } + + xen_be_printf(xendev, 1, "ring mfn %d, remote port %d, local port %d, limit %zd\n", + con->ring_ref, + con->xendev.remote_port, + con->xendev.local_port, + con->buffer.max_capacity); + return 0; +} + +static void con_disconnect(struct XenDevice *xendev) +{ + struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); + + if (!xendev->dev) { + return; + } + if (con->chr) { + qemu_chr_add_handlers(con->chr, NULL, NULL, NULL, NULL); + qemu_chr_fe_release(con->chr); + } + xen_be_unbind_evtchn(&con->xendev); + + if (con->sring) { + if (!xendev->gnttabdev) { + munmap(con->sring, XC_PAGE_SIZE); + } else { + xc_gnttab_munmap(xendev->gnttabdev, con->sring, 1); + } + con->sring = NULL; + } +} + +static void con_event(struct XenDevice *xendev) +{ + struct XenConsole *con = container_of(xendev, struct XenConsole, xendev); + + buffer_append(con); + if (con->buffer.size - con->buffer.consumed) + xencons_send(con); +} + +/* -------------------------------------------------------------------- */ + +struct XenDevOps xen_console_ops = { + .size = sizeof(struct XenConsole), + .flags = DEVOPS_FLAG_IGNORE_STATE|DEVOPS_FLAG_NEED_GNTDEV, + .init = con_init, + .initialise = con_initialise, + .event = con_event, + .disconnect = con_disconnect, +}; diff --git a/hw/char/xilinx_uartlite.c b/hw/char/xilinx_uartlite.c new file mode 100644 index 0000000000..079f4d4e1a --- /dev/null +++ b/hw/char/xilinx_uartlite.c @@ -0,0 +1,231 @@ +/* + * 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 "char/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 + +struct xlx_uartlite +{ + SysBusDevice busdev; + 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]; +}; + +static void uart_update_irq(struct xlx_uartlite *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(struct xlx_uartlite *s) +{ + uint32_t r; + + r = s->regs[R_STATUS]; + r &= ~7; + r |= 1 << 2; /* Tx fifo is always empty. We are fast :) */ + r |= (s->rx_fifo_len == sizeof (s->rx_fifo)) << 1; + r |= (!!s->rx_fifo_len); + s->regs[R_STATUS] = r; +} + +static uint64_t +uart_read(void *opaque, hwaddr addr, unsigned int size) +{ + struct xlx_uartlite *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) +{ + struct xlx_uartlite *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) +{ + struct xlx_uartlite *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) +{ + struct xlx_uartlite *s = opaque; + + return s->rx_fifo_len < sizeof(s->rx_fifo); +} + +static void uart_event(void *opaque, int event) +{ + +} + +static int xilinx_uartlite_init(SysBusDevice *dev) +{ + struct xlx_uartlite *s = FROM_SYSBUS(typeof (*s), dev); + + sysbus_init_irq(dev, &s->irq); + + uart_update_status(s); + memory_region_init_io(&s->mmio, &uart_ops, s, "xlnx.xps-uartlite", + R_MAX * 4); + sysbus_init_mmio(dev, &s->mmio); + + s->chr = qemu_char_get_next_serial(); + if (s->chr) + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + return 0; +} + +static void xilinx_uartlite_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = xilinx_uartlite_init; +} + +static const TypeInfo xilinx_uartlite_info = { + .name = "xlnx.xps-uartlite", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof (struct xlx_uartlite), + .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) |