diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 58f24b5647e..09192e522bc 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -4079,3 +4079,15 @@ if (MACHINES["XC1700E"]~=null) then MAME_DIR .. "src/devices/machine/xc1700e.h", } end + +--------------------------------------------------- +-- +--@src/devices/machine/edlc.h,MACHINES["EDLC"] = true +--------------------------------------------------- + +if (MACHINES["EDLC"]~=null) then + files { + MAME_DIR .. "src/devices/machine/edlc.cpp", + MAME_DIR .. "src/devices/machine/edlc.h", + } +end diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index 45cbcd831b0..bc8233fcf0e 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -710,6 +710,7 @@ MACHINES["AIC6250"] = true MACHINES["DC7085"] = true MACHINES["I82357"] = true MACHINES["XC1700E"] = true +MACHINES["EDLC"] = true -------------------------------------------------- -- specify available bus cores diff --git a/src/devices/machine/edlc.cpp b/src/devices/machine/edlc.cpp new file mode 100644 index 00000000000..a9ebb8a853f --- /dev/null +++ b/src/devices/machine/edlc.cpp @@ -0,0 +1,318 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +/* + * An emulation of the SEEQ 8003 Ethernet Data Link Controller. + * + * This implementation uses transmit/receive fifos which hold entire frames, + * rather than the 16-byte fifos of the real device to simplify logic. Also, + * the bidirectional RxTxEOF line is split into separate read/write handlers + * which must be used strictly as follows: + * + * - rxeof_r() must be read before fifo_r() + * - txeof_w() must be written after fifo_w() + * + * Sources: + * - http://www.bitsavers.org/components/seeq/_dataBooks/1985_SEEQ_Data_Book.pdf + * + * TODO: + * - RxDC (discard) and TxRET (retransmit) logic + * - 80c03 support + * - testing + */ + +#include "emu.h" +#include "edlc.h" +#include "hashing.h" + +#define LOG_GENERAL (1U << 0) +#define LOG_FRAMES (1U << 1) +#define LOG_FILTER (1U << 2) + +//#define VERBOSE (LOG_GENERAL|LOG_FRAMES|LOG_FILTER) +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(SEEQ8003, seeq8003_device, "seeq8003", "SEEQ 8003 EDLC") + +static const u8 ETH_BROADCAST[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +seeq8003_device::seeq8003_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) + : device_t(mconfig, SEEQ8003, tag, owner, clock) + , device_network_interface(mconfig, *this, 10.0f) + , m_out_int(*this) + , m_out_rxrdy(*this) + , m_out_txrdy(*this) +{ +} + +void seeq8003_device::device_start() +{ + m_out_int.resolve_safe(); + m_out_rxrdy.resolve_safe(); + m_out_txrdy.resolve_safe(); + + save_item(NAME(m_int_state)); + save_item(NAME(m_station_address)); + save_item(NAME(m_rx_status)); + save_item(NAME(m_tx_status)); + save_item(NAME(m_rx_command)); + save_item(NAME(m_tx_command)); + + m_tx_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(seeq8003_device::transmit), this)); + m_int_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(seeq8003_device::interrupt), this)); + + m_int_state = 0; +} + +void seeq8003_device::device_reset() +{ + m_rx_status = RXS_O; + m_tx_status = TXS_O; + m_rx_command = 0; + m_tx_command = 0; + + m_rx_fifo.clear(); + m_tx_fifo.clear(); + m_out_rxrdy(0); + + if (m_dev) + m_out_txrdy(1); + + interrupt(); +} + +int seeq8003_device::recv_start_cb(u8 *buf, int length) +{ + // check receiver disabled + if ((m_rx_command & RXC_M) == RXC_M0) + return 0; + + if (address_filter(buf)) + { + LOG("receiving frame length %d\n", length); + dump_bytes(buf, length); + + return receive(buf, length); + } + + return 0; +} + +void seeq8003_device::map(address_map &map) +{ + map.unmap_value_high(); + + map(0, 0).w(FUNC(seeq8003_device::station_address_w<0>)); + map(1, 1).w(FUNC(seeq8003_device::station_address_w<1>)); + map(2, 2).w(FUNC(seeq8003_device::station_address_w<2>)); + map(3, 3).w(FUNC(seeq8003_device::station_address_w<3>)); + map(4, 4).w(FUNC(seeq8003_device::station_address_w<4>)); + map(5, 5).w(FUNC(seeq8003_device::station_address_w<5>)); + map(6, 6).rw(FUNC(seeq8003_device::rx_status_r), FUNC(seeq8003_device::rx_command_w)); + map(7, 7).rw(FUNC(seeq8003_device::tx_status_r), FUNC(seeq8003_device::tx_command_w)); +} + +u8 seeq8003_device::fifo_r() +{ + if (m_rx_fifo.empty()) + fatalerror("seeq8003_device::fifo_r: fifo empty\n"); + + u8 const data = m_rx_fifo.dequeue(); + + if (m_rx_fifo.empty()) + { + // disable rx fifo + m_out_rxrdy(0); + + // schedule interrupt + m_int_timer->adjust(attotime::zero); + } + + return data; +} + +int seeq8003_device::rxeof_r() +{ + return m_rx_fifo.queue_length() == 1; +} + +void seeq8003_device::fifo_w(u8 data) +{ + if (m_tx_fifo.full()) + fatalerror("seeq8003_device::fifo_w: fifo full\n"); + + m_tx_fifo.enqueue(data); + + if (m_tx_fifo.full()) + m_out_txrdy(0); +} + +void seeq8003_device::txeof_w(int state) +{ + if (state) + { + // disable tx fifo + m_out_txrdy(0); + + // schedule transmit + m_tx_timer->adjust(attotime::zero); + } +} + +u8 seeq8003_device::rx_status_r() +{ + u8 const data = m_rx_status; + + // clear interrupt + m_rx_status |= RXS_O; + m_int_timer->adjust(attotime::zero); + + return data; +} + +u8 seeq8003_device::tx_status_r() +{ + u8 const data = m_tx_status; + + // clear interrupt + m_tx_status |= TXS_O; + m_int_timer->adjust(attotime::zero); + + return data; +} + +void seeq8003_device::transmit(void *ptr, int param) +{ + if (m_tx_fifo.queue_length()) + { + u8 buf[MAX_FRAME_SIZE]; + int length = 0; + + // dequeue to buffer + while (!m_tx_fifo.empty()) + buf[length++] = m_tx_fifo.dequeue(); + + // compute and append fcs + u32 const fcs = util::crc32_creator::simple(buf, length); + buf[length++] = (fcs >> 0) & 0xff; + buf[length++] = (fcs >> 8) & 0xff; + buf[length++] = (fcs >> 16) & 0xff; + buf[length++] = (fcs >> 24) & 0xff; + + LOG("transmitting frame length %d\n", length); + dump_bytes(buf, length); + + // transmit the frame + send(buf, length); + + // TODO: transmit errors/TxRET + + // update status + m_tx_status = TXS_S; + } + else + m_tx_status = TXS_U; + + // enable tx fifo + m_out_txrdy(1); + + interrupt(); +} + +int seeq8003_device::receive(u8 *buf, int length) +{ + // discard if rx status has not been read + // TODO: RxDC + if (!(m_rx_status & RXS_O)) + return 0; + + m_rx_status = RXS_E; + + // check for errors + u32 const fcs = util::crc32_creator::simple(buf, length); + if (length < 64) + m_rx_status |= RXS_S; + else if (~fcs != FCS_RESIDUE) + m_rx_status |= RXS_C; + else + m_rx_status |= RXS_G; + + // enqueue from buffer + for (unsigned i = 0; i < length - 4; i++) + m_rx_fifo.enqueue(buf[i]); + + // enable rx fifo + m_out_rxrdy(1); + + return length; +} + +void seeq8003_device::interrupt(void *ptr, int param) +{ + int const state = + (!(m_tx_status & TXS_O) && (m_tx_status & m_tx_command & TXS_M)) || + (!(m_rx_status & RXS_O) && (m_rx_status & m_rx_command & RXS_M)); + + // TODO: assert RxDC for masked rx crc or short frame errors + + if (state != m_int_state) + { + m_int_state = state; + m_out_int(state); + } +} + +bool seeq8003_device::address_filter(u8 *address) +{ + LOGMASKED(LOG_FILTER, "address_filter testing destination address %02x:%02x:%02x:%02x:%02x:%02x\n", + address[0], address[1], address[2], address[3], address[4], address[5]); + + if ((m_rx_command & RXC_M) == RXC_M1) + { + LOG("address_filter accepted: promiscuous mode enabled\n"); + + return true; + } + + // ethernet broadcast + if (!memcmp(address, ETH_BROADCAST, 6)) + { + LOGMASKED(LOG_FILTER, "address_filter accepted: broadcast\n"); + + return true; + } + + // station address + if (!memcmp(address, m_station_address, 6)) + { + LOGMASKED(LOG_FILTER, "address_filter accepted: station address match\n"); + + return true; + } + + // ethernet multicast + if (((m_rx_command & RXC_M) == RXC_M3) && (address[0] & 0x1)) + { + LOGMASKED(LOG_FILTER, "address_filter accepted: multicast address match\n"); + + return true; + } + + return false; +} + +void seeq8003_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]); + } +} diff --git a/src/devices/machine/edlc.h b/src/devices/machine/edlc.h new file mode 100644 index 00000000000..91bd37010d3 --- /dev/null +++ b/src/devices/machine/edlc.h @@ -0,0 +1,125 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +#ifndef MAME_MACHINE_EDLC_H +#define MAME_MACHINE_EDLC_H + +#pragma once + +class seeq8003_device : + public device_t, + public device_network_interface +{ +public: + // callback configuration + auto out_int_cb() { return m_out_int.bind(); } + auto out_rxrdy_cb() { return m_out_rxrdy.bind(); } + auto out_txrdy_cb() { return m_out_txrdy.bind(); } + + seeq8003_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 0); + + // host interface + void map(address_map &map); + u8 fifo_r(); + int rxeof_r(); + void fifo_w(u8 data); + void txeof_w(int state); + +protected: + // device_t overrides + virtual void device_start() override; + virtual void device_reset() override; + + // device_network_interface overrides + virtual int recv_start_cb(u8 *buf, int length) override; + + // command/status interface + template void station_address_w(u8 data) { m_station_address[N] = data; } + u8 rx_status_r(); + u8 tx_status_r(); + void rx_command_w(u8 data) { m_rx_command = data; } + void tx_command_w(u8 data) { m_tx_command = data & TXC_M; } + + // helpers + void transmit(void *ptr, int param); + int receive(u8 *buf, int length); + void interrupt(void *ptr = nullptr, int param = 0); + bool address_filter(u8 *address); + void dump_bytes(u8 *buf, int length); + + // constants and masks + static const unsigned MAX_FRAME_SIZE = 1518; + static const u32 FCS_RESIDUE = 0xdebb20e3; + + enum rx_status_mask : u8 + { + RXS_V = 0x01, // received frame with overflow error + RXS_C = 0x02, // received frame with crc error + RXS_D = 0x04, // received frame with dribble error + RXS_S = 0x08, // received short frame + RXS_E = 0x10, // received end of frame + RXS_G = 0x20, // received good frame + RXS_O = 0x80, // old/new status + + RXS_M = 0x3f, // mask + }; + enum rx_command_mask : u8 + { + RXC_V = 0x01, // interrupt on receive overflow error + RXC_C = 0x02, // interrupt on receive crc error + RXC_D = 0x04, // interrupt on receive dribble error + RXC_S = 0x08, // interrupt on short frame + RXC_E = 0x10, // interrupt on end of frame + RXC_G = 0x20, // interrupt on good frame + RXC_M = 0xc0, // match mode + }; + enum rx_match_mask : u8 + { + RXC_M0 = 0x00, // receiver disable + RXC_M1 = 0x40, // receive all frames + RXC_M2 = 0x80, // receive station or broadcast frames + RXC_M3 = 0xc0, // receive station, broadcast/multicast frames + }; + enum tx_status_mask : u8 + { + TXS_U = 0x01, // transmit underflow + TXS_C = 0x02, // transmit collision + TXS_R = 0x04, // 16 transmission attempts + TXS_S = 0x08, // transmission successful + TXS_O = 0x80, // old/new status + + TXS_M = 0x0f, // mask + }; + enum tx_command_mask : u8 + { + TXC_U = 0x01, // interrupt on transmit underflow + TXC_C = 0x02, // interrupt on transmit collision + TXC_R = 0x04, // interrupt on 16 transmission attempts + TXC_S = 0x08, // interrupt on transmit success + + TXC_M = 0x0f, // write mask + }; + +private: + emu_timer *m_tx_timer; + emu_timer *m_int_timer; + + // device state + devcb_write_line m_out_int; + devcb_write_line m_out_rxrdy; + devcb_write_line m_out_txrdy; + + int m_int_state; + u8 m_station_address[6]; + u8 m_rx_status; + u8 m_tx_status; + u8 m_rx_command; + u8 m_tx_command; + + util::fifo m_rx_fifo; + util::fifo m_tx_fifo; +}; + +DECLARE_DEVICE_TYPE(SEEQ8003, seeq8003_device) + +#endif // MAME_MACHINE_EDLC_H