From 754240e3b7a0c647b74dcca1bbdaef12d2eb0fec Mon Sep 17 00:00:00 2001 From: Patrick Mackinlay Date: Fri, 21 Jul 2017 16:40:55 +0700 Subject: [PATCH] i82586: functional, ~90% complete (#2498) * i82586: functional, ~90% complete * added dumping of 82586 configuration * i82586: state save * Changed address handling and filtering. --- src/devices/machine/i82586.cpp | 1900 +++++++++++++++++++++++++++++++- src/devices/machine/i82586.h | 332 +++++- 2 files changed, 2198 insertions(+), 34 deletions(-) diff --git a/src/devices/machine/i82586.cpp b/src/devices/machine/i82586.cpp index d35039f5f63..6efd2ee9dd7 100644 --- a/src/devices/machine/i82586.cpp +++ b/src/devices/machine/i82586.cpp @@ -4,14 +4,17 @@ /* * An implementation of the Intel 82586 and 82596 Ethernet controller devices. * -* Hopefully this might eventually support all of the following, but initial focus will be 82586 and 82596DX -* because these are used in the InterPro 2xxx and 6xxx series systems: +* 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 82586 or 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 @@ -19,49 +22,210 @@ * https://www.intel.com/assets/pdf/general/82596ca.pdf * * TODO -* - virtually everything +* - testing for 82586 and 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(I82596SX, i82596sx_device, "i82596sx", "Intel 82596SX High-Performance 32-Bit Local Area Network Coprocessor") -DEFINE_DEVICE_TYPE(I82596DX, i82596dx_device, "i82596dx", "Intel 82596DX High-Performance 32-Bit Local Area Network 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)") -i82586_base_device::i82586_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, u8 datawidth, u8 addrwidth) +// 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", ENDIANNESS_LITTLE, datawidth, addrwidth), - m_out_irq(*this) + 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, 16, 24) + : i82586_base_device(mconfig, I82586, tag, owner, clock, ENDIANNESS_LITTLE, 16, 24) {} -i82596_base_device::i82596_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, u8 datawidth, u8 addrwidth) - : i82586_base_device(mconfig, type, tag, owner, clock, datawidth, addrwidth) +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) {} -i82596sx_device::i82596sx_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : i82596_base_device(mconfig, I82596SX, tag, owner, clock, 16, 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) {} -i82596dx_device::i82596dx_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) - : i82596_base_device(mconfig, I82596DX, tag, owner, clock, 32, 32) +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 @@ -73,47 +237,1745 @@ device_memory_interface::space_config_vector i82586_base_device::memory_space_co WRITE_LINE_MEMBER(i82586_base_device::ca) { - logerror("%s: channel attention %s (%s)\n", state ? "asserted" : "deasserted", tag(), machine().describe_context()); + 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: + if (cu_transmit(cb_cs)) + cb_cs |= CB_OK; + 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 i82596_base_device::device_start() +void i82586_device::initialise() +{ + u16 scb_offset = m_space->read_word(m_scp_address + 2); + + m_scb_base = m_space->read_dword(m_scp_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(m_scp_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 (rounded up to 8 byte boundary) + u8 buf[1528]; + int 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_iscp_address)); + + save_item(NAME(m_mac_multi_ia)); } -void i82596_base_device::device_reset() +void i82596_device::device_reset() { i82586_base_device::device_reset(); + + // configure parameter defaults + memcpy(m_cfg_bytes, CFG_DEFAULTS, CFG_SIZE); } -void i82596_base_device::port(u32 data) +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 sysbus and iscp address from scp + m_sysbus = m_space->read_byte(m_scp_address + 2); + m_iscp_address = m_space->read_dword(m_scp_address + 8); + + 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", m_iscp_address); + + switch (mode()) + { + case MODE_82586: + case MODE_32SEGMENTED: + { + u16 scb_offset = m_space->read_word(m_iscp_address + 2); + + m_scb_base = m_space->read_dword(m_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(m_iscp_address + 4); + LOG("initialise scb address 0x%08x\n", m_scb_address); + break; + } + + // clear iscp busy byte + m_space->write_byte(m_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 (rounded up to 8 byte boundary) + u8 buf[1528]; + int 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); +} diff --git a/src/devices/machine/i82586.h b/src/devices/machine/i82586.h index c0ac6694283..1f002951fc9 100644 --- a/src/devices/machine/i82586.h +++ b/src/devices/machine/i82586.h @@ -15,22 +15,236 @@ class i82586_base_device : public device_network_interface { public: + enum scb_mask + { + T = 0x00000008, // throttle timers loaded + RUS = 0x000000f0, // ru status + CUS = 0x00000700, // cu status + RNR = 0x00001000, // ru left ready state + CNA = 0x00002000, // cu left active state + FR = 0x00004000, // ru finished receiving a frame + CX = 0x00008000, // cu finished executing a command with its interrupt bit set + RUC = 0x00700000, // ru command + RESET = 0x00800000, // reset chip (same as hardware reset) + CUC = 0x07000000, // cu command + ACK_RNR = 0x10000000, // acknowledge ru became became not ready + ACK_CNA = 0x20000000, // acknowledge cu became not active + ACK_FR = 0x40000000, // acknowledge ru received a frame + ACK_CX = 0x80000000 // acknowledge cu completed an action command + }; + + enum scb_cu_command + { + CUC_NOP = 0x00000000, // no operation + CUC_START = 0x01000000, // start execution of first command on cbl + CUC_RESUME = 0x02000000, // resume execution of cu + CUC_SUSPEND = 0x03000000, // suspend execution after current command + CUC_ABORT = 0x04000000, // abort current command immediately + CUC_THROTTLE_D = 0x05000000, // load throttle timers after next terminal count + CUC_THROTTLE_I = 0x06000000 // load and restart throttle timers immediately + }; + enum cu_state : u8 + { + CU_IDLE = 0x0, // not executing, not associated with command, initial state + CU_SUSPENDED = 0x1, // not executing, associated with command + CU_ACTIVE = 0x2 // executing, associated with command + }; + + enum scb_ru_command + { + RUC_NOP = 0x00000000, // no operation + RUC_START = 0x00100000, // start reception of frames + RUC_RESUME = 0x00200000, // resume frame reception + RUC_SUSPEND = 0x00300000, // suspend frame reception + RUC_ABORT = 0x00400000 // abort receiver operation immediately + }; + enum ru_state : u8 + { + RU_IDLE = 0x0, // no resources, discarding frames, initial state + RU_SUSPENDED = 0x1, // has resources, discarding frames + RU_NR = 0x2, // no resources, discarding frames, accumulating statistics + RU_READY = 0x4, // has resources, storing frames + RU_NR_RFD = 0xa, // no receive frame descriptor resources (not 82586 mode) + RU_NR_RBD = 0xc // no receive block descriptor resources (not 82586 mode) + }; + + enum cu_cb_cs_mask + { + CB_MAXCOL = 0x00000007, // number of collisions (82596 only) + CB_A = 0x00001000, // command aborted + CB_OK = 0x00002000, // error free completion + CB_B = 0x00004000, // busy executing command + CB_C = 0x00008000, // command completed + CB_CMD = 0x00070000, // command operation code + CB_NC = 0x00100000, // no crc insertion (82596 only) + CB_SF = 0x00080000, // simplified mode (82596 only) + CB_I = 0x20000000, // interrupt after completion + CB_S = 0x40000000, // suspend after completion + CB_EL = 0x80000000 // end of command list + }; + enum cu_cb_command + { + CB_NOP = 0x00000000, // no operation + CB_IASETUP = 0x00010000, // individual address setup + CB_CONFIGURE = 0x00020000, // configure + CB_MCSETUP = 0x00030000, // multicast setup + CB_TRANSMIT = 0x00040000, // transmit + CB_TDREFLECT = 0x00050000, // time domain reflectometer + CB_DUMP = 0x00060000, // dump + CB_DIAGNOSE = 0x00070000 // diagnose + }; + enum cu_transmit_count_mask + { + TB_COUNT = 0x3fff, + TB_EOF = 0x8000 + }; + enum cu_tdreflect_mask + { + TDR_TIME = 0x07ff, // time until echo + TDR_ET_SRT = 0x1000, // short circuit + TDR_ET_OPN = 0x2000, // no termination + TDR_XCVR_PRB = 0x4000, // transceiver problem + TDR_LNK_OK = 0x8000 // no link problem + }; + + enum ru_rfd_cs_mask + { + RFD_S_COLLISION = 0x00000001, // receive collision + RFD_S_MULTICAST = 0x00000002, // multicast address + RFD_S_TRUNCATED = 0x00000020, // frame truncated + + RFD_S_EOP = 0x00000040, // no eop + RFD_S_SHORT = 0x00000080, // frame too short + RFD_S_OVERRUN = 0x00000100, // dma overrun + RFD_S_BUFFER = 0x00000200, // no buffer + RFD_S_ALIGN = 0x00000400, // alignment error + RFD_S_CRC = 0x00000800, // crc error + RFD_S_LENGTH = 0x00001000, // length error + + RFD_ERROR_82586 = 0x00000fc0, // 82586 error bits + RFD_ERROR = 0x00001fe1, // everything except multicast + + RFD_OK = 0x00002000, // frame received successfully + RFD_B = 0x00004000, // busy receiving frame + RFD_C = 0x00008000, // frame reception completed + RFD_SF = 0x00080000, // simplified/flexible mode + RFD_S = 0x40000000, // suspend after receive + RFD_EL = 0x80000000 // end of receive frame list + }; + enum ru_receive_count_mask + { + RB_COUNT = 0x3fff, // actual count + RB_F = 0x4000, // buffer used + RB_EOF = 0x8000 // end of frame + }; + enum ru_receive_size_mask + { + RB_SIZE = 0x3fff, // receive buffer size + RB_P = 0x4000, // rbd prefetched (82596 only) + RB_EL = 0x8000 // end of rbd list + }; + + static const u32 SCP_ADDRESS = 0x00fffff4; // the default value of the system configuration pointer + static const u32 TBD_EMPTY = 0x0000ffff; // FIXME: datasheet says this field should be "all 1's", but InterPro sets only lower 16 bits (in linear mode) + static const u32 RBD_EMPTY = 0x0000ffff; + + static const u32 FCS_RESIDUE = 0xdebb20e3; // the residue after computing the fcs over a complete frame (including fcs) + template static devcb_base &static_set_out_irq_callback(device_t &device, Object &&cb) { return downcast(device).m_out_irq.set_callback(std::forward(cb)); } DECLARE_WRITE_LINE_MEMBER(ca); + virtual void recv_cb(u8 *buf, int length) override; protected: - i82586_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, u8 datawidth, u8 addrwidth); + 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); + // standard device_* overrides virtual void device_start() override; virtual void device_reset() override; + virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override; virtual space_config_vector memory_space_config() const override; + // setup and scb + virtual void initialise() = 0; + virtual void process_scb(); + virtual void update_scb(); + + // command unit + virtual void cu_execute(); + virtual bool cu_iasetup() = 0; + virtual bool cu_configure() = 0; + virtual bool cu_mcsetup() = 0; + virtual bool cu_transmit(u32 command) = 0; + virtual bool cu_tdreflect() = 0; + virtual bool cu_dump() = 0; + + // receive unit + virtual bool address_filter(u8 *mac); + virtual void ru_execute(u8 *buf, int length) = 0; + + // helpers + void set_irq(bool irq); + virtual u32 address(u32 base, int offset, int address) = 0; + virtual u32 address(u32 base, int offset, int address, u16 empty) = 0; + static u32 compute_crc(u8 *buf, int length, bool crc32); + static u64 address_hash(u8 *buf, int length); + int fetch_bytes(u8 *buf, u32 src, int length); + int store_bytes(u32 dst, u8 *buf, int length); + void dump_bytes(u8 *buf, int length); + + // device_* members const address_space_config m_space_config; address_space *m_space; devcb_write_line m_out_irq; + static const device_timer_id CU_TIMER = 0; + static const device_timer_id RU_TIMER = 1; + emu_timer *m_cu_timer; + emu_timer *m_ru_timer; + + // interrupt state + bool m_cx; // command executed (with interrupt) + bool m_fr; // frame received + bool m_cna; // command unit became inactive + bool m_rnr; // receive unit became not ready + bool m_irq_state; + bool m_initialised; + + // receive/command unit state + cu_state m_cu_state; // command unit state + ru_state m_ru_state; // receive unit state + u32 m_scp_address; // system configuration pointer + u32 m_scb_base; // system control block base (segmented modes) + u32 m_scb_address; // system control block address (linear mode) + u32 m_scb_cs; // current scb command and status + u32 m_cba; // current command block address + u32 m_rfd; // current receive frame descriptor address + + u64 m_mac_multi; // multicast address hash table + u8 m_lb_buf[1528]; // storage for loopback frames + int m_lb_length; // length of loopback frame + + // configure parameters + enum lb_mode + { + LOOPBACK_NONE = 0x00, + LOOPBACK_INTERNAL = 0x40, + LOOPBACK_EXTERNAL = 0x80, + LOOPBACK_BOTH = 0xc0 // internal loopback for 82586, external loopback for 82596 + }; + virtual u8 cfg_get(int offset) const = 0; + virtual void cfg_set(int offset, u8 data) = 0; + + bool cfg_save_bad_frames() const { return BIT(cfg_get(2), 7); } + u8 cfg_address_length() const { return (cfg_get(3) & 0x7) % 7; } // "NOTE: 7 is interpreted as 0" + bool cfg_no_src_add_ins() const { return BIT(cfg_get(3), 3); } + lb_mode cfg_loopback_mode() const { return (lb_mode)(cfg_get(3) & LOOPBACK_BOTH); } + bool cfg_promiscuous_mode() const { return BIT(cfg_get(8), 0); } + bool cfg_broadcast_disable() const { return BIT(cfg_get(8), 1); } + bool cfg_no_crc_insertion() const { return BIT(cfg_get(8), 4); } + bool cfg_crc16() const { return BIT(cfg_get(8), 5); } + u8 cfg_min_frame_length() const { return cfg_get(10); } }; class i82586_device : public i82586_base_device @@ -38,49 +252,137 @@ class i82586_device : public i82586_base_device public: i82586_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + static const int CFG_SIZE = 12; + static const int DUMP_SIZE = 170; + protected: + // standard device_* overrides virtual void device_start() override; virtual void device_reset() override; + // setup and scb + virtual void initialise() override; + + // command unit + virtual bool cu_iasetup() override; + virtual bool cu_configure() override; + virtual bool cu_mcsetup() override; + virtual bool cu_transmit(u32 command) override; + virtual bool cu_tdreflect() override; + virtual bool cu_dump() override; + + // receive unit + virtual bool address_filter(u8 *mac) override; + virtual void ru_execute(u8 *buf, int length) override; + + // helpers + virtual u32 address(u32 base, int offset, int address) override { return m_scb_base + m_space->read_word(base + offset); } + virtual u32 address(u32 base, int offset, int address, u16 empty) override; + private: + // configure parameters + virtual u8 cfg_get(int offset) const override { return m_cfg_bytes[offset]; } + virtual void cfg_set(int offset, u8 data) override { m_cfg_bytes[offset] = data; } + + u8 m_cfg_bytes[CFG_SIZE]; }; -class i82596_base_device : public i82586_base_device +class i82596_device : public i82586_base_device { public: + enum sysbus_mask + { + SYSBUS_M = 0x06, + SYSBUS_TRG = 0x08, + SYSBUS_LOCK = 0x10, + SYSBUS_INT = 0x20, + SYSBUS_BE = 0x80 + }; + enum sysbus_mode + { + MODE_82586 = 0x0, + MODE_32SEGMENTED = 0x2, + MODE_LINEAR = 0x4 + }; + + static const int CFG_SIZE = 14; + static const int DUMP_SIZE = 304; + void port(u32 data); // cpu access interface protected: - i82596_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, u8 datawidth, u8 addrwidth); + i82596_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, endianness_t endian, u8 datawidth); + // standard device_* overrides virtual void device_start() override; virtual void device_reset() override; + // setup and scb + virtual void initialise() override; + + // command unit + virtual bool cu_iasetup() override; + virtual bool cu_configure() override; + virtual bool cu_mcsetup() override; + virtual bool cu_transmit(u32 command) override; + virtual bool cu_tdreflect() override; + virtual bool cu_dump() override; + + // receive unit + virtual bool address_filter(u8 *mac) override; + virtual void ru_execute(u8 *buf, int length) override; + + // helpers + virtual u32 address(u32 base, int offset, int address) override { return (mode() == MODE_LINEAR) ? m_space->read_dword(base + address) : m_scb_base + m_space->read_word(base + offset); } + virtual u32 address(u32 base, int offset, int address, u16 empty) override; + private: + // configure parameters + virtual u8 cfg_get(int offset) const override { return m_cfg_bytes[offset]; } + virtual void cfg_set(int offset, u8 data) override { m_cfg_bytes[offset] = data; } + + bool cfg_crc_in_memory() const { return BIT(cfg_get(11), 2); } + bool cfg_mc_all() const { return BIT(cfg_get(11), 5); } + bool cfg_multi_ia() const { return BIT(cfg_get(13), 6); } + + // initialisation, configuration and sysbus + inline sysbus_mode mode() const { return (sysbus_mode)(m_sysbus & SYSBUS_M); } + + u8 m_cfg_bytes[CFG_SIZE]; + u8 m_sysbus; + u32 m_iscp_address; + + u64 m_mac_multi_ia; // multi-ia address hash table }; -class i82596sx_device : public i82596_base_device +class i82596_le16_device : public i82596_device { public: - i82596sx_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - -protected: - -private: + i82596_le16_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; -class i82596dx_device : public i82596_base_device +class i82596_be16_device : public i82596_device { public: - i82596dx_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + i82596_be16_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); +}; -protected: +class i82596_le32_device : public i82596_device +{ +public: + i82596_le32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); +}; -private: +class i82596_be32_device : public i82596_device +{ +public: + i82596_be32_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; DECLARE_DEVICE_TYPE(I82586, i82586_device) -DECLARE_DEVICE_TYPE(I82596SX, i82596sx_device) -DECLARE_DEVICE_TYPE(I82596DX, i82596dx_device) +DECLARE_DEVICE_TYPE(I82596_LE16, i82596_le16_device) +DECLARE_DEVICE_TYPE(I82596_BE16, i82596_be16_device) +DECLARE_DEVICE_TYPE(I82596_LE32, i82596_le32_device) +DECLARE_DEVICE_TYPE(I82596_BE32, i82596_be32_device) #endif // MAME_MACHINE_I82586_H