blob: b31691f06e292adb15eb3040df41b8c4c567facc [file] [log] [blame]
/* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file. */
/* Network driver stub for bnx2x_ */
#include <slab.h>
#include <kmalloc.h>
#include <kref.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <cpio.h>
#include <pmap.h>
#include <smp.h>
#include <arch/pci.h>
#include <net/ip.h>
#include <ns.h>
#include "bnx2x.h"
/* TODO: Cheap externs */
extern int __init bnx2x_init(void);
extern bool is_bnx2x_dev(struct pci_device *dev);
extern const struct pci_device_id *
srch_bnx2x_pci_tbl(struct pci_device *needle);
extern int bnx2x_init_one(struct ether *dev, struct bnx2x *bp,
struct pci_device *pdev,
const struct pci_device_id *ent);
extern int bnx2x_open(struct ether *dev);
extern void bnx2x_set_rx_mode(struct ether *dev);
extern netdev_tx_t bnx2x_start_xmit(struct block *block,
struct bnx2x_fp_txdata *txdata);
spinlock_t bnx2x_tq_lock = SPINLOCK_INITIALIZER;
TAILQ_HEAD(bnx2x_tq, bnx2x);
struct bnx2x_tq bnx2x_tq = TAILQ_HEAD_INITIALIZER(bnx2x_tq);
/* We're required to print out stats at some point. Here are a couple from
* igbe, as an example. */
static char *statistics[Nstatistics] = {
"CRC Error",
"Alignment Error",
};
static long bnx2x_ifstat(struct ether *edev, void *a, long n, uint32_t offset)
{
struct bnx2x *ctlr;
char *p, *s;
int i, l, r;
uint64_t tuvl, ruvl;
ctlr = edev->ctlr;
qlock(&ctlr->slock);
p = kzmalloc(READSTR, 0);
if (p == NULL) {
qunlock(&ctlr->slock);
error(ENOMEM, ERROR_FIXME);
}
l = 0;
for (i = 0; i < Nstatistics; i++) {
/* somehow read the device's HW stats */
//r = csr32r(ctlr, Statistics + i * 4);
r = 3; /* TODO: this is the value for the statistic */
if ((s = statistics[i]) == NULL)
continue;
/* based on the stat, spit out a string */
switch (i) {
default:
ctlr->statistics[i] += r;
if (ctlr->statistics[i] == 0)
continue;
l += snprintf(p + l, READSTR - l, "%s: %ud %ud\n", s,
ctlr->statistics[i], r);
break;
}
}
/* TODO: then print out the software-only (ctlr) stats */
// l += snprintf(p + l, READSTR - l, "lintr: %ud %ud\n",
// ctlr->lintr, ctlr->lsleep);
n = readstr(offset, a, n, p);
kfree(p);
qunlock(&ctlr->slock);
return n;
}
static long bnx2x_ctl(struct ether *edev, void *buf, long n)
{
ERRSTACK(1);
int v;
char *p;
struct bnx2x *ctlr;
struct cmdbuf *cb;
struct cmdtab *ct;
if ((ctlr = edev->ctlr) == NULL)
error(ENODEV, ERROR_FIXME);
cb = parsecmd(buf, n);
if (waserror()) {
kfree(cb);
nexterror();
}
if (cb->nf < 1)
error(EFAIL, "short control request");
/* TODO: handle ctl command somehow. igbe did the following: */
//ct = lookupcmd(cb, igbectlmsg, ARRAY_SIZE(igbectlmsg));
kfree(cb);
poperror();
return n;
}
/* The poke function: we are guaranteed that only one copy of this func runs
* per poke tracker (per queue). Both transmit and tx_int will poke, and after
* any pokes, the func will run at least once.
*
* Some notes for optimizing and synchronization:
*
* If we want a flag or something to keep us from checking the oq and attempting
* the xmit, all that will do is speed up xmit when the tx rings are full.
* You'd need to be careful. The post/poke makes sure that this'll run after
* work was posted, but if this function sets an abort flag and later checks it,
* you need to check tx_avail *after* setting the flag (check, signal, check
* again). Consider this:
*
* this func:
* calls start_xmit, fails with BUSY. wants to set ABORT flag
*
* PAUSE - meanwhile:
*
* tx_int clears the ABORT flag, then pokes:
* drain so there is room;
* clear flag (combo of these is "post work");
* poke;. guaranteed that poke will happen after we cleared flag.
* but it is concurrent with this function
*
* RESUME this func:
*
* sets ABORT flag
* returns.
* tx_int's poke ensures we run again
* we run again and see ABORT, then return
* never try again til the next tx_int, if ever
*
* Instead, in this func, we must set ABORT flag, then check tx_avail. Or
* have two flags, one set by us, another set by tx_int, where this func only
* clears the tx_int flag when it will attempt a start_xmit.
*
* It's probably easier to just check tx_avail before entering the while loop,
* if you're really concerned. If you want to do the flag thing, probably use
* two flags (atomically), and be careful. */
void __bnx2x_tx_queue(void *txdata_arg)
{
struct bnx2x_fp_txdata *txdata = txdata_arg;
struct block *block;
struct queue *oq = txdata->oq;
/* TODO: avoid bugs til multi-queue is working */
assert(oq);
assert(txdata->txq_index == 0);
while ((block = qget(oq))) {
if ((bnx2x_start_xmit(block, txdata) != NETDEV_TX_OK)) {
/* all queue readers are sync'd by the poke, so we can
* putback without fear of going out of order. */
/* TODO: q code has methods that should be called with
* the spinlock held, but no methods to do the
* locking... */
//spin_unlock_irqsave(&oq->lock);
qputback(oq, block);
//spin_lock_irqsave(&oq->lock);
/* device can't handle any more, we're done for now.
* tx_int will poke when space frees up. it may be
* poking concurrently, and in which case, we'll run
* again immediately. */
break;
}
}
}
static void bnx2x_transmit(struct ether *edev)
{
struct bnx2x *ctlr = edev->ctlr;
struct bnx2x_fp_txdata *txdata;
/* TODO: determine the tx queue we're supposed to work on */
int txq_index = 0;
txdata = &ctlr->bnx2x_txq[txq_index];
poke(&txdata->poker, txdata);
}
/* Not mandatory. Called to make sure there are free blocks available for
* incoming packets */
static void bnx2x_replenish(struct bnx2x *ctlr)
{
struct block *bp;
while (1) {
//while (NEXT_RING(rdt, ctlr->nrd) != ctlr->rdh) {
//if we want a new block
{
// TODO: use your block size, e.g. Rbsz
bp = block_alloc(64, MEM_ATOMIC);
if (bp == NULL) {
/* needs to be a safe print for interrupt level
* */
printk("#l%d bnx2x_replenish: no available buffers\n",
ctlr->edev->ctlrno);
break;
}
//ctlr->rb[rdt] = bp;
//rd->addr[0] = paddr_low32(bp->rp);
//rd->addr[1] = paddr_high32(bp->rp);
}
wmb(); /* ensure prev rd writes come before status = 0. */
//rd->status = 0;
}
}
/* Not mandatory. Device init. */
static void bnx2x_rxinit(struct bnx2x *ctlr)
{
bnx2x_replenish(ctlr);
}
static int bnx2x_rim(void* ctlr)
{
//return ((struct bnx2x*)ctlr)->rim != 0;
return 1;
}
/* Do we want a receive proc? It is similar to softirq. Or we can do the work
* in hard IRQ ctx. */
static void bnx2x_rproc(void *arg)
{
struct block *bp;
struct bnx2x *ctlr;
struct ether *edev;
edev = arg;
ctlr = edev->ctlr;
bnx2x_rxinit(ctlr);
/* TODO: one time RX init */
for (;;) {
/* TODO: set up, once per sleep. make sure we'll wake up */
rendez_sleep(&ctlr->rrendez, bnx2x_rim, ctlr);
for (;;) {
/* if we can get a block, here's how to ram it up the
* stack */
if (1) {
bp = (void*)0xdeadbeef;
//bp = ctlr->rb[rdh];
//bp->wp += rd->length;
//bp->next = NULL;
/* conditionally, set block flags */
//bp->flag |= Bipck; /* IP checksum done in HW*/
//bp->flag |= Btcpck | Budpck;
//bp->checksum = rd->checksum;
//bp->flag |= Bpktck; /* Packet checksum? */
etheriq(edev, bp, 1);
} else {
//freeb(ctlr->rb[rdh]);
}
}
// optionally
bnx2x_replenish(ctlr);
}
}
static void bnx2x_attach(struct ether *edev)
{
ERRSTACK(1);
struct block *bp;
struct bnx2x *ctlr;
char *name;
ctlr = edev->ctlr;
ctlr->edev = edev; /* point back to Ether* */
qlock(&ctlr->alock);
if (ctlr->attached) {
qunlock(&ctlr->alock);
return;
}
bnx2x_open(ctlr->edev);
bnx2x_set_rx_mode(edev);
ctlr->attached = TRUE;
qunlock(&ctlr->alock);
/* not sure if we'll need/want any of the other 9ns stuff */
return;
/* Alloc all your ctrl crap. */
/* the ktasks should free these names, if they ever exit */
name = kmalloc(KNAMELEN, MEM_WAIT);
snprintf(name, KNAMELEN, "#l%d-bnx2x_rproc", edev->ctlrno);
ktask(name, bnx2x_rproc, edev);
qunlock(&ctlr->alock);
}
/* Hard IRQ */
static void bnx2x_interrupt(struct hw_trapframe *hw_tf, void *arg)
{
struct bnx2x *ctlr;
struct ether *edev;
int icr, im, txdw;
edev = arg;
ctlr = edev->ctlr;
/* At some point, wake up the rproc */
rendez_wakeup(&ctlr->rrendez);
/* optionally, might need to transmit (not sure if this is a good idea
* in hard irq or not) */
bnx2x_transmit(edev);
}
static void bnx2x_shutdown(struct ether *ether)
{
/*
* Perform a device reset to get the chip back to the
* power-on state, followed by an EEPROM reset to read
* the defaults for some internal registers.
*/
/* igbe did: */
//igbedetach(ether->ctlr);
}
/* "reset", getting it back to the basic power-on state. 9ns drivers call this
* during the initial setup (from the PCI func) */
static int bnx2x_reset(struct bnx2x *ctlr)
{
int ctrl, i, pause, r, swdpio, txcw;
bnx2x_init_one(ctlr->edev, ctlr, ctlr->pcidev, ctlr->pci_id);
return 0;
}
static void bnx2x_pci(void)
{
int id;
struct pci_device *pcidev;
struct bnx2x *ctlr;
const struct pci_device_id *pci_id;
STAILQ_FOREACH(pcidev, &pci_devices, all_dev) {
if (pcidev->class != 0x02 || pcidev->subclass != 0x00)
continue;
id = pcidev->dev_id << 16 | pcidev->ven_id;
pci_id = srch_bnx2x_pci_tbl(pcidev);
if (!pci_id)
continue;
/* only run bnx2x's __init method once we know we have one */
run_once(bnx2x_init());
printk("bnx2x driver found 0x%04x:%04x at %02x:%02x.%x\n",
pcidev->ven_id, pcidev->dev_id,
pcidev->bus, pcidev->dev, pcidev->func);
/* MMIO, pci_bus_master, etc, are all done in bnx2x_attach */
pci_set_cacheline_size(pcidev);
ctlr = kzmalloc(sizeof(struct bnx2x), 0);
if (ctlr == NULL)
error(ENOMEM, ERROR_FIXME);
spinlock_init_irqsave(&ctlr->imlock);
spinlock_init_irqsave(&ctlr->tlock);
qlock_init(&ctlr->alock);
qlock_init(&ctlr->slock);
rendez_init(&ctlr->rrendez);
ctlr->pcidev = pcidev;
ctlr->pci_id = pci_id;
spin_lock(&bnx2x_tq_lock);
TAILQ_INSERT_TAIL(&bnx2x_tq, ctlr, link9ns);
spin_unlock(&bnx2x_tq_lock);
}
}
/* Called by devether's probe routines. Return -1 if the edev does not match
* any of your ctlrs. */
static int bnx2x_pnp(struct ether *edev)
{
struct bnx2x *ctlr;
/* Allocs ctlrs for all PCI devices matching our IDs, does various PCI
* and MMIO/port setup */
run_once(bnx2x_pci());
spin_lock(&bnx2x_tq_lock);
TAILQ_FOREACH(ctlr, &bnx2x_tq, link9ns) {
/* just take the first inactive ctlr on the list */
if (ctlr->active)
continue;
ctlr->active = 1;
break;
}
spin_unlock(&bnx2x_tq_lock);
if (ctlr == NULL)
return -1;
edev->ctlr = ctlr;
ctlr->edev = edev;
strlcpy(edev->drv_name, "bnx2x", KNAMELEN);
//edev->port = ctlr->port; /* might remove this from devether */
edev->irq = ctlr->pcidev->irqline;
edev->tbdf = MKBUS(BusPCI, ctlr->pcidev->bus, ctlr->pcidev->dev,
ctlr->pcidev->func);
edev->mbps = 1000;
memmove(edev->ea, ctlr->link_params.mac_addr, Eaddrlen);
/*
* Linkage to the generic ethernet driver.
*/
edev->attach = bnx2x_attach;
edev->transmit = bnx2x_transmit;
edev->ifstat = bnx2x_ifstat;
edev->ctl = bnx2x_ctl;
edev->shutdown = bnx2x_shutdown;
edev->arg = edev;
edev->promiscuous = NULL;
edev->multicast = NULL;
bnx2x_reset(ctlr);
return 0;
}
linker_func_3(etherbnx2x_link)
{
addethercard("bnx2x", bnx2x_pnp);
}