--- Makefile.target +++ Makefile.target @@ -436,6 +436,12 @@ VL_OBJS += ne2000.o VL_OBJS += pcnet.o VL_OBJS += rtl8139.o +# Arcnet ISA card +ifdef CONFIG_ARCNET +VL_OBJS+=arcnet.o +VL_OBJS += com90c65.o +endif + ifeq ($(TARGET_BASE_ARCH), i386) # Hardware support VL_OBJS+= ide.o pckbd.o ps2.o vga.o $(SOUND_HW) dma.o --- /dev/null +++ arcnet.c @@ -0,0 +1,176 @@ +/* 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 "arcnet.h" + +#include "net.h" +#include "qemu-char.h" + +#include +#include +#include +#include +#include + +#ifdef DEBUG + /* set it to 1 to print send/receive events */ + #define DEBUG_RXTX 0 + /* set it to 1 to print sent/received packets */ + #define DEBUG_DUMP 0 +#else + #define DEBUG_RXTX 0 + #define DEBUG_DUMP 0 +#endif + +#ifdef DEBUG + #define dprintf0(fmt, args...) fprintf (stderr, fmt, ## args) +#else + #define dprintf0(fmt, args...) +#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 ArcnetState { + VLANClientState *vlan_client ; + int socket ; + struct sockaddr address ; +} ArcnetState ; + +/* send a packet from the emulator vlan to the host */ +static void arcnet_send (void *user_data, const uint8_t *buffer, int size) +{ + ArcnetState *s = user_data ; + ArcnetPacket *linux_buffer = (ArcnetPacket*)buffer ; + int data_length, length ; + ssize_t sent ; + + /* ignore bad packets */ + if (buffer == NULL || size <= 0 || size != ARCNET_SIZE (buffer)) { + return ; + } + + /* convert from hardware to linux format (see linux/if_arcnet.h), + * move data to the beginning of the buffer */ + data_length = ARCNET_DATA_LENGTH (buffer) ; + memmove (linux_buffer->soft.raw, ARCNET_DATA (buffer), data_length) ; + length = data_length + (linux_buffer->soft.raw - (uint8_t*)linux_buffer) ; + + if (DEBUG_RXTX) dprintf ("send %d bytes\n", length) ; + if (DEBUG_DUMP) hex_dump (stderr, buffer, length) ; + + /* send raw packet to the predefined interface */ + sent = sendto (s->socket, buffer, length, 0, &s->address, sizeof (s->address)) ; + if (sent != length) { + dperror () ; + return ; + } +} + +/* cannot receive packet if the device is not ready to receive */ +static int arcnet_can_receive (void *user_data) +{ + ArcnetState *s = user_data ; + return qemu_can_send_packet (s->vlan_client) ; +} + +/* receive a packet from the host to the emulator vlan */ +static void arcnet_receive (void *user_data) +{ + ArcnetState *s = user_data ; + uint8_t buffer[ARCNET_MAX_SIZE] ; + ArcnetPacket *linux_buffer = (ArcnetPacket*)buffer ; + ssize_t received ; + int data_length, size ; + + /* receive raw packet from the predefined interface */ + received = recv (s->socket, buffer, ARCNET_MAX_SIZE, 0) ; + if (received <= 0) { + dperror () ; + return ; + } + + if (DEBUG_RXTX) dprintf ("receive %d bytes\n", received) ; + if (DEBUG_DUMP) hex_dump (stderr, buffer, received) ; + + /* get data length */ + data_length = received - (linux_buffer->soft.raw - (uint8_t*)linux_buffer) ; + 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 */ + buffer[ARCNET_SHORT_START] = 0 ; + buffer[ARCNET_LONG_START] = 0 ; + buffer[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 (buffer), linux_buffer->soft.raw, data_length) ; + + /* send packet to the vlan */ + qemu_send_packet (s->vlan_client, buffer, size) ; +} + +int arcnet_init (VLANState *vlan, const char *ifname) +{ + int error_code ; + ArcnetState *s ; + + dprintf ("initialization (%s in vlan%d)\n", ifname, vlan->id) ; + + /* allocate the object of the layer */ + s = qemu_mallocz (sizeof(ArcnetState)) ; + if (s == NULL) { + return -1 ; + } + + /* open a raw socket */ + s->socket = socket (PF_PACKET, SOCK_PACKET, htons (ETH_P_ARCNET)) ; + if (s->socket < 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 and call arcnet_receive() on reception */ + error_code = bind (s->socket, &s->address, sizeof (s->address)) ; + if (error_code != 0) { + dperror ("bind on %s: ", ifname) ; + return -1 ; + } + error_code = qemu_set_fd_handler2 (s->socket, arcnet_can_receive, arcnet_receive, NULL, s) ; + if (error_code != 0) { + return -1 ; + } + + /* call arcnet_send() when an emulated device emits on the same vlan */ + s->vlan_client = qemu_new_vlan_client (vlan, arcnet_send, NULL, s) ; + if (s->vlan_client == NULL) { + return -1 ; + } + + snprintf (s->vlan_client->info_str, sizeof (s->vlan_client->info_str), "arcnet user mode") ; + dprintf ("vlan=%d, interface=%s, socket=%d\n", vlan->id, ifname, s->socket) ; + + return 0 ; +} --- /dev/null +++ arcnet.h @@ -0,0 +1,61 @@ +#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 ] \ +) + +/* The arcnet layer is initialized for a vlan linked with a real arcnet interface called ifname. */ +int arcnet_init (VLANState *vlan, const char *ifname) ; + +#endif /* ARCNET_H */ --- configure +++ configure @@ -84,6 +84,7 @@ mingw32="no" EXESUF="" gdbstub="yes" slirp="yes" +arcnet="no" adlib="no" oss="no" dsound="no" @@ -188,6 +189,7 @@ SunOS) oss="yes" linux="yes" linux_user="yes" +arcnet="yes" if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then kqemu="yes" fi @@ -274,6 +276,8 @@ for opt do ;; --disable-slirp) slirp="no" ;; + --disable-arcnet) arcnet="no" + ;; --enable-adlib) adlib="yes" ;; --disable-kqemu) kqemu="no" @@ -885,6 +889,10 @@ if test "$slirp" = "yes" ; then echo "CONFIG_SLIRP=yes" >> $config_mak echo "#define CONFIG_SLIRP 1" >> $config_h fi +if test "$arcnet" = "yes" ; then + echo "CONFIG_ARCNET=yes" >> $config_mak + echo "#define CONFIG_ARCNET 1" >> $config_h +fi if test "$adlib" = "yes" ; then echo "CONFIG_ADLIB=yes" >> $config_mak echo "#define CONFIG_ADLIB 1" >> $config_h --- net.h +++ net.h @@ -1,6 +1,8 @@ #ifndef QEMU_NET_H #define QEMU_NET_H +#include "cpu.h" + /* VLANs support */ typedef struct VLANClientState VLANClientState; @@ -40,6 +42,9 @@ void do_info_network(void); struct NICInfo { uint8_t macaddr[6]; + uint16_t io; + target_phys_addr_t shmem; + uint8_t irq; const char *model; VLANState *vlan; }; @@ -47,4 +52,7 @@ struct NICInfo { extern int nb_nics; extern NICInfo nd_table[MAX_NICS]; +/* tools */ +void hex_dump(FILE *f, const uint8_t *buf, int size); + #endif --- vl.c +++ vl.c @@ -106,6 +106,10 @@ int inet_aton(const char *cp, struct in_ #include "libslirp.h" #endif +#if defined(CONFIG_ARCNET) +#include "arcnet.h" +#endif + #ifdef _WIN32 #include #include @@ -3433,7 +3437,7 @@ void qemu_chr_close(CharDriverState *chr /* network device redirectors */ __attribute__ (( unused )) -static void hex_dump(FILE *f, const uint8_t *buf, int size) +void hex_dump(FILE *f, const uint8_t *buf, int size) { int len, i, j, c; @@ -3491,6 +3495,18 @@ static int parse_macaddr(uint8_t *macadd return -1; } +static int parse_node_id(uint8_t *node_id, const char *p) +{ + long int value; + char *last_char; + value = strtol(p, &last_char, 0); + if (last_char != p && value >= 0 && value < 256) { + *node_id = (uint8_t)value; + return 0; + } + return -1; +} + static int get_str_sep(char *buf, int buf_size, const char **pp, int sep) { const char *p, *p1; @@ -4690,6 +4706,7 @@ static int net_client_init(const char *s } if (!strcmp(device, "nic")) { NICInfo *nd; + int arcnet = 0; uint8_t *macaddr; if (nb_nics >= MAX_NICS) { @@ -4697,23 +4714,44 @@ static int net_client_init(const char *s return -1; } nd = &nd_table[nb_nics]; + + if (get_param_value(buf, sizeof(buf), "model", p)) { + nd->model = strdup(buf); + if (strcmp(buf, "com90c65") == 0) + arcnet = 1; + } macaddr = nd->macaddr; macaddr[0] = 0x52; macaddr[1] = 0x54; macaddr[2] = 0x00; macaddr[3] = 0x12; macaddr[4] = 0x34; - macaddr[5] = 0x56 + nb_nics; - + macaddr[5] = 0x56 + 0x24 * arcnet + nb_nics; if (get_param_value(buf, sizeof(buf), "macaddr", p)) { if (parse_macaddr(macaddr, buf) < 0) { fprintf(stderr, "invalid syntax for ethernet address\n"); return -1; } } - if (get_param_value(buf, sizeof(buf), "model", p)) { - nd->model = strdup(buf); + if (get_param_value(buf, sizeof(buf), "node-id", p)) { + if (parse_node_id(&macaddr[5], buf) < 0) { + fprintf(stderr, "invalid syntax for arcnet id\n"); + return -1; + } } + if (get_param_value(buf, sizeof(buf), "io", p)) + nd->io = strtoul(buf, NULL, 0); + else + nd->io = 0 ; + if (get_param_value(buf, sizeof(buf), "irq", p)) + nd->irq = strtoul(buf, NULL, 0); + else + nd->irq = 0 ; + if (get_param_value(buf, sizeof(buf), "shmem", p)) + nd->shmem = strtoul(buf, NULL, 0); + else + nd->shmem = 0 ; + nd->vlan = vlan; nb_nics++; vlan->nb_guest_devs++; @@ -4733,6 +4771,15 @@ static int net_client_init(const char *s ret = net_slirp_init(vlan); } else #endif +#ifdef CONFIG_ARCNET + if (!strcmp(device, "user-arcnet")) { + if (!get_param_value(buf, sizeof(buf), "ifname", p)) { + pstrcpy(buf, sizeof(buf), "arc0"); + } + vlan->nb_host_devs++; + ret = arcnet_init(vlan, buf); + } else +#endif #ifdef _WIN32 if (!strcmp(device, "tap")) { char ifname[64]; @@ -7544,13 +7591,19 @@ static void help(int exitcode) "-name string set the name of the guest\n" "\n" "Network options:\n" - "-net nic[,vlan=n][,macaddr=addr][,model=type]\n" + "-net nic[,vlan=n][,macaddr=addr][,node-id=id][,model=type][,io=x][,irq=y][,shmem=z]\n" " create a new Network Interface Card and connect it to VLAN 'n'\n" + " macaddr is specific to ethernet devices\n" + " node-id is specific to arcnet devices\n" #ifdef CONFIG_SLIRP "-net user[,vlan=n][,hostname=host]\n" " connect the user mode network stack to VLAN 'n' and send\n" " hostname 'host' to DHCP clients\n" #endif +#ifdef CONFIG_ARCNET + "-net user-arcnet[,vlan=n][,ifname=name]\n" + " connect the user mode arcnet stack to VLAN 'n'\n" +#endif #ifdef _WIN32 "-net tap[,vlan=n],ifname=name\n" " connect the host TAP network interface to VLAN 'n'\n" --- /dev/null +++ hw/com90c65.c @@ -0,0 +1,725 @@ +/* This is a hardware NIC (Network Interface Card) + * emulation of the COM90C66 device family. + * + * It works on an (emulated) ISA bus. + * 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 "arcnet.h" + +/* name of the device (debug usage) */ +#define DEVICE "com90c65" +/* version of the saved state file */ +#define VERSION 0 + +#ifdef 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, DEVICE ": " fmt, ## args) +#define not_implemented0(fmt, args...) eprintf (fmt ": not implemented\n", ## args) +#ifdef 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 (DEVICE ": " fmt, ## args) +#define BOOL_TEXT(state) (state ? "enable" : "disable") + +/* default I/O addresses */ +#define IO_BASES_MAX 8 +static int io_bases[IO_BASES_MAX] = {0x260, 0x290, 0x2e0, 0x2f0, 0x300, 0x350, 0x380, 0x3e0} ; + +/* 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 */ + target_phys_addr_t mm_base ; + 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 ; + VLANClientState *vlan_client ; + +} COM90C65State ; + +/* map the register variables with 2 arrays for read and write access */ +static 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 ; +} + +/* save current state of the device in a file */ +static void com90c65_save (QEMUFile *file, void *user_data) { + COM90C65State *s = user_data ; + + dprintf ("save VM\n") ; + qemu_put_8s (file, &s->status) ; + qemu_put_8s (file, &s->irq_mask) ; + qemu_put_8s (file, &s->diag_status) ; + qemu_put_8s (file, &s->config) ; + qemu_put_8s (file, &s->io_select) ; + qemu_put_8s (file, &s->mem_select) ; + qemu_put_8s (file, &s->node_id) ; + qemu_put_be16s (file, &s->data) ; + qemu_put_be16s (file, &s->address) ; + qemu_put_buffer (file, s->mem, MEM_SIZE) ; + qemu_put_8s (file, &s->long_packet_enabled) ; + qemu_put_8s (file, &s->broadcast_enabled) ; + qemu_put_8s (file, &s->reception_page) ; +} + +/* load the device state from a file in a backward compatible way */ +static int com90c65_load (QEMUFile *file, void *user_data, int version) { + COM90C65State *s = user_data ; + + if (version > VERSION) + return -EINVAL ; + + dprintf ("load VM\n") ; + qemu_get_8s (file, &s->status) ; + qemu_get_8s (file, &s->irq_mask) ; + qemu_get_8s (file, &s->diag_status) ; + qemu_get_8s (file, &s->config) ; + qemu_get_8s (file, &s->io_select) ; + qemu_get_8s (file, &s->mem_select) ; + qemu_get_8s (file, &s->node_id) ; + qemu_get_be16s (file, &s->data) ; + qemu_get_be16s (file, &s->address) ; + qemu_get_buffer (file, s->mem, MEM_SIZE) ; + qemu_get_8s (file, &s->long_packet_enabled) ; + qemu_get_8s (file, &s->broadcast_enabled) ; + qemu_get_8s (file, &s->reception_page) ; + + return 0 ; +} + +/* 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") ; +} + +/* 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 ; +} + +/* 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 */ +static 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) ; + if (DEBUG_DUMP) hex_dump (stderr, buffer, size) ; + + /* send to other clients of the vlan, + * it should be received by an arcnet layer */ + qemu_send_packet (s->vlan_client, buffer, size) ; +} + +/* receive a packet from the vlan to the specified memory page */ +static void com90c65_receive (void *user_data, const uint8_t *buffer, int size) { + COM90C65State *s = user_data ; + + if (buffer == NULL || size <= 0) + return ; + + /* 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 ; + } + + if (DEBUG_RXTX) dprintf ("receive %d bytes\n", size) ; + if (DEBUG_DUMP) hex_dump (stderr, buffer, 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) ; +} + +/* the arcnet layer send a packet to the vlan only when the device is ready to receive */ +static int com90c65_can_receive (void *user_data) { + COM90C65State *s = user_data ; + 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 uint32_t com90c65_mm_read (void *user_data, target_phys_addr_t address, int size) { + COM90C65State *s = user_data ; + uint32_t value = 0 ; + int offset ; + for (offset = 0 ; offset < size ; offset ++) + value |= s->mem[address - s->mm_base + offset] << (offset << 3) ; + if (DEBUG_MM_RW) dprintf ("read %d bytes from memory 0x%x (0x%x)\n", size, address, value) ; + return value ; +} + +static uint32_t com90c65_mm_read8 (void *user_data, target_phys_addr_t address) { + return com90c65_mm_read (user_data, address, 1) ; +} + +static uint32_t com90c65_mm_read16 (void *user_data, target_phys_addr_t address) { + return com90c65_mm_read (user_data, address, 2) ; +} + +static uint32_t com90c65_mm_read32 (void *user_data, target_phys_addr_t address) { + return com90c65_mm_read (user_data, address, 4) ; +} + +/* memory read can be 8, 16 or 32 bits wide */ +static CPUReadMemoryFunc *com90c65_mm_reads[] = { + &com90c65_mm_read8, + &com90c65_mm_read16, + &com90c65_mm_read32, +} ; + +/* write to memory mapped */ +static void com90c65_mm_write (void *user_data, target_phys_addr_t address, uint32_t value, int size) { + COM90C65State *s = user_data ; + int offset ; + if (DEBUG_MM_RW) dprintf ("write %d bytes to memory: 0x%x (0x%x)\n", size, address, value) ; + for (offset = 0 ; offset < size ; offset ++) + s->mem[address - s->mm_base + offset] = value >> (offset << 3) & 0xff ; +} + +static void com90c65_mm_write8 (void *user_data, target_phys_addr_t address, uint32_t value) { + com90c65_mm_write (user_data, address, value, 1) ; +} + +static void com90c65_mm_write16 (void *user_data, target_phys_addr_t address, uint32_t value) { + com90c65_mm_write (user_data, address, value, 2) ; +} + +static void com90c65_mm_write32 (void *user_data, target_phys_addr_t address, uint32_t value) { + com90c65_mm_write (user_data, address, value, 4) ; +} + +/* memory write can be 8, 16 or 32 bits wide */ +static CPUWriteMemoryFunc *com90c65_mm_writes[] = { + &com90c65_mm_write8, + &com90c65_mm_write16, + &com90c65_mm_write32, +} ; + +/* main function: initialization, + * command line parameters are stored in nic_info */ +void isa_com90c65_init (NICInfo *nic_info, qemu_irq irq) { + COM90C65State *s ; + int io_memory ; + int error_code ; + + dprintf ("initialization (" DEVICE " in vlan%d)\n", nic_info->vlan->id) ; + + /* instantiate the object and initialize it to 0 */ + s = qemu_mallocz (sizeof(COM90C65State)) ; + if (s == NULL) + return ; + + /* initialize IRQ and registers */ + s->irq = irq ; + error_code = com90c65_set_io_select (s, nic_info->io) ; + if (error_code != 0) { + eprintf ("bad I/O address: %X\n", nic_info->io) ; + return ; + } + s->mem_select = 0 ; /* not used */ + s->node_id = nic_info->macaddr[5] ; + com90c65_reset (s) ; + + if (nic_info->vlan != NULL) { + /* register function which receive packets from the vlan (sent by the arcnet bottom layer) */ + s->vlan_client = qemu_new_vlan_client (nic_info->vlan, com90c65_receive, com90c65_can_receive, s) ; + if (s->vlan_client == NULL) + return ; + snprintf (s->vlan_client->info_str, sizeof (s->vlan_client->info_str), DEVICE " node_id=%02x", s->node_id) ; + dprintf ("vlan=%d, " DEVICE " io_id=%d\n", nic_info->vlan->id, s->io_select) ; + dprintf ("device info: %s, io=0x%03x, shmem=0x%05x, irq=%d\n", s->vlan_client->info_str, nic_info->io, nic_info->shmem, nic_info->irq) ; + } + + /* initialize read/write accesses to the registers for easy implementation of the read/write functions */ + com90c65_registers_access_init (s) ; + /* register 8-bit accesses for all the range of I/O addresses */ + register_ioport_read (nic_info->io, REGISTERS_SIZE, 1, com90c65_ioport_read8, s) ; + register_ioport_write (nic_info->io, REGISTERS_SIZE, 1, com90c65_ioport_write8, s) ; + /* register 16-bit access for data register wich need a special handling, + * other 16-bits acesses are simulated by successives 8-bit accesses by default */ + register_ioport_read (nic_info->io + OFFSET_DATA_LOW, 1, 2, com90c65_ioport_read16, s) ; + register_ioport_write (nic_info->io + OFFSET_DATA_LOW, 1, 2, com90c65_ioport_write16, s) ; + + /* register reset by overwriting a range of I/O addresses which trigger a soft reset */ + register_ioport_read (nic_info->io + OFFSET_RESET_BEGIN, + OFFSET_RESET_END - OFFSET_RESET_BEGIN + 1, 1, com90c65_reset_on_read, s) ; + register_ioport_write (nic_info->io + OFFSET_RESET_BEGIN, + OFFSET_RESET_END - OFFSET_RESET_BEGIN + 1, 1, com90c65_reset_on_write, s) ; + /* register a hard reset used by qemu */ + qemu_register_reset (com90c65_reset, s) ; + + /* reserve a physical memory range and map it to the internal memory */ + s->mm_base = nic_info->shmem ; + io_memory = cpu_register_io_memory (0, com90c65_mm_reads, com90c65_mm_writes, s) ; + cpu_register_physical_memory (s->mm_base, MEM_SIZE, io_memory) ; + + /* register functions to suspend/resume the current state */ + register_savevm (DEVICE, nic_info->io, VERSION, com90c65_save, com90c65_load, s) ; +} --- hw/pc.c +++ hw/pc.c @@ -648,6 +648,11 @@ static const int ide_irq[2] = { 14, 15 } static int ne2000_io[NE2000_NB_MAX] = { 0x300, 0x320, 0x340, 0x360, 0x280, 0x380 }; static 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_shmem[COM90C65_NB_MAX] = { 0xd0000, 0xd0800, 0xd1000, 0xd1800, 0xd4000, 0xd4800, 0xd5000, 0xd5800 }; + static int serial_io[MAX_SERIAL_PORTS] = { 0x3f8, 0x2f8, 0x3e8, 0x2e8 }; static int serial_irq[MAX_SERIAL_PORTS] = { 4, 3, 4, 3 }; @@ -696,6 +701,22 @@ static void pc_init_ne2k_isa(NICInfo *nd nb_ne2k++; } +static void pc_init_com90c65_isa(NICInfo *nd, qemu_irq *pic) +{ + static int nb_com90c65 = 0; + + if (nb_com90c65 == COM90C65_NB_MAX) + return; + if (nd->io == 0) + nd->io = com90c65_io[nb_com90c65]; + if (nd->irq == 0) + nd->irq = com90c65_irq[nb_com90c65]; + if (nd->shmem == 0) + nd->shmem = com90c65_shmem[nb_com90c65]; + isa_com90c65_init(nd, pic[nd->irq]); + nb_com90c65++; +} + /* PC hardware initialisation */ static void pc_init1(int ram_size, int vga_ram_size, const char *boot_device, DisplayState *ds, @@ -920,6 +941,8 @@ static void pc_init1(int ram_size, int v } if (strcmp(nd->model, "ne2k_isa") == 0) { pc_init_ne2k_isa(nd, i8259); + } else if (strcmp(nd->model, "com90c65") == 0) { + pc_init_com90c65_isa(nd, i8259); } else if (pci_enabled) { if (strcmp(nd->model, "?") == 0) fprintf(stderr, "qemu: Supported ISA NICs: ne2k_isa\n"); --- hw/pc.h +++ hw/pc.h @@ -142,4 +142,8 @@ void pci_piix4_ide_init(PCIBus *bus, Blo void isa_ne2000_init(int base, qemu_irq irq, NICInfo *nd); +/* com90c65.c */ + +void isa_com90c65_init(NICInfo *nd, qemu_irq irq); + #endif