diff --git a/Makefile.objs b/Makefile.objs index 70c5c79..5fdec97 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -66,6 +66,7 @@ net-obj-y = net.o net-nested-y = queue.o checksum.o util.o net-nested-y += socket.o net-nested-y += dump.o +net-nested-y += user-arcnet.o net-nested-$(CONFIG_POSIX) += tap.o net-nested-$(CONFIG_LINUX) += tap-linux.o net-nested-$(CONFIG_WIN32) += tap-win32.o @@ -290,6 +291,8 @@ hw-obj-$(CONFIG_RTL8139_PCI) += rtl8139.o hw-obj-$(CONFIG_SMC91C111) += smc91c111.o hw-obj-$(CONFIG_LAN9118) += lan9118.o hw-obj-$(CONFIG_NE2000_ISA) += ne2000-isa.o +hw-obj-$(CONFIG_COM90C65_ISA) += com90c65.o com90c65-isa.o +hw-obj-$(CONFIG_DE100_ISA) += de100-isa.o hw-obj-$(CONFIG_OPENCORES_ETH) += opencores_eth.o # IDE diff --git a/VERSION b/VERSION index 9084fa2..64b4f32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.1.0-owi diff --git a/arcnet.h b/arcnet.h new file mode 100644 index 0000000..173f3f0 --- /dev/null +++ b/arcnet.h @@ -0,0 +1,62 @@ +/* + * QEMU ARCnet generic declarations + */ + +#ifndef ARCNET_H +#define ARCNET_H + +#include "qemu-common.h" + +/* arcnet packet size + * The packets can have only 2 sizes: short or long */ +#define ARCNET_SHORT_SIZE 256 +#define ARCNET_LONG_SIZE 512 +#define ARCNET_MAX_SIZE ARCNET_LONG_SIZE + +/* arcnet header + * The source and destination bytes are node ids (0 for broadcast). + * If the short start byte is 0, it is a long packet. + * The start byte is the offset of the first data byte. */ +enum arcnet_packet_offsets { + ARCNET_SOURCE = 0, + ARCNET_DESTINATION, + ARCNET_SHORT_START, + ARCNET_SHORT_HEADER_SIZE, + ARCNET_LONG_START = ARCNET_SHORT_HEADER_SIZE, + ARCNET_LONG_HEADER_SIZE +} ; + +/* get the size of the packet */ +#define ARCNET_SIZE(buffer) (((uint8_t*)buffer)[ARCNET_SHORT_START] != 0 \ + ? ARCNET_SHORT_SIZE \ + : ARCNET_LONG_SIZE \ +) +/* get the size of the header for a packet size */ +#define ARCNET_HEADER_SIZE(size) (size == ARCNET_SHORT_SIZE \ + ? ARCNET_SHORT_HEADER_SIZE \ + : ARCNET_LONG_HEADER_SIZE \ +) +/* get the start byte offset for a packet size */ +#define ARCNET_START(size) (size == ARCNET_SHORT_SIZE \ + ? ARCNET_SHORT_START \ + : ARCNET_LONG_START \ +) + +/* The body of a packet coontains padding and data to the end. + * The data sizes ranges are not contiguous. */ +#define ARCNET_SHORT_DATA_MIN_SIZE 1 +#define ARCNET_SHORT_DATA_MAX_SIZE (ARCNET_SHORT_SIZE - ARCNET_SHORT_HEADER_SIZE) +#define ARCNET_LONG_DATA_MIN_SIZE (ARCNET_LONG_SIZE - 0xff) +#define ARCNET_LONG_DATA_MAX_SIZE (ARCNET_LONG_SIZE - ARCNET_LONG_HEADER_SIZE) +/* get the data pointer of a packet */ +#define ARCNET_DATA(buffer) (ARCNET_SIZE(buffer) == ARCNET_SHORT_SIZE \ + ? ((uint8_t*)buffer) + ((uint8_t*)buffer)[ARCNET_SHORT_START] \ + : ((uint8_t*)buffer) + ((uint8_t*)buffer)[ARCNET_LONG_START ] \ +) +/* get the data length of a packet */ +#define ARCNET_DATA_LENGTH(buffer) (ARCNET_SIZE(buffer) == ARCNET_SHORT_SIZE \ + ? ARCNET_SHORT_SIZE - ((uint8_t*)buffer)[ARCNET_SHORT_START] \ + : ARCNET_LONG_SIZE - ((uint8_t*)buffer)[ARCNET_LONG_START ] \ +) + +#endif /* ARCNET_H */ diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak index 2c78175..891fdae 100644 --- a/default-configs/i386-softmmu.mak +++ b/default-configs/i386-softmmu.mak @@ -19,6 +19,8 @@ CONFIG_DMA=y CONFIG_IDE_ISA=y CONFIG_IDE_PIIX=y CONFIG_NE2000_ISA=y +CONFIG_COM90C65_ISA=y +CONFIG_DE100_ISA=y CONFIG_PIIX_PCI=y CONFIG_SOUND=y CONFIG_HPET=y diff --git a/hw/com90c65-isa.c b/hw/com90c65-isa.c new file mode 100644 index 0000000..b1afb65 --- /dev/null +++ b/hw/com90c65-isa.c @@ -0,0 +1,131 @@ +/* + * QEMU ARCnet COM90C66 emulation + * + * This source file contains the ISA declaration of the network card. To work, + * it uses the generic code of the file com90c65.c. + */ + +#include "hw.h" +#include "pc.h" +#include "isa.h" +#include "qdev.h" +#include "net.h" +#include "com90c65.h" + +/* default I/O addresses */ +#define IO_BASES_MAX 8 +static int io_bases[IO_BASES_MAX] = {0x260, 0x290, 0x2e0, 0x2f0, 0x300, 0x350, 0x380, 0x3e0}; + +typedef struct ISACOM90C65State { + ISADevice dev; + uint32_t iobase; + uint32_t isairq; + uint32_t mmiobase; + uint32_t nodeid; + COM90C65State com90c65; +} ISACOM90C65State; + +static void isa_com90c65_cleanup(VLANClientState *nc) +{ + COM90C65State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static NetClientInfo net_com90c65_isa_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof (NICState), + .can_receive = com90c65_can_receive, + .receive = com90c65_receive, + .cleanup = isa_com90c65_cleanup, +}; + +/* convert I/O address to an index value in the dedicated register */ +static int com90c65_set_io_select(COM90C65State *s, int io_base) +{ + int index ; + for (index = 0 ; index < IO_BASES_MAX ; index ++) { + if (io_base == io_bases[index]) { + s->io_select = index ; + return 0 ; + } + } + return -1 ; +} + +static int isa_com90c65_initfn(ISADevice *dev) { + ISACOM90C65State *isa = DO_UPCAST(ISACOM90C65State, dev, dev); + COM90C65State *s = &isa->com90c65; + + if (s->c.vlan) { + dprintf("initialization (" COM90C65_DEVICE " in vlan%d)\n", + s->c.vlan->id); + dprintf("iobase=0x%x, irq=%d, mmiobase=0x%x, nodeid=%d\n", + isa->iobase, isa->isairq, isa->mmiobase, isa->nodeid); + } + + if (com90c65_set_io_select(s, isa->iobase) != 0) { + eprintf("bad I/O address: %X\n", isa->iobase); + return -1; + } + + com90c65_setup_io(s, REGISTERS_SIZE); + isa_register_ioport(dev, &s->io, isa->iobase); + com90c65_setup_mmio(s, MEM_SIZE); + memory_region_add_subregion_overlap(isa_address_space(dev), + isa->mmiobase, &s->mmio, 2); + + isa_init_irq(dev, &s->irq, isa->isairq); + + if (isa->nodeid > UCHAR_MAX) + qemu_macaddr_default_if_unset(&s->c.macaddr); + else { + memset(s->c.macaddr.a, 0, 5); + s->c.macaddr.a[5] = isa->nodeid; + } + s->mem_select = 0 ; /* not used */ + s->node_id = s->c.macaddr.a[5]; + com90c65_reset(s); + + /* initialize read/write accesses to the registers for easy implementation of the read/write functions */ + com90c65_registers_access_init(s); + + /* register a hard reset used by qemu */ + qemu_register_reset(com90c65_reset, s); + + s->nic = qemu_new_nic(&net_com90c65_isa_info, &s->c, + object_get_typename(OBJECT(dev)), dev->qdev.id, s); + + return 0; +} + +static Property com90c65_isa_properties[] = { + DEFINE_PROP_HEX32("iobase", ISACOM90C65State, iobase, 0x2e0), + DEFINE_PROP_UINT32("irq", ISACOM90C65State, isairq, 5), + DEFINE_PROP_HEX32("mmiobase", ISACOM90C65State, mmiobase, 0xd0000), + DEFINE_PROP_UINT32("nodeid", ISACOM90C65State, nodeid, UCHAR_MAX + 1), + DEFINE_NIC_PROPERTIES(ISACOM90C65State, com90c65.c), + DEFINE_PROP_END_OF_LIST(), +}; + +static void isa_com90c65_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + ic->init = isa_com90c65_initfn; + dc->props = com90c65_isa_properties; +} + +static TypeInfo com90c65_isa_info = { + .name = "com90c65_isa", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof (ISACOM90C65State), + .class_init = isa_com90c65_class_initfn, +}; + +static void com90c65_isa_register_types(void) +{ + type_register_static(&com90c65_isa_info); +} + +type_init(com90c65_isa_register_types) diff --git a/hw/com90c65.c b/hw/com90c65.c new file mode 100644 index 0000000..bc51d7c --- /dev/null +++ b/hw/com90c65.c @@ -0,0 +1,483 @@ +/* + * QEMU ARCnet COM90C66 emulation + * + * This is a hardware NIC (Network Interface Card) + * emulation of the COM90C66 device family. + * + * It works on an (emulated) ISA bus. The code of this file should be generic + * however. ISA dependant code is in the source file com90c65-isa.c. + * Access can be 8 or 16 bits, in I/O or memory mapped mode. + * There is a burst mode called command chaining (not implemented). + * The datasheet is available at: + * http://www.smsc.com/main/tools/discontinued/90c66.pdf + * + * The device communicates with its vlan. + * An arcnet layer must be connected to the same vlan. + * A send comes from the driver to the vlan. + * A receive comes from the vlan to the driver. + */ + +#include "hw.h" +#include "isa.h" +#include "pc.h" +#include "net.h" +#include "com90c65.h" + +/* map the register variables with 2 arrays for read and write access */ +void com90c65_registers_access_init(COM90C65State *s) { + /* FIXME: what happens if WORDS_BIGENDIAN != TARGET_WORDS_BIGENDIAN ? */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +# define LOW_BYTE_OFFSET 0 +# define HIGH_BYTE_OFFSET 1 +#elif __BYTE_ORDER == __BIG_ENDIAN +# define LOW_BYTE_OFFSET 1 +# define HIGH_BYTE_OFFSET 0 +#endif + + /* dummy is used for registers whose written value is not used */ + static uint8_t dummy; + /* structure is initialized with 0, so the unreferenced registers are NULL */ + s->registers.read [OFFSET_STATUS] = &s->status; + s->registers.write[OFFSET_IRQ_MASK] = &s->irq_mask; + s->registers.read [OFFSET_DIAG_STATUS] = &s->diag_status; + s->registers.write[OFFSET_COMMAND] = &dummy; + s->registers.read [OFFSET_CONFIG] = &s->config; + s->registers.write[OFFSET_CONFIG] = &s->config; + s->registers.read [OFFSET_IO_SELECT] = &s->io_select; + s->registers.read [OFFSET_MEM_SELECT] = &s->mem_select; + s->registers.read [OFFSET_NODE_ID] = &s->node_id; + s->registers.write[OFFSET_NODE_ID] = &s->node_id; + s->registers.write[OFFSET_EXTERNAL] = &dummy; + s->registers.read [OFFSET_DATA_LOW] = (uint8_t*)&s->data + LOW_BYTE_OFFSET; + s->registers.write[OFFSET_DATA_LOW] = (uint8_t*)&s->data + LOW_BYTE_OFFSET; + s->registers.read [OFFSET_DATA_HIGH] = (uint8_t*)&s->data + HIGH_BYTE_OFFSET; + s->registers.write[OFFSET_DATA_HIGH] = (uint8_t*)&s->data + HIGH_BYTE_OFFSET; + s->registers.read [OFFSET_ADDR_LOW] = (uint8_t*)&s->address + LOW_BYTE_OFFSET; + s->registers.write[OFFSET_ADDR_LOW] = (uint8_t*)&s->address + LOW_BYTE_OFFSET; + s->registers.read [OFFSET_ADDR_HIGH] = (uint8_t*)&s->address + HIGH_BYTE_OFFSET; + s->registers.write[OFFSET_ADDR_HIGH] = (uint8_t*)&s->address + HIGH_BYTE_OFFSET; +} + +/* print device configuration (for debug use) of bits set in mask */ +static void com90c65_print_config(uint8_t config, uint8_t mask) { + if (DEBUG_CONFIG) { + if (mask & BIT_TXOFF) + dprintf("%s tx\n", BOOL_TEXT(~ config & BIT_TXOFF)); + if (mask & BIT_IO_ACCESS) + dprintf("%s buffer access\n", config & BIT_IO_ACCESS ? "I/O" : "memory"); + if (mask & BIT_WAIT) + dprintf("%s zero wait state\n", BOOL_TEXT(~ config & BIT_WAIT)); + if (mask & (BIT_ET1 | BIT_ET2)) + dprintf("extended timeout = %d%d\n", (config & BIT_ET1) / BIT_ET1, (config & BIT_ET2) / BIT_ET2); + if (mask & (BIT_DECODE | BIT_IO_ACCESS)) + dprintf("%dK ROM\n", config & BIT_IO_ACCESS ? (config & BIT_DECODE ? 16 : 8) : (config & BIT_DECODE ? 2 : 128)); + if (mask & BIT_CCHEN) + dprintf("%s command chaining\n", BOOL_TEXT(config & BIT_CCHEN)); + if (mask & BIT_16EN) + dprintf("%d-bit\n", config & BIT_16EN ? 16 : 8); + } + if (config & mask & BIT_CCHEN) + not_implemented("command chaining"); +} + +/* generate an IRQ if a masked status bit becomes 1 */ +static void com90c65_update_irq(COM90C65State *s, int generate) { + static uint8_t old_isr = 0; + uint8_t isr = s->status & s->irq_mask & IRQ_MASK; + int trigger = (isr & ~old_isr) != 0; + old_isr = isr; + if (generate) { + if (DEBUG_IRQ) { + dprintf ("status=0x%x", s->status); + if (trigger) + dprintf0(" => IRQ\n"); + else + dprintf0("\n"); + } + qemu_set_irq(s->irq, trigger); + } +} + +/* reset the device with the default values, + * stands for hard and soft reset */ +void com90c65_reset(void *user_data) { + COM90C65State *s = user_data; + + dprintf("reset\n"); + + s->status = DEFAULT_STATUS; + s->irq_mask = 0; + s->diag_status = 0; + s->config = DEFAULT_CONFIG; + + /* an arbitrary number is set and can be check for a valid reset */ + s->mem[0] = RESET_MAGIC_NUMBER; + /* the node id can be read after a reset */ + s->mem[1] = s->node_id; + + s->long_packet_enabled = 0; + s->broadcast_enabled = 0; + + com90c65_update_irq(s, 0); +} + +static uint32_t com90c65_reset_on_read(void *user_data, uint32_t address) { + com90c65_reset(user_data); + return 0; +} + +static void com90c65_reset_on_write(void *user_data, uint32_t address, uint32_t value) { + com90c65_reset(user_data); +} + +/* send the packet of the specified memory page to the vlan */ +static void com90c65_send(COM90C65State *s, int page) { + const uint8_t *buffer = &s->mem[PAGE_SIZE * page]; + int size; + + if (s->long_packet_enabled) + size = ARCNET_SIZE(buffer); + else + size = ARCNET_SHORT_SIZE; + + if (DEBUG_RXTX) + dprintf("send %d bytes\n", size); + + /* send to other clients of the vlan, + * it should be received by an arcnet layer */ + qemu_send_packet(&s->nic->nc, buffer, size); +} + +/* receive a packet from the vlan to the specified memory page */ +ssize_t com90c65_receive(VLANClientState *nc, const uint8_t *buffer, size_t size_) +{ + COM90C65State *s = DO_UPCAST(NICState, nc, nc)->opaque; + int size = size_; + + if (buffer == NULL || size <= 0) + return -1; + + /* a broadcasted packet has a recipient 0 and can be disabled by a command */ + if (buffer [ARCNET_DESTINATION] == 0 && ! s->broadcast_enabled) { + if (DEBUG_RXTX) + dprintf("ignore broadcasted packet\n"); + return -1; + } + + if (DEBUG_RXTX) + dprintf("receive %d bytes\n", size); + + /* copy the packet into the internal memory page specified on rx start */ + memcpy(&s->mem[PAGE_SIZE * (s->reception_page & 3)], buffer, size); + + /* an IRQ signals the packet availability */ + s->status |= BIT_RI; + com90c65_update_irq(s, 1); + return size_; +} + +/* the arcnet layer send a packet to the vlan only when the device is ready to receive */ +int com90c65_can_receive(VLANClientState *nc) +{ + COM90C65State *s = DO_UPCAST(NICState, nc, nc)->opaque; + return (s->status & BIT_RI) == 0; +} + +/* various actions must be done after a write in the command register */ +static void com90c65_command(COM90C65State *s, uint32_t command) { + switch (command & COMMAND_MASK) { + /* command chaining is not implemented */ + case COMMAND_CLEAR_IRQ: + if ((command & BIT_CLEAR_IRQ) == 0) { + if (DEBUG_IRQ) + dprintf("command chaining: clear tx interrupt\n"); + /* TODO (not used in Linux) */ + } else { + if (DEBUG_IRQ) + dprintf("command chaining: clear rx interrupt\n"); + /* TODO (not used in Linux) */ + } + break; + /* the emulation has no pending transmit, so cancel does nothing */ + case COMMMAND_DISABLE_TX: + if (DEBUG_RXTX) + dprintf("cancel tx\n"); + s->status |= BIT_TA; + break; + /* cancel reception if it has not begun */ + case COMMMAND_DISABLE_RX: + if (DEBUG_RXTX) + dprintf("cancel rx\n"); + s->status |= BIT_RI; + /* save the bit state without generate an IRQ */ + com90c65_update_irq(s, 0); + break; + /* send the packet of the specified page */ + case COMMMAND_ENABLE_TX: + if (DEBUG_RXTX) + dprintf("start tx from page %d\n", PAGE(command)); + s->status &= ~(BIT_TA | BIT_TMA); + /* save the resetted bits states */ + com90c65_update_irq(s, 0); + com90c65_send(s, PAGE(command)); + if (1) { /* on acknowledgement (assume it is ok) */ + s->status |= BIT_TMA; + } + if (1) { /* on transmit completion (assume it is ok) */ + s->status |= BIT_TA; + /* generate an IRQ */ + com90c65_update_irq(s, 1); + } + break; + /* prepare to receive a packet in the specified page */ + case COMMMAND_ENABLE_RX: + s->broadcast_enabled = command & BIT_RX_BROADCAST; + s->reception_page = PAGE(command); + if (DEBUG_RXTX) + dprintf("start rx to page %d (broadcast %sd)\n", s->reception_page, BOOL_TEXT(s->broadcast_enabled)); + s->status &= ~ BIT_RI; + /* save the cleared bit state (IRQ is generated on reception) */ + com90c65_update_irq(s, 0); + break; + /* set max size of packet to send */ + case COMMMAND_CONFIG: + s->long_packet_enabled = command & BIT_TX_LONG_PACKET; + if (DEBUG_CONFIG) + dprintf("%s long packet\n", BOOL_TEXT(s->long_packet_enabled)); + break; + /* clear status bits */ + case COMMMAND_CLEAR_FLAGS: + if (command & BIT_CLEAR_POR) { + if (DEBUG_IO_RW) + dprintf("clear flag POR\n"); + s->status &= ~ BIT_POR; + } + if (command & BIT_CLEAR_RECON) { + if (DEBUG_IO_RW) + dprintf("clear flag RECON\n"); + s->status &= ~ BIT_RECON; + /* save the cleared bit state */ + com90c65_update_irq(s, 0); + } + break; + default: + dprintf("an invalid command (0x%x) is received\n", command); + } +} + +/* read data in internal memory at the address written in the address registers + * and set it in the data registers */ +static void com90c65_update_data(COM90C65State *s) { + s->data = s->mem[s->address & ADDRESS_MASK] | s->mem[(s->address & ADDRESS_MASK) + 1] << 8; +} + +/* increment address registers if needed and update data registers, + * auto_increment can be 1 or 2 for 8 or 16 bits access */ +static void com90c65_auto_increment(COM90C65State *s, int auto_increment) { + if (s->address & BIT_AUTO_INC) + s->address = ((s->address & ADDRESS_MASK) + auto_increment) | BIT_AUTO_INC; + com90c65_update_data(s); +} + +/* return the value of the register */ +static uint32_t com90c65_ioport_read(COM90C65State *s, uint32_t address, int width) { + uint8_t *reg; + uint32_t value; + int auto_increment = 1; + + address &= 0xf; + + if (width == 16 && s->config & BIT_16EN && address == OFFSET_DATA_LOW) { + /* 16 bits: read the word of the data register */ + value = s->data; + auto_increment = 2; + } else { + /* 8 bits: read the byte of the specified register */ + reg = s->registers.read[address]; + if (reg == NULL) { + dprintf("an invalid register (0x%X) is read\n", address); + return 0xaa; + } + value = *reg; + } + if (DEBUG_IO_RW) + dprintf("register 0x%X is read (0x%x)\n", address, value); + + /* do specific actions */ + switch (address) { + case OFFSET_DIAG_STATUS: + /* reset diag_status after a read */ + s->diag_status = 0; + break; + case OFFSET_DATA_LOW: + /* load next data if the auto increment bit is set */ + com90c65_auto_increment(s, auto_increment); + break; + } + + return value; +} + +/* a register read can be 8-bit wide (inb syscall) */ +static uint32_t com90c65_ioport_read8(void *user_data, uint32_t address) { + COM90C65State *s = user_data; + return com90c65_ioport_read(s, address, 8); +} + +/* a register read can be 16-bit wide (inw syscall) */ +static uint32_t com90c65_ioport_read16(void *user_data, uint32_t address) { + COM90C65State *s = user_data; + return com90c65_ioport_read(s, address, 16); +} + +/* write value into the specified register */ +static void com90c65_ioport_write(COM90C65State *s, uint32_t address, uint32_t value, int width) { + uint8_t *reg; + uint8_t old_value = 0; + int auto_increment = 1; + + address &= 0xf; + + if (width == 16 && s->config & BIT_16EN && address == OFFSET_DATA_LOW) { + /* 16 bits: write the word into the data register */ + value &= 0xffff; + s->data = value; + auto_increment = 2; + } else { + /* 8 bit: write the byte into the specified register */ + value &= 0xff; + reg = s->registers.write[address]; + if (reg == NULL) { + dprintf("an invalid register (0x%X) is written\n", address); + return; + } + old_value = *reg; + *reg = value; + } + if (DEBUG_IO_RW) + dprintf("register 0x%X is written (0x%x)\n", address, value); + + /* do specific actions */ + switch (address) { + case OFFSET_IRQ_MASK: + if (DEBUG_IRQ) + dprintf("mask=0x%x\n", s->irq_mask); + /* generate an IRQ if it unmaks a bit set at 1 */ + com90c65_update_irq(s, 1); + break; + case OFFSET_COMMAND: + com90c65_command(s, value); + break; + case OFFSET_CONFIG: + /* print the configuration changes (debug only) */ + com90c65_print_config(s->config, s->config ^ old_value); + break; + case OFFSET_DATA_LOW: + /* write the data in the internal memory */ + s->mem[s->address & ADDRESS_MASK] = s->data & 0xff; + if (auto_increment == 2) { + /* write the upper byte of the 16-bit data */ + s->mem[(s->address + 1) & ADDRESS_MASK] = s->data >> 8; + } + /* increment address for the next data if the auto increment bit is set */ + com90c65_auto_increment(s, auto_increment); + break; + case OFFSET_ADDR_LOW: + case OFFSET_ADDR_HIGH: + /* load data of the specified address for a read */ + com90c65_update_data(s); + break; + } +} + +/* a register write can be 8-bit wide (outb syscall) */ +static void com90c65_ioport_write8(void *user_data, uint32_t address, uint32_t value) { + COM90C65State *s = user_data; + com90c65_ioport_write(s, address, value, 8); +} + +/* a register write can be 16-bit wide (outw syscall) */ +static void com90c65_ioport_write16(void *user_data, uint32_t address, uint32_t value) { + COM90C65State *s = user_data; + com90c65_ioport_write(s, address, value, 16); +} + +/* read from memory mapped */ +static uint64_t com90c65_mmio_read(void *opaque, target_phys_addr_t addr, unsigned size) { + COM90C65State *s = opaque; + uint32_t value = 0; + int offset; + for (offset = 0; offset < size; offset ++) + value |= s->mem[addr + offset] << (offset << 3); + if (DEBUG_MM_RW) + dprintf("read %d bytes from memory 0x" TARGET_FMT_plx " (0x%x)\n", + size, addr, value); + return value; +} + +/* write to memory mapped */ +static void com90c65_mmio_write(void *opaque, target_phys_addr_t addr, uint64_t data, unsigned size) { + COM90C65State *s = opaque; + uint32_t value = data; + int offset; + if (DEBUG_MM_RW) + dprintf("write %d bytes to memory: 0x" TARGET_FMT_plx " (0x%x)\n", + size, addr, value); + for (offset = 0; offset < size; offset ++) + s->mem[addr + offset] = value >> (offset << 3) & 0xff; +} + +static uint64_t com90c65_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + COM90C65State *s = opaque; + + if (addr == OFFSET_DATA_LOW && size == 2) { + return com90c65_ioport_read16(s, addr); + } else if (addr >= OFFSET_RESET_BEGIN && + addr <= OFFSET_RESET_END && + size == 1) { + return com90c65_reset_on_read(s, addr); + } else if (addr < REGISTERS_SIZE && size == 1) { + return com90c65_ioport_read8(s, addr); + } + return ((uint64_t)1 << (size * 8)) - 1; +} + +static void com90c65_write(void *opaque, target_phys_addr_t addr, + uint64_t data, unsigned size) +{ + COM90C65State *s = opaque; + + if (addr == OFFSET_DATA_LOW && size == 2) { + return com90c65_ioport_write16(s, addr, data); + } else if (addr >= OFFSET_RESET_BEGIN && + addr <= OFFSET_RESET_END && + size == 1) { + return com90c65_reset_on_write(s, addr, data); + } else if (addr < REGISTERS_SIZE && size == 1) { + return com90c65_ioport_write8(s, addr, data); + } +} + +static const MemoryRegionOps com90c65_io_ops = { + .read = com90c65_read, + .write = com90c65_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +void com90c65_setup_io(COM90C65State *s, unsigned size) +{ + memory_region_init_io(&s->io, &com90c65_io_ops, s, "com90c65-io", size); +} + +static const MemoryRegionOps com90c65_mmio_ops = { + .read = com90c65_mmio_read, + .write = com90c65_mmio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +void com90c65_setup_mmio(COM90C65State *s, unsigned size) +{ + memory_region_init_io(&s->mmio, &com90c65_mmio_ops, s, "com90c65-mmio", size); +} diff --git a/hw/com90c65.h b/hw/com90c65.h new file mode 100644 index 0000000..998e653 --- /dev/null +++ b/hw/com90c65.h @@ -0,0 +1,174 @@ +/* + * QEMU ARCnet COM90C66 emulation + */ + +#include "arcnet.h" + +/* name of the device (debug usage) */ +#define COM90C65_DEVICE "com90c65" + +#ifdef COM90C65_DEBUG +/* set it to 1 to print configuration changes */ +# define DEBUG_CONFIG 1 +/* set it to 1 to print send/receive events */ +# define DEBUG_RXTX 1 +/* set it to 1 to print sent/received packets */ +# define DEBUG_DUMP 0 +/* set it to 1 to print interrupt related events */ +# define DEBUG_IRQ 0 +/* set it to 1 to print read/write of registers */ +# define DEBUG_IO_RW 0 +/* set it to 1 to print read/write of memory mapped */ +# define DEBUG_MM_RW 0 +#else +# define DEBUG_CONFIG 0 +# define DEBUG_RXTX 0 +# define DEBUG_DUMP 0 +# define DEBUG_IRQ 0 +# define DEBUG_IO_RW 0 +# define DEBUG_MM_RW 0 +#endif + +#define eprintf(fmt, args...) fprintf(stderr, COM90C65_DEVICE ": " fmt, ## args) +#define not_implemented0(fmt, args...) eprintf(fmt ": not implemented\n", ## args) +#ifdef COM90C65_DEBUG +# define dprintf0(fmt, args...) fprintf(stderr, fmt, ## args) +# define not_implemented(fmt, args...) not_implemented0(fmt, ## args) +#else +# define dprintf0(fmt, args...) do {} while (0) +# define not_implemented(fmt, args...) { \ + static int once = 0; \ + if (! once) { once = 1; not_implemented0(fmt, ## args); } \ + } +#endif +#define dprintf(fmt, args...) dprintf0(COM90C65_DEVICE ": " fmt, ## args) +#define BOOL_TEXT(state) (state ? "enable" : "disable") + +/* known byte to check the device at initialization */ +#define RESET_MAGIC_NUMBER 0xD1 + +/* the 2 kB internal memory is partitioned in 4 blocks */ +#define PAGE_SIZE ARCNET_MAX_SIZE +#define MEM_SIZE (PAGE_SIZE * 4) + +/* addresses can be read/write or can have different uses in read and write */ +enum register_offsets { + OFFSET_STATUS = 0x0, /* R */ + OFFSET_IRQ_MASK = 0x0, /* W */ + OFFSET_DIAG_STATUS = 0x1, /* R */ + OFFSET_COMMAND = 0x1, /* W */ + OFFSET_CONFIG = 0x2, /* R/W */ + OFFSET_IO_SELECT = 0x3, /* R */ + OFFSET_MEM_SELECT = 0x4, /* R */ + OFFSET_NODE_ID = 0x5, /* R/W */ + OFFSET_EXTERNAL = 0x7, /* W */ + OFFSET_RESET_BEGIN = 0x8, /* R/W */ + /* RESET 0x9 R/W */ + /* RESET 0xA R/W */ + OFFSET_RESET_END = 0xB, /* R/W */ + OFFSET_DATA_LOW = 0xC, /* R/W */ + OFFSET_DATA_HIGH = 0xD, /* R/W */ + OFFSET_ADDR_LOW = 0xE, /* R/W */ + OFFSET_ADDR_HIGH = 0xF, /* R/W */ + REGISTERS_SIZE +}; + +enum status_bits { + BIT_TA = 1 << 0, + BIT_TMA = 1 << 1, + BIT_RECON = 1 << 2, + BIT_TEST = 1 << 3, + BIT_POR = 1 << 4, + BIT_RI = 1 << 7 +}; +/* RECON should not be set by default (see status register in datasheet) but is needed by Linux */ +#define DEFAULT_STATUS (BIT_POR | BIT_RECON | BIT_RI | BIT_TA) +/* these status bits can be "masked" to generate an IRQ */ +#define IRQ_MASK (BIT_TA | BIT_RECON | BIT_RI) + +enum diag_status_bits { + BIT_TOKEN = 1 << 4, + BIT_RCVACT = 1 << 5, + BIT_MYRECON = 1 << 7 +}; + +/* commands can be distinguished by their first 3 bits */ +#define COMMAND_MASK 7 +enum commands { + COMMAND_CLEAR_IRQ = 0, + COMMMAND_DISABLE_TX = 1, + COMMMAND_DISABLE_RX = 2, + COMMMAND_ENABLE_TX = 3, + COMMMAND_ENABLE_RX = 4, + COMMMAND_CONFIG = 5, + COMMMAND_CLEAR_FLAGS = 6 +}; +enum command_bits { + BIT_CLEAR_IRQ = 1 << 3, + BIT_RX_BROADCAST = 1 << 7, + BIT_TX_LONG_PACKET = 1 << 3, + BIT_CLEAR_POR = 1 << 3, + BIT_CLEAR_RECON = 1 << 4 +}; +/* bits 3-4 of rx/tx commands define a page number */ +#define PAGE(command) ((command & 0x18) >> 3) + +enum config_bits { + BIT_TXOFF = 1 << 0, + BIT_IO_ACCESS = 1 << 1, + BIT_WAIT = 1 << 2, + BIT_ET2 = 1 << 3, + BIT_ET1 = 1 << 4, + BIT_DECODE = 1 << 5, + BIT_CCHEN = 1 << 6, + BIT_16EN = 1 << 7 +}; +#define DEFAULT_CONFIG (BIT_ET1 | BIT_ET2 | BIT_WAIT) + +/* addresses are 11 bits wide to access to the 2 kB of the internal memory */ +#define ADDRESS_MASK 0x7ff +enum address_bits { + BIT_AUTO_INC = 1 << 14 +}; + +/* THE object */ +typedef struct COM90C65State { + + /* internal storage */ + MemoryRegion io; + MemoryRegion mmio; + uint8_t mem[MEM_SIZE]; + uint8_t long_packet_enabled; + uint8_t broadcast_enabled; + uint8_t reception_page; + + /* registers */ + uint8_t status; + uint8_t irq_mask; + uint8_t diag_status; + uint8_t config; + uint8_t io_select; + uint8_t mem_select; + uint8_t node_id; + uint16_t data; + uint16_t address; + + /* registers access by arrays */ + struct { + uint8_t *read[REGISTERS_SIZE]; + uint8_t *write[REGISTERS_SIZE]; + } registers; + + /* qemu handlers */ + qemu_irq irq; + NICState *nic; + NICConf c; + +} COM90C65State; + +void com90c65_setup_io(COM90C65State *s, unsigned size); +void com90c65_setup_mmio(COM90C65State *s, unsigned size); +void com90c65_registers_access_init(COM90C65State *s); +void com90c65_reset(void *user_data); +int com90c65_can_receive(VLANClientState *nc); +ssize_t com90c65_receive(VLANClientState *nc, const uint8_t *buffer, size_t size_); diff --git a/hw/de100-isa.c b/hw/de100-isa.c new file mode 100644 index 0000000..6255c08 --- /dev/null +++ b/hw/de100-isa.c @@ -0,0 +1,320 @@ +/* + * QEMU DE100 emulation + * + * The DE100 is an ISA DECnet network card. It is also known as DEC EtherWORKS + * LC Ethernet Controller. + */ + +#include "isa.h" +#include "net.h" +#include "pcnet.h" + +#define DE100_DEVICE "de100" + +#ifdef DEBUG_DE100 +# define DPRINTF(fmt, args...) fprintf(stderr, DE100_DEVICE ": " fmt, ## args) +#else +# define DPRINTF(fmt, args...) do {} while (0) +#endif /* !DEBUG_DE100 */ + +#ifdef DEBUG_DE100_IO +# define DPRINTF_IO(fmt, args...) fprintf(stderr, DE100_DEVICE ": " fmt, ## args) +#else +# define DPRINTF_IO(fmt, args...) do {} while (0) +#endif /* !DEBUG_DE100_IO */ + +#ifdef DEBUG_DE100_MMIO +# define DPRINTF_MMIO(fmt, args...) fprintf(stderr, DE100_DEVICE ": " fmt, ## args) +#else +# define DPRINTF_MMIO(fmt, args...) do {} while (0) +#endif /* !DEBUG_DE100_MMIO */ + +/** + * NETWORK INTERFACE CSR (NI_CSR) bit definitions + * The NICSR is a register of the DE100 card. + */ +#define NICSR_TO 0x0100 /* Time Out for remote boot */ +#define NICSR_SHE 0x0080 /* SHadow memory Enable */ +#define NICSR_BS 0x0040 /* Bank Select */ +#define NICSR_BUF 0x0020 /* BUFfer size (1->32k, 0->64k) */ +#define NICSR_RBE 0x0010 /* Remote Boot Enable (1->net boot) */ +#define NICSR_AAC 0x0008 /* Address ROM Address Counter (1->enable) */ +#define NICSR_IM 0x0004 /* Interrupt Mask (1->mask) */ +#define NICSR_IEN 0x0002 /* Interrupt tristate ENable (1->enable) */ +#define NICSR_LED 0x0001 /* LED control */ + +/** + * Device signature returned by the DE100 ISA card. This signature is checked by + * the host driver to recognise the device. + */ +#define DE100_SIGNATURE 0xAA5500FFUL + + +typedef struct ISADE100State { + ISADevice dev; + PCNetState state; /**< state of the Am7990 chip */ + + uint32_t iobase; /**< Base address of I/O ports */ + MemoryRegion io; + + uint32_t mmiobase; /**< Base address of I/O memory */ + MemoryRegion mmio; + uint8_t mmiosize; /**< Size of I/O mem (32 or 64 KB) */ + + uint32_t isairq; /**< IRQ of the board */ + + uint16_t nicsr; /**< Network interface CSR */ + uint signature_pos; /**< signature position used when reading the + signature of the card */ + uint eth_addr_pos; /**< prom position used when reading the MAC + address */ +} ISADE100State; + +/** + * Write data to an I/O port of the DE100 device + * + * @param opaque Device state (ISADE100State) + * @param addr address of the I/O port + * @param val value to write + * @param size size of the val (should be 2 bytes for this device) + */ +static void de100_io_write(void *opaque, target_phys_addr_t addr, + uint64_t val, unsigned size) +{ + ISADE100State *isa = opaque; + + DPRINTF_IO("I/O write: addr=0x%" PRIx64 ", val=0x%" PRIx64 "\n", addr, val); + switch (addr) { + case 0x00: + isa->nicsr = val & 0xffff; + case 0x04: + case 0x06: + pcnet_ioport_writew(&isa->state, addr - 0x04, val & 0xffff); + break; + default: + break; + } +} + +/** + * Return the MAC address and the checksum byte by byte + * + * @param isa Device state + * + * @return a part of the MAC address or checksum + */ +static uint8_t de100_get_hw_addr(ISADE100State *isa) +{ + static int chksum = 0; + uint8_t val = 0; + + if (isa->eth_addr_pos == 0) + chksum = 0; + if (isa->eth_addr_pos < 6) { + val = isa->state.prom[isa->eth_addr_pos]; + chksum = chksum + (val << ((isa->eth_addr_pos % 2) * 8)); + if ((isa->eth_addr_pos == 1) || (isa->eth_addr_pos == 3)) + chksum <<= 1; + if (chksum > 0xffff) + chksum -= 0xffff; + } else if (isa->eth_addr_pos == 6) { + val = chksum & 0xff; + } else if (isa->eth_addr_pos == 7) { + val = (chksum >> 8) & 0xff; + } + isa->eth_addr_pos = (isa->eth_addr_pos + 1) % 8; + return val; +} + +/** + * Read data from an I/O port of the DE100 device + * + * @param opaque Device state (ISADE100State) + * @param addr address of the I/O port + * @param size size of the return value (should be 2 bytes for this device) + * + * @return read value + */ +static uint64_t de100_io_read(void *opaque, target_phys_addr_t addr, + unsigned size) +{ + ISADE100State *isa = opaque; + uint64_t val = -1; + + switch (addr) { + case 0x00: + val = isa->nicsr; + break; + case 0x04: + case 0x06: + val = pcnet_ioport_readw(&isa->state, addr - 0x04) & 0xffff; + break; + case 0x0c: + val = (DE100_SIGNATURE >> ((isa->signature_pos % 4) * 8)) & 0xff; + ++isa->signature_pos; + break; + case 0x0d: + val = de100_get_hw_addr(isa); + break; + default: + break; + } + DPRINTF_IO("I/O read: addr=0x%" PRIx64 ", val=0x%" PRIx64 "\n", addr, val); + return val; +} + +static const MemoryRegionOps de100_io_ops = { + .read = de100_io_read, + .write = de100_io_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 2, + .max_access_size = 2, + }, +}; + +static void de100_isa_cleanup(VLANClientState *nc) +{ + PCNetState *d = DO_UPCAST(NICState, nc, nc)->opaque; + + pcnet_common_cleanup(d); +} + +static NetClientInfo net_de100_isa_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = pcnet_can_receive, + .receive = pcnet_receive, + .link_status_changed = pcnet_set_link_status, + .cleanup = de100_isa_cleanup, +}; + +static const VMStateDescription de100_isa_vmstate = { + .name = "pcnet", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_STRUCT(state, ISADE100State, 0, vmstate_pcnet, PCNetState), + VMSTATE_END_OF_LIST() + } +}; + +/** + * Write data into the memory of the device + * + * @param dma_opaque base address of the mmio + * @param addr relative address of the data to write + * @param buf data to write + * @param len length of the data + * @param do_bswap not used + */ +static void isa_physical_memory_write(void *dma_opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap) +{ + uint32_t dmabase = *((uint32_t*) dma_opaque); + + DPRINTF_MMIO("mmio write: addr=0x%" PRIx64 " len=%d\n", addr, len); + cpu_physical_memory_rw(dmabase | addr, buf, len, 1); +} + +/** + * Read data from the memory of the device + * + * @param dma_opaque base address of the mmio + * @param addr relative address of the data to read + * @param buf data to read + * @param len length of the data + * @param do_bswap not used + */ +static void isa_physical_memory_read(void *dma_opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap) +{ + uint32_t dmabase = *((uint32_t*) dma_opaque); + + cpu_physical_memory_rw(dmabase | addr, buf, len, 0); + DPRINTF_MMIO("mmio read: addr=0x%" PRIx64 " len=%d\n", addr, len); +} + +/** + * Initialize the device + * + * @param dev ISA Device + * + * @return 0 on success + */ +static int de100_isa_init(ISADevice *dev) +{ + ISADE100State *isa = DO_UPCAST(ISADE100State, dev, dev); + PCNetState *s = &isa->state; + + if (s->conf.vlan) { + DPRINTF("initialization (DE100 in vlan%d)\n", + s->conf.vlan->id); + DPRINTF("iobase=0x%x, irq=%d, mmiobase=0x%x, mmiosize=%d\n", + isa->iobase, isa->isairq, isa->mmiobase, isa->mmiosize); + } + + /* Handler for memory-mapped I/O */ + memory_region_init_io(&isa->io, &de100_io_ops, isa, "de100-io", 0x10); + isa_register_ioport(dev, &isa->io, isa->iobase); + memory_region_init_ram(&isa->mmio, "de100-mmio", isa->mmiosize * 1024); + vmstate_register_ram_global(&isa->mmio); + memory_region_add_subregion_overlap(isa_address_space(dev), + isa->mmiobase, &isa->mmio, 2); + + isa_init_irq(dev, &s->irq, isa->isairq); + s->phys_mem_read = isa_physical_memory_read; + s->phys_mem_write = isa_physical_memory_write; + s->dma_opaque = &(isa->mmiobase); + + isa->nicsr = (isa->mmiosize == 32) ? NICSR_BUF : 0; + isa->signature_pos = 0; + isa->eth_addr_pos = 0; + + return pcnet_common_init(&dev->qdev, s, &net_de100_isa_info); +} + +static void de100_isa_reset(DeviceState *dev) +{ + ISADE100State *isa = DO_UPCAST(ISADE100State, dev.qdev, dev); + + pcnet_h_reset(&isa->state); + isa->nicsr = (isa->mmiosize == 32) ? NICSR_BUF : 0; + isa->signature_pos = 0; + isa->eth_addr_pos = 0; +} + +static Property de100_isa_properties[] = { + DEFINE_PROP_HEX32("iobase", ISADE100State, iobase, 0x300), + DEFINE_PROP_UINT32("irq", ISADE100State, isairq, 2), + DEFINE_PROP_HEX32("mmiobase", ISADE100State, mmiobase, 0xd8000), + DEFINE_PROP_UINT8("mmiosize", ISADE100State, mmiosize, 32), + DEFINE_NIC_PROPERTIES(ISADE100State, state.conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void de100_isa_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass); + + ic->init = de100_isa_init; + dc->reset = de100_isa_reset; + dc->vmsd = &de100_isa_vmstate; + dc->props = de100_isa_properties; +} + +static TypeInfo de100_isa_info = { + .name = "de100_isa", + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISADE100State), + .class_init = de100_isa_class_init, +}; + +static void de100_isa_register_types(void) +{ + type_register_static(&de100_isa_info); +} + +type_init(de100_isa_register_types) diff --git a/hw/isa-bus.c b/hw/isa-bus.c index 5a43f03..7590b92 100644 --- a/hw/isa-bus.c +++ b/hw/isa-bus.c @@ -21,6 +21,7 @@ #include "sysbus.h" #include "isa.h" #include "exec-memory.h" +#include "net.h" static ISABus *isabus; target_phys_addr_t isa_mem_base = 0; @@ -110,6 +111,56 @@ void isa_register_portio_list(ISADevice *dev, uint16_t start, portio_list_add(piolist, isabus->address_space_io, start); } +/** + * List of ISA NIC. + * ISA names must end with "_isa" in order to be initialized. + */ +static const char * const isa_nic_models[] = { + "ne2k_isa", + "com90c65_isa", + "de100_isa", + NULL +}; + +/* Initialize an ISA NIC. */ +ISADevice *isa_nic_init(ISABus *bus, NICInfo *nd, const char *default_model, + bool (* const pc_prop_set_funcs[])(DeviceState *qdev)) +{ + ISADevice *isa_dev; + DeviceState *dev; + int i; + + i = qemu_find_nic_model(nd, isa_nic_models, default_model); + if (i < 0) + return NULL; + + isa_dev = isa_try_create(bus, isa_nic_models[i]); + if (!isa_dev) { + return NULL; + } + dev = &isa_dev->qdev; + if (pc_prop_set_funcs[i] && !pc_prop_set_funcs[i](dev)) + return NULL; + qdev_set_nic_properties(dev, nd); + if (qdev_init(dev) < 0) + return NULL; + return isa_dev; +} + +ISADevice *isa_nic_init_nofail(ISABus *bus, NICInfo *nd, const char *default_model, + bool (* const pc_prop_set_funcs[])(DeviceState *qdev)) +{ + ISADevice *res; + + if (qemu_show_nic_models(nd->model, isa_nic_models)) + exit(0); + + res = isa_nic_init(bus, nd, default_model, pc_prop_set_funcs); + if (!res) + exit(1); + return res; +} + static int isa_qdev_init(DeviceState *qdev) { ISADevice *dev = ISA_DEVICE(qdev); diff --git a/hw/isa.h b/hw/isa.h index f7bc4b5..9b11e33 100644 --- a/hw/isa.h +++ b/hw/isa.h @@ -46,6 +46,26 @@ ISADevice *isa_create(ISABus *bus, const char *name); ISADevice *isa_try_create(ISABus *bus, const char *name); ISADevice *isa_create_simple(ISABus *bus, const char *name); +ISADevice *isa_nic_init(ISABus *bus, NICInfo *nd, const char *default_model, + bool (* const pc_prop_set_funcs[])(DeviceState *qdev)); +/** + * isa_nic_init_nofail: Initialize a new NIC on the ISA bus. + * + * Create the new NIC represented by nd or, if unspecified, the NIC named + * default_model. The array pc_prop_set_funcs contains function pointers + * allowing to set NIC properties. This array must follow the same order as the + * array isa_nic_models. + * + * @bus: ISA bus + * @nd: ISA NIC to initialize + * @default_model: default model to use (if nb is not valid) + * @pc_prop_set_funcs: array of function pointers + * + * @return: An ISA device (or NULL on failure) + */ +ISADevice *isa_nic_init_nofail(ISABus *bus, NICInfo *nd, const char *default_model, + bool (* const pc_prop_set_funcs[])(DeviceState *qdev)); + /** * isa_register_ioport: Install an I/O port region on the ISA bus. * diff --git a/hw/pc.c b/hw/pc.c index e81a06c..e9bd6d6 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -857,20 +857,76 @@ static const int ne2000_io[NE2000_NB_MAX] = { 0x300, 0x320, 0x340, 0x360, 0x280, 0x380 }; static const int ne2000_irq[NE2000_NB_MAX] = { 9, 10, 11, 3, 4, 5 }; +#define COM90C65_NB_MAX 8 +static int com90c65_io[COM90C65_NB_MAX] = { 0x2e0, 0x2f0, 0x300, 0x350, 0x380, 0x3e0, 0x260, 0x290 }; +static int com90c65_irq[COM90C65_NB_MAX] = { 5, 6, 7, 9, 10, 11, 3, 4 }; +static int com90c65_mmio[COM90C65_NB_MAX] = { 0xd0000, 0xd0800, 0xd1000, 0xd1800, 0xd4000, 0xd4800, 0xd5000, 0xd5800 }; + +#define DE100_NB_MAX 2 +static int de100_io[DE100_NB_MAX] = { 0x300, 0x200 }; +static int de100_irq[DE100_NB_MAX] = { 2, 5 }; +static int de100_mmiobase[DE100_NB_MAX] = { 0xd8000, 0xd0000 }; +static int de100_mmiosize[DE100_NB_MAX] = { 32, 32 }; + static const int parallel_io[MAX_PARALLEL_PORTS] = { 0x378, 0x278, 0x3bc }; static const int parallel_irq[MAX_PARALLEL_PORTS] = { 7, 7, 7 }; -void pc_init_ne2k_isa(ISABus *bus, NICInfo *nd) +/* ne2000.c */ +static bool pc_prop_set_ne2k_isa(DeviceState *qdev) { static int nb_ne2k = 0; if (nb_ne2k == NE2000_NB_MAX) - return; - isa_ne2000_init(bus, ne2000_io[nb_ne2k], - ne2000_irq[nb_ne2k], nd); + return false; + + qdev_prop_set_uint32(qdev, "iobase", ne2000_io[nb_ne2k]); + qdev_prop_set_uint32(qdev, "irq", ne2000_irq[nb_ne2k]); nb_ne2k++; + return true; +} + +/* com90c65-isa.c */ +static bool pc_prop_set_com90c65_isa(DeviceState *qdev) +{ + static int nb_com90c65 = 0; + + if (nb_com90c65 == COM90C65_NB_MAX) + return false; + + qdev_prop_set_uint32(qdev, "iobase", com90c65_io[nb_com90c65]); + qdev_prop_set_uint32(qdev, "irq", com90c65_irq[nb_com90c65]); + qdev_prop_set_uint32(qdev, "mmiobase", com90c65_mmio[nb_com90c65]); + nb_com90c65++; + return true; } +/* de100-isa.c */ +static bool pc_prop_set_de100_isa(DeviceState *qdev) +{ + static int nb_de100 = 0; + + if (nb_de100 == DE100_NB_MAX) + return false; + + qdev_prop_set_uint32(qdev, "iobase", de100_io[nb_de100]); + qdev_prop_set_uint32(qdev, "irq", de100_irq[nb_de100]); + qdev_prop_set_uint32(qdev, "mmiobase", de100_mmiobase[nb_de100]); + qdev_prop_set_uint8(qdev, "mmiosize", de100_mmiosize[nb_de100]); + nb_de100++; + return true; +} + +/** + * Array of functions setting ISA NICs properties. + * This array must have the same size as isa_nic_models. + */ +bool (* const pc_prop_set_isa_functions[])(DeviceState *qdev) = { + pc_prop_set_ne2k_isa, + pc_prop_set_com90c65_isa, + pc_prop_set_de100_isa, + NULL +}; + int cpu_is_bsp(CPUX86State *env) { /* We hard-wire the BSP to the first CPU. */ diff --git a/hw/pc.h b/hw/pc.h index 74d3369..521044c 100644 --- a/hw/pc.h +++ b/hw/pc.h @@ -101,6 +101,7 @@ void i8042_setup_a20_line(ISADevice *dev, qemu_irq *a20_out); /* pc.c */ extern int fd_bootchk; +extern bool (* const pc_prop_set_isa_functions[])(DeviceState *qdev); void pc_register_ferr_irq(qemu_irq irq); void pc_acpi_smi_interrupt(void *opaque, int irq, int level); @@ -120,7 +121,6 @@ void pc_basic_device_init(ISABus *isa_bus, qemu_irq *gsi, ISADevice **rtc_state, ISADevice **floppy, bool no_vmport); -void pc_init_ne2k_isa(ISABus *bus, NICInfo *nd); void pc_cmos_init(ram_addr_t ram_size, ram_addr_t above_4g_mem_size, const char *boot_device, ISADevice *floppy, BusState *ide0, BusState *ide1, @@ -197,24 +197,6 @@ int isa_vga_mm_init(target_phys_addr_t vram_base, /* cirrus_vga.c */ DeviceState *pci_cirrus_vga_init(PCIBus *bus); -/* ne2000.c */ -static inline bool isa_ne2000_init(ISABus *bus, int base, int irq, NICInfo *nd) -{ - ISADevice *dev; - - qemu_check_nic_model(nd, "ne2k_isa"); - - dev = isa_try_create(bus, "ne2k_isa"); - if (!dev) { - return false; - } - qdev_prop_set_uint32(&dev->qdev, "iobase", base); - qdev_prop_set_uint32(&dev->qdev, "irq", irq); - qdev_set_nic_properties(&dev->qdev, nd); - qdev_init_nofail(&dev->qdev); - return true; -} - /* pc_sysfw.c */ void pc_system_firmware_init(MemoryRegion *rom_memory); diff --git a/hw/pc_piix.c b/hw/pc_piix.c index a7aad4b..a4bd22f 100644 --- a/hw/pc_piix.c +++ b/hw/pc_piix.c @@ -237,9 +237,14 @@ static void pc_init1(MemoryRegion *system_memory, for(i = 0; i < nb_nics; i++) { NICInfo *nd = &nd_table[i]; - - if (!pci_enabled || (nd->model && strcmp(nd->model, "ne2k_isa") == 0)) - pc_init_ne2k_isa(isa_bus, nd); + size_t sz = 0; + + if (nd->model) + sz = strlen(nd->model); + if (!pci_enabled || + (nd->model && strcmp(nd->model + sz - 4, "_isa") == 0)) + isa_nic_init_nofail(isa_bus, nd, "ne2k_isa", + pc_prop_set_isa_functions); else pci_nic_init_nofail(nd, "e1000", NULL); } diff --git a/net.c b/net.c index 1922d8a..d6ea3d6 100644 --- a/net.c +++ b/net.c @@ -31,6 +31,7 @@ #include "net/slirp.h" #include "net/vde.h" #include "net/util.h" +#include "net/user-arcnet.h" #include "monitor.h" #include "qemu-common.h" #include "qemu_socket.h" @@ -1083,6 +1084,19 @@ static const struct { }, }, #endif /* CONFIG_NET_BRIDGE */ + [NET_CLIENT_TYPE_USER_ARCNET] = { + .type = "user-arcnet", + .init = net_init_user_arcnet, + .desc = { + NET_COMMON_PARAMS_DESC, + { + .name = "ifname", + .type = QEMU_OPT_STRING, + .help = "interface name", + }, + { /* end of list */ } + }, + }, }; int net_client_init(Monitor *mon, QemuOpts *opts, int is_netdev) @@ -1387,6 +1401,7 @@ void net_check_clients(void) case NET_CLIENT_TYPE_TAP: case NET_CLIENT_TYPE_SOCKET: case NET_CLIENT_TYPE_VDE: + case NET_CLIENT_TYPE_USER_ARCNET: has_host_dev = 1; break; default: ; diff --git a/net.h b/net.h index 64993b4..68d029b 100644 --- a/net.h +++ b/net.h @@ -38,6 +38,7 @@ typedef enum { NET_CLIENT_TYPE_VDE, NET_CLIENT_TYPE_DUMP, NET_CLIENT_TYPE_BRIDGE, + NET_CLIENT_TYPE_USER_ARCNET, NET_CLIENT_TYPE_MAX } net_client_type; diff --git a/net/user-arcnet.c b/net/user-arcnet.c new file mode 100644 index 0000000..b5c4f6d --- /dev/null +++ b/net/user-arcnet.c @@ -0,0 +1,187 @@ +/* + * QEMU ARCNet user mode + * + * This is a layer/tunnel which connects + * an emulated arcnet device and a Linux arcnet interface of the host. + * + * It communicates with the upper layer (the emulated device) by a Qemu vlan + * and with the bottom layer (the real device) by a raw socket. + * It converts packets between the real format and the Linux one. + * A send comes from the vlan to the socket. + * A receive comes from the socket to the vlan. + */ + +#include +#include +#include +#include +#include + +#include "qemu-char.h" + +#include "user-arcnet.h" +#include "arcnet.h" + +#ifdef USER_ARCNET_DEBUG +/* set it to 1 to print send/receive events */ +# define USER_ARCNET_DEBUG_RXTX 0 +#else +# define USER_ARCNET_DEBUG_RXTX 0 +#endif + +#ifdef USER_ARCNET_DEBUG +# define dprintf0(fmt, args...) fprintf(stderr, fmt, ## args) +#else +# define dprintf0(fmt, args...) do {} while (0) +#endif +#define dprintf(fmt, args...) dprintf0("arcnet: " fmt, ## args) +#define dperror(fmt, args...) fprintf(stderr, "%s: " fmt "ERROR %d: %s\n", __FUNCTION__, ## args, errno, strerror(errno)) + +typedef struct archdr ArcnetPacket; + +/* arcnet connection between the emulator and the host interface */ +typedef struct UserArcnetState { + VLANClientState nc; + int fd; + struct sockaddr address; +} UserArcnetState; + +/* send a packet from the emulator vlan to the host */ +static ssize_t net_user_arcnet_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + UserArcnetState *s = DO_UPCAST(UserArcnetState, nc, nc); + ArcnetPacket *linux_buf = (ArcnetPacket*)buf; + int data_length, length; + ssize_t sent; + + /* ignore bad packets */ + if (buf == NULL || size <= 0 || size != ARCNET_SIZE(buf)) { + return -1; + } + + /* convert from hardware to linux format (see linux/if_arcnet.h), + * move data to the beginning of the buffer */ + data_length = ARCNET_DATA_LENGTH(buf); + memmove(linux_buf->soft.raw, ARCNET_DATA(buf), data_length); + length = data_length + (linux_buf->soft.raw - (uint8_t*) linux_buf); + + if (USER_ARCNET_DEBUG_RXTX) + dprintf("send %d bytes\n", length); + + /* send raw packet to the predefined interface */ + sent = sendto(s->fd, buf, length, 0, &s->address, sizeof (s->address)); + if (sent != length) { + dperror(); + return -1; + } + return sent; +} + +/* cannot receive packet if the device is not ready to receive */ +static int net_user_arcnet_can_send(void *opaque) +{ + UserArcnetState *s = opaque; + return qemu_can_send_packet(&s->nc); +} + +/* receive a packet from the host to the emulator vlan */ +static void net_user_arcnet_send(void *opaque) +{ + UserArcnetState *s = opaque; + uint8_t buf[ARCNET_MAX_SIZE]; + ArcnetPacket *linux_buf = (ArcnetPacket*)buf; + ssize_t received; + int data_length, size; + + /* receive raw packet from the predefined interface */ + received = qemu_recv(s->fd, buf, ARCNET_MAX_SIZE, 0); + if (received <= 0) { + dperror(); + return; + } + + if (USER_ARCNET_DEBUG_RXTX) + dprintf("receive %d bytes\n", received); + + /* get data length */ + data_length = received - (linux_buf->soft.raw - (uint8_t*) linux_buf); + if (data_length <= ARCNET_SHORT_DATA_MAX_SIZE) { + size = ARCNET_SHORT_SIZE; + } else if (data_length < ARCNET_LONG_DATA_MIN_SIZE) { /* should not happen */ + size = ARCNET_LONG_SIZE; + /* data length between short and long => add padding to make a long packet */ + data_length = ARCNET_LONG_DATA_MIN_SIZE; + } else if (data_length <= ARCNET_LONG_DATA_MAX_SIZE) { + size = ARCNET_LONG_SIZE; + } else { /* message too long */ + errno = EMSGSIZE; + dperror(); + return; + } + /* write data length in the header */ + buf[ARCNET_SHORT_START] = 0; + buf[ARCNET_LONG_START] = 0; + buf[ARCNET_START(size)] = size - data_length; + /* convert from linux to hardware format (see linux/if_arcnet.h), + * move data to the end of the buffer */ + memmove(ARCNET_DATA(buf), linux_buf->soft.raw, data_length); + + /* send packet to the vlan */ + qemu_send_packet(&s->nc, buf, size); +} + +static void net_user_arcnet_cleanup(VLANClientState *nc) +{ + UserArcnetState *s = DO_UPCAST(UserArcnetState, nc, nc); + qemu_set_fd_handler(s->fd, NULL, NULL, NULL); + close(s->fd); +} + +static NetClientInfo net_user_arcnet_info = { + .type = NET_CLIENT_TYPE_USER_ARCNET, + .size = sizeof (UserArcnetState), + .receive = net_user_arcnet_receive, + .cleanup = net_user_arcnet_cleanup, +}; + +int net_init_user_arcnet(QemuOpts *opts, Monitor *mon, + const char *name, VLANState *vlan) +{ + VLANClientState *nc; + UserArcnetState *s; + const char *ifname; + + if ((ifname = qemu_opt_get(opts, "ifname")) == NULL) + ifname = "arc0"; + + dprintf("initialization (%s in vlan%d)\n", ifname, vlan->id); + + nc = qemu_new_net_client(&net_user_arcnet_info, vlan, NULL, "user-arcnet", name); + + s = DO_UPCAST(UserArcnetState, nc, nc); + + /* open a raw socket */ + s->fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARCNET)); + if (s->fd < 0) { + dperror(); + return -1; + } + /* store address of the specified interface */ + s->address.sa_family = ARPHRD_ARCNET; + strcpy(s->address.sa_data, ifname); + + /* listen the specified interface */ + if (bind(s->fd, &s->address, sizeof (s->address))) { + dperror("bind on %s: ", ifname); + return -1; + } + + /* set a handler to receive data from the ARCnet network */ + qemu_set_fd_handler2(s->fd, net_user_arcnet_can_send, + net_user_arcnet_send, NULL, s); + + snprintf(nc->info_str, sizeof (nc->info_str), "arcnet user mode"); + dprintf("vlan=%d, interface=%s, fd=%d\n", vlan->id, ifname, s->fd); + + return 0; +} diff --git a/net/user-arcnet.h b/net/user-arcnet.h new file mode 100644 index 0000000..a4a3b9c --- /dev/null +++ b/net/user-arcnet.h @@ -0,0 +1,14 @@ +/* + * QEMU ARCNet user mode + */ + +#ifndef QEMU_NET_USER_ARCNET_H +#define QEMU_NET_USER_ARCNET_H + +#include "net.h" +#include "qemu-common.h" + +int net_init_user_arcnet(QemuOpts *opts, Monitor *mon, + const char *name, VLANState *vlan); + +#endif /* QEMU_NET_USER_ARCNET_H */