summaryrefslogtreecommitdiffstatshomepage
path: root/src
diff options
context:
space:
mode:
author Patrick Mackinlay <pmackinlay@hotmail.com>2017-07-21 16:40:55 +0700
committer Vas Crabb <cuavas@users.noreply.github.com>2017-07-21 19:40:55 +1000
commit754240e3b7a0c647b74dcca1bbdaef12d2eb0fec (patch)
treeee27bc84775a9356034df954193410f6a2784d21 /src
parent3e60ab4a04882a141fa8187bd3b485db50c56451 (diff)
i82586: functional, ~90% complete (#2498)
* i82586: functional, ~90% complete * added dumping of 82586 configuration * i82586: state save * Changed address handling and filtering.
Diffstat (limited to 'src')
-rw-r--r--src/devices/machine/i82586.cpp1900
-rw-r--r--src/devices/machine/i82586.h332
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)")
+
+// 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 };
-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)
+// 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 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 i82596_base_device::device_start()
+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 <class Object> static devcb_base &static_set_out_irq_callback(device_t &device, Object &&cb) { return downcast<i82586_base_device &>(device).m_out_irq.set_callback(std::forward<Object>(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