// license:BSD-3-Clause // copyright-holders:Patrick Mackinlay /* * An implementation of the Intel 82586 and 82596 Ethernet controller devices. * * This driver covers the following devices: * * - 82586 - 16/24 data/address bus, 6/8/10 MHz * - 82596SX - 16/32 data/address bus, 16/20 MHz * - 82596DX - 32/32 data/address bus, 25/33 MHz * - 82596CA - 32/32 data/address bus, 16/20/25/33 MHz * * This implementation should cover all of the above reasonably well, but * no testing of big endian mode in particular, and very limited testing * of the 82596 in non-linear modes has been done so far. * * Some documents covering the above include: * * http://bitsavers.org/pdf/intel/_dataBooks/1991_Microcommunications.pdf * http://bitsavers.org/pdf/intel/_dataBooks/1996_Networking.pdf * https://www.intel.com/assets/pdf/general/82596ca.pdf * * TODO * - testing for 82596 big endian and non-linear modes * - more complete statistics capturing * - 82596 monitor mode * - throttle timers and diagnostic command * - special case handling for different 82596 steppings in big endian mode */ #include "emu.h" #include "i82586.h" #include "hashing.h" #define LOG_GENERAL (1U << 0) #define LOG_FRAMES (1U << 1) #define LOG_FILTER (1U << 2) #define LOG_CONFIG (1U << 3) //#define VERBOSE (LOG_GENERAL | LOG_FRAMES | LOG_FILTER | LOG_CONFIG) #include "logmacro.h" // disable FCS insertion (on transmit) and checking (on receive) because pcap doesn't expose them #define I82586_FCS 0 ALLOW_SAVE_TYPE(i82586_base_device::cu_state); ALLOW_SAVE_TYPE(i82586_base_device::ru_state); DEFINE_DEVICE_TYPE(I82586, i82586_device, "i82586", "Intel 82586 IEEE 802.3 Ethernet LAN Coprocessor") DEFINE_DEVICE_TYPE(I82596_LE16, i82596_le16_device, "i82596sx_le", "Intel 82596 SX High-Performance 32-Bit Local Area Network Coprocessor (little)") DEFINE_DEVICE_TYPE(I82596_BE16, i82596_be16_device, "i82596sx_be", "Intel 82596 SX High-Performance 32-Bit Local Area Network Coprocessor (big)") DEFINE_DEVICE_TYPE(I82596_LE32, i82596_le32_device, "i82596dx_le", "Intel 82596 DX/CA High-Performance 32-Bit Local Area Network Coprocessor (little)") DEFINE_DEVICE_TYPE(I82596_BE32, i82596_be32_device, "i82596dx_be", "Intel 82596 DX/CA High-Performance 32-Bit Local Area Network Coprocessor (big)") // Ethernet broadcast address static const u8 ETH_BROADCAST[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; // configure parameter default values static const u8 CFG_DEFAULTS[] = { 0x00, 0xc8, 0x40, 0x26, 0x00, 0x60, 0x00, 0xf2, 0x00, 0x00, 0x40, 0xff, 0x00, 0x3f }; // describes parameters and default values for logging static const struct { const char *const name, *const unit; const u8 dflt, byte, mask, shift; const bool ieee8023; } CFG_PARAMS[] = { { "address length", "bytes", 6, 3, 0x07, 0, true }, { "a/l field location", "located in fd", 0, 3, 0x08, 3, false }, { "auto retransmit", "auto retransmit enable", 1, 11, 0x08, 3, false }, { "bitstuffing/eoc", "eoc", 0, 8, 0x40, 6, false }, { "broadcast disable", "broadcast reception enabled", 0, 8, 0x02, 1, false }, { "cdbsac", "disabled", 1, 11, 0x10, 4, false }, { "cdt filter", "bit times", 0, 9, 0x70, 4, false }, { "cdt src", "external collision detection", 0, 9, 0x80, 7, false }, { "crc in memory", "crc not transferred to memory", 1, 11, 0x04, 2, false }, { "crc-16/crc-32", "crc-32", 0, 8, 0x20, 5, true }, { "crs filter", "bit times", 0, 9, 0x07, 0, false }, { "crs src", "external crs", 0, 9, 0x08, 3, false }, { "disbof", "backoff enabled", 0, 13, 0x80, 7, false }, { "ext loopback", "disabled", 0, 3, 0x80, 7, false }, { "exponential priority", "802.3 algorithm", 0, 4, 0x70, 4, true }, { "exponential backoff method", "802.3 algorithm", 0, 4, 0x80, 7, true }, { "full duplex (fdx)", "csma/cd protocol (no fdx)", 0, 12, 0x40, 6, false }, { "fifo threshold", "tx: 32 bytes, rx: 64 bytes", 8, 1, 0x0f, 0, false }, { "int loopback", "disabled", 0, 3, 0x40, 6, false }, { "interframe spacing", "bit times", 96, 5, 0xff, 0, true }, { "linear priority", "802.3 algorithm", 0, 4, 0x07, 0, true }, { "length field", "padding disabled", 1, 11, 0x02, 1, false }, { "min frame length", "bytes", 64, 10, 0xff, 0, true }, { "mc all", "disabled", 1, 11, 0x20, 5, false }, { "monitor", "disabled", 3, 11, 0xc0, 6, false }, { "manchester/nrz", "nrz", 0, 8, 0x04, 2, false }, { "multi ia", "disabled", 0, 14, 0x40, 6, false }, { "number of retries", "maximum number of retries", 15, 7, 0xf0, 4, true }, { "no crc insertion", "crc appended to frame", 0, 8, 0x10, 4, false }, { "prefetch bit in rbd", "disabled (valid only in new modes)", 0, 0, 0x80, 7, false }, { "preamble length", "bytes", 7, 3, 0x30, 4, true }, { "preamble until crs", "disabled", 1, 11, 0x01, 0, false }, { "promiscuous mode", "address filter on", 0, 8, 0x01, 0, false }, { "padding", "no padding", 0, 8, 0x80, 7, false }, { "resume rd", "do not reread next cb on resume (82596B stepping only)", 0, 2, 0x02, 1, false }, { "slot time (lo)", "bit times", 0, 6, 0xff, 0, true }, { "slot time (hi)", "bit times", 2, 7, 0x07, 0, true }, { "save bad frame", "discards bad frames", 0, 2, 0x80, 7, false }, { "transmit on no crs", "disabled", 0, 8, 0x08, 3, false }, }; i82586_base_device::i82586_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, endianness_t endian, u8 datawidth, u8 addrwidth) : device_t(mconfig, type, tag, owner, clock), device_memory_interface(mconfig, *this), device_network_interface(mconfig, *this, 10.0f), m_space_config("shared", endian, datawidth, addrwidth), m_out_irq(*this), m_cx(false), m_fr(false), m_cna(false), m_rnr(false), m_irq_state(false), m_initialised(false), m_cu_state(CU_IDLE), m_ru_state(RU_IDLE), m_scp_address(SCP_ADDRESS), m_lb_length(0) {} i82586_device::i82586_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i82586_base_device(mconfig, I82586, tag, owner, clock, ENDIANNESS_LITTLE, 16, 24) {} i82596_device::i82596_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, endianness_t endian, u8 datawidth) : i82586_base_device(mconfig, type, tag, owner, clock, endian, datawidth, 32) {} i82596_le16_device::i82596_le16_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i82596_device(mconfig, I82596_LE16, tag, owner, clock, ENDIANNESS_LITTLE, 16) {} i82596_be16_device::i82596_be16_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i82596_device(mconfig, I82596_BE16, tag, owner, clock, ENDIANNESS_BIG, 16) {} i82596_le32_device::i82596_le32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i82596_device(mconfig, I82596_LE32, tag, owner, clock, ENDIANNESS_LITTLE, 32) {} i82596_be32_device::i82596_be32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : i82596_device(mconfig, I82596_BE32, tag, owner, clock, ENDIANNESS_BIG, 32) {} // shared implementation void i82586_base_device::device_start() { m_space = &space(0); m_out_irq.resolve(); m_cu_timer = timer_alloc(CU_TIMER); m_cu_timer->enable(false); m_ru_timer = timer_alloc(RU_TIMER); m_ru_timer->enable(false); save_item(NAME(m_cx)); save_item(NAME(m_fr)); save_item(NAME(m_cna)); save_item(NAME(m_rnr)); save_item(NAME(m_irq_state)); save_item(NAME(m_initialised)); save_item(NAME(m_cu_state)); save_item(NAME(m_ru_state)); save_item(NAME(m_scp_address)); save_item(NAME(m_scb_base)); save_item(NAME(m_scb_address)); save_item(NAME(m_scb_cs)); save_item(NAME(m_cba)); save_item(NAME(m_rfd)); save_item(NAME(m_mac_multi)); save_item(NAME(m_lb_length)); save_item(NAME(m_lb_buf)); } void i82586_base_device::device_reset() { m_cu_timer->enable(false); m_ru_timer->enable(false); m_cx = false; m_fr = false; m_cna = false; m_rnr = false; m_irq_state = false; m_initialised = false; m_cu_state = CU_IDLE; m_ru_state = RU_IDLE; m_scp_address = SCP_ADDRESS; m_lb_length = 0; } void i82586_base_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case CU_TIMER: cu_execute(); update_scb(); break; case RU_TIMER: if (m_lb_length) { LOG("device_timer injecting loopback frame length %d\n", m_lb_length); recv_cb(m_lb_buf, m_lb_length); } m_lb_length = 0; break; } } device_memory_interface::space_config_vector i82586_base_device::memory_space_config() const { return space_config_vector { std::make_pair(0, &m_space_config) }; } WRITE_LINE_MEMBER(i82586_base_device::ca) { LOG("channel attention %s (%s)\n", state ? "asserted" : "deasserted", machine().describe_context()); if (state) { // on first ca after reset, initialise if (!m_initialised) initialise(); else process_scb(); } } void i82586_base_device::recv_cb(u8 *buf, int length) { switch (m_ru_state) { case RU_IDLE: case RU_SUSPENDED: // discard all frames break; case RU_READY: if (address_filter(buf)) { LOG("recv_cb receiving frame length %d\n", length); dump_bytes(buf, length); ru_execute(buf, length); update_scb(); } break; default: // no resources // TODO: accumulate statistics break; } } void i82586_base_device::process_scb() { // fetch current command and status m_scb_cs = m_space->read_dword(m_scb_address); static const char *const CUC_NAME[] = { "NOP", "START", "RESUME", "SUSPEND", "ABORT", "THROTTLE_D", "THROTTLE_I", "reserved" }; static const char *const RUC_NAME[] = { "NOP", "START", "RESUME", "SUSPEND", "ABORT", "reserved", "reserved", "reserved" }; LOG("process_scb command/status 0x%08x (cuc %s, ruc %s%s)\n", m_scb_cs, CUC_NAME[(m_scb_cs & CUC) >> 24], RUC_NAME[(m_scb_cs & RUC) >> 20], m_scb_cs & RESET ? ", reset" : ""); // clear interrupt flags when acknowledged if (m_scb_cs & ACK_CX) m_cx = false; if (m_scb_cs & ACK_FR) m_fr = false; if (m_scb_cs & ACK_CNA) m_cna = false; if (m_scb_cs & ACK_RNR) m_rnr = false; switch (m_scb_cs & CUC) { case CUC_NOP: break; case CUC_START: m_cba = address(m_scb_address, 4, 4); LOG("process_scb cuc start command block address 0x%08x\n", m_cba); m_cu_state = CU_ACTIVE; m_cu_timer->adjust(attotime::zero); break; case CUC_RESUME: m_cu_state = CU_ACTIVE; m_cu_timer->enable(true); break; case CUC_SUSPEND: m_cu_state = CU_SUSPENDED; m_cu_timer->enable(false); m_cna = true; break; case CUC_ABORT: m_cu_state = CU_IDLE; m_cu_timer->reset(); m_cna = true; break; case CUC_THROTTLE_D: case CUC_THROTTLE_I: break; } switch (m_scb_cs & RUC) { case RUC_NOP: break; case RUC_START: m_rfd = address(m_scb_address, 6, 8); LOG("process_scb ruc start receive frame descriptor address 0x%08x\n", m_rfd); m_ru_state = RU_READY; break; case RUC_RESUME: m_ru_state = RU_READY; break; case RUC_SUSPEND: m_ru_state = RU_SUSPENDED; m_rnr = true; break; case RUC_ABORT: m_ru_state = RU_IDLE; m_rnr = true; break; } LOG("process_scb complete\n"); update_scb(); } void i82586_base_device::update_scb() { // write the status word and clear the command word of the scb // TODO: T (throttle) status flag m_space->write_dword(m_scb_address, (m_cx ? CX : 0) | (m_fr ? FR : 0) | (m_cna ? CNA : 0) | (m_rnr ? RNR : 0) | (m_cu_state << 8) | (m_ru_state << 4)); // update interrupt status set_irq(m_cx || m_fr || m_cna || m_rnr); } void i82586_base_device::cu_execute() { // fetch the command block command/status u32 cb_cs = m_space->read_dword(m_cba); // set busy status m_space->write_dword(m_cba, cb_cs | CB_B); static const char *const CMD_NAME[] = { "NOP", "INDIVIDUAL ADDRESS SETUP", "CONFIGURE", "MULTICAST SETUP", "TRANSMIT", "TIME DOMAIN REFLECTOMETER", "DUMP", "DIAGNOSE" }; LOG("cu_execute command 0x%08x (%s)\n", cb_cs, CMD_NAME[(cb_cs & CB_CMD) >> 16]); if (m_cu_state != CU_IDLE) { // execute command logic switch (cb_cs & CB_CMD) { case CB_NOP: cb_cs |= CB_OK; break; case CB_IASETUP: if (cu_iasetup()) cb_cs |= CB_OK; break; case CB_CONFIGURE: if (cu_configure()) cb_cs |= CB_OK; break; case CB_MCSETUP: if (cu_mcsetup()) cb_cs |= CB_OK; break; case CB_TRANSMIT: // always turn on the heartbeat indicator status after a successful transmission; not // strictly correct, but allows one InterPro 2000 diagnostic to pass if (cu_transmit(cb_cs)) cb_cs |= CB_OK | CB_S6; break; case CB_TDREFLECT: if (cu_tdreflect()) cb_cs |= CB_OK; break; case CB_DUMP: if (cu_dump()) cb_cs |= CB_OK; break; case CB_DIAGNOSE: cb_cs |= CB_OK; break; } } else // abort status cb_cs |= CB_A; // clear busy status and set completion status m_space->write_dword(m_cba, cb_cs | CB_C); // chain to next command if (!(cb_cs & CB_EL)) { // check for suspend or abort if (m_cu_state == CU_ACTIVE) { // fetch link address m_cba = address(m_cba, 4, 4); // restart timer m_cu_timer->adjust(attotime::zero); } } else { // no more commands m_cu_state = CU_IDLE; m_cna = true; } // suspend on completion if (cb_cs & CB_S) { m_cu_state = CU_SUSPENDED; m_cu_timer->enable(false); m_cna = true; } static const char *const CU_STATE_NAME[] = { "IDLE", "SUSPENDED", "ACTIVE" }; LOG("cu_execute complete state %s\n", CU_STATE_NAME[m_cu_state]); // set command executed status m_cx = (cb_cs & CB_I) && (cb_cs & CB_OK); } bool i82586_base_device::address_filter(u8 *mac) { if (cfg_address_length() != 6) { LOG("address_filter error: address length %d not supported\n", cfg_address_length()); return false; } LOGMASKED(LOG_FILTER, "address_filter testing destination address %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); if (cfg_promiscuous_mode()) { LOG("address_filter accepted: promiscuous mode enabled\n"); return true; } // ethernet broadcast if (!cfg_broadcast_disable() && !memcmp(mac, ETH_BROADCAST, cfg_address_length())) { LOGMASKED(LOG_FILTER, "address_filter accepted: broadcast\n"); return true; } // individual address if (!memcmp(mac, get_mac(), cfg_address_length())) { LOGMASKED(LOG_FILTER, "address_filter accepted: individual address match\n"); return true; } // ethernet multicast if ((mac[0] & 0x1) && m_mac_multi) if (m_mac_multi & address_hash(mac, cfg_address_length())) { LOGMASKED(LOG_FILTER, "address_filter accepted: multicast filter match\n"); return true; } return false; } // shared helpers void i82586_base_device::set_irq(bool irq) { if (m_irq_state != irq) { m_irq_state = irq; m_out_irq(m_irq_state ? ASSERT_LINE : CLEAR_LINE); } else if (m_irq_state && irq) { m_out_irq(CLEAR_LINE); m_out_irq(ASSERT_LINE); } } u32 i82586_base_device::compute_crc(u8 *buf, int length, bool crc16) { // TODO: crc16 (not used by Ethernet) return util::crc32_creator::simple(buf, length); } u64 i82586_base_device::address_hash(u8 *buf, int length) { // address hash is computed using bits 2-7 from crc of address u32 crc = compute_crc(buf, length, false); return 1U << ((crc >> 2) & 0x3f); } int i82586_base_device::fetch_bytes(u8 *buf, u32 src, int length) { int offset = 0; switch (m_space->data_width()) { case 16: // handle misaligned start address if (src & 1) { buf[offset] = m_space->read_byte(src + offset); offset++; } // fetch aligned words from the source while (offset + 1 < length) { *(u16 *)&buf[offset] = m_space->read_word(src + offset); offset += 2; } // handle misaligned end address if ((src + length) & 1) { buf[offset] = m_space->read_byte(src + offset); offset++; } break; case 32: // handle misaligned start address switch (src & 3) { case 1: buf[offset] = m_space->read_byte(src + offset); offset++; *(u16 *)&buf[offset] = m_space->read_word(src + offset); offset += 2; break; case 2: *(u16 *)&buf[offset] = m_space->read_word(src + offset); offset += 2; break; case 3: buf[offset] = m_space->read_byte(src + offset); offset++; break; } // fetch aligned dwords from the source while (offset + 3 < length) { *(u32 *)&buf[offset] = m_space->read_dword(src + offset); offset += 4; } // handle misaligned end address switch ((src + length) & 3) { case 1: buf[offset] = m_space->read_byte(src + offset); offset++; break; case 2: *(u16 *)&buf[offset] = m_space->read_word(src + offset); offset += 2; break; case 3: *(u16 *)&buf[offset] = m_space->read_word(src + offset); offset += 2; buf[offset] = m_space->read_byte(src + offset); offset++; break; } break; } return offset; } int i82586_base_device::store_bytes(u32 dst, u8 *buf, int length) { int offset = 0; switch (m_space->data_width()) { case 16: // handle misaligned start address if (dst & 1) { m_space->write_byte(dst + offset, buf[offset]); offset++; } // store aligned words to the destination while (offset + 1 < length) { m_space->write_word(dst + offset, *(u16 *)&buf[offset]); offset += 2; } // handle misaligned end address if ((dst + length) & 1) { m_space->write_byte(dst + offset, buf[offset]); offset++; } break; case 32: // handle misaligned start address switch (dst & 3) { case 1: m_space->write_byte(dst + offset, buf[offset]); offset++; m_space->write_word(dst + offset, *(u16 *)&buf[offset]); offset += 2; break; case 2: m_space->write_word(dst + offset, *(u16 *)&buf[offset]); offset += 2; break; case 3: m_space->write_byte(dst + offset, buf[offset]); offset++; break; } // store aligned dwords to the destination while (offset + 3 < length) { m_space->write_dword(dst + offset, *(u32 *)&buf[offset]); offset += 4; } // handle misaligned end address switch ((dst + length) & 3) { case 1: m_space->write_byte(dst + offset, buf[offset]); offset++; break; case 2: m_space->write_word(dst + offset, *(u16 *)&buf[offset]); offset += 2; break; case 3: m_space->write_word(dst + offset, *(u16 *)&buf[offset]); offset += 2; m_space->write_byte(dst + offset, buf[offset]); offset++; break; } break; } return offset; } void i82586_base_device::dump_bytes(u8 *buf, int length) { if (VERBOSE & LOG_FRAMES) { // pad frame 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_FRAMES, "%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]); } } // 82586 implementation void i82586_device::device_start() { i82586_base_device::device_start(); save_item(NAME(m_cfg_bytes)); } void i82586_device::device_reset() { i82586_base_device::device_reset(); // configure parameter defaults memcpy(m_cfg_bytes, CFG_DEFAULTS, CFG_SIZE); } void i82586_device::initialise() { // read iscp address from scp u32 iscp_address = m_space->read_dword(m_scp_address + 8); LOG("initialise iscp address 0x%08x\n", iscp_address); u16 scb_offset = m_space->read_word(iscp_address + 2); m_scb_base = m_space->read_dword(iscp_address + 4); m_scb_address = m_scb_base + scb_offset; LOG("initialise scb base address 0x%06x offset 0x%04x address 0x%08x\n", m_scb_base, scb_offset, m_scb_address); // clear iscp busy byte m_space->write_byte(iscp_address, 0); m_cx = true; m_cna = true; m_initialised = true; LOG("initialise complete\n"); // update scb update_scb(); } bool i82586_device::cu_iasetup() { int len = cfg_address_length(); char mac[6]; u32 data; if (len != 6) { LOG("cu_iasetup unexpected individual address length %d != 6\n", len); return false; } data = m_space->read_dword(m_cba + 4); mac[0] = (data >> 16) & 0xff; mac[1] = (data >> 24) & 0xff; data = m_space->read_dword(m_cba + 8); mac[2] = (data >> 0) & 0xff; mac[3] = (data >> 8) & 0xff; mac[4] = (data >> 16) & 0xff; mac[5] = (data >> 24) & 0xff; LOG("cu_iasetup individual address %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); set_mac(mac); return true; } bool i82586_device::cu_configure() { int count; // first two bytes u16 data = m_space->read_word(m_cba + 6); cfg_set(0, (data >> 0) & 0xff); cfg_set(1, (data >> 8) & 0xff); // extract byte count (4 <= count <= 12) count = cfg_get(0) & 0xf; count = count < 4 ? 4 : (count > CFG_SIZE ? CFG_SIZE : count); // read remaining bytes one word at a time for (int i = 2; i < count; i++) { if ((i & 1) == 0) { data = m_space->read_word(m_cba + 6 + i); cfg_set(i, (data >> 0) & 0xff); } else cfg_set(i, (data >> 8) & 0xff); } if (VERBOSE & LOG_CONFIG) { LOGMASKED(LOG_CONFIG, "%-30s %3s %3s %3s %s\n", "parameter", "def", "cur", "chg", "default value interpretation"); for (auto param : CFG_PARAMS) { if (param.byte < (CFG_SIZE - 1)) { u8 value = (m_cfg_bytes[param.byte] & param.mask) >> param.shift; LOGMASKED(LOG_CONFIG, "%-30s %3d %3d %c %s%s\n", param.name, param.dflt, value, value == param.dflt ? ' ' : '*', param.unit, param.ieee8023 ? (value == param.dflt ? "" : " (current value not 802.3 compatible)") : ""); } } } return true; } bool i82586_device::cu_mcsetup() { int addr_len = cfg_address_length(); u16 mc_count; u8 data[6]; if (addr_len != 6) { LOG("cu_mcsetup unexpected address length %d != 6\n", addr_len); return false; } // read the address count mc_count = m_space->read_word(m_cba + 6, TB_COUNT); // reset current list LOG("mc_setup configuring %d addresses\n", mc_count); m_mac_multi = 0; // read and process the addresses for (int i = 0; i < mc_count; i++) { *(u16 *)&data[0] = m_space->read_word(m_cba + 8 + i * 6 + 0); *(u16 *)&data[1] = m_space->read_word(m_cba + 8 + i * 6 + 2); *(u16 *)&data[2] = m_space->read_word(m_cba + 8 + i * 6 + 4); // add a hash of this address to the table m_mac_multi |= address_hash(data, cfg_address_length()); LOG("mc_setup inserting address %02x:%02x:%02x:%02x:%02x:%02x\n", data[0], data[1], data[2], data[3], data[4], data[5]); } return true; } bool i82586_device::cu_transmit(u32 command) { u16 tbd_count; // ethernet frame buffer u8 buf[MAX_FRAME_SIZE]; u16 length = 0; u16 tbd_offset = m_space->read_word(m_cba + 6); // optionally insert source, destination address and length (14 bytes) if (!cfg_no_src_add_ins()) { const char *mac = get_mac(); u32 data; // insert destination address (6 bytes) data = m_space->read_dword(m_cba + 8); buf[length++] = (data >> 0) & 0xff; buf[length++] = (data >> 8) & 0xff; buf[length++] = (data >> 16) & 0xff; buf[length++] = (data >> 24) & 0xff; data = m_space->read_dword(m_cba + 12); buf[length++] = (data >> 0) & 0xff; buf[length++] = (data >> 8) & 0xff; // insert source address (6 bytes) LOG("cu_transmit inserting source address %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); for (int i = 0; i < 6; i++) buf[length++] = mac[i]; // insert length (2 bytes) LOG("cu_transmit frame length 0x%04x\n", ((data >> 24) & 0xff) | ((data >> 16) & 0xff00)); buf[length++] = (data >> 16) & 0xff; buf[length++] = (data >> 24) & 0xff; } // check if there is no tbd tbd_count = (tbd_offset == TBD_EMPTY) ? TB_EOF : 0; // insert payload from tbd while (!(tbd_count & TB_EOF)) { u32 tbd_address = m_scb_base + tbd_offset; u32 tb_address, data; // get the size and address of this buffer, and address of the next descriptor data = m_space->read_dword(tbd_address + 0); tbd_count = (data >> 0) & 0xffff; tbd_offset = (data >> 16) & 0xffff; tb_address = m_space->read_dword(tbd_address + 4); LOG("cu_transmit inserting %d bytes from transmit buffer address 0x%08x\n", tbd_count & TB_COUNT, tb_address); length += fetch_bytes(&buf[length], tb_address, tbd_count & TB_COUNT); } #if I82586_FCS // optionally compute/insert ethernet frame check sequence (4 bytes) if (!cfg_no_crc_insertion()) { LOG("cu_transmit inserting frame check sequence\n"); u32 crc = compute_crc(buf, length, cfg_crc16()); // insert the fcs buf[length++] = (crc >> 0) & 0xff; buf[length++] = (crc >> 8) & 0xff; buf[length++] = (crc >> 16) & 0xff; buf[length++] = (crc >> 24) & 0xff; } #endif if (cfg_loopback_mode() != LOOPBACK_NONE) { LOG("cu_transmit loopback frame length %d\n", length); if (m_lb_length == 0) { memcpy(m_lb_buf, buf, length); m_lb_length = length; m_ru_timer->adjust(attotime::zero); } else LOG("cu_tranmit error: loopback buffer not empty\n"); return true; } else { LOG("cu_transmit sending frame length %d\n", length); dump_bytes(buf, length); return send(buf, length) == 0; } } bool i82586_device::cu_tdreflect() { m_space->write_word(m_cba + 6, TDR_LNK_OK | TDR_TIME); return true; } bool i82586_device::cu_dump() { int length = DUMP_SIZE; u8 buf[DUMP_SIZE]; u32 dump_address; // clear dump buffer memset(buf, 0, length); // populate dump buffer // configure bytes memcpy(&buf[0x00], &m_cfg_bytes[0], CFG_SIZE); // individual address memcpy(&buf[0x0c], get_mac(), 6); // hash register *(u64 *)&buf[0x24] = m_mac_multi; // store dump buffer dump_address = m_scb_base + m_space->read_word(m_cba + 6); LOG("cu_dump storing %d bytes address 0x%08x\n", length, dump_address); store_bytes(dump_address, buf, length); return true; } bool i82586_device::address_filter(u8 *mac) { if (i82586_base_device::address_filter(mac)) return true; LOGMASKED(LOG_FILTER, "address_filter rejected\n"); return false; } void i82586_device::ru_execute(u8 *buf, int length) { // fetch receive frame descriptor command/status u32 rfd_cs = m_space->read_dword(m_rfd); // current buffer position and bytes remaining int position = 0, remaining = length; // set busy status m_space->write_dword(m_rfd, rfd_cs | RFD_B); LOG("ru_execute receiving %d bytes into rfd 0x%08x\n", length, m_rfd); // set short frame status if (length < cfg_min_frame_length()) rfd_cs |= RFD_S_SHORT; #if I82586_FCS // set crc status if (~compute_crc(buf, length, cfg_crc16()) != FCS_RESIDUE) { LOGMASKED(LOG_FRAMES, "ru_execute crc error computed 0x%08x stored 0x%08x\n", compute_crc(buf, length - 4, cfg_crc16()), *(u32 *)&buf[length - 4]); // increment crc error count m_space->write_word(m_scb_address + 8, m_space->read_word(m_scb_address + 8) + 1); rfd_cs |= RFD_S_CRC; } #endif // TODO: alignment error (crc in misaligned frame), status bit 10 // TODO: increment alignment error counter // fetch initial rbd offset from rfd u16 rbd_offset = m_space->read_word(m_rfd + 6); if (!cfg_no_src_add_ins()) { // compute stored length (from 2 * addresses + length field) int actual = cfg_address_length() * 2 + 2; LOG("ru_execute storing %d bytes into rfd\n", actual); // store data in rfd store_bytes(m_rfd + 8, buf, actual); position += actual; remaining -= actual; } // store remaining bytes in receive buffers while (remaining && rbd_offset != RBD_EMPTY) { // fetch the count and address for this buffer u32 rb_address = m_space->read_dword(m_scb_base + rbd_offset + 4); u16 rbd_size = m_space->read_word(m_scb_base + rbd_offset + 8); // compute number of bytes to store in buffer int actual = remaining > (rbd_size & RB_SIZE) ? (rbd_size & RB_SIZE) : remaining; LOG("ru_execute storing %d bytes into receive buffer 0x%08x size %d\n", actual, rb_address, rbd_size & RB_SIZE); // store data in buffer store_bytes(rb_address, &buf[position], actual); position += actual; remaining -= actual; // store actual count m_space->write_word(m_scb_base + rbd_offset + 0, actual | RB_F | (remaining ? 0 : RB_EOF)); // check if buffers exhausted if ((rbd_size & RB_EL)) { rbd_offset = RBD_EMPTY; if (remaining) { // set buffers exhausted status rfd_cs |= RFD_S_BUFFER; m_ru_state = RU_NR; m_rnr = true; } } else // fetch next rbd offset rbd_offset = m_space->read_word(m_scb_base + rbd_offset + 2); } if (remaining == 0 || cfg_save_bad_frames()) // set frame received status rfd_cs |= RFD_C; // frame received without errors if (!(rfd_cs & RFD_ERROR_82586)) { LOG("ru_execute frame received without error\n"); rfd_cs |= RFD_OK; } else LOG("ru_execute frame received with errors status 0x%04x\n", rfd_cs); // store status m_space->write_dword(m_rfd, rfd_cs); // if we received without error, or we're saving bad frames, advance to the next rfd if ((rfd_cs & RFD_OK) || cfg_save_bad_frames()) { if (!(rfd_cs & RFD_EL)) { // advance to next rfd m_rfd = m_scb_base + m_space->read_word(m_rfd + 4); // store next free rbd address into rfd if (rbd_offset != RBD_EMPTY) m_space->write_word(m_rfd + 6, rbd_offset); } else { m_ru_state = RU_NR; m_rnr = true; } // set frame received status m_fr = true; } // suspend on completion if (rfd_cs & RFD_S) { m_ru_state = RU_SUSPENDED; m_rnr = true; } static const char *const RU_STATE_NAME[] = { "IDLE", "SUSPENDED", "NO RESOURCES", nullptr, "READY" }; LOG("ru_execute complete state %s\n", RU_STATE_NAME[m_ru_state]); } u32 i82586_device::address(u32 base, int offset, int address, u16 empty) { u16 data = m_space->read_word(base + offset); return (data == empty) ? empty : m_scb_base + data; } // 82596 implementation void i82596_device::device_start() { i82586_base_device::device_start(); save_item(NAME(m_cfg_bytes)); save_item(NAME(m_sysbus)); save_item(NAME(m_mac_multi_ia)); } void i82596_device::device_reset() { i82586_base_device::device_reset(); // configure parameter defaults memcpy(m_cfg_bytes, CFG_DEFAULTS, CFG_SIZE); } void i82596_device::port(u32 data) { switch (data & 0xf) { case 0: // execute a software reset LOG("port reset\n"); reset(); break; case 1: // execute a self-test LOG("port self-test\n"); break; case 2: // write an alterantive system configuration pointer address if (!m_initialised) { m_scp_address = data & ~0xf; LOG("port scp address 0x%08x\n", data); } break; case 3: // write an alternative dump area pointer and perform dump LOG("port dump\n"); break; } } void i82596_device::initialise() { // read iscp address and sysbus from scp u32 iscp_address = m_space->read_dword(m_scp_address + 8); m_sysbus = m_space->read_byte(m_scp_address + 2); LOG("initialise sysbus 0x%02x mode %s, %s triggering of bus throttle timers, lock function %s, interrupt active %s, 32-bit address pointers in linear mode per %s stepping)\n", m_sysbus, mode() == MODE_82586 ? "82586" : (mode() == MODE_32SEGMENTED ? "32-bit segmented mode" : (mode() == MODE_LINEAR ? "linear" : "reserved")), m_sysbus & SYSBUS_TRG ? "external" : "internal", m_sysbus & SYSBUS_LOCK ? "disabled" : "enabled", m_sysbus & SYSBUS_INT ? "low" : "high", m_sysbus & SYSBUS_BE ? "B" : "A1"); LOG("initialise iscp address 0x%08x\n", iscp_address); switch (mode()) { case MODE_82586: case MODE_32SEGMENTED: { u16 scb_offset = m_space->read_word(iscp_address + 2); m_scb_base = m_space->read_dword(iscp_address + 4); m_scb_address = m_scb_base + scb_offset; LOG("initialise scb base address 0x%08x offset 0x%04x address 0x%08x\n", m_scb_base, scb_offset, m_scb_address); } break; case MODE_LINEAR: m_scb_address = m_space->read_dword(iscp_address + 4); LOG("initialise scb address 0x%08x\n", m_scb_address); break; } // clear iscp busy byte m_space->write_byte(iscp_address, 0); m_cx = true; m_cna = true; m_initialised = true; LOG("initialise complete\n"); // update scb update_scb(); } bool i82596_device::cu_iasetup() { int len = cfg_address_length(); u32 data; char mac[6]; if (len != 6) { LOG("cu_iasetup unexpected individual address length %d != 6\n", len); return false; } switch (mode()) { case MODE_82586: case MODE_32SEGMENTED: data = m_space->read_dword(m_cba + 4); mac[0] = (data >> 16) & 0xff; mac[1] = (data >> 24) & 0xff; data = m_space->read_dword(m_cba + 8); mac[2] = (data >> 0) & 0xff; mac[3] = (data >> 8) & 0xff; mac[4] = (data >> 16) & 0xff; mac[5] = (data >> 24) & 0xff; break; case MODE_LINEAR: data = m_space->read_dword(m_cba + 8); mac[0] = (data >> 0) & 0xff; mac[1] = (data >> 8) & 0xff; mac[2] = (data >> 16) & 0xff; mac[3] = (data >> 24) & 0xff; data = m_space->read_dword(m_cba + 12); mac[4] = (data >> 0) & 0xff; mac[5] = (data >> 8) & 0xff; break; } LOG("cu_iasetup individual address %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); set_mac(mac); return true; } bool i82596_device::cu_configure() { u32 data32 = 0; u16 data16; int count; switch (mode()) { case MODE_82586: // first two bytes are word aligned data16 = m_space->read_word(m_cba + 6); cfg_set(0, (data16 >> 0) & 0xff); cfg_set(1, (data16 >> 8) & 0xff); // extract byte count (datasheet does not state minimum count) count = cfg_get(0) & 0xf; count = count < 4 ? 4 : (count > i82586_device::CFG_SIZE ? i82586_device::CFG_SIZE : count); // read remaining bytes one dword at a time for (int i = 2; i < count; i++) { switch (i & 3) { case 2: data32 = m_space->read_dword(m_cba + 6 + i); cfg_set(i, (data32 >> 0) & 0xff); break; case 3: cfg_set(i, (data32 >> 8) & 0xff); break; case 0: cfg_set(i, (data32 >> 16) & 0xff); break; case 1: cfg_set(i, (data32 >> 24) & 0xff); break; } } break; case MODE_32SEGMENTED: // first two bytes are word aligned data16 = m_space->read_word(m_cba + 6); cfg_set(0, (data16 >> 0) & 0xff); cfg_set(1, (data16 >> 8) & 0xff); // extract byte count (datasheet does not state minimum count) count = cfg_get(0) & 0xf; count = count < 4 ? 4 : (count > CFG_SIZE ? CFG_SIZE : count); // read remaining bytes one dword at a time for (int i = 2; i < count; i++) { switch (i & 3) { case 2: data32 = m_space->read_dword(m_cba + 6 + i); cfg_set(i, (data32 >> 0) & 0xff); break; case 3: cfg_set(i, (data32 >> 8) & 0xff); break; case 0: cfg_set(i, (data32 >> 16) & 0xff); break; case 1: cfg_set(i, (data32 >> 24) & 0xff); break; } } break; case MODE_LINEAR: // bytes are all dword aligned data32 = m_space->read_dword(m_cba + 8); cfg_set(0, (data32 >> 0) & 0xff); cfg_set(1, (data32 >> 8) & 0xff); cfg_set(2, (data32 >> 16) & 0xff); cfg_set(3, (data32 >> 24) & 0xff); // extract byte count (datasheet does not state minimum count) count = cfg_get(0) & 0xf; count = count < 4 ? 4 : (count > CFG_SIZE ? CFG_SIZE : count); // read remaining bytes one dword at a time for (int i = 4; i < count; i++) { switch (i & 3) { case 0: data32 = m_space->read_dword(m_cba + 8 + i); cfg_set(i, (data32 >> 0) & 0xff); break; case 1: cfg_set(i, (data32 >> 8) & 0xff); break; case 2: cfg_set(i, (data32 >> 16) & 0xff); break; case 3: cfg_set(i, (data32 >> 24) & 0xff); break; } } break; } if (VERBOSE & LOG_CONFIG) { LOGMASKED(LOG_CONFIG, "%-30s %3s %3s %3s %s\n", "parameter", "def", "cur", "chg", "default value interpretation"); for (auto param : CFG_PARAMS) { u8 value = (m_cfg_bytes[param.byte] & param.mask) >> param.shift; LOGMASKED(LOG_CONFIG, "%-30s %3d %3d %c %s%s\n", param.name, param.dflt, value, value == param.dflt ? ' ' : '*', param.unit, param.ieee8023 ? (value == param.dflt ? "" : " (current value not 802.3 compatible)") : ""); } } return true; } bool i82596_device::cu_mcsetup() { int addr_len = cfg_address_length(); u16 mc_count = 0; int offset = 0; u8 data[20]; bool multi_ia; if (addr_len != 6) { LOG("cu_mcsetup unexpected address length %d != 6\n", addr_len); return false; } switch (mode()) { case MODE_82586: case MODE_32SEGMENTED: mc_count = m_space->read_word(m_cba + 6, TB_COUNT); break; case MODE_LINEAR: mc_count = m_space->read_word(m_cba + 8, TB_COUNT); offset = 2; break; } // if count is zero, release multicast list and finish if (mc_count == 0) { LOG("mc_setup multicast filter disabled\n"); m_mac_multi = 0; return true; } // fetch the first word *(u32 *)&data[0] = m_space->read_dword(m_cba + 8); // multi ia when configured and lsb of first address is clear multi_ia = cfg_multi_ia() && !BIT(data[offset], 0); // clear existing list LOG("mc_setup configuring %d %s addresses\n", mc_count, multi_ia ? "multi-ia" : "multicast"); (multi_ia ? m_mac_multi_ia : m_mac_multi) = 0; for (int i = 0; i < mc_count; i++) { // compute offset of address in 18 byte buffer int n = (i % 3) * 6; // read the next dword *(u32 *)&data[n + 6] = m_space->read_dword(m_cba + 8 + i * 4 + 4); // unaligned case needs special handling if (n == 12 && offset == 2) *(u16 *)&data[18] = *(u16 *)&data[0]; // add a hash of this address to the table (multi_ia ? m_mac_multi_ia : m_mac_multi) |= address_hash(&data[n + offset], cfg_address_length()); LOG("mc_setup inserting address %02x:%02x:%02x:%02x:%02x:%02x\n", data[n + offset + 0], data[n + offset + 1], data[n + offset + 2], data[n + offset + 3], data[n + offset + 4], data[n + offset + 5]); } return true; } bool i82596_device::cu_transmit(u32 command) { u32 tbd_address; u16 tcb_count, tbd_count; // ethernet frame buffer u8 buf[MAX_FRAME_SIZE]; u16 length = 0; // need offset into tcb for linear mode int offset = mode() == MODE_LINEAR ? 4 : 0; // fetch tbd address if (mode() != MODE_LINEAR) { u16 tbd_offset = m_space->read_word(m_cba + 6); tbd_address = (tbd_offset == TBD_EMPTY) ? tbd_offset : m_scb_base + tbd_offset; } else tbd_address = m_space->read_dword(m_cba + 8); // fetch the tcb count tcb_count = (mode() == MODE_82586) ? 0 : m_space->read_word(m_cba + 8 + offset); LOG("cu_transmit %s mode, crc insertion %s, tcb count %d, %s tbd\n", command & CB_SF ? "flexible" : "simplified", command & CB_NC ? "disabled" : "enabled", tcb_count & TB_COUNT, (tbd_address == TBD_EMPTY) ? "no" : "valid"); if ((command & CB_SF) && !(tcb_count & TB_EOF)) LOG("cu_transmit error: tcb eof not set in simplified mode\n"); // insert payload from tcb when in simplified mode, or when flexible mode and tcb_count > 0 if ((command & CB_SF) || (!(command & CB_SF) && (tcb_count & TB_COUNT))) { // optionally insert destination, source and length (14 bytes) if (!cfg_no_src_add_ins()) { const char *mac = get_mac(); u32 data; // insert destination address (6 bytes) data = m_space->read_dword(m_cba + 12 + offset); buf[length++] = (data >> 0) & 0xff; buf[length++] = (data >> 8) & 0xff; buf[length++] = (data >> 16) & 0xff; buf[length++] = (data >> 24) & 0xff; data = m_space->read_dword(m_cba + 16 + offset); buf[length++] = (data >> 0) & 0xff; buf[length++] = (data >> 8) & 0xff; // insert source address (6 bytes) LOG("cu_transmit inserting source address %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); for (int i = 0; i < 6; i++) buf[length++] = mac[i]; // insert length from tcb (2 bytes) LOG("cu_transmit frame length 0x%04x\n", ((data >> 24) & 0xff) | ((data >> 16) & 0xff00)); buf[length++] = (data >> 16) & 0xff; buf[length++] = (data >> 24) & 0xff; // insert payload from tcb LOG("cu_transmit inserting %d bytes from transmit command block\n", (tcb_count & TB_COUNT) - 8); length += fetch_bytes(&buf[length], m_cba + 20 + offset, (tcb_count & TB_COUNT) - 8); } else { // insert entire payload from tcb LOG("cu_transmit inserting %d bytes from transmit command block\n", tcb_count & TB_COUNT); length += fetch_bytes(&buf[length], m_cba + 12 + offset, tcb_count & TB_COUNT); } } else if (!cfg_no_src_add_ins()) LOG("cu_transmit error: don't know how to insert source address in flexible mode without tcb payload\n"); // check for no tbd tbd_count = ((tcb_count & TB_EOF) || (tbd_address == TBD_EMPTY)) ? TB_EOF : 0; // insert payload from tbd while (!(tbd_count & TB_EOF)) { u32 data, tb_address = 0; // fetch the count and address for this buffer, and address of the next descriptor switch (mode()) { case MODE_82586: case MODE_32SEGMENTED: data = m_space->read_dword(tbd_address + 0); tbd_count = data; tb_address = m_space->read_dword(tbd_address + 4); tbd_address = m_scb_base + (data >> 16); break; case MODE_LINEAR: tbd_count = m_space->read_word(tbd_address + 0); tb_address = m_space->read_dword(tbd_address + 8); tbd_address = m_space->read_dword(tbd_address + 4); break; } // fetch and insert the buffer bytes into our transmit buffer LOG("cu_transmit inserting %d bytes from transmit buffer address 0x%08x\n", tbd_count & TB_COUNT, tb_address); length += fetch_bytes(&buf[length], tb_address, tbd_count & TB_COUNT); } #if I82586_FCS // optionally compute/insert ethernet frame check sequence (4 bytes) if (!cfg_no_crc_insertion() && !(command & CB_NC)) { LOG("cu_transmit inserting frame check sequence\n"); u32 crc = compute_crc(buf, length, cfg_crc16()); // append the fcs buf[length++] = (crc >> 0) & 0xff; buf[length++] = (crc >> 8) & 0xff; buf[length++] = (crc >> 16) & 0xff; buf[length++] = (crc >> 24) & 0xff; } #endif if (cfg_loopback_mode() != LOOPBACK_NONE) { LOG("cu_transmit loopback frame length %d\n", length); if (m_lb_length == 0) { dump_bytes(buf, length); memcpy(m_lb_buf, buf, length); m_lb_length = length; m_ru_timer->adjust(attotime::zero); } else LOG("cu_tranmit error: loopback buffer not empty\n"); return true; } else { LOG("cu_transmit sending frame length %d\n", length); dump_bytes(buf, length); return send(buf, length) == 0; } } bool i82596_device::cu_tdreflect() { switch (mode()) { case MODE_82586: case MODE_32SEGMENTED: m_space->write_word(m_cba + 6, TDR_LNK_OK | TDR_TIME); break; case MODE_LINEAR: m_space->write_word(m_cba + 8, TDR_LNK_OK | TDR_TIME); break; } return true; } bool i82596_device::cu_dump() { int length = mode() == MODE_82586 ? i82586_device::DUMP_SIZE : DUMP_SIZE; u8 buf[DUMP_SIZE]; u32 dump_address; // clear dump buffer memset(buf, 0, length); // populate dump buffer if (mode() == MODE_82586) { // configure bytes 2-10 memcpy(&buf[0x02], &m_cfg_bytes[2], 9); // individual address memcpy(&buf[0x0c], get_mac(), 6); // hash register *(u64 *)&buf[0x24] = m_mac_multi; } else { // configure bytes 2-13 memcpy(&buf[0x00], &m_cfg_bytes[2], 12); // individual address memcpy(&buf[0x0e], get_mac(), 6); // hash register *(u64 *)&buf[0x26] = m_mac_multi; } // store dump buffer dump_address = address(m_cba, 6, 8); LOG("cu_dump storing %d bytes address 0x%08x\n", length, dump_address); store_bytes(dump_address, buf, length); return true; } bool i82596_device::address_filter(u8 *mac) { if (i82586_base_device::address_filter(mac)) return true; // check for accept all multicast if ((mac[0] & 0x1) && !cfg_mc_all()) { LOGMASKED(LOG_FILTER, "address_filter accepted: multicast and configured to accept all multicast\n"); return true; } // not ethernet multicast, check multi-ia if (!(mac[0] & 0x1) && cfg_multi_ia() && m_mac_multi_ia) { if (m_mac_multi_ia & address_hash(mac, cfg_address_length())) { LOGMASKED(LOG_FILTER, "address_filter accepted: multi-ia filter match"); return true; } } LOGMASKED(LOG_FILTER, "address_filter rejected\n"); return false; } void i82596_device::ru_execute(u8 *buf, int length) { // fetch receive frame descriptor command/status u32 rfd_cs = m_space->read_dword(m_rfd); // offset into rfd/rbd for linear mode int linear_offset = mode() == MODE_LINEAR ? 4 : 0; if (!cfg_crc_in_memory()) { // compute and append fcs u32 crc = compute_crc(buf, length, false); // append the fcs buf[length++] = (crc >> 0) & 0xff; buf[length++] = (crc >> 8) & 0xff; buf[length++] = (crc >> 16) & 0xff; buf[length++] = (crc >> 24) & 0xff; } // current buffer position and bytes remaining int position = 0, remaining = length; // set busy status m_space->write_dword(m_rfd, rfd_cs | RFD_B); LOG("ru_execute receiving %d bytes using %s mode into rfd 0x%08x\n", length, (mode() == MODE_82586 ? "82586" : ((rfd_cs & RFD_SF) ? "flexible" : "simplified")), m_rfd); // TODO: check length if configured, status bit 12 // set short frame status if (length < cfg_min_frame_length()) { LOGMASKED(LOG_FRAMES, "ru_execute frame length %d less than minimum %d\n", length, cfg_min_frame_length()); // increment short frame count if (mode() != MODE_82586) m_space->write_dword(m_scb_address + 28 + linear_offset, m_space->read_dword(m_scb_address + 28 + linear_offset) + 1); rfd_cs |= RFD_S_SHORT; } #if I82586_FCS // set crc status if (~compute_crc(buf, length, cfg_crc16()) != FCS_RESIDUE) { LOGMASKED(LOG_FRAMES, "ru_execute crc error computed 0x%08x stored 0x%08x\n", compute_crc(buf, length - 4, cfg_crc16()), *(u32 *)&buf[length - 4]); // increment crc error count if (mode() == MODE_82586) m_space->write_word(m_scb_address + 8, m_space->read_word(m_scb_address + 8) + 1); else m_space->write_dword(m_scb_address + 8 + linear_offset, m_space->read_dword(m_scb_address + 8 + linear_offset) + 1); rfd_cs |= RFD_S_CRC; } #endif // TODO: alignment error (crc in misaligned frame), status bit 10 // TODO: increment alignment error counter // set multicast status if (mode() != MODE_82586 && memcmp(buf, get_mac(), cfg_address_length())) rfd_cs |= RFD_S_MULTICAST; // fetch initial rbd address from rfd u32 rbd_address = address(m_rfd, 6, 8, RBD_EMPTY); // check for simplified mode if (mode() != MODE_82586 && !(rfd_cs & RFD_SF)) { // fetch size word u16 rfd_size = m_space->read_word(m_rfd + 10 + linear_offset, RB_SIZE); // increment "no resources" counter if (rfd_size < length) m_space->write_dword(m_scb_address + 16 + linear_offset, m_space->read_dword(m_scb_address + 16 + linear_offset) + 1); // truncate/capture the frame if (length <= rfd_size || cfg_save_bad_frames()) { // compute stored length int actual = (rfd_size < length) ? rfd_size : length; LOG("ru_execute storing %d bytes into rfd size %d\n", actual, rfd_size); // store data in rfd store_bytes(m_rfd + 12 + linear_offset, buf, actual); position += actual; remaining -= actual; // store actual count, f and eof m_space->write_word(m_rfd + 8 + linear_offset, actual | RB_F | RB_EOF); // set frame received and truncated frame status rfd_cs |= RFD_C | (actual < length ? RFD_S_TRUNCATED : 0); } else LOG("ru_execute discarding %d byte frame exceeding rfd size %d\n", length, rfd_size); } else { // flexible mode, store leading data into rfd if (mode() != MODE_82586) { // fetch size word u16 rfd_size = m_space->read_word(m_rfd + 10 + linear_offset, RB_SIZE); // compute stored length (from rfd_size) int actual = (rfd_size < length) ? rfd_size : length; LOG("ru_execute storing %d bytes into rfd size %d\n", actual, rfd_size); // store data in rfd store_bytes(m_rfd + 12 + linear_offset, buf, actual); position += actual; remaining -= actual; // store actual count, f and eof m_space->write_word(m_rfd + 8 + linear_offset, actual | RB_F | (remaining ? 0 : RB_EOF)); } else if (!cfg_no_src_add_ins()) { // compute stored length (from 2 * addresses + length field) int actual = cfg_address_length() * 2 + 2; LOG("ru_execute storing %d bytes into rfd\n", actual); // store data in rfd store_bytes(m_rfd + 8, buf, actual); position += actual; remaining -= actual; } // store remaining bytes in receive buffers while (remaining && rbd_address != RBD_EMPTY) { // fetch the count and address for this buffer u32 rb_address = m_space->read_dword(rbd_address + 4 + linear_offset); u16 rbd_size = m_space->read_word(rbd_address + 8 + linear_offset); // compute number of bytes to store in buffer int actual = remaining > (rbd_size & RB_SIZE) ? (rbd_size & RB_SIZE) : remaining; LOG("ru_execute storing %d bytes into receive buffer 0x%08x size %d\n", actual, rb_address, rbd_size & RB_SIZE); // store data in buffer store_bytes(rb_address, &buf[position], actual); position += actual; remaining -= actual; // store actual count m_space->write_word(rbd_address + 0, actual | RB_F | (remaining ? 0 : RB_EOF)); // check if buffers exhausted if ((rbd_size & RB_EL)) { rbd_address = RBD_EMPTY; if (remaining) { // set buffers exhausted status rfd_cs |= RFD_S_BUFFER; m_ru_state = mode() == MODE_82586 ? RU_NR : RU_NR_RBD; m_rnr = true; } } else // fetch next rbd address rbd_address = address(rbd_address, 2, 4); } if (remaining == 0 || cfg_save_bad_frames()) // set frame received status rfd_cs |= RFD_C; } // frame received without errors if (!(rfd_cs & (mode() == MODE_82586 ? RFD_ERROR_82586 : RFD_ERROR))) { LOG("ru_execute frame received without error\n"); rfd_cs |= RFD_OK; } else LOG("ru_execute frame received with errors status 0x%04x\n", rfd_cs); // store status m_space->write_dword(m_rfd, rfd_cs); // if we received without error, or we're saving bad frames, advance to the next rfd if ((rfd_cs & RFD_OK) || cfg_save_bad_frames()) { if (!(rfd_cs & RFD_EL)) { // advance to next rfd m_rfd = address(m_rfd, 4, 4); // store next free rbd address into rfd if (rbd_address != RBD_EMPTY) { if (mode() == MODE_LINEAR) m_space->write_dword(m_rfd + 8, rbd_address); else m_space->write_word(m_rfd + 6, rbd_address - m_scb_base); } } else { m_ru_state = mode() == MODE_82586 ? RU_NR : RU_NR_RFD; m_rnr = true; } // set frame received status m_fr = true; } // suspend on completion if (rfd_cs & RFD_S) { m_ru_state = RU_SUSPENDED; m_rnr = true; } static const char *const RU_STATE_NAME[] = { "IDLE", "SUSPENDED", "NO RESOURCES", nullptr, "READY", nullptr, nullptr, nullptr, nullptr, nullptr, "NO RESOURCES (RFD)", nullptr, "NO RESOURCES (RBD)" }; LOG("ru_execute complete state %s\n", RU_STATE_NAME[m_ru_state]); } u32 i82596_device::address(u32 base, int offset, int address, u16 empty) { if (mode() != MODE_LINEAR) { u16 data = m_space->read_word(base + offset); return (data == empty) ? empty : m_scb_base + data; } else return m_space->read_dword(base + address); }