/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Implementation of the following network controllers: * - SMC/WD 8003E (ISA 8-bit); * - SMC/WD 8013EBT (ISA 16-bit); * - SMC/WD 8013EP/A (MCA). * * Version: @(#)net_wd8003.c 1.0.1 2018/10/02 * * Authors: Fred N. van Kempen, * TheCollector1995, * Miran Grca, * Peter Grehan, * * Copyright 2017,2018 Fred N. van Kempen. * Copyright 2016-2018 Miran Grca. * Portions Copyright (C) 2002 MandrakeSoft S.A. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the: * * Free Software Foundation, Inc. * 59 Temple Place - Suite 330 * Boston, MA 02111-1307 * USA. */ #include #include #include #include #include #include #include #define HAVE_STDARG_H #include "../86box.h" #include "../io.h" #include "../mem.h" #include "../rom.h" #include "../mca.h" #include "../pci.h" #include "../pic.h" #include "../random.h" #include "../device.h" #include "../ui.h" #include "network.h" #include "net_dp8390.h" #include "net_wd8003.h" #include "bswap.h" /* Board type codes in card ID */ #define WE_TYPE_WD8003 0x01 #define WE_TYPE_WD8003S 0x02 #define WE_TYPE_WD8003E 0x03 #define WE_TYPE_WD8013EBT 0x05 #define WE_TYPE_TOSHIBA1 0x11 /* named PCETA1 */ #define WE_TYPE_TOSHIBA2 0x12 /* named PCETA2 */ #define WE_TYPE_TOSHIBA3 0x13 /* named PCETB */ #define WE_TYPE_TOSHIBA4 0x14 /* named PCETC */ #define WE_TYPE_WD8003W 0x24 #define WE_TYPE_WD8003EB 0x25 #define WE_TYPE_WD8013W 0x26 #define WE_TYPE_WD8013EP 0x27 #define WE_TYPE_WD8013WC 0x28 #define WE_TYPE_WD8013EPC 0x29 #define WE_TYPE_SMC8216T 0x2a #define WE_TYPE_SMC8216C 0x2b #define WE_TYPE_WD8013EBP 0x2c typedef struct { dp8390_t dp8390; mem_mapping_t ram_mapping; uint32_t ram_addr, ram_size; uint8_t macaddr[32]; /* ASIC ROM'd MAC address, even bytes */ uint8_t maclocal[6]; /* configured MAC (local) address */ int board; const char *name; uint32_t base_address; int base_irq; /* POS registers, MCA boards only */ uint8_t pos_regs[8]; /* Memory for WD cards*/ uint8_t reg1; uint8_t reg5; uint8_t if_chip; uint8_t board_chip; } wd_t; static void wd_rx(void *, uint8_t *, int); static void wd_tx(wd_t *, uint32_t); #ifdef ENABLE_WD_LOG int wd_do_log = ENABLE_WD_LOG; #endif static void wdlog(int lvl, const char *fmt, ...) { #ifdef ENABLE_WD_LOG va_list ap; if (wd_do_log >= lvl) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } #endif } static void wd_interrupt(wd_t *dev, int set) { if (set) picint(1<base_irq); else picintc(1<base_irq); } /* reset - restore state to power-up, cancelling all i/o */ static void wd_reset(void *priv) { wd_t *dev = (wd_t *)priv; wdlog(1, "%s: reset\n", dev->name); /* Initialize the MAC address area by doubling the physical address */ dev->macaddr[0] = dev->dp8390.physaddr[0]; dev->macaddr[1] = dev->dp8390.physaddr[1]; dev->macaddr[2] = dev->dp8390.physaddr[2]; dev->macaddr[3] = dev->dp8390.physaddr[3]; dev->macaddr[4] = dev->dp8390.physaddr[4]; dev->macaddr[5] = dev->dp8390.physaddr[5]; /* Zero out registers and memory */ memset(&dev->dp8390.CR, 0x00, sizeof(dev->dp8390.CR) ); memset(&dev->dp8390.ISR, 0x00, sizeof(dev->dp8390.ISR)); memset(&dev->dp8390.IMR, 0x00, sizeof(dev->dp8390.IMR)); memset(&dev->dp8390.DCR, 0x00, sizeof(dev->dp8390.DCR)); memset(&dev->dp8390.TCR, 0x00, sizeof(dev->dp8390.TCR)); memset(&dev->dp8390.TSR, 0x00, sizeof(dev->dp8390.TSR)); memset(&dev->dp8390.RSR, 0x00, sizeof(dev->dp8390.RSR)); dev->dp8390.tx_timer_active = 0; dev->dp8390.local_dma = 0; dev->dp8390.page_start = 0; dev->dp8390.page_stop = 0; dev->dp8390.bound_ptr = 0; dev->dp8390.tx_page_start = 0; dev->dp8390.num_coll = 0; dev->dp8390.tx_bytes = 0; dev->dp8390.fifo = 0; dev->dp8390.remote_dma = 0; dev->dp8390.remote_start = 0; dev->dp8390.remote_bytes = 0; dev->dp8390.tallycnt_0 = 0; dev->dp8390.tallycnt_1 = 0; dev->dp8390.tallycnt_2 = 0; dev->dp8390.curr_page = 0; dev->dp8390.rempkt_ptr = 0; dev->dp8390.localpkt_ptr = 0; dev->dp8390.address_cnt = 0; memset(&dev->dp8390.mem, 0x00, sizeof(dev->dp8390.mem)); /* Set power-up conditions */ dev->dp8390.CR.stop = 1; dev->dp8390.CR.rdma_cmd = 4; dev->dp8390.ISR.reset = 1; dev->dp8390.DCR.longaddr = 1; wd_interrupt(dev, 0); } static uint32_t wd_chipmem_read(wd_t *dev, uint32_t addr, unsigned int len) { uint32_t retval = 0; if ((len == 2) && (addr & 0x1)) { wdlog(3, "%s: unaligned chipmem word read\n", dev->name); } /* ROM'd MAC address */ if (dev->board == WD8003E) { if (addr <= 15) { retval = dev->macaddr[addr % 16]; if (len == 2) { retval |= (dev->macaddr[(addr + 1) % 16] << 8); } return(retval); } } return(0xff); } static uint32_t wd_ram_read(uint32_t addr, unsigned len, void *priv) { wd_t *dev = (wd_t *)priv; uint32_t ret; if ((addr & 0x3fff) >= 0x2000) { if (len == 2) return 0xffff; else if (len == 1) return 0xff; else return 0xffffffff; } ret = dev->dp8390.mem[addr & 0x1fff]; if (len == 2 || len == 4) ret |= dev->dp8390.mem[(addr+1) & 0x1fff] << 8; if (len == 4) { ret |= dev->dp8390.mem[(addr+2) & 0x1fff] << 16; ret |= dev->dp8390.mem[(addr+3) & 0x1fff] << 24; } return ret; } static uint8_t wd_ram_readb(uint32_t addr, void *priv) { wd_t *dev = (wd_t *)priv; return wd_ram_read(addr, 1, dev); } static uint16_t wd_ram_readw(uint32_t addr, void *priv) { wd_t *dev = (wd_t *)priv; return wd_ram_read(addr, 2, dev); } static uint32_t wd_ram_readl(uint32_t addr, void *priv) { wd_t *dev = (wd_t *)priv; return wd_ram_read(addr, 4, dev); } static void wd_ram_write(uint32_t addr, uint32_t val, unsigned len, void *priv) { wd_t *dev = (wd_t *)priv; if ((addr & 0x3fff) >= 0x2000) return; dev->dp8390.mem[addr & 0x1fff] = val & 0xff; if (len == 2 || len == 4) dev->dp8390.mem[(addr+1) & 0x1fff] = val >> 8; if (len == 4) { dev->dp8390.mem[(addr+2) & 0x1fff] = val >> 16; dev->dp8390.mem[(addr+3) & 0x1fff] = val >> 24; } } static void wd_ram_writeb(uint32_t addr, uint8_t val, void *priv) { wd_t *dev = (wd_t *)priv; wd_ram_write(addr, val, 1, dev); } static void wd_ram_writew(uint32_t addr, uint16_t val, void *priv) { wd_t *dev = (wd_t *)priv; wd_ram_write(addr, val, 2, dev); } static void wd_ram_writel(uint32_t addr, uint32_t val, void *priv) { wd_t *dev = (wd_t *)priv; wd_ram_write(addr, val, 4, dev); } static uint32_t wd_smc_read(wd_t *dev, uint32_t off) { uint32_t retval = 0; uint32_t checksum = 0; switch(off) { case 0x00: break; case 0x01: if (dev->board == WD8003E) retval = dev->dp8390.physaddr[0]; if (dev->board == WD8013EPA) retval = dev->reg1; break; case 0x02: if (dev->board == WD8003E) retval = dev->dp8390.physaddr[1]; break; case 0x03: if (dev->board == WD8003E) retval = dev->dp8390.physaddr[2]; break; case 0x04: if (dev->board == WD8003E) retval = dev->dp8390.physaddr[3]; break; case 0x05: if (dev->board == WD8003E) retval = dev->dp8390.physaddr[4]; if (dev->board == WD8013EPA) retval = dev->reg5; break; case 0x06: if (dev->board == WD8003E) retval = dev->dp8390.physaddr[5]; break; case 0x07: if (dev->board == WD8013EPA) { if (dev->if_chip != 0x35 && dev->if_chip != 0x3A) { retval = 0; break; } retval = dev->if_chip; } break; case 0x08: retval = dev->dp8390.physaddr[0]; break; case 0x09: retval = dev->dp8390.physaddr[1]; break; case 0x0a: retval = dev->dp8390.physaddr[2]; break; case 0x0b: retval = dev->dp8390.physaddr[3]; break; case 0x0c: retval = dev->dp8390.physaddr[4]; break; case 0x0d: retval = dev->dp8390.physaddr[5]; break; case 0x0e: retval = dev->board_chip; break; case 0x0f: { /*This has to return the byte that adds up to 0xFF*/ checksum = (dev->dp8390.physaddr[0] + dev->dp8390.physaddr[1] + dev->dp8390.physaddr[2] + dev->dp8390.physaddr[3] + dev->dp8390.physaddr[4] + dev->dp8390.physaddr[5] + dev->board_chip); retval = 0xff - (checksum & 0xff); } break; } wdlog(2, "%s: ASIC read addr=0x%02x, value=0x%04x\n", dev->name, (unsigned)off, (unsigned) retval); return(retval); } static void wd_smc_write(wd_t *dev, uint32_t off, uint32_t val) { wdlog(2, "%s: ASIC write addr=0x%02x, value=0x%04x\n", dev->name, (unsigned)off, (unsigned) val); switch(off) { case 0x00: /* WD Control register */ if (val & 0x80) { dev->dp8390.ISR.reset = 1; return; } mem_mapping_disable(&dev->ram_mapping); if (val & 0x40) { mem_mapping_enable(&dev->ram_mapping); } break; case 0x01: dev->reg1 = val; break; case 0x04: break; case 0x05: dev->reg5 = val; break; case 0x06: break; case 0x07: dev->if_chip = val; break; default: /* this is invalid, but happens under win95 device detection */ wdlog(3, "%s: ASIC write invalid address %04x, ignoring\n", dev->name, (unsigned)off); break; } } /* Handle reads/writes to the 'zeroth' page of the DS8390 register file. */ static uint8_t page0_read(wd_t *dev, uint32_t off) { uint8_t retval = 0; switch(off) { case 0x01: /* CLDA0 */ retval = (dev->dp8390.local_dma & 0xff); break; case 0x02: /* CLDA1 */ retval = (dev->dp8390.local_dma >> 8); break; case 0x03: /* BNRY */ retval = dev->dp8390.bound_ptr; break; case 0x04: /* TSR */ retval = ((dev->dp8390.TSR.ow_coll << 7) | (dev->dp8390.TSR.cd_hbeat << 6) | (dev->dp8390.TSR.fifo_ur << 5) | (dev->dp8390.TSR.no_carrier << 4) | (dev->dp8390.TSR.aborted << 3) | (dev->dp8390.TSR.collided << 2) | (dev->dp8390.TSR.tx_ok)); break; case 0x05: /* NCR */ retval = dev->dp8390.num_coll; break; case 0x06: /* FIFO */ /* reading FIFO is only valid in loopback mode */ wdlog(3, "%s: reading FIFO not supported yet\n", dev->name); retval = dev->dp8390.fifo; break; case 0x07: /* ISR */ retval = ((dev->dp8390.ISR.reset << 7) | (dev->dp8390.ISR.rdma_done << 6) | (dev->dp8390.ISR.cnt_oflow << 5) | (dev->dp8390.ISR.overwrite << 4) | (dev->dp8390.ISR.tx_err << 3) | (dev->dp8390.ISR.rx_err << 2) | (dev->dp8390.ISR.pkt_tx << 1) | (dev->dp8390.ISR.pkt_rx)); break; case 0x08: /* CRDA0 */ retval = (dev->dp8390.remote_dma & 0xff); break; case 0x09: /* CRDA1 */ retval = (dev->dp8390.remote_dma >> 8); break; case 0x0a: /* reserved / RTL8029ID0 */ wdlog(3, "%s: reserved Page0 read - 0x0a\n", dev->name); retval = 0xff; break; case 0x0b: /* reserved / RTL8029ID1 */ wdlog(3, "%s: reserved Page0 read - 0x0b\n", dev->name); retval = 0xff; break; case 0x0c: /* RSR */ retval = ((dev->dp8390.RSR.deferred << 7) | (dev->dp8390.RSR.rx_disabled << 6) | (dev->dp8390.RSR.rx_mbit << 5) | (dev->dp8390.RSR.rx_missed << 4) | (dev->dp8390.RSR.fifo_or << 3) | (dev->dp8390.RSR.bad_falign << 2) | (dev->dp8390.RSR.bad_crc << 1) | (dev->dp8390.RSR.rx_ok)); break; case 0x0d: /* CNTR0 */ retval = dev->dp8390.tallycnt_0; break; case 0x0e: /* CNTR1 */ retval = dev->dp8390.tallycnt_1; break; case 0x0f: /* CNTR2 */ retval = dev->dp8390.tallycnt_2; break; default: wdlog(3, "%s: Page0 register 0x%02x out of range\n", dev->name, off); break; } wdlog(3, "%s: Page0 read from register 0x%02x, value=0x%02x\n", dev->name, off, retval); return(retval); } static void page0_write(wd_t *dev, uint32_t off, uint8_t val) { uint8_t val2; wdlog(3, "%s: Page0 write to register 0x%02x, value=0x%02x\n", dev->name, off, val); switch(off) { case 0x01: /* PSTART */ dev->dp8390.page_start = val; break; case 0x02: /* PSTOP */ dev->dp8390.page_stop = val; break; case 0x03: /* BNRY */ dev->dp8390.bound_ptr = val; break; case 0x04: /* TPSR */ dev->dp8390.tx_page_start = val; break; case 0x05: /* TBCR0 */ /* Clear out low byte and re-insert */ dev->dp8390.tx_bytes &= 0xff00; dev->dp8390.tx_bytes |= (val & 0xff); break; case 0x06: /* TBCR1 */ /* Clear out high byte and re-insert */ dev->dp8390.tx_bytes &= 0x00ff; dev->dp8390.tx_bytes |= ((val & 0xff) << 8); break; case 0x07: /* ISR */ val &= 0x7f; /* clear RST bit - status-only bit */ /* All other values are cleared iff the ISR bit is 1 */ dev->dp8390.ISR.pkt_rx &= !((int)((val & 0x01) == 0x01)); dev->dp8390.ISR.pkt_tx &= !((int)((val & 0x02) == 0x02)); dev->dp8390.ISR.rx_err &= !((int)((val & 0x04) == 0x04)); dev->dp8390.ISR.tx_err &= !((int)((val & 0x08) == 0x08)); dev->dp8390.ISR.overwrite &= !((int)((val & 0x10) == 0x10)); dev->dp8390.ISR.cnt_oflow &= !((int)((val & 0x20) == 0x20)); dev->dp8390.ISR.rdma_done &= !((int)((val & 0x40) == 0x40)); val = ((dev->dp8390.ISR.rdma_done << 6) | (dev->dp8390.ISR.cnt_oflow << 5) | (dev->dp8390.ISR.overwrite << 4) | (dev->dp8390.ISR.tx_err << 3) | (dev->dp8390.ISR.rx_err << 2) | (dev->dp8390.ISR.pkt_tx << 1) | (dev->dp8390.ISR.pkt_rx)); val &= ((dev->dp8390.IMR.rdma_inte << 6) | (dev->dp8390.IMR.cofl_inte << 5) | (dev->dp8390.IMR.overw_inte << 4) | (dev->dp8390.IMR.txerr_inte << 3) | (dev->dp8390.IMR.rxerr_inte << 2) | (dev->dp8390.IMR.tx_inte << 1) | (dev->dp8390.IMR.rx_inte)); if (val == 0x00) wd_interrupt(dev, 0); break; case 0x08: /* RSAR0 */ /* Clear out low byte and re-insert */ dev->dp8390.remote_start &= 0xff00; dev->dp8390.remote_start |= (val & 0xff); dev->dp8390.remote_dma = dev->dp8390.remote_start; break; case 0x09: /* RSAR1 */ /* Clear out high byte and re-insert */ dev->dp8390.remote_start &= 0x00ff; dev->dp8390.remote_start |= ((val & 0xff) << 8); dev->dp8390.remote_dma = dev->dp8390.remote_start; break; case 0x0a: /* RBCR0 */ /* Clear out low byte and re-insert */ dev->dp8390.remote_bytes &= 0xff00; dev->dp8390.remote_bytes |= (val & 0xff); break; case 0x0b: /* RBCR1 */ /* Clear out high byte and re-insert */ dev->dp8390.remote_bytes &= 0x00ff; dev->dp8390.remote_bytes |= ((val & 0xff) << 8); break; case 0x0c: /* RCR */ /* Check if the reserved bits are set */ if (val & 0xc0) { wdlog(3, "%s: RCR write, reserved bits set\n", dev->name); } /* Set all other bit-fields */ dev->dp8390.RCR.errors_ok = ((val & 0x01) == 0x01); dev->dp8390.RCR.runts_ok = ((val & 0x02) == 0x02); dev->dp8390.RCR.broadcast = ((val & 0x04) == 0x04); dev->dp8390.RCR.multicast = ((val & 0x08) == 0x08); dev->dp8390.RCR.promisc = ((val & 0x10) == 0x10); dev->dp8390.RCR.monitor = ((val & 0x20) == 0x20); /* Monitor bit is a little suspicious... */ if (val & 0x20) wdlog(3, "%s: RCR write, monitor bit set!\n", dev->name); break; case 0x0d: /* TCR */ /* Check reserved bits */ if (val & 0xe0) wdlog(3, "%s: TCR write, reserved bits set\n", dev->name); /* Test loop mode (not supported) */ if (val & 0x06) { dev->dp8390.TCR.loop_cntl = (val & 0x6) >> 1; wdlog(3, "%s: TCR write, loop mode %d not supported\n", dev->name, dev->dp8390.TCR.loop_cntl); } else { dev->dp8390.TCR.loop_cntl = 0; } /* Inhibit-CRC not supported. */ if (val & 0x01) wdlog(3, "%s: TCR write, inhibit-CRC not supported\n",dev->name); /* Auto-transmit disable very suspicious */ if (val & 0x08) { wdlog(3, "%s: TCR write, auto transmit disable not supported\n", dev->name); } /* Allow collision-offset to be set, although not used */ dev->dp8390.TCR.coll_prio = ((val & 0x08) == 0x08); break; case 0x0e: /* DCR */ /* the loopback mode is not suppported yet */ if (! (val & 0x08)) wdlog(3, "%s: DCR write, loopback mode selected\n", dev->name); /* It is questionable to set longaddr and auto_rx, since * they are not supported on the NE2000. Print a warning * and continue. */ if (val & 0x04) wdlog(3, "%s: DCR write - LAS set ???\n", dev->name); if (val & 0x10) wdlog(3, "%s: DCR write - AR set ???\n", dev->name); /* Set other values. */ dev->dp8390.DCR.wdsize = ((val & 0x01) == 0x01); dev->dp8390.DCR.endian = ((val & 0x02) == 0x02); dev->dp8390.DCR.longaddr = ((val & 0x04) == 0x04); /* illegal ? */ dev->dp8390.DCR.loop = ((val & 0x08) == 0x08); dev->dp8390.DCR.auto_rx = ((val & 0x10) == 0x10); /* also illegal ? */ dev->dp8390.DCR.fifo_size = (val & 0x50) >> 5; break; case 0x0f: /* IMR */ /* Check for reserved bit */ if (val & 0x80) wdlog(3, "%s: IMR write, reserved bit set\n",dev->name); /* Set other values */ dev->dp8390.IMR.rx_inte = ((val & 0x01) == 0x01); dev->dp8390.IMR.tx_inte = ((val & 0x02) == 0x02); dev->dp8390.IMR.rxerr_inte = ((val & 0x04) == 0x04); dev->dp8390.IMR.txerr_inte = ((val & 0x08) == 0x08); dev->dp8390.IMR.overw_inte = ((val & 0x10) == 0x10); dev->dp8390.IMR.cofl_inte = ((val & 0x20) == 0x20); dev->dp8390.IMR.rdma_inte = ((val & 0x40) == 0x40); val2 = ((dev->dp8390.ISR.rdma_done << 6) | (dev->dp8390.ISR.cnt_oflow << 5) | (dev->dp8390.ISR.overwrite << 4) | (dev->dp8390.ISR.tx_err << 3) | (dev->dp8390.ISR.rx_err << 2) | (dev->dp8390.ISR.pkt_tx << 1) | (dev->dp8390.ISR.pkt_rx)); if (((val & val2) & 0x7f) == 0) wd_interrupt(dev, 0); else wd_interrupt(dev, 1); break; default: wdlog(3, "%s: Page0 write, bad register 0x%02x\n", dev->name, off); break; } } /* Handle reads/writes to the first page of the DS8390 register file. */ static uint8_t page1_read(wd_t *dev, uint32_t off) { wdlog(3, "%s: Page1 read from register 0x%02x\n", dev->name, off); switch(off) { case 0x01: /* PAR0-5 */ case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: return(dev->dp8390.physaddr[off - 1]); case 0x07: /* CURR */ wdlog(3, "%s: returning current page: 0x%02x\n", dev->name, (dev->dp8390.curr_page)); return(dev->dp8390.curr_page); case 0x08: /* MAR0-7 */ case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: return(dev->dp8390.mchash[off - 8]); default: wdlog(3, "%s: Page1 read register 0x%02x out of range\n", dev->name, off); return(0); } } static void page1_write(wd_t *dev, uint32_t off, uint8_t val) { wdlog(3, "%s: Page1 write to register 0x%02x, value=0x%04x\n", dev->name, off, val); switch(off) { case 0x01: /* PAR0-5 */ case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: dev->dp8390.physaddr[off - 1] = val; if (off == 6) wdlog(3, "%s: physical address set to %02x:%02x:%02x:%02x:%02x:%02x\n", dev->name, dev->dp8390.physaddr[0], dev->dp8390.physaddr[1], dev->dp8390.physaddr[2], dev->dp8390.physaddr[3], dev->dp8390.physaddr[4], dev->dp8390.physaddr[5]); break; case 0x07: /* CURR */ dev->dp8390.curr_page = val; break; case 0x08: /* MAR0-7 */ case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: dev->dp8390.mchash[off - 8] = val; break; default: wdlog(3, "%s: Page1 write register 0x%02x out of range\n", dev->name, off); break; } } /* Handle reads/writes to the second page of the DS8390 register file. */ static uint8_t page2_read(wd_t *dev, uint32_t off) { wdlog(3, "%s: Page2 read from register 0x%02x\n", dev->name, off); switch(off) { case 0x01: /* PSTART */ return(dev->dp8390.page_start); case 0x02: /* PSTOP */ return(dev->dp8390.page_stop); case 0x03: /* Remote Next-packet pointer */ return(dev->dp8390.rempkt_ptr); case 0x04: /* TPSR */ return(dev->dp8390.tx_page_start); case 0x05: /* Local Next-packet pointer */ return(dev->dp8390.localpkt_ptr); case 0x06: /* Address counter (upper) */ return(dev->dp8390.address_cnt >> 8); case 0x07: /* Address counter (lower) */ return(dev->dp8390.address_cnt & 0xff); case 0x08: /* Reserved */ case 0x09: case 0x0a: case 0x0b: wdlog(3, "%s: reserved Page2 read - register 0x%02x\n", dev->name, off); return(0xff); case 0x0c: /* RCR */ return ((dev->dp8390.RCR.monitor << 5) | (dev->dp8390.RCR.promisc << 4) | (dev->dp8390.RCR.multicast << 3) | (dev->dp8390.RCR.broadcast << 2) | (dev->dp8390.RCR.runts_ok << 1) | (dev->dp8390.RCR.errors_ok)); case 0x0d: /* TCR */ return ((dev->dp8390.TCR.coll_prio << 4) | (dev->dp8390.TCR.ext_stoptx << 3) | ((dev->dp8390.TCR.loop_cntl & 0x3) << 1) | (dev->dp8390.TCR.crc_disable)); case 0x0e: /* DCR */ return (((dev->dp8390.DCR.fifo_size & 0x3) << 5) | (dev->dp8390.DCR.auto_rx << 4) | (dev->dp8390.DCR.loop << 3) | (dev->dp8390.DCR.longaddr << 2) | (dev->dp8390.DCR.endian << 1) | (dev->dp8390.DCR.wdsize)); case 0x0f: /* IMR */ return ((dev->dp8390.IMR.rdma_inte << 6) | (dev->dp8390.IMR.cofl_inte << 5) | (dev->dp8390.IMR.overw_inte << 4) | (dev->dp8390.IMR.txerr_inte << 3) | (dev->dp8390.IMR.rxerr_inte << 2) | (dev->dp8390.IMR.tx_inte << 1) | (dev->dp8390.IMR.rx_inte)); default: wdlog(3, "%s: Page2 register 0x%02x out of range\n", dev->name, off); break; } return(0); } #if 0 static void page2_write(wd_t *dev, uint32_t off, uint8_t val) { /* Maybe all writes here should be BX_PANIC()'d, since they affect internal operation, but let them through for now and print a warning. */ wdlog(3, "%s: Page2 write to register 0x%02x, value=0x%04x\n", dev->name, off, val); switch(off) { case 0x01: /* CLDA0 */ /* Clear out low byte and re-insert */ dev->dp8390.local_dma &= 0xff00; dev->dp8390.local_dma |= (val & 0xff); break; case 0x02: /* CLDA1 */ /* Clear out high byte and re-insert */ dev->dp8390.local_dma &= 0x00ff; dev->dp8390.local_dma |= ((val & 0xff) << 8); break; case 0x03: /* Remote Next-pkt pointer */ dev->dp8390.rempkt_ptr = val; break; case 0x04: wdlog(3, "page 2 write to reserved register 0x04\n"); break; case 0x05: /* Local Next-packet pointer */ dev->dp8390.localpkt_ptr = val; break; case 0x06: /* Address counter (upper) */ /* Clear out high byte and re-insert */ dev->dp8390.address_cnt &= 0x00ff; dev->dp8390.address_cnt |= ((val & 0xff) << 8); break; case 0x07: /* Address counter (lower) */ /* Clear out low byte and re-insert */ dev->dp8390.address_cnt &= 0xff00; dev->dp8390.address_cnt |= (val & 0xff); break; case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: wdlog(3, "%s: Page2 write to reserved register 0x%02x\n", dev->name, off); break; default: wdlog(3, "%s: Page2 write, illegal register 0x%02x\n", dev->name, off); break; } } #endif /* Routines for handling reads/writes to the Command Register. */ static uint8_t read_cr(wd_t *dev) { uint32_t retval; retval = (((dev->dp8390.CR.pgsel & 0x03) << 6) | ((dev->dp8390.CR.rdma_cmd & 0x07) << 3) | (dev->dp8390.CR.tx_packet << 2) | (dev->dp8390.CR.start << 1) | (dev->dp8390.CR.stop)); wdlog(3, "%s: read CR returns 0x%02x\n", dev->name, retval); return(retval); } static void write_cr(wd_t *dev, uint8_t val) { wdlog(3, "%s: wrote 0x%02x to CR\n", dev->name, val); /* Validate remote-DMA */ if ((val & 0x38) == 0x00) { wdlog(3, "%s: CR write - invalid rDMA value 0\n", dev->name); val |= 0x20; /* dma_cmd == 4 is a safe default */ } /* Check for s/w reset */ if (val & 0x01) { dev->dp8390.ISR.reset = 1; dev->dp8390.CR.stop = 1; } else { dev->dp8390.CR.stop = 0; } dev->dp8390.CR.rdma_cmd = (val & 0x38) >> 3; /* If start command issued, the RST bit in the ISR */ /* must be cleared */ if ((val & 0x02) && !dev->dp8390.CR.start) dev->dp8390.ISR.reset = 0; dev->dp8390.CR.start = ((val & 0x02) == 0x02); dev->dp8390.CR.pgsel = (val & 0xc0) >> 6; /* Check for send-packet command */ if (dev->dp8390.CR.rdma_cmd == 3) { /* Set up DMA read from receive ring */ dev->dp8390.remote_start = dev->dp8390.remote_dma = dev->dp8390.bound_ptr * 256; dev->dp8390.remote_bytes = (uint16_t) wd_chipmem_read(dev, dev->dp8390.bound_ptr * 256 + 2, 2); wdlog(2, "%s: sending buffer #x%x length %d\n", dev->name, dev->dp8390.remote_start, dev->dp8390.remote_bytes); } /* Check for start-tx */ if ((val & 0x04) && dev->dp8390.TCR.loop_cntl) { if (dev->dp8390.TCR.loop_cntl) { wd_rx(dev, &dev->dp8390.mem[dev->dp8390.tx_page_start*256 - DP8390_WORD_MEMSTART], dev->dp8390.tx_bytes); } } else if (val & 0x04) { if (dev->dp8390.CR.stop) { if (dev->dp8390.tx_bytes == 0) /* njh@bandsman.co.uk */ { return; /* Solaris9 probe */ } wdlog(3, "%s: CR write - tx start, dev in reset\n", dev->name); } if (dev->dp8390.tx_bytes == 0) wdlog(3, "%s: CR write - tx start, tx bytes == 0\n", dev->name); /* Send the packet to the system driver */ dev->dp8390.CR.tx_packet = 1; network_tx(dev->dp8390.mem, dev->dp8390.tx_bytes); wd_tx(dev, val); } /* Linux probes for an interrupt by setting up a remote-DMA read * of 0 bytes with remote-DMA completion interrupts enabled. * Detect this here */ if (dev->dp8390.CR.rdma_cmd == 0x01 && dev->dp8390.CR.start && dev->dp8390.remote_bytes == 0) { dev->dp8390.ISR.rdma_done = 1; if (dev->dp8390.IMR.rdma_inte) { wd_interrupt(dev, 1); wd_interrupt(dev, 0); } } } static uint8_t wd_readb(uint16_t addr, void *priv) { wd_t *dev = (wd_t *)priv; uint8_t retval = 0; int off = addr - dev->base_address; wdlog(3, "%s: read addr %x\n", dev->name, addr); if (off == 0x10) { retval = read_cr(dev); } else if (off >= 0x00 && off <= 0x0f) { retval = wd_smc_read(dev, off); } else { switch(dev->dp8390.CR.pgsel) { case 0x00: retval = page0_read(dev, off - 0x10); break; case 0x01: retval = page1_read(dev, off - 0x10); break; case 0x02: retval = page2_read(dev, off - 0x10); break; default: wdlog(3, "%s: unknown value of pgsel in read - %d\n", dev->name, dev->dp8390.CR.pgsel); break; } } return(retval); } static void wd_writeb(uint16_t addr, uint8_t val, void *priv) { wd_t *dev = (wd_t *)priv; int off = addr - dev->base_address; wdlog(3, "%s: write addr %x, value %x\n", dev->name, addr, val); if (off == 0x10) { write_cr(dev, val); } else if (off >= 0x00 && off <= 0x0f) { wd_smc_write(dev, off, val); } else { switch(dev->dp8390.CR.pgsel) { case 0x00: page0_write(dev, off - 0x10, val); break; case 0x01: page1_write(dev, off - 0x10, val); break; default: wdlog(3, "%s: unknown value of pgsel in write - %d\n", dev->name, dev->dp8390.CR.pgsel); break; } } } static void wd_ioset(wd_t *dev, uint16_t addr); static void wd_ioremove(wd_t *dev, uint16_t addr); static void wd_ioset(wd_t *dev, uint16_t addr) { io_sethandler(addr, 0x20, wd_readb, NULL, NULL, wd_writeb, NULL, NULL, dev); } static void wd_ioremove(wd_t *dev, uint16_t addr) { io_removehandler(addr, 0x20, wd_readb, NULL, NULL, wd_writeb, NULL, NULL, dev); } /* * Called by the platform-specific code when an Ethernet frame * has been received. The destination address is tested to see * if it should be accepted, and if the RX ring has enough room, * it is copied into it and the receive process is updated. */ static void wd_rx(void *priv, uint8_t *buf, int io_len) { static uint8_t bcast_addr[6] = {0xff,0xff,0xff,0xff,0xff,0xff}; wd_t *dev = (wd_t *)priv; uint8_t pkthdr[4]; uint8_t *startptr; int pages, avail; int idx, nextpage; int endbytes; //FIXME: move to upper layer ui_sb_update_icon(SB_NETWORK, 1); if (io_len != 60) wdlog(2, "%s: rx_frame with length %d\n", dev->name, io_len); if ((dev->dp8390.CR.stop != 0) || (dev->dp8390.page_start == 0)) return; /* * Add the pkt header + CRC to the length, and work * out how many 256-byte pages the frame would occupy. */ pages = (io_len + sizeof(pkthdr) + sizeof(uint32_t) + 255)/256; if (dev->dp8390.curr_page < dev->dp8390.bound_ptr) { avail = dev->dp8390.bound_ptr - dev->dp8390.curr_page; } else { avail = (dev->dp8390.page_stop - dev->dp8390.page_start) - (dev->dp8390.curr_page - dev->dp8390.bound_ptr); } /* * Avoid getting into a buffer overflow condition by * not attempting to do partial receives. The emulation * to handle this condition seems particularly painful. */ if ((avail < pages) #if NE2K_NEVER_FULL_RING || (avail == pages) #endif ) { wdlog(1, "%s: no space\n", dev->name); //FIXME: move to upper layer ui_sb_update_icon(SB_NETWORK, 0); return; } if ((io_len < 40/*60*/) && !dev->dp8390.RCR.runts_ok) { wdlog(1, "%s: rejected small packet, length %d\n", dev->name, io_len); //FIXME: move to upper layer ui_sb_update_icon(SB_NETWORK, 0); return; } /* Some computers don't care... */ if (io_len < 60) io_len = 60; wdlog(2, "%s: RX %x:%x:%x:%x:%x:%x > %x:%x:%x:%x:%x:%x len %d\n", dev->name, buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], io_len); /* Do address filtering if not in promiscuous mode. */ if (! dev->dp8390.RCR.promisc) { /* If this is a broadcast frame.. */ if (! memcmp(buf, bcast_addr, 6)) { /* Broadcast not enabled, we're done. */ if (! dev->dp8390.RCR.broadcast) { wdlog(2, "%s: RX BC disabled\n", dev->name); //FIXME: move to upper layer ui_sb_update_icon(SB_NETWORK, 0); return; } } /* If this is a multicast frame.. */ else if (buf[0] & 0x01) { /* Multicast not enabled, we're done. */ if (! dev->dp8390.RCR.multicast) { #if 1 wdlog(2, "%s: RX MC disabled\n", dev->name); #endif //FIXME: move to upper layer ui_sb_update_icon(SB_NETWORK, 0); return; } /* Are we listening to this multicast address? */ idx = mcast_index(buf); if (! (dev->dp8390.mchash[idx>>3] & (1<<(idx&0x7)))) { wdlog(2, "%s: RX MC not listed\n", dev->name); //FIXME: move to upper layer ui_sb_update_icon(SB_NETWORK, 0); return; } } /* Unicast, must be for us.. */ else if (memcmp(buf, dev->dp8390.physaddr, 6)) return; } else { wdlog(2, "%s: RX promiscuous receive\n", dev->name); } nextpage = dev->dp8390.curr_page + pages; if (nextpage >= dev->dp8390.page_stop) nextpage -= (dev->dp8390.page_stop - dev->dp8390.page_start); /* Set up packet header. */ pkthdr[0] = 0x01; /* RXOK - packet is OK */ pkthdr[1] = nextpage; /* ptr to next packet */ pkthdr[2] = (io_len + sizeof(pkthdr))&0xff; /* length-low */ pkthdr[3] = (io_len + sizeof(pkthdr))>>8; /* length-hi */ wdlog(2, "%s: RX pkthdr [%02x %02x %02x %02x]\n", dev->name, pkthdr[0], pkthdr[1], pkthdr[2], pkthdr[3]); /* Copy into buffer, update curpage, and signal interrupt if config'd */ startptr = dev->dp8390.mem + (dev->dp8390.curr_page * 256); memcpy(startptr, pkthdr, sizeof(pkthdr)); if ((nextpage > dev->dp8390.curr_page) || ((dev->dp8390.curr_page + pages) == dev->dp8390.page_stop)) { memcpy(startptr+sizeof(pkthdr), buf, io_len); } else { endbytes = (dev->dp8390.page_stop - dev->dp8390.curr_page) * 256; memcpy(startptr+sizeof(pkthdr), buf, endbytes-sizeof(pkthdr)); startptr = dev->dp8390.mem + (dev->dp8390.page_start * 256); memcpy(startptr, buf+endbytes-sizeof(pkthdr), io_len-endbytes+8); } dev->dp8390.curr_page = nextpage; dev->dp8390.RSR.rx_ok = 1; dev->dp8390.RSR.rx_mbit = (buf[0] & 0x01) ? 1 : 0; dev->dp8390.ISR.pkt_rx = 1; if (dev->dp8390.IMR.rx_inte) wd_interrupt(dev, 1); //FIXME: move to upper layer ui_sb_update_icon(SB_NETWORK, 0); } static void wd_tx(wd_t *dev, uint32_t val) { dev->dp8390.CR.tx_packet = 0; dev->dp8390.TSR.tx_ok = 1; dev->dp8390.ISR.pkt_tx = 1; /* Generate an interrupt if not masked */ if (dev->dp8390.IMR.tx_inte) wd_interrupt(dev, 1); dev->dp8390.tx_timer_active = 0; } static uint8_t wd_mca_read(int port, void *priv) { wd_t *dev = (wd_t *)priv; return(dev->pos_regs[port & 7]); } #define MCA_61C8_IRQS { 3, 4, 10, 15 } static void wd_mca_write(int port, uint8_t val, void *priv) { wd_t *dev = (wd_t *)priv; int8_t irq[4] = MCA_61C8_IRQS; uint32_t ram_size = 0; /* MCA does not write registers below 0x0100. */ if (port < 0x0102) return; /* Save the MCA register value. */ dev->pos_regs[port & 7] = val; wd_ioremove(dev, dev->base_address); dev->base_address = 0x800 + (((dev->pos_regs[2] & 0xf0) >> 4) * 0x1000); dev->ram_addr = 0xC0000 + ((dev->pos_regs[3] & 0x0f) * 0x2000) + ((dev->pos_regs[3] & 0x80) ? 0xF00000 : 0); dev->base_irq = irq[(dev->pos_regs[5] & 0x0c) >> 2]; ram_size = (dev->pos_regs[3] & 0x10) ? 0x4000 : 0x2000; /* * The PS/2 Model 80 BIOS always enables a card if it finds one, * even if no resources were assigned yet (because we only added * the card, but have not run AutoConfig yet...) * * So, remove current address, if any. */ /* Initialize the device if fully configured. */ if (dev->pos_regs[2] & 0x01) { /* Card enabled; register (new) I/O handler. */ wd_ioset(dev, dev->base_address); wd_reset(dev); mem_mapping_add(&dev->ram_mapping, dev->ram_addr, ram_size, wd_ram_readb, wd_ram_readw, wd_ram_readl, wd_ram_writeb, wd_ram_writew, wd_ram_writel, NULL, MEM_MAPPING_EXTERNAL, dev); mem_mapping_disable(&dev->ram_mapping); wdlog(1, "%s: attached IO=0x%X IRQ=%d, RAM addr=0x%06x\n", dev->name, dev->base_address, dev->base_irq, dev->ram_addr); } } static void * wd_init(const device_t *info) { uint32_t mac; wd_t *dev; #ifdef ENABLE_NIC_LOG int i; #endif /* Get the desired debug level. */ #ifdef ENABLE_NIC_LOG i = device_get_config_int("debug"); if (i > 0) wd_do_log = i; #endif dev = malloc(sizeof(wd_t)); memset(dev, 0x00, sizeof(wd_t)); dev->name = info->name; dev->board = info->local; switch(dev->board) { case WD8003E: dev->board_chip = WE_TYPE_WD8003E; dev->maclocal[0] = 0x00; /* 00:00:C0 (WD/SMC OID) */ dev->maclocal[1] = 0x00; dev->maclocal[2] = 0xC0; break; case WD8013EBT: dev->board_chip = WE_TYPE_WD8013EBT; dev->maclocal[0] = 0x00; /* 00:00:C0 (WD/SMC OID) */ dev->maclocal[1] = 0x00; dev->maclocal[2] = 0xC0; break; case WD8013EPA: dev->board_chip = WE_TYPE_WD8013EP | 0x80; dev->maclocal[0] = 0x00; /* 00:00:C0 (WD/SMC OID) */ dev->maclocal[1] = 0x00; dev->maclocal[2] = 0xC0; dev->pos_regs[0] = 0xC8; dev->pos_regs[1] = 0x61; break; } if (dev->board != WD8013EPA) { dev->base_address = device_get_config_hex16("base"); dev->base_irq = device_get_config_int("irq"); dev->ram_addr = device_get_config_hex20("ram_addr"); } else { mca_add(wd_mca_read, wd_mca_write, dev); } /* See if we have a local MAC address configured. */ mac = device_get_config_mac("mac", -1); /* * Make this device known to the I/O system. * PnP and PCI devices start with address spaces inactive. */ if (dev->board != WD8013EPA) wd_ioset(dev, dev->base_address); /* Set up our BIA. */ if (mac & 0xff000000) { /* Generate new local MAC. */ dev->maclocal[3] = random_generate(); dev->maclocal[4] = random_generate(); dev->maclocal[5] = random_generate(); mac = (((int) dev->maclocal[3]) << 16); mac |= (((int) dev->maclocal[4]) << 8); mac |= ((int) dev->maclocal[5]); device_set_config_mac("mac", mac); } else { dev->maclocal[3] = (mac>>16) & 0xff; dev->maclocal[4] = (mac>>8) & 0xff; dev->maclocal[5] = (mac & 0xff); } memcpy(dev->dp8390.physaddr, dev->maclocal, sizeof(dev->maclocal)); wdlog(0, "%s: I/O=%04x, IRQ=%d, MAC=%02x:%02x:%02x:%02x:%02x:%02x\n", dev->name, dev->base_address, dev->base_irq, dev->dp8390.physaddr[0], dev->dp8390.physaddr[1], dev->dp8390.physaddr[2], dev->dp8390.physaddr[3], dev->dp8390.physaddr[4], dev->dp8390.physaddr[5]); /* Reset the board. */ if (dev->board != WD8013EPA) wd_reset(dev); /* Attach ourselves to the network module. */ network_attach(dev, dev->dp8390.physaddr, wd_rx); /* Map this system into the memory map. */ if (dev->board != WD8013EPA) { mem_mapping_add(&dev->ram_mapping, dev->ram_addr, 0x4000, wd_ram_readb, NULL, NULL, wd_ram_writeb, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, dev); mem_mapping_disable(&dev->ram_mapping); wdlog(1, "%s: attached IO=0x%X IRQ=%d, RAM addr=0x%06x\n", dev->name, dev->base_address, dev->base_irq, dev->ram_addr); } return(dev); } static void wd_close(void *priv) { wd_t *dev = (wd_t *)priv; /* Make sure the platform layer is shut down. */ network_close(); wd_ioremove(dev, dev->base_address); wdlog(1, "%s: closed\n", dev->name); free(dev); } static const device_config_t wd8003_config[] = { { "base", "Address", CONFIG_HEX16, "", 0x300, { { "0x240", 0x240 }, { "0x280", 0x280 }, { "0x300", 0x300 }, { "0x380", 0x380 }, { "" } }, }, { "irq", "IRQ", CONFIG_SELECTION, "", 3, { { "IRQ 2", 2 }, { "IRQ 3", 3 }, { "IRQ 5", 5 }, { "IRQ 7", 7 }, { "" } }, }, { "mac", "MAC Address", CONFIG_MAC, "", -1 }, { "ram_addr", "RAM address", CONFIG_HEX20, "", 0xD0000, { { "C800", 0xC8000 }, { "CC00", 0xCC000 }, { "D000", 0xD0000 }, { "D400", 0xD4000 }, { "D800", 0xD8000 }, { "DC00", 0xDC000 }, { "" } }, }, { "", "", -1 } }; static const device_config_t wd8013_config[] = { { "base", "Address", CONFIG_HEX16, "", 0x300, { { "0x240", 0x240 }, { "0x280", 0x280 }, { "0x300", 0x300 }, { "0x380", 0x380 }, { "" } }, }, { "irq", "IRQ", CONFIG_SELECTION, "", 3, { { "IRQ 2", 2 }, { "IRQ 3", 3 }, { "IRQ 5", 5 }, { "IRQ 7", 7 }, { "IRQ 10", 10 }, { "IRQ 11", 11 }, { "IRQ 15", 15 }, { "" } }, }, { "mac", "MAC Address", CONFIG_MAC, "", -1 }, { "ram_addr", "RAM address", CONFIG_HEX20, "", 0xD0000, { { "C800", 0xC8000 }, { "CC00", 0xCC000 }, { "D000", 0xD0000 }, { "D400", 0xD4000 }, { "D800", 0xD8000 }, { "DC00", 0xDC000 }, { "" } }, }, { "", "", -1 } }; static const device_config_t mca_mac_config[] = { { "mac", "MAC Address", CONFIG_MAC, "", -1 }, { "", "", -1 } }; const device_t wd8003e_device = { "Western Digital WD8003E", DEVICE_ISA, WD8003E, wd_init, wd_close, NULL, NULL, NULL, NULL, wd8003_config }; const device_t wd8013ebt_device = { "Western Digital WD8013EBT", DEVICE_ISA, WD8013EBT, wd_init, wd_close, NULL, NULL, NULL, NULL, wd8013_config }; const device_t wd8013epa_device = { "Western Digital WD8013EP/A", DEVICE_MCA, WD8013EPA, wd_init, wd_close, NULL, NULL, NULL, NULL, mca_mac_config };