mirror of
https://github.com/holub/mame
synced 2025-05-08 23:31:54 +03:00
906 lines
24 KiB
C++
906 lines
24 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles
|
|
/*************************************************************************
|
|
|
|
SMC91C9X ethernet controller implementation
|
|
|
|
by Aaron Giles, Jean-François DEL NERO
|
|
|
|
***************************************************************************
|
|
|
|
Notes:
|
|
* Connected mode working
|
|
|
|
**************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "smc91c9x.h"
|
|
// Needed for netdev_count???
|
|
#include "osdnet.h"
|
|
|
|
|
|
/***************************************************************************
|
|
DEBUGGING
|
|
***************************************************************************/
|
|
|
|
//#define VERBOSE 1
|
|
#include "logmacro.h"
|
|
|
|
#define DISPLAY_STATS (0)
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
/* Ethernet registers - bank 0 */
|
|
#define EREG_TCR (0*8 + 0)
|
|
#define EREG_EPH_STATUS (0*8 + 1)
|
|
#define EREG_RCR (0*8 + 2)
|
|
#define EREG_COUNTER (0*8 + 3)
|
|
#define EREG_MIR (0*8 + 4)
|
|
#define EREG_MCR (0*8 + 5)
|
|
#define EREG_BANK (0*8 + 7)
|
|
|
|
/* Ethernet registers - bank 1 */
|
|
#define EREG_CONFIG (1*8 + 0)
|
|
#define EREG_BASE (1*8 + 1)
|
|
#define EREG_IA0_1 (1*8 + 2)
|
|
#define EREG_IA2_3 (1*8 + 3)
|
|
#define EREG_IA4_5 (1*8 + 4)
|
|
#define EREG_GENERAL_PURP (1*8 + 5)
|
|
#define EREG_CONTROL (1*8 + 6)
|
|
|
|
/* Ethernet registers - bank 2 */
|
|
#define EREG_MMU_COMMAND (2*8 + 0)
|
|
#define EREG_PNR_ARR (2*8 + 1)
|
|
#define EREG_FIFO_PORTS (2*8 + 2)
|
|
#define EREG_POINTER (2*8 + 3)
|
|
#define EREG_DATA_0 (2*8 + 4)
|
|
#define EREG_DATA_1 (2*8 + 5)
|
|
#define EREG_INTERRUPT (2*8 + 6)
|
|
|
|
/* Ethernet registers - bank 3 */
|
|
#define EREG_MT0_1 (3*8 + 0)
|
|
#define EREG_MT2_3 (3*8 + 1)
|
|
#define EREG_MT4_5 (3*8 + 2)
|
|
#define EREG_MT6_7 (3*8 + 3)
|
|
#define EREG_MGMT (3*8 + 4)
|
|
#define EREG_REVISION (3*8 + 5)
|
|
#define EREG_ERCV (3*8 + 6)
|
|
|
|
/* Ethernet MMU commands */
|
|
#define ECMD_NOP 0
|
|
#define ECMD_ALLOCATE 2
|
|
#define ECMD_RESET_MMU 4
|
|
#define ECMD_REMOVE_TOPFRAME_RX 6
|
|
#define ECMD_REMOVE_TOPFRAME_TX 7
|
|
#define ECMD_REMOVE_RELEASE_TOPFRAME_RX 8
|
|
#define ECMD_RELEASE_PACKET 10
|
|
#define ECMD_ENQUEUE_PACKET 12
|
|
#define ECMD_RESET_FIFOS 14
|
|
|
|
/* Ethernet interrupt bits */
|
|
#define EINT_RCV 0x01
|
|
#define EINT_TX 0x02
|
|
#define EINT_TX_EMPTY 0x04
|
|
#define EINT_ALLOC 0x08
|
|
#define EINT_RX_OVRN 0x10
|
|
#define EINT_EPH 0x20
|
|
#define EINT_ERCV 0x40
|
|
|
|
/* 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
|
|
***************************************************************************/
|
|
|
|
smc91c9x_device::smc91c9x_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
|
|
: device_t(mconfig, type, tag, owner, clock)
|
|
, device_network_interface(mconfig, *this, 10.0f)
|
|
, m_irq_handler(*this)
|
|
, m_link_unconnected(false)
|
|
{
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_start - device-specific startup
|
|
//-------------------------------------------------
|
|
|
|
void smc91c9x_device::device_start()
|
|
{
|
|
// TX timer
|
|
m_tx_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(smc91c9x_device::send_frame), this));
|
|
|
|
m_irq_handler.resolve_safe();
|
|
|
|
// Set completion queues to max size to save properly
|
|
m_comp_rx.resize(ETHER_BUFFERS);
|
|
m_comp_tx.resize(ETHER_BUFFERS);
|
|
|
|
/* register ide states */
|
|
save_item(NAME(m_reg));
|
|
save_item(NAME(m_regmask));
|
|
save_item(NAME(m_irq_state));
|
|
save_item(NAME(m_buffer));
|
|
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_comp_rx));
|
|
save_item(NAME(m_comp_tx));
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_reset - device-specific reset
|
|
//-------------------------------------------------
|
|
|
|
void smc91c9x_device::device_reset()
|
|
{
|
|
std::fill(std::begin(m_reg), std::end(m_reg), 0);
|
|
|
|
std::fill(std::begin(m_regmask), std::end(m_regmask), 0);
|
|
|
|
m_irq_state = 0;
|
|
|
|
m_sent = 0;
|
|
m_recd = 0;
|
|
|
|
m_reg[EREG_TCR] = 0x0000; m_regmask[EREG_TCR] = 0x3d87;
|
|
m_reg[EREG_EPH_STATUS] = 0x0000; m_regmask[EREG_EPH_STATUS] = 0x0000;
|
|
m_reg[EREG_RCR] = 0x0000; m_regmask[EREG_RCR] = 0xc307;
|
|
m_reg[EREG_COUNTER] = 0x0000; m_regmask[EREG_COUNTER] = 0x0000;
|
|
m_reg[EREG_MIR] = 0x1212; m_regmask[EREG_MIR] = 0x0000;
|
|
m_reg[EREG_MCR] = 0x3300; m_regmask[EREG_MCR] = 0x00ff;
|
|
m_reg[EREG_BANK] = 0x3300; m_regmask[EREG_BANK] = 0x0007;
|
|
|
|
m_reg[EREG_CONFIG] = 0x0030; m_regmask[EREG_CONFIG] = 0x17c6;
|
|
m_reg[EREG_BASE] = 0x1866; m_regmask[EREG_BASE] = 0xfffe;
|
|
|
|
// Default MAC
|
|
m_reg[EREG_IA0_1] = 0x1300; m_regmask[EREG_IA0_1] = 0xffff;
|
|
m_reg[EREG_IA2_3] = 0x12F7; m_regmask[EREG_IA2_3] = 0xffff;
|
|
m_reg[EREG_IA4_5] = 0x5634; m_regmask[EREG_IA4_5] = 0xffff;
|
|
|
|
m_reg[EREG_GENERAL_PURP] = 0x0000; m_regmask[EREG_GENERAL_PURP] = 0xffff;
|
|
m_reg[EREG_CONTROL] = 0x0100; m_regmask[EREG_CONTROL] = 0x68e7;
|
|
|
|
m_reg[EREG_MMU_COMMAND] = 0x0000; m_regmask[EREG_MMU_COMMAND] = 0x00e7;
|
|
m_reg[EREG_PNR_ARR] = 0x8000; m_regmask[EREG_PNR_ARR] = 0x00ff;
|
|
m_reg[EREG_FIFO_PORTS] = 0x8080; m_regmask[EREG_FIFO_PORTS] = 0x0000;
|
|
m_reg[EREG_POINTER] = 0x0000; m_regmask[EREG_POINTER] = 0xf7ff;
|
|
m_reg[EREG_DATA_0] = 0x0000; m_regmask[EREG_DATA_0] = 0xffff;
|
|
m_reg[EREG_DATA_1] = 0x0000; m_regmask[EREG_DATA_1] = 0xffff;
|
|
m_reg[EREG_INTERRUPT] = 0x0004; m_regmask[EREG_INTERRUPT] = 0x7f00;
|
|
|
|
m_reg[EREG_MT0_1] = 0x0000; m_regmask[EREG_MT0_1] = 0xffff;
|
|
m_reg[EREG_MT2_3] = 0x0000; m_regmask[EREG_MT2_3] = 0xffff;
|
|
m_reg[EREG_MT4_5] = 0x0000; m_regmask[EREG_MT4_5] = 0xffff;
|
|
m_reg[EREG_MT6_7] = 0x0000; m_regmask[EREG_MT6_7] = 0xffff;
|
|
m_reg[EREG_MGMT] = 0x3030; m_regmask[EREG_MGMT] = 0x0f0f;
|
|
m_reg[EREG_REVISION] = 0x3345; m_regmask[EREG_REVISION] = 0x0000;
|
|
m_reg[EREG_ERCV] = 0x331f; m_regmask[EREG_ERCV] = 0x009f;
|
|
|
|
update_ethernet_irq();
|
|
m_tx_timer->reset();
|
|
|
|
// Setup real network if enabled
|
|
m_network_available = false;
|
|
if (netdev_count()) {
|
|
m_network_available = true;
|
|
osd_list_network_adapters();
|
|
unsigned char const *const mac = (const unsigned char *)get_mac();
|
|
if (VERBOSE & LOG_GENERAL)
|
|
{
|
|
logerror("MAC : ");
|
|
for (int i = 0; i < ETHERNET_ADDR_SIZE; i++)
|
|
logerror("%.2X", mac[i]);
|
|
|
|
logerror("\n");
|
|
}
|
|
|
|
set_promisc(true);
|
|
// Interface MAC
|
|
m_reg[EREG_IA0_1] = mac[0] | (mac[1] << 8);
|
|
m_reg[EREG_IA2_3] = mac[2] | (mac[3] << 8);
|
|
m_reg[EREG_IA4_5] = mac[4] | (mac[5] << 8);
|
|
}
|
|
|
|
// Reset MMU
|
|
mmu_reset();
|
|
}
|
|
|
|
void smc91c9x_device::mmu_reset()
|
|
{
|
|
// Reset MMU allocations
|
|
m_alloc_rx = 0;
|
|
m_alloc_tx = 0;
|
|
|
|
// Reset completion FIFOs
|
|
m_comp_tx.clear();
|
|
m_comp_rx.clear();
|
|
|
|
// Flush fifos.
|
|
clear_tx_fifo();
|
|
clear_rx_fifo();
|
|
}
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
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)
|
|
{
|
|
}
|
|
|
|
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 < ETHER_BUFFERS; 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);
|
|
m_alloc_tx &= clear_mask;
|
|
m_alloc_rx &= clear_mask;
|
|
}
|
|
|
|
void smc91c9x_device::clear_tx_fifo()
|
|
{
|
|
}
|
|
|
|
void smc91c9x_device::clear_rx_fifo()
|
|
{
|
|
}
|
|
|
|
int smc91c9x_device::is_broadcast(const uint8_t *mac_address)
|
|
{
|
|
int i;
|
|
|
|
i = 0;
|
|
|
|
while(mac_address[i] == 0xFF)
|
|
{
|
|
i++;
|
|
}
|
|
|
|
if ( i == 6 )
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int smc91c9x_device::ethernet_packet_is_for_me(const uint8_t *mac_address)
|
|
{
|
|
// tcpdump -i eth0 -q ether host 08:00:1e:01:ae:a5 or ether broadcast or ether dst 09:00:1e:00:00:00 or ether dst 09:00:1e:00:00:01
|
|
// wireshark filter: eth.addr eq 08:00:1e:01:ae:a5 or eth.dst eq ff:ff:ff:ff:ff:ff or eth.dst eq 09:00:1e:00:00:00 or eth.dst eq 09:00:1e:00:00:01
|
|
|
|
int i;
|
|
|
|
LOG("\n");
|
|
|
|
if (VERBOSE & LOG_GENERAL)
|
|
{
|
|
for ( i = 0 ; i < ETHERNET_ADDR_SIZE ; i++ )
|
|
{
|
|
logerror("%.2X", ((u8 *)&m_reg[EREG_IA0_1])[i]);
|
|
}
|
|
logerror("=");
|
|
for ( i = 0 ; i < ETHERNET_ADDR_SIZE ; i++ )
|
|
{
|
|
logerror("%.2X",mac_address[i]);
|
|
}
|
|
logerror("?");
|
|
}
|
|
|
|
// skip Ethernet broadcast packets if RECV_BROAD is not set
|
|
if (is_broadcast(mac_address))
|
|
{
|
|
LOG(" -- Broadcast rx\n");
|
|
return 2;
|
|
}
|
|
|
|
if (memcmp(mac_address, &m_reg[EREG_IA0_1], ETHERNET_ADDR_SIZE) == 0)
|
|
{
|
|
LOG(" -- Address Match\n");
|
|
return 1;
|
|
}
|
|
|
|
LOG(" -- Not Matching\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***************************************************************************
|
|
recv_cb - receive callback - receive and process an ethernet packet
|
|
***************************************************************************/
|
|
|
|
void smc91c9x_device::recv_cb(uint8_t *data, int length)
|
|
{
|
|
LOG("recv_cb : %d/0x%x\n",length,length);
|
|
|
|
int const isforme = ethernet_packet_is_for_me( data );
|
|
|
|
if (isforme==1 && (length >= ETHERNET_ADDR_SIZE) && (VERBOSE & LOG_GENERAL))
|
|
{
|
|
logerror("RX: ");
|
|
for (int i = 0; i < ETHERNET_ADDR_SIZE; i++)
|
|
logerror("%.2X", data[i]);
|
|
|
|
logerror(" ");
|
|
|
|
for (int i = 0; i < length-ETHERNET_ADDR_SIZE; i++)
|
|
logerror("%.2X", data[ETHERNET_ADDR_SIZE + i]);
|
|
|
|
logerror(" - IsForMe %d - %d/0x%x bytes\n", isforme, length, length);
|
|
}
|
|
|
|
if ( (length < ETHERNET_ADDR_SIZE || !isforme) && !(m_reg[EREG_RCR] & 0x0102) )
|
|
{
|
|
LOG("\n");
|
|
|
|
// skip packet
|
|
return;
|
|
}
|
|
|
|
/* signal a receive */
|
|
|
|
// Try to request a packet number
|
|
int packet_num;
|
|
if (!alloc_req(0, packet_num)) {
|
|
logerror("recv_cb: Couldn't allocate a receive packet\n");
|
|
return;
|
|
}
|
|
|
|
/* compute the packet length */
|
|
|
|
if ( ( length < ( ETHER_BUFFER_SIZE - ( 2+2+2 ) ) ) )
|
|
{
|
|
uint8_t *const packet = &m_buffer[ packet_num * ETHER_BUFFER_SIZE];
|
|
|
|
int dst = 0;
|
|
|
|
// build up the packet
|
|
|
|
// Status word
|
|
packet[dst++] = 0x00;
|
|
|
|
// set the broadcast flag
|
|
if ( isforme == 2 )
|
|
packet[dst++] |= 0x40;
|
|
else
|
|
packet[dst++] = 0x00;
|
|
|
|
//bytes count
|
|
packet[dst++] = 0x00;
|
|
packet[dst++] = 0x00;
|
|
|
|
memcpy(&packet[dst], data, length );
|
|
dst += length;
|
|
|
|
if ( dst & 1 )
|
|
{
|
|
// ODD Frame
|
|
packet[dst++] = 0x40 | 0x20; // Control
|
|
}
|
|
else
|
|
{
|
|
packet[dst++] = 0x00; // Pad
|
|
packet[dst++] = 0x40 | 0x00; // Control
|
|
}
|
|
|
|
//dst += 2;
|
|
|
|
dst &= 0x7FF;
|
|
|
|
packet[2] = (dst&0xFF);
|
|
packet[3] = (dst) >> 8;
|
|
|
|
// Push packet number to rx completion fifo
|
|
m_comp_rx.push_back(packet_num);
|
|
}
|
|
else
|
|
{
|
|
LOG("Rejected ! Fifo Full ?");
|
|
}
|
|
|
|
update_ethernet_irq();
|
|
|
|
LOG("\n");
|
|
}
|
|
|
|
/***************************************************************************
|
|
INTERNAL HELPERS
|
|
***************************************************************************/
|
|
|
|
/*-------------------------------------------------
|
|
update_ethernet_irq - update the IRQ state
|
|
-------------------------------------------------*/
|
|
|
|
void smc91c9x_device::update_ethernet_irq()
|
|
{
|
|
// Check tx completion fifo empty
|
|
if (m_comp_tx.empty()) {
|
|
m_reg[EREG_INTERRUPT] |= EINT_TX_EMPTY;
|
|
}
|
|
else {
|
|
m_reg[EREG_INTERRUPT] &= ~EINT_TX_EMPTY;
|
|
}
|
|
//if (m_comp_tx.empty()) {
|
|
// m_reg[EREG_INTERRUPT] &= ~EINT_TX;
|
|
//}
|
|
//else {
|
|
// m_reg[EREG_INTERRUPT] |= EINT_TX;
|
|
//}
|
|
// Check rx completion fifo empty
|
|
if (m_comp_rx.empty())
|
|
m_reg[EREG_INTERRUPT] &= ~EINT_RCV;
|
|
else
|
|
m_reg[EREG_INTERRUPT] |= EINT_RCV;
|
|
|
|
uint8_t const mask = m_reg[EREG_INTERRUPT] >> 8;
|
|
uint8_t const state = m_reg[EREG_INTERRUPT] & 0xff;
|
|
|
|
|
|
/* update the IRQ state */
|
|
uint8_t new_state = mask & state;
|
|
if (m_irq_state ^ new_state)
|
|
{
|
|
logerror("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);
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------
|
|
send_frame - push a frame to the interface
|
|
-------------------------------------------------*/
|
|
|
|
TIMER_CALLBACK_MEMBER(smc91c9x_device::send_frame)
|
|
{
|
|
const int packet_num = m_comp_tx.front();
|
|
uint8_t *const tx_buffer = &m_buffer[packet_num * ETHER_BUFFER_SIZE];
|
|
|
|
/* update the EPH register */
|
|
m_reg[EREG_EPH_STATUS] = 0x0001;
|
|
|
|
if (is_broadcast(&tx_buffer[4]))
|
|
m_reg[EREG_EPH_STATUS] |= 0x0040;
|
|
|
|
// Set Tx Empty interrupt
|
|
// TODO: If more than 1 packet is enqueued should wait for all to finish
|
|
//m_reg[EREG_INTERRUPT] |= EINT_TX_EMPTY;
|
|
m_reg[EREG_INTERRUPT] |= EINT_TX;
|
|
//m_comp_tx.erase(m_comp_tx.begin());
|
|
m_sent++;
|
|
|
|
update_stats();
|
|
|
|
int buffer_len = ((tx_buffer[3] << 8) | tx_buffer[2]) & 0x7ff;
|
|
// Remove status, length, [pad], control
|
|
if (tx_buffer[buffer_len - 1] & 0x20)
|
|
buffer_len -= 5;
|
|
else
|
|
buffer_len -= 6;
|
|
// Add padding
|
|
if (buffer_len < 64 && (m_reg[EREG_TCR] & 0x0080)) {
|
|
while (buffer_len < 64)
|
|
tx_buffer[4 + buffer_len++] = 0x00;
|
|
}
|
|
if (VERBOSE & LOG_GENERAL)
|
|
{
|
|
logerror("TX: ");
|
|
for (int i = 0; i < ETHERNET_ADDR_SIZE; i++)
|
|
logerror("%.2X", tx_buffer[4 + i]);
|
|
|
|
logerror(" ");
|
|
|
|
for (int i = ETHERNET_ADDR_SIZE; i < buffer_len; i++)
|
|
logerror("%.2X", tx_buffer[4 + i]);
|
|
|
|
logerror("--- %d/0x%x bytes\n", buffer_len, buffer_len);
|
|
}
|
|
|
|
if (buffer_len > 4)
|
|
{
|
|
if (m_link_unconnected)
|
|
{
|
|
// Set lost carrier
|
|
if (m_reg[EREG_TCR] & 0x0400)
|
|
{
|
|
m_reg[EREG_EPH_STATUS] |= 0x400;
|
|
// Clear Tx Enable on error
|
|
m_reg[EREG_TCR] &= ~0x1;
|
|
}
|
|
|
|
// Set signal quality error
|
|
if (m_reg[EREG_TCR] & 0x1000)
|
|
{
|
|
m_reg[EREG_EPH_STATUS] |= 0x20;
|
|
// Clear Tx Enable on error
|
|
m_reg[EREG_TCR] &= ~0x1;
|
|
}
|
|
|
|
// signal a no transmit
|
|
m_reg[EREG_INTERRUPT] &= ~EINT_TX;
|
|
// Set a ethernet phy status interrupt
|
|
m_reg[EREG_INTERRUPT] |= EINT_EPH;
|
|
|
|
// Flush fifos.
|
|
clear_tx_fifo();
|
|
clear_rx_fifo();
|
|
}
|
|
else
|
|
{
|
|
// Send the frame
|
|
if (!send(&tx_buffer[4], buffer_len))
|
|
{
|
|
// FIXME: failed to send the Ethernet packet
|
|
//logerror("failed to send Ethernet packet\n");
|
|
//LOG(this,("read_command_port(): !!! failed to send Ethernet packet"));
|
|
}
|
|
|
|
// Loopback if loopback is set or fduplx is set
|
|
// TODO: Figure out correct size
|
|
// TODO: Check for addtional filter options for FDUPLX mode
|
|
if ((m_reg[EREG_TCR] & 0x2002) || (m_network_available && (m_reg[EREG_TCR] & 0x0800)))
|
|
recv_cb(&tx_buffer[4], buffer_len);
|
|
}
|
|
}
|
|
// Update status in the transmit word
|
|
tx_buffer[0] = m_reg[EREG_EPH_STATUS];
|
|
tx_buffer[1] = m_reg[EREG_EPH_STATUS] >> 8;
|
|
|
|
update_ethernet_irq();
|
|
}
|
|
|
|
/*-------------------------------------------------
|
|
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)\n", (data & 7));
|
|
{
|
|
int packet_num;
|
|
if (alloc_req(1, packet_num)) {
|
|
// Set ARR register
|
|
m_reg[EREG_PNR_ARR] &= ~0xff00;
|
|
m_reg[EREG_PNR_ARR] |= packet_num << 8;
|
|
m_reg[EREG_INTERRUPT] |= EINT_ALLOC;
|
|
|
|
update_ethernet_irq();
|
|
}
|
|
else {
|
|
logerror("ECMD_ALLOCATE: Couldn't allocate TX memory\n");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ECMD_RESET_MMU:
|
|
/*
|
|
0100
|
|
- RESET MMU TO INITIAL STATE -
|
|
Frees all memory allocations, clears relevant
|
|
interrupts, resets packet FIFO pointers.
|
|
*/
|
|
|
|
LOG(" RESET MMU\n");
|
|
mmu_reset();
|
|
break;
|
|
|
|
case ECMD_REMOVE_TOPFRAME_TX:
|
|
LOG(" REMOVE FRAME FROM TX FIFO\n");
|
|
m_comp_tx.erase(m_comp_tx.begin());
|
|
// TODO: Should we clear TX_INT?
|
|
break;
|
|
|
|
case ECMD_REMOVE_RELEASE_TOPFRAME_RX:
|
|
LOG(" REMOVE AND RELEASE FRAME FROM RX FIFO (PACK_NUM=%d)\n", m_comp_rx.front());
|
|
// Release memory allocation
|
|
alloc_release(m_comp_rx.front());
|
|
// Fall through
|
|
case ECMD_REMOVE_TOPFRAME_RX:
|
|
LOG(" REMOVE FRAME FROM RX FIFO\n");
|
|
// remove entry from rx queue
|
|
m_comp_rx.erase(m_comp_rx.begin());
|
|
|
|
update_ethernet_irq();
|
|
m_recd++;
|
|
update_stats();
|
|
break;
|
|
|
|
case ECMD_RELEASE_PACKET:
|
|
{
|
|
const int packet_number = m_reg[EREG_PNR_ARR] & 0xff;
|
|
alloc_release(packet_number);
|
|
LOG(" RELEASE SPECIFIC PACKET %d\n", packet_number);
|
|
}
|
|
break;
|
|
|
|
case ECMD_ENQUEUE_PACKET:
|
|
LOG(" ENQUEUE TX PACKET\n");
|
|
|
|
if (m_reg[EREG_TCR] & 0x0001) // TX EN ?
|
|
{
|
|
const int packet_number = m_reg[EREG_PNR_ARR] & 0xff;
|
|
// Push packet number tx completion fifo
|
|
m_comp_tx.push_back(packet_number);
|
|
// Calculate transmit time
|
|
uint8_t *const tx_buffer = &m_buffer[packet_number * ETHER_BUFFER_SIZE];
|
|
int buffer_len = ((tx_buffer[3] << 8) | tx_buffer[2]) & 0x7ff;
|
|
buffer_len -= 6;
|
|
// ~16 Mbps
|
|
int usec = ((buffer_len * 8) >> 4) + 1;
|
|
m_tx_timer->adjust(attotime::from_usec(usec));
|
|
}
|
|
break;
|
|
|
|
case ECMD_RESET_FIFOS:
|
|
LOG(" RESET TX FIFOS\n");
|
|
// Flush fifos.
|
|
clear_tx_fifo();
|
|
clear_rx_fifo();
|
|
|
|
break;
|
|
}
|
|
// Set Busy (clear on next read)
|
|
m_reg[EREG_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 != EREG_BANK )
|
|
offset += 8 * (m_reg[EREG_BANK] & 7);
|
|
|
|
result = m_reg[offset];
|
|
|
|
switch (offset)
|
|
{
|
|
case EREG_MMU_COMMAND:
|
|
// Clear busy
|
|
m_reg[EREG_MMU_COMMAND] &= ~0x0001;
|
|
break;
|
|
|
|
case EREG_PNR_ARR:
|
|
if ( ACCESSING_BITS_8_15 )
|
|
{
|
|
m_reg[EREG_INTERRUPT] &= ~EINT_ALLOC;
|
|
update_ethernet_irq();
|
|
}
|
|
break;
|
|
|
|
case EREG_FIFO_PORTS:
|
|
result = 0;
|
|
if (!m_comp_tx.empty())
|
|
result |= m_comp_tx.front();
|
|
else
|
|
result |= 0x80;
|
|
if (!m_comp_rx.empty())
|
|
result |= m_comp_rx.front() << 8;
|
|
else
|
|
result |= 0x80 << 8;
|
|
break;
|
|
|
|
|
|
case EREG_DATA_0: /* data register */
|
|
case EREG_DATA_1: /* data register */
|
|
{
|
|
uint8_t *buffer;
|
|
int addr = m_reg[EREG_POINTER] & 0x7ff;
|
|
|
|
if ( m_reg[EREG_POINTER] & 0x8000 )
|
|
buffer = &m_buffer[m_comp_rx.front() * ETHER_BUFFER_SIZE];
|
|
else
|
|
buffer = &m_buffer[(m_reg[EREG_PNR_ARR] & 0x1f) * ETHER_BUFFER_SIZE];;
|
|
|
|
result = buffer[addr++];
|
|
if ( ACCESSING_BITS_8_15 )
|
|
result |= buffer[addr++] << 8;
|
|
if ( m_reg[EREG_POINTER] & 0x4000 )
|
|
m_reg[EREG_POINTER] = (m_reg[EREG_POINTER] & ~0x7ff) | (addr & 0x7ff);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (offset != EREG_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 != EREG_BANK)
|
|
offset += 8 * (m_reg[EREG_BANK] & 7);
|
|
|
|
/* update the data generically */
|
|
|
|
if (offset != EREG_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 EREG_TCR: /* transmit control register */
|
|
// Setting Tx Enable clears some status and interrupts
|
|
if ( data & 0x1 ) {
|
|
m_reg[EREG_EPH_STATUS] &= ~0x420;
|
|
m_reg[EREG_INTERRUPT] &= ~EINT_EPH;
|
|
update_ethernet_irq();
|
|
}
|
|
|
|
if (data & 0x2000) LOG(" EPH LOOP\n");
|
|
if (data & 0x1000) LOG(" STP SQET\n");
|
|
if (data & 0x0800) LOG(" FDUPLX\n");
|
|
if (data & 0x0400) LOG(" MON_CSN\n");
|
|
if (data & 0x0100) LOG(" NOCRC\n");
|
|
if (data & 0x0080) LOG(" PAD_EN\n");
|
|
if (data & 0x0004) LOG(" FORCOL\n");
|
|
if (data & 0x0002) LOG(" LOOP\n");
|
|
if (data & 0x0001) LOG(" TXENA\n");
|
|
break;
|
|
|
|
case EREG_RCR: /* receive control register */
|
|
|
|
if ( data & 0x8000 )
|
|
{
|
|
clear_rx_fifo();
|
|
clear_tx_fifo();
|
|
}
|
|
|
|
if ( !(data & 0x0100) )
|
|
{
|
|
clear_rx_fifo();
|
|
}
|
|
|
|
if (data & 0x8000) reset();
|
|
if (data & 0x8000) LOG(" SOFT RST\n");
|
|
if (data & 0x4000) LOG(" FILT_CAR\n");
|
|
if (data & 0x0200) LOG(" STRIP CRC\n");
|
|
if (data & 0x0100) LOG(" RXEN\n");
|
|
if (data & 0x0004) LOG(" ALMUL\n");
|
|
if (data & 0x0002) LOG(" PRMS\n");
|
|
if (data & 0x0001) LOG(" RX_ABORT\n");
|
|
break;
|
|
|
|
case EREG_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 EREG_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 EREG_CONTROL: /* control register */
|
|
if (data & 0x4000) LOG(" RCV_BAD\n");
|
|
if (data & 0x2000) LOG(" PWRDN\n");
|
|
if (data & 0x0800) LOG(" AUTO RELEASE\n");
|
|
if (data & 0x0080) LOG(" LE ENABLE\n");
|
|
if (data & 0x0040) LOG(" CR ENABLE\n");
|
|
if (data & 0x0020) LOG(" TE ENABLE\n");
|
|
if (data & 0x0004) LOG(" EEPROM SELECT\n");
|
|
if (data & 0x0002) LOG(" RELOAD\n");
|
|
if (data & 0x0001) LOG(" STORE\n");
|
|
break;
|
|
|
|
case EREG_MMU_COMMAND: /* command register */
|
|
process_command(data);
|
|
break;
|
|
|
|
case EREG_DATA_0: /* data register */
|
|
case EREG_DATA_1: /* data register */
|
|
{
|
|
uint8_t *buffer;
|
|
int addr = m_reg[EREG_POINTER] & 0x7ff;
|
|
|
|
if (m_reg[EREG_POINTER] & 0x8000)
|
|
buffer = &m_buffer[m_comp_rx.front() * ETHER_BUFFER_SIZE];
|
|
else
|
|
buffer = &m_buffer[(m_reg[EREG_PNR_ARR] & 0x1f) * ETHER_BUFFER_SIZE];;
|
|
|
|
// TODO: Should be checking if incr is set
|
|
buffer[addr++] = data;
|
|
if ( ACCESSING_BITS_8_15 )
|
|
buffer[addr++] = data >> 8;
|
|
if ( m_reg[EREG_POINTER] & 0x4000 )
|
|
m_reg[EREG_POINTER] = (m_reg[EREG_POINTER] & ~0x7ff) | (addr & 0x7ff);
|
|
break;
|
|
}
|
|
|
|
case EREG_INTERRUPT:
|
|
// Pop tx fifo packet from completion fifo if clear tx int is set
|
|
if (m_reg[EREG_INTERRUPT] & data & EINT_TX) {
|
|
m_comp_tx.erase(m_comp_tx.begin());
|
|
m_reg[EREG_INTERRUPT] &= ~EINT_TX;
|
|
}
|
|
m_reg[EREG_INTERRUPT] &= ~(data & 0x56);
|
|
update_ethernet_irq();
|
|
break;
|
|
}
|
|
}
|