Make sbni(4) MPSAFE:

- Add a mutex to the softc and use it to protect the softc and device
  hardware.
- Setup interrupt handler after attaching device to network stack.
- Use device_set_desc() rather than device_quiet() plus a manual printf
  that simulates the normal probe printf.
- Axe next_sbni_unit and instead just leave room for two sbni devices for
  each bus attachment.
- Don't bzero the already-zero'd softc.
- Add a detach method to the PCI driver.
- Add a lock to protect the list of available devices used to chain
  interrupt handlers for dual port ISA cards.
- Remove unused watchdog routine.
- If if_alloc() fails, make sbni_attach() return an error rather than
  panic'ing.
- Consolidate code to free bus resources into sbni_release_resources().
- Clear IFF_DRV_RUNNING|OACTIVE in stop() routine instead of in callers.
- Let ether_ioctl() handle SIOCSIFMTU.
This commit is contained in:
John Baldwin 2008-07-04 20:53:41 +00:00
parent 6819e13eeb
commit 5d3f96f731
4 changed files with 206 additions and 125 deletions

View File

@ -62,6 +62,7 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/sockio.h>
@ -89,9 +90,10 @@ __FBSDID("$FreeBSD$");
#define ASM_CRC 1
static void sbni_init(void *);
static void sbni_init_locked(struct sbni_softc *);
static void sbni_start(struct ifnet *);
static void sbni_start_locked(struct ifnet *);
static int sbni_ioctl(struct ifnet *, u_long, caddr_t);
static void sbni_watchdog(struct ifnet *);
static void sbni_stop(struct sbni_softc *);
static void handle_channel(struct sbni_softc *);
@ -125,11 +127,11 @@ static __inline void sbni_outsb(struct sbni_softc *, u_char *, u_int);
static u_int32_t crc32tab[];
#ifdef SBNI_DUAL_COMPOUND
struct sbni_softc *sbni_headlist;
static struct mtx headlist_lock;
MTX_SYSINIT(headlist_lock, &headlist_lock, "sbni headlist", MTX_DEF);
static struct sbni_softc *sbni_headlist;
#endif
u_int32_t next_sbni_unit;
/* -------------------------------------------------------------------------- */
static __inline u_char
@ -217,7 +219,7 @@ sbni_probe(struct sbni_softc *sc)
/*
* Install interface into kernel networking data structures
*/
void
int
sbni_attach(struct sbni_softc *sc, int unit, struct sbni_flags flags)
{
struct ifnet *ifp;
@ -225,27 +227,27 @@ sbni_attach(struct sbni_softc *sc, int unit, struct sbni_flags flags)
ifp = sc->ifp = if_alloc(IFT_ETHER);
if (ifp == NULL)
panic("sbni%d: can not if_alloc()", unit);
return (ENOMEM);
sbni_outb(sc, CSR0, 0);
set_initial_values(sc, flags);
callout_handle_init(&sc->wch);
/* Initialize ifnet structure */
ifp->if_softc = sc;
if_initname(ifp, "sbni", unit);
ifp->if_init = sbni_init;
ifp->if_start = sbni_start;
ifp->if_ioctl = sbni_ioctl;
ifp->if_watchdog = sbni_watchdog;
ifp->if_snd.ifq_maxlen = IFQ_MAXLEN;
IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN);
/* report real baud rate */
csr0 = sbni_inb(sc, CSR0);
ifp->if_baudrate =
(csr0 & 0x01 ? 500000 : 2000000) / (1 << flags.rate);
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST |
IFF_NEEDSGIANT;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
mtx_init(&sc->lock, ifp->if_xname, MTX_NETWORK_LOCK, MTX_DEF);
callout_init_mtx(&sc->wch, &sc->lock, 0);
ether_ifattach(ifp, sc->enaddr);
/* device attach does transition from UNCONFIGURED to IDLE state */
@ -254,6 +256,34 @@ sbni_attach(struct sbni_softc *sc, int unit, struct sbni_flags flags)
printf("auto\n");
else
printf("%d (fixed)\n", sc->cur_rxl_index);
return (0);
}
void
sbni_detach(struct sbni_softc *sc)
{
SBNI_LOCK(sc);
sbni_stop(sc);
SBNI_UNLOCK(sc);
callout_drain(&sc->wch);
ether_ifdetach(sc->ifp);
if (sc->irq_handle)
bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_handle);
mtx_destroy(&sc->lock);
if_free(sc->ifp);
}
void
sbni_release_resources(struct sbni_softc *sc)
{
if (sc->irq_res)
bus_release_resource(sc->dev, SYS_RES_IRQ, sc->irq_rid,
sc->irq_res);
if (sc->io_res && sc->io_off == 0)
bus_release_resource(sc->dev, SYS_RES_IOPORT, sc->io_rid,
sc->io_res);
}
/* -------------------------------------------------------------------------- */
@ -262,10 +292,18 @@ static void
sbni_init(void *xsc)
{
struct sbni_softc *sc;
struct ifnet *ifp;
int s;
sc = (struct sbni_softc *)xsc;
SBNI_LOCK(sc);
sbni_init_locked(sc);
SBNI_UNLOCK(sc);
}
static void
sbni_init_locked(struct sbni_softc *sc)
{
struct ifnet *ifp;
ifp = sc->ifp;
/*
@ -275,24 +313,31 @@ sbni_init(void *xsc)
if (ifp->if_drv_flags & IFF_DRV_RUNNING)
return;
s = splimp();
ifp->if_timer = 0;
card_start(sc);
sc->wch = timeout(sbni_timeout, sc, hz/SBNI_HZ);
callout_reset(&sc->wch, hz/SBNI_HZ, sbni_timeout, sc);
ifp->if_drv_flags |= IFF_DRV_RUNNING;
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
/* attempt to start output */
sbni_start(ifp);
splx(s);
sbni_start_locked(ifp);
}
static void
sbni_start(struct ifnet *ifp)
{
struct sbni_softc *sc = ifp->if_softc;
SBNI_LOCK(sc);
sbni_start_locked(ifp);
SBNI_UNLOCK(sc);
}
static void
sbni_start_locked(struct ifnet *ifp)
{
struct sbni_softc *sc = ifp->if_softc;
if (sc->tx_frameno == 0)
prepare_to_send(sc);
}
@ -309,8 +354,8 @@ sbni_stop(struct sbni_softc *sc)
sc->rx_buf_p = NULL;
}
untimeout(sbni_timeout, sc, sc->wch);
sc->wch.callout = NULL;
callout_stop(&sc->wch);
sc->ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
}
/* -------------------------------------------------------------------------- */
@ -340,14 +385,20 @@ sbni_intr(void *arg)
do {
repeat = 0;
SBNI_LOCK(sc);
if (sbni_inb(sc, CSR0) & (RC_RDY | TR_RDY)) {
handle_channel(sc);
repeat = 1;
}
if (sc->slave_sc && /* second channel present */
(sbni_inb(sc->slave_sc, CSR0) & (RC_RDY | TR_RDY))) {
handle_channel(sc->slave_sc);
repeat = 1;
SBNI_UNLOCK(sc);
if (sc->slave_sc) {
/* second channel present */
SBNI_LOCK(sc->slave_sc);
if (sbni_inb(sc->slave_sc, CSR0) & (RC_RDY | TR_RDY)) {
handle_channel(sc->slave_sc);
repeat = 1;
}
SBNI_UNLOCK(sc->slave_sc);
}
} while (repeat);
}
@ -378,7 +429,7 @@ handle_channel(struct sbni_softc *sc)
*/
csr0 = sbni_inb(sc, CSR0);
if ((csr0 & TR_RDY) == 0 || (csr0 & RC_RDY) != 0)
printf("sbni: internal error!\n");
if_printf(sc->ifp, "internal error!\n");
/* if state & FL_NEED_RESEND != 0 then tx_frameno != 0 */
if (req_ans || sc->tx_frameno != 0)
@ -856,9 +907,11 @@ indicate_pkt(struct sbni_softc *sc)
m = sc->rx_buf_p;
m->m_pkthdr.rcvif = ifp;
m->m_pkthdr.len = m->m_len = sc->inppos;
(*ifp->if_input)(ifp, m);
sc->rx_buf_p = NULL;
SBNI_UNLOCK(sc);
(*ifp->if_input)(ifp, m);
SBNI_LOCK(sc);
}
/* -------------------------------------------------------------------------- */
@ -872,11 +925,10 @@ static void
sbni_timeout(void *xsc)
{
struct sbni_softc *sc;
int s;
u_char csr0;
sc = (struct sbni_softc *)xsc;
s = splimp();
SBNI_ASSERT_LOCKED(sc);
csr0 = sbni_inb(sc, CSR0);
if (csr0 & RC_CHK) {
@ -895,9 +947,8 @@ sbni_timeout(void *xsc)
}
}
sbni_outb(sc, CSR0, csr0 | RC_CHK);
sc->wch = timeout(sbni_timeout, sc, hz/SBNI_HZ);
splx(s);
sbni_outb(sc, CSR0, csr0 | RC_CHK);
callout_reset(&sc->wch, hz/SBNI_HZ, sbni_timeout, sc);
}
/* -------------------------------------------------------------------------- */
@ -918,19 +969,6 @@ card_start(struct sbni_softc *sc)
/* -------------------------------------------------------------------------- */
/*
* Device timeout/watchdog routine. Entered if the device neglects to
* generate an interrupt after a transmit has been started on it.
*/
static void
sbni_watchdog(struct ifnet *ifp)
{
log(LOG_ERR, "%s: device timeout\n", ifp->if_xname);
ifp->if_oerrors++;
}
static u_char rxl_tab[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
0x0a, 0x0c, 0x0f, 0x16, 0x18, 0x1a, 0x1c, 0x1f
@ -971,12 +1009,22 @@ set_initial_values(struct sbni_softc *sc, struct sbni_flags flags)
#ifdef SBNI_DUAL_COMPOUND
void
sbni_add(struct sbni_softc *sc)
{
mtx_lock(&headlist_lock);
sc->link = sbni_headlist;
sbni_headlist = sc;
mtx_unlock(&headlist_lock);
}
struct sbni_softc *
connect_to_master(struct sbni_softc *sc)
{
struct sbni_softc *p, *p_prev;
mtx_lock(&headlist_lock);
for (p = sbni_headlist, p_prev = NULL; p; p_prev = p, p = p->link) {
if (rman_get_start(p->io_res) == rman_get_start(sc->io_res) + 4 ||
rman_get_start(p->io_res) == rman_get_start(sc->io_res) - 4) {
@ -985,9 +1033,11 @@ connect_to_master(struct sbni_softc *sc)
p_prev->link = p->link;
else
sbni_headlist = p->link;
mtx_unlock(&headlist_lock);
return p;
}
}
mtx_unlock(&headlist_lock);
return (NULL);
}
@ -1049,30 +1099,29 @@ sbni_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
struct thread *td;
struct sbni_in_stats *in_stats;
struct sbni_flags flags;
int error, s;
int error;
sc = ifp->if_softc;
ifr = (struct ifreq *)data;
td = curthread;
error = 0;
s = splimp();
switch (command) {
case SIOCSIFFLAGS:
/*
* If the interface is marked up and stopped, then start it.
* If it is marked down and running, then stop it.
*/
SBNI_LOCK(sc);
if (ifp->if_flags & IFF_UP) {
if (!(ifp->if_drv_flags & IFF_DRV_RUNNING))
sbni_init(sc);
sbni_init_locked(sc);
} else {
if (ifp->if_drv_flags & IFF_DRV_RUNNING) {
sbni_stop(sc);
ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
}
}
SBNI_UNLOCK(sc);
break;
case SIOCADDMULTI:
@ -1086,29 +1135,29 @@ sbni_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
error = EAFNOSUPPORT; */
break;
case SIOCSIFMTU:
if (ifr->ifr_mtu > ETHERMTU)
error = EINVAL;
else
ifp->if_mtu = ifr->ifr_mtu;
break;
/*
* SBNI specific ioctl
*/
case SIOCGHWFLAGS: /* get flags */
SBNI_LOCK(sc);
bcopy((caddr_t)IF_LLADDR(sc->ifp)+3, (caddr_t) &flags, 3);
flags.rxl = sc->cur_rxl_index;
flags.rate = sc->csr1.rate;
flags.fixed_rxl = (sc->delta_rxl == 0);
flags.fixed_rate = 1;
SBNI_UNLOCK(sc);
ifr->ifr_data = *(caddr_t*) &flags;
break;
case SIOCGINSTATS:
in_stats = (struct sbni_in_stats *)ifr->ifr_data;
bcopy((void *)(&(sc->in_stats)), (void *)in_stats,
sizeof(struct sbni_in_stats));
in_stats = malloc(sizeof(struct sbni_in_stats), M_DEVBUF,
M_WAITOK);
SBNI_LOCK(sc);
bcopy(&sc->in_stats, in_stats, sizeof(struct sbni_in_stats));
SBNI_UNLOCK(sc);
error = copyout(ifr->ifr_data, in_stats,
sizeof(struct sbni_in_stats));
free(in_stats, M_DEVBUF);
break;
case SIOCSHWFLAGS: /* set flags */
@ -1117,6 +1166,7 @@ sbni_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
if (error)
break;
flags = *(struct sbni_flags*)&ifr->ifr_data;
SBNI_LOCK(sc);
if (flags.fixed_rxl) {
sc->delta_rxl = 0;
sc->cur_rxl_index = flags.rxl;
@ -1132,11 +1182,14 @@ sbni_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
/* Don't be afraid... */
sbni_outb(sc, CSR1, *(char*)(&sc->csr1) | PR_RES);
SBNI_UNLOCK(sc);
break;
case SIOCRINSTATS:
SBNI_LOCK(sc);
if (!(error = priv_check(td, PRIV_DRIVER))) /* root only */
bzero(&sc->in_stats, sizeof(struct sbni_in_stats));
SBNI_UNLOCK(sc);
break;
default:
@ -1144,7 +1197,6 @@ sbni_ioctl(struct ifnet *ifp, u_long command, caddr_t data)
break;
}
splx(s);
return (error);
}

View File

@ -85,7 +85,6 @@ sbni_probe_isa(device_t dev)
return (error);
sc = device_get_softc(dev);
bzero(sc, sizeof(struct sbni_softc));
sc->io_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->io_rid,
0ul, ~0ul, SBNI_PORTS, RF_ACTIVE);
@ -95,12 +94,11 @@ sbni_probe_isa(device_t dev)
}
if (sbni_probe(sc) != 0) {
bus_release_resource(dev, SYS_RES_IOPORT,
sc->io_rid, sc->io_res);
sbni_release_resources(sc);
return (ENXIO);
}
device_quiet(dev);
device_set_desc(dev, "Granch SBNI12/ISA adapter");
return (0);
}
@ -113,50 +111,32 @@ sbni_attach_isa(device_t dev)
int error;
sc = device_get_softc(dev);
sc->dev = dev;
printf("sbni%d: <Granch SBNI12/ISA adapter> port 0x%lx",
next_sbni_unit, rman_get_start(sc->io_res));
sc->irq_res = bus_alloc_resource_any(
dev, SYS_RES_IRQ, &sc->irq_rid, RF_ACTIVE);
if (sc->irq_res) {
printf(" irq %ld\n", rman_get_start(sc->irq_res));
error = bus_setup_intr(
dev, sc->irq_res, INTR_TYPE_NET,
NULL, sbni_intr, sc, &sc->irq_handle);
if (error) {
printf("sbni%d: bus_setup_intr\n", next_sbni_unit);
bus_release_resource(
dev, SYS_RES_IOPORT, sc->io_rid, sc->io_res);
bus_release_resource(
dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res);
return (error);
}
#ifndef SBNI_DUAL_COMPOUND
} else {
printf("\nsbni%d: irq conflict!\n", next_sbni_unit);
bus_release_resource(dev, SYS_RES_IOPORT,
sc->io_rid, sc->io_res);
if (sc->irq_res == NULL) {
device_printf(dev, "irq conflict!\n");
sbni_release_resources(sc);
return (ENOENT);
}
#else /* SBNI_DUAL_COMPOUND */
sc->link = sbni_headlist;
sbni_headlist = sc;
if (sc->irq_res) {
sbni_add(sc);
} else {
struct sbni_softc *master;
if ((master = connect_to_master(sc)) == 0) {
printf("\nsbni%d: failed to alloc irq\n",
next_sbni_unit);
bus_release_resource(
dev, SYS_RES_IOPORT, sc->io_rid, sc->io_res);
device_printf(dev, "failed to alloc irq\n");
sbni_release_resources(sc);
return (ENXIO);
} else {
printf(" shared irq with %s\n",
device_printf(dev, "shared irq with %s\n",
master->ifp->if_xname);
}
}
@ -164,6 +144,24 @@ sbni_attach_isa(device_t dev)
*(u_int32_t*)&flags = device_get_flags(dev);
sbni_attach(sc, next_sbni_unit++, flags);
error = sbni_attach(sc, device_get_unit(dev) * 2, flags);
if (error) {
device_printf(dev, "cannot initialize driver\n");
sbni_release_resources(sc);
return (error);
}
if (sc->irq_res) {
error = bus_setup_intr(
dev, sc->irq_res, INTR_TYPE_NET | INTR_MPSAFE,
NULL, sbni_intr, sc, &sc->irq_handle);
if (error) {
device_printf(dev, "bus_setup_intr\n");
sbni_detach(sc);
sbni_release_resources(sc);
return (error);
}
}
return (0);
}

View File

@ -51,11 +51,13 @@ __FBSDID("$FreeBSD$");
static int sbni_pci_probe(device_t);
static int sbni_pci_attach(device_t);
static int sbni_pci_detach(device_t);
static device_method_t sbni_pci_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, sbni_pci_probe),
DEVMETHOD(device_attach, sbni_pci_attach),
DEVMETHOD(device_detach, sbni_pci_detach),
{ 0, 0 }
};
@ -75,14 +77,13 @@ sbni_pci_probe(device_t dev)
{
struct sbni_softc *sc;
u_int32_t ports;
ports = SBNI_PORTS;
if (pci_get_vendor(dev) != SBNI_PCI_VENDOR ||
pci_get_device(dev) != SBNI_PCI_DEVICE)
return (ENXIO);
sc = device_get_softc(dev);
bzero(sc, sizeof(struct sbni_softc));
if (pci_get_subdevice(dev) == 2) {
ports <<= 1;
sc->slave_sc = malloc(sizeof(struct sbni_softc),
@ -97,7 +98,7 @@ sbni_pci_probe(device_t dev)
sc->io_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->io_rid,
0ul, ~0ul, ports, RF_ACTIVE);
if (!sc->io_res) {
printf("sbni: cannot allocate io ports!\n");
device_printf(dev, "cannot allocate io ports!\n");
if (sc->slave_sc)
free(sc->slave_sc, M_DEVBUF);
return (ENOENT);
@ -108,14 +109,12 @@ sbni_pci_probe(device_t dev)
sc->slave_sc->io_off = 4;
}
if (sbni_probe(sc) != 0) {
bus_release_resource(dev, SYS_RES_IOPORT,
sc->io_rid, sc->io_res);
sbni_release_resources(sc);
if (sc->slave_sc)
free(sc->slave_sc, M_DEVBUF);
return (ENXIO);
}
device_quiet(dev);
return (0);
}
@ -127,41 +126,66 @@ sbni_pci_attach(device_t dev)
int error;
sc = device_get_softc(dev);
sc->dev = dev;
printf("sbni%d: <Granch SBNI12/PCI%sadapter> port 0x%lx",
next_sbni_unit, sc->slave_sc ? " Dual " : " ",
rman_get_start(sc->io_res));
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irq_rid,
RF_SHAREABLE);
if (sc->irq_res) {
printf(" irq %ld\n", rman_get_start(sc->irq_res));
error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET,
NULL, sbni_intr, sc, &sc->irq_handle);
if (error) {
printf("sbni%d: bus_setup_intr\n", next_sbni_unit);
goto attach_failed;
}
} else {
printf("\nsbni%d: cannot claim irq!\n", next_sbni_unit);
if (sc->irq_res == NULL) {
device_printf(dev, "cannot claim irq!\n");
error = ENOENT;
goto attach_failed;
}
*(u_int32_t*)&flags = 0;
sbni_attach(sc, next_sbni_unit++, flags);
if (sc->slave_sc)
sbni_attach(sc->slave_sc, next_sbni_unit++, flags);
error = sbni_attach(sc, device_get_unit(dev) * 2, flags);
if (error) {
device_printf(dev, "cannot initialize driver\n");
goto attach_failed;
}
if (sc->slave_sc) {
error = sbni_attach(sc->slave_sc, device_get_unit(dev) * 2 + 1,
flags);
if (error) {
device_printf(dev, "cannot initialize slave\n");
sbni_detach(sc);
goto attach_failed;
}
}
if (sc->irq_res) {
error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET |
INTR_MPSAFE, NULL, sbni_intr, sc, &sc->irq_handle);
if (error) {
device_printf(dev, "bus_setup_intr\n");
sbni_detach(sc);
if (sc->slave_sc)
sbni_detach(sc);
goto attach_failed;
}
}
return (0);
attach_failed:
bus_release_resource(dev, SYS_RES_IOPORT, sc->io_rid, sc->io_res);
if (sc->irq_res) {
bus_release_resource(
dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res);
}
sbni_release_resources(sc);
if (sc->slave_sc)
free(sc->slave_sc, M_DEVBUF);
return (error);
}
static int
sbni_pci_detach(device_t dev)
{
struct sbni_softc *sc;
sc = device_get_softc(dev);
sbni_detach(sc);
if (sc->slave_sc)
sbni_detach(sc);
sbni_release_resources(sc);
if (sc->slave_sc)
free(sc->slave_sc, M_DEVBUF);
return (0);
}

View File

@ -68,6 +68,7 @@ struct sbni_flags {
struct sbni_softc {
struct ifnet *ifp;
device_t dev;
u_char enaddr[6];
int io_rid;
@ -111,7 +112,8 @@ struct sbni_softc {
struct sbni_csr1 csr1; /* current value of CSR1 */
struct sbni_in_stats in_stats; /* internal statistics */
struct callout_handle wch;
struct callout wch;
struct mtx lock;
struct sbni_softc *slave_sc;
@ -120,15 +122,20 @@ struct sbni_softc {
#endif
};
#define SBNI_LOCK(sc) mtx_lock(&(sc)->lock)
#define SBNI_UNLOCK(sc) mtx_unlock(&(sc)->lock)
#define SBNI_ASSERT_LOCKED(sc) mtx_assert(&(sc)->lock, MA_OWNED)
void sbni_intr(void *);
int sbni_probe(struct sbni_softc *);
void sbni_attach(struct sbni_softc *, int, struct sbni_flags);
int sbni_attach(struct sbni_softc *, int, struct sbni_flags);
void sbni_detach(struct sbni_softc *);
void sbni_release_resources(struct sbni_softc *);
extern u_int32_t next_sbni_unit;
#ifdef SBNI_DUAL_COMPOUND
extern struct sbni_softc *sbni_headlist;
void sbni_add(struct sbni_softc *);
struct sbni_softc *connect_to_master(struct sbni_softc *);
#endif
#endif /* _KERNEL */