summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Patrick Mackinlay <pmackinlay@hotmail.com>2019-06-26 20:21:15 +0700
committer Patrick Mackinlay <pmackinlay@hotmail.com>2019-06-26 20:21:15 +0700
commit4b70016e833fa55b735108b5ebb9f800db0b47d4 (patch)
tree4153e9c7bea6ec4ace832a98bad071a023a62a5c
parente78ac44ab0d5f449b282db64dfe57487594bbc6f (diff)
new device seeq8003
-rw-r--r--scripts/src/machine.lua12
-rw-r--r--scripts/target/mame/mess.lua1
-rw-r--r--src/devices/machine/edlc.cpp318
-rw-r--r--src/devices/machine/edlc.h125
4 files changed, 456 insertions, 0 deletions
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 <unsigned N> 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<u8, MAX_FRAME_SIZE> m_rx_fifo;
+ util::fifo<u8, MAX_FRAME_SIZE> m_tx_fifo;
+};
+
+DECLARE_DEVICE_TYPE(SEEQ8003, seeq8003_device)
+
+#endif // MAME_MACHINE_EDLC_H