// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*************************************************************************
SMC91C9X ethernet controller implementation
by Aaron Giles, Ted Green
***************************************************************************/
#include "emu.h"
#include "smc91c9x.h"
#include <sstream>
#include <iomanip>
/***************************************************************************
DEBUGGING
***************************************************************************/
#define LOG_GENERAL (1U << 0)
#define LOG_PACKETS (1U << 1)
#define LOG_TX (1U << 2)
#define LOG_RX (1U << 3)
#define LOG_FILTER (1U << 4)
//#define VERBOSE (LOG_GENERAL | LOG_PACKETS | LOG_TX | LOG_RX | LOG_FILTER)
#include "logmacro.h"
#define DISPLAY_STATS (0)
/* Ethernet register names */
static const char *const ethernet_regname[64] =
{
"TCR", "EPH STATUS", "RCR", "COUNTER", "MIR", "MCR", "(0.6)", "BANK",
"CONFIG", "BASE", "IA0-1", "IA2-3", "IA4-5", "GENERAL PURPOSE", "CONTROL", "BANK",
"MMU COMMAND", "PNR ARR", "FIFO PORTS", "POINTER", "DATA", "DATA", "INTERRUPT", "BANK",
"MT0-1", "MT2-3", "MT4-5", "MT6-7", "MGMT", "REVISION", "ERCV", "BANK",
"(4.0)", "(4.1)", "(4.2)", "(4.3)", "(4.4)", "(4.5)", "(4.6)", "BANK",
"(5.0)", "(5.1)", "(5.2)", "(5.3)", "(5.4)", "(5.5)", "(5.6)", "BANK",
"(6.0)", "(6.1)", "(6.2)", "(6.3)", "(6.4)", "(6.5)", "(6.6)", "BANK",
"(7.0)", "(7.1)", "(7.2)", "(7.3)", "(7.4)", "(7.5)", "(7.6)", "BANK"
};
/***************************************************************************
DEVICE INTERFACE
***************************************************************************/
DEFINE_DEVICE_TYPE(SMC91C94, smc91c94_device, "smc91c94", "SMC91C94 Ethernet Controller")
smc91c94_device::smc91c94_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: smc91c9x_device(mconfig, SMC91C94, tag, owner, clock, dev_type::SMC91C94)
{
m_num_ebuf = 18;
}
DEFINE_DEVICE_TYPE(SMC91C96, smc91c96_device, "smc91c96", "SMC91C96 Ethernet Controller")
smc91c96_device::smc91c96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: smc91c9x_device(mconfig, SMC91C96, tag, owner, clock, dev_type::SMC91C96)
{
m_num_ebuf = 24;
}
smc91c9x_device::smc91c9x_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, dev_type device_type)
: device_t(mconfig, type, tag, owner, clock)
, device_network_interface(mconfig, *this, 10.0f)
, m_device_type(device_type)
, m_num_ebuf(16)
, m_irq_handler(*this)
, m_link_unconnected(false)
{
}
const u8 smc91c9x_device::ETH_BROADCAST[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
const u8 smc91c9x_device::WMS_OUI[] = { 0x00, 0xA0, 0xAF };
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void smc91c9x_device::device_start()
{
// Allocate main buffer
m_buffer = std::make_unique<u8[]>(ETHER_BUFFER_SIZE * m_num_ebuf);
// TX timer
m_tx_poll = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(smc91c9x_device::tx_poll), this));
m_irq_handler.resolve_safe();
// These registers don't get cleared on reset
m_reg[B1_CONFIG] = 0x0030; m_regmask[B1_CONFIG] = 0x17c6;
m_reg[B1_BASE] = 0x1866; m_regmask[B1_BASE] = 0xfffe;
m_reg[B1_IA0_1] = 0x0000; m_regmask[B1_IA0_1] = 0xffff;
m_reg[B1_IA2_3] = 0x0000; m_regmask[B1_IA2_3] = 0xffff;
m_reg[B1_IA4_5] = 0x0000; m_regmask[B1_IA4_5] = 0xffff;
// Revision is set based on chip type
m_regmask[B3_REVISION] = 0x0000;
if (m_device_type == dev_type::SMC91C94)
m_reg[B3_REVISION] = 0x3345;
else if (m_device_type == dev_type::SMC91C96)
m_reg[B3_REVISION] = 0x3346;
else
fatalerror("device_start: Unknown device type\n");
/* register ide states */
save_item(NAME(m_reg));
save_item(NAME(m_regmask));
save_item(NAME(m_irq_state));
save_pointer(NAME(m_buffer), ETHER_BUFFER_SIZE * m_num_ebuf);
save_item(NAME(m_sent));
save_item(NAME(m_recd));
save_item(NAME(m_alloc_rx));
save_item(NAME(m_alloc_tx));
save_item(NAME(m_tx_active));
save_item(NAME(m_rx_active));
save_item(NAME(m_tx_retry_count));
save_item(NAME(m_rx_hash));
save_item(NAME(m_loopback_result));
// Circular FIFOs
save_item(NAME(m_queued_tx));
save_item(NAME(m_queued_tx_h));
save_item(NAME(m_queued_tx_t));
save_item(NAME(m_completed_tx));
save_item(NAME(m_completed_tx_h));
save_item(NAME(m_completed_tx_t));
save_item(NAME(m_completed_rx));
save_item(NAME(m_completed_rx_h));
save_item(NAME(m_completed_rx_t));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void smc91c9x_device::device_reset()
{
m_irq_state = 0;
m_sent = 0;
m_recd = 0;
m_tx_active = 0;
m_rx_active = 0;
m_tx_retry_count = 0;
m_reg[B0_TCR] = 0x0000; m_regmask[B0_TCR] = 0x3d87;
m_reg[B0_EPH_STATUS] = 0x0000; m_regmask[B0_EPH_STATUS] = 0x0000;
m_reg[B0_RCR] = 0x0000; m_regmask[B0_RCR] = 0xc307;
m_reg[B0_COUNTER] = 0x0000; m_regmask[B0_COUNTER] = 0x0000;
m_reg[B0_MIR] = 0x1212; m_regmask[B0_MIR] = 0x0000;
m_reg[B0_MCR] = 0x3300; m_regmask[B0_MCR] = 0x00ff;
m_reg[B0_BANK] = 0x3300; m_regmask[B0_BANK] = 0x0007;
m_reg[B1_GENERAL_PURP] = 0x0000; m_regmask[B1_GENERAL_PURP] = 0xffff;
m_reg[B1_CONTROL] = 0x0100; m_regmask[B1_CONTROL] = 0x68e7;
m_reg[B2_MMU_COMMAND] = 0x0000; m_regmask[B2_MMU_COMMAND] = 0x00e7;
m_reg[B2_PNR_ARR] = 0x8000; m_regmask[B2_PNR_ARR] = 0x00ff;
m_reg[B2_FIFO_PORTS] = 0x8080; m_regmask[B2_FIFO_PORTS] = 0x0000;
m_reg[B2_POINTER] = 0x0000; m_regmask[B2_POINTER] = 0xf7ff;
m_reg[B2_DATA_0] = 0x0000; m_regmask[B2_DATA_0] = 0xffff;
m_reg[B2_DATA_1] = 0x0000; m_regmask[B2_DATA_1] = 0xffff;
m_reg[B2_INTERRUPT] = 0x0004; m_regmask[B2_INTERRUPT] = 0x7f00;
m_reg[B3_MT0_1] = 0x0000; m_regmask[B3_MT0_1] = 0xffff;
m_reg[B3_MT2_3] = 0x0000; m_regmask[B3_MT2_3] = 0xffff;
m_reg[B3_MT4_5] = 0x0000; m_regmask[B3_MT4_5] = 0xffff;
m_reg[B3_MT6_7] = 0x0000; m_regmask[B3_MT6_7] = 0xffff;
m_reg[B3_MGMT] = 0x3030; m_regmask[B3_MGMT] = 0x0f0f;
m_reg[B3_ERCV] = 0x331f; m_regmask[B3_ERCV] = 0x009f;
update_ethernet_irq();
// Reset MMU
mmu_reset();
}
void smc91c9x_device::mmu_reset()
{
// Reset MMU allocations
m_alloc_rx = 0;
m_alloc_tx = 0;
// Reset fifos.
reset_tx_fifos();
reset_completed_rx();
update_ethernet_irq();
}
void smc91c9x_device::reset_tx_fifos()
{
// Disable transmit timer
m_tx_poll->enable(false);
// Reset transmit queue
reset_queued_tx();
// Reset completion FIFOs
reset_completed_tx();
}
bool smc91c9x_device::alloc_req(const int tx, int &packet_num)
{
u32 curr_alloc = m_alloc_rx | m_alloc_tx;
for (int index = 0; index < m_num_ebuf; index++)
{
if (!(curr_alloc & (1 << index)))
{
packet_num = index;
if (tx)
m_alloc_tx |= 1 << index;
else
m_alloc_rx |= 1 << index;
return true;
}
}
return false;
}
void smc91c9x_device::alloc_release(const int packet_num)
{
int clear_mask = ~(1 << packet_num);
if (!((m_alloc_tx | m_alloc_rx) & (1 << packet_num)))
{
logerror("alloc_release: Trying to release a non-allocated packet. packet_num: %02x alloc_tx: %04x alloc_rx: %04x\n",
packet_num, m_alloc_tx, m_alloc_rx);
}
m_alloc_tx &= clear_mask;
m_alloc_rx &= clear_mask;
}
/***************************************************************************
INTERNAL HELPERS
***************************************************************************/
/*-------------------------------------------------
update_ethernet_irq - update the IRQ state
-------------------------------------------------*/
void smc91c9x_device::update_ethernet_irq()
{
// Check tx completion fifo empty
if (empty_completed_tx())
m_reg[B2_INTERRUPT] &= ~EINT_TX;
else
m_reg[B2_INTERRUPT] |= EINT_TX;
// Check rx completion fifo empty
if (empty_completed_rx())
m_reg[B2_INTERRUPT] &= ~EINT_RCV;
else
m_reg[B2_INTERRUPT] |= EINT_RCV;
uint8_t const mask = m_reg[B2_INTERRUPT] >> 8;
uint8_t const state = m_reg[B2_INTERRUPT] & 0xff;
/* update the IRQ state */
uint8_t new_state = mask & state;
if (m_irq_state ^ new_state)
{
LOG("update_ethernet_irq: old: %02x new: %02x\n", m_irq_state, new_state);
m_irq_state = new_state;
m_irq_handler(m_irq_state ? ASSERT_LINE : CLEAR_LINE);
}
}
/*-------------------------------------------------
update_stats - draw statistics
-------------------------------------------------*/
void smc91c9x_device::update_stats()
{
if ( DISPLAY_STATS )
popmessage("Sent:%d Rec'd:%d", m_sent, m_recd);
}
/*-------------------------------------------------
dump_bytes - Print packet bytes
-------------------------------------------------*/
void smc91c9x_device::dump_bytes(u8 *buf, int length)
{
if (VERBOSE & LOG_PACKETS)
{
std::stringstream ss_bytes;
ss_bytes << std::hex << std::setfill('0');
for (int i = 0; i < length; i++)
{
ss_bytes << std::setw(2) << (int) buf[i];
// Send newline every 16 bytes and at the end
if ((i & 0xf) == 0xf || i == length - 1)
{
LOGMASKED(LOG_PACKETS, "%s\n", ss_bytes.str());
ss_bytes.str("");
}
else
ss_bytes << " ";
}
}
}
/*-------------------------------------------------
address_filter - Filter the received packet
-------------------------------------------------*/
int smc91c9x_device::address_filter(u8 *buf)
{
if (m_reg[B0_RCR] & PRMS)
{
// TODO: 91C94 doesn't receive it's own transmisson when not in full duplex
LOGMASKED(LOG_FILTER, "address_filter accepted (promiscuous mode)\n");
return ADDR_UNICAST;
}
else if (buf[0] & 1)
{
// broadcast
if (!memcmp(ETH_BROADCAST, buf, 6))
{
LOGMASKED(LOG_FILTER, "address_filter accepted (broadcast) %02x-%02x-%02x-%02x-%02x-%02x\n",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
return ADDR_BROADCAST;
}
// multicast
/*
* Multicast address matching is performed by computing the fcs crc of
* the destination address, and then using the upper 6 bits as an index
* into the 64-bit logical address filter.
*/
// Check for all multicast bit
if (m_reg[B0_RCR] & ALMUL)
return ADDR_MULTICAST;
u32 const crc = util::crc32_creator::simple(buf, 6);
// The hash is based on the top 6 MSBs of the CRC
// The CRC needs to be inverted and reflected
m_rx_hash = 0x0;
for (int i = 0; i < 6; i++)
m_rx_hash |= (((~crc) >> i) & 1) << (5 - i);
u64 multicast_addr = *(u64*)&m_reg[B3_MT0_1];
if (BIT(multicast_addr, m_rx_hash))
{
LOGMASKED(LOG_FILTER, "address_filter accepted (multicast address match) %02x-%02x-%02x-%02x-%02x-%02x\n",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
return ADDR_MULTICAST;
}
LOGMASKED(LOG_FILTER, "address_filter rejected multicast %02x-%02x-%02x-%02x-%02x-%02x crc: %08x hash: %02x multi: %16ullx\n",
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], crc, m_rx_hash, *(u64*)&m_reg[B3_MT0_1]);
}
else
{
// unicast
if (!memcmp(&m_reg[B1_IA0_1], buf, 6))
{
LOGMASKED(LOG_FILTER, "address_filter accepted (physical address match)\n");
return ADDR_UNICAST;
}
}
return ADDR_NOMATCH;
}
/*-------------------------------------------------
recv_start_cb - Start receiving packet
A return value of 0 will stop rx processing in dinetwork device
Any other value will be sent to the recv_complete_cb
-------------------------------------------------*/
int smc91c9x_device::recv_start_cb(u8 *buf, int length)
{
// check internal loopback
if (m_reg[B0_TCR] & (EPH_LOOP | LOOP))
{
LOGMASKED(LOG_RX, "receive internal loopback mode, external packet discarded\n");
return 0;
}
// discard bad length packets
if (length < 64 || length > 256*6 - 6)
{
LOGMASKED(LOG_RX, "received bad length packet length %d discarded\n", length);
return 0;
}
// discard packets not from WMS
if (memcmp(WMS_OUI, &buf[6], 3))
{
LOGMASKED(LOG_RX, "received non-WMS packet OUI: %02x:%02x:%02x length %d discarded\n", buf[6], buf[7], buf[8], length);
return 0;
}
// Check for active transmission
if (m_tx_active)
{
// TODO: Update collision counters
LOGMASKED(LOG_RX, "transmit active COLLISION, rx packet length %d discarded\n", length);
return 0;
}
return receive(buf, length);
}
/*-------------------------------------------------
receive - Receive data into buffer
Returns the buffer packet number + 1 if successful
-------------------------------------------------*/
int smc91c9x_device::receive(u8 *buf, int length)
{
// check receiver enabled
if (!(m_reg[B0_RCR] & RXEN))
{
LOGMASKED(LOG_RX, "receive disabled, external packet discarded\n");
return -1;
}
// address filter
int filter = address_filter(buf);
if (filter == ADDR_NOMATCH)
return -1;
LOGMASKED(LOG_RX, "receive packet length %d\n", length);
dump_bytes(buf, length);
// Try to request a packet number
int packet_num;
if (!alloc_req(0, packet_num))
{
logerror("recv_cb: Couldn't allocate memory for receive packet\n");
return -2;
}
m_rx_active = 1;
// build up the packet
uint8_t *const packet = &m_buffer[packet_num * ETHER_BUFFER_SIZE];
// Strip CRC
if (m_reg[B0_RCR] & STRIP_CRC)
length -= 4;
// Copy received payload
memcpy(&packet[4], buf, length);
// Status word
u16 *rx_status = (u16*)&packet[0];
*rx_status = 0x0000;
// set the broadcast flag
if (filter == ADDR_BROADCAST)
*rx_status |= BRODCAST;
// set the multicast flag and hash
if (filter == ADDR_MULTICAST)
{
*rx_status |= (m_rx_hash << 1) | MULTCAST;
}
// Calculate buffer length and set control byte
u16 buf_length;
if (length & 1)
{
// ODD Frame
*rx_status |= ODDFRM;
packet[length + 4] = EBUF_RX_ALWAYS | EBUF_ODD; // Control
buf_length = length + 5;
}
else
{
packet[length + 4] = 0x00; // Pad
packet[length + 5] = EBUF_RX_ALWAYS; // Control
buf_length = length + 6;
}
// Set buffer length word
*(u16*)&packet[2] = buf_length;
return packet_num + 1;
}
/*-------------------------------------------------
recv_complete_cb - End of receive
-------------------------------------------------*/
void smc91c9x_device::recv_complete_cb(int result)
{
if (result > 0)
{
// Push packet number to rx completion fifo
push_completed_rx(result - 1);
}
// Couldn't allocate memory
else if (result == -2)
{
m_reg[B2_INTERRUPT] |= EINT_ALLOC;
}
update_ethernet_irq();
m_rx_active = 0;
}
/*-------------------------------------------------
tx_poll - Starts transmit
-------------------------------------------------*/
TIMER_CALLBACK_MEMBER(smc91c9x_device::tx_poll)
{
// Check for active RX and delay if necessary
if (m_rx_active)
{
// TODO: Implement correct CSMA/CD algorithm
m_tx_poll->adjust(attotime::from_usec(40));
m_tx_retry_count++;
LOGMASKED(LOG_TX, "tx_poll: Delaying TX due to active RX retry_count = %d\n", m_tx_retry_count);
}
// Check if TX is enabled and packet is queued
else if ((m_reg[B0_TCR] & TXENA) && !empty_queued_tx())
{
// Reset retry count
m_tx_retry_count = 0;
// Get the packet number from the transmit fifo
const int packet_num = curr_queued_tx();
uint8_t *const tx_buffer = &m_buffer[packet_num * ETHER_BUFFER_SIZE];
// Get the length and control fields from buffer
u16 length = (*(u16*)&tx_buffer[2]) & 0x7ff;
const u8 control = tx_buffer[length - 1];
// Remove [pad], control
if (control & EBUF_ODD)
length -= 1;
else
length -= 2;
// Add padding up to CRC area
// take into account status & length removal (-4) and crc addtion (+4)
while (length < 64 + 4 - 4 && (m_reg[B0_TCR] & PAD_EN))
tx_buffer[length++] = 0x00;
// Add CRC
// TODO: Calculate CRC
if (1 && ((control & EBUF_CRC) || !(m_reg[B0_TCR] & NOCRC)))
{
tx_buffer[length++] = 0x11;
tx_buffer[length++] = 0x22;
tx_buffer[length++] = 0x33;
tx_buffer[length++] = 0x44;
}
// Remove status, length
length -= 4;
// Reset the EPH register */
m_reg[B0_EPH_STATUS] &= LINK_OK;
// Send the frame
m_tx_active = 1;
m_tx_poll->enable(false);
LOGMASKED(LOG_TX, "Start sending packet %d length = %d time: %s\n", packet_num, length, machine().scheduler().time().as_string());
dump_bytes(&tx_buffer[4], length);
// Write loopback data and save result
if (m_reg[B0_TCR] & (EPH_LOOP | LOOP | FDUPLX))
m_loopback_result = receive(&tx_buffer[4], length);
else
m_loopback_result = 0;
// Local loopback isn't sent to cable
//if ((m_reg[B0_TCR] & (EPH_LOOP | LOOP) || (get_interface() < 0 && (m_reg[B0_TCR] & FDUPLX))))
if (m_reg[B0_TCR] & (EPH_LOOP | LOOP))
send_complete_cb(length);
else
send(&tx_buffer[4], length);
}
}
/*-------------------------------------------------
send_complete_cb - Called after transmit complete
-------------------------------------------------*/
void smc91c9x_device::send_complete_cb(int result)
{
m_sent++;
update_stats();
// Pop the packet number from the transmit fifo
const int packet_num = pop_queued_tx();
uint8_t *const tx_buffer = &m_buffer[packet_num * ETHER_BUFFER_SIZE];
LOGMASKED(LOG_TX, "End sending packet %d result = %d time: %s\n", packet_num, result, machine().scheduler().time().as_string());
/* update the EPH register */
m_reg[B0_EPH_STATUS] |= TX_SUC;
// Set LINK_OK in status
if (0 && !(m_reg[B0_EPH_STATUS] & LINK_OK))
{
m_reg[B0_EPH_STATUS] |= LINK_OK;
// Set a ethernet phy status interrupt
m_reg[B2_INTERRUPT] |= EINT_EPH;
}
// Set Tx broadcast flag
if (!memcmp(ETH_BROADCAST, &tx_buffer[4], 6))
m_reg[B0_EPH_STATUS] |= LTX_BRD;
// Check tx queued fifo empty
if (empty_queued_tx())
m_reg[B2_INTERRUPT] |= EINT_TX_EMPTY;
// Set no-transmission flags
if (m_link_unconnected)
{
//m_reg[B0_EPH_STATUS] &= ~LINK_OK;
//m_reg[B0_EPH_STATUS] &= ~TX_SUC;
// Set lost carrier
if (m_reg[B0_TCR] & MON_CSN)
{
m_reg[B0_EPH_STATUS] |= LOST_CARR;
// Clear Tx Enable on error
m_reg[B0_TCR] &= ~TXENA;
}
// Set signal quality error
if (m_reg[B0_TCR] & STP_SQET)
{
m_reg[B0_EPH_STATUS] |= SQET;
// Clear Tx Enable on error
m_reg[B0_TCR] &= ~TXENA;
}
// Set a ethernet phy status interrupt
m_reg[B2_INTERRUPT] |= EINT_EPH;
}
// Update status in the transmit word
*(u16*)&tx_buffer[0] = m_reg[B0_EPH_STATUS];
// Push the packet number onto the tx completion fifo
push_completed_tx(packet_num);
update_ethernet_irq();
// Loopback if loopback is set or fduplx is set
if (m_loopback_result)
{
//int rx_result = receive(&tx_buffer[4], result);
recv_complete_cb(m_loopback_result);
}
// If there is more packets to transmit then start the tx polling
if ((m_reg[B0_TCR] & TXENA) && !empty_queued_tx())
{
m_tx_poll->adjust(attotime::from_usec(10));
}
m_tx_active = 0;
}
/*-------------------------------------------------
process_command - handle MMU commands
-------------------------------------------------*/
void smc91c9x_device::process_command(uint16_t data)
{
switch ((data >> 4) & 0xF)
{
case ECMD_NOP:
LOG(" NOP\n");
break;
case ECMD_ALLOCATE:
LOG(" ALLOCATE MEMORY FOR TX (%d)", (data & 7));
{
int packet_num;
if (alloc_req(1, packet_num))
{
LOG(" packet_num = %02x\n", (packet_num));
// Set ARR register
m_reg[B2_PNR_ARR] &= ~0xff00;
m_reg[B2_PNR_ARR] |= packet_num << 8;
m_reg[B2_INTERRUPT] |= EINT_ALLOC;
update_ethernet_irq();
}
else
{
logerror("ECMD_ALLOCATE: Couldn't allocate TX memory\n");
}
}
break;
case ECMD_RESET_MMU:
LOG(" RESET MMU\n");
mmu_reset();
break;
case ECMD_REMOVE_TOPFRAME_TX:
LOG(" REMOVE FRAME FROM TX FIFO\n");
if (empty_completed_tx())
logerror("process_command: Trying to remove entry from empty tx completion fifo\n");
else
pop_completed_tx();
break;
case ECMD_REMOVE_RELEASE_TOPFRAME_RX:
LOG(" REMOVE AND RELEASE FRAME FROM RX FIFO (PACK_NUM=%d)\n", curr_completed_rx());
// Release memory allocation
alloc_release(curr_completed_rx());
// Fall through
case ECMD_REMOVE_TOPFRAME_RX:
LOG(" REMOVE FRAME FROM RX FIFO\n");
// remove entry from rx completion queue
if (empty_completed_rx())
logerror("process_command: Trying to remove entry from empty rx completion fifo\n");
else
pop_completed_rx();
update_ethernet_irq();
m_recd++;
update_stats();
break;
case ECMD_RELEASE_PACKET:
{
const int packet_number = m_reg[B2_PNR_ARR] & 0xff;
alloc_release(packet_number);
LOG(" RELEASE SPECIFIC PACKET %d\n", packet_number);
}
break;
case ECMD_ENQUEUE_PACKET:
LOG(" ENQUEUE TX PACKET ");
if (m_reg[B0_TCR] & TXENA)
{
const int packet_number = m_reg[B2_PNR_ARR] & 0xff;
LOG("(PACKET_NUM=%d)\n", packet_number);
// Push packet number to tx transmit fifo
push_queued_tx(packet_number);
// Start timer to send frame if not already transmitting
if (!m_tx_active && !m_tx_poll->enabled())
{
m_tx_poll->adjust(attotime::from_usec(10));
LOG("Start polling time: %s\n", machine().scheduler().time().as_string());
}
}
break;
case ECMD_RESET_FIFOS:
LOG(" RESET TX FIFOS\n");
// Flush fifos.
reset_tx_fifos();
break;
}
// Set Busy (clear on next read)
m_reg[B2_MMU_COMMAND] |= 0x0001;
}
/***************************************************************************
CORE READ/WRITE HANDLERS
***************************************************************************/
/*-------------------------------------------------
smc91c9x_r - handle a read from the device
-------------------------------------------------*/
READ16_MEMBER( smc91c9x_device::read )
{
uint32_t result;
/* determine the effective register */
offset %= 8;
if ( offset != B0_BANK )
offset += 8 * (m_reg[B0_BANK] & 7);
result = m_reg[offset];
switch (offset)
{
case B2_MMU_COMMAND:
// Clear busy
m_reg[B2_MMU_COMMAND] &= ~0x0001;
break;
case B2_PNR_ARR:
if ( ACCESSING_BITS_8_15 )
{
m_reg[B2_INTERRUPT] &= ~EINT_ALLOC;
update_ethernet_irq();
}
break;
case B2_FIFO_PORTS:
result = 0;
if (!empty_completed_tx())
result |= curr_completed_tx();
else
result |= 0x80;
if (!empty_completed_rx())
result |= curr_completed_rx() << 8;
else
result |= 0x80 << 8;
break;
case B2_DATA_0: /* data register */
case B2_DATA_1: /* data register */
{
uint8_t *buffer;
int addr = m_reg[B2_POINTER] & 0x7ff;
if ( m_reg[B2_POINTER] & 0x8000 )
buffer = &m_buffer[curr_completed_rx() * ETHER_BUFFER_SIZE];
else
buffer = &m_buffer[(m_reg[B2_PNR_ARR] & 0x1f) * ETHER_BUFFER_SIZE];;
result = buffer[addr++];
if ( ACCESSING_BITS_8_15 )
result |= buffer[addr++] << 8;
if ( m_reg[B2_POINTER] & 0x4000 )
m_reg[B2_POINTER] = (m_reg[B2_POINTER] & ~0x7ff) | (addr & 0x7ff);
break;
}
}
if (offset != B0_BANK)
LOG("%s:smc91c9x_r(%s) = %04X & %04X\n", machine().describe_context(), ethernet_regname[offset], result, mem_mask);
return result;
}
/*-------------------------------------------------
smc91c9x_w - handle a write to the device
-------------------------------------------------*/
WRITE16_MEMBER( smc91c9x_device::write )
{
/* determine the effective register */
offset %= 8;
if (offset != B0_BANK)
offset += 8 * (m_reg[B0_BANK] & 7);
/* update the data generically */
if (offset != B0_BANK && offset < sizeof(m_reg))
LOG("%s:smc91c9x_w(%s) = [%04X]<-%04X & (%04X & %04X)\n", machine().describe_context(), ethernet_regname[offset], offset, data, mem_mask , m_regmask[offset]);
mem_mask &= m_regmask[offset];
COMBINE_DATA(&m_reg[offset]);
/* handle it */
switch (offset)
{
case B0_TCR: /* transmit control register */
// Setting Tx Enable clears some status and interrupts
if ( data & TXENA )
{
if (m_reg[B0_EPH_STATUS] & (LOST_CARR | SQET | LATCOL | E16COL))
{
m_reg[B0_EPH_STATUS] &= ~(LOST_CARR | SQET | LATCOL | E16COL);
m_reg[B2_INTERRUPT] &= ~EINT_EPH;
update_ethernet_irq();
}
}
if (VERBOSE & LOG_GENERAL)
{
if (data & FDSE) LOG(" FDSE\n");
if (data & EPH_LOOP) LOG(" EPH LOOP\n");
if (data & STP_SQET) LOG(" STP SQET\n");
if (data & FDUPLX) LOG(" FDUPLX\n");
if (data & MON_CSN) LOG(" MON_CSN\n");
if (data & NOCRC) LOG(" NOCRC\n");
if (data & PAD_EN) LOG(" PAD_EN\n");
if (data & FORCOL) LOG(" FORCOL\n");
if (data & LOOP) LOG(" LOOP\n");
if (data & TXENA) LOG(" TXENA\n");
}
break;
case B0_RCR: /* receive control register */
if ( data & SOFT_RST)
{
reset();
}
if ( !(data & RXEN) )
{
reset_completed_rx();
}
if (data & RXEN)
{
// Set LINK_OK in status
m_reg[B0_EPH_STATUS] |= LINK_OK;
}
if (VERBOSE & LOG_GENERAL)
{
if (data & SOFT_RST) LOG(" SOFT RST\n");
if (data & FILT_CAR) LOG(" FILT_CAR\n");
if (data & STRIP_CRC) LOG(" STRIP CRC\n");
if (data & RXEN) LOG(" RXEN\n");
if (data & ALMUL) LOG(" ALMUL\n");
if (data & PRMS) LOG(" PRMS\n");
if (data & RX_ABORT) LOG(" RX_ABORT\n");
}
break;
case B1_CONFIG: /* configuration register */
if (data & 0x1000) LOG(" NO WAIT\n");
if (data & 0x0400) LOG(" FULL STEP\n");
if (data & 0x0200) LOG(" SET SQLCH\n");
if (data & 0x0100) LOG(" AUI SELECT\n");
if (data & 0x0080) LOG(" 16 BIT\n");
if (data & 0x0040) LOG(" DIS LINK\n");
if (data & 0x0004) LOG(" INT SEL1\n");
if (data & 0x0002) LOG(" INT SEL0\n");
break;
case B1_BASE: /* base address register */
LOG(" base = $%04X\n", (data & 0xe000) | ((data & 0x1f00) >> 3));
LOG(" romsize = %d\n", ((data & 0xc0) >> 6));
LOG(" romaddr = $%05X\n", ((data & 0x3e) << 13));
break;
case B1_IA4_5:
set_promisc(m_reg[B0_RCR] & PRMS);
set_mac((char *)&m_reg[B1_IA0_1]);
break;
case B1_CONTROL: /* control register */
// Clearing LE_EN clears interrupt from LINK_OK status change
if (!(data & LE_ENABLE))
{
m_reg[B2_INTERRUPT] &= ~EINT_EPH;
update_ethernet_irq();
}
if (0 && (data & LE_ENABLE))
{
if (m_reg[B0_EPH_STATUS] & LINK_OK)
{
m_reg[B0_EPH_STATUS] &= ~(LINK_OK);
m_reg[B2_INTERRUPT] &= ~EINT_EPH;
update_ethernet_irq();
}
}
if (VERBOSE & LOG_GENERAL)
{
if (data & RCV_BAD) LOG(" RCV_BAD\n");
if (data & PWRDN) LOG(" PWRDN\n");
if (data & WAKEUP_EN) LOG(" WAKEUP ENABLE\n");
if (data & AUTO_RELEASE) LOG(" AUTO RELEASE\n");
if (data & LE_ENABLE) LOG(" LE ENABLE\n");
if (data & CR_ENABLE) LOG(" CR ENABLE\n");
if (data & TE_ENABLE) LOG(" TE ENABLE\n");
if (data & EEPROM_SEL) LOG(" EEPROM SELECT\n");
if (data & RELOAD) LOG(" RELOAD\n");
if (data & STORE) LOG(" STORE\n");
}
break;
case B2_MMU_COMMAND: /* command register */
process_command(data);
break;
case B2_DATA_0: /* data register */
case B2_DATA_1: /* data register */
{
uint8_t *buffer;
int addr = m_reg[B2_POINTER] & PTR;
if (m_reg[B2_POINTER] & RCV)
buffer = &m_buffer[curr_completed_rx() * ETHER_BUFFER_SIZE];
else
buffer = &m_buffer[(m_reg[B2_PNR_ARR] & 0x1f) * ETHER_BUFFER_SIZE];;
buffer[addr++] = data;
if ( ACCESSING_BITS_8_15 )
buffer[addr++] = data >> 8;
if ( m_reg[B2_POINTER] & AUTO_INCR)
m_reg[B2_POINTER] = (m_reg[B2_POINTER] & ~PTR) | (addr & PTR);
break;
}
case B2_INTERRUPT:
// Pop tx fifo packet from completion fifo if clear tx int is set
if (m_reg[B2_INTERRUPT] & data & EINT_TX)
{
if (empty_completed_tx())
logerror("write: Trying to remove an entry from empty tx completion fifo\n");
else
{
LOG("Removing tx completion packet_num = %d\n", curr_completed_tx());
pop_completed_tx();
}
}
// Clear interrupts
m_reg[B2_INTERRUPT] &= ~(data & (EINT_ERCV | EINT_RX_OVRN | EINT_TX_EMPTY | EINT_TX));
update_ethernet_irq();
break;
}
}