3c90x.c revision a734c64bff58bda2fa48c2795453e092167b0ff7
/*
* 3c90x.c -- This file implements a iPXE API 3c90x driver
*
* Originally written for etherboot by:
* Greg Beeley, Greg.Beeley@LightSys.org
* Modified by Steve Smith,
* Steve.Smith@Juno.Com. Alignment bug fix Neil Newell (nn@icenoir.net).
* by Thomas Miletich, thomas.miletich@gmail.com
* Thanks to Marty Connor and Stefan Hajnoczi for their help and feedback,
* and to Daniel Verkamp for his help with testing.
*
* Copyright (c) 2009 Thomas Miletich
*
* Copyright (c) 1999 LightSys Technology Services, Inc.
* Portions Copyright (c) 1999 Steve Smith
*
* This program may be re-distributed in source or binary form, modified,
* sold, or copied for any purpose, provided that the above copyright message
* and this text are included with all source copies or derivative works, and
* provided that the above copyright message and this text are included in the
* documentation of any binary-only distributions. This program is distributed
* WITHOUT ANY WARRANTY, without even the warranty of FITNESS FOR A PARTICULAR
* PURPOSE or MERCHANTABILITY. Please read the associated documentation
* "3c90x.txt" before compiling and using this driver.
*
* [ --mdc 20090313 The 3c90x.txt file is now at:
*
* This program was written with the assistance of the 3com documentation for
* the 3c905B-TX card, as well as with some assistance from the 3c59x
* driver Donald Becker wrote for the Linux kernel, and with some assistance
* from the remainder of the Etherboot distribution.
*
* Indented with unix 'indent' command:
* $ indent -kr -i8 3c90x.c
*/
FILE_LICENCE ( BSD2 );
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <byteswap.h>
#include <errno.h>
#include <ipxe/ethernet.h>
#include <ipxe/if_ether.h>
#include <ipxe/netdevice.h>
#include "3c90x.h"
/**
* a3c90x_internal_IssueCommand: sends a command to the 3c90x card
* and waits for it's completion
*
* @v ioaddr IOAddress of the NIC
* @v cmd Command to be issued
* @v param Command parameter
*/
{
int cnt = 0;
DBGP("a3c90x_internal_IssueCommand\n");
/* Send the cmd to the cmd register */
/* Wait for the cmd to complete */
continue;
} else {
return;
}
}
}
/**
* a3c90x_internal_SetWindow: selects a register window set.
*
* @v inf_3c90x private NIC data
* @v window window to be selected
*/
{
DBGP("a3c90x_internal_SetWindow\n");
/* Window already as set? */
return;
/* Issue the window command. */
return;
}
{
int cnt = 0;
DBGP("a3c90x_internal_WaitForEeprom\n");
if (cnt == EEPROM_TIMEOUT) {
DBG("Read from eeprom failed: timeout\n");
return;
}
udelay(1);
cnt++;
}
}
/**
* a3c90x_internal_ReadEeprom - nvs routine to read eeprom data
* We only support reading one word(2 byte). The nvs subsystem will make sure
* that the routine will never be called with len != 2.
*
* @v nvs nvs data.
* @v address eeprom address to read data from.
* @v data data is put here.
* @v len number of bytes to read.
*/
static int
{
DBGP("a3c90x_internal_ReadEeprom\n");
/* we support reading 2 bytes only */
/* Select correct window */
/* set eepromRead bits in command sent to NIC */
/* send address to NIC */
/* read value */
return 0;
}
/**
* a3c90x_internal_WriteEeprom - nvs routine to write eeprom data
* currently not implemented
*
* @v nvs nvs data.
* @v address eeprom address to read data from.
* @v data data is put here.
* @v len number of bytes to read.
*/
static int
{
return -ENOTSUP;
}
{
DBGP("a3c90x_internal_ReadEepromContents\n");
}
/**
* a3c90x_reset: exported function that resets the card to its default
* state. This is so the Linux driver can re-set the card up the way
* it wants to. If CFG_3C90X_PRESERVE_XCVR is defined, then the reset will
* not alter the selected transceiver that we used to download the boot
* image.
*
* @v inf_3c90x Private NIC data
*/
{
DBGP("a3c90x_reset\n");
/* Send the reset command to the card */
DBG2("3c90x: Issuing RESET\n");
/* reset of the receiver on B-revision cards re-negotiates the link
* takes several seconds (a computer eternity), so we don't reset
* it here.
*/
/* global reset command resets station mask, non-B revision cards
* require explicit reset of values
*/
/* enable rxComplete and txComplete indications */
/* acknowledge any pending status flags */
cmdAcknowledgeInterrupt, 0x661);
return;
}
/**
* a3c90x_setup_tx_ring - Allocates TX ring, initialize tx_desc values
*
* @v p Private NIC data
*
* @ret Returns 0 on success, negative on failure
*/
static int a3c90x_setup_tx_ring(struct INF_3C90X *p)
{
DBGP("a3c90x_setup_tx_ring\n");
p->tx_ring =
if (!p->tx_ring) {
DBG("Could not allocate TX-ring\n");
return -ENOMEM;
}
p->tx_cur = 0;
p->tx_cnt = 0;
p->tx_tail = 0;
return 0;
}
/**
* a3c90x_process_tx_packets - Checks for successfully sent packets,
* reports them to iPXE with netdev_tx_complete();
*
* @v netdev Network device info
*/
{
unsigned int downlist_ptr;
DBGP("a3c90x_process_tx_packets\n");
/* NIC is currently working on this tx desc */
return;
DBG2("transmitted packet\n");
p->tx_cnt--;
}
}
static void a3c90x_free_tx_ring(struct INF_3C90X *p)
{
DBGP("a3c90x_free_tx_ring\n");
/* io_buffers are free()ed by netdev_tx_complete[,_err]() */
}
/**
* a3c90x_transmit - Transmits a packet.
*
* @v netdev Network device info
* @v iob io_buffer containing the data to be send
*
* @ret Returns 0 on success, negative on failure
*/
{
struct TXD *tx_cur_desc;
struct TXD *tx_prev_desc;
unsigned int len;
unsigned int downlist_ptr;
DBGP("a3c90x_transmit\n");
DBG("TX-Ring overflow\n");
return -ENOBUFS;
}
/* Setup the DPD (download descriptor) */
tx_cur_desc->DnNextPtr = 0;
/* FrameStartHeader differs in 90x and >= 90xB
* It contains length in 90x and a round up boundary and packet ID for
* 90xB and 90xC. We can leave this to 0 for 90xB and 90xC.
*/
/* We have to stall the download engine, so the NIC won't access the
* tx descriptor while we modify it. There is a way around this
* from revision B and upwards. To stay compatible with older revisions
* we don't use it here.
*/
dnStall);
if (downlist_ptr == 0) {
/* currently no DownList, sending a new one */
}
/* End Stall */
return 0;
}
/**
* a3c90x_prepare_rx_desc - fills the rx desc with initial data
*
* @v p NIC private data
* @v index Index for rx_iobuf and rx_ring array
*/
{
DBGP("a3c90x_prepare_rx_desc\n");
/* We have to stall the upload engine, so the NIC won't access the
* rx descriptor while we modify it. There is a way around this
* from revision B and upwards. To stay compatible with older revisions
* we don't use it here.
*/
/* unstall upload engine */
}
/**
* a3c90x_refill_rx_ring -checks every entry in the rx ring and reallocates
* them as necessary. Then it calls a3c90x_prepare_rx_desc to fill the rx desc
* with initial data.
*
* @v p NIC private data
*/
static void a3c90x_refill_rx_ring(struct INF_3C90X *p)
{
int i;
unsigned int status;
struct RXD *rx_cur_desc;
DBGP("a3c90x_refill_rx_ring\n");
for (i = 0; i < RX_RING_SIZE; i++) {
rx_cur_desc = p->rx_ring + i;
/* only refill used descriptor */
if (!(status & upComplete))
continue;
/* we still need to process this descriptor */
continue;
DBG("alloc_iob() failed\n");
break;
}
a3c90x_prepare_rx_desc(p, i);
}
}
/**
* a3c90x_setup_rx_ring - Allocates RX ring, initialize rx_desc values
*
* @v p Private NIC data
*
* @ret Returns 0 on success, negative on failure
*/
static int a3c90x_setup_rx_ring(struct INF_3C90X *p)
{
int i;
DBGP("a3c90x_setup_rx_ring\n");
p->rx_ring =
if (!p->rx_ring) {
DBG("Could not allocate RX-ring\n");
return -ENOMEM;
}
p->rx_cur = 0;
for (i = 0; i < RX_RING_SIZE; i++) {
/* these are needed so refill_rx_ring initializes the ring */
}
/* Loop the ring */
return 0;
}
static void a3c90x_free_rx_ring(struct INF_3C90X *p)
{
DBGP("a3c90x_free_rx_ring\n");
}
static void a3c90x_free_rx_iobuf(struct INF_3C90X *p)
{
int i;
DBGP("a3c90x_free_rx_iobuf\n");
for (i = 0; i < RX_RING_SIZE; i++) {
}
}
/**
* a3c90x_process_rx_packets - Checks for received packets,
* reports them to iPXE with netdev_rx() or netdev_rx_err() if there was an
* error while receiving the packet
*
* @v netdev Network device info
*/
{
int i;
unsigned int rx_status;
struct RXD *rx_cur_desc;
DBGP("a3c90x_process_rx_packets\n");
for (i = 0; i < RX_RING_SIZE; i++) {
break;
break;
-EINVAL);
} else {
/* if we're here, we've got good packet */
int packet_len;
DBG2("received packet\n");
}
}
}
/**
* a3c90x_poll - Routine that gets called periodically.
* Here we hanle transmitted and received packets.
* We could also check the link status from time to time, which we
* currently don't do.
*
* @v netdev Network device info
*/
{
DBGP("a3c90x_poll\n");
if ( int_status == 0 )
return;
if (int_status & INT_TXCOMPLETE)
}
static void a3c90x_free_resources(struct INF_3C90X *p)
{
DBGP("a3c90x_free_resources\n");
}
/**
* a3c90x_remove - Routine to remove the card. Unregisters
*
* @v pci PCI device info
*/
{
DBGP("a3c90x_remove\n");
/* Disable the receiver and transmitter. */
}
{
DBGP("a3c90x_irq\n");
if (enable == 0) {
/* disable interrupts */
} else {
0x661);
}
}
/**
* a3c90x_hw_start - Initialize hardware, copy MAC address
* to NIC registers, set default receiver
*/
{
int i, c;
unsigned int cfg;
unsigned int mopt;
unsigned short linktype;
DBGP("a3c90x_hw_start\n");
/* 3C556: Invert MII power */
unsigned int tmp;
tmp |= 0x4000;
}
/* Copy MAC address into the NIC registers */
for (i = 0; i < ETH_ALEN; i++)
for (i = 0; i < ETH_ALEN; i++)
/* Read the media options register, print a message and set default
* xcvr.
*
* Uses Media Option command on B revision, Reset Option on non-B
* revision cards -- same register address
*/
/* mask out VCO bit that is defined as 10baseFL bit on B-rev cards */
mopt &= 0x7F;
}
DBG2("Connectors present: ");
c = 0;
linktype = 0x0008;
if (mopt & 0x01) {
}
if (mopt & 0x04) {
}
if (mopt & 0x10) {
}
if (mopt & 0x20) {
}
if (mopt & 0x40) {
}
}
DBG2(".\n");
/* Determine transceiver type to use, depending on value stored in
* eeprom 0x16
*/
/* User-defined */
}
} else {
/* I don't know what MII MAC only mode is!!! */
if (linktype == linkExternalMII) {
DBG("WARNING: MII External MAC Mode only supported on B-revision " "cards!!!!\nFalling Back to MII Mode\n");
}
}
/* enable DC converter for 10-Base-T */
if (linktype == link10Base2) {
cmdEnableDcConverter, 0);
}
/* Set the link to the type we just determined. */
DBG2("Setting internal cfg register: 0x%08X (linktype: 0x%02X)\n",
/* Now that we set the xcvr type, reset the Tx and Rx */
/* Set the RX filter = receive only individual pkts & multicast & bcast. */
0x01 + 0x02 + 0x04);
/*
* set Indication and Interrupt flags , acknowledge any IRQ's
*/
cmdAcknowledgeInterrupt, 0x661);
}
/**
* a3c90x_open - Routine to initialize the card. Initialize hardware,
* allocate TX and RX ring, send RX ring address to the NIC.
*
* @v netdev Network device info
*
* @ret Returns 0 on success, negative on failure
*/
{
int rc;
DBGP("a3c90x_open\n");
if (rc != 0) {
DBG("Error setting up TX Ring\n");
goto error;
}
if (rc != 0) {
DBG("Error setting up RX Ring\n");
goto error;
}
/* send rx_ring address to NIC */
/* enable packet transmission and reception */
return 0;
return rc;
}
/**
*
* @v netdev Network device info
*/
{
DBGP("a3c90x_close\n");
}
static struct net_device_operations a3c90x_operations = {
.open = a3c90x_open,
.close = a3c90x_close,
.poll = a3c90x_poll,
.irq = a3c90x_irq,
};
/**
* a3c90x_probe: exported routine to probe for the 3c905 card.
* If this routine is called, the pci functions did find the
* card. We read the eeprom here and get the MAC address.
* Initialization is done in a3c90x_open().
*
* @v pci PCI device info
* @ pci_id PCI device IDs
*
* @ret rc Returns 0 on success, negative on failure
*/
{
struct net_device *netdev;
unsigned char *HWAddr;
int rc;
DBGP("a3c90x_probe\n");
return -EINVAL;
if (!netdev)
return -ENOMEM;
case 0x9000: /* 10 Base TPO */
case 0x9001: /* 10/100 T4 */
case 0x9050: /* 10/100 TPO */
case 0x9051: /* 10 Base Combo */
break;
}
DBG2("[3c90x]: found NIC(0x%04X, 0x%04X), isBrev=%d, is3c556=%d\n",
/* initialize nvs device */
/* reset NIC before accessing any data from it */
/* load eeprom contents to inf_3c90x->eeprom */
/* Retrieve the Hardware address */
DBG("3c90x: register_netdev() failed\n");
return rc;
}
/* we don't handle linkstates yet, so we're always up */
return 0;
}
static struct pci_device_id a3c90x_nics[] = {
/* Original 90x revisions: */
/* Newer 90xB revisions: */
/* Newer 90xC revision: */
PCI_ROM(0x10b7, 0x9202, "3c920b-emb-ati", "3c920B-EMB-WNM (ATI Radeon 9100 IGP)", 0), /* 3c920B-EMB-WNM (ATI Radeon 9100 IGP) */
};
.ids = a3c90x_nics,
.probe = a3c90x_probe,
.remove = a3c90x_remove,
};
/*
* Local variables:
* c-basic-offset: 8
* c-indent-level: 8
* tab-width: 8
* End:
*/