blob: 20ecac26e5662ea821d205c619d079100b5f839f [file] [log] [blame]
/* Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
* Portions Copyright © 1997-1999 Vita Nuova Limited
* Portions Copyright © 2000-2007 Vita Nuova Holdings Limited
* (www.vitanuova.com)
* Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
*
* Modified for the Akaros operating system:
* Copyright (c) 2013-2014 The Regents of the University of California
* Copyright (c) 2013-2015 Google Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. */
#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 <net/ip.h>
static int netown(struct netfile *, char *unused_char_p_t, int);
static int openfile(struct ether *, int);
static char *matchtoken(char *unused_char_p_t, char *);
static char *netmulti(struct ether *, struct netfile *,
uint8_t *unused_uint8_p_t, int);
static int parseaddr(uint8_t *unused_uint8_p_t, char *unused_char_p_t, int);
/*
* set up a new network interface
*/
void netifinit(struct ether *nif, char *name, int nfile, uint32_t limit)
{
qlock_init(&nif->qlock);
strlcpy(nif->name, name, KNAMELEN);
nif->nfile = nfile;
nif->f = kzmalloc(nfile * sizeof(struct netfile *), 0);
if (nif->f)
memset(nif->f, 0, nfile * sizeof(struct netfile *));
else
nif->nfile = 0;
nif->limit = limit;
}
/*
* generate a 3 level directory
*/
static int netifgen(struct chan *c, char *unused_char_p_t, struct dirtab *vp,
int unused_int, int i, struct dir *dp)
{
struct qid q;
struct ether *nif = (struct ether *)vp;
struct netfile *f;
int perm;
char *o;
q.type = QTFILE;
q.vers = 0;
/* top level directory contains the name of the network */
if (c->qid.path == 0) {
switch (i) {
case DEVDOTDOT:
q.path = 0;
q.type = QTDIR;
devdir(c, q, ".", 0, eve.name, 0555, dp);
break;
case 0:
q.path = N2ndqid;
q.type = QTDIR;
strlcpy(get_cur_genbuf(), nif->name, GENBUF_SZ);
devdir(c, q, get_cur_genbuf(), 0, eve.name, 0555, dp);
break;
default:
return -1;
}
return 1;
}
/* second level contains clone plus all the conversations.
*
* This ancient comment is from plan9. Inferno and nxm both had issues
* here. You couldn't ls /net/ether0/ when it didn't have any convs.
* There were also issues with nxm where you couldn't stat
* ether0/x/stats properly.
*
* The issue is that if we handle things like Nstatqid, then we will
* never pass it down to the third level. And since we just set the path
* == Nstatqid, we won't have the NETID muxed in. If someone isn't
* trying to generate a chan, but instead is looking it up (devwalk
* generates, devstat already has the chan), then they are also looking
* for a devdir with path containing ID << 5. So if you stat
* ether0/1/ifstats, devstat is looking for path 41, but we return path
* 9 (41 = 32 + 9). (these numbers are before we tracked NETID + 1).
*
* We (akaros and plan9) had a big if here, that would catch things that
* do not exist in the subdirs of a netif. Things like clone make sense
* here. I guess addr too, though that seems to be added since the
* original comment. You can see what the 3rd level was expecting to
* parse by looking farther down in the code.
*
* The root of the problem was that the old code couldn't tell the
* difference between no netid and netid 0. Now, we determine if we're
* at the second level by the lack of a netid, instead of trying to
* enumerate the qid types that the second level could have. The latter
* approach allowed for something like ether0/1/stats, but we couldn't
* actually devstat ether0/stats directly. It's worth noting that there
* is no difference to the content of ether0/stats and ether0/x/stats
* (when you read), but they have different chan qids.
*
* Here's the old if block:
t = NETTYPE(c->qid.path);
if (t == N2ndqid || t == Ncloneqid || t == Naddrqid) {
*/
if (NETID(c->qid.path) == -1) {
switch (i) {
case DEVDOTDOT:
q.type = QTDIR;
q.path = 0;
devdir(c, q, ".", 0, eve.name, DMDIR | 0555, dp);
break;
case 0:
q.path = Ncloneqid;
devdir(c, q, "clone", 0, eve.name, 0666, dp);
break;
case 1:
q.path = Naddrqid;
devdir(c, q, "addr", 0, eve.name, 0666, dp);
break;
case 2:
q.path = Nstatqid;
devdir(c, q, "stats", 0, eve.name, 0444, dp);
break;
case 3:
q.path = Nifstatqid;
devdir(c, q, "ifstats", 0, eve.name, 0444, dp);
break;
default:
i -= 4;
if (i >= nif->nfile)
return -1;
if (nif->f[i] == 0)
return 0;
q.type = QTDIR;
q.path = NETQID(i, N3rdqid);
snprintf(get_cur_genbuf(), GENBUF_SZ, "%d", i);
devdir(c, q, get_cur_genbuf(), 0, eve.name,
DMDIR | 0555, dp);
break;
}
return 1;
}
/* third level */
f = nif->f[NETID(c->qid.path)];
if (f == 0)
return 0;
if (*f->owner) {
o = f->owner;
perm = f->mode;
} else {
o = eve.name;
perm = 0666;
}
switch (i) {
case DEVDOTDOT:
q.type = QTDIR;
q.path = N2ndqid;
strlcpy(get_cur_genbuf(), nif->name, GENBUF_SZ);
devdir(c, q, get_cur_genbuf(), 0, eve.name, DMDIR | 0555, dp);
break;
case 0:
q.path = NETQID(NETID(c->qid.path), Ndataqid);
devdir(c, q, "data", 0, o, perm, dp);
break;
case 1:
q.path = NETQID(NETID(c->qid.path), Nctlqid);
devdir(c, q, "ctl", 0, o, perm, dp);
break;
case 2:
q.path = NETQID(NETID(c->qid.path), Nstatqid);
devdir(c, q, "stats", 0, eve.name, 0444, dp);
break;
case 3:
q.path = NETQID(NETID(c->qid.path), Ntypeqid);
devdir(c, q, "type", 0, eve.name, 0444, dp);
break;
case 4:
q.path = NETQID(NETID(c->qid.path), Nifstatqid);
devdir(c, q, "ifstats", 0, eve.name, 0444, dp);
break;
default:
return -1;
}
return 1;
}
struct walkqid *netifwalk(struct ether *nif, struct chan *c, struct chan *nc,
char **name, int nname)
{
return devwalk(c, nc, name, nname, (struct dirtab *)nif, 0, netifgen);
}
struct chan *netifopen(struct ether *nif, struct chan *c, int omode)
{
int id;
struct netfile *f;
id = 0;
if (c->qid.type & QTDIR) {
if (omode & O_WRITE)
error(EPERM, ERROR_FIXME);
} else {
switch (NETTYPE(c->qid.path)) {
case Ndataqid:
case Nctlqid:
id = NETID(c->qid.path);
openfile(nif, id);
break;
case Ncloneqid:
id = openfile(nif, -1);
c->qid.path = NETQID(id, Nctlqid);
break;
default:
if (omode & O_WRITE)
error(EINVAL, ERROR_FIXME);
}
switch (NETTYPE(c->qid.path)) {
case Ndataqid:
case Nctlqid:
f = nif->f[id];
if (netown(f, current->user.name, omode & 7) < 0)
error(EPERM, ERROR_FIXME);
break;
}
}
c->mode = openmode(omode);
c->flag |= COPEN;
c->offset = 0;
c->iounit = qiomaxatomic;
return c;
}
/* Helper for building the features for netifread */
static int feature_appender(int features, char *p, int sofar)
{
if (features & NETF_IPCK)
sofar += snprintf(p + sofar, READSTR - sofar, "ipck ");
if (features & NETF_UDPCK)
sofar += snprintf(p + sofar, READSTR - sofar, "udpck ");
if (features & NETF_TCPCK)
sofar += snprintf(p + sofar, READSTR - sofar, "tcpck ");
if (features & NETF_PADMIN)
sofar += snprintf(p + sofar, READSTR - sofar, "padmin ");
if (features & NETF_SG)
sofar += snprintf(p + sofar, READSTR - sofar, "sg ");
if (features & NETF_TSO)
sofar += snprintf(p + sofar, READSTR - sofar, "tso ");
if (features & NETF_LRO)
sofar += snprintf(p + sofar, READSTR - sofar, "lro ");
if (features & NETF_RXCSUM)
sofar += snprintf(p + sofar, READSTR - sofar, "rxcsum ");
return sofar;
}
long netifread(struct ether *nif, struct chan *c, void *a, long n,
uint32_t offset)
{
int i, j;
struct netfile *f;
char *p;
if (c->qid.type & QTDIR)
return devdirread(c, a, n, (struct dirtab *)nif, 0, netifgen);
switch (NETTYPE(c->qid.path)) {
case Ndataqid:
f = nif->f[NETID(c->qid.path)];
return qread(f->in, a, n);
case Nctlqid:
return readnum(offset, a, n, NETID(c->qid.path), NUMSIZE);
case Nstatqid:
p = kzmalloc(READSTR, 0);
if (p == NULL)
return 0;
j = 0;
j += snprintf(p + j, READSTR - j, "driver: %s\n",
nif->drv_name);
j += snprintf(p + j, READSTR - j, "in: %d\n", nif->inpackets);
j += snprintf(p + j, READSTR - j, "link: %d\n", nif->link);
j += snprintf(p + j, READSTR - j, "out: %d\n", nif->outpackets);
j += snprintf(p + j, READSTR - j, "crc errs: %d\n", nif->crcs);
j += snprintf(p + j, READSTR - j, "overflows: %d\n",
nif->overflows);
j += snprintf(p + j, READSTR - j, "soft overflows: %d\n",
nif->soverflows);
j += snprintf(p + j, READSTR - j, "framing errs: %d\n",
nif->frames);
j += snprintf(p + j, READSTR - j, "buffer errs: %d\n",
nif->buffs);
j += snprintf(p + j, READSTR - j, "output errs: %d\n",
nif->oerrs);
j += snprintf(p + j, READSTR - j, "prom: %d\n", nif->prom);
j += snprintf(p + j, READSTR - j, "mbps: %d\n", nif->mbps);
j += snprintf(p + j, READSTR - j, "addr: ");
for (i = 0; i < nif->alen; i++)
j += snprintf(p + j, READSTR - j, "%02.2x",
nif->addr[i]);
j += snprintf(p + j, READSTR - j, "\n");
j += snprintf(p + j, READSTR - j, "feat: ");
j = feature_appender(nif->feat, p, j);
j += snprintf(p + j, READSTR - j, "\n");
j += snprintf(p + j, READSTR - j, "hw_features: ");
j = feature_appender(nif->hw_features, p, j);
j += snprintf(p + j, READSTR - j, "\n");
n = readstr(offset, a, n, p);
kfree(p);
return n;
case Naddrqid:
p = kzmalloc(READSTR, 0);
if (p == NULL)
return 0;
j = 0;
for (i = 0; i < nif->alen; i++)
j += snprintf(p + j, READSTR - j, "%02.2x",
nif->addr[i]);
n = readstr(offset, a, n, p);
kfree(p);
return n;
case Ntypeqid:
f = nif->f[NETID(c->qid.path)];
return readnum(offset, a, n, f->type, NUMSIZE);
case Nifstatqid:
return 0;
}
error(EINVAL, ERROR_FIXME);
return -1; /* not reached */
}
struct block *netifbread(struct ether *nif, struct chan *c, long n,
uint32_t offset)
{
if ((c->qid.type & QTDIR) || NETTYPE(c->qid.path) != Ndataqid)
return devbread(c, n, offset);
return qbread(nif->f[NETID(c->qid.path)]->in, n);
}
/*
* make sure this type isn't already in use on this device
*/
static int typeinuse(struct ether *nif, int type)
{
struct netfile *f, **fp, **efp;
if (type <= 0)
return 0;
efp = &nif->f[nif->nfile];
for (fp = nif->f; fp < efp; fp++) {
f = *fp;
if (f == 0)
continue;
if (f->type == type)
return 1;
}
return 0;
}
/*
* the devxxx.c that calls us handles writing data, it knows best
*/
long netifwrite(struct ether *nif, struct chan *c, void *a, long n)
{
ERRSTACK(1);
struct netfile *f;
int type;
char *p, buf[64];
uint8_t binaddr[Nmaxaddr];
if (NETTYPE(c->qid.path) != Nctlqid)
error(EPERM, ERROR_FIXME);
if (n >= sizeof(buf))
n = sizeof(buf) - 1;
memmove(buf, a, n);
buf[n] = 0;
qlock(&nif->qlock);
if (waserror()) {
qunlock(&nif->qlock);
nexterror();
}
f = nif->f[NETID(c->qid.path)];
if ((p = matchtoken(buf, "connect")) != 0) {
/* We'd like to not use the NIC until it has come up fully -
* auto-negotiation is done and packets will get sent out. This
* is about the best place to do it. */
netif_wait_for_carrier(nif);
type = strtol(p, 0, 0);
if (typeinuse(nif, type))
error(EBUSY, ERROR_FIXME);
f->type = type;
if (f->type < 0)
nif->all++;
} else if (matchtoken(buf, "promiscuous")) {
if (f->prom == 0) {
/* Note that promisc has two meanings: put the NIC into
* promisc mode, and record our outbound traffic. See
* etheroq(). */
/* TODO: consider porting linux's interface for
* set_rx_mode. */
if (nif->prom == 0 && nif->promiscuous != NULL)
nif->promiscuous(nif->arg, 1);
f->prom = 1;
nif->prom++;
}
} else if ((p = matchtoken(buf, "scanbs")) != 0) {
/* scan for base stations */
if (f->scan == 0) {
type = strtol(p, 0, 0);
if (type < 5)
type = 5;
if (nif->scanbs != NULL)
nif->scanbs(nif->arg, type);
f->scan = type;
nif->scan++;
}
} else if (matchtoken(buf, "bridge")) {
f->bridge = 1;
} else if (matchtoken(buf, "headersonly")) {
f->headersonly = 1;
} else if ((p = matchtoken(buf, "addmulti")) != 0) {
if (parseaddr(binaddr, p, nif->alen) < 0)
error(EFAIL, "bad address");
p = netmulti(nif, f, binaddr, 1);
if (p)
error(EFAIL, p);
} else if ((p = matchtoken(buf, "remmulti")) != 0) {
if (parseaddr(binaddr, p, nif->alen) < 0)
error(EFAIL, "bad address");
p = netmulti(nif, f, binaddr, 0);
if (p)
error(EFAIL, p);
} else if (matchtoken(buf, "oneblock")) {
/* Qmsg + Qcoal = one block at a time. */
q_toggle_qmsg(f->in, TRUE);
q_toggle_qcoalesce(f->in, TRUE);
} else
n = -1;
qunlock(&nif->qlock);
poperror();
return n;
}
int netifwstat(struct ether *nif, struct chan *c, uint8_t * db, int n)
{
struct dir *dir;
struct netfile *f;
int m;
f = nif->f[NETID(c->qid.path)];
if (f == 0)
error(ENOENT, ERROR_FIXME);
if (netown(f, current->user.name, O_WRITE) < 0)
error(EPERM, ERROR_FIXME);
dir = kzmalloc(sizeof(struct dir) + n, 0);
m = convM2D(db, n, &dir[0], (char *)&dir[1]);
if (m == 0) {
kfree(dir);
error(ENODATA, ERROR_FIXME);
}
if (!emptystr(dir[0].uid))
strlcpy(f->owner, dir[0].uid, KNAMELEN);
if (dir[0].mode != -1)
f->mode = dir[0].mode;
kfree(dir);
return m;
}
int netifstat(struct ether *nif, struct chan *c, uint8_t * db, int n)
{
return devstat(c, db, n, (struct dirtab *)nif, 0, netifgen);
}
void netifclose(struct ether *nif, struct chan *c)
{
struct netfile *f;
int t;
struct netaddr *ap;
if ((c->flag & COPEN) == 0)
return;
t = NETTYPE(c->qid.path);
if (t != Ndataqid && t != Nctlqid)
return;
f = nif->f[NETID(c->qid.path)];
qlock(&f->qlock);
if (--(f->inuse) == 0) {
if (f->prom) {
qlock(&nif->qlock);
if (--(nif->prom) == 0 && nif->promiscuous != NULL)
nif->promiscuous(nif->arg, 0);
qunlock(&nif->qlock);
f->prom = 0;
}
if (f->scan) {
qlock(&nif->qlock);
if (--(nif->scan) == 0 && nif->scanbs != NULL)
nif->scanbs(nif->arg, 0);
qunlock(&nif->qlock);
f->prom = 0;
f->scan = 0;
}
if (f->nmaddr) {
qlock(&nif->qlock);
t = 0;
for (ap = nif->maddr; ap; ap = ap->next) {
if (f->maddr[t / 8] & (1 << (t % 8)))
netmulti(nif, f, ap->addr, 0);
}
qunlock(&nif->qlock);
f->nmaddr = 0;
}
if (f->type < 0) {
qlock(&nif->qlock);
--(nif->all);
qunlock(&nif->qlock);
}
f->owner[0] = 0;
f->type = 0;
f->bridge = 0;
f->headersonly = 0;
qclose(f->in);
}
qunlock(&f->qlock);
}
spinlock_t netlock = SPINLOCK_INITIALIZER;
static int netown(struct netfile *p, char *o, int omode)
{
int mode;
int rwx;
spin_lock(&netlock);
if (*p->owner) {
if (strncmp(o, p->owner, KNAMELEN) == 0)
mode = p->mode;
else if (strncmp(o, eve.name, KNAMELEN) == 0)
mode = p->mode << 3;
else
mode = p->mode << 6;
rwx = omode_to_rwx(omode);
if ((rwx & mode) == rwx) {
spin_unlock(&netlock);
return 0;
} else {
spin_unlock(&netlock);
return -1;
}
}
strlcpy(p->owner, o, KNAMELEN);
p->mode = 0660;
spin_unlock(&netlock);
return 0;
}
/*
* Increment the reference count of a network device.
* If id < 0, return an unused ether device.
*/
static int openfile(struct ether *nif, int id)
{
ERRSTACK(1);
struct netfile *f, **fp, **efp;
if (id >= 0) {
f = nif->f[id];
if (f == 0)
error(ENODEV, ERROR_FIXME);
qlock(&f->qlock);
qreopen(f->in);
f->inuse++;
qunlock(&f->qlock);
return id;
}
qlock(&nif->qlock);
if (waserror()) {
qunlock(&nif->qlock);
nexterror();
}
efp = &nif->f[nif->nfile];
for (fp = nif->f; fp < efp; fp++) {
f = *fp;
if (f == 0) {
f = kzmalloc(sizeof(struct netfile), 0);
if (f == 0)
exhausted("memory");
/* since we lock before netifinit (if we ever call
* that...) */
qlock_init(&f->qlock);
f->in = qopen(nif->limit, Qmsg, 0, 0);
if (f->in == NULL) {
kfree(f);
exhausted("memory");
}
*fp = f;
qlock(&f->qlock);
} else {
qlock(&f->qlock);
if (f->inuse) {
qunlock(&f->qlock);
continue;
}
}
f->inuse = 1;
qreopen(f->in);
netown(f, current->user.name, 0);
qunlock(&f->qlock);
qunlock(&nif->qlock);
poperror();
return fp - nif->f;
}
error(ENODEV, ERROR_FIXME);
return -1; /* not reached */
}
/*
* look for a token starting a string,
* return a pointer to first non-space char after it
*/
static char *matchtoken(char *p, char *token)
{
int n;
n = strlen(token);
if (strncmp(p, token, n))
return 0;
p += n;
if (*p == 0)
return p;
if (*p != ' ' && *p != '\t' && *p != '\n')
return 0;
while (*p == ' ' || *p == '\t' || *p == '\n')
p++;
return p;
}
static uint32_t hash(uint8_t * a, int len)
{
uint32_t sum = 0;
while (len-- > 0)
sum = (sum << 1) + *a++;
return sum % Nmhash;
}
int activemulti(struct ether *nif, uint8_t * addr, int alen)
{
struct netaddr *hp;
for (hp = nif->mhash[hash(addr, alen)]; hp; hp = hp->hnext)
if (memcmp(addr, hp->addr, alen) == 0) {
if (hp->ref)
return 1;
else
break;
}
return 0;
}
static int parseaddr(uint8_t *to, char *from, int alen)
{
char nip[4];
char *p;
int i;
p = from;
for (i = 0; i < alen; i++) {
if (*p == 0)
return -1;
nip[0] = *p++;
if (*p == 0)
return -1;
nip[1] = *p++;
nip[2] = 0;
to[i] = strtoul(nip, 0, 16);
if (*p == ':')
p++;
}
return 0;
}
/*
* keep track of multicast addresses
*/
static char *netmulti(struct ether *nif, struct netfile *f, uint8_t *addr,
int add)
{
struct netaddr **l, *ap;
int i;
uint32_t h;
if (nif->multicast == NULL)
return "interface does not support multicast";
l = &nif->maddr;
i = 0;
for (ap = *l; ap; ap = *l) {
if (memcmp(addr, ap->addr, nif->alen) == 0)
break;
i++;
l = &ap->next;
}
if (add) {
if (ap == 0) {
/* TODO: AFAIK, this never gets freed. if we fix that,
* we can use a kref too (instead of int ap->ref). */
*l = ap = kzmalloc(sizeof(*ap), 0);
memmove(ap->addr, addr, nif->alen);
ap->next = 0;
ap->ref = 1;
h = hash(addr, nif->alen);
ap->hnext = nif->mhash[h];
nif->mhash[h] = ap;
} else {
ap->ref++;
}
if (ap->ref == 1) {
nif->nmaddr++;
nif->multicast(nif->arg, addr, 1);
}
if (i < 8 * sizeof(f->maddr)) {
if ((f->maddr[i / 8] & (1 << (i % 8))) == 0)
f->nmaddr++;
f->maddr[i / 8] |= 1 << (i % 8);
}
} else {
if (ap == 0 || ap->ref == 0)
return 0;
ap->ref--;
if (ap->ref == 0) {
nif->nmaddr--;
nif->multicast(nif->arg, addr, 0);
}
if (i < 8 * sizeof(f->maddr)) {
if ((f->maddr[i / 8] & (1 << (i % 8))) != 0)
f->nmaddr--;
f->maddr[i / 8] &= ~(1 << (i % 8));
}
}
return 0;
}
/* Prints the contents of stats to [va + offset, va + offset + amt). */
ssize_t linux_ifstat(struct netif_stats *stats, void *va, size_t amt,
off_t offset)
{
char *p;
size_t sofar = 0;
ssize_t ret;
p = kzmalloc(READSTR, MEM_WAIT);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx pkts : %lu\n", stats->rx_packets);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx pkts : %lu\n", stats->tx_packets);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx bytes : %lu\n", stats->rx_bytes);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx bytes : %lu\n", stats->tx_bytes);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx errors : %lu\n", stats->rx_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx errors : %lu\n", stats->tx_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx dropped : %lu\n", stats->rx_dropped);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx dropped : %lu\n", stats->tx_dropped);
sofar += snprintf(p + sofar, READSTR - sofar,
"multicast : %lu\n", stats->multicast);
sofar += snprintf(p + sofar, READSTR - sofar,
"collisions : %lu\n", stats->collisions);
sofar += snprintf(p + sofar, READSTR - sofar, "\n");
sofar += snprintf(p + sofar, READSTR - sofar,
"rx length errors : %lu\n",
stats->rx_length_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx over errors : %lu\n", stats->rx_over_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx crc errors : %lu\n", stats->rx_crc_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx frame errors : %lu\n", stats->rx_frame_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx fifo errors : %lu\n", stats->rx_fifo_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx missed errors : %lu\n",
stats->rx_missed_errors);
sofar += snprintf(p + sofar, READSTR - sofar, "\n");
sofar += snprintf(p + sofar, READSTR - sofar,
"tx aborted errors : %lu\n",
stats->tx_aborted_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx carrier errors : %lu\n",
stats->tx_carrier_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx fifo errors : %lu\n", stats->tx_fifo_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx heartbeat errors: %lu\n",
stats->tx_heartbeat_errors);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx window errors : %lu\n",
stats->tx_window_errors);
sofar += snprintf(p + sofar, READSTR - sofar, "\n");
sofar += snprintf(p + sofar, READSTR - sofar,
"rx compressed : %lu\n", stats->rx_compressed);
sofar += snprintf(p + sofar, READSTR - sofar,
"tx compressed : %lu\n", stats->tx_compressed);
sofar += snprintf(p + sofar, READSTR - sofar,
"rx nohandler : %lu\n", stats->rx_nohandler);
ret = readstr(offset, va, amt, p);
kfree(p);
return ret;
}