This patch adds an emulation of the 3Com 3C90x series of NICs to QEMU.

Signed-off-by: Matthew Iselin <matthew@theiselins.net>
---
 Makefile.target |    1 +
 hw/3c90x.c      | 2408 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/pci.c        |    2 +
 hw/pci_ids.h    |    3 +
 4 files changed, 2414 insertions(+), 0 deletions(-)
 create mode 100644 hw/3c90x.c

diff --git a/Makefile.target b/Makefile.target
index a593503..eb45199 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -555,6 +555,7 @@ obj-y += eepro100.o
 obj-y += ne2000.o
 obj-y += pcnet.o
 obj-y += rtl8139.o
+obj-y += 3c90x.o
 obj-y += e1000.o
 
 # Generic watchdog support and some watchdog devices
diff --git a/hw/3c90x.c b/hw/3c90x.c
new file mode 100644
index 0000000..bcbe5a4
--- /dev/null
+++ b/hw/3c90x.c
@@ -0,0 +1,2408 @@
+/**
+ * QEMU 3C90X Emulation
+ *
+ * Copyright (c) 2009 Matthew Iselin (QEMU VERSION)
+ * Copyright (C) 2004 John Kelley (pearpc@kelley.ca) (PEARPC VERSION)
+ * Copyright (C) 2003 Stefan Weyergraf (PEARPC VERSION)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+
+ * Modifications:
+ *  (none)
+ */
+
+/**
+ * TODO:
+ *  - Still a lot of unimplemented functionality
+ *  - On-chip TCP checksum emulation
+ *  - savevm functions
+ * TESTED ON:
+ *  - Damn Small Linux (4.4.10)
+ *  - Ubuntu (8.10, 5.10)
+ *  - Pedigree
+ */
+
+#include "hw.h"
+#include "pci.h"
+#include "pc.h"
+#include "qemu-timer.h"
+#include "net.h"
+
+// Should we inspect incoming frames and print extra debugging information about them?
+//#define DEBUG_3C90X_ANALYSE_FRAMES          0
+
+#ifdef DEBUG_3C90X_ANALYSE_FRAMES
+#define __FAVOR_BSD
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#endif
+
+#define PACKED      __attribute__((packed));
+#define ALIGNED     __attribute__((aligned));
+#define PACKED8     __attribute__((aligned(8)));
+#define PACKED16    __attribute__((aligned(16)));
+#define PACKED32    __attribute__((aligned(32)));
+
+/* debug 3C90X card */
+// #define DEBUG_3C90X 1
+
+/* define this to output TX and RX events */
+// #define DEBUG_3C90X_AUX 1
+
+#define PCI_FREQUENCY 33000000L
+
+/* Calculate CRCs properly on Tx packets */
+#define A3C90X_CALCULATE_TXCRC 1
+
+#if defined(A3C90x_CALCULATE_RXCRC)
+/* For crc32 */
+#include <zlib.h>
+#endif
+
+#if defined (DEBUG_3C90X)
+#  define DEBUG_PRINT(x) do { printf(x) ; } while (0)
+#  define DEBUG_PRINT_FORMAT(x) do { printf x  ; } while (0)
+#else
+#  define DEBUG_PRINT(x)
+#  define DEBUG_PRINT_FORMAT(x)
+#endif
+
+#if defined (DEBUG_3C90X_AUX)
+#  define AUX_DEBUG_PRINT(x) do { printf(x) ; } while (0)
+#  define AUX_DEBUG_PRINT_FORMAT(x) do { printf x  ; } while (0)
+#else
+#  define AUX_DEBUG_PRINT(x) DEBUG_PRINT(x)
+#  define AUX_DEBUG_PRINT_FORMAT(x) DEBUG_PRINT_FORMAT(x)
+#endif
+
+#define MAX_PACKET_SIZE 16384
+
+enum Command
+{
+    CmdTotalReset = 0<<11,
+    CmdSelectWindow = 1<<11,
+    CmdEnableDC = 2<<11,                // CmdStartCoax
+    CmdRxDisable = 3<<11,
+    CmdRxEnable = 4<<11,
+    CmdRxReset = 5<<11,
+    CmdStall = 6<<11,
+    CmdTxDone = 7<<11,
+    CmdRxDiscard = 8<<11,
+    CmdTxEnable = 9<<11,
+    CmdTxDisable = 10<<11,
+    CmdTxReset = 11<<11,
+    CmdReqIntr = 12<<11,                // CmdFakeIntr
+    CmdAckIntr = 13<<11,
+    CmdSetIntrEnb = 14<<11,
+    CmdSetIndicationEnable = 15<<11,    // CmdSetStatusEnb
+    CmdSetRxFilter = 16<<11,
+    CmdSetRxEarlyThresh = 17<<11,
+    CmdSetTxThreshold = 18<<11,         // aka TxAgain ?
+    CmdSetTxStartThresh = 19<<11,       // set TxStartTresh
+    //  CmdStartDMAUp = 20<<11,
+    //  CmdStartDMADown = (20<<11)+1,
+    CmdStatsEnable = 21<<11,
+    CmdStatsDisable = 22<<11,
+    CmdDisableDC = 23<<11,              // CmdStopCoax
+    CmdSetTxReclaimThresh = 24<<11,
+    CmdSetHashFilterBit = 25<<11
+};
+
+/*
+ *  IntStatusBits
+ */
+enum IntStatusBits
+{
+    IS_interruptLatch = 1<<0,
+    IS_hostError =      1<<1,
+    IS_txComplete =     1<<2,
+    /* bit 3 is unspecified */
+    IS_rxComplete =     1<<4,
+    IS_rxEarly =        1<<5,
+    IS_intRequested =   1<<6,
+    IS_updateStats =    1<<7,
+    IS_linkEvent =      1<<8,
+    IS_dnComplete =     1<<9,
+    IS_upComplete =     1<<10,
+    IS_cmdInProgress =  1<<11,
+    /* bit 12 is unspecified */
+    /* [15:13] is currently selected window */
+};
+
+/*
+ *  DmaCtrlBits ([1] p.96)
+ */
+enum DmaCtrlBits
+{
+    /* bit 0 unspecified */
+    DC_dnCmplReq =          1<<1,
+    DC_dnStalled =          1<<2,
+    DC_upComplete =         1<<3,   // FIXME: same as in IntStatus, but always visible
+    DC_dnComplete =         1<<4,   // same as above ^^^
+    DC_upRxEarlyEnable =    1<<5,
+    DC_armCountdown =       1<<6,
+    DC_dnInProg =           1<<7,
+    DC_counterSpeed =       1<<8,
+    DC_countdownMode =      1<<9,
+    /* bits 10-15 unspecified */
+    DC_upAltSeqDisable =    1<<16,
+    DC_dnAltSeqDisable =    1<<17,
+    DC_defeatMWI =          1<<20,
+    DC_defeatMRL =          1<<21,
+    DC_upOverDiscEnable =   1<<22,
+    DC_targetAbort =        1<<30,
+    DC_masterAbort =        1<<31
+};
+
+/*
+ *  MII Registers
+ *  TODO: Implement MII properly
+ */
+/*enum MIIControlBits {
+    MIIC_collision =        1<<7,
+    MIIC_fullDuplex =       1<<8,
+    MIIC_restartNegote =    1<<9,
+    MIIC_collision =        1<<7,
+    rest missing
+};*/
+
+struct MIIRegisters
+{
+    uint16_t control;
+    uint16_t status;
+    uint16_t id0;
+    uint16_t id1;
+    uint16_t advert;
+    uint16_t linkPartner;
+    uint16_t expansion;
+    uint16_t nextPage;
+} PACKED;
+typedef struct MIIRegisters MIIRegisters;
+
+/*
+ *  Registers
+ */
+union RegWindow
+{
+    uint8_t b[16];
+    uint16_t u16[8];
+} PACKED;
+typedef union RegWindow RegWindow;
+
+struct Registers
+{
+    // 0x10 uint8_ts missing (current window)
+    uint32_t    r0;
+    uint32_t    r1;
+    uint8_t     TxPktId;
+    uint8_t     r2;
+    uint8_t     Timer;
+    uint8_t     TxStatus;
+    uint16_t    r3;
+    uint16_t    __dontUseMe;//  really: uint16_t IntStatusAuto;
+    uint32_t    DmaCtrl;    //  [1] p.95 (dn), p.100 (up)
+    uint32_t    DnListPtr;  //  [1] p.98
+    uint16_t    r4;
+    uint8_t     DnBurstThresh;  // [1] p.97
+    uint8_t     r5;
+    uint8_t     DnPriorityThresh;
+    uint8_t     DnPoll;     // [1] p.100
+    uint16_t    r6;
+    uint32_t    UpPktStatus;
+    uint16_t    FreeTimer;
+    uint16_t    Countdown;
+    uint32_t    UpListPtr;  // [1] p.115
+    uint8_t     UpPriorityThresh;
+    uint8_t     UpPoll;
+    uint8_t     UpBurstThresh;
+    uint8_t     r7;
+    uint32_t    RealTimeCount;
+    uint8_t     ConfigAddress;
+    uint8_t     r8;
+    uint8_t     r9;
+    uint8_t     r10;
+    uint8_t     ConfigData;
+    uint8_t     r11;
+    uint8_t     r12;
+    uint8_t     r13;
+    uint32_t    r14[9];
+    uint32_t    DebugData;
+    uint16_t    DebugControl;
+    uint16_t    r15;
+    uint16_t    DnMaxBurst;
+    uint16_t    UpMaxBurst;
+    uint16_t    PowerMgmtCtrl;
+    uint16_t    r16;
+} PACKED;
+typedef struct Registers Registers;
+
+#define RA_INV 0
+
+static uint8_t gRegAccess[0x70] =
+{
+    /* 0x10 */
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    /* 0x14 */
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    /* 0x18 */
+    1,              /* TxPktId */
+    RA_INV,
+    1,              /* Timer */
+    1,              /* TxStatus */
+    /* 0x1c */
+    RA_INV, RA_INV,
+    RA_INV, RA_INV,             /* IntStatusAuto */
+    /* 0x20 */
+    4, RA_INV, RA_INV, RA_INV,  /* DmaCtrl */
+    /* 0x24 */
+    4, RA_INV, RA_INV, RA_INV,  /* DnListPtr */
+    /* 0x28 */
+    RA_INV, RA_INV,
+    1,              /* DnBurstThresh */
+    RA_INV,
+    /* 0x2c */
+    1,              /* DnPriorityThresh */
+    1,              /* DnPoll */
+    RA_INV,
+    1,
+    /* 0x30 */
+    4, RA_INV, RA_INV, RA_INV,  /* UpPktStatus */
+    /* 0x34 */
+    2, RA_INV,          /* FreeTimer */
+    2, RA_INV,          /* Countdown */
+    /* 0x38 */
+    4, RA_INV, RA_INV, RA_INV,  /* UpListPtr */
+    /* 0x3c */
+    1,              /* UpPriorityThresh */
+    1,              /* UpPoll */
+    1,              /* UpBurstThresh */
+    RA_INV,
+    /* 0x40 */
+    4, RA_INV, RA_INV, RA_INV,  /* RealTimeCount */
+    /* 0x44 */
+    1,              /* ConfigAddress */
+    RA_INV,
+    RA_INV,
+    RA_INV,
+    /* 0x48 */
+    1,              /* ConfigData */
+    RA_INV,
+    RA_INV,
+    RA_INV,
+    /* 0x4c */
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    /* 0x50 */
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    RA_INV, RA_INV, RA_INV, RA_INV,
+    /* 0x70 */
+    4, RA_INV, RA_INV, RA_INV,  /* DebugData */
+    /* 0x74 */
+    2, RA_INV,          /* DebugControl */
+    RA_INV, RA_INV,
+    /* 0x78 */
+    2, RA_INV,          /* DnMaxBurst */
+    2, RA_INV,          /* UpMaxBurst */
+    /* 0x7c */
+    2, RA_INV,          /* PowerMgmtCtrl */
+    RA_INV, RA_INV
+};
+
+/*
+ *  Window 0
+ */
+struct RegWindow0
+{
+    uint32_t    r0;
+    uint32_t    BiosRomAddr;
+    uint8_t     BiosRomData;
+    uint8_t     r1;
+    uint16_t    EepromCommand;
+    uint16_t    EepromData;
+    uint16_t    XXX;        // IntStatus/CommandRegister
+} PACKED;
+typedef struct RegWindow0 RegWindow0;
+
+enum W0_Offsets
+{
+    W0_EEPROMCmd = 0xa,
+    W0_EEPROMData = 0xc
+};
+
+enum W0_EEPROMOpcode
+{
+    EEOP_SubCmd = 0<<6,
+    EEOP_WriteReg = 1<<6,
+    EEOP_ReadReg = 2<<6,
+    EEOP_EraseReg = 3<<6
+};
+
+enum W0_EEPROMSubCmd
+{
+    EESC_WriteDisable = 0<<4,
+    EESC_WriteAll = 1<<4,
+    EESC_EraseAll = 2<<4,
+    EESC_WriteEnable = 3<<4
+};
+
+/*
+ *  Window 2
+ */
+struct RegWindow2
+{
+    uint16_t    StationAddress[6];
+    uint16_t    StationMask[6];
+    uint16_t    ResetOptions;
+    uint16_t    XXX;        // IntStatus/CommandRegister
+} PACKED;
+typedef struct RegWindow2 RegWindow2;
+
+/*
+ *  Window 3
+ */
+struct RegWindow3
+{
+    uint32_t    InternalConfig; // [1] p.58,76
+    uint16_t    MaxPktSize;
+    uint16_t    MacControl;     // [1] p.179
+    uint16_t    MediaOptions;   // [1] p.78 (EE), p.181
+    uint16_t    RxFree;
+    uint16_t    TxFree;         // [1] p.101
+    uint16_t    XXX;            // IntStatus/CommandRegister
+} PACKED;
+typedef struct RegWindow3 RegWindow3;
+
+/*
+ *  Window 4
+ */
+enum W4_PhysMgmtBits
+{
+    PM_mgmtClk  = 1<<0,
+    PM_mgmtData = 1<<1,
+    PM_mgmtDir  = 1<<2
+};
+
+struct RegWindow4
+{
+    uint16_t    r0;             /* offset 0x0 */
+    uint16_t    r1;             /* offset 0x2 */
+    uint16_t    FifoDiagnostic; /* offset 0x4 */
+    uint16_t    NetDiagnostic;  // [1] p.184 /* offset 0x6 */
+    uint16_t    PhysMgmt;       // [1] p.186 /* offset 0x8 */
+    uint16_t    MediaStatus;    // [1] p.182 /* offset 0xa */
+    uint8_t     BadSSD;
+    uint8_t     Upperuint8sOK;
+    uint16_t    XXX;            // IntStatus/CommandRegister
+} PACKED;
+typedef struct RegWindow4 RegWindow4;
+
+/*
+ *  Window 5
+ */
+enum RxFilterBits   // [1] p.112
+{
+    RXFILT_receiveIndividual = 1,
+    RXFILT_receiveMulticast = 2,
+    RXFILT_receiveBroadcast = 4,
+    RXFILT_receiveAllFrames = 8,
+    RXFILT_receiveMulticastHash = 16
+};
+
+struct RegWindow5
+{
+    uint16_t    TxStartThresh;
+    uint16_t    r0;
+    uint16_t    r1;
+    uint16_t    RxEarlyThresh;
+    uint8_t     RxFilter;           // [1] p.112
+    uint8_t     TxReclaimThresh;
+    uint16_t    InterruptEnable;    // [1] p.120
+    uint16_t    IndicationEnable;   // [1] p.120
+    uint16_t    XXX;                // IntStatus/CommandRegister
+} PACKED;
+typedef struct RegWindow5 RegWindow5;
+
+/*
+ *  Window 6
+ */
+struct RegWindow6
+{
+    uint8_t     CarrierLost;
+    uint8_t     SqeErrors;
+    uint8_t     MultipleCollisions;
+    uint8_t     SingleCollisions;
+    uint8_t     LateCollisions;
+    uint8_t     RxOverruns;
+    uint8_t     FramesXmittedOk;
+    uint8_t     FramesRcvdOk;
+    uint8_t     FramesDeferred;
+    uint8_t     UpperFramesOk;
+    uint16_t    BytesRcvdOk;
+    uint16_t    BytesXmittedOk;
+    uint16_t    XXX;                // IntStatus/CommandRegister
+} PACKED;
+typedef struct RegWindow6 RegWindow6;
+
+/*
+ *  EEPROM
+ */
+enum EEPROMField
+{
+    EEPROM_NodeAddress0 =       0x00,
+    EEPROM_NodeAddress1 =       0x01,
+    EEPROM_NodeAddress2 =       0x02,
+    EEPROM_DeviceID =           0x03,
+    EEPROM_ManifacturerID =     0x07,
+    EEPROM_PCIParam =           0x08,
+    EEPROM_RomInfo =            0x09,
+    EEPROM_OEMNodeAddress0 =    0x0a,
+    EEPROM_OEMNodeAddress1 =    0x0b,
+    EEPROM_OEMNodeAddress2 =    0x0c,
+    EEPROM_SoftwareInfo =       0x0d,
+    EEPROM_CompWord =           0x0e,
+    EEPROM_SoftwareInfo2 =      0x0f,
+    EEPROM_Caps =               0x10,
+    EEPROM_InternalConfig0 =    0x12,
+    EEPROM_InternalConfig1 =    0x13,
+    EEPROM_SubsystemVendorID =  0x17,
+    EEPROM_SubsystemID =        0x18,
+    EEPROM_MediaOptions =       0x19,
+    EEPROM_SmbAddress =         0x1b,
+    EEPROM_PCIParam2 =          0x1c,
+    EEPROM_PCIParam3 =          0x1d,
+    EEPROM_Checksum =           0x20
+};
+
+/*
+ *  Up/Downloading
+ */
+
+// must be on 8-uint8_t physical address boundary
+struct DPD0
+{
+    uint32_t    DnNextPtr;
+    uint32_t    FrameStartHeader;
+    /*  DPDFragDesc Frags[n] */
+} PACKED8;
+typedef struct DPD0 DPD0;
+
+enum FrameStartHeaderBits
+{
+    FSH_rndupBndry =        3<<0,
+    FSH_pktId =             15<<2,
+    /* 12:10 unspecified */
+    FSH_crcAppendDisable =  1<<13,
+    FSH_txIndicate =        1<<15,
+    FSH_dnComplete =        1<<16,
+    FSH_reArmDisable =      1<<23,
+    FSH_lastKap =           1<<24,
+    FSH_addIpChecksum =     1<<25, /** TODO: write support for these */
+    FSH_addTcpChecksum =    1<<26,
+    FSH_addUdpChecksum =    1<<27,
+    FSH_rndupDefeat =       1<<28,
+    FSH_dpdEmpty =          1<<29,
+    /* 30 unspecified */
+    FSH_dnIndicate =        1<<31
+};
+
+// must be on 16-uint8_t physical address boundary
+struct DPD1
+{
+    uint32_t    DnNextPtr;
+    uint32_t    ScheduleTime;
+    uint32_t    FrameStartHeader;
+    uint32_t    res;
+    /*  DPDFragDesc Frags[n] */
+} PACKED16;
+typedef struct DPD1 DPD1;
+
+struct DPDFragDesc
+{
+    uint32_t    DnFragAddr;
+    uint32_t    DnFragLen;  // [12:0] fragLen, [31] lastFrag
+} PACKED;
+typedef struct DPDFragDesc DPDFragDesc;
+
+// must be on 8-uint8_t physical address boundary
+struct UPD
+{
+    uint32_t    UpNextPtr;
+    uint32_t    UpPktStatus;
+    /*  UPDFragDesc Frags[n] */
+} PACKED8;
+typedef struct UPD UPD;
+
+struct UPDFragDesc
+{
+    uint32_t    UpFragAddr;
+    uint32_t    UpFragLen;  // [12:0] fragLen, [31] lastFrag
+} PACKED;
+typedef struct UPDFragDesc UPDFragDesc;
+
+#define MAX_DPD_FRAGS   63
+#define MAX_UPD_FRAGS   63
+#define MAX_UPD_SIZE    (sizeof(UPD) + sizeof(UPDFragDesc)*MAX_UPD_FRAGS) // 512
+
+enum UpPktStatusBits
+{
+    UPS_upPktLen = 0x1fff,
+    /* 13 unspecified */
+    UPS_upError = 1<<14,
+    UPS_upComplete = 1<<15,
+    UPS_upOverrun = 1<<16,
+    UPS_runtFrame = 1<<17,
+    UPS_alignmentError = 1<<18,
+    UPS_crcError = 1<<19,
+    UPS_oversizedFrame = 1<<20,
+    /* 22:21 unspecified */
+    UPS_dribbleBits = 1<<23,
+    UPS_upOverflow = 1<<24,
+    UPS_ipChecksumError = 1<<25,
+    UPS_tcpChecksumError = 1<<26,
+    UPS_udpChecksumError = 1<<27,
+    UPD_impliedBufferEnable = 1<<28,
+    UPS_ipChecksumChecked = 1<<29,
+    UPS_tcpChecksumChecked = 1<<30,
+    UPS_udpChecksumChecked = 1<<31
+};
+
+// IEEE 802.3 MAC, Ethernet-II
+struct EthFrameII
+{
+    uint8_t destMAC[6];
+    uint8_t srcMAC[6];
+    uint8_t type[2];
+} PACKED;
+typedef struct EthFrameII EthFrameII;
+
+struct A3C90XState
+{
+
+    uint16_t    mEEPROM[0x40];
+    char        mEEPROMWritable;
+    Registers   mRegisters;
+    RegWindow   mWindows[8];
+    uint16_t    mIntStatus;
+    char        mRxEnabled;
+    char        mTxEnabled;
+    char        mUpStalled;
+    char        mDnStalled;
+    uint8_t     mRxPacket[MAX_PACKET_SIZE];
+    uint32_t    mRxPacketSize;
+    uint16_t    mMIIRegs[8];
+    uint32_t    mMIIReadWord;
+    uint64_t    mMIIWriteWord;
+    uint32_t    mMIIWrittenBits;
+    uint16_t    mLastHiClkPhysMgmt;
+    uint8_t     mMAC[6];
+
+
+    PCIDevice   *pci_dev;
+    int         a3c90x_mmio_io_addr;
+
+    VLANClientState *vc;
+
+};
+typedef struct A3C90XState A3C90XState;
+
+static void a3c90x_reset(A3C90XState *s);
+
+static void setCR(uint16_t cr, void *opaque);
+
+static uint32_t readRegWindow(A3C90XState *s, uint32_t window, uint32_t port, uint32_t data, uint32_t size);
+static void writeRegWindow(A3C90XState *s, uint32_t window, uint32_t port, uint32_t data, uint32_t size);
+
+static void checkDnWork(void *opaque);
+static void checkUpWork(void *opaque);
+
+static void txDPD0(void *opaque, DPD0 *dpd);
+static void rxUPD(void *opaque, UPD *upd);
+
+static void indicate(uint32_t indications, void *opaque);
+static void acknowledge(uint32_t indications, void *opaque);
+static void maybeRaiseIntr(void *opaque);
+
+static char passesRxFilter(uint8_t *pbuf, uint32_t psize, void *opaque);
+
+static void handle_rx(void *opaque, const uint8_t *buf, int size);
+
+static void a3c90x_cleanup(VLANClientState *vc);
+
+static int compareMACs(uint8_t a[6], uint8_t b[6]);
+
+/*
+ *  misc
+ */
+static int compareMACs(uint8_t a[6], uint8_t b[6])
+{
+    uint32_t i;
+    for (i = 0; i < 6; i++)
+    {
+        if (a[i] != b[i]) return a[i] - b[i];
+    }
+    return 0;
+}
+
+static void a3c90x_reset(A3C90XState *s)
+{
+    /* FIXME: resetting can be done more fine-grained (see TotalReset cmd).
+     *        this is reset ALL regs.
+     */
+    if (sizeof(Registers) != 0x70)
+    {
+        DEBUG_PRINT("sizeof Registers != 0x70\n");
+    }
+
+    RegWindow3 *w3 = (RegWindow3*) &(s->mWindows[3]);
+    RegWindow4 *w4 = (RegWindow4*) &(s->mWindows[4]);
+    RegWindow5 *w5 = (RegWindow5*) &(s->mWindows[5]);
+
+    // internals
+    s->mEEPROMWritable = 0;
+    memset(&(s->mWindows), 0, sizeof s->mWindows);
+    memset(&(s->mRegisters), 0, sizeof s->mRegisters);
+    s->mIntStatus = 0;
+    s->mRxEnabled = 0;
+    s->mTxEnabled = 0;
+    s->mUpStalled = 0;
+    s->mDnStalled = 0;
+    w3->MaxPktSize = 1514 /* FIXME: should depend on sizeof mRxPacket*/;
+    w3->RxFree = 16*1024;
+    w3->TxFree = 16*1024;
+    s->mRxPacketSize = 0;
+    w5->TxStartThresh = 8188;
+    memset(s->mEEPROM, 0, sizeof s->mEEPROM);
+    s->mEEPROM[EEPROM_NodeAddress0] =       (s->mMAC[0]<<8) | s->mMAC[1];
+    s->mEEPROM[EEPROM_NodeAddress1] =       (s->mMAC[2]<<8) | s->mMAC[3];
+    s->mEEPROM[EEPROM_NodeAddress2] =       (s->mMAC[4]<<8) | s->mMAC[5];
+    s->mEEPROM[EEPROM_DeviceID] =           0x9200;
+    s->mEEPROM[EEPROM_ManifacturerID] =     0x6d50;
+    s->mEEPROM[EEPROM_PCIParam] =           0x2940;
+    s->mEEPROM[EEPROM_RomInfo] =            0;  // no ROM
+    s->mEEPROM[EEPROM_OEMNodeAddress0] =    s->mEEPROM[EEPROM_NodeAddress0];
+    s->mEEPROM[EEPROM_OEMNodeAddress1] =    s->mEEPROM[EEPROM_NodeAddress1];
+    s->mEEPROM[EEPROM_OEMNodeAddress2] =    s->mEEPROM[EEPROM_NodeAddress2];
+    s->mEEPROM[EEPROM_SoftwareInfo] =       0x4010;
+    s->mEEPROM[EEPROM_CompWord] =           0;
+    s->mEEPROM[EEPROM_SoftwareInfo2] =      0x00aa;
+    s->mEEPROM[EEPROM_Caps] =               0x72a2;
+    s->mEEPROM[EEPROM_InternalConfig0] =    0;
+    s->mEEPROM[EEPROM_InternalConfig1] =    0x0050; // default is 0x0180
+    s->mEEPROM[EEPROM_SubsystemVendorID] =  0x10b7;
+    s->mEEPROM[EEPROM_SubsystemID] =        0x9200;
+    s->mEEPROM[EEPROM_MediaOptions] =       0x000a;
+    s->mEEPROM[EEPROM_SmbAddress] =         0x6300;
+    s->mEEPROM[EEPROM_PCIParam2] =          0xffb7;
+    s->mEEPROM[EEPROM_PCIParam3] =          0xb7b7;
+    s->mEEPROM[EEPROM_Checksum] =           0;
+
+    // MII
+    memset(s->mMIIRegs, 0, sizeof s->mMIIRegs);
+    MIIRegisters *miiregs = (MIIRegisters*) s->mMIIRegs;
+    miiregs->status = (1<<14) | (1<<13) | (1<<12) | (1<<11) | (1<<5) | (1<<3) | (1<<2) | 1;
+    miiregs->linkPartner = (1<<14) | (1<<7) | 1;
+    miiregs->advert = (1<<14) | (1 << 10) | (1<<7) | 1;
+    s->mMIIReadWord = 0;
+    s->mMIIWriteWord = 0;
+    s->mMIIWrittenBits = 0;
+    s->mLastHiClkPhysMgmt = 0;
+
+    // Register follow-ups
+    w3->MediaOptions = s->mEEPROM[EEPROM_MediaOptions];
+    w3->InternalConfig = s->mEEPROM[EEPROM_InternalConfig0] |
+                         (s->mEEPROM[EEPROM_InternalConfig1] << 16);
+
+    // A valid link is established on the NIC
+    w4->MediaStatus = (1 << 11) | 0x8000;
+
+    // And clean out the RX buffer
+    memset(s->mRxPacket, 0xab, MAX_PACKET_SIZE);
+}
+
+static uint32_t readRegWindow(A3C90XState *s, uint32_t window, uint32_t port, uint32_t data, uint32_t size)
+{
+    DEBUG_PRINT("readRegWindow\n");
+    switch (window)
+    {
+        /* window 0 */
+    case 0:
+    {
+        RegWindow0 *w0 = (RegWindow0*) &s->mWindows[0];
+        switch (port)
+        {
+        case W0_EEPROMCmd:
+        {
+            if (size != 2)
+            {
+                DEBUG_PRINT("WARN: EepromCommand, size != 2\n");
+                return 0;
+            }
+            return w0->EepromCommand;
+            break;
+        }
+        case W0_EEPROMData:
+        {
+            if (size != 2)
+            {
+                DEBUG_PRINT("WARN: EepromData, size != 2\n");
+                return 0;
+            }
+            return w0->EepromData;
+            break;
+        }
+        default:
+            DEBUG_PRINT("WARN: reading here unimpl\n");
+            return 0;
+            break;
+        }
+        break;
+    }
+    /* window 1 */
+    case 1:
+    {
+        data = 0;
+        memcpy(&data, &s->mWindows[1].b[port], size);
+        return data;
+        break;
+    }
+    /* window 2 */
+    case 2:
+    {
+        data = 0;
+        memcpy(&data, &s->mWindows[2].b[port], size);
+        return data;
+        break;
+    }
+    /* window 3 */
+    case 3:
+    {
+        data = 0;
+        memcpy(&data, &s->mWindows[3].b[port], size);
+        return data;
+        break;
+    }
+    /* window 4 */
+    case 4:
+    {
+        DEBUG_PRINT("Read from window 4\n");
+        RegWindow4 *w4 = (RegWindow4*) &s->mWindows[4];
+        data = 0;
+        switch (port)
+        {
+        case 8:
+        {
+            // MII-interface
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.4.8.read\n");
+                return 0;
+            }
+            char mgmtData = (s->mMIIReadWord & 0x80000000) ? 1 : 0;
+            if (mgmtData)
+            {
+                data = w4->PhysMgmt | PM_mgmtData;
+            }
+            else
+            {
+                data = w4->PhysMgmt & (~PM_mgmtData);
+            }
+            break;
+        }
+        case 0xc:
+        {
+            if (size != 1)
+            {
+                DEBUG_PRINT("alignment.4.c.read\n");
+                return 0;
+            }
+            // reading clears
+            w4->BadSSD = 0;
+            memcpy(&data, &s->mWindows[4].b[port], size);
+            return data;
+            break;
+        }
+        default:
+            memcpy(&data, &(s->mWindows[4].b[port]), size);
+            return data;
+        }
+        break;
+    }
+    /* Window 5 */
+    case 5:
+    {
+        data = 0;
+        memcpy(&data, &s->mWindows[5].b[port], size);
+        return data;
+        break;
+    }
+    /* Window 6 */
+    case 6:
+    {
+        RegWindow6 *w6 = (RegWindow6*) &s->mWindows[6];
+        // reading clears
+        if ((port == 0xa) && (size == 2))
+        {
+            // FIXME: BytesRcvdOk really is 20 bits !
+            // when reading here, write upper 4 bits
+            // in w4.UpperBytesOk[3:0]. no clearing.
+            w6->BytesRcvdOk = 0;
+        }
+        else if ((port == 0xc) && (size == 2))
+        {
+            // FIXME: BytesXmittedOk really is 20 bits !
+            // when reading here, write upper 4 bits
+            // in w4.UpperBytesOk[7:4]. no clearing.
+            w6->BytesXmittedOk = 0;
+        }
+        else if ((port == 0) && (size == 1))
+        {
+            w6->CarrierLost = 0;
+        }
+        else if ((port == 8) && (size == 1))
+        {
+            w6->FramesDeferred = 0;
+        }
+        else if ((port == 7) && (size == 1))
+        {
+            // FIXME: FramesRcvdOk really is 10 bits !
+            // when reading here, write upper 2 bits
+            // in w6.UpperFramesOk[1:0]. no clearing.
+        }
+        else if ((port == 6) && (size == 1))
+        {
+            // FIXME: FramesXmittedOk really is 10 bits !
+            // when reading here, write upper 2 bits
+            // in w6.UpperFramesOk[5:4]. no clearing.
+        }
+        else if ((port == 4) && (size == 1))
+        {
+            w6->LateCollisions = 0;
+        }
+        else if ((port == 2) && (size == 1))
+        {
+            w6->MultipleCollisions = 0;
+        }
+        else if ((port == 5) && (size == 1))
+        {
+            w6->RxOverruns = 0;
+        }
+        else if ((port == 3) && (size == 1))
+        {
+            w6->SingleCollisions = 0;
+        }
+        else if ((port == 1) && (size == 1))
+        {
+            w6->SqeErrors = 0;
+        }
+        data = 0;
+        memcpy(&data, &s->mWindows[6].b[port], size);
+        return data;
+        break;
+    }
+    /* Window 7 */
+    case 7:
+    {
+        data = 0;
+        memcpy(&data, &s->mWindows[7].b[port], size);
+        return data;
+        break;
+    }
+    default:
+        DEBUG_PRINT("reading here unimpl.\n");
+    }
+
+    return 0;
+}
+
+static void writeRegWindow(A3C90XState *s, uint32_t window, uint32_t port, uint32_t data, uint32_t size)
+{
+    DEBUG_PRINT("writeRegWindow\n");
+    switch (window)
+    {
+        /* Window 0 */
+    case 0:
+    {
+        RegWindow0 *w0 = (RegWindow0*) &s->mWindows[0];
+        switch (port)
+        {
+        case W0_EEPROMCmd:
+        {
+            if (size != 2)
+            {
+                DEBUG_PRINT("EepromCommand, size != 2\n");
+                return;
+            }
+            w0->EepromCommand = data & 0xff7f;  // clear eepromBusy
+            uint32_t eeprom_addr =  ((data >> 2) & 0xffc0) | (data & 0x3f);
+            switch (data & 0xc0)
+            {
+            case EEOP_SubCmd:
+                switch (data & 0x30)
+                {
+                case EESC_WriteDisable:
+                    DEBUG_PRINT("EESC_WriteDisable\n");
+                    s->mEEPROMWritable = 0;
+                    break;
+                case EESC_WriteAll:
+                    // FIXME: this needs fixing :)
+                    DEBUG_PRINT("WriteAll not impl.\n");
+                    memset(s->mEEPROM, 0xff, sizeof s->mEEPROM);
+                    s->mEEPROMWritable = 0;
+                    break;
+                case EESC_EraseAll:
+                    DEBUG_PRINT("EraseAll not impl.\n");
+                    // SINGLESTEP("");
+                    memset(s->mEEPROM, 0, sizeof s->mEEPROM);
+                    s->mEEPROMWritable = 0;
+                    break;
+                case EESC_WriteEnable:
+                    DEBUG_PRINT("EESC_WriteEnable\n");
+                    s->mEEPROMWritable = 0;
+                    break;
+                default:
+                    DEBUG_PRINT("impossible\n");
+                    // SINGLESTEP("");
+                }
+                break;
+            case EEOP_WriteReg:
+                if (s->mEEPROMWritable)
+                {
+                    if (eeprom_addr*2 < sizeof s->mEEPROM)
+                    {
+                        // disabled
+                        DEBUG_PRINT("EEOP_WriteReg\n");
+                        // SINGLESTEP("");
+                        s->mEEPROM[eeprom_addr] = w0->EepromData;
+                    }
+                    else
+                    {
+                        DEBUG_PRINT("FAILED(out of bounds): EEOP_WriteReg\n");
+                    }
+                    s->mEEPROMWritable = 0;
+                }
+                else
+                {
+                    DEBUG_PRINT("FAILED(not writable): EEOP_WriteReg\n");
+                }
+                break;
+            case EEOP_ReadReg:
+                if (eeprom_addr*2 < sizeof s->mEEPROM)
+                {
+                    w0->EepromData = s->mEEPROM[eeprom_addr];
+                    DEBUG_PRINT("EEOP_ReadReg\n");
+                }
+                else
+                {
+                    DEBUG_PRINT("FAILED(out of bounds): EEOP_ReadReg\n");
+                }
+                break;
+            case EEOP_EraseReg:
+                if (s->mEEPROMWritable)
+                {
+                    if (eeprom_addr*2 < sizeof s->mEEPROM)
+                    {
+                        // disabled
+                        DEBUG_PRINT("EEOP_EraseReg\n");
+                        // SINGLESTEP("");
+                        s->mEEPROM[eeprom_addr] = 0;
+                    }
+                    else
+                    {
+                        DEBUG_PRINT("FAILED(out of bounds): EEOP_EraseReg\n");
+                        // SINGLESTEP("");
+                    }
+                    s->mEEPROMWritable = 0;
+                }
+                else
+                {
+                    DEBUG_PRINT("FAILED(not writable): EEOP_EraseReg\n");
+                    // SINGLESTEP("");
+                }
+                break;
+            default:
+                DEBUG_PRINT("impossible\n");
+                // SINGLESTEP("");
+            }
+            break;
+        }
+        case W0_EEPROMData:
+            if (size != 2)
+            {
+                DEBUG_PRINT("EepromData, size != 2\n");
+                // SINGLESTEP("");
+            }
+            w0->EepromData = data;
+            break;
+        default:
+            DEBUG_PRINT("writing here unimpl.0\n");
+            // SINGLESTEP("");
+            break;
+        }
+        break;
+    }
+    /* Window 2 */
+    case 2:
+    {
+        if (port+size<=0xc)
+        {
+            DEBUG_PRINT("StationAddress or StationMask\n");
+            /* StationAddress or StationMask */
+            memcpy(&s->mWindows[2].b[port], &data, size);
+        }
+        else
+        {
+            DEBUG_PRINT("writing here unimpl.2\n");
+            // SINGLESTEP("");
+        }
+        break;
+    }
+    /* Window 3 */
+    case 3:
+    {
+        RegWindow3 *w3 = (RegWindow3*) &s->mWindows[3];
+        switch (port)
+        {
+        case 0:
+            if (size != 4)
+            {
+                DEBUG_PRINT("alignment.3.0\n");
+                // SINGLESTEP("");
+            }
+            DEBUG_PRINT("InternalConfig\n");
+            w3->InternalConfig = data;
+            break;
+        case 4:
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.3.4\n");
+                // SINGLESTEP("");
+            }
+            DEBUG_PRINT("ERR: MaxPktSize\n");
+            w3->MaxPktSize = data;
+            break;
+        case 6:
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.3.6\n");
+                // SINGLESTEP("");
+            }
+            DEBUG_PRINT("MacControl\n");
+            if (data != 0)
+            {
+                DEBUG_PRINT("setting MacControl != 0\n");
+                // SINGLESTEP("");
+            }
+            w3->MacControl = data;
+            break;
+        case 8:
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.3.8\n");
+                // SINGLESTEP("");
+            }
+            DEBUG_PRINT("MediaOptions\n");
+            w3->MediaOptions = data;
+            break;
+        case 10:
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.3.10\n");
+                // SINGLESTEP("");
+            }
+            DEBUG_PRINT("RxFree\n");
+            // SINGLESTEP("");
+            w3->RxFree = data;
+            break;
+        case 12:
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.3.12\n");
+                // SINGLESTEP("");
+            }
+            DEBUG_PRINT("TxFree\n");
+            // SINGLESTEP("");
+            w3->TxFree = data;
+            break;
+        default:
+            DEBUG_PRINT("writing here unimpl.3\n");
+            // SINGLESTEP("");
+        }
+        break;
+    }
+    /* Window 4 */
+    case 4:
+    {
+        RegWindow4 *w4 = (RegWindow4*) &s->mWindows[4];
+        switch (port)
+        {
+        case 6:
+        {
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.4.6\n");
+                // SINGLESTEP("");
+            }
+            uint32_t mask = 0xf341;
+            DEBUG_PRINT("NetDiagnostic");
+            w4->NetDiagnostic &= ~mask;
+            w4->NetDiagnostic |= data & mask;
+            break;
+        }
+        case 8:
+        {
+            // MII-interface
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.4.8\n");
+                // SINGLESTEP("");
+            }
+            char hiClk = (!((w4->PhysMgmt & PM_mgmtClk) && (data & PM_mgmtClk))) ? 1 : 0;
+            if (hiClk)
+            {
+                // Z means lo edge of mgmtDir
+                char Z = ((s->mLastHiClkPhysMgmt & PM_mgmtDir) && !(data & PM_mgmtDir)) ? 1 : 0;
+                if (Z)
+                {
+                    // check if the 5 frames have been sent
+                    if (((s->mMIIWriteWord >> (s->mMIIWrittenBits-32-2)) & 0x3ffffffffULL) == 0x3fffffffdULL)
+                    {
+                        uint32_t opcode = (s->mMIIWriteWord >> (s->mMIIWrittenBits-32-2-2)) & 3;
+                        uint32_t PHYaddr = (s->mMIIWriteWord >> (s->mMIIWrittenBits-32-2-2-5)) & 0x1f;
+                        uint32_t REGaddr = (s->mMIIWriteWord >> (s->mMIIWrittenBits-32-2-2-5-5)) & 0x1f;
+                        if ((PHYaddr == 0x18 /* hardcoded address [1] p.196 */)
+                                && (REGaddr < 0x10))
+                        {
+                            switch (opcode)
+                            {
+                            case 1:
+                            {
+                                // Opcode Write
+                                DEBUG_PRINT("Opcode Write\n");
+                                if (s->mMIIWrittenBits == 64)
+                                {
+                                    uint32_t value = s->mMIIWriteWord & 0xffff;
+                                    s->mMIIRegs[REGaddr] = value;
+                                }
+                                else
+                                {
+                                    DEBUG_PRINT("But invalid write count\n");
+                                }
+                                s->mMIIWriteWord = 0;
+                                break;
+                            }
+                            case 2:
+                            {
+                                // Opcode Read
+                                DEBUG_PRINT("Opcode Read\n");
+                                if (s->mMIIWrittenBits == 32+2+2+5+5)
+                                {
+                                    // msb gets sent first and is zero to indicated success
+                                    // the register to be sent follows msb to lsb
+                                    s->mMIIReadWord = s->mMIIRegs[REGaddr] << 15;
+                                }
+                                else
+                                {
+                                    DEBUG_PRINT("But invalid write count\n");
+                                }
+                                s->mMIIWriteWord = 0;
+                                break;
+                            }
+                            default:
+                                // error
+                                DEBUG_PRINT("Invalid opcode\n");
+                                s->mMIIReadWord = 0xffffffff;
+                            }
+                        }
+                        else
+                        {
+                            // error
+                            DEBUG_PRINT("Invalid PHY or REG\n");
+                            s->mMIIReadWord = 0xffffffff;
+                        }
+                    }
+                    s->mMIIWrittenBits = 0;
+                    w4->PhysMgmt = data;
+                }
+                else if (data & PM_mgmtDir)
+                {
+                    // write
+                    char mgmtData = (data & PM_mgmtData) ? 1 : 0;
+                    w4->PhysMgmt = data;
+                    s->mMIIWriteWord <<= 1;
+                    s->mMIIWriteWord |= mgmtData ? 1 : 0;
+                    s->mMIIWrittenBits++;
+                }
+                else
+                {
+                    // read
+                    char mgmtData = (s->mMIIReadWord & 0x80000000) ? 1 : 0;
+                    w4->PhysMgmt = data;
+                    if (mgmtData)
+                    {
+                        w4->PhysMgmt = w4->PhysMgmt | PM_mgmtData;
+                    }
+                    else
+                    {
+                        w4->PhysMgmt = w4->PhysMgmt & (~PM_mgmtData);
+                    }
+                    s->mMIIReadWord <<= 1;
+                }
+                s->mLastHiClkPhysMgmt = w4->PhysMgmt;
+            }
+            else
+            {
+                w4->PhysMgmt = data;
+            }
+            break;
+        }
+        case 10:
+        {
+            if (size != 2)
+            {
+                DEBUG_PRINT("alignment.4.10\n");
+                // SINGLESTEP("");
+            }
+            uint32_t mask = 0x10cc;
+            DEBUG_PRINT("MediaStatus\n");
+            w4->MediaStatus &= ~mask;
+            w4->MediaStatus |= data & mask;
+            w4->MediaStatus |= 0x8000;  // auiDisable always on
+            break;
+        }
+        default:
+            DEBUG_PRINT("generic to window 4\n");
+            // SINGLESTEP("");
+            memcpy(&s->mWindows[4].b[port], &data, size);
+        }
+        break;
+    }
+    /**/
+    default:
+        DEBUG_PRINT("writing here unimpl.\n");
+        // SINGLESTEP("");
+    }
+}
+
+static void setCR(uint16_t cr, void *opaque)
+{
+    A3C90XState *s = opaque;
+
+    DEBUG_PRINT("setCR\n");
+    switch (cr & (31<<11))
+    {
+    case CmdTotalReset:
+        // FIXME: care about params
+        DEBUG_PRINT("TotalReset\n");
+        a3c90x_reset(opaque);
+        break;
+    case CmdSelectWindow:
+    {
+        DEBUG_PRINT("SelectWindow\n");
+        s->mIntStatus &= 0x1fff;
+        s->mIntStatus |= (cr & 7)<<13;
+        break;
+    }
+    case CmdTxReset:
+        DEBUG_PRINT("TxReset\n");
+        break;
+    case CmdRxReset:
+        DEBUG_PRINT("RxReset\n");
+        break;
+    case CmdSetIndicationEnable:
+    {
+        RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+        DEBUG_PRINT("SetIndicationEnable\n");
+        w5->IndicationEnable = cr & 0x7fe;
+        break;
+    }
+    case CmdSetIntrEnb:
+    {
+        RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+        DEBUG_PRINT("SetIntrEnab\n");
+        w5->InterruptEnable = cr & 0x7fe;
+        break;
+    }
+    case CmdStatsEnable:
+        /* implement me */
+        DEBUG_PRINT("StatsEnable\n");
+        break;
+    case CmdStatsDisable:
+        /* implement me */
+        DEBUG_PRINT("StatsDisable\n");
+        break;
+    case CmdEnableDC:
+        /* implement me */
+        DEBUG_PRINT("EnableDC\n");
+        break;
+    case CmdDisableDC:
+        /* implement me */
+        DEBUG_PRINT("DisableDC\n");
+        break;
+    case CmdStall:
+    {
+        /* FIXME: threading */
+        switch (cr & 3)
+        {
+        case 0: /* UpStall */
+        case 1: /* UpUnstall */
+        {
+            DEBUG_PRINT("Stall\n");
+            char stall = (!(cr & 1)) ? 1 : 0;
+            s->mUpStalled = stall;
+            checkUpWork(opaque);
+            break;
+        }
+        case 2: /* DnStall */
+        case 3: /* DnUnstall */
+        {
+            DEBUG_PRINT("Stall\n");
+            char stall = (!(cr & 1)) ? 1 : 0;
+            s->mDnStalled = stall;
+            s->mRegisters.DmaCtrl &= ~DC_dnStalled;
+            if (stall) s->mRegisters.DmaCtrl |= DC_dnStalled;
+            checkDnWork(opaque);
+            break;
+        }
+        }
+        break;
+    }
+    case CmdSetRxFilter:
+    {
+        DEBUG_PRINT("SetRxFilter\n");
+        RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+        w5->RxFilter = cr & 31;
+        break;
+    }
+    case CmdSetTxReclaimThresh:
+    {
+        DEBUG_PRINT("SetTxReclaimHash\n");
+        RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+        w5->TxReclaimThresh = cr & 255;
+        break;
+    }
+    case CmdSetTxStartThresh:
+    {
+        DEBUG_PRINT("SetTxStartTresh\n");
+        RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+        w5->TxStartThresh = (cr & 0x7ff) << 2;
+        break;
+    }
+    case CmdSetHashFilterBit:
+    {
+        /** TODO: implement */
+        // char value = (cr & 0x400) ? 1 : 0;
+        // uint32_t which = cr & 0x3f;
+        DEBUG_PRINT("SetHashFilterBit\n");
+        break;
+    }
+    case CmdSetRxEarlyThresh:
+    {
+        DEBUG_PRINT("SetTxStartTresh\n");
+        RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+        w5->RxEarlyThresh = (cr & 0x7ff) << 2;
+        break;
+    }
+    case CmdRxEnable:
+    {
+        DEBUG_PRINT("RxEnable\n");
+        s->mRxEnabled = 1;
+        break;
+    }
+    case CmdRxDisable:
+    {
+        DEBUG_PRINT("RxDisable\n");
+        s->mRxEnabled = 0;
+        break;
+    }
+    case CmdTxEnable:
+    {
+        DEBUG_PRINT("TxEnable\n");
+        s->mTxEnabled = 1;
+        break;
+    }
+    case CmdTxDisable:
+    {
+        DEBUG_PRINT("TxDisable\n");
+        s->mTxEnabled = 0;
+        break;
+    }
+    case CmdAckIntr:
+    {
+        /*
+        0x1     interruptLatchAck
+        0x2     linkEventAck
+        0x20    rxEarlyAck
+        0x40    intRequestedAck
+        0x200   dnCompleteAck
+        0x400   upCompleteAck
+
+        0x5
+        */
+        DEBUG_PRINT("AckIntr\n");
+        // ack/clear corresponding bits in IntStatus
+        uint32_t ISack = 0;
+        if (cr & 0x01) ISack |= IS_interruptLatch;
+        if (cr & 0x02) ISack |= IS_linkEvent;
+        if (cr & 0x20) ISack |= IS_rxEarly;
+        if (cr & 0x40) ISack |= IS_intRequested;
+        if (cr & 0x200) ISack |= IS_dnComplete;
+        if (cr & 0x400) ISack |= IS_upComplete;
+        acknowledge(ISack, opaque);
+        break;
+    }
+    /*  case CmdReqIntr: {
+            RegWindow5 &w5 = (RegWindow5&)mWindows[5];
+            // set intRequested in IntStatus
+            mIntStatus |= IS_intRequested;
+
+            // FIXME: generate Interrupt (if enabled)
+            break;
+        }*/
+
+    /*
+        case CmdTxDone:
+        case CmdRxDiscard:
+        case CmdSetTxThreshold:
+    */
+    default:
+        DEBUG_PRINT("command not implemented\n");
+    }
+}
+
+static void txDPD0(void *opaque, DPD0 *dpd)
+{
+    A3C90XState *s = opaque;
+
+    DEBUG_PRINT("txDPD0\n");
+
+    // FIXME: createHostStruct()
+    uint32_t fsh = dpd->FrameStartHeader;
+    DEBUG_PRINT_FORMAT(("fsh = %08x\n", fsh));
+    if (fsh & FSH_dpdEmpty)
+    {
+        // modify FrameStartHeader in DPD (!)
+        dpd->FrameStartHeader |= FSH_dnComplete;
+        // set next DnListPtr
+        s->mRegisters.DnListPtr = dpd->DnNextPtr;
+        DEBUG_PRINT("dpd empty\n");
+        return;
+    }
+    DPDFragDesc *frags = (DPDFragDesc*)(dpd+1);
+    uint8_t pbuf[MAX_PACKET_SIZE];
+    uint8_t *p = pbuf;
+
+    // some packet drivers need padding
+    // uint framePrefix = mEthTun->getWriteFramePrefix();
+    // memset(p, 0, framePrefix);
+    // p += framePrefix;
+
+    DEBUG_PRINT_FORMAT(("DPD: NextPtr = %x, FSH = %x, FragAddr = %x, FragLen = %x\n",
+                        dpd->DnNextPtr, dpd->FrameStartHeader, frags->DnFragAddr, frags->DnFragLen));
+
+    //
+    uint32_t i = 0;
+    // assemble packet from fragments (up to MAX_DPD_FRAGS fragments)
+    while (i < MAX_DPD_FRAGS)
+    {
+        uint32_t addr = frags->DnFragAddr;
+        uint32_t len = frags->DnFragLen & 0x1fff;
+        if (p-pbuf+len >= sizeof pbuf)
+        {
+            DEBUG_PRINT("packet too big!\n");
+            // SINGLESTEP("");
+            return;
+        }
+        DEBUG_PRINT("dma_read\n");
+
+        if (len == 0 || addr == 0)
+        {
+            DEBUG_PRINT("No data! Bail!\n");
+            return;
+        }
+
+        cpu_physical_memory_read(addr, p, len);
+
+        DEBUG_PRINT_FORMAT((" - DnAddr = %x, DnFragLen = %x\n", frags->DnFragAddr, frags->DnFragLen));
+
+        p += len;
+        // last fragment ?
+        if (frags->DnFragLen & 0x80000000) break;
+        frags++;
+        i++;
+    }
+    uint32_t psize = p-pbuf;
+    if (!(fsh & FSH_rndupDefeat))
+    {
+        // round packet length
+        switch (fsh & FSH_rndupBndry)
+        {
+        case 0:
+        {
+            // 4 bytes
+            uint32_t gap = ((psize+3) & ~3) -psize;
+            memset(pbuf+psize, 0, gap);
+            psize += gap;
+            break;
+        }
+        case 2:
+        {
+            // 2 bytes
+            uint32_t gap = ((psize+1) & ~1) -psize;
+            memset(pbuf+psize, 0, gap);
+            psize += gap;
+            break;
+        }
+        }
+    }
+    //FSH_reArmDisable =    1<<23,
+    //FSH_lastKap =         1<<24,
+    //FSH_addIpChecksum =   1<<25,
+    //FSH_addTcpChecksum =  1<<26,
+    //FSH_addUdpChecksum =  1<<27,
+    if (fsh & (0x1f << 23))
+    {
+        DEBUG_PRINT("unsupported flags in fsh\n");
+    }
+
+    if (psize<60)
+    {
+        // pad packet to at least 60 bytes (+4 bytes crc = 64 bytes)
+        memset(pbuf+psize, 0, (60-psize));
+        psize = 60;
+    }
+    // append crc
+    if (!(fsh & FSH_crcAppendDisable))
+    {
+#ifdef A3C90x_CALCULATE_TXCRC
+        uint32_t crc = crc32(0, pbuf, psize);
+#else
+        uint32_t crc = 0;
+#endif
+        pbuf[psize+0] = crc;
+        pbuf[psize+1] = crc>>8;
+        pbuf[psize+2] = crc>>16;
+        pbuf[psize+3] = crc>>24;
+        psize += 4;
+        DEBUG_PRINT("crc complete\n");
+    }
+
+    AUX_DEBUG_PRINT("3C90X: Packet sent\n");
+
+    qemu_send_packet(s->vc, pbuf, psize);
+
+    // indications
+    s->mRegisters.DmaCtrl |= DC_dnComplete;
+    uint8_t txStatus = 0;
+    uint32_t inds = 0;
+    if (fsh & FSH_dnIndicate) inds |= IS_dnComplete;
+    if (fsh & FSH_txIndicate)
+    {
+        inds |= IS_txComplete;
+        txStatus |= (1 << 6);
+    }
+
+    // transmit complete
+    txStatus |= (1 << 7);
+
+    indicate(inds, opaque);
+    // modify FrameStartHeader in DPD (!)
+    dpd->FrameStartHeader |= FSH_dnComplete;
+    // set next DnListPtr, TxPktId
+    s->mRegisters.DnListPtr = dpd->DnNextPtr;
+    uint32_t pktId = (fsh & FSH_pktId) >> 2;
+    s->mRegisters.TxPktId = pktId;
+    s->mRegisters.TxStatus = txStatus;
+    // maybe generate interrupt
+    maybeRaiseIntr(opaque);
+}
+
+static char passesRxFilter(uint8_t *pbuf, uint32_t psize, void *opaque)
+{
+    A3C90XState *s = opaque;
+
+    EthFrameII *f = (EthFrameII*) pbuf;
+    RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+    if (w5->RxFilter & RXFILT_receiveAllFrames) return 1;
+    // FIXME: Multicast hashing not implemented
+    if (w5->RxFilter & RXFILT_receiveMulticastHash) return 1;
+    // FIXME: Multicasting not understood
+    if (w5->RxFilter & RXFILT_receiveMulticast) return 1;
+    if (w5->RxFilter & RXFILT_receiveBroadcast)
+    {
+        uint8_t broadcastMAC[6] = {0xff,0xff,0xff,0xff,0xff,0xff};
+        if (compareMACs(f->destMAC, broadcastMAC) == 0) return 1;
+    }
+    if (w5->RxFilter & RXFILT_receiveIndividual)
+    {
+        uint8_t destMAC[6];
+        uint8_t thisMAC[6];
+        RegWindow2 *w2 = (RegWindow2*) &s->mWindows[2];
+        uint32_t i;
+        for (i = 0; i < 6; i++)
+        {
+            destMAC[i] = f->destMAC[i] & ~w2->StationMask[i];
+            thisMAC[i] = w2->StationAddress[i] & ~w2->StationMask[i];
+        }
+        return (compareMACs(destMAC, thisMAC) == 0) ? 1 : 0;
+    }
+    return 0;
+}
+
+static void rxUPD(void *opaque, UPD *upd)
+{
+    A3C90XState *s = opaque;
+
+    // FIXME: threading to care about (mRegisters.DmaCtrl & DC_upAltSeqDisable)
+    DEBUG_PRINT("rxUPD()\n");
+
+    char error = 0;
+
+    if (upd->UpPktStatus & UPS_upComplete)
+    {
+        // IO_3C90X_WARN("UPD already upComplete!\n");
+
+        // the top of the ring buffer is already used,
+        // stall the upload and throw away the packet.
+        // the ring buffers are filled.
+
+        s->mUpStalled = 1;
+        return;
+    }
+
+    uint32_t upPktStatus = 0;
+
+    if (s->mRegisters.UpPoll)
+    {
+        DEBUG_PRINT("UpPoll unsupported\n");
+        // SINGLESTEP("");
+        return;
+    }
+    // FIXME:
+//  if (mRegisters.DmaCtrl & DC_upRxEarlyEnable)
+//      IO_3C90X_ERR("DC_upRxEarlyEnable unsupported\n");
+
+    if ((s->mRxPacketSize > 0x1fff) || (s->mRxPacketSize > sizeof s->mRxPacket))
+    {
+        DEBUG_PRINT("oversized frame\n");
+        upd->UpPktStatus = UPS_upError | UPS_oversizedFrame;
+        error = 1;
+    }
+
+    if (s->mRxPacketSize < 60)
+    {
+        // pad packet to at least 60 bytes (+4 bytes crc = 64 bytes)
+        memset(s->mRxPacket+s->mRxPacketSize, 0, (60-s->mRxPacketSize));
+        s->mRxPacketSize = 60;
+    }
+
+    /*  RegWindow5 &w5 = (RegWindow5&)mWindows[5];
+        if ((mRxPacketSize < 60) && (w5.RxEarlyThresh >= 60)) {
+            IO_3C90X_TRACE("runt frame\n");
+            upPktStatus |= UPS_upError | UPS_runtFrame;
+            upd->UpPktStatus = upPktStatus;
+            error = true;
+        }*/
+    if (upd->UpPktStatus & UPD_impliedBufferEnable)
+    {
+        DEBUG_PRINT("UPD_impliedBufferEnable unsupported\n");
+        // SINGLESTEP("");
+        return;
+    }
+    UPDFragDesc *frags = (UPDFragDesc*)(upd+1);
+
+    uint8_t *p = s->mRxPacket;
+    uint32_t i = 0;
+    while (!error && i < MAX_UPD_FRAGS)     // (up to MAX_UPD_FRAGS fragments)
+    {
+        uint32_t addr = frags->UpFragAddr;
+        uint32_t len = frags->UpFragLen & 0x1fff;
+        if (p-s->mRxPacket+len > sizeof s->mRxPacket)
+        {
+            upPktStatus |= UPS_upError | UPS_upOverflow;
+            upd->UpPktStatus = upPktStatus;
+            DEBUG_PRINT("UPD overflow!\n");
+            // SINGLESTEP("");
+            error = 1;
+            break;
+        }
+
+        cpu_physical_memory_write(addr, p, len);
+
+        p += len;
+        // last fragment ?
+        if (frags->UpFragLen & 0x80000000) break;
+        frags++;
+        i++;
+    }
+
+    if (!error)
+    {
+        DEBUG_PRINT("successfully uploaded packet\n");
+    }
+    upPktStatus |= s->mRxPacketSize & 0x1fff;
+    upPktStatus |= UPS_upComplete;
+    upd->UpPktStatus = upPktStatus;
+
+    s->mRxPacketSize = 0;
+
+    /* The client OS is waiting for a change in status, but won't see it
+     * until we dma our local copy upd->UpPktStatus back to the client address space
+     */
+    cpu_physical_memory_write(s->mRegisters.UpListPtr + 4, (const uint8_t*) &(upd->UpPktStatus), sizeof(upd->UpPktStatus));
+
+    s->mRegisters.UpListPtr = upd->UpNextPtr;
+
+    // Indications
+    s->mRegisters.DmaCtrl |= DC_upComplete;
+    indicate(IS_upComplete, opaque);
+    maybeRaiseIntr(opaque);
+}
+
+static void indicate(uint32_t indications, void *opaque)
+{
+    A3C90XState *s = opaque;
+
+    RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+    if ((w5->IndicationEnable & indications) != indications)
+    {
+        DEBUG_PRINT("some masked\n");
+    }
+    s->mIntStatus |= w5->IndicationEnable & indications;
+    if (indications & IS_upComplete)
+    {
+        s->mRegisters.DmaCtrl |= DC_upComplete;
+    }
+    if (indications & IS_dnComplete)
+    {
+        s->mRegisters.DmaCtrl |= DC_dnComplete;
+    }
+}
+
+static void acknowledge(uint32_t indications, void *opaque)
+{
+    A3C90XState *s = opaque;
+
+    DEBUG_PRINT_FORMAT(("intStatus was %x [indications=%x]\n", s->mIntStatus, indications));
+    s->mIntStatus &= ~indications;
+    DEBUG_PRINT_FORMAT(("intStatus is now %x\n", s->mIntStatus));
+    if (indications & IS_upComplete)
+    {
+        s->mRegisters.DmaCtrl &= ~DC_upComplete;
+    }
+    if (indications & IS_dnComplete)
+    {
+        s->mRegisters.DmaCtrl &= ~DC_dnComplete;
+    }
+
+    // lower the irq line now that the IRQ is ack'd
+    qemu_set_irq(s->pci_dev->irq[0], 0);
+}
+
+static void maybeRaiseIntr(void *opaque)
+{
+    A3C90XState *s = opaque;
+    RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+
+    DEBUG_PRINT("maybeRaiseIntr\n");
+    DEBUG_PRINT_FORMAT(("IndEnable = %x, IntEnable = %x, IntStatus = %x\n", w5->IndicationEnable, w5->InterruptEnable, s->mIntStatus));
+
+    if (w5->IndicationEnable & w5->InterruptEnable & s->mIntStatus)
+    {
+        s->mIntStatus |= IS_interruptLatch;
+
+        DEBUG_PRINT("Generating interrupt!\n");
+
+        // raise the IRQ line
+        qemu_set_irq(s->pci_dev->irq[0], 1);
+    }
+    else
+    {
+        // lower the IRQ line
+        qemu_set_irq(s->pci_dev->irq[0], 0);
+    }
+}
+
+static void checkDnWork(void *opaque)
+{
+    A3C90XState *s = opaque;
+
+    while (!s->mDnStalled && (s->mRegisters.DnListPtr != 0))
+    {
+        uint8_t dpd[512];
+
+        cpu_physical_memory_read(s->mRegisters.DnListPtr, dpd, sizeof dpd);
+
+        uint8_t type = dpd[7] >> 6;
+        switch (type)
+        {
+        case 0:
+        case 2:
+        {
+            DPD0 *p = (DPD0*) dpd;
+            DEBUG_PRINT("Got a type 0 DPD!\n");
+            txDPD0(opaque, p);
+            break;
+        }
+        case 1:
+        {
+            DEBUG_PRINT("Got a type 1 DPD! Not implemented!\n");
+            s->mRegisters.DnListPtr = 0;
+            break;
+        }
+        default:
+            DEBUG_PRINT("Unsupported packet type\n");
+            s->mRegisters.DnListPtr = 0;
+            break;
+        };
+
+        break;
+    }
+}
+
+static void checkUpWork(void *opaque)
+{
+    A3C90XState *s = opaque;
+
+    if (s->mRxEnabled && !s->mUpStalled && s->mRxPacketSize && (s->mRegisters.UpListPtr != 0))
+    {
+        uint8_t upd[MAX_UPD_SIZE];
+
+        cpu_physical_memory_read(s->mRegisters.UpListPtr, upd, sizeof upd);
+        UPD *p = (UPD*) upd;
+        rxUPD(opaque, p);
+
+    }
+    else
+    {
+        DEBUG_PRINT("Not uploading\n");
+        DEBUG_PRINT_FORMAT(("rxEnabled = %x, upStalled = %x, RX Packet size = %x, Up list ptr = %x\n",
+                            s->mRxEnabled, s->mUpStalled, s->mRxPacketSize, s->mRegisters.UpListPtr));
+    }
+}
+
+static void handle_rx(void *opaque, const uint8_t *buf, int size)
+{
+    A3C90XState *s = opaque;
+
+    DEBUG_PRINT_FORMAT(("3c90x: handle_rx (%d bytes)\n", size));
+    if (s->mRxPacketSize)
+    {
+        DEBUG_PRINT("Old packet not yet uploaded!\n");
+    }
+    else
+    {
+        s->mRxPacketSize = size;
+        memcpy(s->mRxPacket, buf, size);
+        if (s->mRxEnabled && (s->mRxPacketSize > sizeof(EthFrameII)))
+        {
+            indicate(IS_rxComplete, opaque);
+            maybeRaiseIntr(opaque);
+            acknowledge(IS_rxComplete, opaque);
+            if (!passesRxFilter(s->mRxPacket, s->mRxPacketSize, opaque))
+            {
+                DEBUG_PRINT_FORMAT(("Received %d bytes, but they don't pass the filter!\n", s->mRxPacketSize));
+                s->mRxPacketSize = 0;
+            }
+            else
+            {
+                // and now, we do some extra debugging output...
+#ifdef DEBUG_3C90X_ANALYSE_FRAMES
+                EthFrameII *ethFrame = (EthFrameII*) s->mRxPacket;
+                AUX_DEBUG_PRINT_FORMAT(("Incoming packet information [%d bytes]:\n", s->mRxPacketSize));
+                if (ethFrame->type[0] == 8)
+                {
+                    if (ethFrame->type[1] == 0x06)
+                        AUX_DEBUG_PRINT("ARP\n");
+                    else if (ethFrame->type[1] == 0)
+                    {
+                        struct ip *ipHeader = (struct ip*) (s->mRxPacket + sizeof(EthFrameII));
+                        if (ipHeader->ip_p == 0x11)
+                        {
+                            struct udphdr *udpHeader = (struct udphdr*) (s->mRxPacket + sizeof(EthFrameII) + (ipHeader->ip_hl * 4));
+                            AUX_DEBUG_PRINT_FORMAT(("UDP: src=%d dest=%d\n", ntohs(udpHeader->uh_sport), ntohs(udpHeader->uh_dport)));
+                        }
+                        else if (ipHeader->ip_p == 0x06)
+                        {
+                            struct tcphdr *tcpHeader = (struct tcphdr*) (s->mRxPacket + sizeof(EthFrameII) + (ipHeader->ip_hl * 4));
+                            AUX_DEBUG_PRINT_FORMAT(("TCP: src=%d dest=%d ", ntohs(tcpHeader->th_sport), ntohs(tcpHeader->th_dport)));
+                            AUX_DEBUG_PRINT("flags =");
+                            if (tcpHeader->th_flags & TH_FIN)
+                                AUX_DEBUG_PRINT(" FIN");
+                            if (tcpHeader->th_flags & TH_SYN)
+                                AUX_DEBUG_PRINT(" SYN");
+                            if (tcpHeader->th_flags & TH_RST)
+                                AUX_DEBUG_PRINT(" RST");
+                            if (tcpHeader->th_flags & TH_PUSH)
+                                AUX_DEBUG_PRINT(" PSH");
+                            if (tcpHeader->th_flags & TH_ACK)
+                                AUX_DEBUG_PRINT(" ACK");
+                            if (tcpHeader->th_flags & TH_URG)
+                                AUX_DEBUG_PRINT(" URG");
+                            AUX_DEBUG_PRINT_FORMAT((" seq=%d ack=%d", ntohl(tcpHeader->th_seq), ntohl(tcpHeader->th_ack)));
+                            AUX_DEBUG_PRINT("\n");
+                        }
+                    }
+                }
+                else
+                    AUX_DEBUG_PRINT("(can't inspect)\n");
+#endif
+                DEBUG_PRINT_FORMAT(("Received %d bytes!\n", s->mRxPacketSize));
+            }
+        }
+        else
+        {
+            DEBUG_PRINT_FORMAT(("Oops - RxEnabled = %x, packetSize = %d [eth=%d]\n", s->mRxEnabled, s->mRxPacketSize, sizeof(EthFrameII)));
+            s->mRxPacketSize = 0;
+        }
+    }
+    checkUpWork(opaque);
+}
+
+static int a3c90x_can_receive(void *opaque)
+{
+    A3C90XState *s = opaque;
+    if (s->mRxEnabled)
+    {
+        if (s->mRxPacketSize)
+        {
+            // If there's already a packet there, try and upload it again
+            DEBUG_PRINT("Old packet not yet uploaded!\n");
+            checkUpWork(opaque);
+            return 0;
+        }
+        else
+            return 1;
+    }
+    else
+        return 0;
+}
+
+static void a3c90x_receive(void *opaque, const uint8_t *buf, int size)
+{
+    AUX_DEBUG_PRINT("3C90X: Packet received\n");
+    handle_rx(opaque, buf, size);
+}
+
+static uint32_t a3c90x_io_readx(void *opaque, uint8_t port, int size)
+{
+    A3C90XState *s = opaque;
+    uint32_t data = 0;
+
+    if (port == 0xe)
+    {
+        // IntStatus (no matter which window)
+        if (size != 2)
+        {
+            DEBUG_PRINT("unaligned read from IntStatus\n");
+        }
+        DEBUG_PRINT("read IntStatus\n");
+        return s->mIntStatus;
+    }
+    else if (port >= 0 && (port+size <= 0x0e))
+    {
+        // read from window
+        uint32_t curwindow = s->mIntStatus >> 13;
+        return readRegWindow(opaque, curwindow, port, data, size);
+    }
+    else if ((port+size > 0x1e) && (port <= 0x1f))
+    {
+        if ((port != 0x1e) || (size != 2))
+        {
+            DEBUG_PRINT("unaligned read from IntStatusAuto\n");
+        }
+        RegWindow5 *w5 = (RegWindow5*) &s->mWindows[5];
+        // side-effects of reading IntStatusAuto:
+        // 1.clear InterruptEnable
+        w5->InterruptEnable = 0;
+        // 2.clear some flags
+        acknowledge(IS_dnComplete | IS_upComplete
+                    | IS_rxEarly | IS_intRequested
+                    | IS_interruptLatch | IS_linkEvent, opaque);
+        DEBUG_PRINT("read IntStatusAuto\n");
+        return s->mIntStatus;
+    }
+    else if ((port >= 0x10) && (port+size <= 0x10 + sizeof(Registers)))
+    {
+        uint8_t l = gRegAccess[port-0x10];
+        if (l != size)
+        {
+            DEBUG_PRINT("invalid/unaligned read\n");
+        }
+        // read from (standard) register
+        memcpy(&data, ((uint8_t*)&s->mRegisters)+port-0x10, size);
+        switch (port)
+        {
+        case 0x1a:
+            DEBUG_PRINT("read Timer\n");
+            break;
+        case 0x20:
+            DEBUG_PRINT("read DmaCtrl\n");
+            break;
+        case 0x24:
+            DEBUG_PRINT("read DownListPtr\n");
+            break;
+        case 0x38:
+            DEBUG_PRINT("read UpListPtr\n");
+            break;
+        default:
+            DEBUG_PRINT("read reg\n");
+            break;
+        }
+        return data;
+    }
+    return 0;
+}
+
+static void a3c90x_io_writex(void *opaque, uint8_t port, uint32_t data, int size)
+{
+    A3C90XState *s = opaque;
+
+    if (port == 0xe)
+    {
+        // CommandReg (no matter which window)
+        if (size != 2)
+        {
+            DEBUG_PRINT("unaligned write to CommandReg\n");
+        }
+        setCR(data, opaque);
+    }
+    else if (port >= 0 && (port+size <= 0x0e))
+    {
+        // write to window
+        uint32_t curwindow = s->mIntStatus >> 13;
+        writeRegWindow(opaque, curwindow, port, data, size);
+    }
+    else if (port >= 0x10 && (port + size <= 0x10 + sizeof(Registers)))
+    {
+        uint8_t l = gRegAccess[port-0x10];
+        if (l != size)
+        {
+            DEBUG_PRINT("invalid/unaligned write to register\n");
+        }
+        switch (port)
+        {
+        case 0x20:
+        {
+            uint32_t DmaCtrlRWMask = DC_upRxEarlyEnable | DC_counterSpeed |
+                                     DC_countdownMode | DC_defeatMWI | DC_defeatMRL |
+                                     DC_upOverDiscEnable;
+            s->mRegisters.DmaCtrl &= ~DmaCtrlRWMask;
+            s->mRegisters.DmaCtrl |= data & DmaCtrlRWMask;
+            DEBUG_PRINT("write DmaCtrl\n");
+            break;
+        }
+        case 0x24:
+        {
+            if (!s->mRegisters.DnListPtr)
+            {
+                s->mRegisters.DnListPtr = data;
+                DEBUG_PRINT("write DnListPtr\n");
+            }
+            else
+            {
+                DEBUG_PRINT("didn't write DnListPtr cause it's not 0\n");
+            }
+            checkDnWork(opaque);
+            break;
+        }
+        case 0x38:
+        {
+            s->mRegisters.UpListPtr = data;
+            DEBUG_PRINT("write UpListPtr\n");
+            checkUpWork(opaque);
+            break;
+        }
+        case 0x2d:
+            DEBUG_PRINT("DnPoll\n");
+            // SINGLESTEP("");
+            break;
+        case 0x2a:
+            memcpy(((uint8_t*)&s->mRegisters)+port-0x10, &data, size);
+            DEBUG_PRINT("write DnBurstThresh\n");
+            break;
+        case 0x2c:
+            memcpy(((uint8_t*)&s->mRegisters)+port-0x10, &data, size);
+            DEBUG_PRINT("write DnPriorityThresh\n");
+            break;
+        case 0x2f:
+            // used by Darwin as TxFreeThresh. Not documented in [1].
+            memcpy(((uint8_t*)&s->mRegisters)+port-0x10, &data, size);
+            DEBUG_PRINT("write TxFreeThresh\n");
+            break;
+        case 0x3c:
+            memcpy(((uint8_t*)&s->mRegisters)+port-0x10, &data, size);
+            DEBUG_PRINT("write UpPriorityThresh\n");
+            break;
+        case 0x3e:
+            memcpy(((uint8_t*)&s->mRegisters)+port-0x10, &data, size);
+            DEBUG_PRINT("write UpBurstThresh\n");
+            break;
+        case 0x1b:
+            if (size != 1)
+            {
+                DEBUG_PRINT("wrong size for write to TxStatus\n");
+                return;
+            }
+            DEBUG_PRINT_FORMAT(("Writing %x to TxStatus\n", data));
+            s->mRegisters.TxStatus = data;
+
+            // acknowledge the relevant bits
+            acknowledge(IS_txComplete, opaque);
+            //  | IS_dnComplete | IS_cmdInProgress
+
+            // NOTE: "An I/O write of an arbitrary value to TxStatus advances the queue to the next transmit status byte."
+            // We also need to keep the queue of TX Status bytes!
+
+            // maybeRaiseIntr(opaque);
+            break;
+        default:
+            DEBUG_PRINT("write to register\n");
+            // SINGLESTEP("");
+            // write to (standard) register
+            memcpy(((uint8_t*)&s->mRegisters)+port-0x10, &data, size);
+        }
+    }
+}
+
+static void a3c90x_io_writeb(void *opaque, uint8_t addr, uint32_t val)
+{
+    a3c90x_io_writex(opaque, addr, val, 1);
+}
+
+static void a3c90x_io_writew(void *opaque, uint8_t addr, uint32_t val)
+{
+    a3c90x_io_writex(opaque, addr, val, 2);
+}
+
+static void a3c90x_io_writel(void *opaque, uint8_t addr, uint32_t val)
+{
+    a3c90x_io_writex(opaque, addr, val, 4);
+}
+
+static uint32_t a3c90x_io_readb(void *opaque, uint8_t addr)
+{
+    return a3c90x_io_readx(opaque, addr, 1);
+}
+
+static uint32_t a3c90x_io_readw(void *opaque, uint8_t addr)
+{
+    return a3c90x_io_readx(opaque, addr, 2);
+}
+
+static uint32_t a3c90x_io_readl(void *opaque, uint8_t addr)
+{
+    return a3c90x_io_readx(opaque, addr, 4);
+}
+
+/* */
+
+static void a3c90x_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
+{
+    a3c90x_io_writeb(opaque, addr & 0xFF, val);
+}
+
+static void a3c90x_ioport_writew(void *opaque, uint32_t addr, uint32_t val)
+{
+    a3c90x_io_writew(opaque, addr & 0xFF, val);
+}
+
+static void a3c90x_ioport_writel(void *opaque, uint32_t addr, uint32_t val)
+{
+    a3c90x_io_writel(opaque, addr & 0xFF, val);
+}
+
+static uint32_t a3c90x_ioport_readb(void *opaque, uint32_t addr)
+{
+    return a3c90x_io_readb(opaque, addr & 0xFF);
+}
+
+static uint32_t a3c90x_ioport_readw(void *opaque, uint32_t addr)
+{
+    return a3c90x_io_readw(opaque, addr & 0xFF);
+}
+
+static uint32_t a3c90x_ioport_readl(void *opaque, uint32_t addr)
+{
+    return a3c90x_io_readl(opaque, addr & 0xFF);
+}
+
+/* */
+
+static void a3c90x_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    a3c90x_io_writeb(opaque, addr & 0xFF, val);
+}
+
+static void a3c90x_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+#ifdef TARGET_WORDS_BIGENDIAN
+    val = bswap16(val);
+#endif
+    a3c90x_io_writew(opaque, addr & 0xFF, val);
+}
+
+static void a3c90x_mmio_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+#ifdef TARGET_WORDS_BIGENDIAN
+    val = bswap32(val);
+#endif
+    a3c90x_io_writel(opaque, addr & 0xFF, val);
+}
+
+static uint32_t a3c90x_mmio_readb(void *opaque, target_phys_addr_t addr)
+{
+    return a3c90x_io_readb(opaque, addr & 0xFF);
+}
+
+static uint32_t a3c90x_mmio_readw(void *opaque, target_phys_addr_t addr)
+{
+    uint32_t val = a3c90x_io_readw(opaque, addr & 0xFF);
+#ifdef TARGET_WORDS_BIGENDIAN
+    val = bswap16(val);
+#endif
+    return val;
+}
+
+static uint32_t a3c90x_mmio_readl(void *opaque, target_phys_addr_t addr)
+{
+    uint32_t val = a3c90x_io_readl(opaque, addr & 0xFF);
+#ifdef TARGET_WORDS_BIGENDIAN
+    val = bswap32(val);
+#endif
+    return val;
+}
+
+/***********************************************************/
+/* PCI 3C90x definitions */
+
+typedef struct PCI3C90XState
+{
+    PCIDevice dev;
+    A3C90XState a3c90x;
+} PCI3C90XState;
+
+static void a3c90x_mmio_map(PCIDevice *pci_dev, int region_num,
+                            uint32_t addr, uint32_t size, int type)
+{
+    PCI3C90XState *d = (PCI3C90XState *)pci_dev;
+    A3C90XState *s = &d->a3c90x;
+
+    cpu_register_physical_memory(addr + 0, 0x100, s->a3c90x_mmio_io_addr);
+}
+
+static void a3c90x_ioport_map(PCIDevice *pci_dev, int region_num,
+                              uint32_t addr, uint32_t size, int type)
+{
+    PCI3C90XState *d = (PCI3C90XState *)pci_dev;
+    A3C90XState *s = &d->a3c90x;
+
+    register_ioport_write(addr, 0x100, 1, a3c90x_ioport_writeb, s);
+    register_ioport_read( addr, 0x100, 1, a3c90x_ioport_readb,  s);
+
+    register_ioport_write(addr, 0x100, 2, a3c90x_ioport_writew, s);
+    register_ioport_read( addr, 0x100, 2, a3c90x_ioport_readw,  s);
+
+    register_ioport_write(addr, 0x100, 4, a3c90x_ioport_writel, s);
+    register_ioport_read( addr, 0x100, 4, a3c90x_ioport_readl,  s);
+}
+
+static CPUReadMemoryFunc *a3c90x_mmio_read[3] =
+{
+    a3c90x_mmio_readb,
+    a3c90x_mmio_readw,
+    a3c90x_mmio_readl,
+};
+
+static CPUWriteMemoryFunc *a3c90x_mmio_write[3] =
+{
+    a3c90x_mmio_writeb,
+    a3c90x_mmio_writew,
+    a3c90x_mmio_writel,
+};
+
+static inline int64_t a3c90x_get_next_tctr_time(A3C90XState *s, int64_t current_time)
+{
+    int64_t next_time = current_time +
+                        muldiv64(1, ticks_per_sec, PCI_FREQUENCY);
+    if (next_time <= current_time)
+        next_time = current_time + 1;
+    return next_time;
+}
+
+void a3c90x_cleanup(VLANClientState *vc)
+{
+    DEBUG_PRINT("3C90X: Cleanup not implemented\n");
+}
+
+static void pci_a3c90x_init(PCIDevice *pci_dev)
+{
+    PCI3C90XState *d;
+    A3C90XState *s;
+    uint8_t *pci_conf;
+
+    d = (PCI3C90XState *) pci_dev;
+    pci_conf = d->dev.config;
+    pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_3COM);
+    pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_3C90X);
+    pci_conf[0x04] = 0x07; /* command = I/O space, Bus Master */
+    pci_conf[0x08] = 0;
+    pci_config_set_class(pci_conf, PCI_CLASS_NETWORK_ETHERNET);
+    pci_conf[0x0e] = 0x00; /* header_type */
+    pci_conf[0x3d] = 1;    /* interrupt pin 0 */
+    pci_conf[0x34] = 0xdc;
+
+    pci_conf[0x3e] = 5;
+    pci_conf[0x3f] = 48;
+
+    s = &d->a3c90x;
+
+    /* I/O handler for memory-mapped I/O */
+    s->a3c90x_mmio_io_addr =
+        cpu_register_io_memory(0, a3c90x_mmio_read, a3c90x_mmio_write, s);
+
+    pci_register_io_region(&d->dev, 0, 0x100,
+                           PCI_ADDRESS_SPACE_IO,  a3c90x_ioport_map);
+
+    pci_register_io_region(&d->dev, 1, 0x100,
+                           PCI_ADDRESS_SPACE_MEM, a3c90x_mmio_map);
+
+    s->pci_dev = (PCIDevice *)d;
+    qdev_get_macaddr(&d->dev.qdev, s->mMAC);
+    a3c90x_reset(s);
+    s->vc = qdev_get_vlan_client(&d->dev.qdev,
+                                 a3c90x_receive, a3c90x_can_receive,
+                                 a3c90x_cleanup, s);
+
+    qemu_format_nic_info_str(s->vc, s->mMAC);
+
+    //register_savevm("3c90x", -1, 4, a3c90x_save, a3c90x_load, s);
+}
+
+static void a3c90x_register_devices(void)
+{
+    pci_qdev_register("3c90x", sizeof(PCI3C90XState), pci_a3c90x_init);
+}
+
+device_init(a3c90x_register_devices)
diff --git a/hw/pci.c b/hw/pci.c
index 4458079..e11dda6 100644
--- a/hw/pci.c
+++ b/hw/pci.c
@@ -771,6 +771,7 @@ static const char * const pci_nic_models[] = {
     "i82557b",
     "i82559er",
     "rtl8139",
+	"3c90x",
     "e1000",
     "pcnet",
     "virtio",
@@ -783,6 +784,7 @@ static const char * const pci_nic_names[] = {
     "i82557b",
     "i82559er",
     "rtl8139",
+	"3c90x",
     "e1000",
     "pcnet",
     "virtio-net-pci",
diff --git a/hw/pci_ids.h b/hw/pci_ids.h
index 3afe674..edbbcb7 100644
--- a/hw/pci_ids.h
+++ b/hw/pci_ids.h
@@ -75,6 +75,9 @@
 #define PCI_VENDOR_ID_REALTEK            0x10ec
 #define PCI_DEVICE_ID_REALTEK_8139       0x8139
 
+#define PCI_VENDOR_ID_3COM               0x10b7
+#define PCI_VENDOR_ID_3COM_3C90X         0x9200
+
 #define PCI_VENDOR_ID_XILINX             0x10ee
 
 #define PCI_VENDOR_ID_MARVELL            0x11ab
-- 
1.6.3.2.1299.gee46c

