Make fe(4) MPSAFE:

- Add a mutex to the softc to protect the softc and device hardware.
- Don't leak bus resources if if_alloc() fails during attach.
- Setup the interrupt handler after calling ether_ifattach().
- Use a private timer to manage the transmit watchdog.

Tested by:	 WATANABE Kazuhiro  CQG00620 of nifty.ne.jp
This commit is contained in:
jhb 2008-06-02 19:58:48 +00:00
parent 5c17f7e3cf
commit 2a86f1806c
3 changed files with 86 additions and 44 deletions

View File

@ -71,6 +71,7 @@ __FBSDID("$FreeBSD$");
*/
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/sockio.h>
@ -135,10 +136,12 @@ static struct fe_filter const fe_filter_all = { FE_FILTER_ALL };
/* Standard driver entry points. These can be static. */
static void fe_init (void *);
static void fe_init_locked (struct fe_softc *);
static driver_intr_t fe_intr;
static int fe_ioctl (struct ifnet *, u_long, caddr_t);
static void fe_start (struct ifnet *);
static void fe_watchdog (struct ifnet *);
static void fe_start_locked (struct ifnet *);
static void fe_watchdog (void *);
static int fe_medchange (struct ifnet *);
static void fe_medstat (struct ifnet *, struct ifmediareq *);
@ -737,16 +740,13 @@ fe_attach (device_t dev)
ifp = sc->ifp = if_alloc(IFT_ETHER);
if (ifp == NULL) {
device_printf(dev, "can not ifalloc\n");
fe_release_resource(dev);
return (ENOSPC);
}
error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET,
NULL, fe_intr, sc, &sc->irq_handle);
if (error) {
if_free(ifp);
fe_release_resource(dev);
return ENXIO;
}
mtx_init(&sc->lock, device_get_nameunit(dev), MTX_NETWORK_LOCK,
MTX_DEF);
callout_init_mtx(&sc->timer, &sc->lock, 0);
/*
* Initialize ifnet structure
@ -755,7 +755,6 @@ fe_attach (device_t dev)
if_initname(sc->ifp, device_get_name(dev), device_get_unit(dev));
ifp->if_start = fe_start;
ifp->if_ioctl = fe_ioctl;
ifp->if_watchdog = fe_watchdog;
ifp->if_init = fe_init;
ifp->if_linkmib = &sc->mibdata;
ifp->if_linkmiblen = sizeof (sc->mibdata);
@ -767,8 +766,7 @@ fe_attach (device_t dev)
/*
* Set fixed interface flags.
*/
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST |
IFF_NEEDSGIANT;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
#if 1
/*
@ -830,9 +828,21 @@ fe_attach (device_t dev)
#endif
/* Attach and stop the interface. */
ether_ifattach(sc->ifp, sc->enaddr);
FE_LOCK(sc);
fe_stop(sc);
FE_UNLOCK(sc);
ether_ifattach(sc->ifp, sc->enaddr);
error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_NET | INTR_MPSAFE,
NULL, fe_intr, sc, &sc->irq_handle);
if (error) {
ether_ifdetach(ifp);
mtx_destroy(&sc->lock);
if_free(ifp);
fe_release_resource(dev);
return ENXIO;
}
/* Print additional info when attached. */
device_printf(dev, "type %s%s\n", sc->typestr,
(sc->proto_dlcr4 & FE_D4_DSC) ? ", full duplex" : "");
@ -942,7 +952,7 @@ fe_reset (struct fe_softc *sc)
/* Put the interface into known initial state. */
fe_stop(sc);
if (sc->ifp->if_flags & IFF_UP)
fe_init(sc);
fe_init_locked(sc);
}
/*
@ -954,9 +964,8 @@ fe_reset (struct fe_softc *sc)
void
fe_stop (struct fe_softc *sc)
{
int s;
s = splimp();
FE_ASSERT_LOCKED(sc);
/* Disable interrupts. */
fe_outb(sc, FE_DLCR2, 0x00);
@ -978,7 +987,7 @@ fe_stop (struct fe_softc *sc)
/* Reset transmitter variables and interface flags. */
sc->ifp->if_drv_flags &= ~(IFF_DRV_OACTIVE | IFF_DRV_RUNNING);
sc->ifp->if_timer = 0;
callout_stop(&sc->timer);
sc->txb_free = sc->txb_size;
sc->txb_count = 0;
sc->txb_sched = 0;
@ -989,8 +998,6 @@ fe_stop (struct fe_softc *sc)
/* Call a device-specific hook. */
if (sc->stop)
sc->stop(sc);
(void) splx(s);
}
/*
@ -998,16 +1005,18 @@ fe_stop (struct fe_softc *sc)
* generate an interrupt after a transmit has been started on it.
*/
static void
fe_watchdog ( struct ifnet *ifp )
fe_watchdog (void *arg)
{
struct fe_softc *sc = ifp->if_softc;
struct fe_softc *sc = arg;
FE_ASSERT_LOCKED(sc);
/* A "debug" message. */
if_printf(ifp, "transmission timeout (%d+%d)%s\n",
if_printf(sc->ifp, "transmission timeout (%d+%d)%s\n",
sc->txb_sched, sc->txb_count,
(ifp->if_flags & IFF_UP) ? "" : " when down");
(sc->ifp->if_flags & IFF_UP) ? "" : " when down");
if (sc->ifp->if_opackets == 0 && sc->ifp->if_ipackets == 0)
if_printf(ifp, "wrong IRQ setting in config?\n");
if_printf(sc->ifp, "wrong IRQ setting in config?\n");
fe_reset(sc);
}
@ -1018,10 +1027,17 @@ static void
fe_init (void * xsc)
{
struct fe_softc *sc = xsc;
int s;
FE_LOCK(sc);
fe_init_locked(sc);
FE_UNLOCK(sc);
}
static void
fe_init_locked (struct fe_softc *sc)
{
/* Start initializing 86960. */
s = splimp();
/* Call a hook before we start initializing the chip. */
if (sc->init)
@ -1128,10 +1144,8 @@ fe_init (void * xsc)
the interface keeping it idle. The upper layer will soon
start the interface anyway, and there are no significant
delay. */
fe_start(sc->ifp);
fe_start_locked(sc->ifp);
#endif
(void) splx(s);
}
/*
@ -1145,7 +1159,7 @@ fe_xmit (struct fe_softc *sc)
* We use longer timeout for multiple packet transmission.
* I'm not sure this timer value is appropriate. FIXME.
*/
sc->ifp->if_timer = 1 + sc->txb_count;
callout_reset(&sc->timer, (1 + sc->txb_count) * hz, fe_watchdog, sc);
/* Update txb variables. */
sc->txb_sched = sc->txb_count;
@ -1159,15 +1173,22 @@ fe_xmit (struct fe_softc *sc)
/*
* Start output on interface.
* We make two assumptions here:
* 1) that the current priority is set to splimp _before_ this code
* is called *and* is returned to the appropriate priority after
* return
* 2) that the IFF_DRV_OACTIVE flag is checked before this code is called
* We make one assumption here:
* 1) that the IFF_DRV_OACTIVE flag is checked before this code is called
* (i.e. that the output part of the interface is idle)
*/
static void
fe_start (struct ifnet *ifp)
{
struct fe_softc *sc = ifp->if_softc;
FE_LOCK(sc);
fe_start_locked(ifp);
FE_UNLOCK(sc);
}
static void
fe_start_locked (struct ifnet *ifp)
{
struct fe_softc *sc = ifp->if_softc;
struct mbuf *m;
@ -1534,7 +1555,7 @@ fe_tint (struct fe_softc * sc, u_char tstat)
* Reset output active flag and watchdog timer.
*/
sc->ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
sc->ifp->if_timer = 0;
callout_stop(&sc->timer);
/*
* If more data is ready to transmit in the buffer, start
@ -1685,6 +1706,8 @@ fe_intr (void *arg)
u_char tstat, rstat;
int loop_count = FE_MAX_LOOP;
FE_LOCK(sc);
/* Loop until there are no more new interrupt conditions. */
while (loop_count-- > 0) {
/*
@ -1692,8 +1715,10 @@ fe_intr (void *arg)
*/
tstat = fe_inb(sc, FE_DLCR0) & FE_TMASK;
rstat = fe_inb(sc, FE_DLCR1) & FE_RMASK;
if (tstat == 0 && rstat == 0)
if (tstat == 0 && rstat == 0) {
FE_UNLOCK(sc);
return;
}
/*
* Reset the conditions we are acknowledging.
@ -1741,8 +1766,9 @@ fe_intr (void *arg)
* interrupt when the transmission buffer is full.
*/
if ((sc->ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0)
fe_start(sc->ifp);
fe_start_locked(sc->ifp);
}
FE_UNLOCK(sc);
if_printf(sc->ifp, "too many loops\n");
}
@ -1756,9 +1782,7 @@ fe_ioctl (struct ifnet * ifp, u_long command, caddr_t data)
{
struct fe_softc *sc = ifp->if_softc;
struct ifreq *ifr = (struct ifreq *)data;
int s, error = 0;
s = splimp();
int error = 0;
switch (command) {
@ -1767,9 +1791,10 @@ fe_ioctl (struct ifnet * ifp, u_long command, caddr_t data)
* Switch interface state between "running" and
* "stopped", reflecting the UP flag.
*/
FE_LOCK(sc);
if (sc->ifp->if_flags & IFF_UP) {
if ((sc->ifp->if_drv_flags & IFF_DRV_RUNNING) == 0)
fe_init(sc);
fe_init_locked(sc);
} else {
if ((sc->ifp->if_drv_flags & IFF_DRV_RUNNING) != 0)
fe_stop(sc);
@ -1780,6 +1805,7 @@ fe_ioctl (struct ifnet * ifp, u_long command, caddr_t data)
* so reprogram the multicast filter and/or receive mode.
*/
fe_setmode(sc);
FE_UNLOCK(sc);
/* Done. */
break;
@ -1790,7 +1816,9 @@ fe_ioctl (struct ifnet * ifp, u_long command, caddr_t data)
* Multicast list has changed; set the hardware filter
* accordingly.
*/
FE_LOCK(sc);
fe_setmode(sc);
FE_UNLOCK(sc);
break;
case SIOCSIFMEDIA:
@ -1805,7 +1833,6 @@ fe_ioctl (struct ifnet * ifp, u_long command, caddr_t data)
break;
}
(void) splx(s);
return (error);
}
@ -1821,6 +1848,8 @@ fe_get_packet (struct fe_softc * sc, u_short len)
struct ether_header *eh;
struct mbuf *m;
FE_ASSERT_LOCKED(sc);
/*
* NFS wants the data be aligned to the word (4 byte)
* boundary. Ethernet header has 14 bytes. There is a
@ -1890,7 +1919,9 @@ fe_get_packet (struct fe_softc * sc, u_short len)
}
/* Feed the packet to upper layer. */
FE_UNLOCK(sc);
(*ifp->if_input)(ifp, m);
FE_LOCK(sc);
return 0;
}
@ -2164,7 +2195,7 @@ fe_setmode (struct fe_softc *sc)
/*
* Load a new multicast address filter into MARs.
*
* The caller must have splimp'ed before fe_loadmar.
* The caller must have acquired the softc lock before fe_loadmar.
* This function starts the DLC upon return. So it can be called only
* when the chip is working, i.e., from the driver's point of view, when
* a device is RUNNING. (I mistook the point in previous versions.)
@ -2223,9 +2254,11 @@ fe_medchange (struct ifnet *ifp)
until the transmission buffer being empty? Changing the
media when we are sending a frame will cause two garbages
on wires, one on old media and another on new. FIXME */
FE_LOCK(sc);
if (sc->ifp->if_flags & IFF_UP) {
if (sc->msel) sc->msel(sc);
}
FE_UNLOCK(sc);
return 0;
}

View File

@ -175,11 +175,15 @@ fe_pccard_detach(device_t dev)
struct fe_softc *sc = device_get_softc(dev);
struct ifnet *ifp = sc->ifp;
FE_LOCK(sc);
fe_stop(sc);
FE_UNLOCK(sc);
callout_drain(&sc->timer);
ether_ifdetach(ifp);
bus_teardown_intr(dev, sc->irq_res, sc->irq_handle);
if_free(ifp);
fe_release_resource(dev);
mtx_destroy(&sc->lock);
return 0;
}

View File

@ -117,6 +117,8 @@ struct fe_softc {
int defmedia; /* default media */
void (* msel)(struct fe_softc *); /* media selector. */
struct mtx lock;
struct callout timer;
};
struct fe_simple_probe_struct {
@ -125,6 +127,9 @@ struct fe_simple_probe_struct {
u_char bits; /* Values to be compared against. */
};
#define FE_LOCK(sc) mtx_lock(&(sc)->lock)
#define FE_UNLOCK(sc) mtx_unlock(&(sc)->lock)
#define FE_ASSERT_LOCKED(sc) mtx_assert(&(sc)->lock, MA_OWNED)
extern devclass_t fe_devclass;