From patchwork Thu Aug 23 13:59:20 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Alberto Garcia X-Patchwork-Id: 179658 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 949EA2C00AF for ; Fri, 24 Aug 2012 00:00:41 +1000 (EST) Received: from localhost ([::1]:53107 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T4Xxf-0001qU-Lb for incoming@patchwork.ozlabs.org; Thu, 23 Aug 2012 10:00:39 -0400 Received: from eggs.gnu.org ([208.118.235.92]:43253) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T4Xww-0000Nl-0D for qemu-devel@nongnu.org; Thu, 23 Aug 2012 10:00:03 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1T4Xwp-00069z-Gv for qemu-devel@nongnu.org; Thu, 23 Aug 2012 09:59:53 -0400 Received: from smtp4.mundo-r.com ([212.51.32.151]:65215) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1T4Xwo-00068J-RQ for qemu-devel@nongnu.org; Thu, 23 Aug 2012 09:59:47 -0400 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: ArsCAA03NlBbdWObgWdsb2JhbABFhgG0UCIBARYmJ4IhAQUjBAsBRhAJHAIYDgICPBsZiBGmQ5MjgSGJZ4V/gRIDiEmNC5J6 X-IronPort-AV: E=Sophos;i="4.77,815,1336341600"; d="scan'208";a="943794656" Received: from fanzine.igalia.com ([91.117.99.155]) by smtp4.mundo-r.com with ESMTP; 23 Aug 2012 15:59:45 +0200 Received: from dsl-hkibrasgw4-fe53df00-164.dhcp.inet.fi ([80.223.83.164] helo=hermes.local) by fanzine.igalia.com with esmtpsa (Cipher TLS1.0:DHE_RSA_AES_128_CBC_SHA1:16) (Exim) id 1T4Xwn-00042Y-E6; Thu, 23 Aug 2012 15:59:45 +0200 Received: from berto by hermes.local with local (Exim 4.80) (envelope-from ) id 1T4XwZ-0006ne-DS; Thu, 23 Aug 2012 16:59:31 +0300 From: Alberto Garcia To: qemu-devel@nongnu.org Date: Thu, 23 Aug 2012 16:59:20 +0300 Message-Id: <20d5cefbef9d277d33c68cf8fd46417154f215bb.1345730024.git.agarcia@igalia.com> X-Mailer: git-send-email 1.7.10.4 In-Reply-To: References: In-Reply-To: References: MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 212.51.32.151 Cc: Anthony Liguori , Alberto Garcia , Paul Brook Subject: [Qemu-devel] =?utf-8?q?=5BPATCH_2/2=5D_Add_IP-Octal_232_IndustryP?= =?utf-8?q?ack_emulation?= X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org The IP-Octal 232 is an IndustryPack module that implements eight RS-232 serial ports, each one of which can be redirected to a character device in the host. Signed-off-by: Alberto Garcia --- hw/Makefile.objs | 2 +- hw/ipoctal.c | 662 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 ficheiros modificados, 663 adições(+), 1 eliminado(-) create mode 100644 hw/ipoctal.c diff --git a/hw/Makefile.objs b/hw/Makefile.objs index f4a3a9b..dfb3042 100644 --- a/hw/Makefile.objs +++ b/hw/Makefile.objs @@ -100,7 +100,7 @@ hw-obj-$(CONFIG_PCI) += wdt_i6300esb.o hw-obj-$(CONFIG_PCI) += pcie.o pcie_aer.o pcie_port.o # IndustryPack -hw-obj-$(CONFIG_IPACK) += tpci200.o ipack.o +hw-obj-$(CONFIG_IPACK) += tpci200.o ipoctal.o ipack.o # PCI network cards hw-obj-$(CONFIG_NE2000_PCI) += ne2000.o diff --git a/hw/ipoctal.c b/hw/ipoctal.c new file mode 100644 index 0000000..e655bf2 --- /dev/null +++ b/hw/ipoctal.c @@ -0,0 +1,662 @@ +/* + * QEMU IP-Octal 232 IndustryPack emulation + * + * Copyright (C) 2012 Igalia, S.L. + * Author: Alberto Garcia + * + * This code is licensed under the GNU GPL v2 or (at your option) any + * later version. + */ + +#include "ipack.h" +#include "qemu-timer.h" +#include + +/* #define DEBUG_IPOCTAL */ + +#ifdef DEBUG_IPOCTAL +#define DPRINTF2(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF2(fmt, ...) do { } while (0) +#endif + +#define DPRINTF(fmt, ...) DPRINTF2("IP-Octal: " fmt, ## __VA_ARGS__) + +#define TIMER_INTERVAL (qemu_get_clock_ms(vm_clock) + 1) + +/* The IP-Octal has 8 channels (a-h) + divided into 4 blocks (A-D) */ +#define N_CHANNELS 8 +#define N_BLOCKS 4 + +#define REG_MRa 0x01 +#define REG_MRb 0x11 +#define REG_SRa 0x03 +#define REG_SRb 0x13 +#define REG_CSRa 0x03 +#define REG_CSRb 0x13 +#define REG_CRa 0x05 +#define REG_CRb 0x15 +#define REG_RHRa 0x07 +#define REG_RHRb 0x17 +#define REG_THRa 0x07 +#define REG_THRb 0x17 +#define REG_ACR 0x09 +#define REG_ISR 0x0B +#define REG_IMR 0x0B +#define REG_OPCR 0x1B + +#define CR_ENABLE_RX (1 << 0) +#define CR_DISABLE_RX (1 << 1) +#define CR_ENABLE_TX (1 << 2) +#define CR_DISABLE_TX (1 << 3) +#define CR_CMD(cr) ((cr) >> 4) +#define CR_NO_OP 0 +#define CR_RESET_MR 1 +#define CR_RESET_RX 2 +#define CR_RESET_TX 3 +#define CR_RESET_ERR 4 +#define CR_RESET_BRKINT 5 +#define CR_START_BRK 6 +#define CR_STOP_BRK 7 +#define CR_ASSERT_RTSN 8 +#define CR_NEGATE_RTSN 9 +#define CR_TIMEOUT_ON 10 +#define CR_TIMEOUT_OFF 12 + +#define SR_RXRDY (1 << 0) +#define SR_FFULL (1 << 1) +#define SR_TXRDY (1 << 2) +#define SR_TXEMT (1 << 3) +#define SR_OVERRUN (1 << 4) +#define SR_PARITY (1 << 5) +#define SR_FRAMING (1 << 6) +#define SR_BREAK (1 << 7) + +#define ISR_TXRDYA (1 << 0) +#define ISR_RXRDYA (1 << 1) +#define ISR_BREAKA (1 << 2) +#define ISR_CNTRDY (1 << 3) +#define ISR_TXRDYB (1 << 4) +#define ISR_RXRDYB (1 << 5) +#define ISR_BREAKB (1 << 6) +#define ISR_MPICHG (1 << 7) +#define ISR_TXRDY(CH) (1 << (((CH) & 1) ? 4 : 0)) +#define ISR_RXRDY(CH) (1 << (((CH) & 1) ? 5 : 1)) +#define ISR_BREAK(CH) (1 << (((CH) & 1) ? 6 : 2)) + +typedef struct IPOctalState IPOctalState; +typedef struct SCC2698Channel SCC2698Channel; +typedef struct SCC2698Block SCC2698Block; + +struct SCC2698Channel { + IPOctalState *ipoctal; + CharDriverState *dev; + char *devpath; + bool tx_enabled; + bool rx_enabled; + bool tx_data; + bool rx_data; + uint8_t mr[2]; + uint8_t mr_idx; + uint8_t sr; + uint8_t thr; + uint8_t rhr; +}; + +struct SCC2698Block { + uint8_t imr; + uint8_t isr; +}; + +struct IPOctalState { + IPackDevice dev; + SCC2698Channel ch[N_CHANNELS]; + SCC2698Block blk[N_BLOCKS]; + uint8_t irq_vector; + QEMUTimer *timer; +}; + +static const VMStateDescription vmstate_scc2698_channel = { + .name = "scc2698_channel", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_BOOL(tx_enabled, SCC2698Channel), + VMSTATE_BOOL(rx_enabled, SCC2698Channel), + VMSTATE_BOOL(tx_data, SCC2698Channel), + VMSTATE_BOOL(rx_data, SCC2698Channel), + VMSTATE_UINT8_ARRAY(mr, SCC2698Channel, 2), + VMSTATE_UINT8(mr_idx, SCC2698Channel), + VMSTATE_UINT8(sr, SCC2698Channel), + VMSTATE_UINT8(thr, SCC2698Channel), + VMSTATE_UINT8(rhr, SCC2698Channel), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_scc2698_block = { + .name = "scc2698_block", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(imr, SCC2698Block), + VMSTATE_UINT8(isr, SCC2698Block), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_ipoctal = { + .name = "ipoctal", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_IPACK_DEVICE(dev, IPOctalState), + VMSTATE_STRUCT_ARRAY(ch, IPOctalState, N_CHANNELS, 1, + vmstate_scc2698_channel, SCC2698Channel), + VMSTATE_STRUCT_ARRAY(blk, IPOctalState, N_BLOCKS, 1, + vmstate_scc2698_block, SCC2698Block), + VMSTATE_UINT8(irq_vector, IPOctalState), + VMSTATE_TIMER(timer, IPOctalState), + VMSTATE_END_OF_LIST() + } +}; + +/* data[10] is 0x0C, not 0x0B as the doc says */ +static const uint8_t id_prom_data[] = { + 0x49, 0x50, 0x41, 0x43, 0xF0, 0x22, + 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xCC +}; + +static void ipoctal_set_irq(IPOctalState *dev) +{ + qemu_mod_timer(dev->timer, TIMER_INTERVAL); +} + +static bool write_cr(IPOctalState *dev, unsigned channel, uint8_t val) +{ + SCC2698Channel *ch = &dev->ch[channel]; + SCC2698Block *blk = &dev->blk[channel / 2]; + bool set_irq = false; + + DPRINTF("Write CR%c %u: ", channel + 'a', val); + + /* The lower 4 bits are used to enable and disable Tx and Rx */ + if (val & CR_ENABLE_RX) { + DPRINTF2("Rx on, "); + ch->rx_enabled = true; + } + if (val & CR_DISABLE_RX) { + DPRINTF2("Rx off, "); + ch->rx_enabled = false; + ch->rx_data = false; + } + if (val & CR_ENABLE_TX) { + DPRINTF2("Tx on, "); + ch->tx_enabled = true; + /* Set the TxRDY bit unless there's data being transmitted */ + if (!ch->tx_data) { + ch->sr |= SR_TXRDY | SR_TXEMT; + set_irq = true; + } + } + if (val & CR_DISABLE_TX) { + DPRINTF2("Tx off, "); + ch->tx_enabled = false; + ch->sr &= ~(SR_TXRDY | SR_TXEMT); + } + + DPRINTF2("cmd: "); + + /* The rest of the bits implement different commands */ + switch (CR_CMD(val)) { + case CR_NO_OP: + DPRINTF2("none"); + break; + case CR_RESET_MR: + DPRINTF2("reset MR"); + ch->mr_idx = 0; + break; + case CR_RESET_RX: + DPRINTF2("reset Rx"); + ch->rx_enabled = false; + ch->rx_data = false; + break; + case CR_RESET_TX: + DPRINTF2("reset Tx"); + ch->tx_enabled = false; + ch->tx_data = false; + break; + case CR_RESET_ERR: + DPRINTF2("reset err"); + ch->sr &= ~(SR_OVERRUN | SR_PARITY | SR_FRAMING | SR_BREAK); + break; + case CR_RESET_BRKINT: + DPRINTF2("reset brk ch int"); + blk->isr &= ~(ISR_BREAKA | ISR_BREAKB); + break; + default: + DPRINTF2("unsupported 0x%x", CR_CMD(val)); + } + + DPRINTF2("\n"); + + return set_irq; +} + +static uint16_t io_read(IPackDevice *ip, uint8_t addr) +{ + IPOctalState *dev = DO_UPCAST(IPOctalState, dev, ip); + uint16_t ret = 0; + /* addr[7:6]: block (A-D) + addr[7:5]: channel (a-h) + addr[5:0]: register */ + unsigned block = addr >> 5; + unsigned channel = addr >> 4; + /* Big endian, accessed using 8-bit bytes at odd locations */ + unsigned offset = (addr & 0x1F) ^ 1; + SCC2698Channel *ch = &dev->ch[channel]; + SCC2698Block *blk = &dev->blk[block]; + bool set_irq = false; + + switch (offset) { + + case REG_MRa: + case REG_MRb: + ret = ch->mr[ch->mr_idx]; + DPRINTF("Read MR%u%c: 0x%x\n", ch->mr_idx + 1, channel + 'a', ret); + ch->mr_idx = 1; + break; + + case REG_SRa: + case REG_SRb: + ret = ch->sr; + DPRINTF("Read SR%c: 0x%x\n", channel + 'a', ret); + break; + + case REG_RHRa: + case REG_RHRb: + ret = ch->rhr; + ch->rx_data = false; + ch->sr &= ~SR_RXRDY; + if (ch->sr & SR_BREAK) { + ch->sr &= ~SR_BREAK; + blk->isr |= ISR_BREAK(channel); + set_irq = true; + } + DPRINTF("Read RHR%c '%c'\n", channel + 'a', ret); + break; + + case REG_ISR: { + /* Duplicate TxRDY/RxRDY from SR in ISR */ + SCC2698Channel *cha = &dev->ch[block * 2]; + SCC2698Channel *chb = &dev->ch[block * 2 + 1]; + + if (cha->sr & SR_TXRDY) { + blk->isr |= ISR_TXRDYA; + } else { + blk->isr &= ~ISR_TXRDYA; + } + + if (cha->sr & SR_RXRDY) { + blk->isr |= ISR_RXRDYA; + } else { + blk->isr &= ~ISR_RXRDYA; + } + + if (chb->sr & SR_TXRDY) { + blk->isr |= ISR_TXRDYB; + } else { + blk->isr &= ~ISR_TXRDYB; + } + + if (chb->sr & SR_RXRDY) { + blk->isr |= ISR_RXRDYB; + } else { + blk->isr &= ~ISR_RXRDYB; + } + + ret = blk->isr; + DPRINTF("Read ISR%c: 0x%x\n", block + 'A', ret); + } + break; + + default: + DPRINTF("Read unknown/unsupported register 0x%02x\n", offset); + } + + if (set_irq) { + ipoctal_set_irq(dev); + } + + return ret; +} + +static void io_write(IPackDevice *ip, uint8_t addr, uint16_t val) +{ + IPOctalState *dev = DO_UPCAST(IPOctalState, dev, ip); + unsigned reg = val & 0xFF; + /* addr[7:6]: block (A-D) + addr[7:5]: channel (a-h) + addr[5:0]: register */ + unsigned block = addr >> 5; + unsigned channel = addr >> 4; + /* Big endian, accessed using 8-bit bytes at odd locations */ + unsigned offset = (addr & 0x1F) ^ 1; + SCC2698Channel *ch = &dev->ch[channel]; + SCC2698Block *blk = &dev->blk[block]; + bool set_irq = false; + + switch (offset) { + + case REG_MRa: + case REG_MRb: + ch->mr[ch->mr_idx] = reg; + DPRINTF("Write MR%u%c 0x%x\n", ch->mr_idx + 1, channel + 'a', reg); + ch->mr_idx = 1; + break; + + /* Not implemented */ + case REG_CSRa: + case REG_CSRb: + DPRINTF("Write CSR%c: 0x%x\n", channel + 'a', reg); + break; + + case REG_CRa: + case REG_CRb: + set_irq = write_cr(dev, channel, reg); + break; + + case REG_THRa: + case REG_THRb: + if (ch->tx_enabled) { + DPRINTF("Write THR%c '%c'\n", channel + 'a', reg); + ch->sr &= ~(SR_TXRDY | SR_TXEMT); + ch->thr = reg; + ch->tx_data = true; + set_irq = true; + } else { + DPRINTF("Write THR%c '%c', but Tx disabled\n", channel + 'a', reg); + } + break; + + /* Not implemented */ + case REG_ACR: + DPRINTF("Write ACR%c 0x%x\n", block + 'A', val); + break; + + case REG_IMR: + DPRINTF("Write IMR%c 0x%x\n", block + 'A', val); + blk->imr = reg; + break; + + /* Not implemented */ + case REG_OPCR: + DPRINTF("Write OPCR%c 0x%x\n", block + 'A', val); + break; + + default: + DPRINTF("Write unknown/unsupported register 0x%02x %u\n", offset, val); + } + + if (set_irq) { + ipoctal_set_irq(dev); + } +} + +static uint16_t id_read(IPackDevice *ip, uint8_t addr) +{ + uint16_t ret = 0; + unsigned pos = addr / 2; /* The ID PROM data is stored every other byte */ + + if (pos < ARRAY_SIZE(id_prom_data)) { + ret = id_prom_data[pos]; + } else { + DPRINTF("Attempt to read unavailable PROM data at 0x%x\n", addr); + } + + return ret; +} + +static void id_write(IPackDevice *ip, uint8_t addr, uint16_t val) +{ + IPOctalState *dev = DO_UPCAST(IPOctalState, dev, ip); + if (addr == 1) { + DPRINTF("Write IRQ vector: %u\n", (unsigned) val); + dev->irq_vector = val; /* Undocumented, but the hw works like that */ + } else { + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); + } +} + +static uint16_t int_read(IPackDevice *ip, uint8_t addr) +{ + IPOctalState *dev = DO_UPCAST(IPOctalState, dev, ip); + if (addr != 0 && addr != 2) { + DPRINTF("Attempt to read from 0x%x\n", addr); + return 0; + } else { + return dev->irq_vector; + } +} + +static void int_write(IPackDevice *ip, uint8_t addr, uint16_t val) +{ + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); +} + +static uint16_t mem_read16(IPackDevice *ip, uint32_t addr) +{ + DPRINTF("Attempt to read from 0x%x\n", addr); + return 0; +} + +static void mem_write16(IPackDevice *ip, uint32_t addr, uint16_t val) +{ + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); +} + +static uint8_t mem_read8(IPackDevice *ip, uint32_t addr) +{ + DPRINTF("Attempt to read from 0x%x\n", addr); + return 0; +} + +static void mem_write8(IPackDevice *ip, uint32_t addr, uint8_t val) +{ + IPOctalState *dev = DO_UPCAST(IPOctalState, dev, ip); + if (addr == 1) { + DPRINTF("Write IRQ vector: %u\n", (unsigned) val); + dev->irq_vector = val; + } else { + DPRINTF("Attempt to write 0x%x to 0x%x\n", val, addr); + } +} + +static void ipoctal_timer(void *opaque) +{ + IPOctalState *dev = opaque; + bool intno_set[2] = { false, false }; + unsigned i; + + /* Check the status of each channel to see what interrupts needs + * to be raised. */ + for (i = 0; i < N_CHANNELS; i++) { + unsigned block = i / 2; + unsigned intno = block / 2; + SCC2698Channel *ch = &dev->ch[i]; + SCC2698Block *blk = &dev->blk[block]; + if (ch->tx_data) { + + /* Transmit data */ + if (ch->dev) { + qemu_chr_fe_write(ch->dev, &ch->thr, 1); + } + DPRINTF("Channel %u: transmitted '%c'\n", i, ch->thr); + + /* After transmitting, set TxRDY again if Tx is enabled. */ + ch->tx_data = false; + if (ch->tx_enabled) { + ch->sr |= SR_TXRDY | SR_TXEMT; + } + } + if (((ch->sr & SR_TXRDY) && (blk->imr & ISR_TXRDY(i))) || + ((ch->sr & SR_RXRDY) && (blk->imr & ISR_RXRDY(i))) || + ((blk->isr & ISR_BREAK(i)) && (blk->imr & ISR_BREAK(i)))) { + intno_set[intno] = true; + } + } + + if (intno_set[0]) { + qemu_irq_raise(dev->dev.irq[0]); + } + if (intno_set[1]) { + qemu_irq_raise(dev->dev.irq[1]); + } +} + +static int hostdev_can_receive(void *opaque) +{ + SCC2698Channel *ch = opaque; + return ch->rx_enabled && !ch->rx_data; +} + +static void hostdev_receive(void *opaque, const uint8_t *buf, int size) +{ + SCC2698Channel *ch = opaque; + ch->rhr = buf[0]; + ch->rx_data = true; + ch->sr |= SR_RXRDY; + ipoctal_set_irq(ch->ipoctal); +} + +static void hostdev_event(void *opaque, int event) +{ + SCC2698Channel *ch = opaque; + switch (event) { + case CHR_EVENT_OPENED: + DPRINTF("Device %s opened\n", ch->dev->label); + break; + case CHR_EVENT_BREAK: { + uint8_t zero = 0; + DPRINTF("Device %s received break\n", ch->dev->label); + + if (!(ch->sr & SR_BREAK)) { + IPOctalState *dev = ch->ipoctal; + unsigned block, channel = 0; + + while (&dev->ch[channel] != ch) { + channel++; + } + block = channel / 2; + + ch->sr |= SR_BREAK; + dev->blk[block].isr |= ISR_BREAK(channel); + } + + /* Put a zero character in the buffer */ + hostdev_receive(ch, &zero, 1); + } + break; + default: + DPRINTF("Device %s received event %d\n", ch->dev->label, event); + } +} + +static int ipoctal_init(IPackDevice *ip) +{ + IPOctalState *s = DO_UPCAST(IPOctalState, dev, ip); + unsigned i; + + s->timer = qemu_new_timer_ms(vm_clock, ipoctal_timer, s); + + for (i = 0; i < N_CHANNELS; i++) { + SCC2698Channel *ch = &s->ch[i]; + ch->ipoctal = s; + + /* Redirect IP-Octal channels to host character devices */ + if (ch->devpath) { + const char chr_name[] = "ipoctal"; + char label[ARRAY_SIZE(chr_name) + 2]; + static int index; + + snprintf(label, sizeof(label), "%s%d", chr_name, index); + + ch->dev = qemu_chr_new(label, ch->devpath, NULL); + + if (ch->dev) { + index++; + qemu_chr_add_handlers(ch->dev, hostdev_can_receive, + hostdev_receive, hostdev_event, ch); + DPRINTF("Redirecting channel %u to %s (%s)\n", + i, ch->devpath, label); + } else { + DPRINTF("Could not redirect channel %u to %s\n", + i, ch->devpath); + } + } + } + + return 0; +} + +static int ipoctal_exit(IPackDevice *ip) +{ + IPOctalState *s = DO_UPCAST(IPOctalState, dev, ip); + qemu_del_timer(s->timer); + qemu_free_timer(s->timer); + return 0; +} + +static Property ipoctal_properties[] = { + DEFINE_PROP_STRING("serial0", IPOctalState, ch[0].devpath), + DEFINE_PROP_STRING("serial1", IPOctalState, ch[1].devpath), + DEFINE_PROP_STRING("serial2", IPOctalState, ch[2].devpath), + DEFINE_PROP_STRING("serial3", IPOctalState, ch[3].devpath), + DEFINE_PROP_STRING("serial4", IPOctalState, ch[4].devpath), + DEFINE_PROP_STRING("serial5", IPOctalState, ch[5].devpath), + DEFINE_PROP_STRING("serial6", IPOctalState, ch[6].devpath), + DEFINE_PROP_STRING("serial7", IPOctalState, ch[7].devpath), + DEFINE_PROP_END_OF_LIST(), +}; + +static void ipoctal_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + IPackDeviceClass *ic = IPACK_DEVICE_CLASS(klass); + + ic->init = ipoctal_init; + ic->exit = ipoctal_exit; + ic->io_read = io_read; + ic->io_write = io_write; + ic->id_read = id_read; + ic->id_write = id_write; + ic->int_read = int_read; + ic->int_write = int_write; + ic->mem_read16 = mem_read16; + ic->mem_write16 = mem_write16; + ic->mem_read8 = mem_read8; + ic->mem_write8 = mem_write8; + + dc->desc = "IP-Octal 232 8-channel RS-232 IndustryPack"; + dc->props = ipoctal_properties; + dc->vmsd = &vmstate_ipoctal; +} + +static const TypeInfo ipoctal_info = { + .name = "ipoctal", + .parent = TYPE_IPACK_DEVICE, + .instance_size = sizeof(IPOctalState), + .class_init = ipoctal_class_init, +}; + +static void ipoctal_register_types(void) +{ + type_register_static(&ipoctal_info); +} + +type_init(ipoctal_register_types)