From 9536d4cbc0433ad73e54cad6dc0f121c42340fcd Mon Sep 17 00:00:00 2001 From: Cyd Date: Sun, 5 Jul 2026 21:18:42 -0500 Subject: [PATCH] NET: full networked mission runs end-to-end; fix blank VDB heads (NIC/VDB port clash) Console queues a mission -> egg over the wire -> pod loads + runs it -> all cockpit heads render -> mission ends on the console timer. Four fixes: 1. ne2000.cpp: don't BX_PANIC on the TCR inhibit-CRC / auto-tx-disable bits. A full production boot runs the packet driver's internal loopback self-test, which sets those bits; Bochs' NE2000 aborted the whole emulator. Record them in TCR state instead (harmless for an emulated NIC; pcap/host does framing+CRC). Also fixes a latent coll_prio bit-4 vs bit-3 round-trip bug. Committed copy in vpx-device/. 2. Confirmed the mission-load page fault (00FF:219D) was RIO-OFF; booting with the RIO live (net_full.conf) loads and runs the mission cleanly. 3. net_loop.conf + net-boot/loop.bat: GO.BAT-style loop so the pod stays connected to the console and picks up missions as they're queued (the real pod relaunches netnub after each mission/idle disconnect). 4. HEADLINE: blank MFD/radar heads were an I/O port collision. The VDB video splitter is hardwired at 0x300-0x31A (palettes 0x300/0x308/0x310); the NE2000 at nicbase=300 swallowed the game's VDB palette writes -> vdb_pal stayed zero -> pal_draw decoded every head to black. Same VDB spam corrupted NIC RX, dropping the console EndMission (mission overran its timer). Fix: move the NIC to 0x340 (DOSBox nicbase=340 + DOS NET.CFG PORT 340; must agree). VDB keeps 0x300. Config-only, no rebuild. Adds: net_full.conf, net_loop.conf, net-boot/ (ODI drivers, NET.CFG@340, loop.bat, README), vpx-device/{ne2000,ethernet_pcap}.cpp; updates NET-NOTES.md. Co-Authored-By: Claude Fable 5 --- emulator/NET-NOTES.md | 47 + emulator/net-boot/LSL.COM | Bin 0 -> 18356 bytes emulator/net-boot/NE2000.COM | Bin 0 -> 21188 bytes emulator/net-boot/NET.CFG | 9 + emulator/net-boot/ODIPKT.COM | Bin 0 -> 7000 bytes emulator/net-boot/README.md | 29 + emulator/net-boot/loop.bat | 9 + emulator/net_full.conf | 69 + emulator/net_loop.conf | 43 + emulator/vpx-device/ethernet_pcap.cpp | 285 ++++ emulator/vpx-device/ne2000.cpp | 1733 +++++++++++++++++++++++++ 11 files changed, 2224 insertions(+) create mode 100644 emulator/net-boot/LSL.COM create mode 100644 emulator/net-boot/NE2000.COM create mode 100644 emulator/net-boot/NET.CFG create mode 100644 emulator/net-boot/ODIPKT.COM create mode 100644 emulator/net-boot/README.md create mode 100644 emulator/net-boot/loop.bat create mode 100644 emulator/net_full.conf create mode 100644 emulator/net_loop.conf create mode 100644 emulator/vpx-device/ethernet_pcap.cpp create mode 100644 emulator/vpx-device/ne2000.cpp diff --git a/emulator/NET-NOTES.md b/emulator/NET-NOTES.md index 9b11df8..7ed6c68 100644 --- a/emulator/NET-NOTES.md +++ b/emulator/NET-NOTES.md @@ -372,6 +372,53 @@ so this may not reproduce there. NEXT: retry with RIO enabled + DOSBox ACK deadline during load); if it still faults, disassemble around game code 00FF:219D. This joins the Division-renderer/crash workstream. +## FULL NETWORKED MISSION WORKS END TO END (2026-07-05, later) — all fixed + +A complete networked mission now runs start to finish: console queues a +mission -> egg over the wire -> pod loads it -> RIO live -> **all cockpit +heads render** -> mission ends on the console timer. Four fixes got here: + +1. **Mission-load crash was RIO-OFF** (confirmed). Booting with the RIO + enabled (`net_full.conf`: serial1 COM1 rxpollus:100 rxburst:16, full + RIO+sound production boot) — the page fault at 00FF:219D is gone and the + mission loads + runs. Real pods run RIO-on, so this was the whole thing. + +2. **NE2000 `BX_PANIC` fixed in the emulator.** A full production boot runs + the packet driver's internal loopback self-test, which sets the NIC's TCR + inhibit-CRC bit; Bochs' `ne2000.cpp` `BX_PANIC`'d and aborted the whole + emulator. Patched the TCR write handler to record crc_disable/ext_stoptx + instead of panicking (harmless for an emulated NIC; pcap/host frames+CRCs). + Committed copy: `vpx-device/ne2000.cpp`. + +3. **Pod persistence via GO.BAT loop.** When the console has no mission + queued, the pod connects, sees nothing, and cleanly exits (not a crash) — + the real pod's GO.BAT immediately relaunches netnub. `net_loop.conf` + + `net-boot/loop.bat` replicate that so the pod stays connected and ready. + +4. **HEADLINE FIX — blank cockpit heads = NE2000/VDB I/O port collision.** + All 5 MFD + radar heads decoded to pure black during a live mission even + though the DOSBox SVGA gauge framebuffer was FULL (captured mid-mission: + SENSOR CLUSTER/MYOMERS/SRM 4/mech wireframes/pilot name). Root cause: the + VDB video splitter board is hardwired at **0x300-0x31A** (palettes + 0x300/0x308/0x310; `VDB_BASE` in vpxlog.cpp) and we'd put the NE2000 at + `nicbase=300` — the NIC swallowed the game's VDB palette writes, so + `vdb_pal` stayed zero and `pal_draw` mapped every index to black. The VDB + spam into 0x300-0x31A also corrupted NIC registers, dropping the console's + EndMission -> **the mission overran its timer** (second symptom, same + cause). FIX (config-only): move the NIC to **0x340** in the DOSBox conf + (`nicbase=340`) AND the DOS `NET.CFG` (`PORT 340`) — both must agree; the + game uses odipkt so it's base-agnostic. VDB keeps 0x300 (game hardwires + it; real pod's NIC lived elsewhere for this reason). Non-networked gauge + runs never hit this (no NIC). Verified: heads render perfectly (radar with + contact blips + SPEED/HEADING/ARMOR + mission clock; MFDs full) AND the + mission ends cleanly on the timer (`Sending EndMission`, score 1000). + Diagnostic recipe: `New-Item \DUMP` mid-mission dumps + win0/3/4.bmp; PrintWindow (flag 2, GL) the DOSBox SVGA window to prove the + framebuffer has content; framebuffer-full + heads-black => palette/port. + +Committed: `net_full.conf`, `net_loop.conf`, `net-boot/` (drivers, NET.CFG +@340, loop.bat, README), `vpx-device/ne2000.cpp`, `vpx-device/ethernet_pcap.cpp`. + ## Open questions / notes - Exact TCP listen port(s) — not in the source grep; get from NETNUB.EXE or a capture at milestone 3. diff --git a/emulator/net-boot/LSL.COM b/emulator/net-boot/LSL.COM new file mode 100644 index 0000000000000000000000000000000000000000..af998b203c99e634c2ce9262277476316e3afe34 GIT binary patch literal 18356 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 c:\rel410\bt\nn.log +32rtm.exe -u +echo ===== netnub cycle ended -- reconnecting to console ===== +goto go diff --git a/emulator/net_full.conf b/emulator/net_full.conf new file mode 100644 index 0000000..e4d7786 --- /dev/null +++ b/emulator/net_full.conf @@ -0,0 +1,69 @@ +# Networked pod boot (single mission, then DOS pause). +# Console (SheepShaver) pushes the mission egg over TCP 1501; pod runs it. +# RIO on COM1 + dual AWE32 sound (host env VWE_AWE32=1 + VWE_AWE_ROM=). +# +# CRITICAL: NE2000 is at nicbase=340, NOT 300 -- the VDB video splitter board +# is hardwired at 0x300-0x31A (palettes 0x300/0x308/0x310). nicbase=300 makes +# the NIC swallow the game's VDB palette writes -> all MFD/radar heads decode +# to black AND corrupts NIC RX (console EndMission lost, mission overruns its +# timer). net-boot/NET.CFG must match (PORT 340). VDB stays at 0x300. +# +# Launch (host env): VPXLOG, VPX_RESPOND=1, VPX_RENDER=1, VPX_DUMPDIR, +# RIO_TAP, VWE_AWE32=1, VWE_AWE_ROM, and C:\Windows\System32\Npcap on PATH. +# pcap realnic must be the bridge miniport (see NET-NOTES.md). +[sdl] +output=opengl +priority=highest,highest +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=dynamic +cputype=pentium +cycles=max +[ne2000] +ne2000=true +nicbase=340 +nicirq=3 +backend=pcap +[ethernet, pcap] +realnic=DB5521D +[sblaster] +sbtype=sb16 +sbbase=220 +irq=5 +dma=1 +hdma=5 +[mixer] +rate=44100 +blocksize=1024 +prebuffer=60 +[serial] +serial1=directserial realport:COM1 rxpollus:100 rxburst:16 +serial2=disabled +[autoexec] +mount c "C:\VWE\TeslaRel410\ALPHA_1" +mount d "C:\VWE\TeslaRel410\emulator\net-boot" +d: +echo === loading NE2000 packet-driver stack (pcap/bridge, port 340) === +d:\lsl +d:\ne2000 +d:\odipkt +c: +cd \rel410\bt +set VIDEOFORMAT=svga +set BLASTER=A220 I5 D1 H5 P330 T6 +c:\sb16\diagnose /s +set BLASTER=A240 I7 D3 H6 P300 T6 +c:\sb16\diagnose /s +set BLASTER=A220 I5 D1 H5 P330 T6 +set TEMP=c:\ +set HEAPSIZE=15000000 +set L4GAUGE=640x480x16 +call setenv.bat r s s g +echo === launching game via NetNub (RIO + sound; no -egg; waits for console) === +32rtm.exe -x +netnub -p -f btl4opt > nn.log +32rtm.exe -u +echo === NET-RUN-DONE === +pause diff --git a/emulator/net_loop.conf b/emulator/net_loop.conf new file mode 100644 index 0000000..77bb358 --- /dev/null +++ b/emulator/net_loop.conf @@ -0,0 +1,43 @@ +# Networked pod boot, GO.BAT-style loop (persistent: auto-relaunches netnub +# after each mission/idle disconnect so the pod stays connected to the console +# and picks up missions as the operator queues them -- mirrors the real pod's +# GO.BAT). Same 0x340 NIC fix as net_full.conf (see that file's header). +# Sound is OFF here (setenv arg3=n) for fast reconnect cycles; flip to net_full +# for the full sound experience. +[sdl] +output=opengl +priority=highest,highest +[dosbox] +memsize=32 +machine=svga_s3 +[cpu] +core=dynamic +cputype=pentium +cycles=max +[ne2000] +ne2000=true +nicbase=340 +nicirq=3 +backend=pcap +[ethernet, pcap] +realnic=DB5521D +[serial] +serial1=directserial realport:COM1 rxpollus:100 rxburst:16 +serial2=disabled +[autoexec] +mount c "C:\VWE\TeslaRel410\ALPHA_1" +mount d "C:\VWE\TeslaRel410\emulator\net-boot" +d: +echo === loading NE2000 packet-driver stack (pcap/bridge, port 340) === +d:\lsl +d:\ne2000 +d:\odipkt +c: +cd \rel410\bt +set VIDEOFORMAT=svga +set TEMP=c:\ +set HEAPSIZE=15000000 +set L4GAUGE=640x480x16 +call setenv.bat r s n g +echo === entering GO.BAT-style netnub loop (waits for console missions) === +call d:\loop.bat diff --git a/emulator/vpx-device/ethernet_pcap.cpp b/emulator/vpx-device/ethernet_pcap.cpp new file mode 100644 index 0000000..607aca8 --- /dev/null +++ b/emulator/vpx-device/ethernet_pcap.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2021 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 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. + */ + +#include "config.h" + +#if C_PCAP + +#include "ethernet_pcap.h" +#include "dosbox.h" +#include "logging.h" +#include "support.h" /* strcasecmp */ +#include + +extern std::string niclist; + +#ifdef WIN32 + +#include + +// DLL loading +#define pcap_sendpacket(A,B,C) PacketSendPacket(A,B,C) +#define pcap_close(A) PacketClose(A) +#define pcap_freealldevs(A) PacketFreealldevs(A) +#define pcap_open(A,B,C,D,E,F) PacketOpen(A,B,C,D,E,F) +#define pcap_next_ex(A,B,C) PacketNextEx(A,B,C) +#define pcap_findalldevs_ex(A,B,C,D) PacketFindALlDevsEx(A,B,C,D) +#define pcap_geterr(A) PacketGetError(A) + +int (*PacketSendPacket)(pcap_t *, const u_char *, int) = 0; +void (*PacketClose)(pcap_t *) = 0; +void (*PacketFreealldevs)(pcap_if_t *) = 0; +pcap_t* (*PacketOpen)(char const *,int,int,int,struct pcap_rmtauth *,char *) = 0; +int (*PacketNextEx)(pcap_t *, struct pcap_pkthdr **, const u_char **) = 0; +int (*PacketFindALlDevsEx)(char *, struct pcap_rmtauth *, pcap_if_t **, char *) = 0; +char* (*PacketGetError)(pcap_t *) = nullptr; + +char pcap_src_if_string[] = PCAP_SRC_IF_STRING; + +bool LoadPcapLibrary() { + // remember if we've already initialized the library + static HINSTANCE pcapinst = (HINSTANCE)-1; + if(pcapinst!=(HINSTANCE)-1) { + return (pcapinst!=NULL); + } + + // init the library + pcapinst = LoadLibrary("WPCAP.DLL"); + if(pcapinst==NULL) { + niclist = "WinPcap has to be installed for the NE2000 to work."; + LOG_MSG(niclist.c_str()); + return false; + } + FARPROC psp; + +#ifdef __MINGW32__ + // C++ defines function and data pointers as separate types to reflect + // Harvard architecture machines (like the Arduino). As such, casting + // between them isn't portable and GCC will helpfully warn us about it. + // We're only running this code on Windows which explicitly allows this + // behaviour, so silence the warning to avoid confusion. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + + psp = GetProcAddress(pcapinst,"pcap_sendpacket"); + if(!PacketSendPacket) PacketSendPacket = + (int (__cdecl *)(pcap_t *,const u_char *,int))psp; + + psp = GetProcAddress(pcapinst,"pcap_close"); + if(!PacketClose) PacketClose = + (void (__cdecl *)(pcap_t *)) psp; + + psp = GetProcAddress(pcapinst,"pcap_freealldevs"); + if(!PacketFreealldevs) PacketFreealldevs = + (void (__cdecl *)(pcap_if_t *)) psp; + + psp = GetProcAddress(pcapinst,"pcap_open"); + if(!PacketOpen) PacketOpen = + (pcap_t* (__cdecl *)(char const *,int,int,int,struct pcap_rmtauth *,char *)) psp; + + psp = GetProcAddress(pcapinst,"pcap_next_ex"); + if(!PacketNextEx) PacketNextEx = + (int (__cdecl *)(pcap_t *, struct pcap_pkthdr **, const u_char **)) psp; + + psp = GetProcAddress(pcapinst,"pcap_findalldevs_ex"); + if(!PacketFindALlDevsEx) PacketFindALlDevsEx = + (int (__cdecl *)(char *, struct pcap_rmtauth *, pcap_if_t **, char *)) psp; + + psp = GetProcAddress(pcapinst,"pcap_geterr"); + if(!PacketGetError) PacketGetError = + (char* (__cdecl *)(pcap_t *)) psp; + +#ifdef __MINGW32__ +#pragma GCC diagnostic pop +#endif + + if(PacketFindALlDevsEx==0 || PacketNextEx==0 || PacketOpen==0 || + PacketFreealldevs==0 || PacketClose==0 || PacketSendPacket==0 || + PacketGetError==0) { + niclist = "Incorrect or non-functional WinPcap version."; + LOG_MSG(niclist.c_str()); + pcapinst = NULL; + return false; + } + + return true; +} + +#endif + +PcapEthernetConnection::PcapEthernetConnection() + : EthernetConnection() +{ +} + +PcapEthernetConnection::~PcapEthernetConnection() +{ + if(adhandle) pcap_close(adhandle); +} + +bool PcapEthernetConnection::Initialize(Section* config) +{ + Section_prop *section = static_cast(config); + const char* realnicstring = section->Get_string("realnic"); + +#ifdef WIN32 + if(!LoadPcapLibrary()) { + return false; + } +#endif + + // find out which pcap device to use + pcap_if_t *alldevs; + pcap_if_t *currentdev = NULL; + char errbuf[PCAP_ERRBUF_SIZE]; + unsigned int userdev; +#ifdef WIN32 + if (pcap_findalldevs_ex(pcap_src_if_string, NULL, &alldevs, errbuf) == -1) +#else + if (pcap_findalldevs(&alldevs, errbuf) == -1) +#endif + { + niclist = "Cannot enumerate network interfaces: "+std::string(errbuf); + LOG_MSG("%s", niclist.c_str()); + return false; + } + { + Bitu i = 0; + niclist = "Network Interface List\n-------------------------------------------------------------\n"; + for(currentdev=alldevs; currentdev!=NULL; currentdev=currentdev->next) { + const char* desc = "no description"; + if(currentdev->description) desc=currentdev->description; + i++; + niclist+=(i<10?" ":"")+std::to_string(i)+" "+currentdev->name+"\n ("+desc+")\n"; + } + } + if (!strcasecmp(realnicstring,"list")) { + // print list and quit + std::istringstream in(("\n"+niclist+"\n").c_str()); + if (in) for (std::string line; std::getline(in, line); ) + LOG_MSG("%s", line.c_str()); + LOG_MSG("NOTE: No interface was chosen because the realnic setting is set to 'list', set realnic to the number of the interface you want to use."); + pcap_freealldevs(alldevs); + return false; + } else if(1==sscanf(realnicstring,"%u",&userdev)) { + // user passed us a number + Bitu i = 0; + currentdev=alldevs; + while(currentdev!=NULL) { + i++; + if(i==userdev) break; + else currentdev=currentdev->next; + } + } else { + // user might have passed a piece of name + for(currentdev=alldevs; currentdev!=NULL; currentdev=currentdev->next) { + if(strstr(currentdev->name,realnicstring)) { + break; + }else if(currentdev->description!=NULL && + strstr(currentdev->description,realnicstring)) { + break; + } + } + } + + if(currentdev==NULL) { + LOG_MSG("Unable to find network interface - check realnic parameter\n"); + pcap_freealldevs(alldevs); + return false; + } + // print out which interface we are going to use + const char* desc = "no description"; + if(currentdev->description) desc=currentdev->description; + LOG_MSG("Using Network interface:\n%s\n(%s)\n",currentdev->name,desc); + + const char *timeoutstr = section->Get_string("timeout"); + char *end; + int timeout = -1; + if (!strlen(timeoutstr)||(timeoutstr[0]!='-'&&!isdigit(timeoutstr[0]))) { // Default timeout values +#ifdef MACOSX + timeout = 3000; // For macOS, use 3000ms as it does not appear to support -1 +#else + timeout = -1; // For other platforms, use -1 which should mean "non-blocking mode" +#endif + } else + timeout = strtoul(timeoutstr,&end,10); + // attempt to open it +#ifdef WIN32 + if ( (adhandle= pcap_open( + currentdev->name, // name of the device + 65536, // portion of the packet to capture + // 65536 = whole packet + PCAP_OPENFLAG_PROMISCUOUS, // promiscuous mode + timeout, // read timeout + NULL, // authentication on the remote machine + errbuf // error buffer + ) ) == NULL) +#else + /*pcap_t *pcap_open_live(const char *device, int snaplen, + int promisc, int to_ms, char *errbuf)*/ + if ( (adhandle= pcap_open_live( + currentdev->name, // name of the device + 65536, // portion of the packet to capture + // 65536 = whole packet + true, // promiscuous mode + timeout, // read timeout + errbuf // error buffer + ) ) == NULL) + +#endif + { + LOG_MSG("\nUnable to open the interface: %s.", errbuf); + pcap_freealldevs(alldevs); + return false; + } + pcap_freealldevs(alldevs); +#ifndef WIN32 + pcap_setnonblock(adhandle,1,errbuf); +#endif + return true; +} + +void PcapEthernetConnection::SendPacket(const uint8_t* packet, int len) +{ + static unsigned long txn = 0; + if (((++txn) & (txn - 1)) == 0) /* powers of 2, avoid spam */ + LOG_MSG("PCAP TX #%lu len=%d dst=%02x:%02x:%02x src=%02x:%02x:%02x", + txn, len, packet[0],packet[1],packet[2], packet[6],packet[7],packet[8]); + int ret = pcap_sendpacket(adhandle, packet, len); + if(ret) LOG_MSG("PCAP error: %s", pcap_geterr(adhandle)); +} + +void PcapEthernetConnection::GetPackets(std::function callback) +{ + int res; + struct pcap_pkthdr *header; + u_char *pkt_data; + static unsigned long rxn = 0; +//#if 0 + while((res = pcap_next_ex( adhandle, &header, (const u_char **)&pkt_data)) > 0) { + if (((++rxn) & (rxn - 1)) == 0) + LOG_MSG("PCAP RX #%lu len=%d dst=%02x:%02x:%02x src=%02x:%02x:%02x", + rxn, header->len, pkt_data[0],pkt_data[1],pkt_data[2], + pkt_data[6],pkt_data[7],pkt_data[8]); + callback(pkt_data, header->len); + } +//#endif +} + +#endif diff --git a/emulator/vpx-device/ne2000.cpp b/emulator/vpx-device/ne2000.cpp new file mode 100644 index 0000000..84d1435 --- /dev/null +++ b/emulator/vpx-device/ne2000.cpp @@ -0,0 +1,1733 @@ +#include "config.h" + +#include "dosbox.h" +#include +#include +#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 +#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)) { + return; + } + } 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(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)); +}