interpro: notworking -> networking (#3815)

* interpro: notworking -> networking

These changes combine to make InterPro networking work on Windows with the TAP-Windows6 driver.
* osdnet: add a receive delay (1 frame) after transmit to avoid a time-travel problem
* taptun: pad short Ethernet frames and append FCS (Windows-only until Linux taptun behaviour is verified)
* clipper: fix bugs in carry flag handling, prefer sign bit for tests
* i82586: fix transmit bug, handle reset

* networking: delayed transmit/receive

A second attempt to fix networking on InterPro systems, by introducing somewhat realistic delays into network transmit and receive paths. This version works by adding functions to device_network_interface which enable a device to be informed when the transmit or receive completes. The delay is only crudely approximated based on the specified bandwidth and the number of bytes being transmitted, but it should be good enough in practice. Existing drivers should not be impacted by these changes; overriding the new functions (and no longer overriding recv_cb) is necessary to obtain the new behaviour.

Changes from the previous commit:
* i82586: improve interrupt handling, implement delayed transmit/receive behaviour
* dinetwork: add transmit/receive delay timers, handlers and logic
* osdnet: remove receive delay, add the ability to start the receive timer
This commit is contained in:
Patrick Mackinlay 2018-09-04 16:26:58 +07:00 committed by Vas Crabb
parent 21fa0dfd4f
commit 8919ce5645
7 changed files with 276 additions and 128 deletions

View File

@ -42,7 +42,7 @@
#include "logmacro.h"
// disable FCS insertion (on transmit) and checking (on receive) because pcap doesn't expose them
// disable FCS insertion on transmit
#define I82586_FCS 0
ALLOW_SAVE_TYPE(i82586_base_device::cu_state);
@ -112,46 +112,53 @@ CFG_PARAMS[] =
};
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", 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)
{}
: device_t(mconfig, type, tag, owner, clock)
, device_memory_interface(mconfig, *this)
, device_network_interface(mconfig, *this, 10.0f)
, m_space_config("shared", endian, datawidth, addrwidth)
, m_out_irq(*this)
, m_cx(false)
, m_fr(false)
, m_cna(false)
, m_rnr(false)
, m_initialised(false)
, m_irq_assert(1)
, 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, ENDIANNESS_LITTLE, 16, 24)
{}
{
}
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)
{}
{
}
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)
{}
{
}
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()
@ -169,7 +176,6 @@ void i82586_base_device::device_start()
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));
@ -197,7 +203,6 @@ void i82586_base_device::device_reset()
m_fr = false;
m_cna = false;
m_rnr = false;
m_irq_state = false;
m_initialised = false;
m_cu_state = CU_IDLE;
@ -213,7 +218,6 @@ void i82586_base_device::device_timer(emu_timer &timer, device_timer_id id, int
{
case CU_TIMER:
cu_execute();
update_scb();
break;
case RU_TIMER:
@ -237,7 +241,7 @@ device_memory_interface::space_config_vector i82586_base_device::memory_space_co
WRITE_LINE_MEMBER(i82586_base_device::ca)
{
LOG("channel attention %s (%s)\n", state ? "asserted" : "deasserted", machine().describe_context());
LOG("channel attention %s (%s)\n", state ? "asserted" : "cleared", machine().describe_context());
if (state)
{
@ -249,7 +253,7 @@ WRITE_LINE_MEMBER(i82586_base_device::ca)
}
}
void i82586_base_device::recv_cb(u8 *buf, int length)
int i82586_base_device::recv_start_cb(u8 *buf, int length)
{
switch (m_ru_state)
{
@ -264,9 +268,7 @@ void i82586_base_device::recv_cb(u8 *buf, int length)
LOG("recv_cb receiving frame length %d\n", length);
dump_bytes(buf, length);
ru_execute(buf, length);
update_scb();
return ru_execute(buf, length);
}
break;
@ -275,6 +277,15 @@ void i82586_base_device::recv_cb(u8 *buf, int length)
// TODO: accumulate statistics
break;
}
return 0;
}
void i82586_base_device::recv_complete_cb(int result)
{
ru_complete(result);
update_scb();
}
void i82586_base_device::process_scb()
@ -282,12 +293,20 @@ void i82586_base_device::process_scb()
// fetch current command and status
m_scb_cs = m_space->read_dword(m_scb_address);
// handle reset
if (m_scb_cs & RESET)
{
LOG("process_scb reset\n");
device_reset();
return;
}
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,
LOG("process_scb command/status 0x%08x (cuc %s, ruc %s)\n", m_scb_cs,
CUC_NAME[(m_scb_cs & CUC) >> 24],
RUC_NAME[(m_scb_cs & RUC) >> 20],
m_scb_cs & RESET ? ", reset" : "");
RUC_NAME[(m_scb_cs & RUC) >> 20]);
// clear interrupt flags when acknowledged
if (m_scb_cs & ACK_CX)
@ -379,6 +398,8 @@ void i82586_base_device::update_scb()
(m_cu_state << 8) |
(m_ru_state << 4));
LOG("update_scb%s%s%s%s\n", m_cx ? " CX" : "", m_fr ? " FR" : "", m_cna ? " CNA" : "", m_rnr ? " RNR" : "");
// update interrupt status
set_irq(m_cx || m_fr || m_cna || m_rnr);
}
@ -386,7 +407,8 @@ void i82586_base_device::update_scb()
void i82586_base_device::cu_execute()
{
// fetch the command block command/status
u32 cb_cs = m_space->read_dword(m_cba);
const u32 cb_cs = m_space->read_dword(m_cba);
u16 status = 0;
// set busy status
m_space->write_dword(m_cba, cb_cs | CB_B);
@ -400,52 +422,57 @@ void i82586_base_device::cu_execute()
switch (cb_cs & CB_CMD)
{
case CB_NOP:
cb_cs |= CB_OK;
status |= CB_OK;
break;
case CB_IASETUP:
if (cu_iasetup())
cb_cs |= CB_OK;
status |= CB_OK;
break;
case CB_CONFIGURE:
if (cu_configure())
cb_cs |= CB_OK;
status |= CB_OK;
break;
case CB_MCSETUP:
if (cu_mcsetup())
cb_cs |= CB_OK;
status |= CB_OK;
break;
case CB_TRANSMIT:
// always turn on the heartbeat indicator status after a successful transmission; not
// strictly correct, but allows one InterPro 2000 diagnostic to pass
if (cu_transmit(cb_cs))
cb_cs |= CB_OK | CB_S6;
return;
break;
case CB_TDREFLECT:
if (cu_tdreflect())
cb_cs |= CB_OK;
status |= CB_OK;
break;
case CB_DUMP:
if (cu_dump())
cb_cs |= CB_OK;
status |= CB_OK;
break;
case CB_DIAGNOSE:
cb_cs |= CB_OK;
status |= CB_OK;
break;
}
}
else
// abort status
cb_cs |= CB_A;
status |= CB_A;
// complete command
cu_complete(status);
}
void i82586_base_device::cu_complete(const u16 status)
{
// clear busy status and set completion status
m_space->write_dword(m_cba, cb_cs | CB_C);
const u32 cb_cs = m_space->read_dword(m_cba);
m_space->write_dword(m_cba, (cb_cs & ~0xffffU) | CB_C | status);
// chain to next command
if (!(cb_cs & CB_EL))
@ -479,7 +506,9 @@ void i82586_base_device::cu_execute()
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);
m_cx = (cb_cs & CB_I) && (status & CB_OK);
update_scb();
}
bool i82586_base_device::address_filter(u8 *mac)
@ -531,15 +560,12 @@ bool i82586_base_device::address_filter(u8 *mac)
// shared helpers
void i82586_base_device::set_irq(bool irq)
{
if (m_irq_state != irq)
if (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);
LOG("irq asserted\n");
m_out_irq(m_irq_assert);
m_out_irq(!m_irq_assert);
}
}
@ -747,6 +773,7 @@ void i82586_device::device_start()
{
i82586_base_device::device_start();
save_item(NAME(m_rbd_offset));
save_item(NAME(m_cfg_bytes));
}
@ -987,10 +1014,17 @@ bool i82586_device::cu_transmit(u32 command)
LOG("cu_transmit sending frame length %d\n", length);
dump_bytes(buf, length);
return send(buf, length) == 0;
return send(buf, length) == length;
}
}
void i82586_base_device::send_complete_cb(int result)
{
// always turn on the heartbeat indicator status after a successful transmit; not
// strictly correct, but allows one InterPro 2000 diagnostic to pass
cu_complete(result ? (CB_OK | CB_S6) : CB_S9);
}
bool i82586_device::cu_tdreflect()
{
m_space->write_word(m_cba + 6, TDR_LNK_OK | TDR_TIME);
@ -1036,10 +1070,11 @@ bool i82586_device::address_filter(u8 *mac)
return false;
}
void i82586_device::ru_execute(u8 *buf, int length)
u16 i82586_device::ru_execute(u8 *buf, int length)
{
// fetch receive frame descriptor command/status
u32 rfd_cs = m_space->read_dword(m_rfd);
u16 status = 0;
// current buffer position and bytes remaining
int position = 0, remaining = length;
@ -1051,9 +1086,8 @@ void i82586_device::ru_execute(u8 *buf, int length)
// set short frame status
if (length < cfg_min_frame_length())
rfd_cs |= RFD_S_SHORT;
status |= RFD_S_SHORT;
#if I82586_FCS
// set crc status
if (~compute_crc(buf, length, cfg_crc16()) != FCS_RESIDUE)
{
@ -1063,15 +1097,14 @@ void i82586_device::ru_execute(u8 *buf, int length)
// 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;
status |= 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);
m_rbd_offset = m_space->read_word(m_rfd + 6);
if (!cfg_no_src_add_ins())
{
@ -1087,11 +1120,11 @@ void i82586_device::ru_execute(u8 *buf, int length)
}
// store remaining bytes in receive buffers
while (remaining && rbd_offset != RBD_EMPTY)
while (remaining && m_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);
u32 rb_address = m_space->read_dword(m_scb_base + m_rbd_offset + 4);
u16 rbd_size = m_space->read_word(m_scb_base + m_rbd_offset + 8);
// compute number of bytes to store in buffer
int actual = remaining > (rbd_size & RB_SIZE) ? (rbd_size & RB_SIZE) : remaining;
@ -1104,17 +1137,17 @@ void i82586_device::ru_execute(u8 *buf, int length)
remaining -= actual;
// store actual count
m_space->write_word(m_scb_base + rbd_offset + 0, actual | RB_F | (remaining ? 0 : RB_EOF));
m_space->write_word(m_scb_base + m_rbd_offset + 0, actual | RB_F | (remaining ? 0 : RB_EOF));
// check if buffers exhausted
if ((rbd_size & RB_EL))
{
rbd_offset = RBD_EMPTY;
m_rbd_offset = RBD_EMPTY;
if (remaining)
{
// set buffers exhausted status
rfd_cs |= RFD_S_BUFFER;
status |= RFD_S_BUFFER;
m_ru_state = RU_NR;
m_rnr = true;
@ -1122,28 +1155,33 @@ void i82586_device::ru_execute(u8 *buf, int length)
}
else
// fetch next rbd offset
rbd_offset = m_space->read_word(m_scb_base + rbd_offset + 2);
m_rbd_offset = m_space->read_word(m_scb_base + m_rbd_offset + 2);
}
if (remaining == 0 || cfg_save_bad_frames())
// set frame received status
rfd_cs |= RFD_C;
status |= RFD_C;
// frame received without errors
if (!(rfd_cs & RFD_ERROR_82586))
{
LOG("ru_execute frame received without error\n");
if (!(status & RFD_ERROR_82586))
status |= RFD_OK;
rfd_cs |= RFD_OK;
}
return status;
}
void i82586_device::ru_complete(const u16 status)
{
if (status & RFD_OK)
LOG("ru_complete frame received without error\n");
else
LOG("ru_execute frame received with errors status 0x%04x\n", rfd_cs);
LOG("ru_complete frame received with errors status 0x%04x\n", status);
// store status
m_space->write_dword(m_rfd, rfd_cs);
// update receive frame descriptor status
u32 rfd_cs = m_space->read_dword(m_rfd);
m_space->write_dword(m_rfd, (rfd_cs & ~0xffffU) | status);
// 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 ((status & RFD_OK) || cfg_save_bad_frames())
{
if (!(rfd_cs & RFD_EL))
{
@ -1151,8 +1189,8 @@ void i82586_device::ru_execute(u8 *buf, int length)
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);
if (m_rbd_offset != RBD_EMPTY)
m_space->write_word(m_rfd + 6, m_rbd_offset);
}
else
{
@ -1172,7 +1210,7 @@ void i82586_device::ru_execute(u8 *buf, int length)
}
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]);
LOG("ru_complete complete state %s\n", RU_STATE_NAME[m_ru_state]);
}
u32 i82586_device::address(u32 base, int offset, int address, u16 empty)
@ -1191,6 +1229,8 @@ void i82596_device::device_start()
save_item(NAME(m_sysbus));
save_item(NAME(m_irq_assert));
save_item(NAME(m_rbd_address));
save_item(NAME(m_mac_multi_ia));
}
@ -1267,6 +1307,9 @@ void i82596_device::initialise()
break;
}
// configure interrupt polarity
m_irq_assert = (m_sysbus & SYSBUS_INT) ? 0 : 1;
// clear iscp busy byte
m_space->write_byte(iscp_address, 0);
@ -1652,7 +1695,7 @@ bool i82596_device::cu_transmit(u32 command)
LOG("cu_transmit sending frame length %d\n", length);
dump_bytes(buf, length);
return send(buf, length) == 0;
return send(buf, length) == length;
}
}
@ -1743,10 +1786,11 @@ bool i82596_device::address_filter(u8 *mac)
return false;
}
void i82596_device::ru_execute(u8 *buf, int length)
u16 i82596_device::ru_execute(u8 *buf, int length)
{
// fetch receive frame descriptor command/status
u32 rfd_cs = m_space->read_dword(m_rfd);
const u32 rfd_cs = m_space->read_dword(m_rfd);
u16 status = 0;
// offset into rfd/rbd for linear mode
int linear_offset = mode() == MODE_LINEAR ? 4 : 0;
@ -1782,10 +1826,9 @@ void i82596_device::ru_execute(u8 *buf, int length)
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;
status |= RFD_S_SHORT;
}
#if I82586_FCS
// set crc status
if (~compute_crc(buf, length, cfg_crc16()) != FCS_RESIDUE)
{
@ -1798,19 +1841,18 @@ void i82596_device::ru_execute(u8 *buf, int length)
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;
status |= 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;
status |= RFD_S_MULTICAST;
// fetch initial rbd address from rfd
u32 rbd_address = address(m_rfd, 6, 8, RBD_EMPTY);
m_rbd_address = address(m_rfd, 6, 8, RBD_EMPTY);
// check for simplified mode
if (mode() != MODE_82586 && !(rfd_cs & RFD_SF))
@ -1839,7 +1881,7 @@ void i82596_device::ru_execute(u8 *buf, int length)
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);
status |= RFD_C | (actual < length ? RFD_S_TRUNCATED : 0);
}
else
LOG("ru_execute discarding %d byte frame exceeding rfd size %d\n", length, rfd_size);
@ -1879,11 +1921,11 @@ void i82596_device::ru_execute(u8 *buf, int length)
}
// store remaining bytes in receive buffers
while (remaining && rbd_address != RBD_EMPTY)
while (remaining && m_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);
u32 rb_address = m_space->read_dword(m_rbd_address + 4 + linear_offset);
u16 rbd_size = m_space->read_word(m_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;
@ -1896,17 +1938,17 @@ void i82596_device::ru_execute(u8 *buf, int length)
remaining -= actual;
// store actual count
m_space->write_word(rbd_address + 0, actual | RB_F | (remaining ? 0 : RB_EOF));
m_space->write_word(m_rbd_address + 0, actual | RB_F | (remaining ? 0 : RB_EOF));
// check if buffers exhausted
if ((rbd_size & RB_EL))
{
rbd_address = RBD_EMPTY;
m_rbd_address = RBD_EMPTY;
if (remaining)
{
// set buffers exhausted status
rfd_cs |= RFD_S_BUFFER;
status |= RFD_S_BUFFER;
m_ru_state = mode() == MODE_82586 ? RU_NR : RU_NR_RBD;
m_rnr = true;
@ -1914,26 +1956,30 @@ void i82596_device::ru_execute(u8 *buf, int length)
}
else
// fetch next rbd address
rbd_address = address(rbd_address, 2, 4);
m_rbd_address = address(m_rbd_address, 2, 4);
}
if (remaining == 0 || cfg_save_bad_frames())
// set frame received status
rfd_cs |= RFD_C;
status |= 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");
if (!(status & (mode() == MODE_82586 ? RFD_ERROR_82586 : RFD_ERROR)))
status |= RFD_OK;
rfd_cs |= RFD_OK;
}
return status;
}
void i82596_device::ru_complete(const u16 status)
{
if (status & RFD_OK)
LOG("ru_complete frame received without error\n");
else
LOG("ru_execute frame received with errors status 0x%04x\n", rfd_cs);
LOG("ru_complete frame received with errors status 0x%04x\n", status);
// store status
m_space->write_dword(m_rfd, rfd_cs);
const u32 rfd_cs = m_space->read_dword(m_rfd);
m_space->write_dword(m_rfd, (rfd_cs & ~0xffffU) | status);
// 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())
@ -1944,12 +1990,12 @@ void i82596_device::ru_execute(u8 *buf, int length)
m_rfd = address(m_rfd, 4, 4);
// store next free rbd address into rfd
if (rbd_address != RBD_EMPTY)
if (m_rbd_address != RBD_EMPTY)
{
if (mode() == MODE_LINEAR)
m_space->write_dword(m_rfd + 8, rbd_address);
m_space->write_dword(m_rfd + 8, m_rbd_address);
else
m_space->write_word(m_rfd + 6, rbd_address - m_scb_base);
m_space->write_word(m_rfd + 6, m_rbd_address - m_scb_base);
}
}
else
@ -1970,7 +2016,7 @@ void i82596_device::ru_execute(u8 *buf, int length)
}
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]);
LOG("ru_complete complete state %s\n", RU_STATE_NAME[m_ru_state]);
}
u32 i82596_device::address(u32 base, int offset, int address, u16 empty)

View File

@ -158,7 +158,6 @@ public:
auto out_irq_cb() { return m_out_irq.bind(); }
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, endianness_t endian, u8 datawidth, u8 addrwidth);
@ -170,6 +169,11 @@ protected:
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;
// device_network_interface overrides
virtual void send_complete_cb(int result) override;
virtual int recv_start_cb(u8 *buf, int length) override;
virtual void recv_complete_cb(int result) override;
// setup and scb
virtual void initialise() = 0;
virtual void process_scb();
@ -177,6 +181,7 @@ protected:
// command unit
virtual void cu_execute();
virtual void cu_complete(const u16 status);
virtual bool cu_iasetup() = 0;
virtual bool cu_configure() = 0;
virtual bool cu_mcsetup() = 0;
@ -186,7 +191,8 @@ protected:
// receive unit
virtual bool address_filter(u8 *mac);
virtual void ru_execute(u8 *buf, int length) = 0;
virtual u16 ru_execute(u8 *buf, int length) = 0;
virtual void ru_complete(const u16 status) = 0;
// helpers
void set_irq(bool irq);
@ -213,8 +219,8 @@ protected:
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;
int m_irq_assert; // configurable interrupt polarity
// receive/command unit state
cu_state m_cu_state; // command unit state
@ -279,7 +285,8 @@ protected:
// receive unit
virtual bool address_filter(u8 *mac) override;
virtual void ru_execute(u8 *buf, int length) override;
virtual u16 ru_execute(u8 *buf, int length) override;
virtual void ru_complete(const u16 status) override;
// helpers
virtual u32 address(u32 base, int offset, int address) override { return m_scb_base + m_space->read_word(base + offset); }
@ -291,6 +298,7 @@ private:
virtual void cfg_set(int offset, u8 data) override { m_cfg_bytes[offset] = data; }
u8 m_cfg_bytes[CFG_SIZE];
u16 m_rbd_offset; // next available receive buffer descriptor
};
class i82596_device : public i82586_base_device
@ -336,7 +344,8 @@ protected:
// receive unit
virtual bool address_filter(u8 *mac) override;
virtual void ru_execute(u8 *buf, int length) override;
virtual u16 ru_execute(u8 *buf, int length) override;
virtual void ru_complete(const u16 status) 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); }
@ -357,6 +366,7 @@ private:
u8 m_cfg_bytes[CFG_SIZE];
u8 m_sysbus;
u32 m_rbd_address; // next available receive buffer descriptor
u64 m_mac_multi_ia; // multi-ia address hash table
};

View File

@ -16,14 +16,56 @@ device_network_interface::~device_network_interface()
{
}
void device_network_interface::interface_pre_start()
{
m_send_timer = device().machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(device_network_interface::send_complete), this));
m_recv_timer = device().machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(device_network_interface::recv_complete), this));
}
int device_network_interface::send(u8 *buf, int len) const
{
if(!m_dev) return 0;
return m_dev->send(buf, len);
// TODO: enable this check when other devices implement delayed transmit
//assert_always(!m_send_timer->enabled(), "attempted to transmit while transmit already in progress");
// send the data
int result = m_dev->send(buf, len);
// schedule transmit complete callback
m_send_timer->adjust(attotime::from_ticks(len, m_bandwidth * 1'000'000 / 8), result);
return result;
}
TIMER_CALLBACK_MEMBER(device_network_interface::send_complete)
{
send_complete_cb(param);
}
void device_network_interface::recv_cb(u8 *buf, int len)
{
assert_always(!m_recv_timer->enabled(), "attempted to receive while receive already in progress");
// process the received data
int result = recv_start_cb(buf, len);
if (result)
{
// stop receiving more data from the network
m_dev->stop();
// schedule receive complete callback
m_recv_timer->adjust(attotime::from_ticks(len, m_bandwidth * 1'000'000 / 8), result);
}
}
TIMER_CALLBACK_MEMBER(device_network_interface::recv_complete)
{
recv_complete_cb(param);
// start receiving data from the network again
m_dev->start();
}
void device_network_interface::set_promisc(bool promisc)

View File

@ -11,6 +11,8 @@ public:
device_network_interface(const machine_config &mconfig, device_t &device, float bandwidth);
virtual ~device_network_interface();
void interface_pre_start() override;
void set_interface(int id);
void set_promisc(bool promisc);
void set_mac(const char *mac);
@ -20,14 +22,27 @@ public:
int get_interface() const { return m_intf; }
int send(u8 *buf, int len) const;
// TODO: de-virtualise this when existing devices implement delayed receive
virtual void recv_cb(u8 *buf, int len);
// delayed transmit/receive handlers
virtual void send_complete_cb(int result) {}
virtual int recv_start_cb(u8 *buf, int len) { return 0; }
virtual void recv_complete_cb(int result) {}
protected:
TIMER_CALLBACK_MEMBER(send_complete);
TIMER_CALLBACK_MEMBER(recv_complete);
bool m_promisc;
char m_mac[6];
float m_bandwidth;
std::unique_ptr<osd_netdev> m_dev;
int m_intf;
emu_timer *m_send_timer;
emu_timer *m_recv_timer;
};

View File

@ -28,6 +28,9 @@
// for some reason this isn't defined in the header, and presumably it changes
// with major? versions of the driver - perhaps it should be configurable?
#define PRODUCT_TAP_WIN_COMPONENT_ID "tap0901"
// Ethernet minimum frame length
static constexpr int ETHERNET_MIN_FRAME = 64;
#endif
class taptun_module : public osd_module, public netdev_module
@ -53,10 +56,10 @@ public:
netdev_tap(const char *name, class device_network_interface *ifdev, int rate);
~netdev_tap();
int send(uint8_t *buf, int len);
int send(uint8_t *buf, int len) override;
void set_mac(const char *mac);
protected:
int recv_dev(uint8_t **buf);
int recv_dev(uint8_t **buf) override;
private:
#if defined(WIN32)
HANDLE m_handle = INVALID_HANDLE_VALUE;
@ -136,6 +139,35 @@ void netdev_tap::set_mac(const char *mac)
}
#if defined(WIN32)
static u32 finalise_frame(u8 buf[], u32 length)
{
/*
* On Windows, the taptun driver receives frames which are shorter
* than the Ethernet minimum. Partly this is because it can't see
* the frame check sequence bytes, but mainly it's because NDIS
* expects the lower level device to add the required padding.
* We do the equivalent padding here (i.e. pad with zeroes to the
* minimum Ethernet length minus FCS), so that devices which check
* for this will not reject these packets.
*/
if (length < ETHERNET_MIN_FRAME - 4)
{
std::fill_n(&buf[length], ETHERNET_MIN_FRAME - length - 4, 0);
length = ETHERNET_MIN_FRAME - 4;
}
// compute and append the frame check sequence
const u32 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;
return length;
}
int netdev_tap::send(uint8_t *buf, int len)
{
OVERLAPPED overlapped = {};
@ -171,7 +203,7 @@ int netdev_tap::recv_dev(uint8_t **buf)
// handle unexpected synchronous completion
*buf = m_buf;
return bytes_transferred;
return finalise_frame(m_buf, bytes_transferred);
}
else if (GetLastError() == ERROR_IO_PENDING)
m_receive_pending = true;
@ -184,7 +216,7 @@ int netdev_tap::recv_dev(uint8_t **buf)
m_receive_pending = false;
*buf = m_buf;
return bytes_transferred;
return finalise_frame(m_buf, bytes_transferred);
}
}

View File

@ -38,7 +38,6 @@ class osd_netdev *open_netdev(int id, class device_network_interface *ifdev, int
osd_netdev::osd_netdev(class device_network_interface *ifdev, int rate)
{
m_dev = ifdev;
m_stop = false;
m_timer = ifdev->device().machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(osd_netdev::recv), this));
m_timer->adjust(attotime::from_hz(rate), 0, attotime::from_hz(rate));
}
@ -47,10 +46,14 @@ osd_netdev::~osd_netdev()
{
}
void osd_netdev::start()
{
m_timer->enable(true);
}
void osd_netdev::stop()
{
m_stop = true;
m_timer->reset();
m_timer->enable(false);
}
int osd_netdev::send(uint8_t *buf, int len)
@ -63,7 +66,7 @@ void osd_netdev::recv(void *ptr, int param)
uint8_t *buf;
int len;
//const char atalkmac[] = { 0x09, 0x00, 0x07, 0xff, 0xff, 0xff };
while((!m_stop) && (len = recv_dev(&buf)))
while(m_timer->enabled() && (len = recv_dev(&buf)))
{
#if 0
if(buf[0] & 1)

View File

@ -22,6 +22,7 @@ public:
};
osd_netdev(class device_network_interface *ifdev, int rate);
virtual ~osd_netdev();
void start();
void stop();
virtual int send(uint8_t *buf, int len);
@ -39,7 +40,6 @@ private:
class device_network_interface *m_dev;
emu_timer *m_timer;
bool m_stop;
};
class osd_netdev *open_netdev(int id, class device_network_interface *ifdev, int rate);