diff options
Diffstat (limited to 'hw/char')
-rw-r--r-- | hw/char/Makefile.objs | 15 | ||||
-rw-r--r-- | hw/char/debugcon.c | 134 | ||||
-rw-r--r-- | hw/char/etraxfs_ser.c | 246 | ||||
-rw-r--r-- | hw/char/exynos4210_uart.c | 676 | ||||
-rw-r--r-- | hw/char/grlib_apbuart.c | 293 | ||||
-rw-r--r-- | hw/char/imx_serial.c | 467 | ||||
-rw-r--r-- | hw/char/lm32_juart.c | 159 | ||||
-rw-r--r-- | hw/char/lm32_uart.c | 297 | ||||
-rw-r--r-- | hw/char/mcf_uart.c | 307 | ||||
-rw-r--r-- | hw/char/milkymist-uart.c | 244 | ||||
-rw-r--r-- | hw/char/omap_uart.c | 187 | ||||
-rw-r--r-- | hw/char/sclpconsole.c | 305 | ||||
-rw-r--r-- | hw/char/sh_serial.c | 410 | ||||
-rw-r--r-- | hw/char/spapr_vty.c | 221 |
14 files changed, 3961 insertions, 0 deletions
diff --git a/hw/char/Makefile.objs b/hw/char/Makefile.objs index ddfd3ec9cb..f8f3dbca3e 100644 --- a/hw/char/Makefile.objs +++ b/hw/char/Makefile.objs @@ -9,4 +9,19 @@ common-obj-$(CONFIG_XILINX) += xilinx_uartlite.o common-obj-$(CONFIG_XEN_BACKEND) += xen_console.o common-obj-$(CONFIG_CADENCE) += cadence_uart.o +obj-$(CONFIG_EXYNOS4) += exynos4210_uart.o +obj-$(CONFIG_COLDFIRE) += mcf_uart.o +obj-$(CONFIG_OMAP) += omap_uart.o +obj-$(CONFIG_SH4) += sh_serial.o +obj-$(CONFIG_PSERIES) += spapr_vty.o + +common-obj-$(CONFIG_ETRAXFS) += etraxfs_ser.o +common-obj-$(CONFIG_ISA_DEBUG) += debugcon.o +common-obj-$(CONFIG_GRLIB) += grlib_apbuart.o +common-obj-$(CONFIG_IMX) += imx_serial.o +common-obj-$(CONFIG_LM32) += lm32_juart.o +common-obj-$(CONFIG_LM32) += lm32_uart.o +common-obj-$(CONFIG_MILKYMIST) += milkymist-uart.o +common-obj-$(CONFIG_SCLPCONSOLE) += sclpconsole.o + obj-$(CONFIG_VIRTIO) += virtio-serial-bus.o diff --git a/hw/char/debugcon.c b/hw/char/debugcon.c new file mode 100644 index 0000000000..0588eebb77 --- /dev/null +++ b/hw/char/debugcon.c @@ -0,0 +1,134 @@ +/* + * QEMU Bochs-style debug console ("port E9") emulation + * + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2008 Citrix Systems, Inc. + * Copyright (c) Intel Corporation; author: H. Peter Anvin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/hw.h" +#include "char/char.h" +#include "hw/isa/isa.h" +#include "hw/i386/pc.h" + +#define TYPE_ISA_DEBUGCON_DEVICE "isa-debugcon" +#define ISA_DEBUGCON_DEVICE(obj) \ + OBJECT_CHECK(ISADebugconState, (obj), TYPE_ISA_DEBUGCON_DEVICE) + +//#define DEBUG_DEBUGCON + +typedef struct DebugconState { + MemoryRegion io; + CharDriverState *chr; + uint32_t readback; +} DebugconState; + +typedef struct ISADebugconState { + ISADevice parent_obj; + + uint32_t iobase; + DebugconState state; +} ISADebugconState; + +static void debugcon_ioport_write(void *opaque, hwaddr addr, uint64_t val, + unsigned width) +{ + DebugconState *s = opaque; + unsigned char ch = val; + +#ifdef DEBUG_DEBUGCON + printf("debugcon: write addr=0x%04x val=0x%02x\n", addr, val); +#endif + + qemu_chr_fe_write(s->chr, &ch, 1); +} + + +static uint64_t debugcon_ioport_read(void *opaque, hwaddr addr, unsigned width) +{ + DebugconState *s = opaque; + +#ifdef DEBUG_DEBUGCON + printf("debugcon: read addr=0x%04x\n", addr); +#endif + + return s->readback; +} + +static const MemoryRegionOps debugcon_ops = { + .read = debugcon_ioport_read, + .write = debugcon_ioport_write, + .valid.min_access_size = 1, + .valid.max_access_size = 1, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void debugcon_init_core(DebugconState *s) +{ + if (!s->chr) { + fprintf(stderr, "Can't create debugcon device, empty char device\n"); + exit(1); + } + + qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, s); +} + +static int debugcon_isa_initfn(ISADevice *dev) +{ + ISADebugconState *isa = ISA_DEBUGCON_DEVICE(dev); + DebugconState *s = &isa->state; + + debugcon_init_core(s); + memory_region_init_io(&s->io, &debugcon_ops, s, + TYPE_ISA_DEBUGCON_DEVICE, 1); + memory_region_add_subregion(isa_address_space_io(dev), + isa->iobase, &s->io); + return 0; +} + +static Property debugcon_isa_properties[] = { + DEFINE_PROP_HEX32("iobase", ISADebugconState, iobase, 0xe9), + DEFINE_PROP_CHR("chardev", ISADebugconState, state.chr), + DEFINE_PROP_HEX32("readback", ISADebugconState, state.readback, 0xe9), + DEFINE_PROP_END_OF_LIST(), +}; + +static void debugcon_isa_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = debugcon_isa_initfn; + dc->props = debugcon_isa_properties; +} + +static const TypeInfo debugcon_isa_info = { + .name = TYPE_ISA_DEBUGCON_DEVICE, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISADebugconState), + .class_init = debugcon_isa_class_initfn, +}; + +static void debugcon_register_types(void) +{ + type_register_static(&debugcon_isa_info); +} + +type_init(debugcon_register_types) diff --git a/hw/char/etraxfs_ser.c b/hw/char/etraxfs_ser.c new file mode 100644 index 0000000000..b7499d775b --- /dev/null +++ b/hw/char/etraxfs_ser.c @@ -0,0 +1,246 @@ +/* + * QEMU ETRAX System Emulator + * + * Copyright (c) 2007 Edgar E. Iglesias, Axis Communications AB. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sysbus.h" +#include "char/char.h" +#include "qemu/log.h" + +#define D(x) + +#define RW_TR_CTRL (0x00 / 4) +#define RW_TR_DMA_EN (0x04 / 4) +#define RW_REC_CTRL (0x08 / 4) +#define RW_DOUT (0x1c / 4) +#define RS_STAT_DIN (0x20 / 4) +#define R_STAT_DIN (0x24 / 4) +#define RW_INTR_MASK (0x2c / 4) +#define RW_ACK_INTR (0x30 / 4) +#define R_INTR (0x34 / 4) +#define R_MASKED_INTR (0x38 / 4) +#define R_MAX (0x3c / 4) + +#define STAT_DAV 16 +#define STAT_TR_IDLE 22 +#define STAT_TR_RDY 24 + +struct etrax_serial +{ + SysBusDevice busdev; + MemoryRegion mmio; + CharDriverState *chr; + qemu_irq irq; + + int pending_tx; + + uint8_t rx_fifo[16]; + unsigned int rx_fifo_pos; + unsigned int rx_fifo_len; + + /* Control registers. */ + uint32_t regs[R_MAX]; +}; + +static void ser_update_irq(struct etrax_serial *s) +{ + + if (s->rx_fifo_len) { + s->regs[R_INTR] |= 8; + } else { + s->regs[R_INTR] &= ~8; + } + + s->regs[R_MASKED_INTR] = s->regs[R_INTR] & s->regs[RW_INTR_MASK]; + qemu_set_irq(s->irq, !!s->regs[R_MASKED_INTR]); +} + +static uint64_t +ser_read(void *opaque, hwaddr addr, unsigned int size) +{ + struct etrax_serial *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) + { + case R_STAT_DIN: + r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 15]; + if (s->rx_fifo_len) { + r |= 1 << STAT_DAV; + } + r |= 1 << STAT_TR_RDY; + r |= 1 << STAT_TR_IDLE; + break; + case RS_STAT_DIN: + r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & 15]; + if (s->rx_fifo_len) { + r |= 1 << STAT_DAV; + s->rx_fifo_len--; + } + r |= 1 << STAT_TR_RDY; + r |= 1 << STAT_TR_IDLE; + break; + default: + r = s->regs[addr]; + D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr, r)); + break; + } + return r; +} + +static void +ser_write(void *opaque, hwaddr addr, + uint64_t val64, unsigned int size) +{ + struct etrax_serial *s = opaque; + uint32_t value = val64; + unsigned char ch = val64; + + D(qemu_log("%s " TARGET_FMT_plx "=%x\n", __func__, addr, value)); + addr >>= 2; + switch (addr) + { + case RW_DOUT: + qemu_chr_fe_write(s->chr, &ch, 1); + s->regs[R_INTR] |= 3; + s->pending_tx = 1; + s->regs[addr] = value; + break; + case RW_ACK_INTR: + if (s->pending_tx) { + value &= ~1; + s->pending_tx = 0; + D(qemu_log("fixedup value=%x r_intr=%x\n", + value, s->regs[R_INTR])); + } + s->regs[addr] = value; + s->regs[R_INTR] &= ~value; + D(printf("r_intr=%x\n", s->regs[R_INTR])); + break; + default: + s->regs[addr] = value; + break; + } + ser_update_irq(s); +} + +static const MemoryRegionOps ser_ops = { + .read = ser_read, + .write = ser_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4 + } +}; + +static void serial_receive(void *opaque, const uint8_t *buf, int size) +{ + struct etrax_serial *s = opaque; + int i; + + /* Got a byte. */ + if (s->rx_fifo_len >= 16) { + qemu_log("WARNING: UART dropped char.\n"); + return; + } + + for (i = 0; i < size; i++) { + s->rx_fifo[s->rx_fifo_pos] = buf[i]; + s->rx_fifo_pos++; + s->rx_fifo_pos &= 15; + s->rx_fifo_len++; + } + + ser_update_irq(s); +} + +static int serial_can_receive(void *opaque) +{ + struct etrax_serial *s = opaque; + int r; + + /* Is the receiver enabled? */ + if (!(s->regs[RW_REC_CTRL] & (1 << 3))) { + return 0; + } + + r = sizeof(s->rx_fifo) - s->rx_fifo_len; + return r; +} + +static void serial_event(void *opaque, int event) +{ + +} + +static void etraxfs_ser_reset(DeviceState *d) +{ + struct etrax_serial *s = container_of(d, typeof(*s), busdev.qdev); + + /* transmitter begins ready and idle. */ + s->regs[RS_STAT_DIN] |= (1 << STAT_TR_RDY); + s->regs[RS_STAT_DIN] |= (1 << STAT_TR_IDLE); + + s->regs[RW_REC_CTRL] = 0x10000; + +} + +static int etraxfs_ser_init(SysBusDevice *dev) +{ + struct etrax_serial *s = FROM_SYSBUS(typeof (*s), dev); + + sysbus_init_irq(dev, &s->irq); + memory_region_init_io(&s->mmio, &ser_ops, s, "etraxfs-serial", R_MAX * 4); + sysbus_init_mmio(dev, &s->mmio); + + s->chr = qemu_char_get_next_serial(); + if (s->chr) + qemu_chr_add_handlers(s->chr, + serial_can_receive, serial_receive, + serial_event, s); + return 0; +} + +static void etraxfs_ser_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = etraxfs_ser_init; + dc->reset = etraxfs_ser_reset; +} + +static const TypeInfo etraxfs_ser_info = { + .name = "etraxfs,serial", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct etrax_serial), + .class_init = etraxfs_ser_class_init, +}; + +static void etraxfs_serial_register_types(void) +{ + type_register_static(&etraxfs_ser_info); +} + +type_init(etraxfs_serial_register_types) diff --git a/hw/char/exynos4210_uart.c b/hw/char/exynos4210_uart.c new file mode 100644 index 0000000000..8b4e72ca11 --- /dev/null +++ b/hw/char/exynos4210_uart.c @@ -0,0 +1,676 @@ +/* + * Exynos4210 UART Emulation + * + * Copyright (C) 2011 Samsung Electronics Co Ltd. + * Maksim Kozlov, <m.kozlov@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "hw/sysbus.h" +#include "sysemu/sysemu.h" +#include "char/char.h" + +#include "hw/arm/exynos4210.h" + +#undef DEBUG_UART +#undef DEBUG_UART_EXTEND +#undef DEBUG_IRQ +#undef DEBUG_Rx_DATA +#undef DEBUG_Tx_DATA + +#define DEBUG_UART 0 +#define DEBUG_UART_EXTEND 0 +#define DEBUG_IRQ 0 +#define DEBUG_Rx_DATA 0 +#define DEBUG_Tx_DATA 0 + +#if DEBUG_UART +#define PRINT_DEBUG(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + +#if DEBUG_UART_EXTEND +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) +#else +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do {} while (0) +#endif /* EXTEND */ + +#else +#define PRINT_DEBUG(fmt, args...) \ + do {} while (0) +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do {} while (0) +#endif + +#define PRINT_ERROR(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + +/* + * Offsets for UART registers relative to SFR base address + * for UARTn + * + */ +#define ULCON 0x0000 /* Line Control */ +#define UCON 0x0004 /* Control */ +#define UFCON 0x0008 /* FIFO Control */ +#define UMCON 0x000C /* Modem Control */ +#define UTRSTAT 0x0010 /* Tx/Rx Status */ +#define UERSTAT 0x0014 /* UART Error Status */ +#define UFSTAT 0x0018 /* FIFO Status */ +#define UMSTAT 0x001C /* Modem Status */ +#define UTXH 0x0020 /* Transmit Buffer */ +#define URXH 0x0024 /* Receive Buffer */ +#define UBRDIV 0x0028 /* Baud Rate Divisor */ +#define UFRACVAL 0x002C /* Divisor Fractional Value */ +#define UINTP 0x0030 /* Interrupt Pending */ +#define UINTSP 0x0034 /* Interrupt Source Pending */ +#define UINTM 0x0038 /* Interrupt Mask */ + +/* + * for indexing register in the uint32_t array + * + * 'reg' - register offset (see offsets definitions above) + * + */ +#define I_(reg) (reg / sizeof(uint32_t)) + +typedef struct Exynos4210UartReg { + const char *name; /* the only reason is the debug output */ + hwaddr offset; + uint32_t reset_value; +} Exynos4210UartReg; + +static Exynos4210UartReg exynos4210_uart_regs[] = { + {"ULCON", ULCON, 0x00000000}, + {"UCON", UCON, 0x00003000}, + {"UFCON", UFCON, 0x00000000}, + {"UMCON", UMCON, 0x00000000}, + {"UTRSTAT", UTRSTAT, 0x00000006}, /* RO */ + {"UERSTAT", UERSTAT, 0x00000000}, /* RO */ + {"UFSTAT", UFSTAT, 0x00000000}, /* RO */ + {"UMSTAT", UMSTAT, 0x00000000}, /* RO */ + {"UTXH", UTXH, 0x5c5c5c5c}, /* WO, undefined reset value*/ + {"URXH", URXH, 0x00000000}, /* RO */ + {"UBRDIV", UBRDIV, 0x00000000}, + {"UFRACVAL", UFRACVAL, 0x00000000}, + {"UINTP", UINTP, 0x00000000}, + {"UINTSP", UINTSP, 0x00000000}, + {"UINTM", UINTM, 0x00000000}, +}; + +#define EXYNOS4210_UART_REGS_MEM_SIZE 0x3C + +/* UART FIFO Control */ +#define UFCON_FIFO_ENABLE 0x1 +#define UFCON_Rx_FIFO_RESET 0x2 +#define UFCON_Tx_FIFO_RESET 0x4 +#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT 8 +#define UFCON_Tx_FIFO_TRIGGER_LEVEL (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT) +#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT 4 +#define UFCON_Rx_FIFO_TRIGGER_LEVEL (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT) + +/* Uart FIFO Status */ +#define UFSTAT_Rx_FIFO_COUNT 0xff +#define UFSTAT_Rx_FIFO_FULL 0x100 +#define UFSTAT_Rx_FIFO_ERROR 0x200 +#define UFSTAT_Tx_FIFO_COUNT_SHIFT 16 +#define UFSTAT_Tx_FIFO_COUNT (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT) +#define UFSTAT_Tx_FIFO_FULL_SHIFT 24 +#define UFSTAT_Tx_FIFO_FULL (1 << UFSTAT_Tx_FIFO_FULL_SHIFT) + +/* UART Interrupt Source Pending */ +#define UINTSP_RXD 0x1 /* Receive interrupt */ +#define UINTSP_ERROR 0x2 /* Error interrupt */ +#define UINTSP_TXD 0x4 /* Transmit interrupt */ +#define UINTSP_MODEM 0x8 /* Modem interrupt */ + +/* UART Line Control */ +#define ULCON_IR_MODE_SHIFT 6 +#define ULCON_PARITY_SHIFT 3 +#define ULCON_STOP_BIT_SHIFT 1 + +/* UART Tx/Rx Status */ +#define UTRSTAT_TRANSMITTER_EMPTY 0x4 +#define UTRSTAT_Tx_BUFFER_EMPTY 0x2 +#define UTRSTAT_Rx_BUFFER_DATA_READY 0x1 + +/* UART Error Status */ +#define UERSTAT_OVERRUN 0x1 +#define UERSTAT_PARITY 0x2 +#define UERSTAT_FRAME 0x4 +#define UERSTAT_BREAK 0x8 + +typedef struct { + uint8_t *data; + uint32_t sp, rp; /* store and retrieve pointers */ + uint32_t size; +} Exynos4210UartFIFO; + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + + uint32_t reg[EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)]; + Exynos4210UartFIFO rx; + Exynos4210UartFIFO tx; + + CharDriverState *chr; + qemu_irq irq; + + uint32_t channel; + +} Exynos4210UartState; + + +#if DEBUG_UART +/* Used only for debugging inside PRINT_DEBUG_... macros */ +static const char *exynos4210_uart_regname(hwaddr offset) +{ + + int regs_number = sizeof(exynos4210_uart_regs) / sizeof(Exynos4210UartReg); + int i; + + for (i = 0; i < regs_number; i++) { + if (offset == exynos4210_uart_regs[i].offset) { + return exynos4210_uart_regs[i].name; + } + } + + return NULL; +} +#endif + + +static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch) +{ + q->data[q->sp] = ch; + q->sp = (q->sp + 1) % q->size; +} + +static uint8_t fifo_retrieve(Exynos4210UartFIFO *q) +{ + uint8_t ret = q->data[q->rp]; + q->rp = (q->rp + 1) % q->size; + return ret; +} + +static int fifo_elements_number(Exynos4210UartFIFO *q) +{ + if (q->sp < q->rp) { + return q->size - q->rp + q->sp; + } + + return q->sp - q->rp; +} + +static int fifo_empty_elements_number(Exynos4210UartFIFO *q) +{ + return q->size - fifo_elements_number(q); +} + +static void fifo_reset(Exynos4210UartFIFO *q) +{ + if (q->data != NULL) { + g_free(q->data); + q->data = NULL; + } + + q->data = (uint8_t *)g_malloc0(q->size); + + q->sp = 0; + q->rp = 0; +} + +static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(Exynos4210UartState *s) +{ + uint32_t level = 0; + uint32_t reg; + + reg = (s->reg[I_(UFCON)] & UFCON_Tx_FIFO_TRIGGER_LEVEL) >> + UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT; + + switch (s->channel) { + case 0: + level = reg * 32; + break; + case 1: + case 4: + level = reg * 8; + break; + case 2: + case 3: + level = reg * 2; + break; + default: + level = 0; + PRINT_ERROR("Wrong UART channel number: %d\n", s->channel); + } + + return level; +} + +static void exynos4210_uart_update_irq(Exynos4210UartState *s) +{ + /* + * The Tx interrupt is always requested if the number of data in the + * transmit FIFO is smaller than the trigger level. + */ + if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { + + uint32_t count = (s->reg[I_(UFSTAT)] & UFSTAT_Tx_FIFO_COUNT) >> + UFSTAT_Tx_FIFO_COUNT_SHIFT; + + if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) { + s->reg[I_(UINTSP)] |= UINTSP_TXD; + } + } + + s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)]; + + if (s->reg[I_(UINTP)]) { + qemu_irq_raise(s->irq); + +#if DEBUG_IRQ + fprintf(stderr, "UART%d: IRQ has been raised: %08x\n", + s->channel, s->reg[I_(UINTP)]); +#endif + + } else { + qemu_irq_lower(s->irq); + } +} + +static void exynos4210_uart_update_parameters(Exynos4210UartState *s) +{ + int speed, parity, data_bits, stop_bits, frame_size; + QEMUSerialSetParams ssp; + uint64_t uclk_rate; + + if (s->reg[I_(UBRDIV)] == 0) { + return; + } + + frame_size = 1; /* start bit */ + if (s->reg[I_(ULCON)] & 0x20) { + frame_size++; /* parity bit */ + if (s->reg[I_(ULCON)] & 0x28) { + parity = 'E'; + } else { + parity = 'O'; + } + } else { + parity = 'N'; + } + + if (s->reg[I_(ULCON)] & 0x4) { + stop_bits = 2; + } else { + stop_bits = 1; + } + + data_bits = (s->reg[I_(ULCON)] & 0x3) + 5; + + frame_size += data_bits + stop_bits; + + uclk_rate = 24000000; + + speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) + + (s->reg[I_(UFRACVAL)] & 0x7) + 16); + + ssp.speed = speed; + ssp.parity = parity; + ssp.data_bits = data_bits; + ssp.stop_bits = stop_bits; + + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); + + PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n", + s->channel, speed, parity, data_bits, stop_bits); +} + +static void exynos4210_uart_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + uint8_t ch; + + PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n", s->channel, + offset, exynos4210_uart_regname(offset), (long long unsigned int)val); + + switch (offset) { + case ULCON: + case UBRDIV: + case UFRACVAL: + s->reg[I_(offset)] = val; + exynos4210_uart_update_parameters(s); + break; + case UFCON: + s->reg[I_(UFCON)] = val; + if (val & UFCON_Rx_FIFO_RESET) { + fifo_reset(&s->rx); + s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET; + PRINT_DEBUG("UART%d: Rx FIFO Reset\n", s->channel); + } + if (val & UFCON_Tx_FIFO_RESET) { + fifo_reset(&s->tx); + s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET; + PRINT_DEBUG("UART%d: Tx FIFO Reset\n", s->channel); + } + break; + + case UTXH: + if (s->chr) { + s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY | + UTRSTAT_Tx_BUFFER_EMPTY); + ch = (uint8_t)val; + qemu_chr_fe_write(s->chr, &ch, 1); +#if DEBUG_Tx_DATA + fprintf(stderr, "%c", ch); +#endif + s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY | + UTRSTAT_Tx_BUFFER_EMPTY; + s->reg[I_(UINTSP)] |= UINTSP_TXD; + exynos4210_uart_update_irq(s); + } + break; + + case UINTP: + s->reg[I_(UINTP)] &= ~val; + s->reg[I_(UINTSP)] &= ~val; + PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n", + s->channel, offset, s->reg[I_(UINTP)]); + exynos4210_uart_update_irq(s); + break; + case UTRSTAT: + case UERSTAT: + case UFSTAT: + case UMSTAT: + case URXH: + PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n", + s->channel, exynos4210_uart_regname(offset), offset); + break; + case UINTSP: + s->reg[I_(UINTSP)] &= ~val; + break; + case UINTM: + s->reg[I_(UINTM)] = val; + exynos4210_uart_update_irq(s); + break; + case UCON: + case UMCON: + default: + s->reg[I_(offset)] = val; + break; + } +} +static uint64_t exynos4210_uart_read(void *opaque, hwaddr offset, + unsigned size) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + uint32_t res; + + switch (offset) { + case UERSTAT: /* Read Only */ + res = s->reg[I_(UERSTAT)]; + s->reg[I_(UERSTAT)] = 0; + return res; + case UFSTAT: /* Read Only */ + s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff; + if (fifo_empty_elements_number(&s->rx) == 0) { + s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL; + s->reg[I_(UFSTAT)] &= ~0xff; + } + return s->reg[I_(UFSTAT)]; + case URXH: + if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { + if (fifo_elements_number(&s->rx)) { + res = fifo_retrieve(&s->rx); +#if DEBUG_Rx_DATA + fprintf(stderr, "%c", res); +#endif + if (!fifo_elements_number(&s->rx)) { + s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY; + } else { + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } + } else { + s->reg[I_(UINTSP)] |= UINTSP_ERROR; + exynos4210_uart_update_irq(s); + res = 0; + } + } else { + s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY; + res = s->reg[I_(URXH)]; + } + return res; + case UTXH: + PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n", + s->channel, exynos4210_uart_regname(offset), offset); + break; + default: + return s->reg[I_(offset)]; + } + + return 0; +} + +static const MemoryRegionOps exynos4210_uart_ops = { + .read = exynos4210_uart_read, + .write = exynos4210_uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .max_access_size = 4, + .unaligned = false + }, +}; + +static int exynos4210_uart_can_receive(void *opaque) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + + return fifo_empty_elements_number(&s->rx); +} + + +static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + int i; + + if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { + if (fifo_empty_elements_number(&s->rx) < size) { + for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) { + fifo_store(&s->rx, buf[i]); + } + s->reg[I_(UINTSP)] |= UINTSP_ERROR; + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } else { + for (i = 0; i < size; i++) { + fifo_store(&s->rx, buf[i]); + } + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } + /* XXX: Around here we maybe should check Rx trigger level */ + s->reg[I_(UINTSP)] |= UINTSP_RXD; + } else { + s->reg[I_(URXH)] = buf[0]; + s->reg[I_(UINTSP)] |= UINTSP_RXD; + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } + + exynos4210_uart_update_irq(s); +} + + +static void exynos4210_uart_event(void *opaque, int event) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + + if (event == CHR_EVENT_BREAK) { + /* When the RxDn is held in logic 0, then a null byte is pushed into the + * fifo */ + fifo_store(&s->rx, '\0'); + s->reg[I_(UERSTAT)] |= UERSTAT_BREAK; + exynos4210_uart_update_irq(s); + } +} + + +static void exynos4210_uart_reset(DeviceState *dev) +{ + Exynos4210UartState *s = + container_of(dev, Exynos4210UartState, busdev.qdev); + int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg); + int i; + + for (i = 0; i < regs_number; i++) { + s->reg[I_(exynos4210_uart_regs[i].offset)] = + exynos4210_uart_regs[i].reset_value; + } + + fifo_reset(&s->rx); + fifo_reset(&s->tx); + + PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size); +} + +static const VMStateDescription vmstate_exynos4210_uart_fifo = { + .name = "exynos4210.uart.fifo", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sp, Exynos4210UartFIFO), + VMSTATE_UINT32(rp, Exynos4210UartFIFO), + VMSTATE_VBUFFER_UINT32(data, Exynos4210UartFIFO, 1, NULL, 0, size), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_uart = { + .name = "exynos4210.uart", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(rx, Exynos4210UartState, 1, + vmstate_exynos4210_uart_fifo, Exynos4210UartFIFO), + VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState, + EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)), + VMSTATE_END_OF_LIST() + } +}; + +DeviceState *exynos4210_uart_create(hwaddr addr, + int fifo_size, + int channel, + CharDriverState *chr, + qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *bus; + + const char chr_name[] = "serial"; + char label[ARRAY_SIZE(chr_name) + 1]; + + dev = qdev_create(NULL, "exynos4210.uart"); + + if (!chr) { + if (channel >= MAX_SERIAL_PORTS) { + hw_error("Only %d serial ports are supported by QEMU.\n", + MAX_SERIAL_PORTS); + } + chr = serial_hds[channel]; + if (!chr) { + snprintf(label, ARRAY_SIZE(label), "%s%d", chr_name, channel); + chr = qemu_chr_new(label, "null", NULL); + if (!(chr)) { + hw_error("Can't assign serial port to UART%d.\n", channel); + } + } + } + + qdev_prop_set_chr(dev, "chardev", chr); + qdev_prop_set_uint32(dev, "channel", channel); + qdev_prop_set_uint32(dev, "rx-size", fifo_size); + qdev_prop_set_uint32(dev, "tx-size", fifo_size); + + bus = SYS_BUS_DEVICE(dev); + qdev_init_nofail(dev); + if (addr != (hwaddr)-1) { + sysbus_mmio_map(bus, 0, addr); + } + sysbus_connect_irq(bus, 0, irq); + + return dev; +} + +static int exynos4210_uart_init(SysBusDevice *dev) +{ + Exynos4210UartState *s = FROM_SYSBUS(Exynos4210UartState, dev); + + /* memory mapping */ + memory_region_init_io(&s->iomem, &exynos4210_uart_ops, s, "exynos4210.uart", + EXYNOS4210_UART_REGS_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + sysbus_init_irq(dev, &s->irq); + + qemu_chr_add_handlers(s->chr, exynos4210_uart_can_receive, + exynos4210_uart_receive, exynos4210_uart_event, s); + + return 0; +} + +static Property exynos4210_uart_properties[] = { + DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr), + DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0), + DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16), + DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16), + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_uart_init; + dc->reset = exynos4210_uart_reset; + dc->props = exynos4210_uart_properties; + dc->vmsd = &vmstate_exynos4210_uart; +} + +static const TypeInfo exynos4210_uart_info = { + .name = "exynos4210.uart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210UartState), + .class_init = exynos4210_uart_class_init, +}; + +static void exynos4210_uart_register(void) +{ + type_register_static(&exynos4210_uart_info); +} + +type_init(exynos4210_uart_register) diff --git a/hw/char/grlib_apbuart.c b/hw/char/grlib_apbuart.c new file mode 100644 index 0000000000..62f799083c --- /dev/null +++ b/hw/char/grlib_apbuart.c @@ -0,0 +1,293 @@ +/* + * QEMU GRLIB APB UART Emulator + * + * Copyright (c) 2010-2011 AdaCore + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sysbus.h" +#include "char/char.h" + +#include "trace.h" + +#define UART_REG_SIZE 20 /* Size of memory mapped registers */ + +/* UART status register fields */ +#define UART_DATA_READY (1 << 0) +#define UART_TRANSMIT_SHIFT_EMPTY (1 << 1) +#define UART_TRANSMIT_FIFO_EMPTY (1 << 2) +#define UART_BREAK_RECEIVED (1 << 3) +#define UART_OVERRUN (1 << 4) +#define UART_PARITY_ERROR (1 << 5) +#define UART_FRAMING_ERROR (1 << 6) +#define UART_TRANSMIT_FIFO_HALF (1 << 7) +#define UART_RECEIVE_FIFO_HALF (1 << 8) +#define UART_TRANSMIT_FIFO_FULL (1 << 9) +#define UART_RECEIVE_FIFO_FULL (1 << 10) + +/* UART control register fields */ +#define UART_RECEIVE_ENABLE (1 << 0) +#define UART_TRANSMIT_ENABLE (1 << 1) +#define UART_RECEIVE_INTERRUPT (1 << 2) +#define UART_TRANSMIT_INTERRUPT (1 << 3) +#define UART_PARITY_SELECT (1 << 4) +#define UART_PARITY_ENABLE (1 << 5) +#define UART_FLOW_CONTROL (1 << 6) +#define UART_LOOPBACK (1 << 7) +#define UART_EXTERNAL_CLOCK (1 << 8) +#define UART_RECEIVE_FIFO_INTERRUPT (1 << 9) +#define UART_TRANSMIT_FIFO_INTERRUPT (1 << 10) +#define UART_FIFO_DEBUG_MODE (1 << 11) +#define UART_OUTPUT_ENABLE (1 << 12) +#define UART_FIFO_AVAILABLE (1 << 31) + +/* Memory mapped register offsets */ +#define DATA_OFFSET 0x00 +#define STATUS_OFFSET 0x04 +#define CONTROL_OFFSET 0x08 +#define SCALER_OFFSET 0x0C /* not supported */ +#define FIFO_DEBUG_OFFSET 0x10 /* not supported */ + +#define FIFO_LENGTH 1024 + +typedef struct UART { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq irq; + + CharDriverState *chr; + + /* registers */ + uint32_t status; + uint32_t control; + + /* FIFO */ + char buffer[FIFO_LENGTH]; + int len; + int current; +} UART; + +static int uart_data_to_read(UART *uart) +{ + return uart->current < uart->len; +} + +static char uart_pop(UART *uart) +{ + char ret; + + if (uart->len == 0) { + uart->status &= ~UART_DATA_READY; + return 0; + } + + ret = uart->buffer[uart->current++]; + + if (uart->current >= uart->len) { + /* Flush */ + uart->len = 0; + uart->current = 0; + } + + if (!uart_data_to_read(uart)) { + uart->status &= ~UART_DATA_READY; + } + + return ret; +} + +static void uart_add_to_fifo(UART *uart, + const uint8_t *buffer, + int length) +{ + if (uart->len + length > FIFO_LENGTH) { + abort(); + } + memcpy(uart->buffer + uart->len, buffer, length); + uart->len += length; +} + +static int grlib_apbuart_can_receive(void *opaque) +{ + UART *uart = opaque; + + return FIFO_LENGTH - uart->len; +} + +static void grlib_apbuart_receive(void *opaque, const uint8_t *buf, int size) +{ + UART *uart = opaque; + + if (uart->control & UART_RECEIVE_ENABLE) { + uart_add_to_fifo(uart, buf, size); + + uart->status |= UART_DATA_READY; + + if (uart->control & UART_RECEIVE_INTERRUPT) { + qemu_irq_pulse(uart->irq); + } + } +} + +static void grlib_apbuart_event(void *opaque, int event) +{ + trace_grlib_apbuart_event(event); +} + + +static uint64_t grlib_apbuart_read(void *opaque, hwaddr addr, + unsigned size) +{ + UART *uart = opaque; + + addr &= 0xff; + + /* Unit registers */ + switch (addr) { + case DATA_OFFSET: + case DATA_OFFSET + 3: /* when only one byte read */ + return uart_pop(uart); + + case STATUS_OFFSET: + /* Read Only */ + return uart->status; + + case CONTROL_OFFSET: + return uart->control; + + case SCALER_OFFSET: + /* Not supported */ + return 0; + + default: + trace_grlib_apbuart_readl_unknown(addr); + return 0; + } +} + +static void grlib_apbuart_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + UART *uart = opaque; + unsigned char c = 0; + + addr &= 0xff; + + /* Unit registers */ + switch (addr) { + case DATA_OFFSET: + case DATA_OFFSET + 3: /* When only one byte write */ + /* Transmit when character device available and transmitter enabled */ + if ((uart->chr) && (uart->control & UART_TRANSMIT_ENABLE)) { + c = value & 0xFF; + qemu_chr_fe_write(uart->chr, &c, 1); + /* Generate interrupt */ + if (uart->control & UART_TRANSMIT_INTERRUPT) { + qemu_irq_pulse(uart->irq); + } + } + return; + + case STATUS_OFFSET: + /* Read Only */ + return; + + case CONTROL_OFFSET: + uart->control = value; + return; + + case SCALER_OFFSET: + /* Not supported */ + return; + + default: + break; + } + + trace_grlib_apbuart_writel_unknown(addr, value); +} + +static const MemoryRegionOps grlib_apbuart_ops = { + .write = grlib_apbuart_write, + .read = grlib_apbuart_read, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int grlib_apbuart_init(SysBusDevice *dev) +{ + UART *uart = FROM_SYSBUS(typeof(*uart), dev); + + qemu_chr_add_handlers(uart->chr, + grlib_apbuart_can_receive, + grlib_apbuart_receive, + grlib_apbuart_event, + uart); + + sysbus_init_irq(dev, &uart->irq); + + memory_region_init_io(&uart->iomem, &grlib_apbuart_ops, uart, + "uart", UART_REG_SIZE); + + sysbus_init_mmio(dev, &uart->iomem); + + return 0; +} + +static void grlib_apbuart_reset(DeviceState *d) +{ + UART *uart = container_of(d, UART, busdev.qdev); + + /* Transmitter FIFO and shift registers are always empty in QEMU */ + uart->status = UART_TRANSMIT_FIFO_EMPTY | UART_TRANSMIT_SHIFT_EMPTY; + /* Everything is off */ + uart->control = 0; + /* Flush receive FIFO */ + uart->len = 0; + uart->current = 0; +} + +static Property grlib_apbuart_properties[] = { + DEFINE_PROP_CHR("chrdev", UART, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void grlib_apbuart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = grlib_apbuart_init; + dc->reset = grlib_apbuart_reset; + dc->props = grlib_apbuart_properties; +} + +static const TypeInfo grlib_apbuart_info = { + .name = "grlib,apbuart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(UART), + .class_init = grlib_apbuart_class_init, +}; + +static void grlib_apbuart_register_types(void) +{ + type_register_static(&grlib_apbuart_info); +} + +type_init(grlib_apbuart_register_types) diff --git a/hw/char/imx_serial.c b/hw/char/imx_serial.c new file mode 100644 index 0000000000..d7ec2090fd --- /dev/null +++ b/hw/char/imx_serial.c @@ -0,0 +1,467 @@ +/* + * IMX31 UARTS + * + * Copyright (c) 2008 OKL + * Originally Written by Hans Jiang + * Copyright (c) 2011 NICTA Pty Ltd. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + * This is a `bare-bones' implementation of the IMX series serial ports. + * TODO: + * -- implement FIFOs. The real hardware has 32 word transmit + * and receive FIFOs; we currently use a 1-char buffer + * -- implement DMA + * -- implement BAUD-rate and modem lines, for when the backend + * is a real serial device. + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "sysemu/sysemu.h" +#include "char/char.h" +#include "hw/arm/imx.h" + +//#define DEBUG_SERIAL 1 +#ifdef DEBUG_SERIAL +#define DPRINTF(fmt, args...) \ +do { printf("imx_serial: " fmt , ##args); } while (0) +#else +#define DPRINTF(fmt, args...) do {} while (0) +#endif + +/* + * Define to 1 for messages about attempts to + * access unimplemented registers or similar. + */ +//#define DEBUG_IMPLEMENTATION 1 +#ifdef DEBUG_IMPLEMENTATION +# define IPRINTF(fmt, args...) \ + do { fprintf(stderr, "imx_serial: " fmt, ##args); } while (0) +#else +# define IPRINTF(fmt, args...) do {} while (0) +#endif + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + int32_t readbuff; + + uint32_t usr1; + uint32_t usr2; + uint32_t ucr1; + uint32_t ucr2; + uint32_t uts1; + + /* + * The registers below are implemented just so that the + * guest OS sees what it has written + */ + uint32_t onems; + uint32_t ufcr; + uint32_t ubmr; + uint32_t ubrc; + uint32_t ucr3; + + qemu_irq irq; + CharDriverState *chr; +} IMXSerialState; + +static const VMStateDescription vmstate_imx_serial = { + .name = "imx-serial", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(readbuff, IMXSerialState), + VMSTATE_UINT32(usr1, IMXSerialState), + VMSTATE_UINT32(usr2, IMXSerialState), + VMSTATE_UINT32(ucr1, IMXSerialState), + VMSTATE_UINT32(uts1, IMXSerialState), + VMSTATE_UINT32(onems, IMXSerialState), + VMSTATE_UINT32(ufcr, IMXSerialState), + VMSTATE_UINT32(ubmr, IMXSerialState), + VMSTATE_UINT32(ubrc, IMXSerialState), + VMSTATE_UINT32(ucr3, IMXSerialState), + VMSTATE_END_OF_LIST() + }, +}; + + +#define URXD_CHARRDY (1<<15) /* character read is valid */ +#define URXD_ERR (1<<14) /* Character has error */ +#define URXD_BRK (1<<11) /* Break received */ + +#define USR1_PARTYER (1<<15) /* Parity Error */ +#define USR1_RTSS (1<<14) /* RTS pin status */ +#define USR1_TRDY (1<<13) /* Tx ready */ +#define USR1_RTSD (1<<12) /* RTS delta: pin changed state */ +#define USR1_ESCF (1<<11) /* Escape sequence interrupt */ +#define USR1_FRAMERR (1<<10) /* Framing error */ +#define USR1_RRDY (1<<9) /* receiver ready */ +#define USR1_AGTIM (1<<8) /* Aging timer interrupt */ +#define USR1_DTRD (1<<7) /* DTR changed */ +#define USR1_RXDS (1<<6) /* Receiver is idle */ +#define USR1_AIRINT (1<<5) /* Aysnch IR interrupt */ +#define USR1_AWAKE (1<<4) /* Falling edge detected on RXd pin */ + +#define USR2_ADET (1<<15) /* Autobaud complete */ +#define USR2_TXFE (1<<14) /* Transmit FIFO empty */ +#define USR2_DTRF (1<<13) /* DTR/DSR transition */ +#define USR2_IDLE (1<<12) /* UART has been idle for too long */ +#define USR2_ACST (1<<11) /* Autobaud counter stopped */ +#define USR2_RIDELT (1<<10) /* Ring Indicator delta */ +#define USR2_RIIN (1<<9) /* Ring Indicator Input */ +#define USR2_IRINT (1<<8) /* Serial Infrared Interrupt */ +#define USR2_WAKE (1<<7) /* Start bit detected */ +#define USR2_DCDDELT (1<<6) /* Data Carrier Detect delta */ +#define USR2_DCDIN (1<<5) /* Data Carrier Detect Input */ +#define USR2_RTSF (1<<4) /* RTS transition */ +#define USR2_TXDC (1<<3) /* Transmission complete */ +#define USR2_BRCD (1<<2) /* Break condition detected */ +#define USR2_ORE (1<<1) /* Overrun error */ +#define USR2_RDR (1<<0) /* Receive data ready */ + +#define UCR1_TRDYEN (1<<13) /* Tx Ready Interrupt Enable */ +#define UCR1_RRDYEN (1<<9) /* Rx Ready Interrupt Enable */ +#define UCR1_TXMPTYEN (1<<6) /* Tx Empty Interrupt Enable */ +#define UCR1_UARTEN (1<<0) /* UART Enable */ + +#define UCR2_TXEN (1<<2) /* Transmitter enable */ +#define UCR2_RXEN (1<<1) /* Receiver enable */ +#define UCR2_SRST (1<<0) /* Reset complete */ + +#define UTS1_TXEMPTY (1<<6) +#define UTS1_RXEMPTY (1<<5) +#define UTS1_TXFULL (1<<4) +#define UTS1_RXFULL (1<<3) + +static void imx_update(IMXSerialState *s) +{ + uint32_t flags; + + flags = (s->usr1 & s->ucr1) & (USR1_TRDY|USR1_RRDY); + if (!(s->ucr1 & UCR1_TXMPTYEN)) { + flags &= ~USR1_TRDY; + } + + qemu_set_irq(s->irq, !!flags); +} + +static void imx_serial_reset(IMXSerialState *s) +{ + + s->usr1 = USR1_TRDY | USR1_RXDS; + /* + * Fake attachment of a terminal: assert RTS. + */ + s->usr1 |= USR1_RTSS; + s->usr2 = USR2_TXFE | USR2_TXDC | USR2_DCDIN; + s->uts1 = UTS1_RXEMPTY | UTS1_TXEMPTY; + s->ucr1 = 0; + s->ucr2 = UCR2_SRST; + s->ucr3 = 0x700; + s->ubmr = 0; + s->ubrc = 4; + s->readbuff = URXD_ERR; +} + +static void imx_serial_reset_at_boot(DeviceState *dev) +{ + IMXSerialState *s = container_of(dev, IMXSerialState, busdev.qdev); + + imx_serial_reset(s); + + /* + * enable the uart on boot, so messages from the linux decompresser + * are visible. On real hardware this is done by the boot rom + * before anything else is loaded. + */ + s->ucr1 = UCR1_UARTEN; + s->ucr2 = UCR2_TXEN; + +} + +static uint64_t imx_serial_read(void *opaque, hwaddr offset, + unsigned size) +{ + IMXSerialState *s = (IMXSerialState *)opaque; + uint32_t c; + + DPRINTF("read(offset=%x)\n", offset >> 2); + switch (offset >> 2) { + case 0x0: /* URXD */ + c = s->readbuff; + if (!(s->uts1 & UTS1_RXEMPTY)) { + /* Character is valid */ + c |= URXD_CHARRDY; + s->usr1 &= ~USR1_RRDY; + s->usr2 &= ~USR2_RDR; + s->uts1 |= UTS1_RXEMPTY; + imx_update(s); + qemu_chr_accept_input(s->chr); + } + return c; + + case 0x20: /* UCR1 */ + return s->ucr1; + + case 0x21: /* UCR2 */ + return s->ucr2; + + case 0x25: /* USR1 */ + return s->usr1; + + case 0x26: /* USR2 */ + return s->usr2; + + case 0x2A: /* BRM Modulator */ + return s->ubmr; + + case 0x2B: /* Baud Rate Count */ + return s->ubrc; + + case 0x2d: /* Test register */ + return s->uts1; + + case 0x24: /* UFCR */ + return s->ufcr; + + case 0x2c: + return s->onems; + + case 0x22: /* UCR3 */ + return s->ucr3; + + case 0x23: /* UCR4 */ + case 0x29: /* BRM Incremental */ + return 0x0; /* TODO */ + + default: + IPRINTF("imx_serial_read: bad offset: 0x%x\n", (int)offset); + return 0; + } +} + +static void imx_serial_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + IMXSerialState *s = (IMXSerialState *)opaque; + unsigned char ch; + + DPRINTF("write(offset=%x, value = %x) to %s\n", + offset >> 2, + (unsigned int)value, s->chr ? s->chr->label : "NODEV"); + + switch (offset >> 2) { + case 0x10: /* UTXD */ + ch = value; + if (s->ucr2 & UCR2_TXEN) { + if (s->chr) { + qemu_chr_fe_write(s->chr, &ch, 1); + } + s->usr1 &= ~USR1_TRDY; + imx_update(s); + s->usr1 |= USR1_TRDY; + imx_update(s); + } + break; + + case 0x20: /* UCR1 */ + s->ucr1 = value & 0xffff; + DPRINTF("write(ucr1=%x)\n", (unsigned int)value); + imx_update(s); + break; + + case 0x21: /* UCR2 */ + /* + * Only a few bits in control register 2 are implemented as yet. + * If it's intended to use a real serial device as a back-end, this + * register will have to be implemented more fully. + */ + if (!(value & UCR2_SRST)) { + imx_serial_reset(s); + imx_update(s); + value |= UCR2_SRST; + } + if (value & UCR2_RXEN) { + if (!(s->ucr2 & UCR2_RXEN)) { + qemu_chr_accept_input(s->chr); + } + } + s->ucr2 = value & 0xffff; + break; + + case 0x25: /* USR1 */ + value &= USR1_AWAKE | USR1_AIRINT | USR1_DTRD | USR1_AGTIM | + USR1_FRAMERR | USR1_ESCF | USR1_RTSD | USR1_PARTYER; + s->usr1 &= ~value; + break; + + case 0x26: /* USR2 */ + /* + * Writing 1 to some bits clears them; all other + * values are ignored + */ + value &= USR2_ADET | USR2_DTRF | USR2_IDLE | USR2_ACST | + USR2_RIDELT | USR2_IRINT | USR2_WAKE | + USR2_DCDDELT | USR2_RTSF | USR2_BRCD | USR2_ORE; + s->usr2 &= ~value; + break; + + /* + * Linux expects to see what it writes to these registers + * We don't currently alter the baud rate + */ + case 0x29: /* UBIR */ + s->ubrc = value & 0xffff; + break; + + case 0x2a: /* UBMR */ + s->ubmr = value & 0xffff; + break; + + case 0x2c: /* One ms reg */ + s->onems = value & 0xffff; + break; + + case 0x24: /* FIFO control register */ + s->ufcr = value & 0xffff; + break; + + case 0x22: /* UCR3 */ + s->ucr3 = value & 0xffff; + break; + + case 0x2d: /* UTS1 */ + case 0x23: /* UCR4 */ + IPRINTF("Unimplemented Register %x written to\n", offset >> 2); + /* TODO */ + break; + + default: + IPRINTF("imx_serial_write: Bad offset 0x%x\n", (int)offset); + } +} + +static int imx_can_receive(void *opaque) +{ + IMXSerialState *s = (IMXSerialState *)opaque; + return !(s->usr1 & USR1_RRDY); +} + +static void imx_put_data(void *opaque, uint32_t value) +{ + IMXSerialState *s = (IMXSerialState *)opaque; + DPRINTF("received char\n"); + s->usr1 |= USR1_RRDY; + s->usr2 |= USR2_RDR; + s->uts1 &= ~UTS1_RXEMPTY; + s->readbuff = value; + imx_update(s); +} + +static void imx_receive(void *opaque, const uint8_t *buf, int size) +{ + imx_put_data(opaque, *buf); +} + +static void imx_event(void *opaque, int event) +{ + if (event == CHR_EVENT_BREAK) { + imx_put_data(opaque, URXD_BRK); + } +} + + +static const struct MemoryRegionOps imx_serial_ops = { + .read = imx_serial_read, + .write = imx_serial_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int imx_serial_init(SysBusDevice *dev) +{ + IMXSerialState *s = FROM_SYSBUS(IMXSerialState, dev); + + + memory_region_init_io(&s->iomem, &imx_serial_ops, s, "imx-serial", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + + if (s->chr) { + qemu_chr_add_handlers(s->chr, imx_can_receive, imx_receive, + imx_event, s); + } else { + DPRINTF("No char dev for uart at 0x%lx\n", + (unsigned long)s->iomem.ram_addr); + } + + return 0; +} + +void imx_serial_create(int uart, const hwaddr addr, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *bus; + CharDriverState *chr; + const char chr_name[] = "serial"; + char label[ARRAY_SIZE(chr_name) + 1]; + + dev = qdev_create(NULL, "imx-serial"); + + if (uart >= MAX_SERIAL_PORTS) { + hw_error("Cannot assign uart %d: QEMU supports only %d ports\n", + uart, MAX_SERIAL_PORTS); + } + chr = serial_hds[uart]; + if (!chr) { + snprintf(label, ARRAY_SIZE(label), "%s%d", chr_name, uart); + chr = qemu_chr_new(label, "null", NULL); + if (!(chr)) { + hw_error("Can't assign serial port to imx-uart%d.\n", uart); + } + } + + qdev_prop_set_chr(dev, "chardev", chr); + bus = SYS_BUS_DEVICE(dev); + qdev_init_nofail(dev); + if (addr != (hwaddr)-1) { + sysbus_mmio_map(bus, 0, addr); + } + sysbus_connect_irq(bus, 0, irq); + +} + + +static Property imx32_serial_properties[] = { + DEFINE_PROP_CHR("chardev", IMXSerialState, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void imx_serial_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = imx_serial_init; + dc->vmsd = &vmstate_imx_serial; + dc->reset = imx_serial_reset_at_boot; + dc->desc = "i.MX series UART"; + dc->props = imx32_serial_properties; +} + +static const TypeInfo imx_serial_info = { + .name = "imx-serial", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMXSerialState), + .class_init = imx_serial_class_init, +}; + +static void imx_serial_register_types(void) +{ + type_register_static(&imx_serial_info); +} + +type_init(imx_serial_register_types) diff --git a/hw/char/lm32_juart.c b/hw/char/lm32_juart.c new file mode 100644 index 0000000000..93f0d1534e --- /dev/null +++ b/hw/char/lm32_juart.c @@ -0,0 +1,159 @@ +/* + * LatticeMico32 JTAG UART model. + * + * Copyright (c) 2010 Michael Walle <michael@walle.cc> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "char/char.h" + +#include "hw/lm32/lm32_juart.h" + +enum { + LM32_JUART_MIN_SAVE_VERSION = 0, + LM32_JUART_CURRENT_SAVE_VERSION = 0, + LM32_JUART_MAX_SAVE_VERSION = 0, +}; + +enum { + JTX_FULL = (1<<8), +}; + +enum { + JRX_FULL = (1<<8), +}; + +struct LM32JuartState { + SysBusDevice busdev; + CharDriverState *chr; + + uint32_t jtx; + uint32_t jrx; +}; +typedef struct LM32JuartState LM32JuartState; + +uint32_t lm32_juart_get_jtx(DeviceState *d) +{ + LM32JuartState *s = container_of(d, LM32JuartState, busdev.qdev); + + trace_lm32_juart_get_jtx(s->jtx); + return s->jtx; +} + +uint32_t lm32_juart_get_jrx(DeviceState *d) +{ + LM32JuartState *s = container_of(d, LM32JuartState, busdev.qdev); + + trace_lm32_juart_get_jrx(s->jrx); + return s->jrx; +} + +void lm32_juart_set_jtx(DeviceState *d, uint32_t jtx) +{ + LM32JuartState *s = container_of(d, LM32JuartState, busdev.qdev); + unsigned char ch = jtx & 0xff; + + trace_lm32_juart_set_jtx(s->jtx); + + s->jtx = jtx; + if (s->chr) { + qemu_chr_fe_write(s->chr, &ch, 1); + } +} + +void lm32_juart_set_jrx(DeviceState *d, uint32_t jtx) +{ + LM32JuartState *s = container_of(d, LM32JuartState, busdev.qdev); + + trace_lm32_juart_set_jrx(s->jrx); + s->jrx &= ~JRX_FULL; +} + +static void juart_rx(void *opaque, const uint8_t *buf, int size) +{ + LM32JuartState *s = opaque; + + s->jrx = *buf | JRX_FULL; +} + +static int juart_can_rx(void *opaque) +{ + LM32JuartState *s = opaque; + + return !(s->jrx & JRX_FULL); +} + +static void juart_event(void *opaque, int event) +{ +} + +static void juart_reset(DeviceState *d) +{ + LM32JuartState *s = container_of(d, LM32JuartState, busdev.qdev); + + s->jtx = 0; + s->jrx = 0; +} + +static int lm32_juart_init(SysBusDevice *dev) +{ + LM32JuartState *s = FROM_SYSBUS(typeof(*s), dev); + + s->chr = qemu_char_get_next_serial(); + if (s->chr) { + qemu_chr_add_handlers(s->chr, juart_can_rx, juart_rx, juart_event, s); + } + + return 0; +} + +static const VMStateDescription vmstate_lm32_juart = { + .name = "lm32-juart", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(jtx, LM32JuartState), + VMSTATE_UINT32(jrx, LM32JuartState), + VMSTATE_END_OF_LIST() + } +}; + +static void lm32_juart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = lm32_juart_init; + dc->reset = juart_reset; + dc->vmsd = &vmstate_lm32_juart; +} + +static const TypeInfo lm32_juart_info = { + .name = "lm32-juart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(LM32JuartState), + .class_init = lm32_juart_class_init, +}; + +static void lm32_juart_register_types(void) +{ + type_register_static(&lm32_juart_info); +} + +type_init(lm32_juart_register_types) diff --git a/hw/char/lm32_uart.c b/hw/char/lm32_uart.c new file mode 100644 index 0000000000..32bc37ac96 --- /dev/null +++ b/hw/char/lm32_uart.c @@ -0,0 +1,297 @@ +/* + * QEMU model of the LatticeMico32 UART block. + * + * Copyright (c) 2010 Michael Walle <michael@walle.cc> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * + * Specification available at: + * http://www.latticesemi.com/documents/mico32uart.pdf + */ + + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "char/char.h" +#include "qemu/error-report.h" + +enum { + R_RXTX = 0, + R_IER, + R_IIR, + R_LCR, + R_MCR, + R_LSR, + R_MSR, + R_DIV, + R_MAX +}; + +enum { + IER_RBRI = (1<<0), + IER_THRI = (1<<1), + IER_RLSI = (1<<2), + IER_MSI = (1<<3), +}; + +enum { + IIR_STAT = (1<<0), + IIR_ID0 = (1<<1), + IIR_ID1 = (1<<2), +}; + +enum { + LCR_WLS0 = (1<<0), + LCR_WLS1 = (1<<1), + LCR_STB = (1<<2), + LCR_PEN = (1<<3), + LCR_EPS = (1<<4), + LCR_SP = (1<<5), + LCR_SB = (1<<6), +}; + +enum { + MCR_DTR = (1<<0), + MCR_RTS = (1<<1), +}; + +enum { + LSR_DR = (1<<0), + LSR_OE = (1<<1), + LSR_PE = (1<<2), + LSR_FE = (1<<3), + LSR_BI = (1<<4), + LSR_THRE = (1<<5), + LSR_TEMT = (1<<6), +}; + +enum { + MSR_DCTS = (1<<0), + MSR_DDSR = (1<<1), + MSR_TERI = (1<<2), + MSR_DDCD = (1<<3), + MSR_CTS = (1<<4), + MSR_DSR = (1<<5), + MSR_RI = (1<<6), + MSR_DCD = (1<<7), +}; + +struct LM32UartState { + SysBusDevice busdev; + MemoryRegion iomem; + CharDriverState *chr; + qemu_irq irq; + + uint32_t regs[R_MAX]; +}; +typedef struct LM32UartState LM32UartState; + +static void uart_update_irq(LM32UartState *s) +{ + unsigned int irq; + + if ((s->regs[R_LSR] & (LSR_OE | LSR_PE | LSR_FE | LSR_BI)) + && (s->regs[R_IER] & IER_RLSI)) { + irq = 1; + s->regs[R_IIR] = IIR_ID1 | IIR_ID0; + } else if ((s->regs[R_LSR] & LSR_DR) && (s->regs[R_IER] & IER_RBRI)) { + irq = 1; + s->regs[R_IIR] = IIR_ID1; + } else if ((s->regs[R_LSR] & LSR_THRE) && (s->regs[R_IER] & IER_THRI)) { + irq = 1; + s->regs[R_IIR] = IIR_ID0; + } else if ((s->regs[R_MSR] & 0x0f) && (s->regs[R_IER] & IER_MSI)) { + irq = 1; + s->regs[R_IIR] = 0; + } else { + irq = 0; + s->regs[R_IIR] = IIR_STAT; + } + + trace_lm32_uart_irq_state(irq); + qemu_set_irq(s->irq, irq); +} + +static uint64_t uart_read(void *opaque, hwaddr addr, + unsigned size) +{ + LM32UartState *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) { + case R_RXTX: + r = s->regs[R_RXTX]; + s->regs[R_LSR] &= ~LSR_DR; + uart_update_irq(s); + qemu_chr_accept_input(s->chr); + break; + case R_IIR: + case R_LSR: + case R_MSR: + r = s->regs[addr]; + break; + case R_IER: + case R_LCR: + case R_MCR: + case R_DIV: + error_report("lm32_uart: read access to write only register 0x" + TARGET_FMT_plx, addr << 2); + break; + default: + error_report("lm32_uart: read access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + trace_lm32_uart_memory_read(addr << 2, r); + return r; +} + +static void uart_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + LM32UartState *s = opaque; + unsigned char ch = value; + + trace_lm32_uart_memory_write(addr, value); + + addr >>= 2; + switch (addr) { + case R_RXTX: + if (s->chr) { + qemu_chr_fe_write(s->chr, &ch, 1); + } + break; + case R_IER: + case R_LCR: + case R_MCR: + case R_DIV: + s->regs[addr] = value; + break; + case R_IIR: + case R_LSR: + case R_MSR: + error_report("lm32_uart: write access to read only register 0x" + TARGET_FMT_plx, addr << 2); + break; + default: + error_report("lm32_uart: write access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + uart_update_irq(s); +} + +static const MemoryRegionOps uart_ops = { + .read = uart_read, + .write = uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void uart_rx(void *opaque, const uint8_t *buf, int size) +{ + LM32UartState *s = opaque; + + if (s->regs[R_LSR] & LSR_DR) { + s->regs[R_LSR] |= LSR_OE; + } + + s->regs[R_LSR] |= LSR_DR; + s->regs[R_RXTX] = *buf; + + uart_update_irq(s); +} + +static int uart_can_rx(void *opaque) +{ + LM32UartState *s = opaque; + + return !(s->regs[R_LSR] & LSR_DR); +} + +static void uart_event(void *opaque, int event) +{ +} + +static void uart_reset(DeviceState *d) +{ + LM32UartState *s = container_of(d, LM32UartState, busdev.qdev); + int i; + + for (i = 0; i < R_MAX; i++) { + s->regs[i] = 0; + } + + /* defaults */ + s->regs[R_LSR] = LSR_THRE | LSR_TEMT; +} + +static int lm32_uart_init(SysBusDevice *dev) +{ + LM32UartState *s = FROM_SYSBUS(typeof(*s), dev); + + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->iomem, &uart_ops, s, "uart", R_MAX * 4); + sysbus_init_mmio(dev, &s->iomem); + + s->chr = qemu_char_get_next_serial(); + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + } + + return 0; +} + +static const VMStateDescription vmstate_lm32_uart = { + .name = "lm32-uart", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, LM32UartState, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void lm32_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = lm32_uart_init; + dc->reset = uart_reset; + dc->vmsd = &vmstate_lm32_uart; +} + +static const TypeInfo lm32_uart_info = { + .name = "lm32-uart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(LM32UartState), + .class_init = lm32_uart_class_init, +}; + +static void lm32_uart_register_types(void) +{ + type_register_static(&lm32_uart_info); +} + +type_init(lm32_uart_register_types) diff --git a/hw/char/mcf_uart.c b/hw/char/mcf_uart.c new file mode 100644 index 0000000000..6724b1b718 --- /dev/null +++ b/hw/char/mcf_uart.c @@ -0,0 +1,307 @@ +/* + * ColdFire UART emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licensed under the GPL + */ +#include "hw/hw.h" +#include "hw/m68k/mcf.h" +#include "char/char.h" +#include "exec/address-spaces.h" + +typedef struct { + MemoryRegion iomem; + uint8_t mr[2]; + uint8_t sr; + uint8_t isr; + uint8_t imr; + uint8_t bg1; + uint8_t bg2; + uint8_t fifo[4]; + uint8_t tb; + int current_mr; + int fifo_len; + int tx_enabled; + int rx_enabled; + qemu_irq irq; + CharDriverState *chr; +} mcf_uart_state; + +/* UART Status Register bits. */ +#define MCF_UART_RxRDY 0x01 +#define MCF_UART_FFULL 0x02 +#define MCF_UART_TxRDY 0x04 +#define MCF_UART_TxEMP 0x08 +#define MCF_UART_OE 0x10 +#define MCF_UART_PE 0x20 +#define MCF_UART_FE 0x40 +#define MCF_UART_RB 0x80 + +/* Interrupt flags. */ +#define MCF_UART_TxINT 0x01 +#define MCF_UART_RxINT 0x02 +#define MCF_UART_DBINT 0x04 +#define MCF_UART_COSINT 0x80 + +/* UMR1 flags. */ +#define MCF_UART_BC0 0x01 +#define MCF_UART_BC1 0x02 +#define MCF_UART_PT 0x04 +#define MCF_UART_PM0 0x08 +#define MCF_UART_PM1 0x10 +#define MCF_UART_ERR 0x20 +#define MCF_UART_RxIRQ 0x40 +#define MCF_UART_RxRTS 0x80 + +static void mcf_uart_update(mcf_uart_state *s) +{ + s->isr &= ~(MCF_UART_TxINT | MCF_UART_RxINT); + if (s->sr & MCF_UART_TxRDY) + s->isr |= MCF_UART_TxINT; + if ((s->sr & ((s->mr[0] & MCF_UART_RxIRQ) + ? MCF_UART_FFULL : MCF_UART_RxRDY)) != 0) + s->isr |= MCF_UART_RxINT; + + qemu_set_irq(s->irq, (s->isr & s->imr) != 0); +} + +uint64_t mcf_uart_read(void *opaque, hwaddr addr, + unsigned size) +{ + mcf_uart_state *s = (mcf_uart_state *)opaque; + switch (addr & 0x3f) { + case 0x00: + return s->mr[s->current_mr]; + case 0x04: + return s->sr; + case 0x0c: + { + uint8_t val; + int i; + + if (s->fifo_len == 0) + return 0; + + val = s->fifo[0]; + s->fifo_len--; + for (i = 0; i < s->fifo_len; i++) + s->fifo[i] = s->fifo[i + 1]; + s->sr &= ~MCF_UART_FFULL; + if (s->fifo_len == 0) + s->sr &= ~MCF_UART_RxRDY; + mcf_uart_update(s); + qemu_chr_accept_input(s->chr); + return val; + } + case 0x10: + /* TODO: Implement IPCR. */ + return 0; + case 0x14: + return s->isr; + case 0x18: + return s->bg1; + case 0x1c: + return s->bg2; + default: + return 0; + } +} + +/* Update TxRDY flag and set data if present and enabled. */ +static void mcf_uart_do_tx(mcf_uart_state *s) +{ + if (s->tx_enabled && (s->sr & MCF_UART_TxEMP) == 0) { + if (s->chr) + qemu_chr_fe_write(s->chr, (unsigned char *)&s->tb, 1); + s->sr |= MCF_UART_TxEMP; + } + if (s->tx_enabled) { + s->sr |= MCF_UART_TxRDY; + } else { + s->sr &= ~MCF_UART_TxRDY; + } +} + +static void mcf_do_command(mcf_uart_state *s, uint8_t cmd) +{ + /* Misc command. */ + switch ((cmd >> 4) & 3) { + case 0: /* No-op. */ + break; + case 1: /* Reset mode register pointer. */ + s->current_mr = 0; + break; + case 2: /* Reset receiver. */ + s->rx_enabled = 0; + s->fifo_len = 0; + s->sr &= ~(MCF_UART_RxRDY | MCF_UART_FFULL); + break; + case 3: /* Reset transmitter. */ + s->tx_enabled = 0; + s->sr |= MCF_UART_TxEMP; + s->sr &= ~MCF_UART_TxRDY; + break; + case 4: /* Reset error status. */ + break; + case 5: /* Reset break-change interrupt. */ + s->isr &= ~MCF_UART_DBINT; + break; + case 6: /* Start break. */ + case 7: /* Stop break. */ + break; + } + + /* Transmitter command. */ + switch ((cmd >> 2) & 3) { + case 0: /* No-op. */ + break; + case 1: /* Enable. */ + s->tx_enabled = 1; + mcf_uart_do_tx(s); + break; + case 2: /* Disable. */ + s->tx_enabled = 0; + mcf_uart_do_tx(s); + break; + case 3: /* Reserved. */ + fprintf(stderr, "mcf_uart: Bad TX command\n"); + break; + } + + /* Receiver command. */ + switch (cmd & 3) { + case 0: /* No-op. */ + break; + case 1: /* Enable. */ + s->rx_enabled = 1; + break; + case 2: + s->rx_enabled = 0; + break; + case 3: /* Reserved. */ + fprintf(stderr, "mcf_uart: Bad RX command\n"); + break; + } +} + +void mcf_uart_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + mcf_uart_state *s = (mcf_uart_state *)opaque; + switch (addr & 0x3f) { + case 0x00: + s->mr[s->current_mr] = val; + s->current_mr = 1; + break; + case 0x04: + /* CSR is ignored. */ + break; + case 0x08: /* Command Register. */ + mcf_do_command(s, val); + break; + case 0x0c: /* Transmit Buffer. */ + s->sr &= ~MCF_UART_TxEMP; + s->tb = val; + mcf_uart_do_tx(s); + break; + case 0x10: + /* ACR is ignored. */ + break; + case 0x14: + s->imr = val; + break; + default: + break; + } + mcf_uart_update(s); +} + +static void mcf_uart_reset(mcf_uart_state *s) +{ + s->fifo_len = 0; + s->mr[0] = 0; + s->mr[1] = 0; + s->sr = MCF_UART_TxEMP; + s->tx_enabled = 0; + s->rx_enabled = 0; + s->isr = 0; + s->imr = 0; +} + +static void mcf_uart_push_byte(mcf_uart_state *s, uint8_t data) +{ + /* Break events overwrite the last byte if the fifo is full. */ + if (s->fifo_len == 4) + s->fifo_len--; + + s->fifo[s->fifo_len] = data; + s->fifo_len++; + s->sr |= MCF_UART_RxRDY; + if (s->fifo_len == 4) + s->sr |= MCF_UART_FFULL; + + mcf_uart_update(s); +} + +static void mcf_uart_event(void *opaque, int event) +{ + mcf_uart_state *s = (mcf_uart_state *)opaque; + + switch (event) { + case CHR_EVENT_BREAK: + s->isr |= MCF_UART_DBINT; + mcf_uart_push_byte(s, 0); + break; + default: + break; + } +} + +static int mcf_uart_can_receive(void *opaque) +{ + mcf_uart_state *s = (mcf_uart_state *)opaque; + + return s->rx_enabled && (s->sr & MCF_UART_FFULL) == 0; +} + +static void mcf_uart_receive(void *opaque, const uint8_t *buf, int size) +{ + mcf_uart_state *s = (mcf_uart_state *)opaque; + + mcf_uart_push_byte(s, buf[0]); +} + +void *mcf_uart_init(qemu_irq irq, CharDriverState *chr) +{ + mcf_uart_state *s; + + s = g_malloc0(sizeof(mcf_uart_state)); + s->chr = chr; + s->irq = irq; + if (chr) { + qemu_chr_fe_claim_no_fail(chr); + qemu_chr_add_handlers(chr, mcf_uart_can_receive, mcf_uart_receive, + mcf_uart_event, s); + } + mcf_uart_reset(s); + return s; +} + +static const MemoryRegionOps mcf_uart_ops = { + .read = mcf_uart_read, + .write = mcf_uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +void mcf_uart_mm_init(MemoryRegion *sysmem, + hwaddr base, + qemu_irq irq, + CharDriverState *chr) +{ + mcf_uart_state *s; + + s = mcf_uart_init(irq, chr); + memory_region_init_io(&s->iomem, &mcf_uart_ops, s, "uart", 0x40); + memory_region_add_subregion(sysmem, base, &s->iomem); +} diff --git a/hw/char/milkymist-uart.c b/hw/char/milkymist-uart.c new file mode 100644 index 0000000000..f3bdf6991a --- /dev/null +++ b/hw/char/milkymist-uart.c @@ -0,0 +1,244 @@ +/* + * QEMU model of the Milkymist UART block. + * + * Copyright (c) 2010 Michael Walle <michael@walle.cc> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * + * Specification available at: + * http://www.milkymist.org/socdoc/uart.pdf + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "char/char.h" +#include "qemu/error-report.h" + +enum { + R_RXTX = 0, + R_DIV, + R_STAT, + R_CTRL, + R_DBG, + R_MAX +}; + +enum { + STAT_THRE = (1<<0), + STAT_RX_EVT = (1<<1), + STAT_TX_EVT = (1<<2), +}; + +enum { + CTRL_RX_IRQ_EN = (1<<0), + CTRL_TX_IRQ_EN = (1<<1), + CTRL_THRU_EN = (1<<2), +}; + +enum { + DBG_BREAK_EN = (1<<0), +}; + +struct MilkymistUartState { + SysBusDevice busdev; + MemoryRegion regs_region; + CharDriverState *chr; + qemu_irq irq; + + uint32_t regs[R_MAX]; +}; +typedef struct MilkymistUartState MilkymistUartState; + +static void uart_update_irq(MilkymistUartState *s) +{ + int rx_event = s->regs[R_STAT] & STAT_RX_EVT; + int tx_event = s->regs[R_STAT] & STAT_TX_EVT; + int rx_irq_en = s->regs[R_CTRL] & CTRL_RX_IRQ_EN; + int tx_irq_en = s->regs[R_CTRL] & CTRL_TX_IRQ_EN; + + if ((rx_irq_en && rx_event) || (tx_irq_en && tx_event)) { + trace_milkymist_uart_raise_irq(); + qemu_irq_raise(s->irq); + } else { + trace_milkymist_uart_lower_irq(); + qemu_irq_lower(s->irq); + } +} + +static uint64_t uart_read(void *opaque, hwaddr addr, + unsigned size) +{ + MilkymistUartState *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) { + case R_RXTX: + r = s->regs[addr]; + break; + case R_DIV: + case R_STAT: + case R_CTRL: + case R_DBG: + r = s->regs[addr]; + break; + + default: + error_report("milkymist_uart: read access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + trace_milkymist_uart_memory_read(addr << 2, r); + + return r; +} + +static void uart_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + MilkymistUartState *s = opaque; + unsigned char ch = value; + + trace_milkymist_uart_memory_write(addr, value); + + addr >>= 2; + switch (addr) { + case R_RXTX: + if (s->chr) { + qemu_chr_fe_write(s->chr, &ch, 1); + } + s->regs[R_STAT] |= STAT_TX_EVT; + break; + case R_DIV: + case R_CTRL: + case R_DBG: + s->regs[addr] = value; + break; + + case R_STAT: + /* write one to clear bits */ + s->regs[addr] &= ~(value & (STAT_RX_EVT | STAT_TX_EVT)); + qemu_chr_accept_input(s->chr); + break; + + default: + error_report("milkymist_uart: write access to unknown register 0x" + TARGET_FMT_plx, addr << 2); + break; + } + + uart_update_irq(s); +} + +static const MemoryRegionOps uart_mmio_ops = { + .read = uart_read, + .write = uart_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void uart_rx(void *opaque, const uint8_t *buf, int size) +{ + MilkymistUartState *s = opaque; + + assert(!(s->regs[R_STAT] & STAT_RX_EVT)); + + s->regs[R_STAT] |= STAT_RX_EVT; + s->regs[R_RXTX] = *buf; + + uart_update_irq(s); +} + +static int uart_can_rx(void *opaque) +{ + MilkymistUartState *s = opaque; + + return !(s->regs[R_STAT] & STAT_RX_EVT); +} + +static void uart_event(void *opaque, int event) +{ +} + +static void milkymist_uart_reset(DeviceState *d) +{ + MilkymistUartState *s = container_of(d, MilkymistUartState, busdev.qdev); + int i; + + for (i = 0; i < R_MAX; i++) { + s->regs[i] = 0; + } + + /* THRE is always set */ + s->regs[R_STAT] = STAT_THRE; +} + +static int milkymist_uart_init(SysBusDevice *dev) +{ + MilkymistUartState *s = FROM_SYSBUS(typeof(*s), dev); + + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->regs_region, &uart_mmio_ops, s, + "milkymist-uart", R_MAX * 4); + sysbus_init_mmio(dev, &s->regs_region); + + s->chr = qemu_char_get_next_serial(); + if (s->chr) { + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + } + + return 0; +} + +static const VMStateDescription vmstate_milkymist_uart = { + .name = "milkymist-uart", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, MilkymistUartState, R_MAX), + VMSTATE_END_OF_LIST() + } +}; + +static void milkymist_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = milkymist_uart_init; + dc->reset = milkymist_uart_reset; + dc->vmsd = &vmstate_milkymist_uart; +} + +static const TypeInfo milkymist_uart_info = { + .name = "milkymist-uart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MilkymistUartState), + .class_init = milkymist_uart_class_init, +}; + +static void milkymist_uart_register_types(void) +{ + type_register_static(&milkymist_uart_info); +} + +type_init(milkymist_uart_register_types) diff --git a/hw/char/omap_uart.c b/hw/char/omap_uart.c new file mode 100644 index 0000000000..26c14266fc --- /dev/null +++ b/hw/char/omap_uart.c @@ -0,0 +1,187 @@ +/* + * TI OMAP processors UART emulation. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org> + * Copyright (C) 2007-2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "char/char.h" +#include "hw/hw.h" +#include "hw/arm/omap.h" +#include "hw/char/serial.h" +#include "exec/address-spaces.h" + +/* UARTs */ +struct omap_uart_s { + MemoryRegion iomem; + hwaddr base; + SerialState *serial; /* TODO */ + struct omap_target_agent_s *ta; + omap_clk fclk; + qemu_irq irq; + + uint8_t eblr; + uint8_t syscontrol; + uint8_t wkup; + uint8_t cfps; + uint8_t mdr[2]; + uint8_t scr; + uint8_t clksel; +}; + +void omap_uart_reset(struct omap_uart_s *s) +{ + s->eblr = 0x00; + s->syscontrol = 0; + s->wkup = 0x3f; + s->cfps = 0x69; + s->clksel = 0; +} + +struct omap_uart_s *omap_uart_init(hwaddr base, + qemu_irq irq, omap_clk fclk, omap_clk iclk, + qemu_irq txdma, qemu_irq rxdma, + const char *label, CharDriverState *chr) +{ + struct omap_uart_s *s = (struct omap_uart_s *) + g_malloc0(sizeof(struct omap_uart_s)); + + s->base = base; + s->fclk = fclk; + s->irq = irq; + s->serial = serial_mm_init(get_system_memory(), base, 2, irq, + omap_clk_getrate(fclk)/16, + chr ?: qemu_chr_new(label, "null", NULL), + DEVICE_NATIVE_ENDIAN); + return s; +} + +static uint64_t omap_uart_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_uart_s *s = (struct omap_uart_s *) opaque; + + if (size == 4) { + return omap_badwidth_read8(opaque, addr); + } + + switch (addr) { + case 0x20: /* MDR1 */ + return s->mdr[0]; + case 0x24: /* MDR2 */ + return s->mdr[1]; + case 0x40: /* SCR */ + return s->scr; + case 0x44: /* SSR */ + return 0x0; + case 0x48: /* EBLR (OMAP2) */ + return s->eblr; + case 0x4C: /* OSC_12M_SEL (OMAP1) */ + return s->clksel; + case 0x50: /* MVR */ + return 0x30; + case 0x54: /* SYSC (OMAP2) */ + return s->syscontrol; + case 0x58: /* SYSS (OMAP2) */ + return 1; + case 0x5c: /* WER (OMAP2) */ + return s->wkup; + case 0x60: /* CFPS (OMAP2) */ + return s->cfps; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_uart_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_uart_s *s = (struct omap_uart_s *) opaque; + + if (size == 4) { + return omap_badwidth_write8(opaque, addr, value); + } + + switch (addr) { + case 0x20: /* MDR1 */ + s->mdr[0] = value & 0x7f; + break; + case 0x24: /* MDR2 */ + s->mdr[1] = value & 0xff; + break; + case 0x40: /* SCR */ + s->scr = value & 0xff; + break; + case 0x48: /* EBLR (OMAP2) */ + s->eblr = value & 0xff; + break; + case 0x4C: /* OSC_12M_SEL (OMAP1) */ + s->clksel = value & 1; + break; + case 0x44: /* SSR */ + case 0x50: /* MVR */ + case 0x58: /* SYSS (OMAP2) */ + OMAP_RO_REG(addr); + break; + case 0x54: /* SYSC (OMAP2) */ + s->syscontrol = value & 0x1d; + if (value & 2) + omap_uart_reset(s); + break; + case 0x5c: /* WER (OMAP2) */ + s->wkup = value & 0x7f; + break; + case 0x60: /* CFPS (OMAP2) */ + s->cfps = value & 0xff; + break; + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_uart_ops = { + .read = omap_uart_read, + .write = omap_uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +struct omap_uart_s *omap2_uart_init(MemoryRegion *sysmem, + struct omap_target_agent_s *ta, + qemu_irq irq, omap_clk fclk, omap_clk iclk, + qemu_irq txdma, qemu_irq rxdma, + const char *label, CharDriverState *chr) +{ + hwaddr base = omap_l4_attach(ta, 0, NULL); + struct omap_uart_s *s = omap_uart_init(base, irq, + fclk, iclk, txdma, rxdma, label, chr); + + memory_region_init_io(&s->iomem, &omap_uart_ops, s, "omap.uart", 0x100); + + s->ta = ta; + + memory_region_add_subregion(sysmem, base + 0x20, &s->iomem); + + return s; +} + +void omap_uart_attach(struct omap_uart_s *s, CharDriverState *chr) +{ + /* TODO: Should reuse or destroy current s->serial */ + s->serial = serial_mm_init(get_system_memory(), s->base, 2, s->irq, + omap_clk_getrate(s->fclk) / 16, + chr ?: qemu_chr_new("null", "null", NULL), + DEVICE_NATIVE_ENDIAN); +} diff --git a/hw/char/sclpconsole.c b/hw/char/sclpconsole.c new file mode 100644 index 0000000000..42ed54c9ab --- /dev/null +++ b/hw/char/sclpconsole.c @@ -0,0 +1,305 @@ +/* + * SCLP event type + * Ascii Console Data (VT220 Console) + * + * Copyright IBM, Corp. 2012 + * + * Authors: + * Heinz Graalfs <graalfs@de.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or (at your + * option) any later version. See the COPYING file in the top-level directory. + * + */ + +#include <hw/qdev.h> +#include "qemu/thread.h" +#include "qemu/error-report.h" + +#include "hw/s390x/sclp.h" +#include "hw/s390x/event-facility.h" +#include "char/char.h" + +typedef struct ASCIIConsoleData { + EventBufferHeader ebh; + char data[0]; +} QEMU_PACKED ASCIIConsoleData; + +/* max size for ASCII data in 4K SCCB page */ +#define SIZE_BUFFER_VT220 4080 + +typedef struct SCLPConsole { + SCLPEvent event; + CharDriverState *chr; + /* io vector */ + uint8_t *iov; /* iov buffer pointer */ + uint8_t *iov_sclp; /* pointer to SCLP read offset */ + uint8_t *iov_bs; /* pointer byte stream read offset */ + uint32_t iov_data_len; /* length of byte stream in buffer */ + uint32_t iov_sclp_rest; /* length of byte stream not read via SCLP */ + qemu_irq irq_read_vt220; +} SCLPConsole; + +/* character layer call-back functions */ + +/* Return number of bytes that fit into iov buffer */ +static int chr_can_read(void *opaque) +{ + SCLPConsole *scon = opaque; + + return scon->iov ? SIZE_BUFFER_VT220 - scon->iov_data_len : 0; +} + +/* Receive n bytes from character layer, save in iov buffer, + * and set event pending */ +static void receive_from_chr_layer(SCLPConsole *scon, const uint8_t *buf, + int size) +{ + assert(scon->iov); + + /* read data must fit into current buffer */ + assert(size <= SIZE_BUFFER_VT220 - scon->iov_data_len); + + /* put byte-stream from character layer into buffer */ + memcpy(scon->iov_bs, buf, size); + scon->iov_data_len += size; + scon->iov_sclp_rest += size; + scon->iov_bs += size; + scon->event.event_pending = true; +} + +/* Send data from a char device over to the guest */ +static void chr_read(void *opaque, const uint8_t *buf, int size) +{ + SCLPConsole *scon = opaque; + + assert(scon); + + receive_from_chr_layer(scon, buf, size); + /* trigger SCLP read operation */ + qemu_irq_raise(scon->irq_read_vt220); +} + +static void chr_event(void *opaque, int event) +{ + SCLPConsole *scon = opaque; + + switch (event) { + case CHR_EVENT_OPENED: + if (!scon->iov) { + scon->iov = g_malloc0(SIZE_BUFFER_VT220); + scon->iov_sclp = scon->iov; + scon->iov_bs = scon->iov; + scon->iov_data_len = 0; + scon->iov_sclp_rest = 0; + } + break; + case CHR_EVENT_CLOSED: + if (scon->iov) { + g_free(scon->iov); + scon->iov = NULL; + } + break; + } +} + +/* functions to be called by event facility */ + +static int event_type(void) +{ + return SCLP_EVENT_ASCII_CONSOLE_DATA; +} + +static unsigned int send_mask(void) +{ + return SCLP_EVENT_MASK_MSG_ASCII; +} + +static unsigned int receive_mask(void) +{ + return SCLP_EVENT_MASK_MSG_ASCII; +} + +/* triggered by SCLP's read_event_data - + * copy console data byte-stream into provided (SCLP) buffer + */ +static void get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size, + int avail) +{ + SCLPConsole *cons = DO_UPCAST(SCLPConsole, event, event); + + /* first byte is hex 0 saying an ascii string follows */ + *buf++ = '\0'; + avail--; + /* if all data fit into provided SCLP buffer */ + if (avail >= cons->iov_sclp_rest) { + /* copy character byte-stream to SCLP buffer */ + memcpy(buf, cons->iov_sclp, cons->iov_sclp_rest); + *size = cons->iov_sclp_rest + 1; + cons->iov_sclp = cons->iov; + cons->iov_bs = cons->iov; + cons->iov_data_len = 0; + cons->iov_sclp_rest = 0; + event->event_pending = false; + /* data provided and no more data pending */ + } else { + /* if provided buffer is too small, just copy part */ + memcpy(buf, cons->iov_sclp, avail); + *size = avail + 1; + cons->iov_sclp_rest -= avail; + cons->iov_sclp += avail; + /* more data pending */ + } +} + +static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr, + int *slen) +{ + int avail; + size_t src_len; + uint8_t *to; + ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr; + + if (!event->event_pending) { + /* no data pending */ + return 0; + } + + to = (uint8_t *)&acd->data; + avail = *slen - sizeof(ASCIIConsoleData); + get_console_data(event, to, &src_len, avail); + + acd->ebh.length = cpu_to_be16(sizeof(ASCIIConsoleData) + src_len); + acd->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA; + acd->ebh.flags |= SCLP_EVENT_BUFFER_ACCEPTED; + *slen = avail - src_len; + + return 1; +} + +/* triggered by SCLP's write_event_data + * - write console data to character layer + * returns < 0 if an error occurred + */ +static ssize_t write_console_data(SCLPEvent *event, const uint8_t *buf, + size_t len) +{ + ssize_t ret = 0; + const uint8_t *iov_offset; + SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event); + + if (!scon->chr) { + /* If there's no backend, we can just say we consumed all data. */ + return len; + } + + iov_offset = buf; + while (len > 0) { + ret = qemu_chr_fe_write(scon->chr, buf, len); + if (ret == 0) { + /* a pty doesn't seem to be connected - no error */ + len = 0; + } else if (ret == -EAGAIN || (ret > 0 && ret < len)) { + len -= ret; + iov_offset += ret; + } else { + len = 0; + } + } + + return ret; +} + +static int write_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr) +{ + int rc; + int length; + ssize_t written; + ASCIIConsoleData *acd = (ASCIIConsoleData *) evt_buf_hdr; + + length = be16_to_cpu(evt_buf_hdr->length) - sizeof(EventBufferHeader); + written = write_console_data(event, (uint8_t *)acd->data, length); + + rc = SCLP_RC_NORMAL_COMPLETION; + /* set event buffer accepted flag */ + evt_buf_hdr->flags |= SCLP_EVENT_BUFFER_ACCEPTED; + + /* written will be zero if a pty is not connected - don't treat as error */ + if (written < 0) { + /* event buffer not accepted due to error in character layer */ + evt_buf_hdr->flags &= ~(SCLP_EVENT_BUFFER_ACCEPTED); + rc = SCLP_RC_CONTAINED_EQUIPMENT_CHECK; + } + + return rc; +} + +static void trigger_ascii_console_data(void *opaque, int n, int level) +{ + sclp_service_interrupt(0); +} + +/* qemu object creation and initialization functions */ + +/* tell character layer our call-back functions */ +static int console_init(SCLPEvent *event) +{ + static bool console_available; + + SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event); + + if (console_available) { + error_report("Multiple VT220 operator consoles are not supported"); + return -1; + } + console_available = true; + event->event_type = SCLP_EVENT_ASCII_CONSOLE_DATA; + if (scon->chr) { + qemu_chr_add_handlers(scon->chr, chr_can_read, + chr_read, chr_event, scon); + } + scon->irq_read_vt220 = *qemu_allocate_irqs(trigger_ascii_console_data, + NULL, 1); + + return 0; +} + +static int console_exit(SCLPEvent *event) +{ + return 0; +} + +static Property console_properties[] = { + DEFINE_PROP_CHR("chardev", SCLPConsole, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void console_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SCLPEventClass *ec = SCLP_EVENT_CLASS(klass); + + dc->props = console_properties; + ec->init = console_init; + ec->exit = console_exit; + ec->get_send_mask = send_mask; + ec->get_receive_mask = receive_mask; + ec->event_type = event_type; + ec->read_event_data = read_event_data; + ec->write_event_data = write_event_data; +} + +static const TypeInfo sclp_console_info = { + .name = "sclpconsole", + .parent = TYPE_SCLP_EVENT, + .instance_size = sizeof(SCLPConsole), + .class_init = console_class_init, + .class_size = sizeof(SCLPEventClass), +}; + +static void register_types(void) +{ + type_register_static(&sclp_console_info); +} + +type_init(register_types) diff --git a/hw/char/sh_serial.c b/hw/char/sh_serial.c new file mode 100644 index 0000000000..450c7d8551 --- /dev/null +++ b/hw/char/sh_serial.c @@ -0,0 +1,410 @@ +/* + * QEMU SCI/SCIF serial port emulation + * + * Copyright (c) 2007 Magnus Damm + * + * Based on serial.c - QEMU 16450 UART emulation + * Copyright (c) 2003-2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "hw/sh4/sh.h" +#include "char/char.h" +#include "exec/address-spaces.h" + +//#define DEBUG_SERIAL + +#define SH_SERIAL_FLAG_TEND (1 << 0) +#define SH_SERIAL_FLAG_TDE (1 << 1) +#define SH_SERIAL_FLAG_RDF (1 << 2) +#define SH_SERIAL_FLAG_BRK (1 << 3) +#define SH_SERIAL_FLAG_DR (1 << 4) + +#define SH_RX_FIFO_LENGTH (16) + +typedef struct { + MemoryRegion iomem; + MemoryRegion iomem_p4; + MemoryRegion iomem_a7; + uint8_t smr; + uint8_t brr; + uint8_t scr; + uint8_t dr; /* ftdr / tdr */ + uint8_t sr; /* fsr / ssr */ + uint16_t fcr; + uint8_t sptr; + + uint8_t rx_fifo[SH_RX_FIFO_LENGTH]; /* frdr / rdr */ + uint8_t rx_cnt; + uint8_t rx_tail; + uint8_t rx_head; + + int freq; + int feat; + int flags; + int rtrg; + + CharDriverState *chr; + + qemu_irq eri; + qemu_irq rxi; + qemu_irq txi; + qemu_irq tei; + qemu_irq bri; +} sh_serial_state; + +static void sh_serial_clear_fifo(sh_serial_state * s) +{ + memset(s->rx_fifo, 0, SH_RX_FIFO_LENGTH); + s->rx_cnt = 0; + s->rx_head = 0; + s->rx_tail = 0; +} + +static void sh_serial_write(void *opaque, hwaddr offs, + uint64_t val, unsigned size) +{ + sh_serial_state *s = opaque; + unsigned char ch; + +#ifdef DEBUG_SERIAL + printf("sh_serial: write offs=0x%02x val=0x%02x\n", + offs, val); +#endif + switch(offs) { + case 0x00: /* SMR */ + s->smr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0x7b : 0xff); + return; + case 0x04: /* BRR */ + s->brr = val; + return; + case 0x08: /* SCR */ + /* TODO : For SH7751, SCIF mask should be 0xfb. */ + s->scr = val & ((s->feat & SH_SERIAL_FEAT_SCIF) ? 0xfa : 0xff); + if (!(val & (1 << 5))) + s->flags |= SH_SERIAL_FLAG_TEND; + if ((s->feat & SH_SERIAL_FEAT_SCIF) && s->txi) { + qemu_set_irq(s->txi, val & (1 << 7)); + } + if (!(val & (1 << 6))) { + qemu_set_irq(s->rxi, 0); + } + return; + case 0x0c: /* FTDR / TDR */ + if (s->chr) { + ch = val; + qemu_chr_fe_write(s->chr, &ch, 1); + } + s->dr = val; + s->flags &= ~SH_SERIAL_FLAG_TDE; + return; +#if 0 + case 0x14: /* FRDR / RDR */ + ret = 0; + break; +#endif + } + if (s->feat & SH_SERIAL_FEAT_SCIF) { + switch(offs) { + case 0x10: /* FSR */ + if (!(val & (1 << 6))) + s->flags &= ~SH_SERIAL_FLAG_TEND; + if (!(val & (1 << 5))) + s->flags &= ~SH_SERIAL_FLAG_TDE; + if (!(val & (1 << 4))) + s->flags &= ~SH_SERIAL_FLAG_BRK; + if (!(val & (1 << 1))) + s->flags &= ~SH_SERIAL_FLAG_RDF; + if (!(val & (1 << 0))) + s->flags &= ~SH_SERIAL_FLAG_DR; + + if (!(val & (1 << 1)) || !(val & (1 << 0))) { + if (s->rxi) { + qemu_set_irq(s->rxi, 0); + } + } + return; + case 0x18: /* FCR */ + s->fcr = val; + switch ((val >> 6) & 3) { + case 0: + s->rtrg = 1; + break; + case 1: + s->rtrg = 4; + break; + case 2: + s->rtrg = 8; + break; + case 3: + s->rtrg = 14; + break; + } + if (val & (1 << 1)) { + sh_serial_clear_fifo(s); + s->sr &= ~(1 << 1); + } + + return; + case 0x20: /* SPTR */ + s->sptr = val & 0xf3; + return; + case 0x24: /* LSR */ + return; + } + } + else { + switch(offs) { +#if 0 + case 0x0c: + ret = s->dr; + break; + case 0x10: + ret = 0; + break; +#endif + case 0x1c: + s->sptr = val & 0x8f; + return; + } + } + + fprintf(stderr, "sh_serial: unsupported write to 0x%02" + HWADDR_PRIx "\n", offs); + abort(); +} + +static uint64_t sh_serial_read(void *opaque, hwaddr offs, + unsigned size) +{ + sh_serial_state *s = opaque; + uint32_t ret = ~0; + +#if 0 + switch(offs) { + case 0x00: + ret = s->smr; + break; + case 0x04: + ret = s->brr; + break; + case 0x08: + ret = s->scr; + break; + case 0x14: + ret = 0; + break; + } +#endif + if (s->feat & SH_SERIAL_FEAT_SCIF) { + switch(offs) { + case 0x00: /* SMR */ + ret = s->smr; + break; + case 0x08: /* SCR */ + ret = s->scr; + break; + case 0x10: /* FSR */ + ret = 0; + if (s->flags & SH_SERIAL_FLAG_TEND) + ret |= (1 << 6); + if (s->flags & SH_SERIAL_FLAG_TDE) + ret |= (1 << 5); + if (s->flags & SH_SERIAL_FLAG_BRK) + ret |= (1 << 4); + if (s->flags & SH_SERIAL_FLAG_RDF) + ret |= (1 << 1); + if (s->flags & SH_SERIAL_FLAG_DR) + ret |= (1 << 0); + + if (s->scr & (1 << 5)) + s->flags |= SH_SERIAL_FLAG_TDE | SH_SERIAL_FLAG_TEND; + + break; + case 0x14: + if (s->rx_cnt > 0) { + ret = s->rx_fifo[s->rx_tail++]; + s->rx_cnt--; + if (s->rx_tail == SH_RX_FIFO_LENGTH) + s->rx_tail = 0; + if (s->rx_cnt < s->rtrg) + s->flags &= ~SH_SERIAL_FLAG_RDF; + } + break; +#if 0 + case 0x18: + ret = s->fcr; + break; +#endif + case 0x1c: + ret = s->rx_cnt; + break; + case 0x20: + ret = s->sptr; + break; + case 0x24: + ret = 0; + break; + } + } + else { + switch(offs) { +#if 0 + case 0x0c: + ret = s->dr; + break; + case 0x10: + ret = 0; + break; + case 0x14: + ret = s->rx_fifo[0]; + break; +#endif + case 0x1c: + ret = s->sptr; + break; + } + } +#ifdef DEBUG_SERIAL + printf("sh_serial: read offs=0x%02x val=0x%x\n", + offs, ret); +#endif + + if (ret & ~((1 << 16) - 1)) { + fprintf(stderr, "sh_serial: unsupported read from 0x%02" + HWADDR_PRIx "\n", offs); + abort(); + } + + return ret; +} + +static int sh_serial_can_receive(sh_serial_state *s) +{ + return s->scr & (1 << 4); +} + +static void sh_serial_receive_break(sh_serial_state *s) +{ + if (s->feat & SH_SERIAL_FEAT_SCIF) + s->sr |= (1 << 4); +} + +static int sh_serial_can_receive1(void *opaque) +{ + sh_serial_state *s = opaque; + return sh_serial_can_receive(s); +} + +static void sh_serial_receive1(void *opaque, const uint8_t *buf, int size) +{ + sh_serial_state *s = opaque; + + if (s->feat & SH_SERIAL_FEAT_SCIF) { + int i; + for (i = 0; i < size; i++) { + if (s->rx_cnt < SH_RX_FIFO_LENGTH) { + s->rx_fifo[s->rx_head++] = buf[i]; + if (s->rx_head == SH_RX_FIFO_LENGTH) { + s->rx_head = 0; + } + s->rx_cnt++; + if (s->rx_cnt >= s->rtrg) { + s->flags |= SH_SERIAL_FLAG_RDF; + if (s->scr & (1 << 6) && s->rxi) { + qemu_set_irq(s->rxi, 1); + } + } + } + } + } else { + s->rx_fifo[0] = buf[0]; + } +} + +static void sh_serial_event(void *opaque, int event) +{ + sh_serial_state *s = opaque; + if (event == CHR_EVENT_BREAK) + sh_serial_receive_break(s); +} + +static const MemoryRegionOps sh_serial_ops = { + .read = sh_serial_read, + .write = sh_serial_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +void sh_serial_init(MemoryRegion *sysmem, + hwaddr base, int feat, + uint32_t freq, CharDriverState *chr, + qemu_irq eri_source, + qemu_irq rxi_source, + qemu_irq txi_source, + qemu_irq tei_source, + qemu_irq bri_source) +{ + sh_serial_state *s; + + s = g_malloc0(sizeof(sh_serial_state)); + + s->feat = feat; + s->flags = SH_SERIAL_FLAG_TEND | SH_SERIAL_FLAG_TDE; + s->rtrg = 1; + + s->smr = 0; + s->brr = 0xff; + s->scr = 1 << 5; /* pretend that TX is enabled so early printk works */ + s->sptr = 0; + + if (feat & SH_SERIAL_FEAT_SCIF) { + s->fcr = 0; + } + else { + s->dr = 0xff; + } + + sh_serial_clear_fifo(s); + + memory_region_init_io(&s->iomem, &sh_serial_ops, s, + "serial", 0x100000000ULL); + + memory_region_init_alias(&s->iomem_p4, "serial-p4", &s->iomem, + 0, 0x28); + memory_region_add_subregion(sysmem, P4ADDR(base), &s->iomem_p4); + + memory_region_init_alias(&s->iomem_a7, "serial-a7", &s->iomem, + 0, 0x28); + memory_region_add_subregion(sysmem, A7ADDR(base), &s->iomem_a7); + + s->chr = chr; + + if (chr) { + qemu_chr_fe_claim_no_fail(chr); + qemu_chr_add_handlers(chr, sh_serial_can_receive1, sh_serial_receive1, + sh_serial_event, s); + } + + s->eri = eri_source; + s->rxi = rxi_source; + s->txi = txi_source; + s->tei = tei_source; + s->bri = bri_source; +} diff --git a/hw/char/spapr_vty.c b/hw/char/spapr_vty.c new file mode 100644 index 0000000000..9df018a230 --- /dev/null +++ b/hw/char/spapr_vty.c @@ -0,0 +1,221 @@ +#include "hw/qdev.h" +#include "char/char.h" +#include "hw/ppc/spapr.h" +#include "hw/ppc/spapr_vio.h" + +#define VTERM_BUFSIZE 16 + +typedef struct VIOsPAPRVTYDevice { + VIOsPAPRDevice sdev; + CharDriverState *chardev; + uint32_t in, out; + uint8_t buf[VTERM_BUFSIZE]; +} VIOsPAPRVTYDevice; + +static int vty_can_receive(void *opaque) +{ + VIOsPAPRVTYDevice *dev = (VIOsPAPRVTYDevice *)opaque; + + return (dev->in - dev->out) < VTERM_BUFSIZE; +} + +static void vty_receive(void *opaque, const uint8_t *buf, int size) +{ + VIOsPAPRVTYDevice *dev = (VIOsPAPRVTYDevice *)opaque; + int i; + + if ((dev->in == dev->out) && size) { + /* toggle line to simulate edge interrupt */ + qemu_irq_pulse(spapr_vio_qirq(&dev->sdev)); + } + for (i = 0; i < size; i++) { + assert((dev->in - dev->out) < VTERM_BUFSIZE); + dev->buf[dev->in++ % VTERM_BUFSIZE] = buf[i]; + } +} + +static int vty_getchars(VIOsPAPRDevice *sdev, uint8_t *buf, int max) +{ + VIOsPAPRVTYDevice *dev = (VIOsPAPRVTYDevice *)sdev; + int n = 0; + + while ((n < max) && (dev->out != dev->in)) { + buf[n++] = dev->buf[dev->out++ % VTERM_BUFSIZE]; + } + + return n; +} + +void vty_putchars(VIOsPAPRDevice *sdev, uint8_t *buf, int len) +{ + VIOsPAPRVTYDevice *dev = (VIOsPAPRVTYDevice *)sdev; + + /* FIXME: should check the qemu_chr_fe_write() return value */ + qemu_chr_fe_write(dev->chardev, buf, len); +} + +static int spapr_vty_init(VIOsPAPRDevice *sdev) +{ + VIOsPAPRVTYDevice *dev = (VIOsPAPRVTYDevice *)sdev; + + if (!dev->chardev) { + fprintf(stderr, "spapr-vty: Can't create vty without a chardev!\n"); + exit(1); + } + + qemu_chr_add_handlers(dev->chardev, vty_can_receive, + vty_receive, NULL, dev); + + return 0; +} + +/* Forward declaration */ +static target_ulong h_put_term_char(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong reg = args[0]; + target_ulong len = args[1]; + target_ulong char0_7 = args[2]; + target_ulong char8_15 = args[3]; + VIOsPAPRDevice *sdev; + uint8_t buf[16]; + + sdev = vty_lookup(spapr, reg); + if (!sdev) { + return H_PARAMETER; + } + + if (len > 16) { + return H_PARAMETER; + } + + *((uint64_t *)buf) = cpu_to_be64(char0_7); + *((uint64_t *)buf + 1) = cpu_to_be64(char8_15); + + vty_putchars(sdev, buf, len); + + return H_SUCCESS; +} + +static target_ulong h_get_term_char(PowerPCCPU *cpu, sPAPREnvironment *spapr, + target_ulong opcode, target_ulong *args) +{ + target_ulong reg = args[0]; + target_ulong *len = args + 0; + target_ulong *char0_7 = args + 1; + target_ulong *char8_15 = args + 2; + VIOsPAPRDevice *sdev; + uint8_t buf[16]; + + sdev = vty_lookup(spapr, reg); + if (!sdev) { + return H_PARAMETER; + } + + *len = vty_getchars(sdev, buf, sizeof(buf)); + if (*len < 16) { + memset(buf + *len, 0, 16 - *len); + } + + *char0_7 = be64_to_cpu(*((uint64_t *)buf)); + *char8_15 = be64_to_cpu(*((uint64_t *)buf + 1)); + + return H_SUCCESS; +} + +void spapr_vty_create(VIOsPAPRBus *bus, CharDriverState *chardev) +{ + DeviceState *dev; + + dev = qdev_create(&bus->bus, "spapr-vty"); + qdev_prop_set_chr(dev, "chardev", chardev); + qdev_init_nofail(dev); +} + +static Property spapr_vty_properties[] = { + DEFINE_SPAPR_PROPERTIES(VIOsPAPRVTYDevice, sdev), + DEFINE_PROP_CHR("chardev", VIOsPAPRVTYDevice, chardev), + DEFINE_PROP_END_OF_LIST(), +}; + +static void spapr_vty_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VIOsPAPRDeviceClass *k = VIO_SPAPR_DEVICE_CLASS(klass); + + k->init = spapr_vty_init; + k->dt_name = "vty"; + k->dt_type = "serial"; + k->dt_compatible = "hvterm1"; + dc->props = spapr_vty_properties; +} + +static const TypeInfo spapr_vty_info = { + .name = "spapr-vty", + .parent = TYPE_VIO_SPAPR_DEVICE, + .instance_size = sizeof(VIOsPAPRVTYDevice), + .class_init = spapr_vty_class_init, +}; + +VIOsPAPRDevice *spapr_vty_get_default(VIOsPAPRBus *bus) +{ + VIOsPAPRDevice *sdev, *selected; + BusChild *kid; + + /* + * To avoid the console bouncing around we want one VTY to be + * the "default". We haven't really got anything to go on, so + * arbitrarily choose the one with the lowest reg value. + */ + + selected = NULL; + QTAILQ_FOREACH(kid, &bus->bus.children, sibling) { + DeviceState *iter = kid->child; + + /* Only look at VTY devices */ + if (!object_dynamic_cast(OBJECT(iter), "spapr-vty")) { + continue; + } + + sdev = DO_UPCAST(VIOsPAPRDevice, qdev, iter); + + /* First VTY we've found, so it is selected for now */ + if (!selected) { + selected = sdev; + continue; + } + + /* Choose VTY with lowest reg value */ + if (sdev->reg < selected->reg) { + selected = sdev; + } + } + + return selected; +} + +VIOsPAPRDevice *vty_lookup(sPAPREnvironment *spapr, target_ulong reg) +{ + VIOsPAPRDevice *sdev; + + sdev = spapr_vio_find_by_reg(spapr->vio_bus, reg); + if (!sdev && reg == 0) { + /* Hack for kernel early debug, which always specifies reg==0. + * We search all VIO devices, and grab the vty with the lowest + * reg. This attempts to mimic existing PowerVM behaviour + * (early debug does work there, despite having no vty with + * reg==0. */ + return spapr_vty_get_default(spapr->vio_bus); + } + + return sdev; +} + +static void spapr_vty_register_types(void) +{ + spapr_register_hypercall(H_PUT_TERM_CHAR, h_put_term_char); + spapr_register_hypercall(H_GET_TERM_CHAR, h_get_term_char); + type_register_static(&spapr_vty_info); +} + +type_init(spapr_vty_register_types) |