///////////////////////////////////////////////////////////////////////// // $Id: ne2k.cc,v 1.107 2009-04-21 20:27:35 sshwarts Exp $ ///////////////////////////////////////////////////////////////////////// // // Copyright (C) 2002 MandrakeSoft S.A. // // MandrakeSoft S.A. // 43, rue d'Aboukir // 75002 Paris - France // http://www.linux-mandrake.com/ // http://www.mandrakesoft.com/ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ///////////////////////////////////////////////////////////////////////// // Peter Grehan (grehan@iprg.nokia.com) coded the initial version of this // NE2000/ether stuff. // Define BX_PLUGGABLE in files that can be compiled into plugins. For // platforms that require a special tag on exported symbols, BX_PLUGGABLE // is used to know when we are exporting symbols and when we are importing. #define BX_PLUGGABLE #include "iodev.h" #if BX_SUPPORT_NE2K #if BX_SUPPORT_PCI #include "pci.h" #endif #include "ne2k.h" #include "eth.h" //Never completely fill the ne2k ring so that we never // hit the unclear completely full buffer condition. #define BX_NE2K_NEVER_FULL_RING (1) #define LOG_THIS theNE2kDevice-> bx_ne2k_c *theNE2kDevice = NULL; const Bit8u ne2k_iomask[32] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; int libne2k_LTX_plugin_init(plugin_t *plugin, plugintype_t type, int argc, char *argv[]) { theNE2kDevice = new bx_ne2k_c(); bx_devices.pluginNE2kDevice = theNE2kDevice; BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theNE2kDevice, BX_PLUGIN_NE2K); return(0); // Success } void libne2k_LTX_plugin_fini(void) { delete theNE2kDevice; } bx_ne2k_c::bx_ne2k_c() { put("NE2K"); s.tx_timer_index = BX_NULL_TIMER_HANDLE; ethdev = NULL; } bx_ne2k_c::~bx_ne2k_c() { if (ethdev != NULL) { delete ethdev; } BX_DEBUG(("Exit")); } // // reset - restore state to power-up, cancelling all i/o // void bx_ne2k_c::reset(unsigned type) { if (type == BX_RESET_HARDWARE) { // Zero out registers and memory memset(&BX_NE2K_THIS s.CR, 0, sizeof(BX_NE2K_THIS s.CR)); memset(&BX_NE2K_THIS s.IMR, 0, sizeof(BX_NE2K_THIS s.IMR)); memset(&BX_NE2K_THIS s.DCR, 0, sizeof(BX_NE2K_THIS s.DCR)); memset(&BX_NE2K_THIS s.TCR, 0, sizeof(BX_NE2K_THIS s.TCR)); memset(&BX_NE2K_THIS s.TSR, 0, sizeof(BX_NE2K_THIS s.TSR)); memset(&BX_NE2K_THIS s.RCR, 0, sizeof(BX_NE2K_THIS s.RCR)); memset(&BX_NE2K_THIS s.RSR, 0, sizeof(BX_NE2K_THIS s.RSR)); BX_NE2K_THIS s.local_dma = 0; BX_NE2K_THIS s.page_start = 0; BX_NE2K_THIS s.page_stop = 0; BX_NE2K_THIS s.bound_ptr = 0; BX_NE2K_THIS s.tx_page_start = 0; BX_NE2K_THIS s.num_coll = 0; BX_NE2K_THIS s.tx_bytes = 0; BX_NE2K_THIS s.fifo = 0; BX_NE2K_THIS s.remote_dma = 0; BX_NE2K_THIS s.remote_start = 0; BX_NE2K_THIS s.remote_bytes = 0; BX_NE2K_THIS s.tallycnt_0 = 0; BX_NE2K_THIS s.tallycnt_1 = 0; BX_NE2K_THIS s.tallycnt_2 = 0; memset(&BX_NE2K_THIS s.physaddr, 0, sizeof(BX_NE2K_THIS s.physaddr)); memset(&BX_NE2K_THIS s.mchash, 0, sizeof(BX_NE2K_THIS s.mchash)); BX_NE2K_THIS s.curr_page = 0; BX_NE2K_THIS s.rempkt_ptr = 0; BX_NE2K_THIS s.localpkt_ptr = 0; BX_NE2K_THIS s.address_cnt = 0; memset(&BX_NE2K_THIS s.mem, 0, sizeof(BX_NE2K_THIS s.mem)); // Set power-up conditions BX_NE2K_THIS s.CR.stop = 1; BX_NE2K_THIS s.CR.rdma_cmd = 4; BX_NE2K_THIS s.DCR.longaddr = 1; set_irq_level(0); } memset(&BX_NE2K_THIS s.ISR, 0, sizeof(BX_NE2K_THIS s.ISR)); BX_NE2K_THIS s.ISR.reset = 1; } void bx_ne2k_c::register_state(void) { unsigned i; char name[6]; bx_list_c *list = new bx_list_c(SIM->get_bochs_root(), "ne2k", "NE2000 State", 31); bx_list_c *CR = new bx_list_c(list, "CR", 5); new bx_shadow_bool_c(CR, "stop", &BX_NE2K_THIS s.CR.stop); new bx_shadow_bool_c(CR, "start", &BX_NE2K_THIS s.CR.start); new bx_shadow_bool_c(CR, "tx_packet", &BX_NE2K_THIS s.CR.tx_packet); new bx_shadow_num_c(CR, "rdma_cmd", &BX_NE2K_THIS s.CR.rdma_cmd); new bx_shadow_num_c(CR, "pgsel", &BX_NE2K_THIS s.CR.pgsel); bx_list_c *ISR = new bx_list_c(list, "ISR", 8); new bx_shadow_bool_c(ISR, "pkt_rx", &BX_NE2K_THIS s.ISR.pkt_rx); new bx_shadow_bool_c(ISR, "pkt_tx", &BX_NE2K_THIS s.ISR.pkt_tx); new bx_shadow_bool_c(ISR, "rx_err", &BX_NE2K_THIS s.ISR.rx_err); new bx_shadow_bool_c(ISR, "tx_err", &BX_NE2K_THIS s.ISR.tx_err); new bx_shadow_bool_c(ISR, "overwrite", &BX_NE2K_THIS s.ISR.overwrite); new bx_shadow_bool_c(ISR, "cnt_oflow", &BX_NE2K_THIS s.ISR.cnt_oflow); new bx_shadow_bool_c(ISR, "rdma_done", &BX_NE2K_THIS s.ISR.rdma_done); new bx_shadow_bool_c(ISR, "reset", &BX_NE2K_THIS s.ISR.reset); bx_list_c *IMR = new bx_list_c(list, "IMR", 7); new bx_shadow_bool_c(IMR, "rx_inte", &BX_NE2K_THIS s.IMR.rx_inte); new bx_shadow_bool_c(IMR, "tx_inte", &BX_NE2K_THIS s.IMR.tx_inte); new bx_shadow_bool_c(IMR, "rxerr_inte", &BX_NE2K_THIS s.IMR.rxerr_inte); new bx_shadow_bool_c(IMR, "txerr_inte", &BX_NE2K_THIS s.IMR.txerr_inte); new bx_shadow_bool_c(IMR, "overw_inte", &BX_NE2K_THIS s.IMR.overw_inte); new bx_shadow_bool_c(IMR, "cofl_inte", &BX_NE2K_THIS s.IMR.cofl_inte); new bx_shadow_bool_c(IMR, "rdma_inte", &BX_NE2K_THIS s.IMR.rdma_inte); bx_list_c *DCR = new bx_list_c(list, "DCR", 6); new bx_shadow_bool_c(DCR, "wdsize", &BX_NE2K_THIS s.DCR.wdsize); new bx_shadow_bool_c(DCR, "endian", &BX_NE2K_THIS s.DCR.endian); new bx_shadow_bool_c(DCR, "longaddr", &BX_NE2K_THIS s.DCR.longaddr); new bx_shadow_bool_c(DCR, "loop", &BX_NE2K_THIS s.DCR.loop); new bx_shadow_bool_c(DCR, "auto_rx", &BX_NE2K_THIS s.DCR.auto_rx); new bx_shadow_num_c(DCR, "fifo_size", &BX_NE2K_THIS s.DCR.fifo_size); bx_list_c *TCR = new bx_list_c(list, "TCR", 4); new bx_shadow_bool_c(TCR, "crc_disable", &BX_NE2K_THIS s.TCR.crc_disable); new bx_shadow_num_c(TCR, "loop_cntl", &BX_NE2K_THIS s.TCR.loop_cntl); new bx_shadow_bool_c(TCR, "ext_stoptx", &BX_NE2K_THIS s.TCR.ext_stoptx); new bx_shadow_bool_c(TCR, "coll_prio", &BX_NE2K_THIS s.TCR.coll_prio); bx_list_c *TSR = new bx_list_c(list, "TSR", 7); new bx_shadow_bool_c(TSR, "tx_ok", &BX_NE2K_THIS s.TSR.tx_ok); new bx_shadow_bool_c(TSR, "collided", &BX_NE2K_THIS s.TSR.collided); new bx_shadow_bool_c(TSR, "aborted", &BX_NE2K_THIS s.TSR.aborted); new bx_shadow_bool_c(TSR, "no_carrier", &BX_NE2K_THIS s.TSR.no_carrier); new bx_shadow_bool_c(TSR, "fifo_ur", &BX_NE2K_THIS s.TSR.fifo_ur); new bx_shadow_bool_c(TSR, "cd_hbeat", &BX_NE2K_THIS s.TSR.cd_hbeat); new bx_shadow_bool_c(TSR, "ow_coll", &BX_NE2K_THIS s.TSR.ow_coll); bx_list_c *RCR = new bx_list_c(list, "RCR", 6); new bx_shadow_bool_c(RCR, "errors_ok", &BX_NE2K_THIS s.RCR.errors_ok); new bx_shadow_bool_c(RCR, "runts_ok", &BX_NE2K_THIS s.RCR.runts_ok); new bx_shadow_bool_c(RCR, "broadcast", &BX_NE2K_THIS s.RCR.broadcast); new bx_shadow_bool_c(RCR, "multicast", &BX_NE2K_THIS s.RCR.multicast); new bx_shadow_bool_c(RCR, "promisc", &BX_NE2K_THIS s.RCR.promisc); new bx_shadow_bool_c(RCR, "monitor", &BX_NE2K_THIS s.RCR.monitor); bx_list_c *RSR = new bx_list_c(list, "RSR", 8); new bx_shadow_bool_c(RSR, "rx_ok", &BX_NE2K_THIS s.RSR.rx_ok); new bx_shadow_bool_c(RSR, "bad_crc", &BX_NE2K_THIS s.RSR.bad_crc); new bx_shadow_bool_c(RSR, "bad_falign", &BX_NE2K_THIS s.RSR.bad_falign); new bx_shadow_bool_c(RSR, "fifo_or", &BX_NE2K_THIS s.RSR.fifo_or); new bx_shadow_bool_c(RSR, "rx_missed", &BX_NE2K_THIS s.RSR.rx_missed); new bx_shadow_bool_c(RSR, "rx_mbit", &BX_NE2K_THIS s.RSR.rx_mbit); new bx_shadow_bool_c(RSR, "rx_disabled", &BX_NE2K_THIS s.RSR.rx_disabled); new bx_shadow_bool_c(RSR, "deferred", &BX_NE2K_THIS s.RSR.deferred); new bx_shadow_num_c(list, "local_dma", &BX_NE2K_THIS s.local_dma, BASE_HEX); new bx_shadow_num_c(list, "page_start", &BX_NE2K_THIS s.page_start, BASE_HEX); new bx_shadow_num_c(list, "page_stop", &BX_NE2K_THIS s.page_stop, BASE_HEX); new bx_shadow_num_c(list, "bound_ptr", &BX_NE2K_THIS s.bound_ptr, BASE_HEX); new bx_shadow_num_c(list, "tx_page_start", &BX_NE2K_THIS s.tx_page_start, BASE_HEX); new bx_shadow_num_c(list, "num_coll", &BX_NE2K_THIS s.num_coll, BASE_HEX); new bx_shadow_num_c(list, "tx_bytes", &BX_NE2K_THIS s.tx_bytes, BASE_HEX); new bx_shadow_num_c(list, "fifo", &BX_NE2K_THIS s.fifo, BASE_HEX); new bx_shadow_num_c(list, "remote_dma", &BX_NE2K_THIS s.remote_dma, BASE_HEX); new bx_shadow_num_c(list, "remote_start", &BX_NE2K_THIS s.remote_start, BASE_HEX); new bx_shadow_num_c(list, "remote_bytes", &BX_NE2K_THIS s.remote_bytes, BASE_HEX); new bx_shadow_num_c(list, "tallycnt_0", &BX_NE2K_THIS s.tallycnt_0, BASE_HEX); new bx_shadow_num_c(list, "tallycnt_1", &BX_NE2K_THIS s.tallycnt_1, BASE_HEX); new bx_shadow_num_c(list, "tallycnt_2", &BX_NE2K_THIS s.tallycnt_2, BASE_HEX); bx_list_c *paddr = new bx_list_c(list, "physaddr", 6); for (i=0; i<6; i++) { sprintf(name, "0x%02x", i); new bx_shadow_num_c(paddr, name, &BX_NE2K_THIS s.physaddr[i], BASE_HEX); } new bx_shadow_num_c(list, "curr_page", &BX_NE2K_THIS s.curr_page, BASE_HEX); bx_list_c *mchash = new bx_list_c(list, "mchash", 8); for (i=0; i<8; i++) { sprintf(name, "0x%02x", i); new bx_shadow_num_c(mchash, name, &BX_NE2K_THIS s.mchash[i], BASE_HEX); } new bx_shadow_num_c(list, "rempkt_ptr", &BX_NE2K_THIS s.rempkt_ptr, BASE_HEX); new bx_shadow_num_c(list, "localpkt_ptr", &BX_NE2K_THIS s.localpkt_ptr, BASE_HEX); new bx_shadow_num_c(list, "address_cnt", &BX_NE2K_THIS s.address_cnt, BASE_HEX); new bx_shadow_data_c(list, "mem", BX_NE2K_THIS s.mem, BX_NE2K_MEMSIZ); new bx_shadow_bool_c(list, "tx_timer_active", &BX_NE2K_THIS s.tx_timer_active); #if BX_SUPPORT_PCI if (BX_NE2K_THIS s.pci_enabled) { register_pci_state(list, BX_NE2K_THIS s.pci_conf); } #endif } #if BX_SUPPORT_PCI void bx_ne2k_c::after_restore_state(void) { if (BX_NE2K_THIS s.pci_enabled) { if (DEV_pci_set_base_io(BX_NE2K_THIS_PTR, read_handler, write_handler, &BX_NE2K_THIS s.base_address, &BX_NE2K_THIS s.pci_conf[0x10], 32, &ne2k_iomask[0], "NE2000 PCI NIC")) { BX_INFO(("new base address: 0x%04x", BX_NE2K_THIS s.base_address)); } } } #endif // // read_cr/write_cr - utility routines for handling reads/writes to // the Command Register // Bit32u bx_ne2k_c::read_cr(void) { Bit32u val = (((BX_NE2K_THIS s.CR.pgsel & 0x03) << 6) | ((BX_NE2K_THIS s.CR.rdma_cmd & 0x07) << 3) | (BX_NE2K_THIS s.CR.tx_packet << 2) | (BX_NE2K_THIS s.CR.start << 1) | (BX_NE2K_THIS s.CR.stop)); BX_DEBUG(("read CR returns 0x%02x", val)); return val; } void bx_ne2k_c::write_cr(Bit32u value) { BX_DEBUG(("wrote 0x%02x to CR", value)); // Validate remote-DMA if ((value & 0x38) == 0x00) { BX_DEBUG(("CR write - invalid rDMA value 0")); value |= 0x20; /* dma_cmd == 4 is a safe default */ } // Check for s/w reset if (value & 0x01) { BX_NE2K_THIS s.ISR.reset = 1; BX_NE2K_THIS s.CR.stop = 1; } else { BX_NE2K_THIS s.CR.stop = 0; } BX_NE2K_THIS s.CR.rdma_cmd = (value & 0x38) >> 3; // If start command issued, the RST bit in the ISR // must be cleared if ((value & 0x02) && !BX_NE2K_THIS s.CR.start) { BX_NE2K_THIS s.ISR.reset = 0; } BX_NE2K_THIS s.CR.start = ((value & 0x02) == 0x02); BX_NE2K_THIS s.CR.pgsel = (value & 0xc0) >> 6; // Check for send-packet command if (BX_NE2K_THIS s.CR.rdma_cmd == 3) { // Set up DMA read from receive ring BX_NE2K_THIS s.remote_start = BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.bound_ptr * 256; BX_NE2K_THIS s.remote_bytes = *((Bit16u*) & BX_NE2K_THIS s.mem[BX_NE2K_THIS s.bound_ptr * 256 + 2 - BX_NE2K_MEMSTART]); BX_INFO(("Sending buffer #x%x length %d", BX_NE2K_THIS s.remote_start, BX_NE2K_THIS s.remote_bytes)); } // Check for start-tx if ((value & 0x04) && BX_NE2K_THIS s.TCR.loop_cntl) { if (BX_NE2K_THIS s.TCR.loop_cntl != 1) { BX_INFO(("Loop mode %d not supported.", BX_NE2K_THIS s.TCR.loop_cntl)); } else { rx_frame (& BX_NE2K_THIS s.mem[BX_NE2K_THIS s.tx_page_start*256 - BX_NE2K_MEMSTART], BX_NE2K_THIS s.tx_bytes); } } else if (value & 0x04) { if (BX_NE2K_THIS s.CR.stop || (!BX_NE2K_THIS s.CR.start && !BX_NE2K_THIS s.pci_enabled)) { if (BX_NE2K_THIS s.tx_bytes == 0) /* njh@bandsman.co.uk */ return; /* Solaris9 probe */ BX_PANIC(("CR write - tx start, dev in reset")); } if (BX_NE2K_THIS s.tx_bytes == 0) BX_PANIC(("CR write - tx start, tx bytes == 0")); // Send the packet to the system driver BX_NE2K_THIS s.CR.tx_packet = 1; BX_NE2K_THIS ethdev->sendpkt(& BX_NE2K_THIS s.mem[BX_NE2K_THIS s.tx_page_start*256 - BX_NE2K_MEMSTART], BX_NE2K_THIS s.tx_bytes); // some more debug if (BX_NE2K_THIS s.tx_timer_active) BX_ERROR(("CR write, tx timer still active")); // Schedule a timer to trigger a tx-complete interrupt // The number of microseconds is the bit-time / 10. // The bit-time is the preamble+sfd (64 bits), the // inter-frame gap (96 bits), the CRC (4 bytes), and the // the number of bits in the frame (s.tx_bytes * 8). // bx_pc_system.activate_timer(BX_NE2K_THIS s.tx_timer_index, (64 + 96 + 4*8 + BX_NE2K_THIS s.tx_bytes*8)/10, 0); // not continuous BX_NE2K_THIS s.tx_timer_active = 1; } // 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 (BX_NE2K_THIS s.CR.rdma_cmd == 0x01 && BX_NE2K_THIS s.CR.start && BX_NE2K_THIS s.remote_bytes == 0) { BX_NE2K_THIS s.ISR.rdma_done = 1; if (BX_NE2K_THIS s.IMR.rdma_inte) { set_irq_level(1); } } } // // chipmem_read/chipmem_write - access the 64K private RAM. // The ne2000 memory is accessed through the data port of // the asic (offset 0) after setting up a remote-DMA transfer. // Both byte and word accesses are allowed. // The first 16 bytes contains the MAC address at even locations, // and there is 16K of buffer memory starting at 16K // Bit32u BX_CPP_AttrRegparmN(2) bx_ne2k_c::chipmem_read(Bit32u address, unsigned int io_len) { Bit32u retval = 0; if ((io_len == 2) && (address & 0x1)) BX_PANIC(("unaligned chipmem word read")); // ROM'd MAC address if ((address >=0) && (address <= 31)) { retval = BX_NE2K_THIS s.macaddr[address]; if ((io_len == 2) || (io_len == 4)) { retval |= (BX_NE2K_THIS s.macaddr[address + 1] << 8); } if (io_len == 4) { retval |= (BX_NE2K_THIS s.macaddr[address + 2] << 16); retval |= (BX_NE2K_THIS s.macaddr[address + 3] << 24); } return (retval); } if ((address >= BX_NE2K_MEMSTART) && (address < BX_NE2K_MEMEND)) { retval = BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART]; if ((io_len == 2) || (io_len == 4)) { retval |= (BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 1] << 8); } if (io_len == 4) { retval |= (BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 2] << 16); retval |= (BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 3] << 24); } return (retval); } BX_DEBUG(("out-of-bounds chipmem read, %04X", address)); return (0xff); } void BX_CPP_AttrRegparmN(3) bx_ne2k_c::chipmem_write(Bit32u address, Bit32u value, unsigned io_len) { if ((io_len == 2) && (address & 0x1)) BX_PANIC(("unaligned chipmem word write")); if ((address >= BX_NE2K_MEMSTART) && (address < BX_NE2K_MEMEND)) { BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART] = value & 0xff; if ((io_len == 2) || (io_len == 4)) { BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 1] = value >> 8; } if (io_len == 4) { BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 2] = value >> 16; BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 3] = value >> 24; } } else BX_DEBUG(("out-of-bounds chipmem write, %04X", address)); } // // asic_read/asic_write - This is the high 16 bytes of i/o space // (the lower 16 bytes is for the DS8390). Only two locations // are used: offset 0, which is used for data transfer, and // offset 0xf, which is used to reset the device. // The data transfer port is used to as 'external' DMA to the // DS8390. The chip has to have the DMA registers set up, and // after that, insw/outsw instructions can be used to move // the appropriate number of bytes to/from the device. // Bit32u BX_CPP_AttrRegparmN(2) bx_ne2k_c::asic_read(Bit32u offset, unsigned int io_len) { Bit32u retval = 0; switch (offset) { case 0x0: // Data register // // A read remote-DMA command must have been issued, // and the source-address and length registers must // have been initialised. // if (io_len > BX_NE2K_THIS s.remote_bytes) { BX_ERROR(("ne2K: dma read underrun iolen=%d remote_bytes=%d",io_len,BX_NE2K_THIS s.remote_bytes)); //return 0; } //BX_INFO(("ne2k read DMA: addr=%4x remote_bytes=%d",BX_NE2K_THIS s.remote_dma,BX_NE2K_THIS s.remote_bytes)); retval = chipmem_read(BX_NE2K_THIS s.remote_dma, io_len); // // The 8390 bumps the address and decreases the byte count // by the selected word size after every access, not by // the amount of data requested by the host (io_len). // if (io_len == 4) { BX_NE2K_THIS s.remote_dma += io_len; } else { BX_NE2K_THIS s.remote_dma += (BX_NE2K_THIS s.DCR.wdsize + 1); } if (BX_NE2K_THIS s.remote_dma == BX_NE2K_THIS s.page_stop << 8) { BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.page_start << 8; } // keep s.remote_bytes from underflowing if (BX_NE2K_THIS s.remote_bytes > BX_NE2K_THIS s.DCR.wdsize) if (io_len == 4) { BX_NE2K_THIS s.remote_bytes -= io_len; } else { BX_NE2K_THIS s.remote_bytes -= (BX_NE2K_THIS s.DCR.wdsize + 1); } else BX_NE2K_THIS s.remote_bytes = 0; // If all bytes have been written, signal remote-DMA complete if (BX_NE2K_THIS s.remote_bytes == 0) { BX_NE2K_THIS s.ISR.rdma_done = 1; if (BX_NE2K_THIS s.IMR.rdma_inte) { set_irq_level(1); } } break; case 0xf: // Reset register theNE2kDevice->reset(BX_RESET_SOFTWARE); break; default: BX_INFO(("asic read invalid address %04x", (unsigned) offset)); break; } return (retval); } void bx_ne2k_c::asic_write(Bit32u offset, Bit32u value, unsigned io_len) { BX_DEBUG(("asic write addr=0x%02x, value=0x%04x", (unsigned) offset, (unsigned) value)); switch (offset) { case 0x0: // Data register - see asic_read for a description if ((io_len > 1) && (BX_NE2K_THIS s.DCR.wdsize == 0)) { BX_PANIC(("dma write length %d on byte mode operation", io_len)); break; } if (BX_NE2K_THIS s.remote_bytes == 0) { BX_ERROR(("ne2K: dma write, byte count 0")); } chipmem_write(BX_NE2K_THIS s.remote_dma, value, io_len); if (io_len == 4) { BX_NE2K_THIS s.remote_dma += io_len; } else { BX_NE2K_THIS s.remote_dma += (BX_NE2K_THIS s.DCR.wdsize + 1); } if (BX_NE2K_THIS s.remote_dma == BX_NE2K_THIS s.page_stop << 8) { BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.page_start << 8; } if (io_len == 4) { BX_NE2K_THIS s.remote_bytes -= io_len; } else { BX_NE2K_THIS s.remote_bytes -= (BX_NE2K_THIS s.DCR.wdsize + 1); } if (BX_NE2K_THIS s.remote_bytes > BX_NE2K_MEMSIZ) BX_NE2K_THIS s.remote_bytes = 0; // If all bytes have been written, signal remote-DMA complete if (BX_NE2K_THIS s.remote_bytes == 0) { BX_NE2K_THIS s.ISR.rdma_done = 1; if (BX_NE2K_THIS s.IMR.rdma_inte) { set_irq_level(1); } } break; case 0xf: // Reset register // end of reset pulse break; default: // this is invalid, but happens under win95 device detection BX_INFO(("asic write invalid address %04x, ignoring", (unsigned) offset)); break; } } // // page0_read/page0_write - These routines handle reads/writes to // the 'zeroth' page of the DS8390 register file // Bit32u bx_ne2k_c::page0_read(Bit32u offset, unsigned int io_len) { Bit8u value = 0; if (io_len > 1) { BX_ERROR(("bad length! page 0 read from register 0x%02x, len=%u", offset, io_len)); /* encountered with win98 hardware probe */ return value; } switch (offset) { case 0x1: // CLDA0 value = (BX_NE2K_THIS s.local_dma & 0xff); break; case 0x2: // CLDA1 value = (BX_NE2K_THIS s.local_dma >> 8); break; case 0x3: // BNRY value = BX_NE2K_THIS s.bound_ptr; break; case 0x4: // TSR value = ((BX_NE2K_THIS s.TSR.ow_coll << 7) | (BX_NE2K_THIS s.TSR.cd_hbeat << 6) | (BX_NE2K_THIS s.TSR.fifo_ur << 5) | (BX_NE2K_THIS s.TSR.no_carrier << 4) | (BX_NE2K_THIS s.TSR.aborted << 3) | (BX_NE2K_THIS s.TSR.collided << 2) | (BX_NE2K_THIS s.TSR.tx_ok)); break; case 0x5: // NCR value = BX_NE2K_THIS s.num_coll; break; case 0x6: // FIFO // reading FIFO is only valid in loopback mode BX_ERROR(("reading FIFO not supported yet")); value = BX_NE2K_THIS s.fifo; break; case 0x7: // ISR value = ((BX_NE2K_THIS s.ISR.reset << 7) | (BX_NE2K_THIS s.ISR.rdma_done << 6) | (BX_NE2K_THIS s.ISR.cnt_oflow << 5) | (BX_NE2K_THIS s.ISR.overwrite << 4) | (BX_NE2K_THIS s.ISR.tx_err << 3) | (BX_NE2K_THIS s.ISR.rx_err << 2) | (BX_NE2K_THIS s.ISR.pkt_tx << 1) | (BX_NE2K_THIS s.ISR.pkt_rx)); break; case 0x8: // CRDA0 value = (BX_NE2K_THIS s.remote_dma & 0xff); break; case 0x9: // CRDA1 value = (BX_NE2K_THIS s.remote_dma >> 8); break; case 0xa: // reserved / RTL8029ID0 if (BX_NE2K_THIS s.pci_enabled) { value = 0x50; } else { BX_INFO(("reserved read - page 0, 0xa")); value = 0xff; } break; case 0xb: // reserved / RTL8029ID1 if (BX_NE2K_THIS s.pci_enabled) { value = 0x43; } else { BX_INFO(("reserved read - page 0, 0xb")); value = 0xff; } break; case 0xc: // RSR value = ((BX_NE2K_THIS s.RSR.deferred << 7) | (BX_NE2K_THIS s.RSR.rx_disabled << 6) | (BX_NE2K_THIS s.RSR.rx_mbit << 5) | (BX_NE2K_THIS s.RSR.rx_missed << 4) | (BX_NE2K_THIS s.RSR.fifo_or << 3) | (BX_NE2K_THIS s.RSR.bad_falign << 2) | (BX_NE2K_THIS s.RSR.bad_crc << 1) | (BX_NE2K_THIS s.RSR.rx_ok)); break; case 0xd: // CNTR0 value = BX_NE2K_THIS s.tallycnt_0; break; case 0xe: // CNTR1 value = BX_NE2K_THIS s.tallycnt_1; break; case 0xf: // CNTR2 value = BX_NE2K_THIS s.tallycnt_2; break; default: BX_PANIC(("page 0 register 0x%02x out of range", offset)); } BX_DEBUG(("page 0 read from register 0x%02x, value=0x%02x", offset, value)); return value; } void bx_ne2k_c::page0_write(Bit32u offset, Bit32u value, unsigned io_len) { Bit8u value2; // It appears to be a common practice to use outw on page0 regs... // break up outw into two outb's if (io_len == 2) { page0_write(offset, (value & 0xff), 1); if (offset < 0x0f) { page0_write(offset + 1, ((value >> 8) & 0xff), 1); } return; } BX_DEBUG(("page 0 write to register 0x%02x, value=0x%02x", offset, value)); switch (offset) { case 0x1: // PSTART BX_NE2K_THIS s.page_start = value; break; case 0x2: // PSTOP BX_NE2K_THIS s.page_stop = value; break; case 0x3: // BNRY BX_NE2K_THIS s.bound_ptr = value; break; case 0x4: // TPSR BX_NE2K_THIS s.tx_page_start = value; break; case 0x5: // TBCR0 // Clear out low byte and re-insert BX_NE2K_THIS s.tx_bytes &= 0xff00; BX_NE2K_THIS s.tx_bytes |= (value & 0xff); break; case 0x6: // TBCR1 // Clear out high byte and re-insert BX_NE2K_THIS s.tx_bytes &= 0x00ff; BX_NE2K_THIS s.tx_bytes |= ((value & 0xff) << 8); break; case 0x7: // ISR value &= 0x7f; // clear RST bit - status-only bit // All other values are cleared iff the ISR bit is 1 BX_NE2K_THIS s.ISR.pkt_rx &= ~((bx_bool)((value & 0x01) == 0x01)); BX_NE2K_THIS s.ISR.pkt_tx &= ~((bx_bool)((value & 0x02) == 0x02)); BX_NE2K_THIS s.ISR.rx_err &= ~((bx_bool)((value & 0x04) == 0x04)); BX_NE2K_THIS s.ISR.tx_err &= ~((bx_bool)((value & 0x08) == 0x08)); BX_NE2K_THIS s.ISR.overwrite &= ~((bx_bool)((value & 0x10) == 0x10)); BX_NE2K_THIS s.ISR.cnt_oflow &= ~((bx_bool)((value & 0x20) == 0x20)); BX_NE2K_THIS s.ISR.rdma_done &= ~((bx_bool)((value & 0x40) == 0x40)); value = ((BX_NE2K_THIS s.ISR.rdma_done << 6) | (BX_NE2K_THIS s.ISR.cnt_oflow << 5) | (BX_NE2K_THIS s.ISR.overwrite << 4) | (BX_NE2K_THIS s.ISR.tx_err << 3) | (BX_NE2K_THIS s.ISR.rx_err << 2) | (BX_NE2K_THIS s.ISR.pkt_tx << 1) | (BX_NE2K_THIS s.ISR.pkt_rx)); value &= ((BX_NE2K_THIS s.IMR.rdma_inte << 6) | (BX_NE2K_THIS s.IMR.cofl_inte << 5) | (BX_NE2K_THIS s.IMR.overw_inte << 4) | (BX_NE2K_THIS s.IMR.txerr_inte << 3) | (BX_NE2K_THIS s.IMR.rxerr_inte << 2) | (BX_NE2K_THIS s.IMR.tx_inte << 1) | (BX_NE2K_THIS s.IMR.rx_inte)); if (value == 0) set_irq_level(0); break; case 0x8: // RSAR0 // Clear out low byte and re-insert BX_NE2K_THIS s.remote_start &= 0xff00; BX_NE2K_THIS s.remote_start |= (value & 0xff); BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.remote_start; break; case 0x9: // RSAR1 // Clear out high byte and re-insert BX_NE2K_THIS s.remote_start &= 0x00ff; BX_NE2K_THIS s.remote_start |= ((value & 0xff) << 8); BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.remote_start; break; case 0xa: // RBCR0 // Clear out low byte and re-insert BX_NE2K_THIS s.remote_bytes &= 0xff00; BX_NE2K_THIS s.remote_bytes |= (value & 0xff); break; case 0xb: // RBCR1 // Clear out high byte and re-insert BX_NE2K_THIS s.remote_bytes &= 0x00ff; BX_NE2K_THIS s.remote_bytes |= ((value & 0xff) << 8); break; case 0xc: // RCR // Check if the reserved bits are set if (value & 0xc0) BX_INFO(("RCR write, reserved bits set")); // Set all other bit-fields BX_NE2K_THIS s.RCR.errors_ok = ((value & 0x01) == 0x01); BX_NE2K_THIS s.RCR.runts_ok = ((value & 0x02) == 0x02); BX_NE2K_THIS s.RCR.broadcast = ((value & 0x04) == 0x04); BX_NE2K_THIS s.RCR.multicast = ((value & 0x08) == 0x08); BX_NE2K_THIS s.RCR.promisc = ((value & 0x10) == 0x10); BX_NE2K_THIS s.RCR.monitor = ((value & 0x20) == 0x20); // Monitor bit is a little suspicious... if (value & 0x20) BX_INFO(("RCR write, monitor bit set!")); break; case 0xd: // TCR // Check reserved bits if (value & 0xe0) BX_ERROR(("TCR write, reserved bits set")); // Test loop mode (not supported) if (value & 0x06) { BX_NE2K_THIS s.TCR.loop_cntl = (value & 0x6) >> 1; BX_INFO(("TCR write, loop mode %d not supported", BX_NE2K_THIS s.TCR.loop_cntl)); } else { BX_NE2K_THIS s.TCR.loop_cntl = 0; } // Inhibit-CRC not supported. if (value & 0x01) BX_PANIC(("TCR write, inhibit-CRC not supported")); // Auto-transmit disable very suspicious if (value & 0x08) BX_PANIC(("TCR write, auto transmit disable not supported")); // Allow collision-offset to be set, although not used BX_NE2K_THIS s.TCR.coll_prio = ((value & 0x08) == 0x08); break; case 0xe: // DCR // the loopback mode is not suppported yet if (!(value & 0x08)) { BX_ERROR(("DCR write, loopback mode selected")); } // It is questionable to set longaddr and auto_rx, since they // aren't supported on the ne2000. Print a warning and continue if (value & 0x04) BX_INFO(("DCR write - LAS set ???")); if (value & 0x10) BX_INFO(("DCR write - AR set ???")); // Set other values. BX_NE2K_THIS s.DCR.wdsize = ((value & 0x01) == 0x01); BX_NE2K_THIS s.DCR.endian = ((value & 0x02) == 0x02); BX_NE2K_THIS s.DCR.longaddr = ((value & 0x04) == 0x04); // illegal ? BX_NE2K_THIS s.DCR.loop = ((value & 0x08) == 0x08); BX_NE2K_THIS s.DCR.auto_rx = ((value & 0x10) == 0x10); // also illegal ? BX_NE2K_THIS s.DCR.fifo_size = (value & 0x50) >> 5; break; case 0xf: // IMR // Check for reserved bit if (value & 0x80) BX_ERROR(("IMR write, reserved bit set")); // Set other values BX_NE2K_THIS s.IMR.rx_inte = ((value & 0x01) == 0x01); BX_NE2K_THIS s.IMR.tx_inte = ((value & 0x02) == 0x02); BX_NE2K_THIS s.IMR.rxerr_inte = ((value & 0x04) == 0x04); BX_NE2K_THIS s.IMR.txerr_inte = ((value & 0x08) == 0x08); BX_NE2K_THIS s.IMR.overw_inte = ((value & 0x10) == 0x10); BX_NE2K_THIS s.IMR.cofl_inte = ((value & 0x20) == 0x20); BX_NE2K_THIS s.IMR.rdma_inte = ((value & 0x40) == 0x40); value2 = ((BX_NE2K_THIS s.ISR.rdma_done << 6) | (BX_NE2K_THIS s.ISR.cnt_oflow << 5) | (BX_NE2K_THIS s.ISR.overwrite << 4) | (BX_NE2K_THIS s.ISR.tx_err << 3) | (BX_NE2K_THIS s.ISR.rx_err << 2) | (BX_NE2K_THIS s.ISR.pkt_tx << 1) | (BX_NE2K_THIS s.ISR.pkt_rx)); if (((value & value2) & 0x7f) == 0) { set_irq_level(0); } else { set_irq_level(1); } break; default: BX_PANIC(("page 0 write, bad register 0x%02x", offset)); } } // // page1_read/page1_write - These routines handle reads/writes to // the first page of the DS8390 register file // Bit32u bx_ne2k_c::page1_read(Bit32u offset, unsigned int io_len) { BX_DEBUG(("page 1 read from register 0x%02x, len=%u", offset, io_len)); if (io_len > 1) BX_PANIC(("bad length! page 1 read from register 0x%02x, len=%u", offset, io_len)); switch (offset) { case 0x1: // PAR0-5 case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: return (BX_NE2K_THIS s.physaddr[offset - 1]); break; case 0x7: // CURR BX_DEBUG(("returning current page: 0x%02x", (BX_NE2K_THIS s.curr_page))); return (BX_NE2K_THIS s.curr_page); case 0x8: // MAR0-7 case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: case 0xf: return (BX_NE2K_THIS s.mchash[offset - 8]); break; default: BX_PANIC(("page 1 read register 0x%02x out of range", offset)); } return (0); } void bx_ne2k_c::page1_write(Bit32u offset, Bit32u value, unsigned io_len) { BX_DEBUG(("page 1 write to register 0x%02x, len=%u, value=0x%04x", offset, io_len, value)); switch (offset) { case 0x1: // PAR0-5 case 0x2: case 0x3: case 0x4: case 0x5: case 0x6: BX_NE2K_THIS s.physaddr[offset - 1] = value; if (offset == 6) { BX_INFO(("Physical address set to %02x:%02x:%02x:%02x:%02x:%02x", BX_NE2K_THIS s.physaddr[0], BX_NE2K_THIS s.physaddr[1], BX_NE2K_THIS s.physaddr[2], BX_NE2K_THIS s.physaddr[3], BX_NE2K_THIS s.physaddr[4], BX_NE2K_THIS s.physaddr[5])); } break; case 0x7: // CURR BX_NE2K_THIS s.curr_page = value; break; case 0x8: // MAR0-7 case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: case 0xf: BX_NE2K_THIS s.mchash[offset - 8] = value; break; default: BX_PANIC(("page 1 write register 0x%02x out of range", offset)); } } // // page2_read/page2_write - These routines handle reads/writes to // the second page of the DS8390 register file // Bit32u bx_ne2k_c::page2_read(Bit32u offset, unsigned int io_len) { BX_DEBUG(("page 2 read from register 0x%02x, len=%u", offset, io_len)); if (io_len > 1) BX_PANIC(("bad length! page 2 read from register 0x%02x, len=%u", offset, io_len)); switch (offset) { case 0x1: // PSTART return (BX_NE2K_THIS s.page_start); case 0x2: // PSTOP return (BX_NE2K_THIS s.page_stop); case 0x3: // Remote Next-packet pointer return (BX_NE2K_THIS s.rempkt_ptr); case 0x4: // TPSR return (BX_NE2K_THIS s.tx_page_start); case 0x5: // Local Next-packet pointer return (BX_NE2K_THIS s.localpkt_ptr); case 0x6: // Address counter (upper) return (BX_NE2K_THIS s.address_cnt >> 8); case 0x7: // Address counter (lower) return (BX_NE2K_THIS s.address_cnt & 0xff); case 0x8: // Reserved case 0x9: case 0xa: case 0xb: BX_ERROR(("reserved read - page 2, register 0x%02x", offset)); return (0xff); case 0xc: // RCR return ((BX_NE2K_THIS s.RCR.monitor << 5) | (BX_NE2K_THIS s.RCR.promisc << 4) | (BX_NE2K_THIS s.RCR.multicast << 3) | (BX_NE2K_THIS s.RCR.broadcast << 2) | (BX_NE2K_THIS s.RCR.runts_ok << 1) | (BX_NE2K_THIS s.RCR.errors_ok)); case 0xd: // TCR return ((BX_NE2K_THIS s.TCR.coll_prio << 4) | (BX_NE2K_THIS s.TCR.ext_stoptx << 3) | ((BX_NE2K_THIS s.TCR.loop_cntl & 0x3) << 1) | (BX_NE2K_THIS s.TCR.crc_disable)); case 0xe: // DCR return (((BX_NE2K_THIS s.DCR.fifo_size & 0x3) << 5) | (BX_NE2K_THIS s.DCR.auto_rx << 4) | (BX_NE2K_THIS s.DCR.loop << 3) | (BX_NE2K_THIS s.DCR.longaddr << 2) | (BX_NE2K_THIS s.DCR.endian << 1) | (BX_NE2K_THIS s.DCR.wdsize)); case 0xf: // IMR return ((BX_NE2K_THIS s.IMR.rdma_inte << 6) | (BX_NE2K_THIS s.IMR.cofl_inte << 5) | (BX_NE2K_THIS s.IMR.overw_inte << 4) | (BX_NE2K_THIS s.IMR.txerr_inte << 3) | (BX_NE2K_THIS s.IMR.rxerr_inte << 2) | (BX_NE2K_THIS s.IMR.tx_inte << 1) | (BX_NE2K_THIS s.IMR.rx_inte)); default: BX_PANIC(("page 2 register 0x%02x out of range", offset)); } return (0); } void bx_ne2k_c::page2_write(Bit32u offset, Bit32u value, unsigned io_len) { // Maybe all writes here should be BX_PANIC()'d, since they // affect internal operation, but let them through for now // and print a warning. BX_ERROR(("page 2 write to register 0x%02x, len=%u, value=0x%04x", offset, io_len, value)); switch (offset) { case 0x1: // CLDA0 // Clear out low byte and re-insert BX_NE2K_THIS s.local_dma &= 0xff00; BX_NE2K_THIS s.local_dma |= (value & 0xff); break; case 0x2: // CLDA1 // Clear out high byte and re-insert BX_NE2K_THIS s.local_dma &= 0x00ff; BX_NE2K_THIS s.local_dma |= ((value & 0xff) << 8); break; case 0x3: // Remote Next-pkt pointer BX_NE2K_THIS s.rempkt_ptr = value; break; case 0x4: BX_PANIC(("page 2 write to reserved register 0x04")); break; case 0x5: // Local Next-packet pointer BX_NE2K_THIS s.localpkt_ptr = value; break; case 0x6: // Address counter (upper) // Clear out high byte and re-insert BX_NE2K_THIS s.address_cnt &= 0x00ff; BX_NE2K_THIS s.address_cnt |= ((value & 0xff) << 8); break; case 0x7: // Address counter (lower) // Clear out low byte and re-insert BX_NE2K_THIS s.address_cnt &= 0xff00; BX_NE2K_THIS s.address_cnt |= (value & 0xff); break; case 0x8: case 0x9: case 0xa: case 0xb: case 0xc: case 0xd: case 0xe: case 0xf: BX_PANIC(("page 2 write to reserved register 0x%02x", offset)); break; default: BX_PANIC(("page 2 write, illegal register 0x%02x", offset)); break; } } // // page3_read/page3_write - writes to this page are illegal // Bit32u bx_ne2k_c::page3_read(Bit32u offset, unsigned int io_len) { if (BX_NE2K_THIS s.pci_enabled) { switch (offset) { case 0x3: // CONFIG0 return (0); case 0x5: // CONFIG2 return (0x40); case 0x6: // CONFIG3 return (0x40); default: BX_ERROR(("page 3 read register 0x%02x attempted", offset)); return (0); } } else { BX_ERROR(("page 3 read register 0x%02x attempted", offset)); return (0); } } void bx_ne2k_c::page3_write(Bit32u offset, Bit32u value, unsigned io_len) { BX_ERROR(("page 3 write register 0x%02x attempted", offset)); } // // tx_timer_handler/tx_timer // void bx_ne2k_c::tx_timer_handler(void *this_ptr) { bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr; class_ptr->tx_timer(); } void bx_ne2k_c::tx_timer(void) { BX_DEBUG(("tx_timer")); BX_NE2K_THIS s.CR.tx_packet = 0; BX_NE2K_THIS s.TSR.tx_ok = 1; // Generate an interrupt if not masked and not one in progress if (BX_NE2K_THIS s.IMR.tx_inte && !BX_NE2K_THIS s.ISR.pkt_tx) { BX_NE2K_THIS s.ISR.pkt_tx = 1; set_irq_level(1); } BX_NE2K_THIS s.tx_timer_active = 0; } // // read_handler/read - i/o 'catcher' function called from BOCHS // mainline when the CPU attempts a read in the i/o space registered // by this ne2000 instance // Bit32u bx_ne2k_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len) { #if !BX_USE_NE2K_SMF bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr; return class_ptr->read(address, io_len); } Bit32u bx_ne2k_c::read(Bit32u address, unsigned io_len) { #else UNUSED(this_ptr); #endif // !BX_USE_NE2K_SMF BX_DEBUG(("read addr %x, len %d", address, io_len)); Bit32u retval = 0; int offset = address - BX_NE2K_THIS s.base_address; if (offset >= 0x10) { retval = asic_read(offset - 0x10, io_len); } else if (offset == 0x00) { retval = read_cr(); } else { switch (BX_NE2K_THIS s.CR.pgsel) { case 0x00: retval = page0_read(offset, io_len); break; case 0x01: retval = page1_read(offset, io_len); break; case 0x02: retval = page2_read(offset, io_len); break; case 0x03: retval = page3_read(offset, io_len); break; default: BX_PANIC(("ne2K: unknown value of pgsel in read - %d", BX_NE2K_THIS s.CR.pgsel)); } } return (retval); } // // write_handler/write - i/o 'catcher' function called from BOCHS // mainline when the CPU attempts a write in the i/o space registered // by this ne2000 instance // void bx_ne2k_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len) { #if !BX_USE_NE2K_SMF bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr; class_ptr->write(address, value, io_len); } void bx_ne2k_c::write(Bit32u address, Bit32u value, unsigned io_len) { #else UNUSED(this_ptr); #endif // !BX_USE_NE2K_SMF BX_DEBUG(("write addr %x, value %x len %d", address, value, io_len)); int offset = address - BX_NE2K_THIS s.base_address; // // The high 16 bytes of i/o space are for the ne2000 asic - // the low 16 bytes are for the DS8390, with the current // page being selected by the PS0,PS1 registers in the // command register // if (offset >= 0x10) { asic_write(offset - 0x10, value, io_len); } else if (offset == 0x00) { write_cr(value); } else { switch (BX_NE2K_THIS s.CR.pgsel) { case 0x00: page0_write(offset, value, io_len); break; case 0x01: page1_write(offset, value, io_len); break; case 0x02: page2_write(offset, value, io_len); break; case 0x03: page3_write(offset, value, io_len); break; default: BX_PANIC(("ne2K: unknown value of pgsel in write - %d", BX_NE2K_THIS s.CR.pgsel)); } } } /* * mcast_index() - return the 6-bit index into the multicast * table. Stolen unashamedly from FreeBSD's if_ed.c */ unsigned bx_ne2k_c::mcast_index(const void *dst) { #define POLYNOMIAL 0x04c11db6 Bit32u crc = 0xffffffffL; int carry, i, j; unsigned char b; unsigned char *ep = (unsigned char *) dst; for (i = 6; --i >= 0;) { b = *ep++; for (j = 8; --j >= 0;) { carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01); crc <<= 1; b >>= 1; if (carry) crc = ((crc ^ POLYNOMIAL) | carry); } } return (crc >> 26); #undef POLYNOMIAL } /* * Callback from the eth system driver when a frame has arrived */ void bx_ne2k_c::rx_handler(void *arg, const void *buf, unsigned len) { // BX_DEBUG(("rx_handler with length %d", len)); bx_ne2k_c *class_ptr = (bx_ne2k_c *) arg; class_ptr->rx_frame(buf, len); } /* * rx_frame() - 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 */ void bx_ne2k_c::rx_frame(const void *buf, unsigned io_len) { int pages; int avail; unsigned idx; int wrapped; int nextpage; unsigned char pkthdr[4]; unsigned char *pktbuf = (unsigned char *) buf; unsigned char *startptr; BX_DEBUG(("rx_frame with length %d", io_len)); if ((BX_NE2K_THIS s.CR.stop != 0) || (BX_NE2K_THIS s.page_start == 0) || ((BX_NE2K_THIS s.DCR.loop == 0) && (BX_NE2K_THIS s.TCR.loop_cntl != 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 + 4 + 4 + 255)/256; if (BX_NE2K_THIS s.curr_page < BX_NE2K_THIS s.bound_ptr) { avail = BX_NE2K_THIS s.bound_ptr - BX_NE2K_THIS s.curr_page; } else { avail = (BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.page_start) - (BX_NE2K_THIS s.curr_page - BX_NE2K_THIS s.bound_ptr); wrapped = 1; } // 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 BX_NE2K_NEVER_FULL_RING || (avail == pages) #endif ) { return; } if ((io_len < 60) && !BX_NE2K_THIS s.RCR.runts_ok) { BX_DEBUG(("rejected small packet, length %d", io_len)); return; } // Do address filtering if not in promiscuous mode if (! BX_NE2K_THIS s.RCR.promisc) { if (!memcmp(buf, broadcast_macaddr, 6)) { if (!BX_NE2K_THIS s.RCR.broadcast) { return; } } else if (pktbuf[0] & 0x01) { if (! BX_NE2K_THIS s.RCR.multicast) { return; } idx = mcast_index(buf); if (!(BX_NE2K_THIS s.mchash[idx >> 3] & (1 << (idx & 0x7)))) { return; } } else if (0 != memcmp(buf, BX_NE2K_THIS s.physaddr, 6)) { return; } } else { BX_DEBUG(("rx_frame promiscuous receive")); } // BX_INFO(("rx_frame %d to %x:%x:%x:%x:%x:%x from %x:%x:%x:%x:%x:%x", // io_len, // pktbuf[0], pktbuf[1], pktbuf[2], pktbuf[3], pktbuf[4], pktbuf[5], // pktbuf[6], pktbuf[7], pktbuf[8], pktbuf[9], pktbuf[10], pktbuf[11])); nextpage = BX_NE2K_THIS s.curr_page + pages; if (nextpage >= BX_NE2K_THIS s.page_stop) { nextpage -= BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.page_start; } // Setup packet header pkthdr[0] = 0; // rx status - old behavior pkthdr[0] = 1; // Probably better to set it all the time // rather than set it to 0, which is clearly wrong. if (pktbuf[0] & 0x01) { pkthdr[0] |= 0x20; // rx status += multicast packet } pkthdr[1] = nextpage; // ptr to next packet pkthdr[2] = (io_len + 4) & 0xff; // length-low pkthdr[3] = (io_len + 4) >> 8; // length-hi // copy into buffer, update curpage, and signal interrupt if config'd startptr = & BX_NE2K_THIS s.mem[BX_NE2K_THIS s.curr_page * 256 - BX_NE2K_MEMSTART]; if ((nextpage > BX_NE2K_THIS s.curr_page) || ((BX_NE2K_THIS s.curr_page + pages) == BX_NE2K_THIS s.page_stop)) { memcpy(startptr, pkthdr, 4); memcpy(startptr + 4, buf, io_len); BX_NE2K_THIS s.curr_page = nextpage; } else { int endbytes = (BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.curr_page) * 256; memcpy(startptr, pkthdr, 4); memcpy(startptr + 4, buf, endbytes - 4); startptr = & BX_NE2K_THIS s.mem[BX_NE2K_THIS s.page_start * 256 - BX_NE2K_MEMSTART]; memcpy(startptr, (void *)(pktbuf + endbytes - 4), io_len - endbytes + 8); BX_NE2K_THIS s.curr_page = nextpage; } BX_NE2K_THIS s.RSR.rx_ok = 1; BX_NE2K_THIS s.RSR.rx_mbit = (bx_bool)((pktbuf[0] & 0x01) > 0); BX_NE2K_THIS s.ISR.pkt_rx = 1; if (BX_NE2K_THIS s.IMR.rx_inte) { set_irq_level(1); } } void bx_ne2k_c::init(void) { char devname[16]; Bit8u macaddr[6]; bx_list_c *base; BX_DEBUG(("Init $Id: ne2k.cc,v 1.107 2009-04-21 20:27:35 sshwarts Exp $")); // Read in values from config interface base = (bx_list_c*) SIM->get_param(BXPN_NE2K); memcpy(macaddr, SIM->get_param_string("macaddr", base)->getptr(), 6); BX_NE2K_THIS s.pci_enabled = 0; strcpy(devname, "NE2000 NIC"); #if BX_SUPPORT_PCI if ((SIM->get_param_bool(BXPN_I440FX_SUPPORT)->get()) && (DEV_is_pci_device(BX_PLUGIN_NE2K))) { BX_NE2K_THIS s.pci_enabled = 1; strcpy(devname, "NE2000 PCI NIC"); BX_NE2K_THIS s.devfunc = 0x00; DEV_register_pci_handlers(this, &BX_NE2K_THIS s.devfunc, BX_PLUGIN_NE2K, devname); for (unsigned i=0; i<256; i++) BX_NE2K_THIS s.pci_conf[i] = 0x0; // readonly registers BX_NE2K_THIS s.pci_conf[0x00] = 0xec; BX_NE2K_THIS s.pci_conf[0x01] = 0x10; BX_NE2K_THIS s.pci_conf[0x02] = 0x29; BX_NE2K_THIS s.pci_conf[0x03] = 0x80; BX_NE2K_THIS s.pci_conf[0x04] = 0x01; BX_NE2K_THIS s.pci_conf[0x0a] = 0x00; BX_NE2K_THIS s.pci_conf[0x0b] = 0x02; BX_NE2K_THIS s.pci_conf[0x0e] = 0x00; BX_NE2K_THIS s.pci_conf[0x10] = 0x01; BX_NE2K_THIS s.pci_conf[0x3d] = BX_PCI_INTA; BX_NE2K_THIS s.base_address = 0x0; } #endif if (BX_NE2K_THIS s.tx_timer_index == BX_NULL_TIMER_HANDLE) { BX_NE2K_THIS s.tx_timer_index = bx_pc_system.register_timer(this, tx_timer_handler, 0, 0,0, "ne2k"); // one-shot, inactive } // Register the IRQ and i/o port addresses if (!BX_NE2K_THIS s.pci_enabled) { BX_NE2K_THIS s.base_address = SIM->get_param_num("ioaddr", base)->get(); BX_NE2K_THIS s.base_irq = SIM->get_param_num("irq", base)->get(); DEV_register_irq(BX_NE2K_THIS s.base_irq, "NE2000 ethernet NIC"); DEV_register_ioread_handler_range(BX_NE2K_THIS_PTR, read_handler, BX_NE2K_THIS s.base_address, BX_NE2K_THIS s.base_address + 0x0F, devname, 3); DEV_register_iowrite_handler_range(BX_NE2K_THIS_PTR, write_handler, BX_NE2K_THIS s.base_address, BX_NE2K_THIS s.base_address + 0x0F, devname, 3); DEV_register_ioread_handler(BX_NE2K_THIS_PTR, read_handler, BX_NE2K_THIS s.base_address + 0x10, devname, 3); DEV_register_iowrite_handler(BX_NE2K_THIS_PTR, write_handler, BX_NE2K_THIS s.base_address + 0x10, devname, 3); DEV_register_ioread_handler(BX_NE2K_THIS_PTR, read_handler, BX_NE2K_THIS s.base_address + 0x1F, devname, 1); DEV_register_iowrite_handler(BX_NE2K_THIS_PTR, write_handler, BX_NE2K_THIS s.base_address + 0x1F, devname, 1); BX_INFO(("port 0x%x/32 irq %d mac %02x:%02x:%02x:%02x:%02x:%02x", BX_NE2K_THIS s.base_address, BX_NE2K_THIS s.base_irq, macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5])); } else { BX_INFO(("%s initialized mac %02x:%02x:%02x:%02x:%02x:%02x", devname, macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5])); } // Initialise the mac address area by doubling the physical address BX_NE2K_THIS s.macaddr[0] = macaddr[0]; BX_NE2K_THIS s.macaddr[1] = macaddr[0]; BX_NE2K_THIS s.macaddr[2] = macaddr[1]; BX_NE2K_THIS s.macaddr[3] = macaddr[1]; BX_NE2K_THIS s.macaddr[4] = macaddr[2]; BX_NE2K_THIS s.macaddr[5] = macaddr[2]; BX_NE2K_THIS s.macaddr[6] = macaddr[3]; BX_NE2K_THIS s.macaddr[7] = macaddr[3]; BX_NE2K_THIS s.macaddr[8] = macaddr[4]; BX_NE2K_THIS s.macaddr[9] = macaddr[4]; BX_NE2K_THIS s.macaddr[10] = macaddr[5]; BX_NE2K_THIS s.macaddr[11] = macaddr[5]; // ne2k signature for (int i = 12; i < 32; i++) BX_NE2K_THIS s.macaddr[i] = 0x57; // Attach to the simulated ethernet dev const char *ethmod = SIM->get_param_enum("ethmod", base)->get_selected(); BX_NE2K_THIS ethdev = eth_locator_c::create(ethmod, SIM->get_param_string("ethdev", base)->getptr(), (const char *) SIM->get_param_string("macaddr", base)->getptr(), rx_handler, this, SIM->get_param_string("script", base)->getptr()); if (BX_NE2K_THIS ethdev == NULL) { BX_PANIC(("could not find eth module %s", ethmod)); // if they continue, use null. BX_INFO(("could not find eth module %s - using null instead", ethmod)); BX_NE2K_THIS ethdev = eth_locator_c::create("null", NULL, (const char *) SIM->get_param_string("macaddr", base)->getptr(), rx_handler, this, ""); if (BX_NE2K_THIS ethdev == NULL) BX_PANIC(("could not locate null module")); } } void bx_ne2k_c::set_irq_level(bx_bool level) { if (BX_NE2K_THIS s.pci_enabled) { #if BX_SUPPORT_PCI DEV_pci_set_irq(BX_NE2K_THIS s.devfunc, BX_NE2K_THIS s.pci_conf[0x3d], level); #endif } else { if (level) { DEV_pic_raise_irq(BX_NE2K_THIS s.base_irq); } else { DEV_pic_lower_irq(BX_NE2K_THIS s.base_irq); } } } #if BX_SUPPORT_PCI // pci configuration space read callback handler Bit32u bx_ne2k_c::pci_read_handler(Bit8u address, unsigned io_len) { Bit32u value = 0; for (unsigned i=0; i 0x13) && (address < 0x34)) return; for (unsigned i=0; i> (i*8)) & 0xFF; switch (address+i) { case 0x05: case 0x06: case 0x3d: break; case 0x04: BX_NE2K_THIS s.pci_conf[address+i] = value8 & 0x03; break; case 0x3c: if (value8 != oldval) { BX_INFO(("new irq line = %d", value8)); BX_NE2K_THIS s.pci_conf[address+i] = value8; } break; case 0x10: value8 = (value8 & 0xfc) | 0x01; case 0x11: case 0x12: case 0x13: baseaddr_change |= (value8 != oldval); default: BX_NE2K_THIS s.pci_conf[address+i] = value8; BX_DEBUG(("NE2000 PCI NIC write register 0x%02x value 0x%02x", address+i, value8)); } } if (baseaddr_change) { if (DEV_pci_set_base_io(BX_NE2K_THIS_PTR, read_handler, write_handler, &BX_NE2K_THIS s.base_address, &BX_NE2K_THIS s.pci_conf[0x10], 32, &ne2k_iomask[0], "NE2000 PCI NIC")) { BX_INFO(("new base address: 0x%04x", BX_NE2K_THIS s.base_address)); } } } #endif /* BX_SUPPORT_PCI */ #if BX_DEBUGGER /* * this implements the info ne2k commands in the debugger. * info ne2k - shows all registers * info ne2k page N - shows all registers in a page * info ne2k page N reg M - shows just one register */ #define SHOW_FIELD(reg,field) do { \ if (n>0 && !(n%5)) dbg_printf ("\n "); \ dbg_printf ("%s=%d ", #field, BX_NE2K_THIS s.reg.field); \ n++; \ } while (0); #define BX_HIGH_BYTE(x) ((0xff00 & (x)) >> 8) #define BX_LOW_BYTE(x) (0x00ff & (x)) #define BX_DUPLICATE(n) if (brief && num!=n) break; void bx_ne2k_c::print_info(FILE *fp, int page, int reg, int brief) { int i; int n = 0; if (page < 0) { for (page=0; page<=2; page++) theNE2kDevice->print_info(fp, page, reg, 1); // tell them how to use this command dbg_printf("\nHow to use the info ne2k command:\n"); dbg_printf("info ne2k - show all registers\n"); dbg_printf("info ne2k page N - show registers in page N\n"); dbg_printf("info ne2k page N reg M - show just one register\n"); return; } if (page > 2) { dbg_printf("NE2K has only pages 0, 1, and 2. Page %d is out of range.\n", page); return; } if (reg < 0) { dbg_printf("NE2K registers, page %d\n", page); dbg_printf("----------------------\n"); for (reg=0; reg<=15; reg++) theNE2kDevice->print_info (fp, page, reg, 1); dbg_printf("----------------------\n"); return; } if (reg > 15) { dbg_printf("NE2K has only registers 0-15 (0x0-0xf). Register %d is out of range.\n", reg); return; } if (!brief) { dbg_printf("NE2K Info - page %d, register 0x%02x\n", page, reg); dbg_printf("----------------------------------\n"); } int num = page*0x100 + reg; switch (num) { case 0x0000: case 0x0100: case 0x0200: dbg_printf ("CR (Command register):\n "); SHOW_FIELD(CR, stop); SHOW_FIELD(CR, start); SHOW_FIELD(CR, tx_packet); SHOW_FIELD(CR, rdma_cmd); SHOW_FIELD(CR, pgsel); dbg_printf("\n"); break; case 0x0003: dbg_printf("BNRY = Boundary Pointer = 0x%02x\n", BX_NE2K_THIS s.bound_ptr); break; case 0x0004: dbg_printf("TSR (Transmit Status Register), read-only:\n "); SHOW_FIELD(TSR, tx_ok); SHOW_FIELD(TSR, reserved); SHOW_FIELD(TSR, collided); SHOW_FIELD(TSR, aborted); SHOW_FIELD(TSR, no_carrier); SHOW_FIELD(TSR, fifo_ur); SHOW_FIELD(TSR, cd_hbeat); SHOW_FIELD(TSR, ow_coll); dbg_printf("\n"); // fall through into TPSR, no break line. case 0x0204: dbg_printf("TPSR = Transmit Page Start = 0x%02x\n", BX_NE2K_THIS s.tx_page_start); break; case 0x0005: case 0x0006: BX_DUPLICATE(0x0005); dbg_printf("NCR = Number of Collisions Register (read-only) = 0x%02x\n", BX_NE2K_THIS s.num_coll); dbg_printf("TBCR1,TBCR0 = Transmit Byte Count = %02x %02x\n", BX_HIGH_BYTE(BX_NE2K_THIS s.tx_bytes), BX_LOW_BYTE(BX_NE2K_THIS s.tx_bytes)); dbg_printf("FIFO = %02x\n", BX_NE2K_THIS s.fifo); break; case 0x0007: dbg_printf("ISR (Interrupt Status Register):\n "); SHOW_FIELD(ISR, pkt_rx); SHOW_FIELD(ISR, pkt_tx); SHOW_FIELD(ISR, rx_err); SHOW_FIELD(ISR, tx_err); SHOW_FIELD(ISR, overwrite); SHOW_FIELD(ISR, cnt_oflow); SHOW_FIELD(ISR, rdma_done); SHOW_FIELD(ISR, reset); dbg_printf("\n"); break; case 0x0008: case 0x0009: BX_DUPLICATE(0x0008); dbg_printf("CRDA1,0 = Current remote DMA address = %02x %02x\n", BX_HIGH_BYTE(BX_NE2K_THIS s.remote_dma), BX_LOW_BYTE(BX_NE2K_THIS s.remote_dma)); dbg_printf("RSAR1,0 = Remote start address = %02x %02x\n", BX_HIGH_BYTE(s.remote_start), BX_LOW_BYTE(s.remote_start)); break; case 0x000a: case 0x000b: BX_DUPLICATE(0x000a); dbg_printf("RCBR1,0 = Remote byte count = %02x\n", BX_NE2K_THIS s.remote_bytes); break; case 0x000c: dbg_printf("RSR (Receive Status Register), read-only:\n "); SHOW_FIELD(RSR, rx_ok); SHOW_FIELD(RSR, bad_crc); SHOW_FIELD(RSR, bad_falign); SHOW_FIELD(RSR, fifo_or); SHOW_FIELD(RSR, rx_missed); SHOW_FIELD(RSR, rx_mbit); SHOW_FIELD(RSR, rx_disabled); SHOW_FIELD(RSR, deferred); dbg_printf("\n"); // fall through into RCR case 0x020c: dbg_printf("RCR (Receive Configuration Register):\n "); SHOW_FIELD(RCR, errors_ok); SHOW_FIELD(RCR, runts_ok); SHOW_FIELD(RCR, broadcast); SHOW_FIELD(RCR, multicast); SHOW_FIELD(RCR, promisc); SHOW_FIELD(RCR, monitor); SHOW_FIELD(RCR, reserved); dbg_printf("\n"); break; case 0x000d: dbg_printf("CNTR0 = Tally Counter 0 (Frame alignment errors) = %02x\n", BX_NE2K_THIS s.tallycnt_0); // fall through into TCR case 0x020d: dbg_printf("TCR (Transmit Configuration Register):\n "); SHOW_FIELD(TCR, crc_disable); SHOW_FIELD(TCR, loop_cntl); SHOW_FIELD(TCR, ext_stoptx); SHOW_FIELD(TCR, coll_prio); SHOW_FIELD(TCR, reserved); dbg_printf("\n"); break; case 0x000e: dbg_printf("CNTR1 = Tally Counter 1 (CRC Errors) = %02x\n", BX_NE2K_THIS s.tallycnt_1); // fall through into DCR case 0x020e: dbg_printf("DCR (Data Configuration Register):\n "); SHOW_FIELD(DCR, wdsize); SHOW_FIELD(DCR, endian); SHOW_FIELD(DCR, longaddr); SHOW_FIELD(DCR, loop); SHOW_FIELD(DCR, auto_rx); SHOW_FIELD(DCR, fifo_size); dbg_printf("\n"); break; case 0x000f: dbg_printf("CNTR2 = Tally Counter 2 (Missed Packet Errors) = %02x\n", BX_NE2K_THIS s.tallycnt_2); // fall through into IMR case 0x020f: dbg_printf("IMR (Interrupt Mask Register)\n "); SHOW_FIELD(IMR, rx_inte); SHOW_FIELD(IMR, tx_inte); SHOW_FIELD(IMR, rxerr_inte); SHOW_FIELD(IMR, txerr_inte); SHOW_FIELD(IMR, overw_inte); SHOW_FIELD(IMR, cofl_inte); SHOW_FIELD(IMR, rdma_inte); SHOW_FIELD(IMR, reserved); dbg_printf("\n"); break; case 0x0101: case 0x0102: BX_DUPLICATE(0x0101); case 0x0103: BX_DUPLICATE(0x0101); case 0x0104: BX_DUPLICATE(0x0101); case 0x0105: BX_DUPLICATE(0x0101); case 0x0106: BX_DUPLICATE(0x0101); dbg_printf("MAC address registers are located at page 1, registers 1-6.\n"); dbg_printf("The MAC address is "); for (i=0; i<=5; i++) dbg_printf("%02x%c", BX_NE2K_THIS s.physaddr[i], i<5?':' : '\n'); break; case 0x0107: dbg_printf("Current page is 0x%02x\n", BX_NE2K_THIS s.curr_page); break; case 0x0108: case 0x0109: BX_DUPLICATE(0x0108); case 0x010A: BX_DUPLICATE(0x0108); case 0x010B: BX_DUPLICATE(0x0108); case 0x010C: BX_DUPLICATE(0x0108); case 0x010D: BX_DUPLICATE(0x0108); case 0x010E: BX_DUPLICATE(0x0108); case 0x010F: BX_DUPLICATE(0x0108); dbg_printf("MAR0-7 (Multicast address registers 0-7) are set to:\n"); for (i=0; i<8; i++) dbg_printf("%02x ", BX_NE2K_THIS s.mchash[i]); dbg_printf("\nMAR0 is listed first.\n"); break; case 0x0001: case 0x0002: BX_DUPLICATE(0x0001); case 0x0201: BX_DUPLICATE(0x0001); case 0x0202: BX_DUPLICATE(0x0001); dbg_printf("PSTART = Page start register = %02x\n", BX_NE2K_THIS s.page_start); dbg_printf("PSTOP = Page stop register = %02x\n", BX_NE2K_THIS s.page_stop); dbg_printf("Local DMA address = %02x %02x\n", BX_HIGH_BYTE(BX_NE2K_THIS s.local_dma), BX_LOW_BYTE(BX_NE2K_THIS s.local_dma)); break; case 0x0203: dbg_printf("Remote Next Packet Pointer = %02x\n", BX_NE2K_THIS s.rempkt_ptr); break; case 0x0205: dbg_printf("Local Next Packet Pointer = %02x\n", BX_NE2K_THIS s.localpkt_ptr); break; case 0x0206: case 0x0207: BX_DUPLICATE(0x0206); dbg_printf("Address Counter= %02x %02x\n", BX_HIGH_BYTE(BX_NE2K_THIS s.address_cnt), BX_LOW_BYTE(BX_NE2K_THIS s.address_cnt)); break; case 0x0208: case 0x0209: BX_DUPLICATE(0x0208); case 0x020A: BX_DUPLICATE(0x0208); case 0x020B: BX_DUPLICATE(0x0208); if (!brief) dbg_printf ("Reserved\n"); case 0xffff: dbg_printf("IMR (Interrupt Mask Register):\n "); dbg_printf("\n"); break; default: dbg_printf("NE2K info: sorry, page %d register %d cannot be displayed.\n", page, reg); } if (!brief) dbg_printf("\n"); } #else void bx_ne2k_c::print_info (FILE *fp, int page, int reg, int brief) { } #endif #endif /* if BX_SUPPORT_NE2K */