blob: 1b3e8770a444d8e7e59cc1637abf95d2f2a03931 [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 bxe */
#include <vfs.h>
#include <kfs.h>
#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 <ip.h>
#include <ns.h>
#include "bxe.h"
/* We're required to print out stats at some point. Here are a couple from
* igbe, as an example. */
spinlock_t bxe_adapter_tq_lock = SPINLOCK_INITIALIZER;
TAILQ_HEAD(bxe_adapter_tq, bxe_adapter);
struct bxe_adapter_tq bxe_adapter_tq = TAILQ_HEAD_INITIALIZER(bxe_adapter_tq);
static char *statistics[Nstatistics] = {
"CRC Error",
"Alignment Error",
};
static long bxeifstat(struct ether *edev, void *a, long n, uint32_t offset)
{
struct bxe_adapter *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);
}
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 bxectl(struct ether *edev, void *buf, long n)
{
ERRSTACK(1);
int v;
char *p;
struct bxe_adapter *ctlr;
struct cmdbuf *cb;
struct cmdtab *ct;
if ((ctlr = edev->ctlr) == NULL)
error(Enonexist);
cb = parsecmd(buf, n);
if (waserror()) {
kfree(cb);
nexterror();
}
/* TODO: handle ctl command somehow. igbe did the following: */
//ct = lookupcmd(cb, igbectlmsg, ARRAY_SIZE(igbectlmsg));
kfree(cb);
poperror();
return n;
}
static void bxepromiscuous(void *arg, int on)
{
int rctl;
struct bxe_adapter *ctlr;
struct ether *edev;
edev = arg;
ctlr = edev->ctlr;
/* TODO: set promisc on/off */
}
static void bxemulticast(void *arg, uint8_t * addr, int add)
{
int bit, x;
struct bxe_adapter *ctlr;
struct ether *edev;
edev = arg;
ctlr = edev->ctlr;
/* TODO: add or remove a multicast addr */
}
/* Transmit initialization. Not mandatory for 9ns, but a good idea */
static void bxetxinit(struct bxe_adapter *ctlr)
{
}
static void bxetransmit(struct ether *edev)
{
struct block *bp;
struct bxe_adapter *ctlr;
ctlr = edev->ctlr;
/* Don't forget to spin_lock_irqsave */
/* TODO: Free any completed packets */
/* Try to fill the ring back up. While there is space, yank from the output
* queue (oq) and put them in the Tx desc. */
while (1) {
//while (NEXT_RING(tdt, ctlr->ntd) != tdh) {
if ((bp = qget(edev->oq)) == NULL)
break;
//td = &ctlr->tdba[tdt];
//td->addr[0] = paddr_low32(bp->rp);
//td->addr[1] = paddr_high32(bp->rp);
/* if we're breaking out, make sure to set the IRQ mask */
//if (NEXT_RING(tdt, ctlr->ntd) == tdh) {
// // other stuff removed
// csr32w(ctlr, Tdt, tdt);
// igbeim(ctlr, Txdw);
// break;
//}
}
}
/* Not mandatory. Called to make sure there are free blocks available for
* incoming packets */
static void bxereplenish(struct bxe_adapter *ctlr)
{
struct block *bp;
while (1) {
//while (NEXT_RING(rdt, ctlr->nrd) != ctlr->rdh) {
//if we want a new block
{
bp = iallocb(64); // TODO: use your block size, e.g. Rbsz
if (bp == NULL) {
/* needs to be a safe print for interrupt level */
printk("#l%d bxereplenish: 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 bxerxinit(struct bxe_adapter *ctlr)
{
bxereplenish(ctlr);
}
static int bxerim(void* ctlr)
{
//return ((struct bxe_adapter*)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 bxerproc(void *arg)
{
struct block *bp;
struct bxe_adapter *ctlr;
struct ether *edev;
edev = arg;
ctlr = edev->ctlr;
bxerxinit(ctlr);
/* TODO: one time RX init */
for (;;) {
/* TODO: set up, once per sleep. make sure we'll wake up */
rendez_sleep(&ctlr->rrendez, bxerim, 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
bxereplenish(ctlr);
}
}
static void bxeattach(struct ether *edev)
{
ERRSTACK(1);
struct block *bp;
struct bxe_adapter *ctlr;
char *name;
ctlr = edev->ctlr;
ctlr->edev = edev; /* point back to Ether* */
/* not sure if we'll need/want any of the 9ns stuff */
return;
qlock(&ctlr->alock);
/* TODO: make sure we haven't attached already. If so, just return */
/* Alloc all your ctrl crap. */
/* the ktasks should free these names, if they ever exit */
name = kmalloc(KNAMELEN, KMALLOC_WAIT);
snprintf(name, KNAMELEN, "#l%d-bxerproc", edev->ctlrno);
ktask(name, bxerproc, edev);
bxetxinit(ctlr);
qunlock(&ctlr->alock);
}
/* Hard IRQ */
static void bxeinterrupt(struct hw_trapframe *hw_tf, void *arg)
{
struct bxe_adapter *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) */
bxetransmit(edev);
}
static void bxeshutdown(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 bxereset(struct bxe_adapter *ctlr)
{
int ctrl, i, pause, r, swdpio, txcw;
/* despite the name, we attach at reset time. BXE attach has a lot of
* mmio mappings that have to happen at boot (in akaros), instead of during
* devether's attach (at runtime) */
extern int bxe_attach(struct bxe_adapter *sc);
bxe_attach(ctlr);
/* normally done during BSD's ifconfig */
extern void bxe_init(void *xsc);
bxe_init(ctlr);
// if (igbedetach(ctlr))
// return -1;
return 0;
}
static void bxepci(void)
{
int cls, id;
struct pci_device *pcidev;
struct bxe_adapter *ctlr;
STAILQ_FOREACH(pcidev, &pci_devices, all_dev) {
/* This checks that pcidev is a Network Controller for Ethernet */
if (pcidev->class != 0x02 || pcidev->subclass != 0x00)
continue;
id = pcidev->dev_id << 16 | pcidev->ven_id;
extern int bxe_probe(struct pci_device *dev);
if (bxe_probe(pcidev))
continue;
printk("bxe 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 bxe_attach */
cls = pcidev_read8(pcidev, PCI_CLSZ_REG);
switch (cls) {
default:
printd("bxe: unexpected CLS - %d\n", cls * 4);
break;
case 0x00:
case 0xFF:
/* bogus value; use a sane default. cls is set in DWORD (u32)
* units. */
cls = ARCH_CL_SIZE / sizeof(long);
pcidev_write8(pcidev, PCI_CLSZ_REG, cls);
break;
case 0x08:
case 0x10:
break;
}
ctlr = kzmalloc(sizeof(struct bxe_adapter), 0);
if (ctlr == NULL)
error(Enomem);
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;
spin_lock(&bxe_adapter_tq_lock);
TAILQ_INSERT_TAIL(&bxe_adapter_tq, ctlr, link9ns);
spin_unlock(&bxe_adapter_tq_lock);
}
}
/* Called by devether's probe routines. Return -1 if the edev does not match
* any of your ctlrs. */
static int bxepnp(struct ether *edev)
{
struct bxe_adapter *ctlr;
run_once(qlock_init(&bxe_prev_mtx));
/* Allocs ctlrs for all PCI devices matching our IDs, does various PCI and
* MMIO/port setup */
run_once(bxepci());
spin_lock(&bxe_adapter_tq_lock);
TAILQ_FOREACH(ctlr, &bxe_adapter_tq, link9ns) {
/* just take the first inactive ctlr on the list */
if (ctlr->active)
continue;
ctlr->active = 1;
break;
}
spin_unlock(&bxe_adapter_tq_lock);
if (ctlr == NULL)
return -1;
edev->ctlr = ctlr;
ctlr->edev = edev;
ctlr->ifp = &edev->netif;
//edev->port = ctlr->port; /* might just remove this from devether */
edev->irq = ctlr->pcidev->irqline;
edev->tbdf = MKBUS(BusPCI, ctlr->pcidev->bus, ctlr->pcidev->dev,
ctlr->pcidev->func);
edev->netif.mbps = 1000;
memmove(edev->ea, ctlr->link_params.mac_addr, Eaddrlen);
/*
* Linkage to the generic ethernet driver.
*/
edev->attach = bxeattach;
edev->transmit = bxetransmit;
edev->ifstat = bxeifstat;
edev->ctl = bxectl;
edev->shutdown = bxeshutdown;
edev->netif.arg = edev;
edev->netif.promiscuous = bxepromiscuous;
edev->netif.multicast = bxemulticast;
bxereset(ctlr);
return 0;
}
linker_func_3(etherbxelink)
{
addethercard("bxe", bxepnp);
}