sync w/ iwi:
o fix locking o use firmware(9) o cosmetic
This commit is contained in:
parent
1b996b2a23
commit
84ca76709f
@ -1,7 +1,7 @@
|
||||
/* $FreeBSD$ */
|
||||
|
||||
/*-
|
||||
* Copyright (c) 2004, 2005
|
||||
* Copyright (c) 2004-2006
|
||||
* Damien Bergamini <damien.bergamini@free.fr>. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -43,9 +43,13 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/socket.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/taskqueue.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/endian.h>
|
||||
#include <sys/linker.h>
|
||||
#include <sys/firmware.h>
|
||||
|
||||
#include <machine/bus.h>
|
||||
#include <machine/resource.h>
|
||||
@ -63,15 +67,15 @@ __FBSDID("$FreeBSD$");
|
||||
#include <net/if_media.h>
|
||||
#include <net/if_types.h>
|
||||
|
||||
#include <net80211/ieee80211_var.h>
|
||||
#include <net80211/ieee80211_radiotap.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/in_systm.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/if_ether.h>
|
||||
|
||||
#include <net80211/ieee80211_var.h>
|
||||
#include <net80211/ieee80211_radiotap.h>
|
||||
|
||||
#include <dev/ipw/if_ipwreg.h>
|
||||
#include <dev/ipw/if_ipwvar.h>
|
||||
|
||||
@ -87,6 +91,7 @@ SYSCTL_INT(_debug, OID_AUTO, ipw, CTLFLAG_RW, &ipw_debug, 0, "ipw debug level");
|
||||
|
||||
MODULE_DEPEND(ipw, pci, 1, 1, 1);
|
||||
MODULE_DEPEND(ipw, wlan, 1, 1, 1);
|
||||
MODULE_DEPEND(ipw, firmware, 1, 1, 1);
|
||||
|
||||
struct ipw_ident {
|
||||
uint16_t vendor;
|
||||
@ -123,11 +128,10 @@ static void ipw_watchdog(struct ifnet *);
|
||||
static int ipw_ioctl(struct ifnet *, u_long, caddr_t);
|
||||
static void ipw_stop_master(struct ipw_softc *);
|
||||
static int ipw_reset(struct ipw_softc *);
|
||||
static int ipw_load_ucode(struct ipw_softc *, u_char *, int);
|
||||
static int ipw_load_firmware(struct ipw_softc *, u_char *, int);
|
||||
static int ipw_cache_firmware(struct ipw_softc *, void *);
|
||||
static void ipw_free_firmware(struct ipw_softc *);
|
||||
static int ipw_load_ucode(struct ipw_softc *, const char *, int);
|
||||
static int ipw_load_firmware(struct ipw_softc *, const char *, int);
|
||||
static int ipw_config(struct ipw_softc *);
|
||||
static void ipw_init_task(void *, int);
|
||||
static void ipw_init(void *);
|
||||
static void ipw_stop(void *);
|
||||
static int ipw_sysctl_stats(SYSCTL_HANDLER_ARGS);
|
||||
@ -138,8 +142,8 @@ static int ipw_read_table2(struct ipw_softc *, uint32_t, void *,
|
||||
uint32_t *);
|
||||
static void ipw_read_mem_1(struct ipw_softc *, bus_size_t, uint8_t *,
|
||||
bus_size_t);
|
||||
static void ipw_write_mem_1(struct ipw_softc *, bus_size_t, uint8_t *,
|
||||
bus_size_t);
|
||||
static void ipw_write_mem_1(struct ipw_softc *, bus_size_t,
|
||||
const uint8_t *, bus_size_t);
|
||||
|
||||
static int ipw_probe(device_t);
|
||||
static int ipw_attach(device_t);
|
||||
@ -176,20 +180,6 @@ DRIVER_MODULE(ipw, pci, ipw_driver, ipw_devclass, 0, 0);
|
||||
static const struct ieee80211_rateset ipw_rateset_11b =
|
||||
{ 4, { 2, 4, 11, 22 } };
|
||||
|
||||
static __inline uint8_t
|
||||
MEM_READ_1(struct ipw_softc *sc, uint32_t addr)
|
||||
{
|
||||
CSR_WRITE_4(sc, IPW_CSR_INDIRECT_ADDR, addr);
|
||||
return CSR_READ_1(sc, IPW_CSR_INDIRECT_DATA);
|
||||
}
|
||||
|
||||
static __inline uint32_t
|
||||
MEM_READ_4(struct ipw_softc *sc, uint32_t addr)
|
||||
{
|
||||
CSR_WRITE_4(sc, IPW_CSR_INDIRECT_ADDR, addr);
|
||||
return CSR_READ_4(sc, IPW_CSR_INDIRECT_DATA);
|
||||
}
|
||||
|
||||
static int
|
||||
ipw_probe(device_t dev)
|
||||
{
|
||||
@ -222,6 +212,8 @@ ipw_attach(device_t dev)
|
||||
mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK,
|
||||
MTX_DEF | MTX_RECURSE);
|
||||
|
||||
TASK_INIT(&sc->sc_init_task, 0, ipw_init_task, sc);
|
||||
|
||||
if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) {
|
||||
device_printf(dev, "chip is in D%d power mode "
|
||||
"-- setting to D0\n", pci_get_powerstate(dev));
|
||||
@ -285,8 +277,11 @@ ipw_attach(device_t dev)
|
||||
ic->ic_state = IEEE80211_S_INIT;
|
||||
|
||||
/* set device capabilities */
|
||||
ic->ic_caps = IEEE80211_C_SHPREAMBLE | IEEE80211_C_TXPMGT |
|
||||
IEEE80211_C_PMGT | IEEE80211_C_IBSS | IEEE80211_C_MONITOR;
|
||||
ic->ic_caps =
|
||||
IEEE80211_C_IBSS | /* IBSS mode supported */
|
||||
IEEE80211_C_MONITOR | /* monitor mode supported */
|
||||
IEEE80211_C_TXPMGT | /* tx power management */
|
||||
IEEE80211_C_SHPREAMBLE; /* short preamble supported */
|
||||
|
||||
/* read MAC address from EEPROM */
|
||||
val = ipw_read_prom_word(sc, IPW_EEPROM_MAC + 0);
|
||||
@ -381,12 +376,7 @@ ipw_detach(device_t dev)
|
||||
struct ieee80211com *ic = &sc->sc_ic;
|
||||
struct ifnet *ifp = ic->ic_ifp;
|
||||
|
||||
IPW_LOCK(sc);
|
||||
|
||||
ipw_stop(sc);
|
||||
ipw_free_firmware(sc);
|
||||
|
||||
IPW_UNLOCK(sc);
|
||||
|
||||
if (ifp != NULL) {
|
||||
bpfdetach(ifp);
|
||||
@ -402,6 +392,7 @@ ipw_detach(device_t dev)
|
||||
|
||||
if (sc->mem != NULL)
|
||||
bus_release_resource(dev, SYS_RES_MEMORY, sc->mem_rid, sc->mem);
|
||||
|
||||
if (ifp != NULL)
|
||||
if_free(ifp);
|
||||
|
||||
@ -723,7 +714,7 @@ ipw_resume(device_t dev)
|
||||
struct ipw_softc *sc = device_get_softc(dev);
|
||||
struct ifnet *ifp = sc->sc_ic.ic_ifp;
|
||||
|
||||
IPW_LOCK(sc);
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
pci_write_config(dev, 0x41, 0, 1);
|
||||
|
||||
@ -733,7 +724,7 @@ ipw_resume(device_t dev)
|
||||
ifp->if_start(ifp);
|
||||
}
|
||||
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -744,25 +735,25 @@ ipw_media_change(struct ifnet *ifp)
|
||||
struct ipw_softc *sc = ifp->if_softc;
|
||||
int error;
|
||||
|
||||
IPW_LOCK(sc);
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
error = ieee80211_media_change(ifp);
|
||||
if (error != ENETRESET) {
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
return error;
|
||||
}
|
||||
|
||||
if ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING))
|
||||
ipw_init(sc);
|
||||
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The firmware automaticly adapt the transmit speed. We report the current
|
||||
* transmit speed here.
|
||||
* The firmware automatically adapts the transmit speed. We report its current
|
||||
* value here.
|
||||
*/
|
||||
static void
|
||||
ipw_media_status(struct ifnet *ifp, struct ifmediareq *imr)
|
||||
@ -852,6 +843,7 @@ ipw_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
|
||||
}
|
||||
|
||||
ic->ic_state = nstate;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1073,8 +1065,8 @@ ipw_data_intr(struct ipw_softc *sc, struct ipw_status *status,
|
||||
|
||||
tap->wr_flags = 0;
|
||||
tap->wr_antsignal = status->rssi;
|
||||
tap->wr_chan_freq = htole16(ic->ic_ibss_chan->ic_freq);
|
||||
tap->wr_chan_flags = htole16(ic->ic_ibss_chan->ic_flags);
|
||||
tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq);
|
||||
tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags);
|
||||
|
||||
bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_rxtap_len, m);
|
||||
}
|
||||
@ -1223,26 +1215,27 @@ ipw_intr(void *arg)
|
||||
struct ipw_softc *sc = arg;
|
||||
uint32_t r;
|
||||
|
||||
IPW_LOCK(sc);
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
if ((r = CSR_READ_4(sc, IPW_CSR_INTR)) == 0 || r == 0xffffffff) {
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* disable interrupts */
|
||||
CSR_WRITE_4(sc, IPW_CSR_INTR_MASK, 0);
|
||||
|
||||
/* acknowledge all interrupts */
|
||||
CSR_WRITE_4(sc, IPW_CSR_INTR, r);
|
||||
|
||||
if (r & (IPW_INTR_FATAL_ERROR | IPW_INTR_PARITY_ERROR)) {
|
||||
device_printf(sc->sc_dev, "fatal error\n");
|
||||
sc->sc_ic.ic_ifp->if_flags &= ~IFF_UP;
|
||||
ipw_stop(sc);
|
||||
device_printf(sc->sc_dev, "firmware error\n");
|
||||
taskqueue_enqueue_fast(taskqueue_fast, &sc->sc_init_task);
|
||||
r = 0; /* don't process more interrupts */
|
||||
}
|
||||
|
||||
if (r & IPW_INTR_FW_INIT_DONE) {
|
||||
if (!(r & (IPW_INTR_FATAL_ERROR | IPW_INTR_PARITY_ERROR)))
|
||||
wakeup(sc);
|
||||
}
|
||||
if (r & IPW_INTR_FW_INIT_DONE)
|
||||
wakeup(sc);
|
||||
|
||||
if (r & IPW_INTR_RX_TRANSFER)
|
||||
ipw_rx_intr(sc);
|
||||
@ -1250,13 +1243,10 @@ ipw_intr(void *arg)
|
||||
if (r & IPW_INTR_TX_TRANSFER)
|
||||
ipw_tx_intr(sc);
|
||||
|
||||
/* acknowledge all interrupts */
|
||||
CSR_WRITE_4(sc, IPW_CSR_INTR, r);
|
||||
|
||||
/* re-enable interrupts */
|
||||
CSR_WRITE_4(sc, IPW_CSR_INTR_MASK, IPW_INTR_MASK);
|
||||
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1348,8 +1338,8 @@ ipw_tx_start(struct ifnet *ifp, struct mbuf *m0, struct ieee80211_node *ni)
|
||||
struct ipw_tx_radiotap_header *tap = &sc->sc_txtap;
|
||||
|
||||
tap->wt_flags = 0;
|
||||
tap->wt_chan_freq = htole16(ic->ic_ibss_chan->ic_freq);
|
||||
tap->wt_chan_flags = htole16(ic->ic_ibss_chan->ic_flags);
|
||||
tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq);
|
||||
tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags);
|
||||
|
||||
bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_txtap_len, m0);
|
||||
}
|
||||
@ -1475,10 +1465,10 @@ ipw_start(struct ifnet *ifp)
|
||||
struct ether_header *eh;
|
||||
struct ieee80211_node *ni;
|
||||
|
||||
IPW_LOCK(sc);
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
if (ic->ic_state != IEEE80211_S_RUN) {
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1525,7 +1515,7 @@ ipw_start(struct ifnet *ifp)
|
||||
ifp->if_timer = 1;
|
||||
}
|
||||
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -1534,20 +1524,25 @@ ipw_watchdog(struct ifnet *ifp)
|
||||
struct ipw_softc *sc = ifp->if_softc;
|
||||
struct ieee80211com *ic = &sc->sc_ic;
|
||||
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
ifp->if_timer = 0;
|
||||
|
||||
if (sc->sc_tx_timer > 0) {
|
||||
if (--sc->sc_tx_timer == 0) {
|
||||
if_printf(ifp, "device timeout\n");
|
||||
ifp->if_oerrors++;
|
||||
ifp->if_flags &= ~IFF_UP;
|
||||
ipw_stop(sc);
|
||||
taskqueue_enqueue_fast(taskqueue_fast,
|
||||
&sc->sc_init_task);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
return;
|
||||
}
|
||||
ifp->if_timer = 1;
|
||||
}
|
||||
|
||||
ieee80211_watchdog(ic);
|
||||
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -1555,10 +1550,9 @@ ipw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
||||
{
|
||||
struct ipw_softc *sc = ifp->if_softc;
|
||||
struct ieee80211com *ic = &sc->sc_ic;
|
||||
struct ifreq *ifr;
|
||||
int error = 0;
|
||||
|
||||
IPW_LOCK(sc);
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
switch (cmd) {
|
||||
case SIOCSIFFLAGS:
|
||||
@ -1571,25 +1565,6 @@ ipw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
||||
}
|
||||
break;
|
||||
|
||||
case SIOCSLOADFW:
|
||||
/* only super-user can do that! */
|
||||
if ((error = suser(curthread)) != 0)
|
||||
break;
|
||||
|
||||
ifr = (struct ifreq *)data;
|
||||
error = ipw_cache_firmware(sc, ifr->ifr_data);
|
||||
break;
|
||||
|
||||
case SIOCSKILLFW:
|
||||
/* only super-user can do that! */
|
||||
if ((error = suser(curthread)) != 0)
|
||||
break;
|
||||
|
||||
ifp->if_flags &= ~IFF_UP;
|
||||
ipw_stop(sc);
|
||||
ipw_free_firmware(sc);
|
||||
break;
|
||||
|
||||
default:
|
||||
error = ieee80211_ioctl(ic, cmd, data);
|
||||
}
|
||||
@ -1601,7 +1576,7 @@ ipw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
||||
error = 0;
|
||||
}
|
||||
|
||||
IPW_UNLOCK(sc);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
|
||||
return error;
|
||||
}
|
||||
@ -1609,6 +1584,7 @@ ipw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
||||
static void
|
||||
ipw_stop_master(struct ipw_softc *sc)
|
||||
{
|
||||
uint32_t tmp;
|
||||
int ntries;
|
||||
|
||||
/* disable interrupts */
|
||||
@ -1623,8 +1599,8 @@ ipw_stop_master(struct ipw_softc *sc)
|
||||
if (ntries == 50)
|
||||
device_printf(sc->sc_dev, "timeout waiting for master\n");
|
||||
|
||||
CSR_WRITE_4(sc, IPW_CSR_RST, CSR_READ_4(sc, IPW_CSR_RST) |
|
||||
IPW_RST_PRINCETON_RESET);
|
||||
tmp = CSR_READ_4(sc, IPW_CSR_RST);
|
||||
CSR_WRITE_4(sc, IPW_CSR_RST, tmp | IPW_RST_PRINCETON_RESET);
|
||||
|
||||
sc->flags &= ~IPW_FLAG_FW_INITED;
|
||||
}
|
||||
@ -1632,13 +1608,14 @@ ipw_stop_master(struct ipw_softc *sc)
|
||||
static int
|
||||
ipw_reset(struct ipw_softc *sc)
|
||||
{
|
||||
uint32_t tmp;
|
||||
int ntries;
|
||||
|
||||
ipw_stop_master(sc);
|
||||
|
||||
/* move adapter to D0 state */
|
||||
CSR_WRITE_4(sc, IPW_CSR_CTL, CSR_READ_4(sc, IPW_CSR_CTL) |
|
||||
IPW_CTL_INIT);
|
||||
tmp = CSR_READ_4(sc, IPW_CSR_CTL);
|
||||
CSR_WRITE_4(sc, IPW_CSR_CTL, tmp | IPW_CTL_INIT);
|
||||
|
||||
/* wait for clock stabilization */
|
||||
for (ntries = 0; ntries < 1000; ntries++) {
|
||||
@ -1649,13 +1626,13 @@ ipw_reset(struct ipw_softc *sc)
|
||||
if (ntries == 1000)
|
||||
return EIO;
|
||||
|
||||
CSR_WRITE_4(sc, IPW_CSR_RST, CSR_READ_4(sc, IPW_CSR_RST) |
|
||||
IPW_RST_SW_RESET);
|
||||
tmp = CSR_READ_4(sc, IPW_CSR_RST);
|
||||
CSR_WRITE_4(sc, IPW_CSR_RST, tmp | IPW_RST_SW_RESET);
|
||||
|
||||
DELAY(10);
|
||||
|
||||
CSR_WRITE_4(sc, IPW_CSR_CTL, CSR_READ_4(sc, IPW_CSR_CTL) |
|
||||
IPW_CTL_INIT);
|
||||
tmp = CSR_READ_4(sc, IPW_CSR_CTL);
|
||||
CSR_WRITE_4(sc, IPW_CSR_CTL, tmp | IPW_CTL_INIT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1664,7 +1641,7 @@ ipw_reset(struct ipw_softc *sc)
|
||||
* Upload the microcode to the device.
|
||||
*/
|
||||
static int
|
||||
ipw_load_ucode(struct ipw_softc *sc, u_char *uc, int size)
|
||||
ipw_load_ucode(struct ipw_softc *sc, const char *uc, int size)
|
||||
{
|
||||
int ntries;
|
||||
|
||||
@ -1716,10 +1693,10 @@ ipw_load_ucode(struct ipw_softc *sc, u_char *uc, int size)
|
||||
#define GETLE32(p) ((p)[0] | (p)[1] << 8 | (p)[2] << 16 | (p)[3] << 24)
|
||||
#define GETLE16(p) ((p)[0] | (p)[1] << 8)
|
||||
static int
|
||||
ipw_load_firmware(struct ipw_softc *sc, u_char *fw, int size)
|
||||
ipw_load_firmware(struct ipw_softc *sc, const char *fw, int size)
|
||||
{
|
||||
u_char *p, *end;
|
||||
uint32_t dst;
|
||||
const uint8_t *p, *end;
|
||||
uint32_t tmp, dst;
|
||||
uint16_t len;
|
||||
int error;
|
||||
|
||||
@ -1742,8 +1719,8 @@ ipw_load_firmware(struct ipw_softc *sc, u_char *fw, int size)
|
||||
/* kick the firmware */
|
||||
CSR_WRITE_4(sc, IPW_CSR_RST, 0);
|
||||
|
||||
CSR_WRITE_4(sc, IPW_CSR_CTL, CSR_READ_4(sc, IPW_CSR_CTL) |
|
||||
IPW_CTL_ALLOW_STANDBY);
|
||||
tmp = CSR_READ_4(sc, IPW_CSR_CTL);
|
||||
CSR_WRITE_4(sc, IPW_CSR_CTL, tmp | IPW_CTL_ALLOW_STANDBY);
|
||||
|
||||
/* wait at most one second for firmware initialization to complete */
|
||||
if ((error = msleep(sc, &sc->sc_mtx, 0, "ipwinit", hz)) != 0) {
|
||||
@ -1752,82 +1729,13 @@ ipw_load_firmware(struct ipw_softc *sc, u_char *fw, int size)
|
||||
return error;
|
||||
}
|
||||
|
||||
CSR_WRITE_4(sc, IPW_CSR_IO, CSR_READ_4(sc, IPW_CSR_IO) |
|
||||
IPW_IO_GPIO1_MASK | IPW_IO_GPIO3_MASK);
|
||||
tmp = CSR_READ_4(sc, IPW_CSR_IO);
|
||||
CSR_WRITE_4(sc, IPW_CSR_IO, tmp | IPW_IO_GPIO1_MASK |
|
||||
IPW_IO_GPIO3_MASK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Store firmware into kernel memory so we can download it when we need to,
|
||||
* e.g when the adapter wakes up from suspend mode.
|
||||
*/
|
||||
static int
|
||||
ipw_cache_firmware(struct ipw_softc *sc, void *data)
|
||||
{
|
||||
struct ipw_firmware *fw = &sc->fw;
|
||||
struct ipw_firmware_hdr hdr;
|
||||
u_char *p = data;
|
||||
int error;
|
||||
|
||||
ipw_free_firmware(sc);
|
||||
|
||||
IPW_UNLOCK(sc);
|
||||
|
||||
if ((error = copyin(data, &hdr, sizeof hdr)) != 0)
|
||||
goto fail1;
|
||||
|
||||
fw->main_size = le32toh(hdr.main_size);
|
||||
fw->ucode_size = le32toh(hdr.ucode_size);
|
||||
p += sizeof hdr;
|
||||
|
||||
fw->main = malloc(fw->main_size, M_DEVBUF, M_NOWAIT);
|
||||
if (fw->main == NULL) {
|
||||
error = ENOMEM;
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
fw->ucode = malloc(fw->ucode_size, M_DEVBUF, M_NOWAIT);
|
||||
if (fw->ucode == NULL) {
|
||||
error = ENOMEM;
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
if ((error = copyin(p, fw->main, fw->main_size)) != 0)
|
||||
goto fail3;
|
||||
|
||||
p += fw->main_size;
|
||||
if ((error = copyin(p, fw->ucode, fw->ucode_size)) != 0)
|
||||
goto fail3;
|
||||
|
||||
DPRINTF(("Firmware cached: main %u, ucode %u\n", fw->main_size,
|
||||
fw->ucode_size));
|
||||
|
||||
IPW_LOCK(sc);
|
||||
|
||||
sc->flags |= IPW_FLAG_FW_CACHED;
|
||||
|
||||
return 0;
|
||||
|
||||
fail3: free(fw->ucode, M_DEVBUF);
|
||||
fail2: free(fw->main, M_DEVBUF);
|
||||
fail1: IPW_LOCK(sc);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void
|
||||
ipw_free_firmware(struct ipw_softc *sc)
|
||||
{
|
||||
if (!(sc->flags & IPW_FLAG_FW_CACHED))
|
||||
return;
|
||||
|
||||
free(sc->fw.main, M_DEVBUF);
|
||||
free(sc->fw.ucode, M_DEVBUF);
|
||||
|
||||
sc->flags &= ~IPW_FLAG_FW_CACHED;
|
||||
}
|
||||
|
||||
static int
|
||||
ipw_config(struct ipw_softc *sc)
|
||||
{
|
||||
@ -1846,12 +1754,10 @@ ipw_config(struct ipw_softc *sc)
|
||||
case IEEE80211_M_HOSTAP:
|
||||
data = htole32(IPW_MODE_BSS);
|
||||
break;
|
||||
|
||||
case IEEE80211_M_IBSS:
|
||||
case IEEE80211_M_AHDEMO:
|
||||
data = htole32(IPW_MODE_IBSS);
|
||||
break;
|
||||
|
||||
case IEEE80211_M_MONITOR:
|
||||
data = htole32(IPW_MODE_MONITOR);
|
||||
break;
|
||||
@ -1863,7 +1769,7 @@ ipw_config(struct ipw_softc *sc)
|
||||
|
||||
if (ic->ic_opmode == IEEE80211_M_IBSS ||
|
||||
ic->ic_opmode == IEEE80211_M_MONITOR) {
|
||||
data = htole32(ieee80211_chan2ieee(ic, ic->ic_ibss_chan));
|
||||
data = htole32(ieee80211_chan2ieee(ic, ic->ic_curchan));
|
||||
DPRINTF(("Setting channel to %u\n", le32toh(data)));
|
||||
error = ipw_cmd(sc, IPW_CMD_SET_CHANNEL, &data, sizeof data);
|
||||
if (error != 0)
|
||||
@ -2035,33 +1941,102 @@ ipw_config(struct ipw_softc *sc)
|
||||
return ipw_cmd(sc, IPW_CMD_ENABLE, NULL, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handler for sc_init_task. This is a simple wrapper around ipw_init().
|
||||
* It is called on firmware panics or on watchdog timeouts.
|
||||
*/
|
||||
static void
|
||||
ipw_init_task(void *context, int pending)
|
||||
{
|
||||
ipw_init(context);
|
||||
}
|
||||
|
||||
static void
|
||||
ipw_init(void *priv)
|
||||
{
|
||||
struct ipw_softc *sc = priv;
|
||||
struct ieee80211com *ic = &sc->sc_ic;
|
||||
struct ifnet *ifp = ic->ic_ifp;
|
||||
struct ipw_firmware *fw = &sc->fw;
|
||||
struct firmware *fp;
|
||||
const struct ipw_firmware_hdr *hdr;
|
||||
const char *imagename, *fw;
|
||||
int owned;
|
||||
|
||||
/* exit immediately if firmware has not been ioctl'd */
|
||||
if (!(sc->flags & IPW_FLAG_FW_CACHED)) {
|
||||
if (!(sc->flags & IPW_FLAG_FW_WARNED))
|
||||
device_printf(sc->sc_dev, "Please load firmware\n");
|
||||
sc->flags |= IPW_FLAG_FW_WARNED;
|
||||
ifp->if_flags &= ~IFF_UP;
|
||||
/*
|
||||
* ipw_init() is exposed through ifp->if_init so it might be called
|
||||
* without the driver's lock held. Since msleep() doesn't like being
|
||||
* called on a recursed mutex, we acquire the driver's lock only if
|
||||
* we're not already holding it.
|
||||
*/
|
||||
if (!(owned = mtx_owned(&sc->sc_mtx)))
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
/*
|
||||
* Avoid re-entrant calls. We need to release the mutex in ipw_init()
|
||||
* when loading the firmware and we don't want to be called during this
|
||||
* operation.
|
||||
*/
|
||||
if (sc->flags & IPW_FLAG_INIT_LOCKED) {
|
||||
if (!owned)
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
return;
|
||||
}
|
||||
sc->flags |= IPW_FLAG_INIT_LOCKED;
|
||||
|
||||
ipw_stop(sc);
|
||||
|
||||
if (ipw_reset(sc) != 0) {
|
||||
device_printf(sc->sc_dev, "could not reset adapter\n");
|
||||
goto fail;
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
if (ipw_load_ucode(sc, fw->ucode, fw->ucode_size) != 0) {
|
||||
switch (ic->ic_opmode) {
|
||||
case IEEE80211_M_STA:
|
||||
imagename = "ipw_bss";
|
||||
break;
|
||||
case IEEE80211_M_IBSS:
|
||||
imagename = "ipw_ibss";
|
||||
break;
|
||||
case IEEE80211_M_MONITOR:
|
||||
imagename = "ipw_monitor";
|
||||
break;
|
||||
default:
|
||||
imagename = NULL; /* should not get there */
|
||||
}
|
||||
|
||||
/*
|
||||
* Load firmware image using the firmware(9) subsystem. We need to
|
||||
* release the driver's lock first.
|
||||
*/
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
fp = firmware_get(imagename);
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
if (fp == NULL) {
|
||||
device_printf(sc->sc_dev,
|
||||
"could not load firmware image '%s'\n", imagename);
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
if (fp->datasize < sizeof *hdr) {
|
||||
device_printf(sc->sc_dev,
|
||||
"firmware image too short %zu\n", fp->datasize);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
hdr = (const struct ipw_firmware_hdr *)fp->data;
|
||||
|
||||
if (fp->datasize < sizeof *hdr + le32toh(hdr->mainsz) +
|
||||
le32toh(hdr->ucodesz)) {
|
||||
device_printf(sc->sc_dev,
|
||||
"firmware image too short %zu\n", fp->datasize);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
fw = (const char *)fp->data + sizeof *hdr + le32toh(hdr->mainsz);
|
||||
if (ipw_load_ucode(sc, fw, le32toh(hdr->ucodesz)) != 0) {
|
||||
device_printf(sc->sc_dev, "could not load microcode\n");
|
||||
goto fail;
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
ipw_stop_master(sc);
|
||||
@ -2086,11 +2061,13 @@ ipw_init(void *priv)
|
||||
|
||||
CSR_WRITE_4(sc, IPW_CSR_STATUS_BASE, sc->status_phys);
|
||||
|
||||
if (ipw_load_firmware(sc, fw->main, fw->main_size) != 0) {
|
||||
fw = (const char *)fp->data + sizeof *hdr;
|
||||
if (ipw_load_firmware(sc, fw, le32toh(hdr->mainsz)) != 0) {
|
||||
device_printf(sc->sc_dev, "could not load firmware\n");
|
||||
goto fail;
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
firmware_put(fp, FIRMWARE_UNLOAD);
|
||||
sc->flags |= IPW_FLAG_FW_INITED;
|
||||
|
||||
/* retrieve information tables base addresses */
|
||||
@ -2101,16 +2078,25 @@ ipw_init(void *priv)
|
||||
|
||||
if (ipw_config(sc) != 0) {
|
||||
device_printf(sc->sc_dev, "device configuration failed\n");
|
||||
goto fail;
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
||||
ifp->if_drv_flags |= IFF_DRV_RUNNING;
|
||||
|
||||
sc->flags &=~ IPW_FLAG_INIT_LOCKED;
|
||||
|
||||
if (!owned)
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
|
||||
return;
|
||||
|
||||
fail: ifp->if_flags &= ~IFF_UP;
|
||||
fail2: firmware_put(fp, FIRMWARE_UNLOAD);
|
||||
fail1: ifp->if_flags &= ~IFF_UP;
|
||||
ipw_stop(sc);
|
||||
sc->flags &=~ IPW_FLAG_INIT_LOCKED;
|
||||
if (!owned)
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -2121,6 +2107,10 @@ ipw_stop(void *priv)
|
||||
struct ifnet *ifp = ic->ic_ifp;
|
||||
int i;
|
||||
|
||||
mtx_lock(&sc->sc_mtx);
|
||||
|
||||
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
|
||||
|
||||
ipw_stop_master(sc);
|
||||
|
||||
CSR_WRITE_4(sc, IPW_CSR_RST, IPW_RST_SW_RESET);
|
||||
@ -2135,7 +2125,7 @@ ipw_stop(void *priv)
|
||||
ifp->if_timer = 0;
|
||||
ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
|
||||
|
||||
ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
|
||||
mtx_unlock(&sc->sc_mtx);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -2219,7 +2209,7 @@ ipw_read_mem_1(struct ipw_softc *sc, bus_size_t offset, uint8_t *datap,
|
||||
}
|
||||
|
||||
static void
|
||||
ipw_write_mem_1(struct ipw_softc *sc, bus_size_t offset, uint8_t *datap,
|
||||
ipw_write_mem_1(struct ipw_softc *sc, bus_size_t offset, const uint8_t *datap,
|
||||
bus_size_t count)
|
||||
{
|
||||
for (; count > 0; offset++, datap++, count--) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* $FreeBSD$ */
|
||||
|
||||
/*-
|
||||
* Copyright (c) 2004, 2005
|
||||
* Copyright (c) 2004-2006
|
||||
* Damien Bergamini <damien.bergamini@free.fr>. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -115,59 +115,59 @@
|
||||
|
||||
/* firmware binary image header */
|
||||
struct ipw_firmware_hdr {
|
||||
u_int32_t version;
|
||||
u_int32_t main_size; /* firmware size */
|
||||
u_int32_t ucode_size; /* microcode size */
|
||||
uint32_t version;
|
||||
uint32_t mainsz;
|
||||
uint32_t ucodesz;
|
||||
} __packed;
|
||||
|
||||
/* buffer descriptor */
|
||||
struct ipw_bd {
|
||||
u_int32_t physaddr;
|
||||
u_int32_t len;
|
||||
u_int8_t flags;
|
||||
uint32_t physaddr;
|
||||
uint32_t len;
|
||||
uint8_t flags;
|
||||
#define IPW_BD_FLAG_TX_FRAME_802_3 0x00
|
||||
#define IPW_BD_FLAG_TX_NOT_LAST_FRAGMENT 0x01
|
||||
#define IPW_BD_FLAG_TX_FRAME_COMMAND 0x02
|
||||
#define IPW_BD_FLAG_TX_FRAME_802_11 0x04
|
||||
#define IPW_BD_FLAG_TX_LAST_FRAGMENT 0x08
|
||||
u_int8_t nfrag; /* number of fragments */
|
||||
u_int8_t reserved[6];
|
||||
uint8_t nfrag; /* number of fragments */
|
||||
uint8_t reserved[6];
|
||||
} __packed;
|
||||
|
||||
/* status */
|
||||
struct ipw_status {
|
||||
u_int32_t len;
|
||||
u_int16_t code;
|
||||
uint32_t len;
|
||||
uint16_t code;
|
||||
#define IPW_STATUS_CODE_COMMAND 0
|
||||
#define IPW_STATUS_CODE_NEWSTATE 1
|
||||
#define IPW_STATUS_CODE_DATA_802_11 2
|
||||
#define IPW_STATUS_CODE_DATA_802_3 3
|
||||
#define IPW_STATUS_CODE_NOTIFICATION 4
|
||||
u_int8_t flags;
|
||||
uint8_t flags;
|
||||
#define IPW_STATUS_FLAG_DECRYPTED 0x01
|
||||
#define IPW_STATUS_FLAG_WEP_ENCRYPTED 0x02
|
||||
u_int8_t rssi; /* received signal strength indicator */
|
||||
uint8_t rssi; /* received signal strength indicator */
|
||||
} __packed;
|
||||
|
||||
/* data header */
|
||||
struct ipw_hdr {
|
||||
u_int32_t type;
|
||||
uint32_t type;
|
||||
#define IPW_HDR_TYPE_SEND 33
|
||||
u_int32_t subtype;
|
||||
u_int8_t encrypted;
|
||||
u_int8_t encrypt;
|
||||
u_int8_t keyidx;
|
||||
u_int8_t keysz;
|
||||
u_int8_t key[IEEE80211_KEYBUF_SIZE];
|
||||
u_int8_t reserved[10];
|
||||
u_int8_t src_addr[IEEE80211_ADDR_LEN];
|
||||
u_int8_t dst_addr[IEEE80211_ADDR_LEN];
|
||||
u_int16_t fragmentsz;
|
||||
uint32_t subtype;
|
||||
uint8_t encrypted;
|
||||
uint8_t encrypt;
|
||||
uint8_t keyidx;
|
||||
uint8_t keysz;
|
||||
uint8_t key[IEEE80211_KEYBUF_SIZE];
|
||||
uint8_t reserved[10];
|
||||
uint8_t src_addr[IEEE80211_ADDR_LEN];
|
||||
uint8_t dst_addr[IEEE80211_ADDR_LEN];
|
||||
uint16_t fragmentsz;
|
||||
} __packed;
|
||||
|
||||
/* command */
|
||||
struct ipw_cmd {
|
||||
u_int32_t type;
|
||||
uint32_t type;
|
||||
#define IPW_CMD_ENABLE 2
|
||||
#define IPW_CMD_SET_CONFIGURATION 6
|
||||
#define IPW_CMD_SET_ESSID 8
|
||||
@ -194,12 +194,12 @@ struct ipw_cmd {
|
||||
#define IPW_CMD_DISABLE_PHY 61
|
||||
#define IPW_CMD_SET_SECURITY_INFORMATION 67
|
||||
#define IPW_CMD_SET_WPA_IE 69
|
||||
u_int32_t subtype;
|
||||
u_int32_t seq;
|
||||
u_int32_t len;
|
||||
u_int8_t data[400];
|
||||
u_int32_t status;
|
||||
u_int8_t reserved[68];
|
||||
uint32_t subtype;
|
||||
uint32_t seq;
|
||||
uint32_t len;
|
||||
uint8_t data[400];
|
||||
uint32_t status;
|
||||
uint8_t reserved[68];
|
||||
} __packed;
|
||||
|
||||
/* possible values for command IPW_CMD_SET_POWER_MODE */
|
||||
@ -216,74 +216,74 @@ struct ipw_cmd {
|
||||
|
||||
/* structure for command IPW_CMD_SET_WEP_KEY */
|
||||
struct ipw_wep_key {
|
||||
u_int8_t idx;
|
||||
u_int8_t len;
|
||||
u_int8_t key[13];
|
||||
uint8_t idx;
|
||||
uint8_t len;
|
||||
uint8_t key[13];
|
||||
} __packed;
|
||||
|
||||
/* structure for command IPW_CMD_SET_SECURITY_INFORMATION */
|
||||
struct ipw_security {
|
||||
u_int32_t ciphers;
|
||||
uint32_t ciphers;
|
||||
#define IPW_CIPHER_NONE 0x00000001
|
||||
#define IPW_CIPHER_WEP40 0x00000002
|
||||
#define IPW_CIPHER_TKIP 0x00000004
|
||||
#define IPW_CIPHER_CCMP 0x00000010
|
||||
#define IPW_CIPHER_WEP104 0x00000020
|
||||
#define IPW_CIPHER_CKIP 0x00000040
|
||||
u_int16_t reserved1;
|
||||
u_int8_t authmode;
|
||||
uint16_t reserved1;
|
||||
uint8_t authmode;
|
||||
#define IPW_AUTH_OPEN 0
|
||||
#define IPW_AUTH_SHARED 1
|
||||
u_int16_t reserved2;
|
||||
uint16_t reserved2;
|
||||
} __packed;
|
||||
|
||||
/* structure for command IPW_CMD_SET_SCAN_OPTIONS */
|
||||
struct ipw_scan_options {
|
||||
u_int32_t flags;
|
||||
uint32_t flags;
|
||||
#define IPW_SCAN_DO_NOT_ASSOCIATE 0x00000001
|
||||
#define IPW_SCAN_PASSIVE 0x00000008
|
||||
u_int32_t channels;
|
||||
uint32_t channels;
|
||||
} __packed;
|
||||
|
||||
/* structure for command IPW_CMD_SET_CONFIGURATION */
|
||||
struct ipw_configuration {
|
||||
u_int32_t flags;
|
||||
uint32_t flags;
|
||||
#define IPW_CFG_PROMISCUOUS 0x00000004
|
||||
#define IPW_CFG_PREAMBLE_AUTO 0x00000010
|
||||
#define IPW_CFG_IBSS_AUTO_START 0x00000020
|
||||
#define IPW_CFG_802_1x_ENABLE 0x00004000
|
||||
#define IPW_CFG_BSS_MASK 0x00008000
|
||||
#define IPW_CFG_IBSS_MASK 0x00010000
|
||||
u_int32_t bss_chan;
|
||||
u_int32_t ibss_chan;
|
||||
uint32_t bss_chan;
|
||||
uint32_t ibss_chan;
|
||||
} __packed;
|
||||
|
||||
/* structure for command IPW_CMD_SET_WPA_IE */
|
||||
struct ipw_wpa_ie {
|
||||
u_int16_t mask;
|
||||
u_int16_t capinfo;
|
||||
u_int16_t lintval;
|
||||
u_int8_t bssid[IEEE80211_ADDR_LEN];
|
||||
u_int32_t len;
|
||||
uint16_t mask;
|
||||
uint16_t capinfo;
|
||||
uint16_t lintval;
|
||||
uint8_t bssid[IEEE80211_ADDR_LEN];
|
||||
uint32_t len;
|
||||
struct ieee80211_ie_wpa ie;
|
||||
} __packed;
|
||||
|
||||
/* element in AP table */
|
||||
struct ipw_node {
|
||||
u_int32_t reserved1[2];
|
||||
u_int8_t bssid[IEEE80211_ADDR_LEN];
|
||||
u_int8_t chan;
|
||||
u_int8_t rates;
|
||||
u_int16_t reserved2;
|
||||
u_int16_t capinfo;
|
||||
u_int16_t reserved3;
|
||||
u_int16_t intval;
|
||||
u_int8_t reserved4[28];
|
||||
u_int8_t essid[IEEE80211_NWID_LEN];
|
||||
u_int16_t reserved5;
|
||||
u_int8_t esslen;
|
||||
u_int8_t reserved6[7];
|
||||
u_int8_t rssi;
|
||||
uint32_t reserved1[2];
|
||||
uint8_t bssid[IEEE80211_ADDR_LEN];
|
||||
uint8_t chan;
|
||||
uint8_t rates;
|
||||
uint16_t reserved2;
|
||||
uint16_t capinfo;
|
||||
uint16_t reserved3;
|
||||
uint16_t intval;
|
||||
uint8_t reserved4[28];
|
||||
uint8_t essid[IEEE80211_NWID_LEN];
|
||||
uint16_t reserved5;
|
||||
uint8_t esslen;
|
||||
uint8_t reserved6[7];
|
||||
uint8_t rssi;
|
||||
} __packed;
|
||||
|
||||
/* EEPROM = Electrically Erasable Programmable Read-Only Memory */
|
||||
@ -332,6 +332,14 @@ struct ipw_node {
|
||||
/*
|
||||
* indirect memory space access macros
|
||||
*/
|
||||
#define MEM_READ_1(sc, addr) \
|
||||
(CSR_WRITE_4((sc), IPW_CSR_INDIRECT_ADDR, (addr)), \
|
||||
CSR_READ_1((sc), IPW_CSR_INDIRECT_DATA))
|
||||
|
||||
#define MEM_READ_4(sc, addr) \
|
||||
(CSR_WRITE_4((sc), IPW_CSR_INDIRECT_ADDR, (addr)), \
|
||||
CSR_READ_4((sc), IPW_CSR_INDIRECT_DATA))
|
||||
|
||||
#define MEM_WRITE_1(sc, addr, val) do { \
|
||||
CSR_WRITE_4((sc), IPW_CSR_INDIRECT_ADDR, (addr)); \
|
||||
CSR_WRITE_1((sc), IPW_CSR_INDIRECT_DATA, (val)); \
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* $FreeBSD$ */
|
||||
|
||||
/*-
|
||||
* Copyright (c) 2004, 2005
|
||||
* Copyright (c) 2004-2006
|
||||
* Damien Bergamini <damien.bergamini@free.fr>. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -27,13 +27,6 @@
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
struct ipw_firmware {
|
||||
void *main;
|
||||
int main_size;
|
||||
void *ucode;
|
||||
int ucode_size;
|
||||
};
|
||||
|
||||
#define IPW_MAX_NSEG 1
|
||||
|
||||
struct ipw_soft_bd {
|
||||
@ -91,11 +84,11 @@ struct ipw_softc {
|
||||
device_t sc_dev;
|
||||
|
||||
struct mtx sc_mtx;
|
||||
struct task sc_init_task;
|
||||
|
||||
struct ipw_firmware fw;
|
||||
uint32_t flags;
|
||||
#define IPW_FLAG_FW_CACHED (1 << 0)
|
||||
#define IPW_FLAG_FW_INITED (1 << 1)
|
||||
#define IPW_FLAG_FW_INITED (1 << 0)
|
||||
#define IPW_FLAG_INIT_LOCKED (1 << 1)
|
||||
#define IPW_FLAG_HAS_RADIO_SWITCH (1 << 2)
|
||||
#define IPW_FLAG_FW_WARNED (1 << 3)
|
||||
|
||||
@ -166,9 +159,3 @@ struct ipw_softc {
|
||||
#define sc_txtap sc_txtapu.th
|
||||
int sc_txtap_len;
|
||||
};
|
||||
|
||||
#define SIOCSLOADFW _IOW('i', 137, struct ifreq)
|
||||
#define SIOCSKILLFW _IOW('i', 138, struct ifreq)
|
||||
|
||||
#define IPW_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
|
||||
#define IPW_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
|
||||
|
Loading…
Reference in New Issue
Block a user