minix/drivers/atl2/atl2.c
2010-01-25 23:23:43 +00:00

1355 lines
36 KiB
C

/* Attansic/Atheros L2 FastEthernet driver, by D.C. van Moolenbroek */
/* No documentation is available for this card. The FreeBSD driver is based
* heavily on the official Linux driver; this driver is based heavily on both.
*/
#include "../drivers.h"
#include <signal.h>
#include <sys/mman.h>
#include <minix/ds.h>
#include <minix/vm.h>
#include <ibm/pci.h>
#include <net/gen/ether.h>
#include <net/gen/eth_io.h>
#include <assert.h>
#include "atl2.h"
#define VERBOSE 0 /* Verbose debugging output */
#define ATL2_FKEY 1 /* Register Shift+F11 for dumping statistics */
#if VERBOSE
#define ATL2_DEBUG(x) printf x
#else
#define ATL2_DEBUG(x)
#endif
typedef struct {
u32_t hdr;
u32_t vtag;
u8_t data[ATL2_RXD_SIZE - sizeof(u32_t) * 2];
} rxd_t;
PRIVATE struct {
int devind; /* PCI device index */
int irq; /* IRQ number */
int hook_id; /* IRQ hook ID */
int mode; /* datalink mode */
char *base; /* base address of memory-mapped registers */
u32_t hwaddr[2]; /* MAC address, in register representation */
u8_t *txd_base; /* local address of TxD ring buffer base */
u32_t *txs_base; /* local address of TxS ring buffer base */
u8_t *rxd_base_u; /* unaligned base address of RxD ring buffer */
rxd_t *rxd_base; /* local address of RxD ring buffer base */
int rxd_align; /* alignment offset of RxD ring buffer */
vir_bytes txd_phys; /* physical address of TxD ring buffer */
vir_bytes txs_phys; /* physical address of TxS ring buffer */
vir_bytes rxd_phys; /* physical address of RxD ring buffer */
int txd_tail; /* tail index into TxD, in bytes */
int txd_num; /* head-tail offset into TxD, in bytes */
int txs_tail; /* tail index into TxS, in elements */
int txs_num; /* head-tail offset into TxS, in elements */
int rxd_tail; /* tail index into RxD, in elements */
int flags; /* state flags (ATL2_FLAG_) */
message read_msg; /* suspended read request (READ_PEND) */
message write_msg; /* suspended write request (WRITE_PEND) */
endpoint_t task_endpt; /* requester endpoint (PACK_RCVD|PACK_SENT) */
size_t recv_count; /* packet size (PACK_RCVD) */
eth_stat_t stat; /* statistics */
} state;
#define ATL2_FLAG_RX_AVAIL 0x01 /* packet available for receipt */
#define ATL2_FLAG_READ_PEND 0x02 /* read request pending */
#define ATL2_FLAG_WRITE_PEND 0x04 /* write request pending */
#define ATL2_FLAG_PACK_RCVD 0x08 /* packet received */
#define ATL2_FLAG_PACK_SENT 0x10 /* packet transmitted */
#define ATL2_READ_U8(off) (* (u8_t *) (state.base + (off)))
#define ATL2_READ_U16(off) (* (u16_t *) (state.base + (off)))
#define ATL2_READ_U32(off) (* (u32_t *) (state.base + (off)))
#define ATL2_WRITE_U8(off, val) * (u8_t *) (state.base + (off)) = (val);
#define ATL2_WRITE_U16(off, val) * (u16_t *) (state.base + (off)) = (val);
#define ATL2_WRITE_U32(off, val) * (u32_t *) (state.base + (off)) = (val);
#define ATL2_ALIGN_32(n) (((n) + 3) & ~3)
PRIVATE iovec_s_t iovec[NR_IOREQS];
PRIVATE struct {
u16_t vid;
u16_t did;
} pcitab[] = {
{ 0x1969, 0x2048 }, /* Attansic Technology Corp, L2 FastEthernet */
{ 0x0000, 0x0000 }
};
PRIVATE long instance;
/*===========================================================================*
* atl2_read_vpd *
*===========================================================================*/
PRIVATE int atl2_read_vpd(int index, u32_t *res)
{
/* Read a value from the VPD register area.
*/
u32_t off, val;
int i;
ATL2_WRITE_U32(ATL2_VPD_DATA_REG, 0);
off = ATL2_VPD_REGBASE + index * sizeof(u32_t);
ATL2_WRITE_U32(ATL2_VPD_CAP_REG,
(off << ATL2_VPD_CAP_ADDR_SHIFT) & ATL2_VPD_CAP_ADDR_MASK);
for (i = 0; i < ATL2_VPD_NTRIES; i++) {
micro_delay(ATL2_VPD_DELAY);
val = ATL2_READ_U32(ATL2_VPD_CAP_REG);
if (val & ATL2_VPD_CAP_DONE)
break;
}
if (i == ATL2_VPD_NTRIES) {
printf("ATL2: timeout reading EEPROM register %d\n", index);
return FALSE;
}
*res = ATL2_READ_U32(ATL2_VPD_DATA_REG);
return TRUE;
}
/*===========================================================================*
* atl2_get_vpd_hwaddr *
*===========================================================================*/
PRIVATE int atl2_get_vpd_hwaddr(void)
{
/* Read the MAC address from the EEPROM, using the Vital Product Data
* register interface.
*/
u32_t key, val;
int i, n, found[2];
/* No idea, copied from FreeBSD which copied it from Linux. */
val = ATL2_READ_U32(ATL2_SPICTL_REG);
if (val & ATL2_SPICTL_VPD_EN) {
val &= ~ATL2_SPICTL_VPD_EN;
ATL2_WRITE_U32(ATL2_SPICTL_REG, val);
}
/* Is VPD supported? */
#ifdef PCI_CAP_VPD /* FIXME: just a guess at the future name */
if (!pci_find_cap(state.devind, PCI_CAP_VPD, &n))
return FALSE;
#endif
/* Read out the set of key/value pairs. Look for the two parts that
* make up the MAC address.
*/
found[0] = found[1] = FALSE;
for (i = 0; i < ATL2_VPD_NREGS; i += 2) {
if (!atl2_read_vpd(i, &key))
break;
if ((key & ATL2_VPD_SIG_MASK) != ATL2_VPD_SIG)
break;
key >>= ATL2_VPD_REG_SHIFT;
if (key != ATL2_HWADDR0_REG && key != ATL2_HWADDR1_REG)
continue;
if (!atl2_read_vpd(i + 1, &val))
break;
n = (key == ATL2_HWADDR1_REG);
state.hwaddr[n] = val;
found[n] = TRUE;
if (found[1 - n]) break;
}
return found[0] && found[1];
}
/*===========================================================================*
* atl2_get_hwaddr *
*===========================================================================*/
PRIVATE void atl2_get_hwaddr(void)
{
/* Get the MAC address of the card. First try the EEPROM; if that
* fails, just use whatever the card was already set to.
*/
if (!atl2_get_vpd_hwaddr()) {
printf("ATL2: unable to read from VPD\n");
state.hwaddr[0] = ATL2_READ_U32(ATL2_HWADDR0_REG);
state.hwaddr[1] = ATL2_READ_U32(ATL2_HWADDR1_REG) & 0xffff;
}
ATL2_DEBUG(("ATL2: MAC address %04lx%08lx\n",
state.hwaddr[1], state.hwaddr[0]));
}
/*===========================================================================*
* atl2_read_mdio *
*===========================================================================*/
PRIVATE int atl2_read_mdio(int addr, u16_t *res)
{
/* Read a MII PHY register using MDIO.
*/
u32_t rval;
int i;
rval = ((addr << ATL2_MDIO_ADDR_SHIFT) & ATL2_MDIO_ADDR_MASK) |
ATL2_MDIO_START | ATL2_MDIO_READ | ATL2_MDIO_SUP_PREAMBLE |
ATL2_MDIO_CLK_25_4;
ATL2_WRITE_U32(ATL2_MDIO_REG, rval);
for (i = 0; i < ATL2_MDIO_NTRIES; i++) {
micro_delay(ATL2_MDIO_DELAY);
rval = ATL2_READ_U32(ATL2_MDIO_REG);
if (!(rval & (ATL2_MDIO_START | ATL2_MDIO_BUSY)))
break;
}
if (i == ATL2_MDIO_NTRIES) return FALSE;
*res = (u16_t) (rval & ATL2_MDIO_DATA_MASK);
return TRUE;
}
/*===========================================================================*
* atl2_alloc_dma *
*===========================================================================*/
PRIVATE int atl2_alloc_dma(void)
{
/* Allocate DMA ring buffers.
*/
state.txd_base = alloc_contig(ATL2_TXD_BUFSIZE,
AC_ALIGN4K, &state.txd_phys);
state.txs_base = alloc_contig(ATL2_TXS_COUNT * sizeof(u32_t),
AC_ALIGN4K, &state.txs_phys);
/* The data buffer in each RxD descriptor must be 128-byte aligned.
* The two Tx buffers merely require a 4-byte start alignment.
*/
state.rxd_align = 128 - offsetof(rxd_t, data);
state.rxd_base_u =
alloc_contig(state.rxd_align + ATL2_RXD_COUNT * ATL2_RXD_SIZE,
AC_ALIGN4K, &state.rxd_phys);
/* Unlike mmap, alloc_contig returns NULL on failure. */
if (!state.txd_base || !state.txs_base || !state.rxd_base_u)
return ENOMEM;
state.rxd_base = (rxd_t *) (state.rxd_base_u + state.rxd_align);
state.rxd_phys += state.rxd_align;
/* Zero out just in case. */
memset(state.txd_base, 0, ATL2_TXD_BUFSIZE);
memset(state.txs_base, 0, ATL2_TXS_COUNT * sizeof(u32_t));
memset(state.rxd_base, 0, ATL2_RXD_COUNT * ATL2_RXD_SIZE);
return OK;
}
/*===========================================================================*
* atl2_stop *
*===========================================================================*/
PRIVATE int atl2_stop(void)
{
/* Stop the device.
*/
u32_t val;
int i;
/* Clear and disable interrupts. */
ATL2_WRITE_U32(ATL2_IMR_REG, 0);
ATL2_WRITE_U32(ATL2_ISR_REG, 0xffffffff);
/* Stop Rx/Tx MACs. */
val = ATL2_READ_U32(ATL2_MAC_REG);
if (val & (ATL2_MAC_RX_EN | ATL2_MAC_TX_EN)) {
val &= ~(ATL2_MAC_RX_EN | ATL2_MAC_TX_EN);
ATL2_WRITE_U32(ATL2_MAC_REG, val);
}
ATL2_WRITE_U8(ATL2_DMAWRITE_REG, 0);
ATL2_WRITE_U8(ATL2_DMAREAD_REG, 0);
/* Wait until everything is idle. */
for (i = 0; i < ATL2_IDLE_NTRIES; i++) {
if (ATL2_READ_U32(ATL2_IDLE_REG) == 0)
break;
micro_delay(ATL2_IDLE_DELAY);
}
/* The caller will generally ignore this return value. */
return (i < ATL2_IDLE_NTRIES);
}
/*===========================================================================*
* atl2_reset *
*===========================================================================*/
PRIVATE int atl2_reset(void)
{
/* Reset the device to a known good state.
*/
u32_t val;
int i;
/* Issue a soft reset, and wait for the device to respond. */
ATL2_WRITE_U32(ATL2_MASTER_REG, ATL2_MASTER_SOFT_RESET);
for (i = 0; i < ATL2_RESET_NTRIES; i++) {
val = ATL2_READ_U32(ATL2_MASTER_REG);
if (!(val & ATL2_MASTER_SOFT_RESET))
break;
micro_delay(ATL2_RESET_DELAY);
}
if (i == ATL2_RESET_NTRIES)
return FALSE;
/* Wait until everything is idle. */
for (i = 0; i < ATL2_IDLE_NTRIES; i++) {
if (ATL2_READ_U32(ATL2_IDLE_REG) == 0)
break;
micro_delay(ATL2_IDLE_DELAY);
}
return (i < ATL2_IDLE_NTRIES);
}
/*===========================================================================*
* atl2_set_mode *
*===========================================================================*/
PRIVATE void atl2_set_mode(void)
{
/* Reconfigure the device's promiscuity, multicast, and broadcast mode
* settings.
*/
u32_t val;
val = ATL2_READ_U32(ATL2_MAC_REG);
val &= ~(ATL2_MAC_PROMISC_EN | ATL2_MAC_MCAST_EN | ATL2_MAC_BCAST_EN);
if (state.mode & DL_PROMISC_REQ)
val |= ATL2_MAC_PROMISC_EN;
if (state.mode & DL_MULTI_REQ)
val |= ATL2_MAC_MCAST_EN;
if (state.mode & DL_BROAD_REQ)
val |= ATL2_MAC_BCAST_EN;
ATL2_WRITE_U32(ATL2_MAC_REG, val);
}
/*===========================================================================*
* atl2_setup *
*===========================================================================*/
PRIVATE int atl2_setup(void)
{
/* Set up the device for normal operation.
*/
u32_t val;
atl2_stop();
if (!atl2_reset())
return FALSE;
/* Initialize PCIE module. Magic. */
ATL2_WRITE_U32(ATL2_LTSSM_TESTMODE_REG, ATL2_LTSSM_TESTMODE_DEFAULT);
ATL2_WRITE_U32(ATL2_DLL_TX_CTRL_REG, ATL2_DLL_TX_CTRL_DEFAULT);
/* Enable PHY. */
ATL2_WRITE_U32(ATL2_PHY_ENABLE_REG, ATL2_PHY_ENABLE);
micro_delay(1000);
/* Clear and disable interrupts. */
ATL2_WRITE_U32(ATL2_ISR_REG, 0xffffffff);
/* Set the MAC address. */
ATL2_WRITE_U32(ATL2_HWADDR0_REG, state.hwaddr[0]);
ATL2_WRITE_U32(ATL2_HWADDR1_REG, state.hwaddr[1]);
/* Initialize ring buffer addresses and sizes. */
ATL2_WRITE_U32(ATL2_DESC_ADDR_HI_REG, 0); /* no 64 bit */
ATL2_WRITE_U32(ATL2_TXD_ADDR_LO_REG, state.txd_phys);
ATL2_WRITE_U32(ATL2_TXS_ADDR_LO_REG, state.txs_phys);
ATL2_WRITE_U32(ATL2_RXD_ADDR_LO_REG, state.rxd_phys);
ATL2_WRITE_U16(ATL2_RXD_COUNT_REG, ATL2_RXD_COUNT);
ATL2_WRITE_U16(ATL2_TXD_BUFSIZE_REG, ATL2_TXD_BUFSIZE / sizeof(u32_t));
ATL2_WRITE_U16(ATL2_TXS_COUNT_REG, ATL2_TXS_COUNT);
/* A whole lot of other initialization copied from Linux/FreeBSD. */
ATL2_WRITE_U32(ATL2_IFG_REG, ATL2_IFG_DEFAULT);
ATL2_WRITE_U32(ATL2_HDPX_REG, ATL2_HDPX_DEFAULT);
ATL2_WRITE_U16(ATL2_IMT_REG, ATL2_IMT_DEFAULT);
val = ATL2_READ_U32(ATL2_MASTER_REG);
ATL2_WRITE_U32(ATL2_MASTER_REG, val | ATL2_MASTER_IMT_EN);
ATL2_WRITE_U16(ATL2_ICT_REG, ATL2_ICT_DEFAULT);
ATL2_WRITE_U32(ATL2_CUT_THRESH_REG, ATL2_CUT_THRESH_DEFAULT);
ATL2_WRITE_U16(ATL2_FLOW_THRESH_HI_REG, (ATL2_RXD_COUNT / 8) * 7);
ATL2_WRITE_U16(ATL2_FLOW_THRESH_LO_REG, ATL2_RXD_COUNT / 12);
/* Set MTU. */
ATL2_WRITE_U16(ATL2_MTU_REG, ATL2_MTU_DEFAULT);
/* Reset descriptors, and enable DMA. */
state.txd_tail = state.txs_tail = state.rxd_tail = 0;
state.txd_num = state.txs_num = 0;
state.flags &= ~ATL2_FLAG_RX_AVAIL;
ATL2_WRITE_U16(ATL2_TXD_IDX_REG, 0);
ATL2_WRITE_U16(ATL2_RXD_IDX_REG, 0);
ATL2_WRITE_U8(ATL2_DMAREAD_REG, ATL2_DMAREAD_EN);
ATL2_WRITE_U8(ATL2_DMAWRITE_REG, ATL2_DMAWRITE_EN);
/* Did everything go alright? */
val = ATL2_READ_U32(ATL2_ISR_REG);
if (val & ATL2_ISR_PHY_LINKDOWN) {
printf("ATL2: initialization failed\n");
return FALSE;
}
/* Clear interrupt status. */
ATL2_WRITE_U32(ATL2_ISR_REG, 0x3fffffff);
ATL2_WRITE_U32(ATL2_ISR_REG, 0);
/* Enable interrupts. */
ATL2_WRITE_U32(ATL2_IMR_REG, ATL2_IMR_DEFAULT);
/* Configure MAC. */
ATL2_WRITE_U32(ATL2_MAC_REG, ATL2_MAC_DEFAULT);
/* Inet does not tell us about the multicast addresses that it is
* interested in, so we have to simply accept all multicast packets.
*/
ATL2_WRITE_U32(ATL2_MHT0_REG, 0xffffffff);
ATL2_WRITE_U32(ATL2_MHT1_REG, 0xffffffff);
atl2_set_mode();
/* Enable Tx/Rx. */
val = ATL2_READ_U32(ATL2_MAC_REG);
ATL2_WRITE_U32(ATL2_MAC_REG, val | ATL2_MAC_TX_EN | ATL2_MAC_RX_EN);
return TRUE;
}
/*===========================================================================*
* atl2_probe *
*===========================================================================*/
PRIVATE int atl2_probe(int instance)
{
/* Find a matching PCI device.
*/
u16_t vid, did;
char *dname;
int i, r, devind, skip;
pci_init();
r = pci_first_dev(&devind, &vid, &did);
if (r <= 0)
return -1;
skip = 0;
for (;;) {
for (i = 0; pcitab[i].vid != 0; i++)
if (pcitab[i].vid == vid && pcitab[i].did == did)
break;
if (pcitab[i].vid != 0) {
if (skip == instance) break;
skip++;
}
r = pci_next_dev(&devind, &vid, &did);
if (r <= 0)
return -1;
}
dname = pci_dev_name(vid, did);
ATL2_DEBUG(("ATL2: found %s (%x/%x) at %s\n",
dname ? dname : "<unknown>", vid, did,
pci_slot_name(devind)));
pci_reserve(devind);
return devind;
}
/*===========================================================================*
* atl2_init *
*===========================================================================*/
PRIVATE void atl2_init(int devind)
{
/* Initialize the device.
*/
u32_t bar;
int r;
/* Initialize global state. */
state.devind = devind;
state.mode = DL_NOMODE;
state.flags = 0;
state.recv_count = 0;
memset(&state.stat, 0, sizeof(state.stat));
bar = pci_attr_r32(devind, PCI_BAR) & 0xfffffff0;
/* FIXME: hardcoded length, as PCI doesn't expose the size, and it is
* not our job to compute the size from the BAR ourselves.
*/
state.base = vm_map_phys(SELF, (void *) bar, ATL2_MMAP_SIZE);
if (state.base == MAP_FAILED)
panic("atl2", "unable to map in registers", NO_NUM);
if ((r = atl2_alloc_dma()) != OK)
panic("atl2", "unable to allocate DMA buffers", r);
state.irq = pci_attr_r8(devind, PCI_ILR);
if ((r = sys_irqsetpolicy(state.irq, 0, &state.hook_id)) != OK)
panic("atl2", "unable to register IRQ", r);
if (!atl2_reset())
panic("atl2", "unable to reset hardware", NO_NUM);
if ((r = sys_irqenable(&state.hook_id)) != OK)
panic("atl2", "unable to enable IRQ", r);
atl2_get_hwaddr();
atl2_setup();
}
/*===========================================================================*
* atl2_tx_stat *
*===========================================================================*/
PRIVATE void atl2_tx_stat(u32_t stat)
{
/* Update statistics for packet transmission.
*/
if (stat & ATL2_TXS_SUCCESS)
state.stat.ets_packetT++;
else
state.stat.ets_recvErr++;
if (stat & ATL2_TXS_DEFER)
state.stat.ets_transDef++;
if (stat & (ATL2_TXS_EXCDEFER | ATL2_TXS_ABORTCOL))
state.stat.ets_transAb++;
if (stat & ATL2_TXS_SINGLECOL)
state.stat.ets_collision++;
if (stat & ATL2_TXS_MULTICOL)
state.stat.ets_collision++;
if (stat & ATL2_TXS_LATECOL)
state.stat.ets_OWC++;
if (stat & ATL2_TXS_UNDERRUN)
state.stat.ets_fifoUnder++;
}
/*===========================================================================*
* atl2_rx_stat *
*===========================================================================*/
PRIVATE void atl2_rx_stat(u32_t stat)
{
/* Update statistics for packet receipt.
*/
if (stat & ATL2_RXD_SUCCESS)
state.stat.ets_packetR++;
else
state.stat.ets_recvErr++;
if (stat & ATL2_RXD_CRCERR)
state.stat.ets_CRCerr++;
if (stat & ATL2_RXD_FRAG)
state.stat.ets_collision++;
if (stat & ATL2_RXD_TRUNC)
state.stat.ets_fifoOver++;
if (stat & ATL2_RXD_ALIGN)
state.stat.ets_frameAll++;
}
/*===========================================================================*
* atl2_tx_advance *
*===========================================================================*/
PRIVATE int atl2_tx_advance(void)
{
/* Advance the TxD/TxS tails by as many sent packets as found.
*/
u32_t stat, size, dsize;
int advanced;
advanced = FALSE;
while (state.txs_num > 0) {
/* Has the tail packet been processed by the driver? */
stat = state.txs_base[state.txs_tail];
if (!(stat & ATL2_TXS_UPDATE))
break;
/* The packet size from the status must match the packet size
* we put in. If they don't, there's not much we can do..
*/
size = stat & ATL2_TXS_SIZE_MASK;
assert(state.txd_tail <= ATL2_TXD_BUFSIZE - sizeof(u32_t));
dsize = * (u32_t *) (state.txd_base + state.txd_tail);
if (size != dsize)
printf("ATL2: TxD/TxS size mismatch (%lx vs %lx)\n",
size, dsize);
/* Advance tails accordingly. */
size = sizeof(u32_t) + ATL2_ALIGN_32(dsize);
assert(state.txd_num >= size);
state.txd_tail = (state.txd_tail + size) % ATL2_TXD_BUFSIZE;
state.txd_num -= size;
state.txs_tail = (state.txs_tail + 1) % ATL2_TXS_COUNT;
state.txs_num--;
if (stat & ATL2_TXS_SUCCESS)
ATL2_DEBUG(("ATL2: successfully sent packet\n"));
else
ATL2_DEBUG(("ATL2: failed to send packet\n"));
/* Update statistics. */
atl2_tx_stat(stat);
advanced = TRUE;
}
return advanced;
}
/*===========================================================================*
* atl2_rx_advance *
*===========================================================================*/
PRIVATE void atl2_rx_advance(int next)
{
/* Advance the RxD tail by as many failed receipts as possible, and
* see if there is an actual packet left to receive. If 'next' is set,
* the packet at the current tail has been processed.
*/
int update_tail;
rxd_t *rxd;
u32_t hdr, size;
update_tail = FALSE;
if (next) {
state.rxd_tail = (state.rxd_tail + 1) % ATL2_RXD_COUNT;
update_tail = TRUE;
ATL2_DEBUG(("ATL2: successfully received packet\n"));
state.flags &= ~ATL2_FLAG_RX_AVAIL;
}
assert(!(state.flags & ATL2_FLAG_RX_AVAIL));
for (;;) {
/* Check the RxD tail for updates. */
rxd = &state.rxd_base[state.rxd_tail];
hdr = rxd->hdr;
if (!(hdr & ATL2_RXD_UPDATE))
break;
rxd->hdr = hdr & ~(ATL2_RXD_UPDATE);
/* Update statistics. */
atl2_rx_stat(hdr);
/* Stop at the first successful receipt. The packet will be
* picked up by Inet later.
*/
size = hdr & ATL2_RXD_SIZE_MASK;
if ((hdr & ATL2_RXD_SUCCESS) && size >= ETH_MIN_PACK_SIZE) {
ATL2_DEBUG(("ATL2: packet available, size %ld\n",
size));
state.flags |= ATL2_FLAG_RX_AVAIL;
break;
}
ATL2_DEBUG(("ATL2: packet receipt failed\n"));
/* Advance tail. */
state.rxd_tail = (state.rxd_tail + 1) % ATL2_RXD_COUNT;
update_tail = TRUE;
}
/* If new RxD descriptors are now up for reuse, tell the device. */
if (update_tail)
ATL2_WRITE_U32(ATL2_RXD_IDX_REG, state.rxd_tail);
}
/*===========================================================================*
* atl2_reply *
*===========================================================================*/
PRIVATE void atl2_reply(void)
{
/* Send a task reply to Inet.
*/
message m;
int r, stat;
stat = 0;
if (state.flags & ATL2_FLAG_PACK_SENT)
stat |= DL_PACK_SEND;
if (state.flags & ATL2_FLAG_PACK_RCVD)
stat |= DL_PACK_RECV;
m.m_type = DL_TASK_REPLY;
m.DL_PORT = 0;
m.DL_PROC = state.task_endpt;
m.DL_STAT = stat;
m.DL_COUNT = state.recv_count;
ATL2_DEBUG(("ATL2: sending reply stat %x count %d\n", stat,
m.DL_COUNT));
if ((r = send(state.task_endpt, &m)) != OK)
panic("atl2", "unable to reply", r);
state.flags &= ~(ATL2_FLAG_PACK_SENT | ATL2_FLAG_PACK_RCVD);
state.recv_count = 0;
}
/*===========================================================================*
* atl2_readv *
*===========================================================================*/
PRIVATE void atl2_readv(message *m, int from_int)
{
/* Read packet data.
*/
rxd_t *rxd;
iovec_s_t *iovp;
size_t count, off, left, size;
u8_t *pos;
int i, j, r, batch;
if (m->DL_PORT != 0) {
printf("ATL2: read from invalid port\n");
return;
}
/* We can deal with only one read request from Inet at a time. */
assert(from_int || !(state.flags & ATL2_FLAG_READ_PEND));
/* The exact semantics of DL_PROC are not clear. This driver takes it
* to be only the grant owner; other drivers treat it as the reply
* destination as well.
*/
assert(m->m_source == m->DL_PROC);
state.task_endpt = m->m_source;
/* Are there any packets available at all? */
if (!(state.flags & ATL2_FLAG_RX_AVAIL))
goto suspend;
/* Get the first available packet's size. Cut off the CRC. */
rxd = &state.rxd_base[state.rxd_tail];
count = rxd->hdr & ATL2_RXD_SIZE_MASK;
count -= ETH_CRC_SIZE;
ATL2_DEBUG(("ATL2: readv: found packet with length %d\n", count));
/* Copy out the packet. */
off = 0;
left = count;
pos = rxd->data;
for (i = 0; i < m->DL_COUNT && left > 0; i += batch) {
/* Copy in the next batch. */
batch = MIN(m->DL_COUNT - i, NR_IOREQS);
r = sys_safecopyfrom(m->DL_PROC, m->DL_GRANT, off,
(vir_bytes) iovec, batch * sizeof(iovec[0]), D);
if (r != OK)
panic("atl2", "vector copy failed", r);
/* Copy out each element in the batch, until we run out. */
for (j = 0, iovp = iovec; j < batch && left > 0; j++, iovp++) {
size = MIN(iovp->iov_size, left);
r = sys_safecopyto(m->DL_PROC, iovp->iov_grant, 0,
(vir_bytes) pos, size, D);
if (r != OK)
panic("atl2", "safe copy failed", r);
pos += size;
left -= size;
}
off += batch * sizeof(iovec[0]);
}
/* Not sure what to do here. Inet shouldn't mess this up anyway. */
if (left > 0) {
printf("ATL2: truncated packet of %d bytes by %d bytes\n",
count, left);
count -= left;
}
/* We are done with this packet. Move on to the next. */
atl2_rx_advance(TRUE /*next*/);
/* We have now successfully received a packet. */
state.flags &= ~ATL2_FLAG_READ_PEND;
state.flags |= ATL2_FLAG_PACK_RCVD;
state.recv_count = count;
/* If called from the interrupt handler, the caller will reply. */
if (!from_int)
atl2_reply();
return;
suspend:
/* No packets are available at this time. If we were not already
* trying to resume receipt, save the read request for later, and tell
* Inet that the request has been suspended.
*/
if (from_int)
return;
state.flags |= ATL2_FLAG_READ_PEND;
state.read_msg = *m;
atl2_reply();
}
/*===========================================================================*
* atl2_writev *
*===========================================================================*/
PRIVATE void atl2_writev(message *m, int from_int)
{
/* Write packet data.
*/
iovec_s_t *iovp;
size_t off, count, left, pos, size, skip;
u8_t *sizep;
int i, j, r, batch, maxnum;
if (m->DL_PORT != 0) {
printf("ATL2: write to invalid port\n");
return;
}
/* We can deal with only one write request from Inet at a time. */
assert(from_int || !(state.flags & ATL2_FLAG_WRITE_PEND));
assert(m->m_source == m->DL_PROC);
state.task_endpt = m->m_source;
/* If we are already certain that the packet won't fit, bail out.
* Keep at least some space between TxD head and tail, as it is not
* clear whether the device deals well with the case that they collide.
*/
if (state.txs_num >= ATL2_TXS_COUNT)
goto suspend;
maxnum = ATL2_TXD_BUFSIZE - ETH_MIN_PACK_SIZE - sizeof(u32_t);
if (state.txd_num >= maxnum)
goto suspend;
/* Optimistically try to copy in the data; suspend if it turns out
* that it does not fit.
*/
off = 0;
count = 0;
left = state.txd_num - sizeof(u32_t);
pos = (state.txd_tail + state.txd_num +
sizeof(u32_t)) % ATL2_TXD_BUFSIZE;
for (i = 0; i < m->DL_COUNT; i += batch) {
/* Copy in the next batch. */
batch = MIN(m->DL_COUNT - i, NR_IOREQS);
r = sys_safecopyfrom(m->DL_PROC, m->DL_GRANT, off,
(vir_bytes) iovec, batch * sizeof(iovec[0]), D);
if (r != OK)
panic("atl2", "vector copy failed", r);
/* Copy in each element in the batch. */
for (j = 0, iovp = iovec; j < batch; j++, iovp++) {
size = iovp->iov_size;
if (size > left)
goto suspend;
skip = 0;
if (size > ATL2_TXD_BUFSIZE - pos) {
skip = ATL2_TXD_BUFSIZE - pos;
r = sys_safecopyfrom(m->DL_PROC,
iovp->iov_grant, 0,
(vir_bytes) (state.txd_base + pos),
skip, D);
if (r != OK)
panic("atl2", "safe copy failed", r);
pos = 0;
}
r = sys_safecopyfrom(m->DL_PROC, iovp->iov_grant, skip,
(vir_bytes) (state.txd_base + pos),
size - skip, D);
if (r != OK)
panic("atl2", "safe copy failed", r);
pos = (pos + size - skip) % ATL2_TXD_BUFSIZE;
left -= size;
count += size;
}
off += batch * sizeof(iovec[0]);
}
assert(count <= ETH_MAX_PACK_SIZE_TAGGED);
/* Write the length to the DWORD right before the packet. */
sizep = state.txd_base +
(state.txd_tail + state.txd_num) % ATL2_TXD_BUFSIZE;
* (u32_t *) sizep = count;
/* Update the TxD head. */
state.txd_num += sizeof(u32_t) + ATL2_ALIGN_32(count);
pos = ATL2_ALIGN_32(pos) % ATL2_TXD_BUFSIZE;
assert(pos == (state.txd_tail + state.txd_num) % ATL2_TXD_BUFSIZE);
/* Initialize and update the TxS head. */
state.txs_base[(state.txs_tail + state.txs_num) % ATL2_TXS_COUNT] = 0;
state.txs_num++;
/* Tell the device about our new position. */
ATL2_WRITE_U32(ATL2_TXD_IDX_REG, pos / sizeof(u32_t));
/* We have now successfully set up the transmission of a packet. */
state.flags &= ~ATL2_FLAG_WRITE_PEND;
state.flags |= ATL2_FLAG_PACK_SENT;
/* If called from the interrupt handler, the caller will reply. */
if (!from_int)
atl2_reply();
return;
suspend:
/* We cannot transmit the packet at this time. If we were not already
* trying to resume transmission, save the write request for later,
* and tell Inet that the request has been suspended.
*/
if (from_int)
return;
state.flags |= ATL2_FLAG_WRITE_PEND;
state.write_msg = *m;
atl2_reply();
}
/*===========================================================================*
* atl2_intr *
*===========================================================================*/
PRIVATE void atl2_intr(message *m)
{
/* Interrupt received.
*/
u32_t val;
int r, try_write, try_read;
/* Clear and disable interrupts. */
val = ATL2_READ_U32(ATL2_ISR_REG);
ATL2_WRITE_U32(ATL2_ISR_REG, val | ATL2_ISR_DISABLE);
ATL2_DEBUG(("ATL2: interrupt (0x%08lx)\n", val));
/* If an error occurred, reset the card. */
if (val & (ATL2_ISR_DMAR_TIMEOUT | ATL2_ISR_DMAW_TIMEOUT |
ATL2_ISR_PHY_LINKDOWN)) {
atl2_setup();
}
try_write = try_read = FALSE;
/* Process sent data, and possibly send pending data. */
if (val & ATL2_ISR_TX_EVENT) {
if (atl2_tx_advance())
try_write = (state.flags & ATL2_FLAG_WRITE_PEND);
}
/* Receive new data, and possible satisfy a pending receive request. */
if (val & ATL2_ISR_RX_EVENT) {
if (!(state.flags & ATL2_FLAG_RX_AVAIL)) {
atl2_rx_advance(FALSE /*next*/);
try_read = (state.flags & ATL2_FLAG_READ_PEND);
}
}
/* Reenable interrupts. */
ATL2_WRITE_U32(ATL2_ISR_REG, 0);
if ((r = sys_irqenable(&state.hook_id)) != OK)
panic("atl2", "unable to enable IRQ", r);
/* Attempt to satisfy pending write and read requests. */
if (try_write)
atl2_writev(&state.write_msg, TRUE /*from_int*/);
if (try_read)
atl2_readv(&state.read_msg, TRUE /*from_int*/);
if (state.flags & (ATL2_FLAG_PACK_SENT | ATL2_FLAG_PACK_RCVD))
atl2_reply();
}
/*===========================================================================*
* atl2_conf *
*===========================================================================*/
PRIVATE void atl2_conf(message *m)
{
/* Configure the mode of the card.
*/
ether_addr_t *addr;
int r;
if (m->DL_PORT != 0) {
m->m3_i1 = ENXIO;
}
else {
state.mode = m->DL_MODE;
atl2_set_mode();
addr = (ether_addr_t *) m->m3_ca1;
addr->ea_addr[0] = state.hwaddr[1] >> 8;
addr->ea_addr[1] = state.hwaddr[1] & 0xff;
addr->ea_addr[2] = state.hwaddr[0] >> 24;
addr->ea_addr[3] = (state.hwaddr[0] >> 16) & 0xff;
addr->ea_addr[4] = (state.hwaddr[0] >> 8) & 0xff;
addr->ea_addr[5] = state.hwaddr[0] & 0xff;
m->m3_i1 = OK;
}
m->m_type = DL_CONF_REPLY;
if ((r = send(m->m_source, m)) != OK)
printf("ATL2: unable to send reply (%d)\n", r);
}
/*===========================================================================*
* atl2_getstat *
*===========================================================================*/
PRIVATE void atl2_getstat(message *m)
{
/* Copy out statistics.
*/
int r;
if (m->DL_PORT != 0) {
r = ENXIO;
}
else {
r = sys_safecopyto(m->DL_PROC, m->DL_GRANT, 0,
(vir_bytes) &state.stat, sizeof(state.stat), D);
}
m->m_type = DL_STAT_REPLY;
/* keep m->DL_PORT */
m->DL_STAT = r;
if ((r = send(m->m_source, m)) != OK)
printf("ATL2: unable to send reply (%d)\n", r);
}
/*===========================================================================*
* atl2_getname *
*===========================================================================*/
PRIVATE void atl2_getname(message *m, int instance)
{
/* Tell Inet the name of this driver.
*/
int r;
/* Each instance must have a unique name. */
m->m_type = DL_NAME_REPLY;
if (instance > 0)
snprintf(m->DL_NAME, sizeof(m->DL_NAME), "atl2:%u",
instance - 1);
else
strcpy(m->DL_NAME, "atl2");
if ((r = send(m->m_source, m)) != OK)
printf("ATL2: unable to send reply (%d)\n", r);
}
/*===========================================================================*
* atl2_shutdown *
*===========================================================================*/
PRIVATE void atl2_shutdown(void)
{
/* Shut down this driver. Stop the device, and deallocate resources
* as proof of concept.
*/
int r;
atl2_stop();
if ((r = sys_irqrmpolicy(&state.hook_id)) != OK)
panic("atl2", "unable to deregister IRQ", r);
munmap(state.txd_base, ATL2_TXD_BUFSIZE);
munmap(state.txs_base, ATL2_TXS_COUNT * sizeof(u32_t));
munmap(state.rxd_base_u,
state.rxd_align + ATL2_RXD_COUNT * ATL2_RXD_SIZE);
vm_unmap_phys(SELF, state.base, ATL2_MMAP_SIZE);
/* We cannot free the PCI device at this time. */
exit(0);
}
/*===========================================================================*
* atl2_dump_link *
*===========================================================================*/
PRIVATE void atl2_dump_link(void)
{
/* Dump link status.
*/
u16_t val;
int link_up;
/* The link status bit is latched. Read the status register twice. */
atl2_read_mdio(ATL2_MII_BMSR, &val);
if (!atl2_read_mdio(ATL2_MII_BMSR, &val)) return;
link_up = val & ATL2_MII_BMSR_LSTATUS;
printf("link status: %4s\t", link_up ? "up" : "down");
if (!link_up) return;
if (!atl2_read_mdio(ATL2_MII_PSSR, &val)) return;
if (!(val & ATL2_MII_PSSR_RESOLVED)) {
printf("(not resolved)\n");
return;
}
switch (val & ATL2_MII_PSSR_SPEED) {
case ATL2_MII_PSSR_10: printf("(10Mbps "); break;
case ATL2_MII_PSSR_100: printf("(100Mbps "); break;
case ATL2_MII_PSSR_1000: printf("(1000Mbps "); break;
default: printf("(unknown, ");
}
printf("%s duplex)", (val & ATL2_MII_PSSR_DUPLEX) ? "full" : "half");
}
/*===========================================================================*
* atl2_dump *
*===========================================================================*/
PRIVATE void atl2_dump(void)
{
/* Dump statistics.
*/
printf("\n");
printf("Attansic L2 statistics:\n");
printf("recvErr: %8ld\t", state.stat.ets_recvErr);
printf("sendErr: %8ld\t", state.stat.ets_sendErr);
printf("OVW: %8ld\n", state.stat.ets_OVW);
printf("CRCerr: %8ld\t", state.stat.ets_CRCerr);
printf("frameAll: %8ld\t", state.stat.ets_frameAll);
printf("missedP: %8ld\n", state.stat.ets_missedP);
printf("packetR: %8ld\t", state.stat.ets_packetR);
printf("packetT: %8ld\t", state.stat.ets_packetT);
printf("transDef: %8ld\n", state.stat.ets_transDef);
printf("collision: %8ld\t", state.stat.ets_collision);
printf("transAb: %8ld\t", state.stat.ets_transAb);
printf("carrSense: %8ld\n", state.stat.ets_carrSense);
printf("fifoUnder: %8ld\t", state.stat.ets_fifoUnder);
printf("fifoOver: %8ld\t", state.stat.ets_fifoOver);
printf("CDheartbeat: %8ld\n", state.stat.ets_CDheartbeat);
printf("OWC: %8ld\t", state.stat.ets_OWC);
printf("TxD tail: %8d\t", state.txd_tail);
printf("TxD count: %8d\n", state.txd_num);
printf("RxD tail: %8d\t", state.rxd_tail);
printf("TxS tail: %8d\t", state.txs_tail);
printf("TxS count: %8d\n", state.txs_num);
printf("flags: 0x%04x\t", state.flags);
atl2_dump_link();
printf("\n");
}
/*===========================================================================*
* sef_cb_init_fresh *
*===========================================================================*/
PRIVATE int sef_cb_init_fresh(int type, sef_init_info_t *info)
{
/* Initialize the atl2 driver.
*/
u32_t inet_endpt;
int r, devind;
#if ATL2_FKEY
int fkeys, sfkeys;
#endif
/* How many matching devices should we skip? */
instance = 0;
env_parse("atl2_instance", "d", 0, &instance, 0, 32);
/* Try to find a recognized device. */
devind = atl2_probe(instance);
if (devind < 0)
panic("atl2", "no matching device found", NO_NUM);
/* Initialize the device. */
atl2_init(devind);
/* Notify Inet of our presence, if it has already been started. */
r = ds_retrieve_label_num("inet", &inet_endpt);
if (r == OK)
notify(inet_endpt);
else if (r != ESRCH)
printf("ATL2: ds_retrieve_label_num failed for 'inet': %d\n",
r);
#if ATL2_FKEY
/* Register debug dump function key. */
fkeys = sfkeys = 0;
bit_set(sfkeys, 11);
if ((r = fkey_map(&fkeys, &sfkeys)) != OK)
printf("ATL2: warning, could not map Shift+F11 key (%d)\n", r);
#endif
return(OK);
}
/*===========================================================================*
* sef_local_startup *
*===========================================================================*/
PRIVATE void sef_local_startup(void)
{
/* Initialize SEF.
*/
/* Register init callbacks. */
sef_setcb_init_fresh(sef_cb_init_fresh);
sef_setcb_init_restart(sef_cb_init_fresh);
/* No support for live update yet. */
/* Let SEF perform startup. */
sef_startup();
}
/*===========================================================================*
* main *
*===========================================================================*/
int main(int argc, char **argv)
{
/* Driver task.
*/
message m;
sigset_t set;
int r;
/* Initialize SEF. */
env_setargs(argc, argv);
sef_local_startup();
while (TRUE) {
if ((r = sef_receive(ANY, &m)) != OK)
panic("atl2", "sef_receive failed", r);
if (is_notify(m.m_type)) {
switch (m.m_source) {
case HARDWARE: /* interrupt */
atl2_intr(&m);
break;
case PM_PROC_NR: /* signal */
if (getsigset(&set) != 0) break;
if (sigismember(&set, SIGTERM))
atl2_shutdown();
break;
case TTY_PROC_NR: /* function key */
atl2_dump();
break;
default:
printf("ATL2: illegal notify from %d\n",
m.m_source);
}
continue;
}
/* Process requests from Inet. */
switch (m.m_type) {
case DL_GETNAME: atl2_getname(&m, instance); break;
case DL_CONF: atl2_conf(&m); break;
case DL_GETSTAT_S: atl2_getstat(&m); break;
case DL_WRITEV_S: atl2_writev(&m, FALSE); break;
case DL_READV_S: atl2_readv(&m, FALSE); break;
default:
printf("ATL2: illegal message %d from %d\n",
m.m_type, m.m_source);
}
}
}