1235 lines
40 KiB
C
1235 lines
40 KiB
C
/*
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
* running old operating systems and software designed for IBM
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
* system designs based on the PCI bus.
|
|
*
|
|
* This file is part of the 86Box distribution.
|
|
*
|
|
* Implementation of the following network controller:
|
|
* - 3Com Etherlink 3c500/3c501 (ISA 8-bit).
|
|
*
|
|
*
|
|
*
|
|
* Based on @(#)Dev3C501.cpp Oracle (VirtualBox)
|
|
*
|
|
* Authors: TheCollector1995, <mariogplayer@gmail.com>
|
|
* Oracle
|
|
*
|
|
* Copyright 2022 TheCollector1995.
|
|
* Portions Copyright (C) 2022 Oracle and/or its affilitates.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the:
|
|
*
|
|
* Free Software Foundation, Inc.
|
|
* 59 Temple Place - Suite 330
|
|
* Boston, MA 02111-1307
|
|
* USA.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <wchar.h>
|
|
#include <time.h>
|
|
#include <stdbool.h>
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include <86box/io.h>
|
|
#include <86box/dma.h>
|
|
#include <86box/pic.h>
|
|
#include <86box/mem.h>
|
|
#include <86box/random.h>
|
|
#include <86box/device.h>
|
|
#include <86box/thread.h>
|
|
#include <86box/timer.h>
|
|
#include <86box/network.h>
|
|
#include <86box/plat_unused.h>
|
|
|
|
/* Maximum number of times we report a link down to the guest (failure to send frame) */
|
|
#define ELNK_MAX_LINKDOWN_REPORTED 3
|
|
|
|
/* Maximum number of times we postpone restoring a link that is temporarily down. */
|
|
#define ELNK_MAX_LINKRST_POSTPONED 3
|
|
|
|
/* Maximum frame size we handle */
|
|
#define MAX_FRAME 1536
|
|
|
|
/* Size of the packet buffer. */
|
|
#define ELNK_BUF_SIZE 2048
|
|
|
|
/* The packet buffer address mask. */
|
|
#define ELNK_BUF_ADR_MASK (ELNK_BUF_SIZE - 1)
|
|
|
|
/* The GP buffer pointer address within the buffer. */
|
|
#define ELNK_GP(dev) (dev->uGPBufPtr & ELNK_BUF_ADR_MASK)
|
|
|
|
/* The GP buffer pointer mask.
|
|
* NB: The GP buffer pointer is internally a 12-bit counter. When addressing into the
|
|
* packet buffer, bit 11 is ignored. Required to pass 3C501 diagnostics.
|
|
*/
|
|
#define ELNK_GP_MASK 0xfff
|
|
|
|
/*********************************************************************************************************************************
|
|
* Structures and Typedefs *
|
|
*********************************************************************************************************************************/
|
|
|
|
/**
|
|
* EtherLink Transmit Command Register.
|
|
*/
|
|
typedef struct ELNK_XMIT_CMD {
|
|
uint8_t det_ufl : 1; /* Detect underflow. */
|
|
uint8_t det_coll : 1; /* Detect collision. */
|
|
uint8_t det_16col : 1; /* Detect collision 16. */
|
|
uint8_t det_succ : 1; /* Detect successful xmit. */
|
|
uint8_t unused : 4;
|
|
} EL_XMT_CMD;
|
|
|
|
/**
|
|
* EtherLink Transmit Status Register.
|
|
*
|
|
* We will never see any real collisions, although collisions (including 16
|
|
* successive collisions) may be useful to report when the link is down
|
|
* (something the 3C501 does not have a concept of).
|
|
*/
|
|
typedef struct ELNK_XMIT_STAT {
|
|
uint8_t uflow : 1; /* Underflow on transmit. */
|
|
uint8_t coll : 1; /* Collision on transmit. */
|
|
uint8_t coll16 : 1; /* 16 collisions on transmit. */
|
|
uint8_t ready : 1; /* Ready for a new frame. */
|
|
uint8_t undef : 4;
|
|
} EL_XMT_STAT;
|
|
|
|
/** Address match (adr_match) modes. */
|
|
typedef enum {
|
|
EL_ADRM_DISABLED = 0, /* Receiver disabled. */
|
|
EL_ADRM_PROMISC = 1, /* Receive all addresses. */
|
|
EL_ADRM_BCAST = 2, /* Receive station + broadcast. */
|
|
EL_ADRM_MCAST = 3 /* Receive station + multicast. */
|
|
} EL_ADDR_MATCH;
|
|
|
|
/**
|
|
* EtherLink Receive Command Register.
|
|
*/
|
|
typedef struct ELNK_RECV_CMD {
|
|
uint8_t det_ofl : 1; /* Detect overflow errors. */
|
|
uint8_t det_fcs : 1; /* Detect FCS errors. */
|
|
uint8_t det_drbl : 1; /* Detect dribble error. */
|
|
uint8_t det_runt : 1; /* Detect short frames. */
|
|
uint8_t det_eof : 1; /* Detect EOF (frames without overflow). */
|
|
uint8_t acpt_good : 1; /* Accept good frames. */
|
|
uint8_t adr_match : 2; /* Address match mode. */
|
|
} EL_RCV_CMD;
|
|
|
|
/**
|
|
* EtherLink Receive Status Register.
|
|
*/
|
|
typedef struct ELNK_RECV_STAT {
|
|
uint8_t oflow : 1; /* Overflow on receive. */
|
|
uint8_t fcs : 1; /* FCS error. */
|
|
uint8_t dribble : 1; /* Dribble error. */
|
|
uint8_t runt : 1; /* Short frame. */
|
|
uint8_t no_ovf : 1; /* Received packet w/o overflow. */
|
|
uint8_t good : 1; /* Received good packet. */
|
|
uint8_t undef : 1;
|
|
uint8_t stale : 1; /* Stale receive status. */
|
|
} EL_RCV_STAT;
|
|
|
|
/** Buffer control (buf_ctl) modes. */
|
|
typedef enum {
|
|
EL_BCTL_SYSTEM = 0, /* Host has buffer access. */
|
|
EL_BCTL_XMT_RCV = 1, /* Transmit, then receive. */
|
|
EL_BCTL_RECEIVE = 2, /* Receive. */
|
|
EL_BCTL_LOOPBACK = 3 /* Loopback. */
|
|
} EL_BUFFER_CONTROL;
|
|
|
|
/**
|
|
* EtherLink Auxiliary Status Register.
|
|
*/
|
|
typedef struct ELNK_AUX_CMD {
|
|
uint8_t ire : 1; /* Interrupt Request Enable. */
|
|
uint8_t xmit_bf : 1; /* Xmit packets with bad FCS. */
|
|
uint8_t buf_ctl : 2; /* Packet buffer control. */
|
|
uint8_t unused : 1;
|
|
uint8_t dma_req : 1; /* DMA request. */
|
|
uint8_t ride : 1; /* Request Interrupt and DMA Enable. */
|
|
uint8_t reset : 1; /* Card in reset while set. */
|
|
} EL_AUX_CMD;
|
|
|
|
/**
|
|
* EtherLink Auxiliary Status Register.
|
|
*/
|
|
typedef struct ELNK_AUX_STAT {
|
|
uint8_t recv_bsy : 1; /* Receive busy. */
|
|
uint8_t xmit_bf : 1; /* Xmit packets with bad FCS. */
|
|
uint8_t buf_ctl : 2; /* Packet buffer control. */
|
|
uint8_t dma_done : 1; /* DMA done. */
|
|
uint8_t dma_req : 1; /* DMA request. */
|
|
uint8_t ride : 1; /* Request Interrupt and DMA Enable. */
|
|
uint8_t xmit_bsy : 1; /* Transmit busy. */
|
|
} EL_AUX_STAT;
|
|
|
|
/**
|
|
* Internal interrupt status.
|
|
*/
|
|
typedef struct ELNK_INTR_STAT {
|
|
uint8_t recv_intr : 1; /* Receive interrupt status. */
|
|
uint8_t xmit_intr : 1; /* Transmit interrupt status. */
|
|
uint8_t dma_intr : 1; /* DMA interrupt status. */
|
|
uint8_t unused : 5;
|
|
} EL_INTR_STAT;
|
|
|
|
typedef struct threec501_t {
|
|
uint32_t base_address;
|
|
int base_irq;
|
|
uint32_t bios_addr;
|
|
uint8_t maclocal[6]; /* configured MAC (local) address. */
|
|
bool fISR; /* Internal interrupt flag. */
|
|
int fDMA; /* Internal DMA active flag. */
|
|
int fInReset; /* Internal in-reset flag. */
|
|
uint8_t aPROM[8]; /* The PROM contents. Only 8 bytes addressable, R/O. */
|
|
uint8_t aStationAddr[6]; /* The station address programmed by the guest, W/O. */
|
|
uint16_t uGPBufPtr; /* General Purpose (GP) Buffer Pointer, R/W. */
|
|
uint16_t uRCVBufPtr; /* Receive (RCV) Buffer Pointer, R/W. */
|
|
/** Transmit Command Register, W/O. */
|
|
union {
|
|
uint8_t XmitCmdReg;
|
|
EL_XMT_CMD XmitCmd;
|
|
};
|
|
/** Transmit Status Register, R/O. */
|
|
union {
|
|
uint8_t XmitStatReg;
|
|
EL_XMT_STAT XmitStat;
|
|
};
|
|
/** Receive Command Register, W/O. */
|
|
union {
|
|
uint8_t RcvCmdReg;
|
|
EL_RCV_CMD RcvCmd;
|
|
};
|
|
/** Receive Status Register, R/O. */
|
|
union {
|
|
uint8_t RcvStatReg;
|
|
EL_RCV_STAT RcvStat;
|
|
};
|
|
/** Auxiliary Command Register, W/O. */
|
|
union {
|
|
uint8_t AuxCmdReg;
|
|
EL_AUX_CMD AuxCmd;
|
|
};
|
|
/** Auxiliary Status Register, R/O. */
|
|
union {
|
|
uint8_t AuxStatReg;
|
|
EL_AUX_STAT AuxStat;
|
|
};
|
|
int fLinkUp; /* If set the link is currently up. */
|
|
int fLinkTempDown; /* If set the link is temporarily down because of a saved state load. */
|
|
uint16_t cLinkDownReported; /* Number of times we've reported the link down. */
|
|
uint16_t cLinkRestorePostponed; /* Number of times we've postponed the link restore. */
|
|
/* Internal interrupt state. */
|
|
union {
|
|
uint8_t IntrStateReg;
|
|
EL_INTR_STAT IntrState;
|
|
};
|
|
uint32_t cMsLinkUpDelay; /* MS to wait before we enable the link. */
|
|
int dma_channel;
|
|
uint8_t abLoopBuf[ELNK_BUF_SIZE]; /* The loopback transmit buffer (avoid stack allocations). */
|
|
uint8_t abRuntBuf[64]; /* The runt pad buffer (only really needs 60 bytes). */
|
|
uint8_t abPacketBuf[ELNK_BUF_SIZE]; /* The packet buffer. */
|
|
int dma_pos;
|
|
pc_timer_t timer_restore;
|
|
netcard_t *netcard;
|
|
} threec501_t;
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
int threec501_do_log = ENABLE_3COM501_LOG;
|
|
|
|
static void
|
|
threec501_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (threec501_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
# define threec501_log(fmt, ...)
|
|
#endif
|
|
|
|
static void elnkSoftReset(threec501_t *dev);
|
|
static void elnkR3HardReset(threec501_t *dev);
|
|
|
|
#ifndef ETHER_IS_MULTICAST /* Net/Open BSD macro it seems */
|
|
# define ETHER_IS_MULTICAST(a) ((*(uint8_t *) (a)) & 1)
|
|
#endif
|
|
|
|
#define ETHER_ADDR_LEN ETH_ALEN
|
|
#define ETH_ALEN 6
|
|
#pragma pack(1)
|
|
struct ether_header /** @todo Use RTNETETHERHDR? */
|
|
{
|
|
uint8_t ether_dhost[ETH_ALEN]; /**< destination ethernet address */
|
|
uint8_t ether_shost[ETH_ALEN]; /**< source ethernet address */
|
|
uint16_t ether_type; /**< packet type ID field */
|
|
};
|
|
#pragma pack()
|
|
|
|
static void
|
|
elnk_do_irq(threec501_t *dev, int set)
|
|
{
|
|
if (set)
|
|
picint(1 << dev->base_irq);
|
|
else
|
|
picintc(1 << dev->base_irq);
|
|
}
|
|
|
|
/**
|
|
* Checks if the link is up.
|
|
* @returns true if the link is up.
|
|
* @returns false if the link is down.
|
|
*/
|
|
static __inline int
|
|
elnkIsLinkUp(threec501_t *dev)
|
|
{
|
|
return !dev->fLinkTempDown && dev->fLinkUp;
|
|
}
|
|
|
|
/**
|
|
* Takes down the link temporarily if it's current status is up.
|
|
*
|
|
* This is used during restore and when replumbing the network link.
|
|
*
|
|
* The temporary link outage is supposed to indicate to the OS that all network
|
|
* connections have been lost and that it for instance is appropriate to
|
|
* renegotiate any DHCP lease.
|
|
*
|
|
* @param pThis The shared instance data.
|
|
*/
|
|
static void
|
|
elnkTempLinkDown(threec501_t *dev)
|
|
{
|
|
if (dev->fLinkUp) {
|
|
dev->fLinkTempDown = 1;
|
|
dev->cLinkDownReported = 0;
|
|
dev->cLinkRestorePostponed = 0;
|
|
timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @interface_method_impl{PDMDEVREG,pfnReset}
|
|
*/
|
|
static void
|
|
elnkR3Reset(void *priv)
|
|
{
|
|
threec501_t *dev = (threec501_t *) priv;
|
|
|
|
if (dev->fLinkTempDown) {
|
|
dev->cLinkDownReported = 0x1000;
|
|
dev->cLinkRestorePostponed = 0x1000;
|
|
timer_disable(&dev->timer_restore);
|
|
}
|
|
|
|
/** @todo How to flush the queues? */
|
|
elnkR3HardReset(dev);
|
|
}
|
|
|
|
static void
|
|
elnkR3HardReset(threec501_t *dev)
|
|
{
|
|
dev->fISR = false;
|
|
elnk_do_irq(dev, 0);
|
|
|
|
/* Clear the packet buffer and station address. */
|
|
memset(dev->abPacketBuf, 0, sizeof(dev->abPacketBuf));
|
|
memset(dev->aStationAddr, 0, sizeof(dev->aStationAddr));
|
|
|
|
/* Reset the buffer pointers. */
|
|
dev->uGPBufPtr = 0;
|
|
dev->uRCVBufPtr = 0;
|
|
|
|
elnkSoftReset(dev);
|
|
}
|
|
|
|
/**
|
|
* Check if incoming frame matches the station address.
|
|
*/
|
|
static __inline int
|
|
padr_match(threec501_t *dev, const uint8_t *buf)
|
|
{
|
|
const struct ether_header *hdr = (const struct ether_header *) buf;
|
|
int result;
|
|
|
|
/* Checks own + broadcast as well as own + multicast. */
|
|
result = (dev->RcvCmd.adr_match >= EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, dev->aStationAddr, 6);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Check if incoming frame is an accepted broadcast frame.
|
|
*/
|
|
static __inline int
|
|
padr_bcast(threec501_t *dev, const uint8_t *buf)
|
|
{
|
|
static uint8_t aBCAST[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
const struct ether_header *hdr = (const struct ether_header *) buf;
|
|
int result = (dev->RcvCmd.adr_match == EL_ADRM_BCAST) && !memcmp(hdr->ether_dhost, aBCAST, 6);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Check if incoming frame is an accepted multicast frame.
|
|
*/
|
|
static __inline int
|
|
padr_mcast(threec501_t *dev, const uint8_t *buf)
|
|
{
|
|
const struct ether_header *hdr = (const struct ether_header *) buf;
|
|
int result = (dev->RcvCmd.adr_match == EL_ADRM_MCAST) && ETHER_IS_MULTICAST(hdr->ether_dhost);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Update the device IRQ line based on internal state.
|
|
*/
|
|
static void
|
|
elnkUpdateIrq(threec501_t *dev)
|
|
{
|
|
bool fISR = false;
|
|
|
|
/* IRQ is active if any interrupt source is active and interrupts
|
|
* are enabled via RIDE or IRE.
|
|
*/
|
|
if (dev->IntrStateReg && (dev->AuxCmd.ride || dev->AuxCmd.ire))
|
|
fISR = true;
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: elnkUpdateIrq: fISR=%d\n", fISR);
|
|
#endif
|
|
|
|
if (fISR != dev->fISR) {
|
|
elnk_do_irq(dev, fISR);
|
|
dev->fISR = fISR;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform a software reset of the NIC.
|
|
*/
|
|
static void
|
|
elnkSoftReset(threec501_t *dev)
|
|
{
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: elnkSoftReset\n");
|
|
#endif
|
|
|
|
/* Clear some of the user-visible register state. */
|
|
dev->XmitCmdReg = 0;
|
|
dev->XmitStatReg = 0;
|
|
dev->RcvCmdReg = 0;
|
|
dev->RcvStatReg = 0;
|
|
dev->AuxCmdReg = 0;
|
|
dev->AuxStatReg = 0;
|
|
|
|
/* The "stale receive status" is cleared by receiving an "interesting" packet. */
|
|
dev->RcvStat.stale = 1;
|
|
|
|
/* By virtue of setting the buffer control to system, transmit is set to busy. */
|
|
dev->AuxStat.xmit_bsy = 1;
|
|
|
|
/* Clear internal interrupt state. */
|
|
dev->IntrStateReg = 0;
|
|
elnkUpdateIrq(dev);
|
|
|
|
/* Note that a soft reset does not clear the packet buffer; software often
|
|
* assumes that it survives soft reset. The programmed station address is
|
|
* likewise not reset, and the buffer pointers are not reset either.
|
|
* Verified on a real 3C501.
|
|
*/
|
|
|
|
/* No longer in reset state. */
|
|
dev->fInReset = 0;
|
|
}
|
|
|
|
/**
|
|
* Write incoming data into the packet buffer.
|
|
*/
|
|
static int
|
|
elnkReceiveLocked(void *priv, uint8_t *src, int size)
|
|
{
|
|
threec501_t *dev = (threec501_t *) priv;
|
|
int is_padr = 0;
|
|
int is_bcast = 0;
|
|
int is_mcast = 0;
|
|
bool fLoopback = dev->RcvCmd.adr_match == EL_BCTL_LOOPBACK;
|
|
|
|
union {
|
|
uint8_t RcvStatNewReg;
|
|
EL_RCV_STAT RcvStatNew;
|
|
} rcvstatnew;
|
|
|
|
/* Drop everything if address matching is disabled. */
|
|
if (dev->RcvCmd.adr_match == EL_ADRM_DISABLED)
|
|
return 0;
|
|
|
|
/* Drop zero-length packets (how does that even happen?). */
|
|
if (!size)
|
|
return 0;
|
|
|
|
/*
|
|
* Drop all packets if the cable is not connected (and not in loopback).
|
|
*/
|
|
if (!elnkIsLinkUp(dev) && !fLoopback)
|
|
return 0;
|
|
|
|
/*
|
|
* Do not receive further packets until receive status was read.
|
|
*/
|
|
if (dev->RcvStat.stale == 0)
|
|
return 0;
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: size on wire=%d, RCV ptr=%u\n", size, dev->uRCVBufPtr);
|
|
#endif
|
|
|
|
/*
|
|
* Perform address matching. Packets which do not pass the address
|
|
* filter are always ignored.
|
|
*/
|
|
/// @todo cbToRecv must be 6 or more (complete address)
|
|
if ((dev->RcvCmd.adr_match == EL_ADRM_PROMISC) /* promiscuous enabled */
|
|
|| (is_padr = padr_match(dev, src))
|
|
|| (is_bcast = padr_bcast(dev, src))
|
|
|| (is_mcast = padr_mcast(dev, src))) {
|
|
uint8_t *dst = dev->abPacketBuf + dev->uRCVBufPtr;
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501 Packet passed address filter (is_padr=%d, is_bcast=%d, is_mcast=%d), size=%d\n", is_padr, is_bcast, is_mcast, size);
|
|
#endif
|
|
|
|
/* Receive status is evaluated from scratch. The stale bit must remain set until we know better. */
|
|
rcvstatnew.RcvStatNewReg = 0;
|
|
rcvstatnew.RcvStatNew.stale = 1;
|
|
dev->RcvStatReg = 0x80;
|
|
|
|
/* Detect errors: Runts, overflow, and FCS errors.
|
|
* NB: Dribble errors can not happen because we can only receive an
|
|
* integral number of bytes. FCS errors are only possible in loopback
|
|
* mode in case the FCS is deliberately corrupted.
|
|
*/
|
|
|
|
/* See if we need to pad, and how much. Have to be careful because the
|
|
* Receive Buffer Pointer might be near the end of the buffer.
|
|
*/
|
|
if (size < 60) {
|
|
/* In loopback mode only, short packets are flagged as errors because
|
|
* diagnostic tools want to see the errors. Otherwise they're padded to
|
|
* minimum length (if packet came over the wire, it should have been
|
|
* properly padded).
|
|
*/
|
|
/// @todo This really is kind of wrong. We shouldn't be doing any
|
|
/// padding here, it should be done by the sending side!
|
|
if (!fLoopback) {
|
|
memset(dev->abRuntBuf, 0, sizeof(dev->abRuntBuf));
|
|
memcpy(dev->abRuntBuf, src, size);
|
|
size = 60;
|
|
src = dev->abRuntBuf;
|
|
} else {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501 runt, size=%d\n", size);
|
|
#endif
|
|
rcvstatnew.RcvStatNew.runt = 1;
|
|
}
|
|
}
|
|
|
|
/* We don't care how big the frame is; if it fits into the buffer, all is
|
|
* good. But conversely if the Receive Buffer Pointer is initially near the
|
|
* end of the buffer, a small frame can trigger an overflow.
|
|
*/
|
|
if ((dev->uRCVBufPtr + size) <= ELNK_BUF_SIZE)
|
|
rcvstatnew.RcvStatNew.no_ovf = 1;
|
|
else {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501 overflow, size=%d\n", size);
|
|
#endif
|
|
rcvstatnew.RcvStatNew.oflow = 1;
|
|
}
|
|
|
|
if (fLoopback && dev->AuxCmd.xmit_bf) {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501 bad FCS\n");
|
|
#endif
|
|
rcvstatnew.RcvStatNew.fcs = 1;
|
|
}
|
|
|
|
/* Error-free packets are considered good. */
|
|
if (rcvstatnew.RcvStatNew.no_ovf && !rcvstatnew.RcvStatNew.fcs && !rcvstatnew.RcvStatNew.runt)
|
|
rcvstatnew.RcvStatNew.good = 1;
|
|
|
|
uint16_t cbCopy = (uint16_t) MIN(ELNK_BUF_SIZE - dev->uRCVBufPtr, size);
|
|
|
|
/* All packets that passed the address filter are copied to the buffer. */
|
|
|
|
/* Copy incoming data to the packet buffer. NB: Starts at the current
|
|
* Receive Buffer Pointer position.
|
|
*/
|
|
memcpy(dst, src, cbCopy);
|
|
|
|
/* Packet length is indicated via the receive buffer pointer. */
|
|
dev->uRCVBufPtr = (dev->uRCVBufPtr + cbCopy) & ELNK_GP_MASK;
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501 Received packet, size=%d, RP=%u\n", cbCopy, dev->uRCVBufPtr);
|
|
#endif
|
|
|
|
/*
|
|
* If one of the "interesting" conditions was hit, stop receiving until
|
|
* the status register is read (mark it not stale).
|
|
* NB: The precise receive logic is not very well described in the EtherLink
|
|
* documentation. It was refined using the 3C501.EXE diagnostic utility.
|
|
*/
|
|
if ((rcvstatnew.RcvStatNew.good && dev->RcvCmd.acpt_good)
|
|
|| (rcvstatnew.RcvStatNew.no_ovf && dev->RcvCmd.det_eof)
|
|
|| (rcvstatnew.RcvStatNew.runt && dev->RcvCmd.det_runt)
|
|
|| (rcvstatnew.RcvStatNew.dribble && dev->RcvCmd.det_drbl)
|
|
|| (rcvstatnew.RcvStatNew.fcs && dev->RcvCmd.det_fcs)
|
|
|| (rcvstatnew.RcvStatNew.oflow && dev->RcvCmd.det_ofl)) {
|
|
dev->AuxStat.recv_bsy = 0;
|
|
dev->IntrState.recv_intr = 1;
|
|
rcvstatnew.RcvStatNew.stale = 0; /* Prevents further receive until set again. */
|
|
}
|
|
|
|
/* Finally update the receive status. */
|
|
dev->RcvStat = rcvstatnew.RcvStatNew;
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: RcvCmd=%02X, RcvStat=%02X, RCVBufPtr=%u\n", dev->RcvCmdReg, dev->RcvStatReg, dev->uRCVBufPtr);
|
|
#endif
|
|
|
|
elnkUpdateIrq(dev);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Actually try transmit frames.
|
|
*
|
|
* @threads TX or EMT.
|
|
*/
|
|
static void
|
|
elnkAsyncTransmit(threec501_t *dev)
|
|
{
|
|
/*
|
|
* Just drop it if not transmitting. Can happen with delayed transmits
|
|
* if transmit was disabled in the meantime.
|
|
*/
|
|
if (!dev->AuxStat.xmit_bsy) {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Nope, xmit disabled\n");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if ((dev->AuxCmd.buf_ctl != EL_BCTL_XMT_RCV) && (dev->AuxCmd.buf_ctl != EL_BCTL_LOOPBACK)) {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Nope, not in xmit-then-receive or loopback state\n");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Blast out data from the packet buffer.
|
|
*/
|
|
do {
|
|
/* Don't send anything when the link is down. */
|
|
if (!elnkIsLinkUp(dev)
|
|
&& dev->cLinkDownReported > ELNK_MAX_LINKDOWN_REPORTED)
|
|
break;
|
|
|
|
bool const fLoopback = dev->AuxCmd.buf_ctl == EL_BCTL_LOOPBACK;
|
|
|
|
/*
|
|
* Sending is easy peasy, there is by definition always
|
|
* a complete packet on hand.
|
|
*/
|
|
const unsigned cb = ELNK_BUF_SIZE - ELNK_GP(dev); /* Packet size. */
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: cb=%d, loopback=%d.\n", cb, fLoopback);
|
|
#endif
|
|
|
|
dev->XmitStatReg = 0; /* Clear transmit status before filling it out. */
|
|
|
|
if (elnkIsLinkUp(dev) || fLoopback) {
|
|
if (cb <= MAX_FRAME) {
|
|
if (fLoopback) {
|
|
elnkReceiveLocked(dev, &dev->abPacketBuf[ELNK_GP(dev)], cb);
|
|
} else {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: elnkAsyncTransmit: transmit loopbuf xmit pos = %d\n", cb);
|
|
#endif
|
|
network_tx(dev->netcard, &dev->abPacketBuf[ELNK_GP(dev)], cb);
|
|
}
|
|
dev->XmitStat.ready = 1;
|
|
} else {
|
|
/* Signal error, as this violates the Ethernet specs. */
|
|
/** @todo check if the correct error is generated. */
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: illegal giant frame (%u bytes) -> signalling error\n", cb);
|
|
#endif
|
|
}
|
|
} else {
|
|
/* Signal a transmit error pretending there was a collision. */
|
|
dev->cLinkDownReported++;
|
|
dev->XmitStat.coll = 1;
|
|
}
|
|
|
|
/* Transmit officially done, update register state. */
|
|
dev->AuxStat.xmit_bsy = 0;
|
|
dev->IntrState.xmit_intr = !!(dev->XmitCmdReg & dev->XmitStatReg);
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: XmitCmd=%02X, XmitStat=%02X\n", dev->XmitCmdReg, dev->XmitStatReg);
|
|
#endif
|
|
|
|
/* NB: After a transmit, the GP Buffer Pointer points just past
|
|
* the end of the packet buffer (3C501 diagnostics).
|
|
*/
|
|
dev->uGPBufPtr = ELNK_BUF_SIZE;
|
|
|
|
/* NB: The buffer control does *not* change to Receive and stays the way it was. */
|
|
if (!fLoopback) {
|
|
dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */
|
|
}
|
|
} while (0); /* No loop, because there isn't ever more than one packet to transmit. */
|
|
|
|
elnkUpdateIrq(dev);
|
|
}
|
|
|
|
static void
|
|
elnkCsrWrite(threec501_t *dev, uint8_t data)
|
|
{
|
|
bool fTransmit = false;
|
|
bool fReceive = false;
|
|
bool fDMAR;
|
|
int mode;
|
|
|
|
union {
|
|
uint8_t reg;
|
|
EL_AUX_CMD val;
|
|
} auxcmd;
|
|
|
|
auxcmd.reg = data;
|
|
|
|
/* Handle reset first. */
|
|
if (dev->AuxCmd.reset != auxcmd.val.reset) {
|
|
if (auxcmd.val.reset) {
|
|
/* Card is placed into reset. Just set the flag. NB: When in reset
|
|
* state, we permit writes to other registers, but those have no
|
|
* effect and will be overwritten when the card is taken out of reset.
|
|
*/
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Card going into reset\n");
|
|
#endif
|
|
dev->fInReset = true;
|
|
|
|
/* Many EtherLink drivers like to reset the card a lot. That can lead to
|
|
* packet loss if a packet was already received before the card was reset.
|
|
*/
|
|
} else {
|
|
/* Card is being taken out of reset. */
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Card going out of reset\n");
|
|
#endif
|
|
elnkSoftReset(dev);
|
|
}
|
|
dev->AuxCmd.reset = auxcmd.val.reset; /* Update the reset bit, if nothing else. */
|
|
}
|
|
|
|
/* If the card is in reset, stop right here. */
|
|
if (dev->fInReset) {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Reset\n");
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
/* Evaluate DMA state. If it changed, we'll have to go back to R3. */
|
|
fDMAR = auxcmd.val.dma_req && auxcmd.val.ride;
|
|
if (fDMAR != dev->fDMA) {
|
|
/* Start/stop DMA as requested. */
|
|
dev->fDMA = fDMAR;
|
|
if (fDMAR) {
|
|
dma_set_drq(dev->dma_channel, fDMAR);
|
|
mode = dma_mode(dev->dma_channel);
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: DMA Mode = %02x.\n", mode & 0x0c);
|
|
#endif
|
|
if ((mode & 0x0c) == 0x04) {
|
|
while (dev->dma_pos < (ELNK_BUF_SIZE - ELNK_GP(dev))) {
|
|
dma_channel_write(dev->dma_channel, dev->abPacketBuf[ELNK_GP(dev) + dev->dma_pos]);
|
|
dev->dma_pos++;
|
|
}
|
|
} else {
|
|
while (dev->dma_pos < (ELNK_BUF_SIZE - ELNK_GP(dev))) {
|
|
int dma_data = dma_channel_read(dev->dma_channel);
|
|
dev->abPacketBuf[ELNK_GP(dev) + dev->dma_pos] = dma_data & 0xff;
|
|
dev->dma_pos++;
|
|
}
|
|
}
|
|
dev->uGPBufPtr = (dev->uGPBufPtr + dev->dma_pos) & ELNK_GP_MASK;
|
|
dma_set_drq(dev->dma_channel, 0);
|
|
dev->dma_pos = 0;
|
|
dev->IntrState.dma_intr = 1;
|
|
dev->AuxStat.dma_done = 1;
|
|
elnkUpdateIrq(dev);
|
|
}
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: DMARQ for channel %u set to %u\n", dev->dma_channel, fDMAR);
|
|
#endif
|
|
}
|
|
|
|
/* Interrupt enable changes. */
|
|
if ((dev->AuxCmd.ire != auxcmd.val.ire) || (dev->AuxCmd.ride != auxcmd.val.ride)) {
|
|
dev->AuxStat.ride = dev->AuxCmd.ride = auxcmd.val.ride;
|
|
dev->AuxCmd.ire = auxcmd.val.ire; /* NB: IRE is not visible in the aux status register. */
|
|
}
|
|
|
|
/* DMA Request changes. */
|
|
if (dev->AuxCmd.dma_req != auxcmd.val.dma_req) {
|
|
dev->AuxStat.dma_req = dev->AuxCmd.dma_req = auxcmd.val.dma_req;
|
|
if (!auxcmd.val.dma_req) {
|
|
/* Clearing the DMA Request bit also clears the DMA Done status bit and any DMA interrupt. */
|
|
dev->IntrState.dma_intr = 0;
|
|
dev->AuxStat.dma_done = 0;
|
|
}
|
|
}
|
|
|
|
/* Packet buffer control changes. */
|
|
if (dev->AuxCmd.buf_ctl != auxcmd.val.buf_ctl) {
|
|
#ifdef ENABLE_3COM501_LOG
|
|
static const char *apszBuffCntrl[4] = { "System", "Xmit then Recv", "Receive", "Loopback" };
|
|
threec501_log("3Com501: Packet buffer control `%s' -> `%s'\n", apszBuffCntrl[dev->AuxCmd.buf_ctl], apszBuffCntrl[auxcmd.val.buf_ctl]);
|
|
#endif
|
|
if (auxcmd.val.buf_ctl == EL_BCTL_XMT_RCV) {
|
|
/* Transmit, then receive. */
|
|
fTransmit = true;
|
|
dev->AuxStat.recv_bsy = 0;
|
|
} else if (auxcmd.val.buf_ctl == EL_BCTL_SYSTEM) {
|
|
dev->AuxStat.xmit_bsy = 1; /* Transmit Busy is set here and cleared once actual transmit completes. */
|
|
dev->AuxStat.recv_bsy = 0;
|
|
} else if (auxcmd.val.buf_ctl == EL_BCTL_RECEIVE) {
|
|
/* Special case: If going from xmit-then-receive mode to receive mode, and we received
|
|
* a packet already (right after the receive), don't restart receive and lose the already
|
|
* received packet.
|
|
*/
|
|
if (!dev->uRCVBufPtr)
|
|
fReceive = true;
|
|
} else {
|
|
/* For loopback, we go through the regular transmit and receive path. That may be an
|
|
* overkill but the receive path is too complex for a special loopback-only case.
|
|
*/
|
|
fTransmit = true;
|
|
dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */
|
|
}
|
|
dev->AuxStat.buf_ctl = dev->AuxCmd.buf_ctl = auxcmd.val.buf_ctl;
|
|
}
|
|
|
|
/* NB: Bit 1 (xmit_bf, transmit packets with bad FCS) is a simple control
|
|
* bit which does not require special handling here. Just copy it over.
|
|
*/
|
|
dev->AuxStat.xmit_bf = dev->AuxCmd.xmit_bf = auxcmd.val.xmit_bf;
|
|
|
|
/* There are multiple bits that affect interrupt state. Handle them now. */
|
|
elnkUpdateIrq(dev);
|
|
|
|
/* After fully updating register state, do a transmit (including loopback) or receive. */
|
|
if (fTransmit)
|
|
elnkAsyncTransmit(dev);
|
|
else if (fReceive) {
|
|
dev->AuxStat.recv_bsy = 1; /* Receive Busy now set until a packet is received. */
|
|
}
|
|
}
|
|
|
|
static uint8_t
|
|
threec501_read(uint16_t addr, void *priv)
|
|
{
|
|
threec501_t *dev = (threec501_t *) priv;
|
|
uint8_t retval = 0xff;
|
|
|
|
switch (addr & 0x0f) {
|
|
case 0x00: /* Receive status register aliases. The SEEQ 8001 */
|
|
case 0x02: /* EDLC clearly only decodes one bit for reads. */
|
|
case 0x04:
|
|
case 0x06: /* Receive status register. */
|
|
retval = dev->RcvStatReg;
|
|
dev->RcvStat.stale = 1; /* Allows further reception. */
|
|
dev->IntrState.recv_intr = 0; /* Reading clears receive interrupt. */
|
|
elnkUpdateIrq(dev);
|
|
break;
|
|
|
|
case 0x01: /* Transmit status register aliases. */
|
|
case 0x03:
|
|
case 0x05:
|
|
case 0x07: /* Transmit status register. */
|
|
retval = dev->XmitStatReg;
|
|
dev->IntrState.xmit_intr = 0; /* Reading clears transmit interrupt. */
|
|
elnkUpdateIrq(dev);
|
|
break;
|
|
|
|
case 0x08: /* GP Buffer pointer LSB. */
|
|
retval = (dev->uGPBufPtr & 0xff);
|
|
break;
|
|
case 0x09: /* GP Buffer pointer MSB. */
|
|
retval = (dev->uGPBufPtr >> 8);
|
|
break;
|
|
|
|
case 0x0a: /* RCV Buffer pointer LSB. */
|
|
retval = (dev->uRCVBufPtr & 0xff);
|
|
break;
|
|
case 0x0b: /* RCV Buffer pointer MSB. */
|
|
retval = (dev->uRCVBufPtr >> 8);
|
|
break;
|
|
|
|
case 0x0c: /* Ethernet address PROM window. */
|
|
case 0x0d: /* Alias. */
|
|
/* Reads use low 3 bits of GP buffer pointer, no auto-increment. */
|
|
retval = dev->aPROM[dev->uGPBufPtr & 7];
|
|
break;
|
|
|
|
case 0x0e: /* Auxiliary status register. */
|
|
retval = dev->AuxStatReg;
|
|
break;
|
|
|
|
case 0x0f: /* Buffer window. */
|
|
/* Reads use low 11 bits of GP buffer pointer, auto-increment. */
|
|
retval = dev->abPacketBuf[ELNK_GP(dev)];
|
|
dev->uGPBufPtr = (dev->uGPBufPtr + 1) & ELNK_GP_MASK;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
elnkUpdateIrq(dev);
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: read addr %x, value %x\n", addr & 0x0f, retval);
|
|
#endif
|
|
return retval;
|
|
}
|
|
|
|
static uint8_t
|
|
threec501_nic_readb(uint16_t addr, void *priv)
|
|
{
|
|
return threec501_read(addr, priv);
|
|
}
|
|
|
|
static uint16_t
|
|
threec501_nic_readw(uint16_t addr, void *priv)
|
|
{
|
|
return threec501_read(addr, priv) | (threec501_read(addr + 1, priv) << 8);
|
|
}
|
|
|
|
static void
|
|
threec501_write(uint16_t addr, uint8_t value, void *priv)
|
|
{
|
|
threec501_t *dev = (threec501_t *) priv;
|
|
int reg = (addr & 0x0f);
|
|
|
|
switch (reg) {
|
|
case 0x00: /* Six bytes of station address. */
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x03:
|
|
case 0x04:
|
|
case 0x05:
|
|
dev->aStationAddr[reg] = value;
|
|
break;
|
|
|
|
case 0x06: /* Receive command. */
|
|
dev->RcvCmdReg = value;
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Receive Command register set to %02X\n", dev->RcvCmdReg);
|
|
#endif
|
|
break;
|
|
|
|
case 0x07: /* Transmit command. */
|
|
dev->XmitCmdReg = value;
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Transmit Command register set to %02X\n", dev->XmitCmdReg);
|
|
#endif
|
|
break;
|
|
|
|
case 0x08: /* GP Buffer pointer LSB. */
|
|
dev->uGPBufPtr = (dev->uGPBufPtr & 0xff00) | value;
|
|
break;
|
|
case 0x09: /* GP Buffer pointer MSB. */
|
|
dev->uGPBufPtr = (dev->uGPBufPtr & 0x00ff) | (value << 8);
|
|
break;
|
|
|
|
case 0x0a: /* RCV Buffer pointer clear. */
|
|
dev->uRCVBufPtr = 0;
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: RCV Buffer Pointer cleared (%02X)\n", value);
|
|
#endif
|
|
break;
|
|
|
|
case 0x0b: /* RCV buffer pointer MSB. */
|
|
case 0x0c: /* Ethernet address PROM window. */
|
|
case 0x0d: /* Undocumented. */
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: Writing read-only register %02X!\n", reg);
|
|
#endif
|
|
break;
|
|
|
|
case 0x0e: /* Auxiliary Command (CSR). */
|
|
elnkCsrWrite(dev, value);
|
|
break;
|
|
|
|
case 0x0f: /* Buffer window. */
|
|
/* Writes use low 11 bits of GP buffer pointer, auto-increment. */
|
|
if (dev->AuxCmd.buf_ctl != EL_BCTL_SYSTEM) {
|
|
/// @todo Does this still increment GPBufPtr?
|
|
break;
|
|
}
|
|
dev->abPacketBuf[ELNK_GP(dev)] = value;
|
|
dev->uGPBufPtr = (dev->uGPBufPtr + 1) & ELNK_GP_MASK;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: write addr %x, value %x\n", reg, value);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
threec501_nic_writeb(uint16_t addr, uint8_t value, void *priv)
|
|
{
|
|
threec501_write(addr, value, priv);
|
|
}
|
|
|
|
static void
|
|
threec501_nic_writew(uint16_t addr, uint16_t value, void *priv)
|
|
{
|
|
threec501_write(addr, value & 0xff, priv);
|
|
threec501_write(addr + 1, value >> 8, priv);
|
|
}
|
|
|
|
static int
|
|
elnkSetLinkState(void *priv, uint32_t link_state)
|
|
{
|
|
threec501_t *dev = (threec501_t *) priv;
|
|
|
|
if (link_state & NET_LINK_TEMP_DOWN) {
|
|
elnkTempLinkDown(dev);
|
|
return 1;
|
|
}
|
|
|
|
bool link_up = !(link_state & NET_LINK_DOWN);
|
|
if (dev->fLinkUp != link_up) {
|
|
dev->fLinkUp = link_up;
|
|
if (link_up) {
|
|
dev->fLinkTempDown = 1;
|
|
dev->cLinkDownReported = 0;
|
|
dev->cLinkRestorePostponed = 0;
|
|
timer_set_delay_u64(&dev->timer_restore, (dev->cMsLinkUpDelay * 1000) * TIMER_USEC);
|
|
} else {
|
|
dev->cLinkDownReported = 0;
|
|
dev->cLinkRestorePostponed = 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
elnkR3TimerRestore(void *priv)
|
|
{
|
|
threec501_t *dev = (threec501_t *) priv;
|
|
|
|
if ((dev->cLinkDownReported <= ELNK_MAX_LINKDOWN_REPORTED) && (dev->cLinkRestorePostponed <= ELNK_MAX_LINKRST_POSTPONED)) {
|
|
timer_advance_u64(&dev->timer_restore, 1500000 * TIMER_USEC);
|
|
dev->cLinkRestorePostponed++;
|
|
} else {
|
|
dev->fLinkTempDown = 0;
|
|
}
|
|
}
|
|
|
|
static void *
|
|
threec501_nic_init(UNUSED(const device_t *info))
|
|
{
|
|
uint32_t mac;
|
|
threec501_t *dev;
|
|
|
|
dev = calloc(1, sizeof(threec501_t));
|
|
dev->maclocal[0] = 0x02; /* 02:60:8C (3Com OID) */
|
|
dev->maclocal[1] = 0x60;
|
|
dev->maclocal[2] = 0x8C;
|
|
|
|
dev->base_address = device_get_config_hex16("base");
|
|
dev->base_irq = device_get_config_int("irq");
|
|
dev->dma_channel = device_get_config_int("dma");
|
|
|
|
dev->fLinkUp = 1;
|
|
dev->cMsLinkUpDelay = 5000;
|
|
|
|
/* See if we have a local MAC address configured. */
|
|
mac = device_get_config_mac("mac", -1);
|
|
|
|
/*
|
|
* Make this device known to the I/O system.
|
|
* PnP and PCI devices start with address spaces inactive.
|
|
*/
|
|
io_sethandler(dev->base_address, 0x10,
|
|
threec501_nic_readb, threec501_nic_readw, NULL,
|
|
threec501_nic_writeb, threec501_nic_writew, NULL, dev);
|
|
|
|
/* Set up our BIA. */
|
|
if (mac & 0xff000000) {
|
|
/* Generate new local MAC. */
|
|
dev->maclocal[3] = random_generate();
|
|
dev->maclocal[4] = random_generate();
|
|
dev->maclocal[5] = random_generate();
|
|
mac = (((int) dev->maclocal[3]) << 16);
|
|
mac |= (((int) dev->maclocal[4]) << 8);
|
|
mac |= ((int) dev->maclocal[5]);
|
|
device_set_config_mac("mac", mac);
|
|
} else {
|
|
dev->maclocal[3] = (mac >> 16) & 0xff;
|
|
dev->maclocal[4] = (mac >> 8) & 0xff;
|
|
dev->maclocal[5] = (mac & 0xff);
|
|
}
|
|
|
|
/* Initialize the PROM */
|
|
memcpy(dev->aPROM, dev->maclocal, sizeof(dev->maclocal));
|
|
dev->aPROM[6] = dev->aPROM[7] = 0; /* The two padding bytes. */
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("I/O=%04x, IRQ=%d, DMA=%d, MAC=%02x:%02x:%02x:%02x:%02x:%02x\n",
|
|
dev->base_address, dev->base_irq, dev->dma_channel,
|
|
dev->aPROM[0], dev->aPROM[1], dev->aPROM[2],
|
|
dev->aPROM[3], dev->aPROM[4], dev->aPROM[5]);
|
|
#endif
|
|
|
|
/* Reset the board. */
|
|
elnkR3HardReset(dev);
|
|
|
|
/* Attach ourselves to the network module. */
|
|
dev->netcard = network_attach(dev, dev->aPROM, elnkReceiveLocked, elnkSetLinkState);
|
|
|
|
timer_add(&dev->timer_restore, elnkR3TimerRestore, dev, 0);
|
|
|
|
return dev;
|
|
}
|
|
|
|
static void
|
|
threec501_nic_close(void *priv)
|
|
{
|
|
threec501_t *dev = (threec501_t *) priv;
|
|
|
|
#ifdef ENABLE_3COM501_LOG
|
|
threec501_log("3Com501: closed\n");
|
|
#endif
|
|
|
|
free(dev);
|
|
}
|
|
|
|
static const device_config_t threec501_config[] = {
|
|
{
|
|
.name = "base",
|
|
.description = "Address",
|
|
.type = CONFIG_HEX16,
|
|
.default_string = NULL,
|
|
.default_int = 0x300,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = {
|
|
{ .description = "0x280", .value = 0x280 },
|
|
{ .description = "0x300", .value = 0x300 },
|
|
{ .description = "0x310", .value = 0x310 },
|
|
{ .description = "0x320", .value = 0x320 },
|
|
{ .description = "" }
|
|
},
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "irq",
|
|
.description = "IRQ",
|
|
.type = CONFIG_SELECTION,
|
|
.default_string = NULL,
|
|
.default_int = 3,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = {
|
|
{ .description = "IRQ 2/9", .value = 9 },
|
|
{ .description = "IRQ 3", .value = 3 },
|
|
{ .description = "IRQ 4", .value = 4 },
|
|
{ .description = "IRQ 5", .value = 5 },
|
|
{ .description = "IRQ 6", .value = 6 },
|
|
{ .description = "IRQ 7", .value = 7 },
|
|
{ .description = "" }
|
|
},
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "dma",
|
|
.description = "DMA",
|
|
.type = CONFIG_SELECTION,
|
|
.default_string = NULL,
|
|
.default_int = 3,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = {
|
|
{ .description = "DMA 1", .value = 1 },
|
|
{ .description = "DMA 2", .value = 2 },
|
|
{ .description = "DMA 3", .value = 3 },
|
|
{ .description = "" }
|
|
},
|
|
.bios = { { 0 } }
|
|
},
|
|
{
|
|
.name = "mac",
|
|
.description = "MAC Address",
|
|
.type = CONFIG_MAC,
|
|
.default_string = NULL,
|
|
.default_int = -1,
|
|
.file_filter = NULL,
|
|
.spinner = { 0 },
|
|
.selection = { { 0 } },
|
|
.bios = { { 0 } }
|
|
},
|
|
{ .name = "", .description = "", .type = CONFIG_END }
|
|
};
|
|
|
|
const device_t threec501_device = {
|
|
.name = "3Com EtherLink (3c500/3c501)",
|
|
.internal_name = "3c501",
|
|
.flags = DEVICE_ISA,
|
|
.local = 0,
|
|
.init = threec501_nic_init,
|
|
.close = threec501_nic_close,
|
|
.reset = elnkR3Reset,
|
|
.available = NULL,
|
|
.speed_changed = NULL,
|
|
.force_redraw = NULL,
|
|
.config = threec501_config
|
|
};
|