qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [RFC] [PATCH] 3C90X Emulation


From: Matthew Iselin
Subject: [Qemu-devel] [RFC] [PATCH] 3C90X Emulation
Date: Sat, 11 Jul 2009 23:52:37 +1000

This patch adds a basic 3C90X emulation to QEMU.

I have tested this with Linux kernels 2.4 and 2.6 (DSL and Ubuntu), and
Pedigree.

This has been ported from the PearPC 3C90X emulation, with significant
modifications to enable it to work on 2.6 kernels.

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

diff --git a/Makefile.target b/Makefile.target
index 1a71f3a..9f3dd58 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -556,6 +556,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..dc242dc
--- /dev/null
+++ b/hw/3c90x.c
@@ -0,0 +1,2421 @@
+/**
+ * QEMU 3C90X Emulation
+ *
+ * Copyright (c) 2009 Matthew Iselin (QEMU VERSION)
+ * Copyright (C) 2004 John Kelley (address@hidden) (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
+ *  - savevm functions
+ * TESTED ON:
+ *  - Damn Small Linux (4.4.10) - Linux 2.4
+ *  - Damn Small Linux N - Linux 2.6
+ *  - 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] =           0x2970;
+    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] =       0x8010;
+    s->mEEPROM[EEPROM_CompWord] =           0;
+    s->mEEPROM[EEPROM_SoftwareInfo2] =      0;
+    s->mEEPROM[0x15] =                      0x4;
+    s->mEEPROM[EEPROM_Caps] =               0x32a2;
+    s->mEEPROM[EEPROM_InternalConfig0] =    0;
+    s->mEEPROM[EEPROM_InternalConfig1] =    0x0040; // default is 0x0180, was 
0x50
+    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;
+
+    // EEPROM Checksum
+    int i, wordCount = (sizeof s->mEEPROM) / 2;
+    uint16_t checksum = 0;
+    for(i = 0; i < wordCount; i++)
+    {
+        uint16_t edata = s->mEEPROM[i];
+        if(i == (wordCount - 1))
+            edata &= 0xFF;
+        checksum ^= edata;
+    }
+
+    checksum = (checksum ^ (checksum >> 8)) & 0xFF;
+    s->mEEPROM[EEPROM_Checksum] = checksum;
+
+    // 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,
+    #define FSH_addIpChecksum   1<<25
+    #define FSH_addTcpChecksum  1<<26
+    #define FSH_addUdpChecksum  1<<27
+    if ((fsh & FSH_addTcpChecksum) || (fsh & FSH_addUdpChecksum))
+    {
+        DEBUG_PRINT("adding UDP/TCP checksum\n");
+        net_checksum_calculate(pbuf, psize);
+    }
+    else if(fsh & FSH_addIpChecksum)
+    {
+        DEBUG_PRINT("adding IP checksum\n");
+        uint16_t csum = net_checksum_add(psize, pbuf);
+        cpu_to_be16wu((uint16_t*) (pbuf + 14), net_checksum_finish(csum));
+    }
+    else if (fsh & (0x3 << 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
+            {
+                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(VLANClientState *vc)
+{
+    void *opaque = vc->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 ssize_t a3c90x_receive(VLANClientState *vc, const uint8_t *buf, size_t 
size)
+{
+    AUX_DEBUG_PRINT("3C90X: Packet received\n");
+    handle_rx(vc->opaque, buf, size);
+    return 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_3COM_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(a3c90x_mmio_read, a3c90x_mmio_write, s);
+
+    pci_register_bar(&d->dev, 0, 0x100,
+                     PCI_ADDRESS_SPACE_IO,  a3c90x_ioport_map);
+
+    pci_register_bar(&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_can_receive, a3c90x_receive, NULL,
+                                 a3c90x_cleanup, s);
+
+    qemu_format_nic_info_str(s->vc, s->mMAC);
+
+    //register_savevm("3c90x", -1, 4, a3c90x_save, a3c90x_load, s);
+}
+
+static PCIDeviceInfo a3c90x_info = {
+    .qdev.name = "3c90x",
+    .qdev.size = sizeof(PCI3C90XState),
+    .init      = pci_a3c90x_init,
+};
+
+static void a3c90x_register_devices(void)
+{
+    pci_qdev_register(&a3c90x_info);
+}
+
+device_init(a3c90x_register_devices)
diff --git a/hw/pci.c b/hw/pci.c
index 71d9227..6b9a7a7 100644
--- a/hw/pci.c
+++ b/hw/pci.c
@@ -779,6 +779,7 @@ static const char * const pci_nic_models[] = {
     "i82557b",
     "i82559er",
     "rtl8139",
+    "3c90x",
     "e1000",
     "pcnet",
     "virtio",
@@ -791,6 +792,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..872cca8 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_DEVICE_ID_3COM_3C90X         0x9200
+
 #define PCI_VENDOR_ID_XILINX             0x10ee
 
 #define PCI_VENDOR_ID_MARVELL            0x11ab
-- 
1.5.6.3





reply via email to

[Prev in Thread] Current Thread [Next in Thread]