Real-NE2000 semantics: accept only own-MAC unicast + broadcast (multicast
excluded -- the guest stack is unicast+ARP only), and drop npcap's loopback
of our own injected frames. Without it, a live-LAN host download floods the
emulated NIC at wire rate (65k+ frames/min) and wedges netnub before boot
("discarding..."). Filter failure is non-fatal: logs and runs unfiltered.
Also log the first 12 unicast accepts/drops with the live PAR registers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1748 lines
53 KiB
C++
1748 lines
53 KiB
C++
#include "config.h"
|
|
|
|
#include "dosbox.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "logging.h"
|
|
#include "support.h"
|
|
#include "inout.h"
|
|
#include "setup.h"
|
|
#include "callback.h"
|
|
#include "timer.h"
|
|
#include "pic.h"
|
|
#include "cpu.h"
|
|
#include "setup.h"
|
|
#include "control.h"
|
|
#include "hardware.h"
|
|
|
|
#ifdef WIN32
|
|
# include <winsock.h>
|
|
#endif
|
|
|
|
/* Couldn't find a real spec for the NE2000 out there, hence this is adapted heavily from Bochs */
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// $Id: ne2k.cc,v 1.56.2.1 2004/02/02 22:37:22 cbothamy Exp $
|
|
/////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2002 MandrakeSoft S.A.
|
|
//
|
|
// MandrakeSoft S.A.
|
|
// 43, rue d'Aboukir
|
|
// 75002 Paris - France
|
|
// http://www.linux-mandrake.com/
|
|
// http://www.mandrakesoft.com/
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
// Peter Grehan (grehan@iprg.nokia.com) coded all of this
|
|
// NE2000/ether stuff.
|
|
|
|
#include "ne2000.h"
|
|
|
|
#if C_IPX
|
|
#include "ipx.h"
|
|
#endif
|
|
|
|
#include "ethernet.h"
|
|
EthernetConnection* ethernet = nullptr;
|
|
|
|
bool ne2k_ipx_redirect = false;
|
|
unsigned int ne2k_ipx_highest_variant = 0; // Windows 95 likes to try all three variations as it runs
|
|
|
|
#if C_IPX
|
|
void ethernetSendToIPX(const unsigned char *outptr, unsigned int outlen, unsigned int vari);
|
|
|
|
bool is_IPX_ethernet_frame(const unsigned char *buf,unsigned int len,const unsigned char **outbuf,unsigned int *outlen,unsigned int *vari) {
|
|
if (outbuf && outlen) {
|
|
*outbuf = NULL;
|
|
*outlen = 0;
|
|
*vari = 0;
|
|
}
|
|
|
|
if (len >= 32) {
|
|
/* bytes 0-5: destination Ethernet MAC
|
|
* bytes 6-11: source Ethernet MAC
|
|
* bytes 12-13: EtherType (or length if less than 1536) */
|
|
const unsigned int EtherType = be16toh( *((uint16_t*)(buf+12)) );
|
|
|
|
/* NTS: I noticed Windows 95 likes to try all three forms of IPX packet */
|
|
|
|
if (EtherType == 0x8137) {
|
|
if (outbuf && outlen) {
|
|
*outbuf = buf+14;
|
|
*outlen = len-14;
|
|
*vari = IPX_8137;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (EtherType < 1536) {
|
|
/* <16-bit len> 0xFFFF
|
|
*
|
|
* or
|
|
*
|
|
* <16-bit len> 0xE0E003 0xFFFF (LLC header between len and packet) */
|
|
if ( *((uint16_t*)(buf+14)) == 0xFFFF) {
|
|
if (outbuf && outlen) {
|
|
*outbuf = buf+14;
|
|
*outlen = len-14;
|
|
*vari = IPX_OLD;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if ( memcmp(buf+14,"\xE0\xE0\x03\xFF\xFF",5) == 0) {
|
|
if (outbuf && outlen) {
|
|
*outbuf = buf+17;
|
|
*outlen = len-17;
|
|
*vari = IPX_OLD_LLC;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static void NE2000_TX_Event(Bitu val);
|
|
void VFILE_Remove(const char *name,const char *dir = "");
|
|
void Capture_WritePacket(bool send,const unsigned char *buf,size_t len);
|
|
|
|
//Never completely fill the ne2k ring so that we never
|
|
// hit the unclear completely full buffer condition.
|
|
#define BX_NE2K_NEVER_FULL_RING (1)
|
|
|
|
#define LOG_THIS theNE2kDevice->
|
|
//#define BX_DEBUG
|
|
//#define BX_INFO
|
|
#define BX_NULL_TIMER_HANDLE 0
|
|
//#define BX_PANIC
|
|
//#define BX_ERROR
|
|
#define BX_RESET_HARDWARE 0
|
|
#define BX_RESET_SOFTWARE 1
|
|
|
|
bool addne2k = false;
|
|
static char bxtmp[1024];
|
|
|
|
static inline void BX_INFO(const char *msg,...) {
|
|
va_list va;
|
|
|
|
va_start(va,msg);
|
|
vsnprintf(bxtmp,sizeof(bxtmp)-1,msg,va);
|
|
va_end(va);
|
|
|
|
LOG(LOG_MISC,LOG_NORMAL)("BX_INFO: %s",bxtmp);
|
|
}
|
|
|
|
static inline void BX_DEBUG(const char *msg,...) {
|
|
if (false/*TOO MUCH DEBUG INFO*/) {
|
|
va_list va;
|
|
|
|
va_start(va,msg);
|
|
vsnprintf(bxtmp,sizeof(bxtmp)-1,msg,va);
|
|
va_end(va);
|
|
|
|
LOG(LOG_MISC,LOG_DEBUG)("BX_DEBUG: %s",bxtmp);
|
|
}
|
|
}
|
|
|
|
static inline void BX_ERROR(const char *msg,...) {
|
|
va_list va;
|
|
|
|
va_start(va,msg);
|
|
vsnprintf(bxtmp,sizeof(bxtmp)-1,msg,va);
|
|
va_end(va);
|
|
|
|
LOG_MSG("BX_ERROR: %s",bxtmp);
|
|
}
|
|
|
|
static inline void BX_PANIC(const char *msg,...) {
|
|
va_list va;
|
|
|
|
va_start(va,msg);
|
|
vsnprintf(bxtmp,sizeof(bxtmp)-1,msg,va);
|
|
va_end(va);
|
|
|
|
LOG_MSG("BX_PANIC: %s",bxtmp);
|
|
E_Exit("BX_PANIC condition");
|
|
}
|
|
|
|
bx_ne2k_c* theNE2kDevice = NULL;
|
|
|
|
|
|
bx_ne2k_c::bx_ne2k_c(void)
|
|
{
|
|
s.tx_timer_index = BX_NULL_TIMER_HANDLE;
|
|
}
|
|
|
|
|
|
bx_ne2k_c::~bx_ne2k_c(void)
|
|
{
|
|
// nothing for now
|
|
}
|
|
|
|
//
|
|
// reset - restore state to power-up, cancelling all i/o
|
|
//
|
|
void
|
|
bx_ne2k_c::reset(unsigned type)
|
|
{
|
|
(void)type;//UNUSED
|
|
BX_DEBUG ("reset");
|
|
// Zero out registers and memory
|
|
memset( & BX_NE2K_THIS s.CR, 0, sizeof(BX_NE2K_THIS s.CR) );
|
|
memset( & BX_NE2K_THIS s.ISR, 0, sizeof(BX_NE2K_THIS s.ISR));
|
|
memset( & BX_NE2K_THIS s.IMR, 0, sizeof(BX_NE2K_THIS s.IMR));
|
|
memset( & BX_NE2K_THIS s.DCR, 0, sizeof(BX_NE2K_THIS s.DCR));
|
|
memset( & BX_NE2K_THIS s.TCR, 0, sizeof(BX_NE2K_THIS s.TCR));
|
|
memset( & BX_NE2K_THIS s.TSR, 0, sizeof(BX_NE2K_THIS s.TSR));
|
|
//memset( & BX_NE2K_THIS s.RCR, 0, sizeof(BX_NE2K_THIS s.RCR));
|
|
memset( & BX_NE2K_THIS s.RSR, 0, sizeof(BX_NE2K_THIS s.RSR));
|
|
BX_NE2K_THIS s.tx_timer_active = 0;
|
|
BX_NE2K_THIS s.local_dma = 0;
|
|
BX_NE2K_THIS s.page_start = 0;
|
|
BX_NE2K_THIS s.page_stop = 0;
|
|
BX_NE2K_THIS s.bound_ptr = 0;
|
|
BX_NE2K_THIS s.tx_page_start = 0;
|
|
BX_NE2K_THIS s.num_coll = 0;
|
|
BX_NE2K_THIS s.tx_bytes = 0;
|
|
BX_NE2K_THIS s.fifo = 0;
|
|
BX_NE2K_THIS s.remote_dma = 0;
|
|
BX_NE2K_THIS s.remote_start = 0;
|
|
BX_NE2K_THIS s.remote_bytes = 0;
|
|
BX_NE2K_THIS s.tallycnt_0 = 0;
|
|
BX_NE2K_THIS s.tallycnt_1 = 0;
|
|
BX_NE2K_THIS s.tallycnt_2 = 0;
|
|
|
|
//memset( & BX_NE2K_THIS s.physaddr, 0, sizeof(BX_NE2K_THIS s.physaddr));
|
|
//memset( & BX_NE2K_THIS s.mchash, 0, sizeof(BX_NE2K_THIS s.mchash));
|
|
BX_NE2K_THIS s.curr_page = 0;
|
|
|
|
BX_NE2K_THIS s.rempkt_ptr = 0;
|
|
BX_NE2K_THIS s.localpkt_ptr = 0;
|
|
BX_NE2K_THIS s.address_cnt = 0;
|
|
|
|
memset( & BX_NE2K_THIS s.mem, 0, sizeof(BX_NE2K_THIS s.mem));
|
|
|
|
// Set power-up conditions
|
|
BX_NE2K_THIS s.CR.stop = 1;
|
|
BX_NE2K_THIS s.CR.rdma_cmd = 4;
|
|
BX_NE2K_THIS s.ISR.reset = 1;
|
|
BX_NE2K_THIS s.DCR.longaddr = 1;
|
|
PIC_DeActivateIRQ((unsigned int)s.base_irq);
|
|
//DEV_pic_lower_irq(BX_NE2K_THIS s.base_irq);
|
|
}
|
|
|
|
//
|
|
// read_cr/write_cr - utility routines for handling reads/writes to
|
|
// the Command Register
|
|
//
|
|
uint32_t
|
|
bx_ne2k_c::read_cr(void)
|
|
{
|
|
uint32_t val =
|
|
(((unsigned int)(BX_NE2K_THIS s.CR.pgsel & 0x03u) << 6u) |
|
|
((unsigned int)(BX_NE2K_THIS s.CR.rdma_cmd & 0x07u) << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.CR.tx_packet << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.CR.start << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.CR.stop));
|
|
BX_DEBUG("read CR returns 0x%08x", val);
|
|
return val;
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::write_cr(uint32_t value)
|
|
{
|
|
BX_DEBUG ("wrote 0x%02x to CR", value);
|
|
|
|
// Validate remote-DMA
|
|
if ((value & 0x38) == 0x00) {
|
|
BX_DEBUG("CR write - invalid rDMA value 0");
|
|
value |= 0x20; /* dma_cmd == 4 is a safe default */
|
|
//value = 0x22; /* dma_cmd == 4 is a safe default */
|
|
}
|
|
|
|
// Check for s/w reset
|
|
if (value & 0x01) {
|
|
BX_NE2K_THIS s.ISR.reset = 1;
|
|
BX_NE2K_THIS s.CR.stop = 1;
|
|
} else {
|
|
BX_NE2K_THIS s.CR.stop = 0;
|
|
}
|
|
|
|
BX_NE2K_THIS s.CR.rdma_cmd = (value & 0x38) >> 3;
|
|
|
|
// If start command issued, the RST bit in the ISR
|
|
// must be cleared
|
|
if ((value & 0x02) && !BX_NE2K_THIS s.CR.start) {
|
|
BX_NE2K_THIS s.ISR.reset = 0;
|
|
}
|
|
|
|
BX_NE2K_THIS s.CR.start = ((value & 0x02) == 0x02);
|
|
BX_NE2K_THIS s.CR.pgsel = (value & 0xc0) >> 6;
|
|
|
|
// Check for send-packet command
|
|
if (BX_NE2K_THIS s.CR.rdma_cmd == 3) {
|
|
// Set up DMA read from receive ring
|
|
BX_NE2K_THIS s.remote_start = BX_NE2K_THIS s.remote_dma =
|
|
BX_NE2K_THIS s.bound_ptr * 256;
|
|
BX_NE2K_THIS s.remote_bytes = *((uint16_t*) &
|
|
BX_NE2K_THIS s.mem[BX_NE2K_THIS s.bound_ptr * 256 + 2 - BX_NE2K_MEMSTART]);
|
|
BX_INFO("Sending buffer #x%x length %d",
|
|
BX_NE2K_THIS s.remote_start,
|
|
BX_NE2K_THIS s.remote_bytes);
|
|
}
|
|
|
|
// Check for start-tx
|
|
if ((value & 0x04) && BX_NE2K_THIS s.TCR.loop_cntl) {
|
|
// loopback mode
|
|
if (BX_NE2K_THIS s.TCR.loop_cntl != 1) {
|
|
BX_INFO("Loop mode %d not supported.", BX_NE2K_THIS s.TCR.loop_cntl);
|
|
} else {
|
|
rx_frame (& BX_NE2K_THIS s.mem[BX_NE2K_THIS s.tx_page_start*256 -
|
|
BX_NE2K_MEMSTART],
|
|
BX_NE2K_THIS s.tx_bytes);
|
|
|
|
// do a TX interrupt
|
|
// Generate an interrupt if not masked and not one in progress
|
|
if (BX_NE2K_THIS s.IMR.tx_inte && !BX_NE2K_THIS s.ISR.pkt_tx) {
|
|
//LOG_MSG("tx complete interrupt");
|
|
PIC_ActivateIRQ((unsigned int)s.base_irq);
|
|
}
|
|
BX_NE2K_THIS s.ISR.pkt_tx = 1;
|
|
}
|
|
} else if (value & 0x04) {
|
|
// start-tx and no loopback
|
|
if (BX_NE2K_THIS s.CR.stop || !BX_NE2K_THIS s.CR.start)
|
|
BX_PANIC(("CR write - tx start, dev in reset"));
|
|
|
|
if (BX_NE2K_THIS s.tx_bytes == 0)
|
|
BX_PANIC(("CR write - tx start, tx bytes == 0"));
|
|
|
|
#ifdef notdef
|
|
// XXX debug stuff
|
|
printf("packet tx (%d bytes):\t", BX_NE2K_THIS s.tx_bytes);
|
|
for (int i = 0; i < BX_NE2K_THIS s.tx_bytes; i++) {
|
|
printf("%02x ", BX_NE2K_THIS s.mem[BX_NE2K_THIS s.tx_page_start*256 -
|
|
BX_NE2K_MEMSTART + i]);
|
|
if (i && (((i+1) % 16) == 0))
|
|
printf("\t");
|
|
}
|
|
printf("");
|
|
#endif
|
|
|
|
if (CaptureState & CAPTURE_NETWORK) Capture_WritePacket(true/*send*/,&s.mem[s.tx_page_start*256 - BX_NE2K_MEMSTART],s.tx_bytes);
|
|
|
|
// Send the packet to the system driver
|
|
/* TODO: Transmit packet */
|
|
//BX_NE2K_THIS ethdev->sendpkt(& BX_NE2K_THIS s.mem[BX_NE2K_THIS s.tx_page_start*256 - BX_NE2K_MEMSTART], BX_NE2K_THIS s.tx_bytes);
|
|
|
|
{
|
|
#if C_IPX
|
|
const unsigned char *outptr = NULL;
|
|
unsigned int outlen = 0;
|
|
unsigned int vari = 0;
|
|
#endif
|
|
|
|
#if C_IPX
|
|
if (ne2k_ipx_redirect && is_IPX_ethernet_frame(&s.mem[s.tx_page_start*256 - BX_NE2K_MEMSTART], s.tx_bytes, &outptr, &outlen, &vari)) {
|
|
if (ne2k_ipx_highest_variant < vari) ne2k_ipx_highest_variant = vari;
|
|
if (vari == IPX_OLD_LLC/*TODO: Make configurable*//*Best for Windows 95*/) ethernetSendToIPX(outptr, outlen, vari);
|
|
}
|
|
else {
|
|
#endif
|
|
ethernet->SendPacket(&s.mem[s.tx_page_start*256 - BX_NE2K_MEMSTART], s.tx_bytes);
|
|
#if C_IPX
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Trigger any pending timers
|
|
if (BX_NE2K_THIS s.tx_timer_active) {
|
|
NE2000_TX_Event(0);
|
|
PIC_RemoveEvents(NE2000_TX_Event);
|
|
}
|
|
//LOG_MSG("send packet command");
|
|
//s.tx_timer_index = (64 + 96 + 4*8 + BX_NE2K_THIS s.tx_bytes*8)/10;
|
|
s.tx_timer_active = 1;
|
|
PIC_AddEvent(NE2000_TX_Event,(float)((64 + 96 + 4*8 + BX_NE2K_THIS s.tx_bytes*8)/10000.0),0);
|
|
// Schedule a timer to trigger a tx-complete interrupt
|
|
// The number of microseconds is the bit-time / 10.
|
|
// The bit-time is the preamble+sfd (64 bits), the
|
|
// inter-frame gap (96 bits), the CRC (4 bytes), and the
|
|
// the number of bits in the frame (s.tx_bytes * 8).
|
|
//
|
|
|
|
/* TODO: Code transmit timer */
|
|
/*
|
|
bx_pc_system.activate_timer(BX_NE2K_THIS s.tx_timer_index,
|
|
(64 + 96 + 4*8 + BX_NE2K_THIS s.tx_bytes*8)/10,
|
|
0); // not continuous
|
|
*/
|
|
} // end transmit-start branch
|
|
|
|
// Linux probes for an interrupt by setting up a remote-DMA read
|
|
// of 0 bytes with remote-DMA completion interrupts enabled.
|
|
// Detect this here
|
|
if (BX_NE2K_THIS s.CR.rdma_cmd == 0x01 &&
|
|
BX_NE2K_THIS s.CR.start &&
|
|
BX_NE2K_THIS s.remote_bytes == 0) {
|
|
BX_NE2K_THIS s.ISR.rdma_done = 1;
|
|
if (BX_NE2K_THIS s.IMR.rdma_inte) {
|
|
PIC_ActivateIRQ((unsigned int)s.base_irq);
|
|
//DEV_pic_raise_irq(BX_NE2K_THIS s.base_irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// chipmem_read/chipmem_write - access the 64K private RAM.
|
|
// The ne2000 memory is accessed through the data port of
|
|
// the asic (offset 0) after setting up a remote-DMA transfer.
|
|
// Both byte and word accesses are allowed.
|
|
// The first 16 bytes contains the MAC address at even locations,
|
|
// and there is 16K of buffer memory starting at 16K
|
|
//
|
|
uint32_t bx_ne2k_c::chipmem_read(uint32_t address, unsigned int io_len)
|
|
{
|
|
uint32_t retval = 0;
|
|
|
|
if ((io_len == 2) && (address & 0x1))
|
|
BX_PANIC(("unaligned chipmem word read"));
|
|
|
|
// ROM'd MAC address
|
|
if (/*(address >=0) && */address <= 31) {
|
|
retval = BX_NE2K_THIS s.macaddr[address];
|
|
if ((io_len == 2u) || (io_len == 4u)) {
|
|
retval |= (unsigned int)(BX_NE2K_THIS s.macaddr[address + 1u] << 8u);
|
|
if (io_len == 4u) {
|
|
retval |= (unsigned int)(BX_NE2K_THIS s.macaddr[address + 2u] << 16u);
|
|
retval |= (unsigned int)(BX_NE2K_THIS s.macaddr[address + 3u] << 24u);
|
|
}
|
|
}
|
|
return (retval);
|
|
}
|
|
|
|
if ((address >= BX_NE2K_MEMSTART) && (address < BX_NE2K_MEMEND)) {
|
|
retval = BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART];
|
|
if ((io_len == 2u) || (io_len == 4u)) {
|
|
retval |= (unsigned int)(BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 1] << 8u);
|
|
}
|
|
if (io_len == 4u) {
|
|
retval |= (unsigned int)(BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 2] << 16u);
|
|
retval |= (unsigned int)(BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 3] << 24u);
|
|
}
|
|
return (retval);
|
|
}
|
|
|
|
BX_DEBUG("out-of-bounds chipmem read, %04X", address);
|
|
|
|
return (0xff);
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::chipmem_write(uint32_t address, uint32_t value, unsigned io_len)
|
|
{
|
|
if ((io_len == 2) && (address & 0x1))
|
|
BX_PANIC(("unaligned chipmem word write"));
|
|
|
|
if ((address >= BX_NE2K_MEMSTART) && (address < BX_NE2K_MEMEND)) {
|
|
BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART] = value & 0xff;
|
|
if (io_len == 2)
|
|
BX_NE2K_THIS s.mem[address - BX_NE2K_MEMSTART + 1] = value >> 8;
|
|
} else
|
|
BX_DEBUG("out-of-bounds chipmem write, %04X", address);
|
|
}
|
|
|
|
//
|
|
// asic_read/asic_write - This is the high 16 bytes of i/o space
|
|
// (the lower 16 bytes is for the DS8390). Only two locations
|
|
// are used: offset 0, which is used for data transfer, and
|
|
// offset 0xf, which is used to reset the device.
|
|
// The data transfer port is used to as 'external' DMA to the
|
|
// DS8390. The chip has to have the DMA registers set up, and
|
|
// after that, insw/outsw instructions can be used to move
|
|
// the appropriate number of bytes to/from the device.
|
|
//
|
|
uint32_t
|
|
bx_ne2k_c::asic_read(uint32_t offset, unsigned int io_len)
|
|
{
|
|
uint32_t retval = 0;
|
|
|
|
switch (offset) {
|
|
case 0x0: // Data register
|
|
//
|
|
// A read remote-DMA command must have been issued,
|
|
// and the source-address and length registers must
|
|
// have been initialised.
|
|
//
|
|
if (io_len > BX_NE2K_THIS s.remote_bytes)
|
|
{
|
|
BX_ERROR("ne2K: dma read underrun iolen=%d remote_bytes=%d",io_len,BX_NE2K_THIS s.remote_bytes);
|
|
//return 0;
|
|
}
|
|
|
|
//BX_INFO(("ne2k read DMA: addr=%4x remote_bytes=%d",BX_NE2K_THIS s.remote_dma,BX_NE2K_THIS s.remote_bytes));
|
|
retval = chipmem_read(BX_NE2K_THIS s.remote_dma, io_len);
|
|
//
|
|
// The 8390 bumps the address and decreases the byte count
|
|
// by the selected word size after every access, not by
|
|
// the amount of data requested by the host (io_len).
|
|
//
|
|
BX_NE2K_THIS s.remote_dma += (BX_NE2K_THIS s.DCR.wdsize + 1);
|
|
if (BX_NE2K_THIS s.remote_dma == BX_NE2K_THIS s.page_stop << 8) {
|
|
BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.page_start << 8;
|
|
}
|
|
// keep s.remote_bytes from underflowing
|
|
if (BX_NE2K_THIS s.remote_bytes > 1)
|
|
BX_NE2K_THIS s.remote_bytes -= (BX_NE2K_THIS s.DCR.wdsize + 1);
|
|
else
|
|
BX_NE2K_THIS s.remote_bytes = 0;
|
|
|
|
// If all bytes have been written, signal remote-DMA complete
|
|
if (BX_NE2K_THIS s.remote_bytes == 0) {
|
|
BX_NE2K_THIS s.ISR.rdma_done = 1;
|
|
if (BX_NE2K_THIS s.IMR.rdma_inte) {
|
|
PIC_ActivateIRQ((unsigned int)s.base_irq);
|
|
//DEV_pic_raise_irq(BX_NE2K_THIS s.base_irq);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0xf: // Reset register
|
|
theNE2kDevice->reset(BX_RESET_SOFTWARE);
|
|
//retval=0x1;
|
|
break;
|
|
|
|
default:
|
|
BX_INFO("asic read invalid address %04x", (unsigned) offset);
|
|
break;
|
|
}
|
|
|
|
return (retval);
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::asic_write(uint32_t offset, uint32_t value, unsigned io_len)
|
|
{
|
|
BX_DEBUG("asic write addr=0x%02x, value=0x%04x", (unsigned) offset, (unsigned) value);
|
|
switch (offset) {
|
|
case 0x0: // Data register - see asic_read for a description
|
|
|
|
if ((io_len == 2) && (BX_NE2K_THIS s.DCR.wdsize == 0)) {
|
|
BX_PANIC(("dma write length 2 on byte mode operation"));
|
|
break;
|
|
}
|
|
|
|
if (BX_NE2K_THIS s.remote_bytes == 0)
|
|
BX_PANIC(("ne2K: dma write, byte count 0"));
|
|
|
|
chipmem_write(BX_NE2K_THIS s.remote_dma, value, io_len);
|
|
// is this right ??? asic_read uses DCR.wordsize
|
|
BX_NE2K_THIS s.remote_dma += io_len;
|
|
if (BX_NE2K_THIS s.remote_dma == BX_NE2K_THIS s.page_stop << 8) {
|
|
BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.page_start << 8;
|
|
}
|
|
|
|
BX_NE2K_THIS s.remote_bytes -= io_len;
|
|
if (BX_NE2K_THIS s.remote_bytes > BX_NE2K_MEMSIZ)
|
|
BX_NE2K_THIS s.remote_bytes = 0;
|
|
|
|
// If all bytes have been written, signal remote-DMA complete
|
|
if (BX_NE2K_THIS s.remote_bytes == 0) {
|
|
BX_NE2K_THIS s.ISR.rdma_done = 1;
|
|
if (BX_NE2K_THIS s.IMR.rdma_inte) {
|
|
PIC_ActivateIRQ((unsigned int)s.base_irq);
|
|
//DEV_pic_raise_irq(BX_NE2K_THIS s.base_irq);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 0xf: // Reset register
|
|
theNE2kDevice->reset(BX_RESET_SOFTWARE);
|
|
break;
|
|
|
|
default: // this is invalid, but happens under win95 device detection
|
|
BX_INFO("asic write invalid address %04x, ignoring", (unsigned) offset);
|
|
break ;
|
|
}
|
|
}
|
|
|
|
//
|
|
// page0_read/page0_write - These routines handle reads/writes to
|
|
// the 'zeroth' page of the DS8390 register file
|
|
//
|
|
uint32_t
|
|
bx_ne2k_c::page0_read(uint32_t offset, unsigned int io_len)
|
|
{
|
|
BX_DEBUG("page 0 read from port %04x, len=%u", (unsigned) offset,
|
|
(unsigned) io_len);
|
|
if (io_len > 1) {
|
|
BX_ERROR("bad length! page 0 read from port %04x, len=%u", (unsigned) offset,
|
|
(unsigned) io_len); /* encountered with win98 hardware probe */
|
|
return 0;
|
|
}
|
|
|
|
|
|
switch (offset) {
|
|
case 0x1: // CLDA0
|
|
return (BX_NE2K_THIS s.local_dma & 0xff);
|
|
break;
|
|
|
|
case 0x2: // CLDA1
|
|
return (unsigned int)(BX_NE2K_THIS s.local_dma >> 8u);
|
|
break;
|
|
|
|
case 0x3: // BNRY
|
|
return (BX_NE2K_THIS s.bound_ptr);
|
|
break;
|
|
|
|
case 0x4: // TSR
|
|
return
|
|
((unsigned int)(BX_NE2K_THIS s.TSR.ow_coll << 7u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TSR.cd_hbeat << 6u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TSR.fifo_ur << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TSR.no_carrier << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TSR.aborted << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TSR.collided << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TSR.tx_ok));
|
|
break;
|
|
|
|
case 0x5: // NCR
|
|
return (BX_NE2K_THIS s.num_coll);
|
|
break;
|
|
|
|
case 0x6: // FIFO
|
|
// reading FIFO is only valid in loopback mode
|
|
BX_ERROR(("reading FIFO not supported yet"));
|
|
return (BX_NE2K_THIS s.fifo);
|
|
break;
|
|
|
|
case 0x7: // ISR
|
|
return
|
|
((unsigned int)(BX_NE2K_THIS s.ISR.reset << 7u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.rdma_done << 6u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.cnt_oflow << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.overwrite << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.tx_err << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.rx_err << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.pkt_tx << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.pkt_rx));
|
|
break;
|
|
|
|
case 0x8: // CRDA0
|
|
return (BX_NE2K_THIS s.remote_dma & 0xff);
|
|
break;
|
|
|
|
case 0x9: // CRDA1
|
|
return (unsigned int)(BX_NE2K_THIS s.remote_dma >> 8u);
|
|
break;
|
|
|
|
case 0xa: // reserved
|
|
BX_INFO(("reserved read - page 0, 0xa"));
|
|
return (0xff);
|
|
break;
|
|
|
|
case 0xb: // reserved
|
|
BX_INFO(("reserved read - page 0, 0xb"));
|
|
return (0xff);
|
|
break;
|
|
|
|
case 0xc: // RSR
|
|
return
|
|
((unsigned int)(BX_NE2K_THIS s.RSR.deferred << 7u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RSR.rx_disabled << 6u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RSR.rx_mbit << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RSR.rx_missed << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RSR.fifo_or << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RSR.bad_falign << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RSR.bad_crc << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RSR.rx_ok));
|
|
break;
|
|
|
|
case 0xd: // CNTR0
|
|
return (BX_NE2K_THIS s.tallycnt_0);
|
|
break;
|
|
|
|
case 0xe: // CNTR1
|
|
return (BX_NE2K_THIS s.tallycnt_1);
|
|
break;
|
|
|
|
case 0xf: // CNTR2
|
|
return (BX_NE2K_THIS s.tallycnt_2);
|
|
break;
|
|
|
|
default:
|
|
BX_PANIC("page 0 offset %04x out of range", (unsigned) offset);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::page0_write(uint32_t offset, uint32_t value, unsigned io_len)
|
|
{
|
|
BX_DEBUG("page 0 write to port %04x, len=%u", (unsigned) offset,
|
|
(unsigned) io_len);
|
|
|
|
// It appears to be a common practice to use outw on page0 regs...
|
|
|
|
// break up outw into two outb's
|
|
if (io_len == 2) {
|
|
page0_write(offset, (value & 0xff), 1);
|
|
page0_write(offset + 1, ((value >> 8) & 0xff), 1);
|
|
return;
|
|
}
|
|
|
|
switch (offset) {
|
|
case 0x1: // PSTART
|
|
BX_NE2K_THIS s.page_start = value;
|
|
break;
|
|
|
|
case 0x2: // PSTOP
|
|
// BX_INFO(("Writing to PSTOP: %02x", value));
|
|
BX_NE2K_THIS s.page_stop = value;
|
|
break;
|
|
|
|
case 0x3: // BNRY
|
|
BX_NE2K_THIS s.bound_ptr = value;
|
|
break;
|
|
|
|
case 0x4: // TPSR
|
|
BX_NE2K_THIS s.tx_page_start = value;
|
|
break;
|
|
|
|
case 0x5: // TBCR0
|
|
// Clear out low byte and re-insert
|
|
BX_NE2K_THIS s.tx_bytes &= 0xff00;
|
|
BX_NE2K_THIS s.tx_bytes |= (value & 0xff);
|
|
break;
|
|
|
|
case 0x6: // TBCR1
|
|
// Clear out high byte and re-insert
|
|
BX_NE2K_THIS s.tx_bytes &= 0x00ff;
|
|
BX_NE2K_THIS s.tx_bytes |= ((value & 0xff) << 8);
|
|
break;
|
|
|
|
case 0x7: // ISR
|
|
value &= 0x7f; // clear RST bit - status-only bit
|
|
// All other values are cleared iff the ISR bit is 1
|
|
BX_NE2K_THIS s.ISR.pkt_rx &= ~((bx_bool)((value & 0x01) == 0x01));
|
|
BX_NE2K_THIS s.ISR.pkt_tx &= ~((bx_bool)((value & 0x02) == 0x02));
|
|
BX_NE2K_THIS s.ISR.rx_err &= ~((bx_bool)((value & 0x04) == 0x04));
|
|
BX_NE2K_THIS s.ISR.tx_err &= ~((bx_bool)((value & 0x08) == 0x08));
|
|
BX_NE2K_THIS s.ISR.overwrite &= ~((bx_bool)((value & 0x10) == 0x10));
|
|
BX_NE2K_THIS s.ISR.cnt_oflow &= ~((bx_bool)((value & 0x20) == 0x20));
|
|
BX_NE2K_THIS s.ISR.rdma_done &= ~((bx_bool)((value & 0x40) == 0x40));
|
|
value = ((unsigned int)(BX_NE2K_THIS s.ISR.rdma_done << 6u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.cnt_oflow << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.overwrite << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.tx_err << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.rx_err << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.pkt_tx << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.ISR.pkt_rx));
|
|
value &= ((unsigned int)(BX_NE2K_THIS s.IMR.rdma_inte << 6u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.cofl_inte << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.overw_inte << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.txerr_inte << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.rxerr_inte << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.tx_inte << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.rx_inte));
|
|
if (value == 0)
|
|
PIC_DeActivateIRQ((unsigned int)s.base_irq);
|
|
//DEV_pic_lower_irq(BX_NE2K_THIS s.base_irq);
|
|
break;
|
|
|
|
case 0x8: // RSAR0
|
|
// Clear out low byte and re-insert
|
|
BX_NE2K_THIS s.remote_start &= 0xff00u;
|
|
BX_NE2K_THIS s.remote_start |= (value & 0xffu);
|
|
BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.remote_start;
|
|
break;
|
|
|
|
case 0x9: // RSAR1
|
|
// Clear out high byte and re-insert
|
|
BX_NE2K_THIS s.remote_start &= 0x00ffu;
|
|
BX_NE2K_THIS s.remote_start |= ((value & 0xffu) << 8u);
|
|
BX_NE2K_THIS s.remote_dma = BX_NE2K_THIS s.remote_start;
|
|
break;
|
|
|
|
case 0xa: // RBCR0
|
|
// Clear out low byte and re-insert
|
|
BX_NE2K_THIS s.remote_bytes &= 0xff00u;
|
|
BX_NE2K_THIS s.remote_bytes |= (value & 0xffu);
|
|
break;
|
|
|
|
case 0xb: // RBCR1
|
|
// Clear out high byte and re-insert
|
|
BX_NE2K_THIS s.remote_bytes &= 0x00ffu;
|
|
BX_NE2K_THIS s.remote_bytes |= ((value & 0xffu) << 8u);
|
|
break;
|
|
|
|
case 0xc: // RCR
|
|
// Check if the reserved bits are set
|
|
if (value & 0xc0)
|
|
BX_INFO(("RCR write, reserved bits set"));
|
|
|
|
// Set all other bit-fields
|
|
BX_NE2K_THIS s.RCR.errors_ok = ((value & 0x01u) == 0x01u);
|
|
BX_NE2K_THIS s.RCR.runts_ok = ((value & 0x02u) == 0x02u);
|
|
BX_NE2K_THIS s.RCR.broadcast = ((value & 0x04u) == 0x04u);
|
|
BX_NE2K_THIS s.RCR.multicast = ((value & 0x08u) == 0x08u);
|
|
BX_NE2K_THIS s.RCR.promisc = ((value & 0x10u) == 0x10u);
|
|
BX_NE2K_THIS s.RCR.monitor = ((value & 0x20u) == 0x20u);
|
|
|
|
// Monitor bit is a little suspicious...
|
|
if (value & 0x20)
|
|
BX_INFO(("RCR write, monitor bit set!"));
|
|
break;
|
|
|
|
case 0xd: // TCR
|
|
// Check reserved bits
|
|
if (value & 0xe0)
|
|
BX_ERROR(("TCR write, reserved bits set"));
|
|
|
|
// Test loop mode (not supported)
|
|
if (value & 0x06) {
|
|
BX_NE2K_THIS s.TCR.loop_cntl = (value & 0x6) >> 1;
|
|
BX_INFO("TCR write, loop mode %d not supported", BX_NE2K_THIS s.TCR.loop_cntl);
|
|
} else {
|
|
BX_NE2K_THIS s.TCR.loop_cntl = 0;
|
|
}
|
|
|
|
// Inhibit-CRC: the emulated NIC never generates real Ethernet CRCs
|
|
// (the host stack / pcap frames outgoing packets), so this bit is a
|
|
// no-op for us. Record it instead of aborting -- drivers set it during
|
|
// the internal loopback self-test at init.
|
|
BX_NE2K_THIS s.TCR.crc_disable = ((value & 0x01) == 0x01);
|
|
|
|
// Auto-transmit disable (ATD, bit 3): also used by the loopback
|
|
// self-test. We don't gate the TX path on it, so just record it.
|
|
BX_NE2K_THIS s.TCR.ext_stoptx = ((value & 0x08) == 0x08);
|
|
|
|
// Allow collision-offset (bit 4) to be set, although not used
|
|
BX_NE2K_THIS s.TCR.coll_prio = ((value & 0x10) == 0x10);
|
|
break;
|
|
|
|
case 0xe: // DCR
|
|
// the loopback mode is not supported yet
|
|
if (!(value & 0x08)) {
|
|
BX_ERROR(("DCR write, loopback mode selected"));
|
|
}
|
|
// It is questionable to set longaddr and auto_rx, since they
|
|
// aren't supported on the ne2000. Print a warning and continue
|
|
if (value & 0x04)
|
|
BX_INFO(("DCR write - LAS set ???"));
|
|
if (value & 0x10)
|
|
BX_INFO(("DCR write - AR set ???"));
|
|
|
|
// Set other values.
|
|
BX_NE2K_THIS s.DCR.wdsize = ((value & 0x01) == 0x01);
|
|
BX_NE2K_THIS s.DCR.endian = ((value & 0x02) == 0x02);
|
|
BX_NE2K_THIS s.DCR.longaddr = ((value & 0x04) == 0x04); // illegal ?
|
|
BX_NE2K_THIS s.DCR.loop = ((value & 0x08) == 0x08);
|
|
BX_NE2K_THIS s.DCR.auto_rx = ((value & 0x10) == 0x10); // also illegal ?
|
|
BX_NE2K_THIS s.DCR.fifo_size = (value & 0x50) >> 5;
|
|
break;
|
|
|
|
case 0xf: // IMR
|
|
// Check for reserved bit
|
|
if (value & 0x80)
|
|
BX_ERROR(("IMR write, reserved bit set"));
|
|
|
|
// Set other values
|
|
BX_NE2K_THIS s.IMR.rx_inte = ((value & 0x01) == 0x01);
|
|
BX_NE2K_THIS s.IMR.tx_inte = ((value & 0x02) == 0x02);
|
|
BX_NE2K_THIS s.IMR.rxerr_inte = ((value & 0x04) == 0x04);
|
|
BX_NE2K_THIS s.IMR.txerr_inte = ((value & 0x08) == 0x08);
|
|
BX_NE2K_THIS s.IMR.overw_inte = ((value & 0x10) == 0x10);
|
|
BX_NE2K_THIS s.IMR.cofl_inte = ((value & 0x20) == 0x20);
|
|
BX_NE2K_THIS s.IMR.rdma_inte = ((value & 0x40) == 0x40);
|
|
if(BX_NE2K_THIS s.ISR.pkt_tx && BX_NE2K_THIS s.IMR.tx_inte) {
|
|
LOG_MSG("tx irq retrigger");
|
|
PIC_ActivateIRQ((unsigned int)s.base_irq);
|
|
}
|
|
break;
|
|
default:
|
|
BX_PANIC("page 0 write, bad offset %0x", offset);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// page1_read/page1_write - These routines handle reads/writes to
|
|
// the first page of the DS8390 register file
|
|
//
|
|
uint32_t
|
|
bx_ne2k_c::page1_read(uint32_t offset, unsigned int io_len)
|
|
{
|
|
BX_DEBUG("page 1 read from port %04x, len=%u", (unsigned) offset,
|
|
(unsigned) io_len);
|
|
if (io_len > 1)
|
|
BX_PANIC("bad length! page 1 read from port %04x, len=%u", (unsigned) offset,
|
|
(unsigned) io_len);
|
|
|
|
switch (offset) {
|
|
case 0x1: // PAR0-5
|
|
case 0x2:
|
|
case 0x3:
|
|
case 0x4:
|
|
case 0x5:
|
|
case 0x6:
|
|
return (BX_NE2K_THIS s.physaddr[offset - 1]);
|
|
break;
|
|
|
|
case 0x7: // CURR
|
|
BX_DEBUG("returning current page: %02x", (BX_NE2K_THIS s.curr_page));
|
|
return (BX_NE2K_THIS s.curr_page);
|
|
|
|
case 0x8: // MAR0-7
|
|
case 0x9:
|
|
case 0xa:
|
|
case 0xb:
|
|
case 0xc:
|
|
case 0xd:
|
|
case 0xe:
|
|
case 0xf:
|
|
return (BX_NE2K_THIS s.mchash[offset - 8]);
|
|
break;
|
|
|
|
default:
|
|
BX_PANIC("page 1 r offset %04x out of range", (unsigned) offset);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::page1_write(uint32_t offset, uint32_t value, unsigned io_len)
|
|
{
|
|
(void)io_len;//UNUSED
|
|
BX_DEBUG("page 1 w offset %04x", (unsigned) offset);
|
|
switch (offset) {
|
|
case 0x1: // PAR0-5
|
|
case 0x2:
|
|
case 0x3:
|
|
case 0x4:
|
|
case 0x5:
|
|
case 0x6:
|
|
BX_NE2K_THIS s.physaddr[offset - 1] = value;
|
|
break;
|
|
|
|
case 0x7: // CURR
|
|
BX_NE2K_THIS s.curr_page = value;
|
|
break;
|
|
|
|
case 0x8: // MAR0-7
|
|
case 0x9:
|
|
case 0xa:
|
|
case 0xb:
|
|
case 0xc:
|
|
case 0xd:
|
|
case 0xe:
|
|
case 0xf:
|
|
BX_NE2K_THIS s.mchash[offset - 8] = value;
|
|
break;
|
|
|
|
default:
|
|
BX_PANIC("page 1 w offset %04x out of range", (unsigned) offset);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// page2_read/page2_write - These routines handle reads/writes to
|
|
// the second page of the DS8390 register file
|
|
//
|
|
uint32_t
|
|
bx_ne2k_c::page2_read(uint32_t offset, unsigned int io_len)
|
|
{
|
|
BX_DEBUG("page 2 read from port %04x, len=%u", (unsigned) offset, (unsigned) io_len);
|
|
|
|
if (io_len > 1)
|
|
BX_PANIC("bad length! page 2 read from port %04x, len=%u", (unsigned) offset, (unsigned) io_len);
|
|
|
|
switch (offset) {
|
|
case 0x1: // PSTART
|
|
return (BX_NE2K_THIS s.page_start);
|
|
break;
|
|
|
|
case 0x2: // PSTOP
|
|
return (BX_NE2K_THIS s.page_stop);
|
|
break;
|
|
|
|
case 0x3: // Remote Next-packet pointer
|
|
return (BX_NE2K_THIS s.rempkt_ptr);
|
|
break;
|
|
|
|
case 0x4: // TPSR
|
|
return (BX_NE2K_THIS s.tx_page_start);
|
|
break;
|
|
|
|
case 0x5: // Local Next-packet pointer
|
|
return (BX_NE2K_THIS s.localpkt_ptr);
|
|
break;
|
|
|
|
case 0x6: // Address counter (upper)
|
|
return (unsigned int)(BX_NE2K_THIS s.address_cnt >> 8u);
|
|
break;
|
|
|
|
case 0x7: // Address counter (lower)
|
|
return (unsigned int)(BX_NE2K_THIS s.address_cnt & 0xff);
|
|
break;
|
|
|
|
case 0x8: // Reserved
|
|
case 0x9:
|
|
case 0xa:
|
|
case 0xb:
|
|
BX_ERROR("reserved read - page 2, 0x%02x", (unsigned) offset);
|
|
return (0xff);
|
|
break;
|
|
|
|
case 0xc: // RCR
|
|
return
|
|
((unsigned int)(BX_NE2K_THIS s.RCR.monitor << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RCR.promisc << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RCR.multicast << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RCR.broadcast << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RCR.runts_ok << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.RCR.errors_ok));
|
|
break;
|
|
|
|
case 0xd: // TCR
|
|
return
|
|
((unsigned int)(BX_NE2K_THIS s.TCR.coll_prio << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TCR.ext_stoptx << 3u) |
|
|
((unsigned int)(BX_NE2K_THIS s.TCR.loop_cntl & 0x3u) << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.TCR.crc_disable));
|
|
break;
|
|
|
|
case 0xe: // DCR
|
|
return
|
|
(((unsigned int)(BX_NE2K_THIS s.DCR.fifo_size & 0x3) << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.DCR.auto_rx << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.DCR.loop << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.DCR.longaddr << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.DCR.endian << 1u) |
|
|
(unsigned int)(BX_NE2K_THIS s.DCR.wdsize));
|
|
break;
|
|
|
|
case 0xf: // IMR
|
|
return
|
|
((unsigned int)(BX_NE2K_THIS s.IMR.rdma_inte << 6u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.cofl_inte << 5u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.overw_inte << 4u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.txerr_inte << 3u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.rxerr_inte << 2u) |
|
|
(unsigned int)(BX_NE2K_THIS s.IMR.tx_inte << 1u) |
|
|
(unsigned int) (BX_NE2K_THIS s.IMR.rx_inte));
|
|
break;
|
|
|
|
default:
|
|
BX_PANIC("page 2 offset %04x out of range", (unsigned) offset);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::page2_write(uint32_t offset, uint32_t value, unsigned io_len)
|
|
{
|
|
(void)io_len;//UNUSED
|
|
// Maybe all writes here should be BX_PANIC()'d, since they
|
|
// affect internal operation, but let them through for now
|
|
// and print a warning.
|
|
if (offset != 0)
|
|
BX_ERROR(("page 2 write ?"));
|
|
|
|
switch (offset) {
|
|
case 0x1: // CLDA0
|
|
// Clear out low byte and re-insert
|
|
BX_NE2K_THIS s.local_dma &= 0xff00;
|
|
BX_NE2K_THIS s.local_dma |= (value & 0xff);
|
|
break;
|
|
|
|
case 0x2: // CLDA1
|
|
// Clear out high byte and re-insert
|
|
BX_NE2K_THIS s.local_dma &= 0x00ff;
|
|
BX_NE2K_THIS s.local_dma |= ((value & 0xff) << 8u);
|
|
break;
|
|
|
|
case 0x3: // Remote Next-pkt pointer
|
|
BX_NE2K_THIS s.rempkt_ptr = value;
|
|
break;
|
|
|
|
case 0x4:
|
|
BX_PANIC(("page 2 write to reserved offset 4"));
|
|
break;
|
|
|
|
case 0x5: // Local Next-packet pointer
|
|
BX_NE2K_THIS s.localpkt_ptr = value;
|
|
break;
|
|
|
|
case 0x6: // Address counter (upper)
|
|
// Clear out high byte and re-insert
|
|
BX_NE2K_THIS s.address_cnt &= 0x00ff;
|
|
BX_NE2K_THIS s.address_cnt |= ((value & 0xff) << 8);
|
|
break;
|
|
|
|
case 0x7: // Address counter (lower)
|
|
// Clear out low byte and re-insert
|
|
BX_NE2K_THIS s.address_cnt &= 0xff00;
|
|
BX_NE2K_THIS s.address_cnt |= (value & 0xff);
|
|
break;
|
|
|
|
case 0x8:
|
|
case 0x9:
|
|
case 0xa:
|
|
case 0xb:
|
|
case 0xc:
|
|
case 0xd:
|
|
case 0xe:
|
|
case 0xf:
|
|
BX_PANIC("page 2 write to reserved offset %0x", offset);
|
|
break;
|
|
|
|
default:
|
|
BX_PANIC("page 2 write, illegal offset %0x", offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// page3_read/page3_write - writes to this page are illegal
|
|
//
|
|
uint32_t
|
|
bx_ne2k_c::page3_read(uint32_t offset, unsigned int io_len)
|
|
{
|
|
(void)offset;//UNUSED
|
|
(void)io_len;//UNUSED
|
|
BX_PANIC(("page 3 read attempted"));
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::page3_write(uint32_t offset, uint32_t value, unsigned io_len)
|
|
{
|
|
(void)value;//UNUSED
|
|
(void)offset;//UNUSED
|
|
(void)io_len;//UNUSED
|
|
BX_PANIC(("page 3 write attempted"));
|
|
}
|
|
|
|
//
|
|
// tx_timer_handler/tx_timer
|
|
//
|
|
void
|
|
bx_ne2k_c::tx_timer_handler(void *this_ptr)
|
|
{
|
|
bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr;
|
|
|
|
class_ptr->tx_timer();
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::tx_timer(void)
|
|
{
|
|
BX_DEBUG(("tx_timer"));
|
|
BX_NE2K_THIS s.TSR.tx_ok = 1;
|
|
// Generate an interrupt if not masked and not one in progress
|
|
if (BX_NE2K_THIS s.IMR.tx_inte && !BX_NE2K_THIS s.ISR.pkt_tx) {
|
|
//LOG_MSG("tx complete interrupt");
|
|
PIC_ActivateIRQ((unsigned int)s.base_irq);
|
|
//DEV_pic_raise_irq(BX_NE2K_THIS s.base_irq);
|
|
} //else LOG_MSG("no tx complete interrupt");
|
|
BX_NE2K_THIS s.ISR.pkt_tx = 1;
|
|
BX_NE2K_THIS s.tx_timer_active = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// read_handler/read - i/o 'catcher' function called from BOCHS
|
|
// mainline when the CPU attempts a read in the i/o space registered
|
|
// by this ne2000 instance
|
|
//
|
|
uint32_t bx_ne2k_c::read_handler(void *this_ptr, uint32_t address, unsigned io_len)
|
|
{
|
|
#if !BX_USE_NE2K_SMF
|
|
bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr;
|
|
|
|
return( class_ptr->read(address, io_len) );
|
|
}
|
|
|
|
uint32_t bx_ne2k_c::read(uint32_t address, unsigned io_len)
|
|
{
|
|
#else
|
|
UNUSED(this_ptr);
|
|
#endif // !BX_USE_NE2K_SMF
|
|
BX_DEBUG("read addr %x, len %d", address, io_len);
|
|
uint32_t retval = 0;
|
|
unsigned int offset = (unsigned int)address - (unsigned int)(BX_NE2K_THIS s.base_address);
|
|
|
|
if (offset >= 0x10) {
|
|
retval = asic_read(offset - 0x10, io_len);
|
|
} else if (offset == 0x00) {
|
|
retval = read_cr();
|
|
} else {
|
|
switch (BX_NE2K_THIS s.CR.pgsel) {
|
|
case 0x00:
|
|
retval = page0_read(offset, io_len);
|
|
break;
|
|
|
|
case 0x01:
|
|
retval = page1_read(offset, io_len);
|
|
break;
|
|
|
|
case 0x02:
|
|
retval = page2_read(offset, io_len);
|
|
break;
|
|
|
|
case 0x03:
|
|
retval = page3_read(offset, io_len);
|
|
break;
|
|
|
|
default:
|
|
BX_PANIC("ne2K: unknown value of pgsel in read - %d",
|
|
BX_NE2K_THIS s.CR.pgsel);
|
|
}
|
|
}
|
|
|
|
return (retval);
|
|
}
|
|
|
|
//
|
|
// write_handler/write - i/o 'catcher' function called from BOCHS
|
|
// mainline when the CPU attempts a write in the i/o space registered
|
|
// by this ne2000 instance
|
|
//
|
|
void
|
|
bx_ne2k_c::write_handler(void *this_ptr, uint32_t address, uint32_t value,
|
|
unsigned io_len)
|
|
{
|
|
#if !BX_USE_NE2K_SMF
|
|
bx_ne2k_c *class_ptr = (bx_ne2k_c *) this_ptr;
|
|
|
|
class_ptr->write(address, value, io_len);
|
|
}
|
|
|
|
void
|
|
bx_ne2k_c::write(uint32_t address, uint32_t value, unsigned io_len)
|
|
{
|
|
#else
|
|
UNUSED(this_ptr);
|
|
#endif // !BX_USE_NE2K_SMF
|
|
BX_DEBUG("write with length %d", io_len);
|
|
unsigned int offset = (unsigned int)address - (unsigned int)(BX_NE2K_THIS s.base_address);
|
|
|
|
//
|
|
// The high 16 bytes of i/o space are for the ne2000 asic -
|
|
// the low 16 bytes are for the DS8390, with the current
|
|
// page being selected by the PS0,PS1 registers in the
|
|
// command register
|
|
//
|
|
if (offset >= 0x10) {
|
|
asic_write(offset - 0x10, value, io_len);
|
|
} else if (offset == 0x00) {
|
|
write_cr(value);
|
|
} else {
|
|
switch (BX_NE2K_THIS s.CR.pgsel) {
|
|
case 0x00:
|
|
page0_write(offset, value, io_len);
|
|
break;
|
|
|
|
case 0x01:
|
|
page1_write(offset, value, io_len);
|
|
break;
|
|
|
|
case 0x02:
|
|
page2_write(offset, value, io_len);
|
|
break;
|
|
|
|
case 0x03:
|
|
page3_write(offset, value, io_len);
|
|
break;
|
|
|
|
default:
|
|
BX_PANIC("ne2K: unknown value of pgsel in write - %d",
|
|
BX_NE2K_THIS s.CR.pgsel);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* mcast_index() - return the 6-bit index into the multicast
|
|
* table. Stolen unashamedly from FreeBSD's if_ed.c
|
|
*/
|
|
unsigned
|
|
bx_ne2k_c::mcast_index(const void *dst)
|
|
{
|
|
#define POLYNOMIAL 0x04c11db6
|
|
unsigned long crc = 0xffffffffL;
|
|
int carry, i, j;
|
|
unsigned char b;
|
|
unsigned char *ep = (unsigned char *) dst;
|
|
|
|
for (i = 6; --i >= 0;) {
|
|
b = *ep++;
|
|
for (j = 8; --j >= 0;) {
|
|
carry = ((crc & 0x80000000L) ? 1 : 0) ^ (b & 0x01);
|
|
crc <<= 1;
|
|
b >>= 1;
|
|
if (carry)
|
|
crc = ((crc ^ POLYNOMIAL) | (unsigned int)carry);
|
|
}
|
|
}
|
|
|
|
return (uint32_t)((crc & 0xfffffffful) >> 26ul); /* WARNING: Caller directly uses our 6-bit return as index. If not truncated, will cause a segfault */
|
|
#undef POLYNOMIAL
|
|
}
|
|
|
|
/*
|
|
* Callback from the eth system driver when a frame has arrived
|
|
*/
|
|
/*
|
|
void
|
|
bx_ne2k_c::rx_handler(void *arg, const void *buf, unsigned len)
|
|
{
|
|
// BX_DEBUG(("rx_handler with length %d", len));
|
|
bx_ne2k_c *class_ptr = (bx_ne2k_c *) arg;
|
|
if(
|
|
class_ptr->rx_frame(buf, len);
|
|
}
|
|
*/
|
|
/*
|
|
* rx_frame() - called by the platform-specific code when an
|
|
* ethernet frame has been received. The destination address
|
|
* is tested to see if it should be accepted, and if the
|
|
* rx ring has enough room, it is copied into it and
|
|
* the receive process is updated
|
|
*/
|
|
void
|
|
bx_ne2k_c::rx_frame(const void *buf, unsigned io_len)
|
|
{
|
|
int pages;
|
|
int avail;
|
|
unsigned idx;
|
|
// int wrapped;
|
|
int nextpage;
|
|
unsigned char pkthdr[4];
|
|
unsigned char *pktbuf = (unsigned char *) buf;
|
|
unsigned char *startptr;
|
|
static unsigned char bcast_addr[6] = {0xff,0xff,0xff,0xff,0xff,0xff};
|
|
|
|
if (CaptureState & CAPTURE_NETWORK) Capture_WritePacket(false/*send*/,pktbuf,io_len);
|
|
|
|
if(io_len != 60) {
|
|
BX_DEBUG("rx_frame with length %d", io_len);
|
|
}
|
|
|
|
//LOG_MSG("stop=%d, pagestart=%x, dcr_loop=%x, tcr_loopcntl=%x",
|
|
// BX_NE2K_THIS s.CR.stop, BX_NE2K_THIS s.page_start,
|
|
// BX_NE2K_THIS s.DCR.loop, BX_NE2K_THIS s.TCR.loop_cntl);
|
|
if ((BX_NE2K_THIS s.CR.stop != 0) ||
|
|
(BX_NE2K_THIS s.page_start == 0) /*||
|
|
((BX_NE2K_THIS s.DCR.loop == 0) &&
|
|
(BX_NE2K_THIS s.TCR.loop_cntl != 0))*/) {
|
|
return;
|
|
}
|
|
|
|
// Add the pkt header + CRC to the length, and work
|
|
// out how many 256-byte pages the frame would occupy
|
|
pages = (int)((io_len + 4u + 4u + 255u)/256u);
|
|
|
|
if (BX_NE2K_THIS s.curr_page < BX_NE2K_THIS s.bound_ptr) {
|
|
avail = BX_NE2K_THIS s.bound_ptr - BX_NE2K_THIS s.curr_page;
|
|
} else {
|
|
avail = (BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.page_start) -
|
|
(BX_NE2K_THIS s.curr_page - BX_NE2K_THIS s.bound_ptr);
|
|
// wrapped = 1;
|
|
}
|
|
|
|
// Avoid getting into a buffer overflow condition by not attempting
|
|
// to do partial receives. The emulation to handle this condition
|
|
// seems particularly painful.
|
|
if ((avail < pages)
|
|
#if BX_NE2K_NEVER_FULL_RING
|
|
|| (avail == pages)
|
|
#endif
|
|
) {
|
|
BX_DEBUG("no space");
|
|
return;
|
|
}
|
|
|
|
if ((io_len < 40/*60*/) && !BX_NE2K_THIS s.RCR.runts_ok) {
|
|
BX_DEBUG("rejected small packet, length %d", io_len);
|
|
return;
|
|
}
|
|
// some computers don't care...
|
|
if (io_len < 60) io_len=60;
|
|
|
|
// Do address filtering if not in promiscuous mode
|
|
if (! BX_NE2K_THIS s.RCR.promisc) {
|
|
if (!memcmp(buf, bcast_addr, 6)) {
|
|
if (!BX_NE2K_THIS s.RCR.broadcast) {
|
|
return;
|
|
}
|
|
} else if (pktbuf[0] & 0x01) {
|
|
if (! BX_NE2K_THIS s.RCR.multicast) {
|
|
return;
|
|
}
|
|
idx = mcast_index(buf);
|
|
if (!(BX_NE2K_THIS s.mchash[idx >> 3] & (1 << (idx & 0x7)))) {
|
|
return;
|
|
}
|
|
} else if (0 != memcmp(buf, BX_NE2K_THIS s.physaddr, 6)) {
|
|
static unsigned drop_n = 0;
|
|
if (drop_n < 12) { drop_n++;
|
|
LOG_MSG("NE2000 rx DROP unicast dst=%02x:%02x:%02x:%02x:%02x:%02x != PAR=%02x:%02x:%02x:%02x:%02x:%02x",
|
|
pktbuf[0],pktbuf[1],pktbuf[2],pktbuf[3],pktbuf[4],pktbuf[5],
|
|
BX_NE2K_THIS s.physaddr[0],BX_NE2K_THIS s.physaddr[1],BX_NE2K_THIS s.physaddr[2],
|
|
BX_NE2K_THIS s.physaddr[3],BX_NE2K_THIS s.physaddr[4],BX_NE2K_THIS s.physaddr[5]);
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
static unsigned uni_n = 0;
|
|
if (uni_n < 12) { uni_n++;
|
|
LOG_MSG("NE2000 rx ACCEPT unicast len=%d dst=%02x:%02x:%02x:%02x:%02x:%02x",
|
|
(int)io_len, pktbuf[0],pktbuf[1],pktbuf[2],pktbuf[3],pktbuf[4],pktbuf[5]);
|
|
}
|
|
}
|
|
} else {
|
|
BX_DEBUG(("rx_frame promiscuous receive"));
|
|
}
|
|
|
|
BX_INFO("rx_frame %d to %x:%x:%x:%x:%x:%x from %x:%x:%x:%x:%x:%x",
|
|
io_len,
|
|
pktbuf[0], pktbuf[1], pktbuf[2], pktbuf[3], pktbuf[4], pktbuf[5],
|
|
pktbuf[6], pktbuf[7], pktbuf[8], pktbuf[9], pktbuf[10], pktbuf[11]);
|
|
|
|
nextpage = BX_NE2K_THIS s.curr_page + pages;
|
|
if (nextpage >= BX_NE2K_THIS s.page_stop) {
|
|
nextpage -= BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.page_start;
|
|
}
|
|
|
|
// Setup packet header
|
|
pkthdr[0] = 0; // rx status - old behavior
|
|
pkthdr[0] = 1; // Probably better to set it all the time
|
|
// rather than set it to 0, which is clearly wrong.
|
|
if (pktbuf[0] & 0x01) {
|
|
pkthdr[0] |= 0x20; // rx status += multicast packet
|
|
}
|
|
pkthdr[1] = nextpage; // ptr to next packet
|
|
pkthdr[2] = (io_len + 4) & 0xff; // length-low
|
|
pkthdr[3] = (io_len + 4) >> 8; // length-hi
|
|
|
|
// copy into buffer, update curpage, and signal interrupt if config'd
|
|
startptr = & BX_NE2K_THIS s.mem[BX_NE2K_THIS s.curr_page * 256 -
|
|
BX_NE2K_MEMSTART];
|
|
if ((nextpage > BX_NE2K_THIS s.curr_page) ||
|
|
((BX_NE2K_THIS s.curr_page + pages) == BX_NE2K_THIS s.page_stop)) {
|
|
memcpy(startptr, pkthdr, 4);
|
|
memcpy(startptr + 4, buf, io_len);
|
|
BX_NE2K_THIS s.curr_page = nextpage;
|
|
} else {
|
|
unsigned int endbytes = (unsigned int)(BX_NE2K_THIS s.page_stop - BX_NE2K_THIS s.curr_page)
|
|
* 256u;
|
|
memcpy(startptr, pkthdr, 4);
|
|
memcpy(startptr + 4, buf, (size_t)(endbytes - 4u));
|
|
startptr = & BX_NE2K_THIS s.mem[BX_NE2K_THIS s.page_start * 256u -
|
|
BX_NE2K_MEMSTART];
|
|
memcpy(startptr, (void *)(pktbuf + endbytes - 4u),
|
|
(size_t)(io_len - endbytes + 8u));
|
|
BX_NE2K_THIS s.curr_page = nextpage;
|
|
}
|
|
|
|
BX_NE2K_THIS s.RSR.rx_ok = 1;
|
|
if (pktbuf[0] & 0x80) {
|
|
BX_NE2K_THIS s.RSR.rx_mbit = 1;
|
|
}
|
|
|
|
BX_NE2K_THIS s.ISR.pkt_rx = 1;
|
|
|
|
if (BX_NE2K_THIS s.IMR.rx_inte) {
|
|
//LOG_MSG("packet rx interrupt");
|
|
PIC_ActivateIRQ((unsigned int)s.base_irq);
|
|
//DEV_pic_raise_irq(BX_NE2K_THIS s.base_irq);
|
|
} //else LOG_MSG("no packet rx interrupt");
|
|
|
|
}
|
|
|
|
//uint8_t macaddr[6] = { 0xAC, 0xDE, 0x48, 0x8E, 0x89, 0x19 };
|
|
|
|
Bitu dosbox_read(Bitu port, Bitu len) {
|
|
Bitu retval = theNE2kDevice->read((uint32_t)port,(unsigned int)len);
|
|
//LOG_MSG("ne2k rd port %x val %4x len %d page %d, CS:IP %8x:%8x",
|
|
// port, retval, len, theNE2kDevice->s.CR.pgsel,SegValue(cs),reg_eip);
|
|
return retval;
|
|
}
|
|
void dosbox_write(Bitu port, Bitu val, Bitu len) {
|
|
//LOG_MSG("ne2k wr port %x val %4x len %d page %d, CS:IP %8x:%8x",
|
|
// port, val, len,theNE2kDevice->s.CR.pgsel,SegValue(cs),reg_eip);
|
|
theNE2kDevice->write((uint32_t)port, (uint32_t)val, (unsigned int)len);
|
|
}
|
|
|
|
void bx_ne2k_c::init()
|
|
{
|
|
//BX_DEBUG(("Init $Id: ne2k.cc,v 1.56.2.1 2004/02/02 22:37:22 cbothamy Exp $"));
|
|
|
|
// Read in values from config file
|
|
//BX_NE2K_THIS s.base_address = 0x300;
|
|
//BX_NE2K_THIS s.base_irq = 3;
|
|
/*
|
|
if (BX_NE2K_THIS s.tx_timer_index == BX_NULL_TIMER_HANDLE) {
|
|
BX_NE2K_THIS s.tx_timer_index =
|
|
bx_pc_system.register_timer(this, tx_timer_handler, 0,
|
|
0,0, "ne2k"); // one-shot, inactive
|
|
}*/
|
|
// Register the IRQ and i/o port addresses
|
|
//DEV_register_irq(BX_NE2K_THIS s.base_irq, "NE2000 ethernet NIC");
|
|
|
|
//DEV_register_ioread_handler(this, read_handler, addr, "ne2000 NIC", 3);
|
|
//DEV_register_iowrite_handler(this, write_handler, addr, "ne2000 NIC", 3);
|
|
|
|
|
|
BX_INFO("port 0x%x/32 irq %d mac %02x:%02x:%02x:%02x:%02x:%02x",
|
|
(unsigned int)(BX_NE2K_THIS s.base_address),
|
|
(int)(BX_NE2K_THIS s.base_irq),
|
|
BX_NE2K_THIS s.physaddr[0],
|
|
BX_NE2K_THIS s.physaddr[1],
|
|
BX_NE2K_THIS s.physaddr[2],
|
|
BX_NE2K_THIS s.physaddr[3],
|
|
BX_NE2K_THIS s.physaddr[4],
|
|
BX_NE2K_THIS s.physaddr[5]);
|
|
|
|
// Initialise the mac address area by doubling the physical address
|
|
BX_NE2K_THIS s.macaddr[0] = BX_NE2K_THIS s.physaddr[0];
|
|
BX_NE2K_THIS s.macaddr[1] = BX_NE2K_THIS s.physaddr[0];
|
|
BX_NE2K_THIS s.macaddr[2] = BX_NE2K_THIS s.physaddr[1];
|
|
BX_NE2K_THIS s.macaddr[3] = BX_NE2K_THIS s.physaddr[1];
|
|
BX_NE2K_THIS s.macaddr[4] = BX_NE2K_THIS s.physaddr[2];
|
|
BX_NE2K_THIS s.macaddr[5] = BX_NE2K_THIS s.physaddr[2];
|
|
BX_NE2K_THIS s.macaddr[6] = BX_NE2K_THIS s.physaddr[3];
|
|
BX_NE2K_THIS s.macaddr[7] = BX_NE2K_THIS s.physaddr[3];
|
|
BX_NE2K_THIS s.macaddr[8] = BX_NE2K_THIS s.physaddr[4];
|
|
BX_NE2K_THIS s.macaddr[9] = BX_NE2K_THIS s.physaddr[4];
|
|
BX_NE2K_THIS s.macaddr[10] = BX_NE2K_THIS s.physaddr[5];
|
|
BX_NE2K_THIS s.macaddr[11] = BX_NE2K_THIS s.physaddr[5];
|
|
|
|
// ne2k signature
|
|
for (Bitu i = 12; i < 32; i++)
|
|
BX_NE2K_THIS s.macaddr[i] = 0x57;
|
|
|
|
// Bring the register state into power-up state
|
|
reset(BX_RESET_HARDWARE);
|
|
}
|
|
|
|
static void NE2000_TX_Event(Bitu val) {
|
|
(void)val;//UNUSED
|
|
theNE2kDevice->tx_timer();
|
|
}
|
|
|
|
static void NE2000_Poller(void) {
|
|
ethernet->GetPackets([](const uint8_t* packet, int len) {
|
|
//LOG_MSG("NE2000: Received %d bytes", header->len);
|
|
|
|
// don't receive in loopback modes
|
|
if((theNE2kDevice->s.DCR.loop == 0) || (theNE2kDevice->s.TCR.loop_cntl != 0))
|
|
return;
|
|
|
|
#if C_IPX
|
|
// If NE2000 IPX redirection is enabled, block incoming IPX packets
|
|
if (ne2k_ipx_redirect && is_IPX_ethernet_frame(packet,len,NULL,NULL,NULL))
|
|
return;
|
|
#endif
|
|
|
|
theNE2kDevice->rx_frame(packet, len);
|
|
});
|
|
}
|
|
|
|
#if C_IPX
|
|
void NE2K_IncomingIPX(const unsigned char *buf,unsigned int len) {
|
|
if (buf == NULL || len < 30) return;
|
|
if (theNE2kDevice == NULL) return;
|
|
if (len >= 1536) return;
|
|
|
|
/* Use the IPX_OLD_LLC variant preferred by Windows 95 */
|
|
unsigned char *tmp = new unsigned char[len + 17];
|
|
|
|
memcpy(tmp,buf+10,6); // destination MAC address
|
|
memcpy(tmp+6,buf+22,6); // source MAC address
|
|
*((uint16_t*)(tmp+12)) = htobe16(len); // EtherType (as length)
|
|
memcpy(tmp+14,"\xE0\xE0\x03",3); // LLC header
|
|
memcpy(tmp+17,buf,len); // payload
|
|
|
|
theNE2kDevice->rx_frame(tmp, len + 17);
|
|
|
|
delete[] tmp;
|
|
}
|
|
#endif
|
|
|
|
extern std::string niclist;
|
|
|
|
class NE2K: public Module_base {
|
|
private:
|
|
// Data
|
|
IO_ReadHandleObject ReadHandler8[0x20];
|
|
IO_WriteHandleObject WriteHandler8[0x20];
|
|
IO_ReadHandleObject ReadHandler16[0x10];
|
|
IO_WriteHandleObject WriteHandler16[0x10];
|
|
|
|
public:
|
|
bool load_success;
|
|
NE2K(Section* configuration):Module_base(configuration) {
|
|
Section_prop * section=static_cast<Section_prop *>(configuration);
|
|
|
|
load_success = true;
|
|
// enabled?
|
|
|
|
if(!section->Get_bool("ne2000")) {
|
|
addne2k = false;
|
|
load_success = false;
|
|
return;
|
|
}
|
|
|
|
const char* backendstring = section->Get_string("backend");
|
|
ethernet = OpenEthernetConnection(backendstring);
|
|
if(!ethernet)
|
|
{
|
|
LOG_MSG("NE2000: Failed to open Ethernet backend %s", backendstring);
|
|
addne2k = false;
|
|
load_success = false;
|
|
return;
|
|
}
|
|
|
|
// get irq and base
|
|
Bitu irq = (Bitu)section->Get_int("nicirq");
|
|
if(!(irq==3 || irq==4 || irq==5 || irq==6 ||irq==7 ||
|
|
irq==9 || irq==10 || irq==11 || irq==12 ||irq==14 ||irq==15)) {
|
|
irq=3;
|
|
}
|
|
Bitu base = (Bitu)section->Get_hex("nicbase");
|
|
if(!(base==0x260||base==0x280||base==0x300||base==0x320||base==0x340||base==0x380)) {
|
|
base=0x300;
|
|
}
|
|
|
|
LOG_MSG("NE2000: Base=0x%x irq=%u",(unsigned int)base,(unsigned int)irq);
|
|
|
|
// mac address
|
|
const char* macstring=section->Get_string("macaddr");
|
|
unsigned int macint[6];
|
|
uint8_t mac[6];
|
|
if(!strcmp(macstring,"random")) {
|
|
mac[0]=((unsigned char)rand() & 0xFE) | 0x02; // unicast local admin
|
|
mac[1]= (unsigned char)rand();
|
|
mac[2]= (unsigned char)rand();
|
|
mac[3]= (unsigned char)rand();
|
|
mac[4]= (unsigned char)rand();
|
|
mac[5]= (unsigned char)rand();
|
|
}
|
|
else if(sscanf(macstring,"%02x:%02x:%02x:%02x:%02x:%02x",
|
|
&macint[0],&macint[1],&macint[2],&macint[3],&macint[4],&macint[5]) != 6) {
|
|
mac[0]=0xac;mac[1]=0xde;mac[2]=0x48;
|
|
mac[3]=0x88;mac[4]=0xbb;mac[5]=0xaa;
|
|
} else {
|
|
mac[0]=macint[0]; mac[1]=macint[1];
|
|
mac[2]=macint[2]; mac[3]=macint[3];
|
|
mac[4]=macint[4]; mac[5]=macint[5];
|
|
}
|
|
|
|
// create the bochs NIC class
|
|
theNE2kDevice = new bx_ne2k_c ();
|
|
memcpy(theNE2kDevice->s.physaddr, mac, 6);
|
|
|
|
theNE2kDevice->s.base_address=(uint32_t)base;
|
|
theNE2kDevice->s.base_irq=(int)irq;
|
|
|
|
theNE2kDevice->init();
|
|
|
|
// install I/O-handlers and timer
|
|
for(Bitu i = 0; i < 0x20; i++) {
|
|
ReadHandler8[i].Install((i+theNE2kDevice->s.base_address),
|
|
dosbox_read,IO_MB|IO_MW);
|
|
WriteHandler8[i].Install((i+theNE2kDevice->s.base_address),
|
|
dosbox_write,IO_MB|IO_MW);
|
|
}
|
|
TIMER_AddTickHandler(NE2000_Poller);
|
|
addne2k = true;
|
|
}
|
|
|
|
~NE2K() {
|
|
if (ethernet) delete ethernet;
|
|
ethernet = nullptr;
|
|
if (theNE2kDevice) delete theNE2kDevice;
|
|
theNE2kDevice = nullptr;
|
|
TIMER_DelTickHandler(NE2000_Poller);
|
|
PIC_RemoveEvents(NE2000_TX_Event);
|
|
if (addne2k) VFILE_Remove("NE2000.COM","SYSTEM");
|
|
}
|
|
};
|
|
|
|
static NE2K* test = NULL;
|
|
|
|
bool NE2K_IsInit(void) {
|
|
return (test != NULL) && (theNE2kDevice != NULL) && (ethernet != NULL);;
|
|
}
|
|
|
|
bool NE2K_GetMacAddress(unsigned char *buf) {
|
|
if (test == NULL || theNE2kDevice == NULL || ethernet == NULL) return false;
|
|
|
|
#if C_IPX
|
|
// If the MAC address is the stock default, then don't use it, because the DOSBox-X scheme
|
|
// will cause all clients to use the same MAC address and then IPX won't work.
|
|
if (!memcmp(theNE2kDevice->s.physaddr,"\xAC\xDE\x48\x88\x99\xAA",6)) return false;
|
|
#endif
|
|
|
|
memcpy(buf,theNE2kDevice->s.physaddr,6);
|
|
return true;
|
|
}
|
|
|
|
void NE2K_ShutDown(Section* sec) {
|
|
(void)sec;//UNUSED
|
|
if (test) {
|
|
delete test;
|
|
test = NULL;
|
|
}
|
|
}
|
|
|
|
void NE2K_OnReset(Section* sec) {
|
|
(void)sec;//UNUSED
|
|
if (test == NULL && !IS_PC98_ARCH) {
|
|
LOG(LOG_MISC,LOG_DEBUG)("Allocating NE2000 emulation");
|
|
test = new NE2K(control->GetSection("ne2000"));
|
|
|
|
if (!test->load_success) {
|
|
LOG(LOG_MISC,LOG_DEBUG)("Sorry, NE2000 allocation failed to load");
|
|
delete test;
|
|
test = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void NE2K_Init() {
|
|
LOG(LOG_MISC,LOG_DEBUG)("Initializing NE2000 network card emulation");
|
|
|
|
AddExitFunction(AddExitFunctionFuncPair(NE2K_ShutDown),true);
|
|
AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(NE2K_OnReset));
|
|
}
|