new device seeq8003

This commit is contained in:
Patrick Mackinlay 2019-06-26 20:21:15 +07:00
parent e78ac44ab0
commit 4b70016e83
4 changed files with 456 additions and 0 deletions

View File

@ -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

View File

@ -710,6 +710,7 @@ MACHINES["AIC6250"] = true
MACHINES["DC7085"] = true
MACHINES["I82357"] = true
MACHINES["XC1700E"] = true
MACHINES["EDLC"] = true
--------------------------------------------------
-- specify available bus cores

View File

@ -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]);
}
}

125
src/devices/machine/edlc.h Normal file
View File

@ -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