mirror of
https://github.com/holub/mame
synced 2025-10-08 01:28:00 +03:00
SMC91C9X Ethernet controller now working in connected mode with WinPcap or TUN/TAP tunnel.
This commit is contained in:
parent
e36ba7d95e
commit
b89717f97a
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
SMC91C9X ethernet controller implementation
|
SMC91C9X ethernet controller implementation
|
||||||
|
|
||||||
by Aaron Giles
|
by Aaron Giles, Jean-François DEL NERO
|
||||||
|
|
||||||
***************************************************************************
|
***************************************************************************
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
* only loopback mode really works
|
* Connected mode working
|
||||||
|
|
||||||
**************************************************************************/
|
**************************************************************************/
|
||||||
|
|
||||||
@ -68,14 +68,15 @@
|
|||||||
#define EREG_ERCV (3*8 + 6)
|
#define EREG_ERCV (3*8 + 6)
|
||||||
|
|
||||||
/* Ethernet MMU commands */
|
/* Ethernet MMU commands */
|
||||||
#define ECMD_NOP 0
|
#define ECMD_NOP 0
|
||||||
#define ECMD_ALLOCATE 1
|
#define ECMD_ALLOCATE 2
|
||||||
#define ECMD_RESET_MMU 2
|
#define ECMD_RESET_MMU 4
|
||||||
#define ECMD_REMOVE 3
|
#define ECMD_REMOVE_TOPFRAME_RX 6
|
||||||
#define ECMD_REMOVE_RELEASE 4
|
#define ECMD_REMOVE_TOPFRAME_TX 7
|
||||||
#define ECMD_RELEASE_PACKET 5
|
#define ECMD_REMOVE_RELEASE_TOPFRAME_RX 8
|
||||||
#define ECMD_ENQUEUE_PACKET 6
|
#define ECMD_RELEASE_PACKET 10
|
||||||
#define ECMD_RESET_FIFOS 7
|
#define ECMD_ENQUEUE_PACKET 12
|
||||||
|
#define ECMD_RESET_FIFOS 14
|
||||||
|
|
||||||
/* Ethernet interrupt bits */
|
/* Ethernet interrupt bits */
|
||||||
#define EINT_RCV 0x01
|
#define EINT_RCV 0x01
|
||||||
@ -99,16 +100,15 @@ static const char *const ethernet_regname[64] =
|
|||||||
"(7.0)", "(7.1)", "(7.2)", "(7.3)", "(7.4)", "(7.5)", "(7.6)", "BANK"
|
"(7.0)", "(7.1)", "(7.2)", "(7.3)", "(7.4)", "(7.5)", "(7.6)", "BANK"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
DEVICE INTERFACE
|
DEVICE INTERFACE
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
smc91c9x_device::smc91c9x_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
|
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_t(mconfig, type, tag, owner, clock)
|
||||||
|
, device_network_interface(mconfig, *this, 10.0f)
|
||||||
, m_irq_handler(*this)
|
, m_irq_handler(*this)
|
||||||
, m_link_unconnected(true)
|
, m_link_unconnected(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,15 +119,12 @@ smc91c9x_device::smc91c9x_device(const machine_config &mconfig, device_type type
|
|||||||
void smc91c9x_device::device_start()
|
void smc91c9x_device::device_start()
|
||||||
{
|
{
|
||||||
m_irq_handler.resolve_safe();
|
m_irq_handler.resolve_safe();
|
||||||
// TX timer
|
|
||||||
m_tx_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(smc91c9x_device::finish_enqueue), this));
|
|
||||||
|
|
||||||
/* register ide states */
|
/* register ide states */
|
||||||
save_item(NAME(m_reg));
|
save_item(NAME(m_reg));
|
||||||
save_item(NAME(m_regmask));
|
save_item(NAME(m_regmask));
|
||||||
save_item(NAME(m_irq_state));
|
save_item(NAME(m_irq_state));
|
||||||
save_item(NAME(m_alloc_count));
|
save_item(NAME(m_alloc_count));
|
||||||
save_item(NAME(m_fifo_count));
|
|
||||||
save_item(NAME(m_rx));
|
save_item(NAME(m_rx));
|
||||||
save_item(NAME(m_tx));
|
save_item(NAME(m_tx));
|
||||||
save_item(NAME(m_sent));
|
save_item(NAME(m_sent));
|
||||||
@ -140,14 +137,42 @@ void smc91c9x_device::device_start()
|
|||||||
|
|
||||||
void smc91c9x_device::device_reset()
|
void smc91c9x_device::device_reset()
|
||||||
{
|
{
|
||||||
|
unsigned char i;
|
||||||
|
const unsigned char * mac;
|
||||||
|
|
||||||
memset(m_reg, 0, sizeof(m_reg));
|
memset(m_reg, 0, sizeof(m_reg));
|
||||||
|
|
||||||
|
memset(m_rx, 0, sizeof(m_rx));
|
||||||
|
memset(m_tx, 0, sizeof(m_tx));
|
||||||
|
|
||||||
memset(m_regmask, 0, sizeof(m_regmask));
|
memset(m_regmask, 0, sizeof(m_regmask));
|
||||||
m_irq_state = 0;
|
m_irq_state = 0;
|
||||||
m_alloc_count = 0;
|
m_alloc_count = 0;
|
||||||
m_fifo_count = 0;
|
rx_fifo_out = 0;
|
||||||
|
rx_fifo_in = 0;
|
||||||
|
|
||||||
|
tx_fifo_out = 0;
|
||||||
|
tx_fifo_in = 0;
|
||||||
|
|
||||||
m_sent = 0;
|
m_sent = 0;
|
||||||
m_recd = 0;
|
m_recd = 0;
|
||||||
|
|
||||||
|
osd_list_network_adapters();
|
||||||
|
|
||||||
|
mac = (const unsigned char *)get_mac();
|
||||||
|
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
{
|
||||||
|
logerror("MAC : ");
|
||||||
|
for(i=0;i<ETHERNET_ADDR_SIZE;i++)
|
||||||
|
{
|
||||||
|
logerror("%.2X",mac[i]);
|
||||||
|
}
|
||||||
|
logerror("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
set_promisc(true);
|
||||||
|
|
||||||
m_reg[EREG_TCR] = 0x0000; m_regmask[EREG_TCR] = 0x3d87;
|
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_EPH_STATUS] = 0x0000; m_regmask[EREG_EPH_STATUS] = 0x0000;
|
||||||
m_reg[EREG_RCR] = 0x0000; m_regmask[EREG_RCR] = 0xc307;
|
m_reg[EREG_RCR] = 0x0000; m_regmask[EREG_RCR] = 0xc307;
|
||||||
@ -158,9 +183,17 @@ void smc91c9x_device::device_reset()
|
|||||||
|
|
||||||
m_reg[EREG_CONFIG] = 0x0030; m_regmask[EREG_CONFIG] = 0x17c6;
|
m_reg[EREG_CONFIG] = 0x0030; m_regmask[EREG_CONFIG] = 0x17c6;
|
||||||
m_reg[EREG_BASE] = 0x1866; m_regmask[EREG_BASE] = 0xfffe;
|
m_reg[EREG_BASE] = 0x1866; m_regmask[EREG_BASE] = 0xfffe;
|
||||||
m_reg[EREG_IA0_1] = 0x0000; m_regmask[EREG_IA0_1] = 0xffff;
|
|
||||||
m_reg[EREG_IA2_3] = 0x0000; m_regmask[EREG_IA2_3] = 0xffff;
|
// Default MAC
|
||||||
m_reg[EREG_IA4_5] = 0x0000; m_regmask[EREG_IA4_5] = 0xffff;
|
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;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
m_reg[EREG_GENERAL_PURP] = 0x0000; m_regmask[EREG_GENERAL_PURP] = 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_CONTROL] = 0x0100; m_regmask[EREG_CONTROL] = 0x68e7;
|
||||||
|
|
||||||
@ -181,10 +214,8 @@ void smc91c9x_device::device_reset()
|
|||||||
m_reg[EREG_ERCV] = 0x331f; m_regmask[EREG_ERCV] = 0x009f;
|
m_reg[EREG_ERCV] = 0x331f; m_regmask[EREG_ERCV] = 0x009f;
|
||||||
|
|
||||||
update_ethernet_irq();
|
update_ethernet_irq();
|
||||||
m_tx_timer->adjust(attotime::never);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DEFINE_DEVICE_TYPE(SMC91C94, smc91c94_device, "smc91c94", "SMC91C94 Ethernet Controller")
|
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)
|
smc91c94_device::smc91c94_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
||||||
@ -192,7 +223,6 @@ smc91c94_device::smc91c94_device(const machine_config &mconfig, const char *tag,
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DEFINE_DEVICE_TYPE(SMC91C96, smc91c96_device, "smc91c96", "SMC91C96 Ethernet Controller")
|
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)
|
smc91c96_device::smc91c96_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
||||||
@ -200,6 +230,194 @@ smc91c96_device::smc91c96_device(const machine_config &mconfig, const char *tag,
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void smc91c9x_device::clear_tx_fifo()
|
||||||
|
{
|
||||||
|
tx_fifo_in = 0;
|
||||||
|
tx_fifo_out = 0;
|
||||||
|
memset(m_tx, 0, sizeof(m_tx));
|
||||||
|
}
|
||||||
|
|
||||||
|
void smc91c9x_device::clear_rx_fifo()
|
||||||
|
{
|
||||||
|
rx_fifo_in = 0;
|
||||||
|
rx_fifo_out = 0;
|
||||||
|
memset(m_rx, 0, sizeof(m_rx));
|
||||||
|
}
|
||||||
|
|
||||||
|
int smc91c9x_device::is_broadcast(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;
|
||||||
|
uint8_t local_address[ETHERNET_ADDR_SIZE];
|
||||||
|
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror("\r\n");
|
||||||
|
|
||||||
|
local_address[0] = (m_reg[EREG_IA0_1]>>0) & 0xFF;
|
||||||
|
local_address[1] = (m_reg[EREG_IA0_1]>>8) & 0xFF;
|
||||||
|
local_address[2] = (m_reg[EREG_IA2_3]>>0) & 0xFF;
|
||||||
|
local_address[3] = (m_reg[EREG_IA2_3]>>8) & 0xFF;
|
||||||
|
local_address[4] = (m_reg[EREG_IA4_5]>>0) & 0xFF;
|
||||||
|
local_address[5] = (m_reg[EREG_IA4_5]>>8) & 0xFF;
|
||||||
|
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
{
|
||||||
|
for(i=0;i<ETHERNET_ADDR_SIZE;i++)
|
||||||
|
{
|
||||||
|
logerror("%.2X",local_address[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((uint8_t *)mac_address) )
|
||||||
|
{
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror(" -- Broadcast rx\r\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(mac_address, local_address, ETHERNET_ADDR_SIZE) == 0)
|
||||||
|
{
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror(" -- Address Match\r\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror(" -- Not Matching\r\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
recv_cb - receive callback - receive and process an ethernet packet
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
void smc91c9x_device::recv_cb(uint8_t *data, int length)
|
||||||
|
{
|
||||||
|
uint8_t *packet;
|
||||||
|
int isforme,i,dst;
|
||||||
|
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror("recv_cb : %d/0x%x\r\n",length,length);
|
||||||
|
|
||||||
|
isforme = ethernet_packet_is_for_me( data );
|
||||||
|
|
||||||
|
if (isforme==1 && length >= ETHERNET_ADDR_SIZE && LOG_ETHERNET)
|
||||||
|
{
|
||||||
|
logerror("RX: ");
|
||||||
|
for(i=0;i<ETHERNET_ADDR_SIZE;i++)
|
||||||
|
{
|
||||||
|
logerror("%.2X",data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
logerror(" ");
|
||||||
|
|
||||||
|
for(i=0;i<length-ETHERNET_ADDR_SIZE;i++)
|
||||||
|
{
|
||||||
|
logerror("%.2X",data[ETHERNET_ADDR_SIZE+i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
logerror(" - IsForMe %d - %d/0x%x bytes\r\n",isforme,length,length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (length < ETHERNET_ADDR_SIZE || !isforme) && !(m_reg[EREG_RCR] & 0x0100) )
|
||||||
|
{
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror("\r\n");
|
||||||
|
// skip packet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* signal a receive */
|
||||||
|
|
||||||
|
/* compute the packet length */
|
||||||
|
|
||||||
|
if( ( length < ( ETHER_BUFFER_SIZE - ( 2+2+2 ) ) ) )
|
||||||
|
{
|
||||||
|
packet = &m_rx[ ( rx_fifo_in & ( ETHER_RX_BUFFERS - 1 ) ) * ETHER_BUFFER_SIZE];
|
||||||
|
|
||||||
|
memset(packet,0x00,ETHER_BUFFER_SIZE);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
m_reg[EREG_INTERRUPT] |= EINT_RCV;
|
||||||
|
m_reg[EREG_FIFO_PORTS] &= ~0x8000;
|
||||||
|
|
||||||
|
rx_fifo_in = (rx_fifo_in + 1) & ( ETHER_RX_BUFFERS - 1 );
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror("Rejected ! Fifo Full ?");
|
||||||
|
}
|
||||||
|
|
||||||
|
update_ethernet_irq();
|
||||||
|
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
INTERNAL HELPERS
|
INTERNAL HELPERS
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
@ -233,99 +451,92 @@ void smc91c9x_device::update_stats()
|
|||||||
|
|
||||||
|
|
||||||
/*-------------------------------------------------
|
/*-------------------------------------------------
|
||||||
finish_enqueue - complete an enqueued packet
|
send_frame - push a frame to the interface
|
||||||
-------------------------------------------------*/
|
-------------------------------------------------*/
|
||||||
|
|
||||||
TIMER_CALLBACK_MEMBER(smc91c9x_device::finish_enqueue)
|
int smc91c9x_device::send_frame()
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
|
uint8_t * tx_buffer;
|
||||||
int is_broadcast = (m_tx[4] == 0xff && m_tx[5] == 0xff && m_tx[6] == 0xff &&
|
int is_broadcast = (m_tx[4] == 0xff && m_tx[5] == 0xff && m_tx[6] == 0xff &&
|
||||||
m_tx[7] == 0xff && m_tx[8] == 0xff && m_tx[9] == 0xff);
|
m_tx[7] == 0xff && m_tx[8] == 0xff && m_tx[9] == 0xff);
|
||||||
|
|
||||||
|
tx_fifo_in = ( tx_fifo_in + 1 ) & ( ETHER_TX_BUFFERS - 1 );
|
||||||
|
|
||||||
|
tx_buffer = &m_tx[(tx_fifo_out & (ETHER_TX_BUFFERS-1))* ETHER_BUFFER_SIZE];
|
||||||
|
tx_fifo_out = ((tx_fifo_out + 1)& (ETHER_TX_BUFFERS-1));
|
||||||
|
|
||||||
/* update the EPH register and stuff it in the first transmit word */
|
/* update the EPH register and stuff it in the first transmit word */
|
||||||
m_reg[EREG_EPH_STATUS] = 0x0001;
|
m_reg[EREG_EPH_STATUS] = 0x0001;
|
||||||
|
|
||||||
if (is_broadcast)
|
if (is_broadcast)
|
||||||
m_reg[EREG_EPH_STATUS] |= 0x0040;
|
m_reg[EREG_EPH_STATUS] |= 0x0040;
|
||||||
m_tx[0] = m_reg[EREG_EPH_STATUS];
|
|
||||||
m_tx[1] = m_reg[EREG_EPH_STATUS] >> 8;
|
tx_buffer[0] = m_reg[EREG_EPH_STATUS];
|
||||||
|
tx_buffer[1] = m_reg[EREG_EPH_STATUS] >> 8;
|
||||||
|
|
||||||
/* signal a transmit interrupt and mark the transmit buffer empty */
|
/* signal a transmit interrupt and mark the transmit buffer empty */
|
||||||
m_reg[EREG_INTERRUPT] |= EINT_TX;
|
m_reg[EREG_INTERRUPT] |= EINT_TX;
|
||||||
m_reg[EREG_INTERRUPT] |= EINT_TX_EMPTY;
|
m_reg[EREG_INTERRUPT] |= EINT_TX_EMPTY;
|
||||||
m_reg[EREG_FIFO_PORTS] |= 0x0080;
|
m_reg[EREG_FIFO_PORTS] |= 0x0080;
|
||||||
m_sent++;
|
m_sent++;
|
||||||
|
|
||||||
update_stats();
|
update_stats();
|
||||||
|
|
||||||
/* loopback? */
|
int buffer_len = ((tx_buffer[3] << 8) | tx_buffer[2]) & 0x7ff;
|
||||||
if (m_reg[EREG_TCR] & 0x2002) {
|
|
||||||
if (m_fifo_count < ETHER_RX_BUFFERS)
|
if (LOG_ETHERNET)
|
||||||
|
{
|
||||||
|
logerror("TX: ");
|
||||||
|
for(i=4;i<4+ETHERNET_ADDR_SIZE;i++)
|
||||||
{
|
{
|
||||||
int buffer_len = ((m_tx[3] << 8) | m_tx[2]) & 0x7ff;
|
logerror("%.2X",tx_buffer[i]);
|
||||||
uint8_t *packet = &m_rx[m_fifo_count++ * ETHER_BUFFER_SIZE];
|
}
|
||||||
int packet_len;
|
|
||||||
|
|
||||||
/* compute the packet length */
|
logerror(" ");
|
||||||
packet_len = buffer_len - 6;
|
|
||||||
if (packet[buffer_len - 1] & 0x20)
|
|
||||||
packet_len++;
|
|
||||||
|
|
||||||
/* build up the packet */
|
for(i=0;i<buffer_len-(ETHERNET_ADDR_SIZE+4);i++)
|
||||||
packet[0] = 0x0000;
|
{
|
||||||
packet[1] = 0x0000;
|
logerror("%.2X",tx_buffer[4+ETHERNET_ADDR_SIZE+i]);
|
||||||
packet[2] = buffer_len;
|
}
|
||||||
packet[3] = buffer_len >> 8;
|
|
||||||
memcpy(&packet[4], &m_tx[4], 6);
|
|
||||||
memcpy(&packet[10], &m_tx[10], 6);
|
|
||||||
memcpy(&packet[16], &m_tx[16], buffer_len - 16);
|
|
||||||
|
|
||||||
/* set the broadcast flag */
|
logerror("--- %d/0x%x bytes\r\n",buffer_len,buffer_len);
|
||||||
if (is_broadcast)
|
}
|
||||||
packet[1] |= 0x40;
|
|
||||||
|
|
||||||
/* pad? */
|
if(buffer_len>4)
|
||||||
if (m_reg[EREG_TCR & 0x0080])
|
{
|
||||||
if (packet_len < 64)
|
// odd or even sized frame ?
|
||||||
{
|
if(tx_buffer[buffer_len-1] & 0x20)
|
||||||
memset(&packet[buffer_len], 0, 64 + 6 - buffer_len);
|
buffer_len--;
|
||||||
packet[buffer_len - 1] = 0;
|
else
|
||||||
buffer_len = 64 + 6;
|
buffer_len -= 2;
|
||||||
packet[2] = buffer_len;
|
|
||||||
packet[3] = buffer_len >> 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* signal a receive */
|
if ( !(m_reg[EREG_TCR] & 0x2002) )
|
||||||
m_reg[EREG_INTERRUPT] |= EINT_RCV;
|
{
|
||||||
m_reg[EREG_FIFO_PORTS] &= ~0x8000;
|
// No loopback... Send the frame
|
||||||
|
if (!send(&tx_buffer[4], buffer_len-4))
|
||||||
|
{
|
||||||
|
// FIXME: failed to send the Ethernet packet
|
||||||
|
//logerror("failed to send Ethernet packet\r\n");
|
||||||
|
//LOG(this,("read_command_port(): !!! failed to send Ethernet packet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO loopback mode : Push the frame to the RX FIFO.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_link_unconnected) {
|
|
||||||
// Set lost carrier
|
return 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
update_ethernet_irq();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*-------------------------------------------------
|
/*-------------------------------------------------
|
||||||
process_command - handle MMU commands
|
process_command - handle MMU commands
|
||||||
-------------------------------------------------*/
|
-------------------------------------------------*/
|
||||||
|
|
||||||
void smc91c9x_device::process_command(uint16_t data)
|
void smc91c9x_device::process_command(uint16_t data)
|
||||||
{
|
{
|
||||||
switch ((data >> 5) & 7)
|
switch ((data >> 4) & 0xF)
|
||||||
{
|
{
|
||||||
case ECMD_NOP:
|
case ECMD_NOP:
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
@ -336,35 +547,52 @@ void smc91c9x_device::process_command(uint16_t data)
|
|||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
logerror(" ALLOCATE MEMORY FOR TX (%d)\n", (data & 7));
|
logerror(" ALLOCATE MEMORY FOR TX (%d)\n", (data & 7));
|
||||||
m_reg[EREG_PNR_ARR] &= ~0xff00;
|
m_reg[EREG_PNR_ARR] &= ~0xff00;
|
||||||
m_reg[EREG_PNR_ARR] |= m_alloc_count++ << 8;
|
m_reg[EREG_PNR_ARR] |= (m_alloc_count++ & 0x7F) << 8;
|
||||||
m_reg[EREG_INTERRUPT] |= 0x0008;
|
m_reg[EREG_INTERRUPT] |= 0x0008;
|
||||||
update_ethernet_irq();
|
update_ethernet_irq();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ECMD_RESET_MMU:
|
case ECMD_RESET_MMU:
|
||||||
|
/*
|
||||||
|
0100
|
||||||
|
- RESET MMU TO INITIAL STATE -
|
||||||
|
Frees all memory allocations, clears relevant
|
||||||
|
interrupts, resets packet FIFO pointers.
|
||||||
|
*/
|
||||||
|
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
logerror(" RESET MMU\n");
|
logerror(" RESET MMU\n");
|
||||||
|
// Flush fifos.
|
||||||
|
clear_tx_fifo();
|
||||||
|
clear_rx_fifo();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ECMD_REMOVE:
|
case ECMD_REMOVE_TOPFRAME_TX:
|
||||||
|
if (LOG_ETHERNET)
|
||||||
|
logerror(" REMOVE FRAME FROM TX FIFO\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ECMD_REMOVE_TOPFRAME_RX:
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
logerror(" REMOVE FRAME FROM RX FIFO\n");
|
logerror(" REMOVE FRAME FROM RX FIFO\n");
|
||||||
break;
|
|
||||||
|
|
||||||
case ECMD_REMOVE_RELEASE:
|
case ECMD_REMOVE_RELEASE_TOPFRAME_RX:
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
logerror(" REMOVE AND RELEASE FRAME FROM RX FIFO\n");
|
logerror(" REMOVE AND RELEASE FRAME FROM RX FIFO (RXI=%d RXO=%d)\n",rx_fifo_in & ( ETHER_RX_BUFFERS - 1 ),rx_fifo_out & ( ETHER_RX_BUFFERS - 1 ));
|
||||||
|
|
||||||
m_reg[EREG_INTERRUPT] &= ~EINT_RCV;
|
m_reg[EREG_INTERRUPT] &= ~EINT_RCV;
|
||||||
if (m_fifo_count > 0)
|
|
||||||
m_fifo_count--;
|
if( (rx_fifo_in & ( ETHER_RX_BUFFERS - 1 ) ) != (rx_fifo_out & ( ETHER_RX_BUFFERS - 1 ) ) )
|
||||||
if (m_fifo_count > 0)
|
rx_fifo_out = ( (rx_fifo_out + 1) & ( ETHER_RX_BUFFERS - 1 ) );
|
||||||
|
|
||||||
|
if( (rx_fifo_in & ( ETHER_RX_BUFFERS - 1 ) ) != (rx_fifo_out & ( ETHER_RX_BUFFERS - 1 ) ) )
|
||||||
{
|
{
|
||||||
memmove(&m_rx[0], &m_rx[ETHER_BUFFER_SIZE], m_fifo_count * ETHER_BUFFER_SIZE);
|
|
||||||
m_reg[EREG_INTERRUPT] |= EINT_RCV;
|
m_reg[EREG_INTERRUPT] |= EINT_RCV;
|
||||||
m_reg[EREG_FIFO_PORTS] &= ~0x8000;
|
m_reg[EREG_FIFO_PORTS] &= ~0x8000;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
m_reg[EREG_FIFO_PORTS] |= 0x8000;
|
m_reg[EREG_FIFO_PORTS] |= 0x8000;
|
||||||
|
|
||||||
update_ethernet_irq();
|
update_ethernet_irq();
|
||||||
m_recd++;
|
m_recd++;
|
||||||
update_stats();
|
update_stats();
|
||||||
@ -378,13 +606,53 @@ void smc91c9x_device::process_command(uint16_t data)
|
|||||||
case ECMD_ENQUEUE_PACKET:
|
case ECMD_ENQUEUE_PACKET:
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
logerror(" ENQUEUE TX PACKET\n");
|
logerror(" ENQUEUE TX PACKET\n");
|
||||||
// Set some delay before tranmit ends
|
|
||||||
m_tx_timer->adjust(attotime::from_usec(100));
|
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
|
||||||
|
{
|
||||||
|
if (m_reg[EREG_TCR] & 0x0001) // TX EN ?
|
||||||
|
{
|
||||||
|
send_frame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_ethernet_irq();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ECMD_RESET_FIFOS:
|
case ECMD_RESET_FIFOS:
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
logerror(" RESET TX FIFOS\n");
|
logerror(" RESET TX FIFOS\n");
|
||||||
|
// Flush fifos.
|
||||||
|
clear_tx_fifo();
|
||||||
|
clear_rx_fifo();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Set Busy (clear on next read)
|
// Set Busy (clear on next read)
|
||||||
@ -409,6 +677,7 @@ READ16_MEMBER( smc91c9x_device::read )
|
|||||||
offset %= 8;
|
offset %= 8;
|
||||||
if (offset != EREG_BANK)
|
if (offset != EREG_BANK)
|
||||||
offset += 8 * (m_reg[EREG_BANK] & 7);
|
offset += 8 * (m_reg[EREG_BANK] & 7);
|
||||||
|
|
||||||
result = m_reg[offset];
|
result = m_reg[offset];
|
||||||
|
|
||||||
switch (offset)
|
switch (offset)
|
||||||
@ -429,8 +698,18 @@ READ16_MEMBER( smc91c9x_device::read )
|
|||||||
case EREG_DATA_0: /* data register */
|
case EREG_DATA_0: /* data register */
|
||||||
case EREG_DATA_1: /* data register */
|
case EREG_DATA_1: /* data register */
|
||||||
{
|
{
|
||||||
uint8_t *buffer = (m_reg[EREG_POINTER] & 0x8000) ? m_rx : m_tx;
|
uint8_t *buffer;
|
||||||
int addr = m_reg[EREG_POINTER] & 0x7ff;
|
int addr = m_reg[EREG_POINTER] & 0x7ff;
|
||||||
|
|
||||||
|
if(m_reg[EREG_POINTER] & 0x8000)
|
||||||
|
{
|
||||||
|
buffer = &m_rx[(rx_fifo_out & ( ETHER_RX_BUFFERS - 1 )) * ETHER_BUFFER_SIZE];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer = (uint8_t *)&m_tx[(tx_fifo_in & (ETHER_TX_BUFFERS-1))* ETHER_BUFFER_SIZE];;
|
||||||
|
}
|
||||||
|
|
||||||
result = buffer[addr++];
|
result = buffer[addr++];
|
||||||
if (ACCESSING_BITS_8_15)
|
if (ACCESSING_BITS_8_15)
|
||||||
result |= buffer[addr++] << 8;
|
result |= buffer[addr++] << 8;
|
||||||
@ -441,7 +720,7 @@ READ16_MEMBER( smc91c9x_device::read )
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (LOG_ETHERNET && offset != EREG_BANK)
|
if (LOG_ETHERNET && offset != EREG_BANK)
|
||||||
logerror("%s:smc91c9x_r(%s) = %04X & %04X\n", machine().describe_context(), ethernet_regname[offset], result, mem_mask);
|
logerror("%s:smc91c9x_r(%s) = %04X & %04X\n", (machine().describe_context()).c_str(), ethernet_regname[offset], result, mem_mask);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,21 +731,19 @@ READ16_MEMBER( smc91c9x_device::read )
|
|||||||
|
|
||||||
WRITE16_MEMBER( smc91c9x_device::write )
|
WRITE16_MEMBER( smc91c9x_device::write )
|
||||||
{
|
{
|
||||||
// uint16_t olddata;
|
|
||||||
|
|
||||||
/* determine the effective register */
|
/* determine the effective register */
|
||||||
offset %= 8;
|
offset %= 8;
|
||||||
if (offset != EREG_BANK)
|
if (offset != EREG_BANK)
|
||||||
offset += 8 * (m_reg[EREG_BANK] & 7);
|
offset += 8 * (m_reg[EREG_BANK] & 7);
|
||||||
|
|
||||||
/* update the data generically */
|
/* update the data generically */
|
||||||
// olddata = m_reg[offset];
|
|
||||||
|
if (LOG_ETHERNET && offset != 7 && offset < sizeof(m_reg))
|
||||||
|
logerror("%s:smc91c9x_w(%s) = [%04X]<-%04X & (%04X & %04X)\n", (machine().describe_context()).c_str(), ethernet_regname[offset], offset, data, mem_mask , m_regmask[offset]);
|
||||||
|
|
||||||
mem_mask &= m_regmask[offset];
|
mem_mask &= m_regmask[offset];
|
||||||
COMBINE_DATA(&m_reg[offset]);
|
COMBINE_DATA(&m_reg[offset]);
|
||||||
|
|
||||||
if (LOG_ETHERNET && offset != 7)
|
|
||||||
logerror("%s:smc91c9x_w(%s) = %04X & %04X\n", machine().describe_context(), ethernet_regname[offset], data, mem_mask);
|
|
||||||
|
|
||||||
/* handle it */
|
/* handle it */
|
||||||
switch (offset)
|
switch (offset)
|
||||||
{
|
{
|
||||||
@ -477,6 +754,7 @@ WRITE16_MEMBER( smc91c9x_device::write )
|
|||||||
m_reg[EREG_INTERRUPT] &= ~EINT_EPH;
|
m_reg[EREG_INTERRUPT] &= ~EINT_EPH;
|
||||||
update_ethernet_irq();
|
update_ethernet_irq();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
{
|
{
|
||||||
if (data & 0x2000) logerror(" EPH LOOP\n");
|
if (data & 0x2000) logerror(" EPH LOOP\n");
|
||||||
@ -492,6 +770,18 @@ WRITE16_MEMBER( smc91c9x_device::write )
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EREG_RCR: /* receive control register */
|
case EREG_RCR: /* receive control register */
|
||||||
|
|
||||||
|
if (data & 0x8000)
|
||||||
|
{
|
||||||
|
clear_rx_fifo();
|
||||||
|
clear_tx_fifo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(data & 0x0100))
|
||||||
|
{
|
||||||
|
clear_rx_fifo();
|
||||||
|
}
|
||||||
|
|
||||||
if (LOG_ETHERNET)
|
if (LOG_ETHERNET)
|
||||||
{
|
{
|
||||||
if (data & 0x8000) reset();
|
if (data & 0x8000) reset();
|
||||||
@ -550,8 +840,18 @@ WRITE16_MEMBER( smc91c9x_device::write )
|
|||||||
case EREG_DATA_0: /* data register */
|
case EREG_DATA_0: /* data register */
|
||||||
case EREG_DATA_1: /* data register */
|
case EREG_DATA_1: /* data register */
|
||||||
{
|
{
|
||||||
uint8_t *buffer = (m_reg[EREG_POINTER] & 0x8000) ? m_rx : m_tx;
|
uint8_t *buffer;
|
||||||
int addr = m_reg[EREG_POINTER] & 0x7ff;
|
int addr = m_reg[EREG_POINTER] & 0x7ff;
|
||||||
|
|
||||||
|
if(m_reg[EREG_POINTER] & 0x8000)
|
||||||
|
{
|
||||||
|
buffer = &m_rx[(rx_fifo_out & ( ETHER_RX_BUFFERS - 1 )) * ETHER_BUFFER_SIZE];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer = (uint8_t *)&m_tx[(tx_fifo_in & (ETHER_TX_BUFFERS-1))* ETHER_BUFFER_SIZE];;
|
||||||
|
}
|
||||||
|
|
||||||
buffer[addr++] = data;
|
buffer[addr++] = data;
|
||||||
if (ACCESSING_BITS_8_15)
|
if (ACCESSING_BITS_8_15)
|
||||||
buffer[addr++] = data >> 8;
|
buffer[addr++] = data >> 8;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
SMC91C9X ethernet controller implementation
|
SMC91C9X ethernet controller implementation
|
||||||
|
|
||||||
by Aaron Giles
|
by Aaron Giles, Jean-François DEL NERO
|
||||||
|
|
||||||
**************************************************************************/
|
**************************************************************************/
|
||||||
|
|
||||||
@ -15,7 +15,7 @@
|
|||||||
TYPE DEFINITIONS
|
TYPE DEFINITIONS
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
class smc91c9x_device : public device_t
|
class smc91c9x_device : public device_t,public device_network_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
template <class Object> static devcb_base &set_irq_callback(device_t &device, Object &&cb) { return downcast<smc91c9x_device &>(device).m_irq_handler.set_callback(std::forward<Object>(cb)); }
|
template <class Object> static devcb_base &set_irq_callback(device_t &device, Object &&cb) { return downcast<smc91c9x_device &>(device).m_irq_handler.set_callback(std::forward<Object>(cb)); }
|
||||||
@ -23,6 +23,8 @@ public:
|
|||||||
DECLARE_READ16_MEMBER( read );
|
DECLARE_READ16_MEMBER( read );
|
||||||
DECLARE_WRITE16_MEMBER( write );
|
DECLARE_WRITE16_MEMBER( write );
|
||||||
|
|
||||||
|
virtual void recv_cb(uint8_t *data, int length) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
smc91c9x_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
|
smc91c9x_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock);
|
||||||
|
|
||||||
@ -32,7 +34,9 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr unsigned ETHER_BUFFER_SIZE = 2048;
|
static constexpr unsigned ETHER_BUFFER_SIZE = 2048;
|
||||||
static constexpr unsigned ETHER_RX_BUFFERS = 4;
|
static constexpr unsigned ETHER_RX_BUFFERS = 16;
|
||||||
|
static constexpr unsigned ETHER_TX_BUFFERS = 16;
|
||||||
|
static constexpr unsigned ETHERNET_ADDR_SIZE = 6;
|
||||||
|
|
||||||
// internal state
|
// internal state
|
||||||
devcb_write_line m_irq_handler;
|
devcb_write_line m_irq_handler;
|
||||||
@ -52,18 +56,30 @@ private:
|
|||||||
|
|
||||||
/* transmit/receive FIFOs */
|
/* transmit/receive FIFOs */
|
||||||
uint8_t m_fifo_count;
|
uint8_t m_fifo_count;
|
||||||
|
|
||||||
|
uint32_t rx_fifo_out;
|
||||||
|
uint32_t rx_fifo_in;
|
||||||
uint8_t m_rx[ETHER_BUFFER_SIZE * ETHER_RX_BUFFERS];
|
uint8_t m_rx[ETHER_BUFFER_SIZE * ETHER_RX_BUFFERS];
|
||||||
uint8_t m_tx[ETHER_BUFFER_SIZE];
|
|
||||||
|
uint32_t tx_fifo_out;
|
||||||
|
uint32_t tx_fifo_in;
|
||||||
|
uint8_t m_tx[ETHER_BUFFER_SIZE * ETHER_TX_BUFFERS];
|
||||||
|
|
||||||
/* counters */
|
/* counters */
|
||||||
uint32_t m_sent;
|
uint32_t m_sent;
|
||||||
uint32_t m_recd;
|
uint32_t m_recd;
|
||||||
|
|
||||||
|
int ethernet_packet_is_for_me(const uint8_t mac_address[]);
|
||||||
|
int is_broadcast(uint8_t mac_address[]);
|
||||||
|
|
||||||
void update_ethernet_irq();
|
void update_ethernet_irq();
|
||||||
void update_stats();
|
void update_stats();
|
||||||
TIMER_CALLBACK_MEMBER(finish_enqueue);
|
|
||||||
void process_command(uint16_t data);
|
void process_command(uint16_t data);
|
||||||
emu_timer* m_tx_timer;
|
void clear_tx_fifo();
|
||||||
|
void clear_rx_fifo();
|
||||||
|
|
||||||
|
int send_frame();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user