// license:BSD-3-Clause // copyright-holders:Patrick Mackinlay /* * An implementation of the National Semiconductor DP83932C SONICâ„¢ (Systems- * Oriented Network Interface Controller) device. * * References: * * http://bitsavers.org/components/national/_dataBooks/1995_National_Ethernet_Databook.pdf * * TODO * - bus mode (big endian, interrupts active low) * - byte count mismatch * - data widths * - tally counters * - software reset * - watchdog timers * - loopback modes * - programmable outputs and extended bus mode */ #include "emu.h" #include "dp83932c.h" #include "hashing.h" #include "multibyte.h" #define LOG_COMMAND (1U << 1) #define LOG_FILTER (1U << 2) #define LOG_PACKETS (1U << 3) //#define VERBOSE (LOG_GENERAL|LOG_COMMAND|LOG_FILTER|LOG_PACKETS) #include "logmacro.h" #define EA(hi, lo) ((u32(hi) << 16) | lo) DEFINE_DEVICE_TYPE(DP83932C, dp83932c_device, "dp83932c", "National Semiconductor DP83932C SONIC") static constexpr u32 FCS_RESIDUE = 0xdebb20e3; static char const *const regname[] = { "CR", "DCR", "RCR", "TCR", "IMR", "ISR", "UTDA", "CTDA", "TPS", "TFC", "TSA0", "TSA1", "TFS", "URDA", "CRDA", "CRBA0", "CRBA1", "RBWC0", "RBWC1", "EOBC", "URRA", "RSA", "REA", "RRP", "RWP", "TRBA0", "TRBA1", "TBWC0", "TBWC1", "ADDR0", "ADDR1", "LLFA", "TTDA", "CEP", "CAP2", "CAP1", "CAP0", "CE", "CDP", "CDC", "SR", "WT0", "WT1", "RSC", "CRCT", "FAET", "MPT", "MDT", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "DCR2", }; static u16 const regmask[] = { 0x03bf, 0xbfff, 0xfe00, 0xf000, 0x7fff, 0x7fff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x000f, 0x0000, 0x0000, 0x0000, 0xffff, 0xfffe, 0x001f, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xf017, }; dp83932c_device::dp83932c_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : device_t(mconfig, DP83932C, tag, owner, clock) , device_network_interface(mconfig, *this, 10) , m_bus(*this, finder_base::DUMMY_TAG, 0) , m_out_int(*this) , m_int_state(false) { } void dp83932c_device::map(address_map &map) { map(0x00, 0x7f).rw(FUNC(dp83932c_device::reg_r), FUNC(dp83932c_device::reg_w)); } void dp83932c_device::device_start() { m_command = timer_alloc(FUNC(dp83932c_device::command), this); save_item(NAME(m_int_state)); save_item(NAME(m_reg)); save_item(NAME(m_cam)); for (u16 ® : m_reg) reg = 0; m_reg[SR] = 6; // silicon revision for DP83932CVF } void dp83932c_device::device_reset() { m_reg[CR] = CR_RST | CR_STP | CR_RXDIS; m_reg[DCR] &= ~(DCR_EXBUS | DCR_LBR); // TODO: sample USR1,0 m_reg[RCR] &= ~(RCR_RNT | RCR_BRD | RCR_LB); m_reg[TCR] |= TCR_NCRS | TCR_PTX; m_reg[TCR] &= ~(TCR_TPC | TCR_BCM); m_reg[IMR] = 0; m_reg[ISR] = 0; m_reg[EOBC] = 0x02f8; m_reg[CE] = 0; m_reg[RSC] = 0; m_reg[DCR2] = 0; update_interrupts(); } int dp83932c_device::recv_start_cb(u8 *buf, int length) { // check for receiver disabled or overflow condition if (!(m_reg[CR] & CR_RXEN) || (m_reg[ISR] & (ISR_RDE | ISR_RBE))) return -1; // reload receive descriptor address after end of list encountered if (BIT(m_reg[CRDA], 0)) { m_reg[CRDA] = read_bus_word(EA(m_reg[URDA], m_reg[LLFA])); if (BIT(m_reg[CRDA], 0)) return -2; } m_reg[RCR] &= ~(RCR_MC | RCR_BC | RCR_LPKT | RCR_CRCR | RCR_FAER | RCR_LBK | RCR_PRX); // address filter if (!address_filter(buf)) return -1; if ((length < 64) && !(m_reg[RCR] & RCR_RNT)) return -1; u32 const fcs = util::crc32_creator::simple(buf, length); if (~fcs != FCS_RESIDUE) { if (m_reg[RCR] & RCR_ERR) m_reg[RCR] |= RCR_CRCR; else return -1; } else m_reg[RCR] |= RCR_PRX; // loopback if (m_reg[RCR] & RCR_LB) m_reg[RCR] |= RCR_LBK; dump_bytes(buf, length); // save rba pointer registers m_reg[TRBA0] = m_reg[CRBA0]; m_reg[TRBA1] = m_reg[CRBA1]; m_reg[TBWC0] = m_reg[RBWC0]; m_reg[TBWC1] = m_reg[RBWC1]; // store data to rba // TODO: word/dword transfers (allow unaligned) // TODO: pad to word/dword size with 0xff // TODO: check for buffer overflow offs_t const rba = EA(m_reg[CRBA1], m_reg[CRBA0]); for (unsigned i = 0; i < length; i++) m_bus->write_byte(rba + i, buf[i]); // update remaining buffer word count u32 const rbwc = ((u32(m_reg[RBWC1]) << 16) | m_reg[RBWC0]) - (length + 1) / 2; LOG("recv_start_cb length %d buffer %d remaining %d\n", length, ((u32(m_reg[RBWC1]) << 16) | m_reg[RBWC0]) * 2, rbwc * 2); m_reg[RBWC1] = rbwc >> 16; m_reg[RBWC0] = u16(rbwc); if (rbwc < m_reg[EOBC]) m_reg[RCR] |= RCR_LPKT; // write status to rda // TODO: don't write the rda if rba limit exceeded (buffer overflow) unsigned const width = (m_reg[DCR] & DCR_DW) ? 4 : 2; offs_t const rda = EA(m_reg[URDA], m_reg[CRDA]); write_bus_word(rda + 0 * width, m_reg[RCR]); write_bus_word(rda + 1 * width, length); write_bus_word(rda + 2 * width, m_reg[CRBA0]); write_bus_word(rda + 3 * width, m_reg[CRBA1]); write_bus_word(rda + 4 * width, m_reg[RSC]); m_reg[LLFA] = m_reg[CRDA] + 5 * width; m_reg[CRDA] = read_bus_word(rda + 5 * width); // check for end of list if (BIT(m_reg[CRDA], 0)) { LOG("recv_start_cb end of list\n"); m_reg[ISR] |= ISR_RDE; } else write_bus_word(rda + 6 * width, 0); // handle buffer exhaustion if (rbwc < m_reg[EOBC]) read_rra(); else m_reg[RSC] = (m_reg[RSC] & 0xff00) | u8(m_reg[RSC] + 1); return length; } void dp83932c_device::recv_complete_cb(int result) { if (result > 0) { m_reg[ISR] |= ISR_PKTRX; update_interrupts(); } } void dp83932c_device::reg_w(offs_t offset, u16 data) { LOG("reg_w register %s data 0x%04x (%s)\n", regname[offset], data, machine().describe_context()); // TODO: can only write during reset: DCR, DCR2 switch (offset) { case CR: if (m_reg[CR] & CR_RST) { if (!(data & CR_RST)) { LOGMASKED(LOG_COMMAND, "exit software reset\n"); m_reg[CR] &= ~CR_RST; } } else if (data & CR_RST) { LOGMASKED(LOG_COMMAND, "enter software reset\n"); m_command->adjust(attotime::never); m_reg[CR] &= ~(CR_LCAM | CR_RRRA | CR_TXP | CR_HTX); m_reg[CR] |= (CR_RST | CR_RXDIS); } else { u16 cmd_to_run = data & regmask[offset]; if (m_reg[CR] & CR_TXP) { // Per section 3.5.4 in the datasheet, TDAs can be dynamically added. // The TXP command will be re-sent by the host, but doesn't do anything // unless the SONIC finished the last commmand right before the TDA was // appended to the list. So, null the CR_TXP bit if it was already set // so we don't smash the currently running transmission. cmd_to_run &= ~CR_TXP; } m_reg[offset] |= data & regmask[offset]; m_command->adjust(attotime::zero, cmd_to_run); } break; case RCR: m_reg[offset] = (m_reg[offset] & ~regmask[offset]) | (data & regmask[offset]); set_loopback(bool(m_reg[offset] & RCR_LB)); break; case IMR: m_reg[offset] = (m_reg[offset] & ~regmask[offset]) | (data & regmask[offset]); update_interrupts(); break; case ISR: // reload rra when rbe is cleared if ((m_reg[offset] & ISR_RBE) && (data & ISR_RBE)) read_rra(); m_reg[offset] &= ~(data & regmask[offset]); update_interrupts(); break; case CRCT: case FAET: case MPT: // inverted m_reg[offset] = ~data; break; default: if (regmask[offset]) m_reg[offset] = (m_reg[offset] & ~regmask[offset]) | (data & regmask[offset]); else logerror("write to read-only register %s data 0x%04x (%s)\n", regname[offset], data, machine().describe_context()); break; } } void dp83932c_device::command(s32 param) { if (param & CR_HTX) { LOGMASKED(LOG_COMMAND, "halt transmission\n"); m_reg[CR] &= ~CR_TXP; } if (param & CR_TXP) { LOGMASKED(LOG_COMMAND, "transmit\n"); m_reg[CR] &= ~CR_HTX; transmit(); } if (param & CR_RXDIS) { LOGMASKED(LOG_COMMAND, "receiver disable\n"); m_reg[CR] &= ~CR_RXEN; } if (param & CR_RXEN) { LOGMASKED(LOG_COMMAND, "receiver enable\n"); m_reg[CR] &= ~CR_RXDIS; } if (param & CR_STP) { LOGMASKED(LOG_COMMAND, "stop timer\n"); m_reg[CR] &= ~CR_ST; } if (param & CR_ST) { LOGMASKED(LOG_COMMAND, "start timer\n"); m_reg[CR] &= ~CR_STP; } if (param & CR_RRRA) { LOGMASKED(LOG_COMMAND, "read rra\n"); read_rra(true); } if (param & CR_LCAM) { LOGMASKED(LOG_COMMAND, "load cam\n"); load_cam(); } update_interrupts(); } void dp83932c_device::transmit() { unsigned const width = (m_reg[DCR] & DCR_DW) ? 4 : 2; m_reg[TTDA] = m_reg[CTDA]; offs_t const tda = EA(m_reg[UTDA], m_reg[CTDA]); unsigned word = 1; // read control information from tda and load registers u16 const tcr = m_reg[TCR]; m_reg[TCR] = read_bus_word(tda + word++ * width) & TCR_TPC; m_reg[TPS] = read_bus_word(tda + word++ * width); m_reg[TFC] = read_bus_word(tda + word++ * width); LOG("transmit tda 0x%08x tps %d tfc %d\n", tda, m_reg[TPS], m_reg[TFC]); // check for programmable interrupt if ((m_reg[TCR] & TCR_PINT) && !(tcr & TCR_PINT)) m_reg[ISR] |= ISR_PINT; // FIXME: abort if tps > buffer size u8 buf[1520]; unsigned length = 0; // read fragments into buffer for (unsigned fragment = 0; fragment < m_reg[TFC]; fragment++) { // read fragment address and size m_reg[TSA0] = read_bus_word(tda + word++ * width); m_reg[TSA1] = read_bus_word(tda + word++ * width); m_reg[TFS] = read_bus_word(tda + word++ * width); offs_t const tsa = EA(m_reg[TSA1], m_reg[TSA0]); LOG("transmit tsa 0x%08x tfs %d\n", tsa, m_reg[TFS]); // FIXME: word/dword transfers (allow unaligned) for (unsigned byte = 0; byte < m_reg[TFS]; byte++) buf[length++] = m_bus->read_byte(tsa + byte); } // append fcs if not inhibited if (!(m_reg[TCR] & TCR_CRCI)) { u32 const crc = util::crc32_creator::simple(buf, length); // insert the fcs put_u32le(&buf[length], crc); length += 4; } // advance ctda to the link field m_reg[CTDA] += word * width; LOG("transmit length %d word %d tda 0x%08x\n", length, word, EA(m_reg[UTDA], m_reg[CTDA])); // transmit data dump_bytes(buf, length); send(buf, length, 4); } void dp83932c_device::send_complete_cb(int result) { // TODO: errors if (result > 0) { // TODO: number of collisions m_reg[TCR] |= TCR_PTX; } // write descriptor status write_bus_word(EA(m_reg[UTDA], m_reg[TTDA]), m_reg[TCR] & TCR_TPS); // check for halt if (!(m_reg[CR] & CR_HTX)) { // load next descriptor address m_reg[CTDA] = read_bus_word(EA(m_reg[UTDA], m_reg[CTDA])); // check for end of list if (BIT(m_reg[CTDA], 0)) { m_reg[ISR] |= ISR_TXDN; m_reg[CR] &= ~CR_TXP; update_interrupts(); } else // transmit next packet if (m_command->enabled()) m_command->set_param(m_command->param() | CR_TXP); else m_command->adjust(attotime::zero, CR_TXP); } else m_reg[CR] &= ~CR_TXP; } void dp83932c_device::read_rra(bool command) { unsigned const width = (m_reg[DCR] & DCR_DW) ? 4 : 2; offs_t const rrp = EA(m_reg[URRA], m_reg[RRP]); m_reg[CRBA0] = read_bus_word(rrp + 0 * width); m_reg[CRBA1] = read_bus_word(rrp + 1 * width); m_reg[RBWC0] = read_bus_word(rrp + 2 * width); m_reg[RBWC1] = read_bus_word(rrp + 3 * width); LOG("read_rra crba 0x%08x rbwc 0x%08x\n", EA(m_reg[CRBA1], m_reg[CRBA0]), EA(m_reg[RBWC1], m_reg[RBWC0])); // advance rrp m_reg[RRP] += 4 * width; // check for wrapping and resource exhaustion if (m_reg[RRP] == m_reg[REA]) m_reg[RRP] = m_reg[RSA]; if (m_reg[RRP] == m_reg[RWP]) m_reg[ISR] |= ISR_RBE; if (command) m_reg[CR] &= ~CR_RRRA; else m_reg[RSC] = (m_reg[RSC] & 0xff00) + 0x100; } void dp83932c_device::load_cam() { unsigned const width = (m_reg[DCR] & DCR_DW) ? 4 : 2; while (m_reg[CDC]) { offs_t const cdp = EA(m_reg[URRA], m_reg[CDP]); u16 const cep = read_bus_word(cdp + 0 * width) & 0xf; u16 const cap0 = read_bus_word(cdp + 1 * width); u16 const cap1 = read_bus_word(cdp + 2 * width); u16 const cap2 = read_bus_word(cdp + 3 * width); // FIXME: documented byte/word order doesn't match emulation LOG("load_cam entry %2d %02x:%02x:%02x:%02x:%02x:%02x\n", cep, u8(cap0), cap0 >> 8, u8(cap1), cap1 >> 8, u8(cap2), cap2 >> 8); m_cam[cep] = (u64(swapendian_int16(cap0)) << 32) | (u64(swapendian_int16(cap1)) << 16) | (u64(swapendian_int16(cap2)) << 0); m_reg[CDP] += 4 * width; m_reg[CDC]--; } // read cam enable m_reg[CE] = read_bus_word(EA(m_reg[URRA], m_reg[CDP])); LOG("load_cam enable 0x%04x\n", m_reg[CE]); m_reg[CR] &= ~CR_LCAM; m_reg[ISR] |= ISR_LCD; } void dp83932c_device::update_interrupts() { bool const int_state = bool(m_reg[ISR] & m_reg[IMR]); if (int_state != m_int_state) { m_int_state = int_state; m_out_int(m_int_state); } } bool dp83932c_device::address_filter(u8 *buf) { if (m_reg[RCR] & RCR_PRO) { LOGMASKED(LOG_FILTER, "address_filter accepted (promiscuous)\n"); return true; } u64 const address = get_u48be(buf); // broadcast if ((address == 0xffff'ffffffffULL) && (m_reg[RCR] & (RCR_AMC | RCR_BRD))) { LOGMASKED(LOG_FILTER, "address_filter accepted (broadcast) %02x-%02x-%02x-%02x-%02x-%02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); m_reg[RCR] |= RCR_BC; return true; } // multicast if ((address & 0x0100'00000000ULL) && (m_reg[RCR] & RCR_AMC)) { LOGMASKED(LOG_FILTER, "address_filter accepted (multicast) %02x-%02x-%02x-%02x-%02x-%02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); m_reg[RCR] |= RCR_MC; return true; } // content addressable memory for (unsigned i = 0; i < 16; i++) { if ((address == m_cam[i]) && BIT(m_reg[CE], i)) { LOGMASKED(LOG_FILTER, "address_filter accepted (cam entry %d match) %02x-%02x-%02x-%02x-%02x-%02x\n", i, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); return true; } } return false; } void dp83932c_device::dump_bytes(u8 *buf, int length) { if (VERBOSE & LOG_PACKETS) { // pad with zeros to 8-byte boundary for (int i = 0; i < 8 - (length % 8); i++) buf[length + i] = 0; // dump length / 8 (rounded up) groups of 8 bytes for (int i = 0; i < (length + 7) / 8; i++) LOGMASKED(LOG_PACKETS, "%02x %02x %02x %02x %02x %02x %02x %02x\n", buf[i * 8 + 0], buf[i * 8 + 1], buf[i * 8 + 2], buf[i * 8 + 3], buf[i * 8 + 4], buf[i * 8 + 5], buf[i * 8 + 6], buf[i * 8 + 7]); } } u16 dp83932c_device::read_bus_word(offs_t address) { return (m_reg[DCR] & DCR_DW) ? m_bus->read_dword(address) : m_bus->read_word(address); } void dp83932c_device::write_bus_word(offs_t address, u16 data) { if (m_reg[DCR] & DCR_DW) m_bus->write_dword(address, data); else m_bus->write_word(address, data); }