brcmfmac: handle SDIO card removal

When removing the card the driver still tries to access registers
in the device. This patch adds another state for the bus that
indicates the device is no longer reachable. This avoids errors
accessing it while cleaning up the driver.

Reviewed-by: Franky Lin <frankyl@broadcom.com>
Reviewed-by: Hante Meuleman <meuleman@broadcom.com>
Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com>
Signed-off-by: Arend van Spriel <arend@broadcom.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Arend van Spriel 2014-01-13 22:20:29 +01:00 committed by John W. Linville
parent 2668b0b16c
commit bb350711ec
5 changed files with 101 additions and 86 deletions

View File

@ -287,6 +287,9 @@ static int brcmf_sdiod_regrw_helper(struct brcmf_sdio_dev *sdiodev, u32 addr,
s32 retry = 0; s32 retry = 0;
int ret; int ret;
if (sdiodev->bus_if->state == BRCMF_BUS_NOMEDIUM)
return -ENOMEDIUM;
/* /*
* figure out how to read the register based on address range * figure out how to read the register based on address range
* 0x00 ~ 0x7FF: function 0 CCCR and FBR * 0x00 ~ 0x7FF: function 0 CCCR and FBR
@ -306,9 +309,12 @@ static int brcmf_sdiod_regrw_helper(struct brcmf_sdio_dev *sdiodev, u32 addr,
usleep_range(1000, 2000); usleep_range(1000, 2000);
ret = brcmf_sdiod_request_data(sdiodev, func_num, addr, regsz, ret = brcmf_sdiod_request_data(sdiodev, func_num, addr, regsz,
data, write); data, write);
} while (ret != 0 && retry++ < SDIOH_API_ACCESS_RETRY_LIMIT); } while (ret != 0 && ret != -ENOMEDIUM &&
retry++ < SDIOH_API_ACCESS_RETRY_LIMIT);
if (ret != 0) if (ret == -ENOMEDIUM)
brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_NOMEDIUM);
else if (ret != 0)
brcmf_err("failed with %d\n", ret); brcmf_err("failed with %d\n", ret);
return ret; return ret;
@ -320,6 +326,9 @@ brcmf_sdiod_set_sbaddr_window(struct brcmf_sdio_dev *sdiodev, u32 address)
int err = 0, i; int err = 0, i;
u8 addr[3]; u8 addr[3];
if (sdiodev->bus_if->state == BRCMF_BUS_NOMEDIUM)
return -ENOMEDIUM;
addr[0] = (address >> 8) & SBSDIO_SBADDRLOW_MASK; addr[0] = (address >> 8) & SBSDIO_SBADDRLOW_MASK;
addr[1] = (address >> 16) & SBSDIO_SBADDRMID_MASK; addr[1] = (address >> 16) & SBSDIO_SBADDRMID_MASK;
addr[2] = (address >> 24) & SBSDIO_SBADDRHIGH_MASK; addr[2] = (address >> 24) & SBSDIO_SBADDRHIGH_MASK;
@ -429,6 +438,7 @@ static int brcmf_sdiod_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn,
bool write, u32 addr, struct sk_buff *pkt) bool write, u32 addr, struct sk_buff *pkt)
{ {
unsigned int req_sz; unsigned int req_sz;
int err;
brcmf_sdiod_pm_resume_wait(sdiodev, &sdiodev->request_buffer_wait); brcmf_sdiod_pm_resume_wait(sdiodev, &sdiodev->request_buffer_wait);
if (brcmf_sdiod_pm_resume_error(sdiodev)) if (brcmf_sdiod_pm_resume_error(sdiodev))
@ -439,18 +449,18 @@ static int brcmf_sdiod_buffrw(struct brcmf_sdio_dev *sdiodev, uint fn,
req_sz &= (uint)~3; req_sz &= (uint)~3;
if (write) if (write)
return sdio_memcpy_toio(sdiodev->func[fn], addr, err = sdio_memcpy_toio(sdiodev->func[fn], addr,
((u8 *)(pkt->data)), ((u8 *)(pkt->data)), req_sz);
req_sz);
else if (fn == 1) else if (fn == 1)
return sdio_memcpy_fromio(sdiodev->func[fn], err = sdio_memcpy_fromio(sdiodev->func[fn], ((u8 *)(pkt->data)),
((u8 *)(pkt->data)), addr, req_sz);
addr, req_sz);
else else
/* function 2 read is FIFO operation */ /* function 2 read is FIFO operation */
return sdio_readsb(sdiodev->func[fn], err = sdio_readsb(sdiodev->func[fn], ((u8 *)(pkt->data)), addr,
((u8 *)(pkt->data)), addr, req_sz);
req_sz); if (err == -ENOMEDIUM)
brcmf_bus_change_state(sdiodev->bus_if, BRCMF_BUS_NOMEDIUM);
return err;
} }
/** /**
@ -593,7 +603,11 @@ static int brcmf_sdiod_sglist_rw(struct brcmf_sdio_dev *sdiodev, uint fn,
mmc_wait_for_req(sdiodev->func[fn]->card->host, &mmc_req); mmc_wait_for_req(sdiodev->func[fn]->card->host, &mmc_req);
ret = mmc_cmd.error ? mmc_cmd.error : mmc_dat.error; ret = mmc_cmd.error ? mmc_cmd.error : mmc_dat.error;
if (ret != 0) { if (ret == -ENOMEDIUM) {
brcmf_bus_change_state(sdiodev->bus_if,
BRCMF_BUS_NOMEDIUM);
break;
} else if (ret != 0) {
brcmf_err("CMD53 sg block %s failed %d\n", brcmf_err("CMD53 sg block %s failed %d\n",
write ? "write" : "read", ret); write ? "write" : "read", ret);
ret = -EIO; ret = -EIO;
@ -852,8 +866,6 @@ int brcmf_sdiod_abort(struct brcmf_sdio_dev *sdiodev, uint fn)
static int brcmf_sdiod_remove(struct brcmf_sdio_dev *sdiodev) static int brcmf_sdiod_remove(struct brcmf_sdio_dev *sdiodev)
{ {
sdiodev->bus_if->state = BRCMF_BUS_DOWN;
if (sdiodev->bus) { if (sdiodev->bus) {
brcmf_sdio_remove(sdiodev->bus); brcmf_sdio_remove(sdiodev->bus);
sdiodev->bus = NULL; sdiodev->bus = NULL;

View File

@ -17,8 +17,12 @@
#ifndef _BRCMF_BUS_H_ #ifndef _BRCMF_BUS_H_
#define _BRCMF_BUS_H_ #define _BRCMF_BUS_H_
#include "dhd_dbg.h"
/* The level of bus communication with the dongle */ /* The level of bus communication with the dongle */
enum brcmf_bus_state { enum brcmf_bus_state {
BRCMF_BUS_UNKNOWN, /* Not determined yet */
BRCMF_BUS_NOMEDIUM, /* No medium access to dongle */
BRCMF_BUS_DOWN, /* Not ready for frame transfers */ BRCMF_BUS_DOWN, /* Not ready for frame transfers */
BRCMF_BUS_LOAD, /* Download access only (CPU reset) */ BRCMF_BUS_LOAD, /* Download access only (CPU reset) */
BRCMF_BUS_DATA /* Ready for frame transfers */ BRCMF_BUS_DATA /* Ready for frame transfers */
@ -144,6 +148,23 @@ struct pktq *brcmf_bus_gettxq(struct brcmf_bus *bus)
return bus->ops->gettxq(bus->dev); return bus->ops->gettxq(bus->dev);
} }
static inline bool brcmf_bus_ready(struct brcmf_bus *bus)
{
return bus->state == BRCMF_BUS_LOAD || bus->state == BRCMF_BUS_DATA;
}
static inline void brcmf_bus_change_state(struct brcmf_bus *bus,
enum brcmf_bus_state new_state)
{
/* NOMEDIUM is permanent */
if (bus->state == BRCMF_BUS_NOMEDIUM)
return;
brcmf_dbg(TRACE, "%d -> %d\n", bus->state, new_state);
bus->state = new_state;
}
/* /*
* interface functions from common layer * interface functions from common layer
*/ */

View File

@ -934,7 +934,7 @@ int brcmf_bus_start(struct device *dev)
p2p_ifp = NULL; p2p_ifp = NULL;
/* signal bus ready */ /* signal bus ready */
bus_if->state = BRCMF_BUS_DATA; brcmf_bus_change_state(bus_if, BRCMF_BUS_DATA);
/* Bus is ready, do any initialization */ /* Bus is ready, do any initialization */
ret = brcmf_c_preinit_dcmds(ifp); ret = brcmf_c_preinit_dcmds(ifp);
@ -1029,6 +1029,8 @@ void brcmf_detach(struct device *dev)
/* stop firmware event handling */ /* stop firmware event handling */
brcmf_fweh_detach(drvr); brcmf_fweh_detach(drvr);
brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN);
/* make sure primary interface removed last */ /* make sure primary interface removed last */
for (i = BRCMF_MAX_IFS-1; i > -1; i--) for (i = BRCMF_MAX_IFS-1; i > -1; i--)
if (drvr->iflist[i]) { if (drvr->iflist[i]) {

View File

@ -1082,10 +1082,6 @@ static void brcmf_sdio_rxfail(struct brcmf_sdio *bus, bool abort, bool rtx)
/* Clear partial in any case */ /* Clear partial in any case */
bus->cur_read.len = 0; bus->cur_read.len = 0;
/* If we can't reach the device, signal failure */
if (err)
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
} }
/* return total length of buffer chain */ /* return total length of buffer chain */
@ -1682,8 +1678,7 @@ static uint brcmf_sdio_readframes(struct brcmf_sdio *bus, uint maxframes)
bus->rxpending = true; bus->rxpending = true;
for (rd->seq_num = bus->rx_seq, rxleft = maxframes; for (rd->seq_num = bus->rx_seq, rxleft = maxframes;
!bus->rxskip && rxleft && !bus->rxskip && rxleft && brcmf_bus_ready(bus->sdiodev->bus_if);
bus->sdiodev->bus_if->state != BRCMF_BUS_DOWN;
rd->seq_num++, rxleft--) { rd->seq_num++, rxleft--) {
/* Handle glomming separately */ /* Handle glomming separately */
@ -2232,39 +2227,37 @@ static void brcmf_sdio_bus_stop(struct device *dev)
bus->watchdog_tsk = NULL; bus->watchdog_tsk = NULL;
} }
sdio_claim_host(bus->sdiodev->func[1]); if (bus_if->state == BRCMF_BUS_DOWN) {
sdio_claim_host(sdiodev->func[1]);
/* Enable clock for device interrupts */ /* Enable clock for device interrupts */
brcmf_sdio_bus_sleep(bus, false, false); brcmf_sdio_bus_sleep(bus, false, false);
/* Disable and clear interrupts at the chip level also */ /* Disable and clear interrupts at the chip level also */
w_sdreg32(bus, 0, offsetof(struct sdpcmd_regs, hostintmask)); w_sdreg32(bus, 0, offsetof(struct sdpcmd_regs, hostintmask));
local_hostintmask = bus->hostintmask; local_hostintmask = bus->hostintmask;
bus->hostintmask = 0; bus->hostintmask = 0;
/* Change our idea of bus state */ /* Force backplane clocks to assure F2 interrupt propagates */
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN; saveclk = brcmf_sdiod_regrb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
&err);
if (!err)
brcmf_sdiod_regwb(sdiodev, SBSDIO_FUNC1_CHIPCLKCSR,
(saveclk | SBSDIO_FORCE_HT), &err);
if (err)
brcmf_err("Failed to force clock for F2: err %d\n",
err);
/* Force clocks on backplane to be sure F2 interrupt propagates */ /* Turn off the bus (F2), free any pending packets */
saveclk = brcmf_sdiod_regrb(bus->sdiodev, brcmf_dbg(INTR, "disable SDIO interrupts\n");
SBSDIO_FUNC1_CHIPCLKCSR, &err); sdio_disable_func(sdiodev->func[SDIO_FUNC_2]);
if (!err) {
brcmf_sdiod_regwb(bus->sdiodev, SBSDIO_FUNC1_CHIPCLKCSR, /* Clear any pending interrupts now that F2 is disabled */
(saveclk | SBSDIO_FORCE_HT), &err); w_sdreg32(bus, local_hostintmask,
offsetof(struct sdpcmd_regs, intstatus));
sdio_release_host(sdiodev->func[1]);
} }
if (err)
brcmf_err("Failed to force clock for F2: err %d\n", err);
/* Turn off the bus (F2), free any pending packets */
brcmf_dbg(INTR, "disable SDIO interrupts\n");
sdio_disable_func(bus->sdiodev->func[SDIO_FUNC_2]);
/* Clear any pending interrupts now that F2 is disabled */
w_sdreg32(bus, local_hostintmask,
offsetof(struct sdpcmd_regs, intstatus));
sdio_release_host(bus->sdiodev->func[1]);
/* Clear the data packet queues */ /* Clear the data packet queues */
brcmu_pktq_flush(&bus->txq, true, NULL, NULL); brcmu_pktq_flush(&bus->txq, true, NULL, NULL);
@ -2354,20 +2347,11 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus)
/* Check for inconsistent device control */ /* Check for inconsistent device control */
devctl = brcmf_sdiod_regrb(bus->sdiodev, devctl = brcmf_sdiod_regrb(bus->sdiodev,
SBSDIO_DEVICE_CTL, &err); SBSDIO_DEVICE_CTL, &err);
if (err) {
brcmf_err("error reading DEVCTL: %d\n", err);
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
}
#endif /* DEBUG */ #endif /* DEBUG */
/* Read CSR, if clock on switch to AVAIL, else ignore */ /* Read CSR, if clock on switch to AVAIL, else ignore */
clkctl = brcmf_sdiod_regrb(bus->sdiodev, clkctl = brcmf_sdiod_regrb(bus->sdiodev,
SBSDIO_FUNC1_CHIPCLKCSR, &err); SBSDIO_FUNC1_CHIPCLKCSR, &err);
if (err) {
brcmf_err("error reading CSR: %d\n",
err);
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
}
brcmf_dbg(SDIO, "DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n", brcmf_dbg(SDIO, "DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n",
devctl, clkctl); devctl, clkctl);
@ -2375,19 +2359,9 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus)
if (SBSDIO_HTAV(clkctl)) { if (SBSDIO_HTAV(clkctl)) {
devctl = brcmf_sdiod_regrb(bus->sdiodev, devctl = brcmf_sdiod_regrb(bus->sdiodev,
SBSDIO_DEVICE_CTL, &err); SBSDIO_DEVICE_CTL, &err);
if (err) {
brcmf_err("error reading DEVCTL: %d\n",
err);
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
}
devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY; devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
brcmf_sdiod_regwb(bus->sdiodev, SBSDIO_DEVICE_CTL, brcmf_sdiod_regwb(bus->sdiodev, SBSDIO_DEVICE_CTL,
devctl, &err); devctl, &err);
if (err) {
brcmf_err("error writing DEVCTL: %d\n",
err);
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
}
bus->clkstate = CLK_AVAIL; bus->clkstate = CLK_AVAIL;
} }
} }
@ -2522,9 +2496,8 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus)
txlimit -= framecnt; txlimit -= framecnt;
} }
if ((bus->sdiodev->bus_if->state == BRCMF_BUS_DOWN) || (err != 0)) { if (!brcmf_bus_ready(bus->sdiodev->bus_if) || (err != 0)) {
brcmf_err("failed backplane access over SDIO, halting operation\n"); brcmf_err("failed backplane access over SDIO, halting operation\n");
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
atomic_set(&bus->intstatus, 0); atomic_set(&bus->intstatus, 0);
} else if (atomic_read(&bus->intstatus) || } else if (atomic_read(&bus->intstatus) ||
atomic_read(&bus->ipend) > 0 || atomic_read(&bus->ipend) > 0 ||
@ -3356,7 +3329,7 @@ static int brcmf_sdio_download_firmware(struct brcmf_sdio *bus)
} }
/* Allow HT Clock now that the ARM is running. */ /* Allow HT Clock now that the ARM is running. */
bus->sdiodev->bus_if->state = BRCMF_BUS_LOAD; brcmf_bus_change_state(bus->sdiodev->bus_if, BRCMF_BUS_LOAD);
bcmerror = 0; bcmerror = 0;
err: err:
@ -3633,7 +3606,7 @@ void brcmf_sdio_isr(struct brcmf_sdio *bus)
return; return;
} }
if (bus->sdiodev->bus_if->state == BRCMF_BUS_DOWN) { if (!brcmf_bus_ready(bus->sdiodev->bus_if)) {
brcmf_err("bus is down. we have nothing to do\n"); brcmf_err("bus is down. we have nothing to do\n");
return; return;
} }
@ -3644,7 +3617,6 @@ void brcmf_sdio_isr(struct brcmf_sdio *bus)
else else
if (brcmf_sdio_intr_rstatus(bus)) { if (brcmf_sdio_intr_rstatus(bus)) {
brcmf_err("failed backplane access\n"); brcmf_err("failed backplane access\n");
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
} }
/* Disable additional interrupts (is this needed now)? */ /* Disable additional interrupts (is this needed now)? */
@ -3781,6 +3753,11 @@ brcmf_sdio_probe_attach(struct brcmf_sdio *bus)
goto fail; goto fail;
} }
/* SDIO register access works so moving
* state from UNKNOWN to DOWN.
*/
brcmf_bus_change_state(bus->sdiodev->bus_if, BRCMF_BUS_DOWN);
if (brcmf_sdio_chip_attach(bus->sdiodev, &bus->ci)) { if (brcmf_sdio_chip_attach(bus->sdiodev, &bus->ci)) {
brcmf_err("brcmf_sdio_chip_attach failed!\n"); brcmf_err("brcmf_sdio_chip_attach failed!\n");
goto fail; goto fail;
@ -4004,7 +3981,6 @@ struct brcmf_sdio *brcmf_sdio_probe(struct brcmf_sdio_dev *sdiodev)
/* Disable F2 to clear any intermediate frame state on the dongle */ /* Disable F2 to clear any intermediate frame state on the dongle */
sdio_disable_func(bus->sdiodev->func[SDIO_FUNC_2]); sdio_disable_func(bus->sdiodev->func[SDIO_FUNC_2]);
bus->sdiodev->bus_if->state = BRCMF_BUS_DOWN;
bus->rxflow = false; bus->rxflow = false;
/* Done with backplane-dependent accesses, can drop clock... */ /* Done with backplane-dependent accesses, can drop clock... */
@ -4060,16 +4036,20 @@ void brcmf_sdio_remove(struct brcmf_sdio *bus)
} }
if (bus->ci) { if (bus->ci) {
sdio_claim_host(bus->sdiodev->func[1]); if (bus->sdiodev->bus_if->state == BRCMF_BUS_DOWN) {
brcmf_sdio_clkctl(bus, CLK_AVAIL, false); sdio_claim_host(bus->sdiodev->func[1]);
/* Leave the device in state where it is 'quiet'. This brcmf_sdio_clkctl(bus, CLK_AVAIL, false);
* is done by putting it in download_state which /* Leave the device in state where it is
* essentially resets all necessary cores * 'quiet'. This is done by putting it in
*/ * download_state which essentially resets
msleep(20); * all necessary cores.
brcmf_sdio_chip_enter_download(bus->sdiodev, bus->ci); */
brcmf_sdio_clkctl(bus, CLK_NONE, false); msleep(20);
sdio_release_host(bus->sdiodev->func[1]); brcmf_sdio_chip_enter_download(bus->sdiodev,
bus->ci);
brcmf_sdio_clkctl(bus, CLK_NONE, false);
sdio_release_host(bus->sdiodev->func[1]);
}
brcmf_sdio_chip_detach(&bus->ci); brcmf_sdio_chip_detach(&bus->ci);
} }

View File

@ -522,10 +522,10 @@ brcmf_usb_state_change(struct brcmf_usbdev_info *devinfo, int state)
/* update state of upper layer */ /* update state of upper layer */
if (state == BRCMFMAC_USB_STATE_DOWN) { if (state == BRCMFMAC_USB_STATE_DOWN) {
brcmf_dbg(USB, "DBUS is down\n"); brcmf_dbg(USB, "DBUS is down\n");
bcmf_bus->state = BRCMF_BUS_DOWN; brcmf_bus_change_state(bcmf_bus, BRCMF_BUS_DOWN);
} else if (state == BRCMFMAC_USB_STATE_UP) { } else if (state == BRCMFMAC_USB_STATE_UP) {
brcmf_dbg(USB, "DBUS is up\n"); brcmf_dbg(USB, "DBUS is up\n");
bcmf_bus->state = BRCMF_BUS_DATA; brcmf_bus_change_state(bcmf_bus, BRCMF_BUS_DATA);
} else { } else {
brcmf_dbg(USB, "DBUS current state=%d\n", state); brcmf_dbg(USB, "DBUS current state=%d\n", state);
} }