mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-13 22:25:03 +00:00
5e57c668dc
Since commit 5561770f80
("staging: wfx: repair external IRQ for
SDIO"), wfx_sdio_irq_subscribe() enforce the device to use IRQs.
However, there is currently a race in this code. An IRQ may happen
before the IRQ has been registered.
The problem has observed during debug session when the device crashes
before the IRQ set up:
[ 1.546] wfx-sdio mmc0:0001:1: started firmware 3.12.2 "WF200_ASIC_WFM_(Jenkins)_FW3.12.2" (API: 3.7, keyset: C0, caps: 0x00000002)
[ 2.559] wfx-sdio mmc0:0001:1: time out while polling control register
[ 3.565] wfx-sdio mmc0:0001:1: chip is abnormally long to answer
[ 6.563] wfx-sdio mmc0:0001:1: chip did not answer
[ 6.568] wfx-sdio mmc0:0001:1: hardware request CONFIGURATION (0x09) on vif 2 returned error -110
[ 6.577] wfx-sdio mmc0:0001:1: PDS bytes 0 to 12: chip didn't reply (corrupted file?)
[ 6.585] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[ 6.592] pgd = c0004000
[ 6.595] [00000000] *pgd=00000000
[ 6.598] Internal error: Oops - BUG: 17 [#1] THUMB2
[ 6.603] Modules linked in:
[ 6.606] CPU: 0 PID: 23 Comm: kworker/u2:1 Not tainted 3.18.19 #78
[ 6.612] Workqueue: kmmcd mmc_rescan
[ 6.616] task: c176d100 ti: c0e50000 task.ti: c0e50000
[ 6.621] PC is at wake_up_process+0xa/0x14
[ 6.625] LR is at sdio_irq+0x61/0x250
[ 6.629] pc : [<c001e8ae>] lr : [<c00ec5bd>] psr: 600001b3
[ 6.629] sp : c0e51bd8 ip : c0e51cc8 fp : 00000001
[ 6.640] r10: 00000003 r9 : 00000000 r8 : c0003c34
[ 6.644] r7 : c0e51bd8 r6 : c0003c30 r5 : 00000001 r4 : c0e78c00
[ 6.651] r3 : 00000000 r2 : 00000000 r1 : 00000003 r0 : 00000000
[ 6.657] Flags: nZCv IRQs off FIQs on Mode SVC_32 ISA Thumb Segment kernel
[ 6.664] Control: 50c53c7d Table: 11fd8059 DAC: 00000015
[ 6.670] Process kworker/u2:1 (pid: 23, stack limit = 0xc0e501b0)
[ 6.676] Stack: (0xc0e51bd8 to 0xc0e52000)
[...]
[ 6.949] [<c001e8ae>] (wake_up_process) from [<c00ec5bd>] (sdio_irq+0x61/0x250)
[ 6.956] [<c00ec5bd>] (sdio_irq) from [<c0025099>] (handle_irq_event_percpu+0x17/0x92)
[ 6.964] [<c0025099>] (handle_irq_event_percpu) from [<c002512f>] (handle_irq_event+0x1b/0x24)
[ 6.973] [<c002512f>] (handle_irq_event) from [<c0026577>] (handle_level_irq+0x5d/0x76)
[ 6.981] [<c0026577>] (handle_level_irq) from [<c0024cc3>] (generic_handle_irq+0x13/0x1c)
[ 6.989] [<c0024cc3>] (generic_handle_irq) from [<c0024dd9>] (__handle_domain_irq+0x31/0x48)
[ 6.997] [<c0024dd9>] (__handle_domain_irq) from [<c0008359>] (ov_handle_irq+0x31/0xe0)
[ 7.005] [<c0008359>] (ov_handle_irq) from [<c000af5b>] (__irq_svc+0x3b/0x5c)
[ 7.013] Exception stack(0xc0e51c68 to 0xc0e51cb0)
[...]
[ 7.038] [<c000af5b>] (__irq_svc) from [<c01775aa>] (wait_for_common+0x9e/0xc4)
[ 7.045] [<c01775aa>] (wait_for_common) from [<c00e1dc3>] (mmc_wait_for_req+0x4b/0xdc)
[ 7.053] [<c00e1dc3>] (mmc_wait_for_req) from [<c00e1e83>] (mmc_wait_for_cmd+0x2f/0x34)
[ 7.061] [<c00e1e83>] (mmc_wait_for_cmd) from [<c00e7b2b>] (mmc_io_rw_direct_host+0x71/0xac)
[ 7.070] [<c00e7b2b>] (mmc_io_rw_direct_host) from [<c00e8f79>] (sdio_claim_irq+0x6b/0x116)
[ 7.078] [<c00e8f79>] (sdio_claim_irq) from [<c00d8415>] (wfx_sdio_irq_subscribe+0x19/0x94)
[ 7.086] [<c00d8415>] (wfx_sdio_irq_subscribe) from [<c00d5229>] (wfx_probe+0x189/0x2ac)
[ 7.095] [<c00d5229>] (wfx_probe) from [<c00d83bf>] (wfx_sdio_probe+0x8f/0xcc)
[ 7.102] [<c00d83bf>] (wfx_sdio_probe) from [<c00e7fbb>] (sdio_bus_probe+0x5f/0xa8)
[ 7.109] [<c00e7fbb>] (sdio_bus_probe) from [<c00be229>] (driver_probe_device+0x59/0x134)
[ 7.118] [<c00be229>] (driver_probe_device) from [<c00bd4d7>] (bus_for_each_drv+0x3f/0x4a)
[ 7.126] [<c00bd4d7>] (bus_for_each_drv) from [<c00be1a5>] (device_attach+0x3b/0x52)
[ 7.134] [<c00be1a5>] (device_attach) from [<c00bdc2b>] (bus_probe_device+0x17/0x4c)
[ 7.141] [<c00bdc2b>] (bus_probe_device) from [<c00bcd69>] (device_add+0x2c5/0x334)
[ 7.149] [<c00bcd69>] (device_add) from [<c00e80bf>] (sdio_add_func+0x23/0x44)
[ 7.156] [<c00e80bf>] (sdio_add_func) from [<c00e79eb>] (mmc_attach_sdio+0x187/0x1ec)
[ 7.164] [<c00e79eb>] (mmc_attach_sdio) from [<c00e31bd>] (mmc_rescan+0x18d/0x1fc)
[ 7.172] [<c00e31bd>] (mmc_rescan) from [<c001a14f>] (process_one_work+0xd7/0x170)
[ 7.179] [<c001a14f>] (process_one_work) from [<c001a59b>] (worker_thread+0x103/0x1bc)
[ 7.187] [<c001a59b>] (worker_thread) from [<c001c731>] (kthread+0x7d/0x90)
[ 7.194] [<c001c731>] (kthread) from [<c0008ce1>] (ret_from_fork+0x11/0x30)
[ 7.201] Code: 2103 b580 2200 af00 (681b) 46bd
[ 7.206] ---[ end trace 3ab50aced42eedb4 ]---
Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
Link: https://lore.kernel.org/r/20210913130203.1903622-33-Jerome.Pouiller@silabs.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
272 lines
6.4 KiB
C
272 lines
6.4 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* SDIO interface.
|
|
*
|
|
* Copyright (c) 2017-2020, Silicon Laboratories, Inc.
|
|
* Copyright (c) 2010, ST-Ericsson
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/mmc/sdio.h>
|
|
#include <linux/mmc/sdio_func.h>
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/irq.h>
|
|
|
|
#include "bus.h"
|
|
#include "wfx.h"
|
|
#include "hwio.h"
|
|
#include "main.h"
|
|
#include "bh.h"
|
|
|
|
static const struct wfx_platform_data wfx_sdio_pdata = {
|
|
.file_fw = "wfm_wf200",
|
|
.file_pds = "wf200.pds",
|
|
};
|
|
|
|
struct wfx_sdio_priv {
|
|
struct sdio_func *func;
|
|
struct wfx_dev *core;
|
|
u8 buf_id_tx;
|
|
u8 buf_id_rx;
|
|
int of_irq;
|
|
};
|
|
|
|
static int wfx_sdio_copy_from_io(void *priv, unsigned int reg_id,
|
|
void *dst, size_t count)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
unsigned int sdio_addr = reg_id << 2;
|
|
int ret;
|
|
|
|
WARN(reg_id > 7, "chip only has 7 registers");
|
|
WARN(((uintptr_t)dst) & 3, "unaligned buffer size");
|
|
WARN(count & 3, "unaligned buffer address");
|
|
|
|
/* Use queue mode buffers */
|
|
if (reg_id == WFX_REG_IN_OUT_QUEUE)
|
|
sdio_addr |= (bus->buf_id_rx + 1) << 7;
|
|
ret = sdio_memcpy_fromio(bus->func, dst, sdio_addr, count);
|
|
if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
|
|
bus->buf_id_rx = (bus->buf_id_rx + 1) % 4;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int wfx_sdio_copy_to_io(void *priv, unsigned int reg_id,
|
|
const void *src, size_t count)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
unsigned int sdio_addr = reg_id << 2;
|
|
int ret;
|
|
|
|
WARN(reg_id > 7, "chip only has 7 registers");
|
|
WARN(((uintptr_t)src) & 3, "unaligned buffer size");
|
|
WARN(count & 3, "unaligned buffer address");
|
|
|
|
/* Use queue mode buffers */
|
|
if (reg_id == WFX_REG_IN_OUT_QUEUE)
|
|
sdio_addr |= bus->buf_id_tx << 7;
|
|
/* FIXME: discards 'const' qualifier for src */
|
|
ret = sdio_memcpy_toio(bus->func, sdio_addr, (void *)src, count);
|
|
if (!ret && reg_id == WFX_REG_IN_OUT_QUEUE)
|
|
bus->buf_id_tx = (bus->buf_id_tx + 1) % 32;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void wfx_sdio_lock(void *priv)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
|
|
sdio_claim_host(bus->func);
|
|
}
|
|
|
|
static void wfx_sdio_unlock(void *priv)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
|
|
sdio_release_host(bus->func);
|
|
}
|
|
|
|
static void wfx_sdio_irq_handler(struct sdio_func *func)
|
|
{
|
|
struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
|
|
|
|
wfx_bh_request_rx(bus->core);
|
|
}
|
|
|
|
static irqreturn_t wfx_sdio_irq_handler_ext(int irq, void *priv)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
|
|
sdio_claim_host(bus->func);
|
|
wfx_bh_request_rx(bus->core);
|
|
sdio_release_host(bus->func);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int wfx_sdio_irq_subscribe(void *priv)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
u32 flags;
|
|
int ret;
|
|
u8 cccr;
|
|
|
|
if (!bus->of_irq) {
|
|
sdio_claim_host(bus->func);
|
|
ret = sdio_claim_irq(bus->func, wfx_sdio_irq_handler);
|
|
sdio_release_host(bus->func);
|
|
return ret;
|
|
}
|
|
|
|
flags = irq_get_trigger_type(bus->of_irq);
|
|
if (!flags)
|
|
flags = IRQF_TRIGGER_HIGH;
|
|
flags |= IRQF_ONESHOT;
|
|
ret = devm_request_threaded_irq(&bus->func->dev, bus->of_irq, NULL,
|
|
wfx_sdio_irq_handler_ext, flags,
|
|
"wfx", bus);
|
|
if (ret)
|
|
return ret;
|
|
sdio_claim_host(bus->func);
|
|
cccr = sdio_f0_readb(bus->func, SDIO_CCCR_IENx, NULL);
|
|
cccr |= BIT(0);
|
|
cccr |= BIT(bus->func->num);
|
|
sdio_f0_writeb(bus->func, cccr, SDIO_CCCR_IENx, NULL);
|
|
sdio_release_host(bus->func);
|
|
return 0;
|
|
}
|
|
|
|
static int wfx_sdio_irq_unsubscribe(void *priv)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
int ret;
|
|
|
|
if (bus->of_irq)
|
|
devm_free_irq(&bus->func->dev, bus->of_irq, bus);
|
|
sdio_claim_host(bus->func);
|
|
ret = sdio_release_irq(bus->func);
|
|
sdio_release_host(bus->func);
|
|
return ret;
|
|
}
|
|
|
|
static size_t wfx_sdio_align_size(void *priv, size_t size)
|
|
{
|
|
struct wfx_sdio_priv *bus = priv;
|
|
|
|
return sdio_align_size(bus->func, size);
|
|
}
|
|
|
|
static const struct hwbus_ops wfx_sdio_hwbus_ops = {
|
|
.copy_from_io = wfx_sdio_copy_from_io,
|
|
.copy_to_io = wfx_sdio_copy_to_io,
|
|
.irq_subscribe = wfx_sdio_irq_subscribe,
|
|
.irq_unsubscribe = wfx_sdio_irq_unsubscribe,
|
|
.lock = wfx_sdio_lock,
|
|
.unlock = wfx_sdio_unlock,
|
|
.align_size = wfx_sdio_align_size,
|
|
};
|
|
|
|
static const struct of_device_id wfx_sdio_of_match[] = {
|
|
{ .compatible = "silabs,wfx-sdio" },
|
|
{ .compatible = "silabs,wf200" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, wfx_sdio_of_match);
|
|
|
|
static int wfx_sdio_probe(struct sdio_func *func,
|
|
const struct sdio_device_id *id)
|
|
{
|
|
struct device_node *np = func->dev.of_node;
|
|
struct wfx_sdio_priv *bus;
|
|
int ret;
|
|
|
|
if (func->num != 1) {
|
|
dev_err(&func->dev, "SDIO function number is %d while it should always be 1 (unsupported chip?)\n",
|
|
func->num);
|
|
return -ENODEV;
|
|
}
|
|
|
|
bus = devm_kzalloc(&func->dev, sizeof(*bus), GFP_KERNEL);
|
|
if (!bus)
|
|
return -ENOMEM;
|
|
|
|
if (np) {
|
|
if (!of_match_node(wfx_sdio_of_match, np)) {
|
|
dev_warn(&func->dev, "no compatible device found in DT\n");
|
|
return -ENODEV;
|
|
}
|
|
bus->of_irq = irq_of_parse_and_map(np, 0);
|
|
} else {
|
|
dev_warn(&func->dev,
|
|
"device is not declared in DT, features will be limited\n");
|
|
/* FIXME: ignore VID/PID and only rely on device tree */
|
|
// return -ENODEV;
|
|
}
|
|
|
|
bus->func = func;
|
|
sdio_set_drvdata(func, bus);
|
|
func->card->quirks |= MMC_QUIRK_LENIENT_FN0 |
|
|
MMC_QUIRK_BLKSZ_FOR_BYTE_MODE |
|
|
MMC_QUIRK_BROKEN_BYTE_MODE_512;
|
|
|
|
sdio_claim_host(func);
|
|
ret = sdio_enable_func(func);
|
|
/* Block of 64 bytes is more efficient than 512B for frame sizes < 4k */
|
|
sdio_set_block_size(func, 64);
|
|
sdio_release_host(func);
|
|
if (ret)
|
|
goto err0;
|
|
|
|
bus->core = wfx_init_common(&func->dev, &wfx_sdio_pdata,
|
|
&wfx_sdio_hwbus_ops, bus);
|
|
if (!bus->core) {
|
|
ret = -EIO;
|
|
goto err1;
|
|
}
|
|
|
|
ret = wfx_probe(bus->core);
|
|
if (ret)
|
|
goto err1;
|
|
|
|
return 0;
|
|
|
|
err1:
|
|
sdio_claim_host(func);
|
|
sdio_disable_func(func);
|
|
sdio_release_host(func);
|
|
err0:
|
|
return ret;
|
|
}
|
|
|
|
static void wfx_sdio_remove(struct sdio_func *func)
|
|
{
|
|
struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
|
|
|
|
wfx_release(bus->core);
|
|
sdio_claim_host(func);
|
|
sdio_disable_func(func);
|
|
sdio_release_host(func);
|
|
}
|
|
|
|
#define SDIO_VENDOR_ID_SILABS 0x0000
|
|
#define SDIO_DEVICE_ID_SILABS_WF200 0x1000
|
|
static const struct sdio_device_id wfx_sdio_ids[] = {
|
|
{ SDIO_DEVICE(SDIO_VENDOR_ID_SILABS, SDIO_DEVICE_ID_SILABS_WF200) },
|
|
/* FIXME: ignore VID/PID and only rely on device tree */
|
|
// { SDIO_DEVICE(SDIO_ANY_ID, SDIO_ANY_ID) },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(sdio, wfx_sdio_ids);
|
|
|
|
struct sdio_driver wfx_sdio_driver = {
|
|
.name = "wfx-sdio",
|
|
.id_table = wfx_sdio_ids,
|
|
.probe = wfx_sdio_probe,
|
|
.remove = wfx_sdio_remove,
|
|
.drv = {
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = wfx_sdio_of_match,
|
|
}
|
|
};
|