sdhci: Move real work out of an atomic context
Subsystems (7)
v2 cover sdhci: Move real work out of an atomic context
Hi all,
Currently the sdhci driver does everything in the atomic context. And what is worse, PIO transfers are made from the IRQ handler.
Some patches were already submitted to solve this issue. But there were rejected because they involved new issues.
This set of patches is an evolution of an old patch from Anton Vorontsov. I tried to fix all the problems involved by the patches. I tested it for several time now with SD cards and SDIO.
So, this patch set reworks sdhci code to avoid atomic context, almost completely.
Thanks,
Jeremie Samuel
Jeremie Samuel (8):
sdhci: Turn timeout timer into delayed work
sdhci: Turn tuning timeout timer into delayed work
sdhci: Use work structs instead of tasklets
sdhci: Use threaded IRQ handler
sdhci: Delay led blinking
sdhci: Turn host->lock into a mutex
sdhci: Get rid of mdelay()s where it is safe and makes sense
sdhci: Use jiffies instead of a timeout counter
drivers/mmc/host/sdhci-bcm-kona.c | 2 +- drivers/mmc/host/sdhci-dove.c | 6 +- drivers/mmc/host/sdhci-esdhc-imx.c | 4 +- drivers/mmc/host/sdhci-pxav3.c | 10 +- drivers/mmc/host/sdhci-s3c.c | 7 +- drivers/mmc/host/sdhci-sirf.c | 4 +- drivers/mmc/host/sdhci-spear.c | 2 +- drivers/mmc/host/sdhci.c | 329 ++++++++++++++++++------------------ include/linux/mmc/sdhci.h | 13 +- 9 files changed, 185 insertions(+), 192 deletions(-)
Signature · 1 line
1.7.10.4
PATCH v2 1/8 sdhci: Turn timeout timer into delayed work
There is no need for the timeout handler to run in the atomic context, so this patch turns timeout timeout into the delayed work.
Note that the timeout handler still grabs an irqsave spinlock, we'll deal with it in a separate patch.
Patch based on http://thread.gmane.org/gmane.linux.kernel.mmc/2579.
--- drivers/mmc/host/sdhci.c | 13 +++++++------ include/linux/mmc/sdhci.h | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-)
drivers/mmc/host/sdhci.c +7 -6
@@ -13,6 +13,7 @@* - JMicron (hardware and technical support) */ +#include <linux/workqueue.h> #include <linux/delay.h> #include <linux/highmem.h> #include <linux/io.h>
@@ -1013,7 +1014,7 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)mdelay (1 ); } -mod_timer (&host->timer, jiffies +10 *HZ ); +schedule_delayed_work (&host->timeout_work,10 *HZ ); host->cmd = cmd;
@@ -2140,7 +2141,7 @@ static void sdhci_tasklet_finish(unsigned long param)return ; } -del_timer (&host->timer); +cancel_delayed_work (&host->timeout_work); mrq = host->mrq;
@@ -2180,12 +2181,12 @@ static void sdhci_tasklet_finish(unsigned long param)sdhci_runtime_pm_put (host); } -static void sdhci_timeout_timer (unsigned long data) +static void sdhci_timeout_work (struct work_struct *wk) {struct sdhci_host *host;unsigned long flags; - host = (struct sdhci_host *)data; + host = container_of(wk, struct sdhci_host, timeout_work.work);spin_lock_irqsave (&host->lock, flags);
@@ -3213,7 +3214,7 @@ int sdhci_add_host(struct sdhci_host *host)tasklet_init (&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long )host); -setup_timer (&host->timer, sdhci_timeout_timer, (unsigned long )host); +INIT_DELAYED_WORK (&host->timeout_work, sdhci_timeout_work);if (host->version >=SDHCI_SPEC_300 ) {init_waitqueue_head (&host->buf_ready_int);
@@ -3316,7 +3317,7 @@ void sdhci_remove_host(struct sdhci_host *host, int dead)sdhci_mask_irqs (host,SDHCI_INT_ALL_MASK );free_irq (host->irq, host); -del_timer_sync (&host->timer); +flush_delayed_work (&host->timeout_work);tasklet_kill (&host->card_tasklet);tasklet_kill (&host->finish_tasklet);
include/linux/mmc/sdhci.h +2 -1
@@ -12,6 +12,7 @@#define LINUX_MMC_SDHCI_H #include <linux/scatterlist.h> +#include <linux/workqueue.h> #include <linux/compiler.h> #include <linux/types.h> #include <linux/io.h>
@@ -163,7 +164,7 @@ struct sdhci_host {struct tasklet_struct card_tasklet;/* Tasklet structures */ struct tasklet_struct finish_tasklet; -struct timer_list timer;/* Timer for timeouts */ +struct delayed_work timeout_work;/* Work for timeouts */ u32 caps;/* Alternative CAPABILITY_0 */ u32 caps1;/* Alternative CAPABILITY_1 */
Signature · 1 line
1.7.10.4
PATCH v2 2/8 sdhci: Turn tuning timeout timer into delayed work
In reply to Jeremie SamuelSame as previous patch
--- drivers/mmc/host/sdhci.c | 46 ++++++++++++++++++++++----------------------- include/linux/mmc/sdhci.h | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-)
drivers/mmc/host/sdhci.c +23 -23
@@ -52,7 +52,7 @@ static void sdhci_finish_data(struct sdhci_host *);static void sdhci_finish_command (struct sdhci_host *);static int sdhci_execute_tuning (struct mmc_host *mmc,u32 opcode); -static void sdhci_tuning_timer (unsigned long data); +static void sdhci_tuning_timeout_work (struct work_struct *wk);static void sdhci_enable_preset_value (struct sdhci_host *host,bool enable);#ifdef CONFIG_PM_RUNTIME
@@ -267,7 +267,7 @@ static void sdhci_reinit(struct sdhci_host *host)if (host->flags &SDHCI_USING_RETUNING_TIMER ) { host->flags &= ~SDHCI_USING_RETUNING_TIMER ; -del_timer_sync (&host->tuning_timer); +flush_delayed_work (&host->tuning_timeout_work); host->flags &= ~SDHCI_NEEDS_RETUNING ; host->mmc->max_blk_count = (host->quirks &SDHCI_QUIRK_NO_MULTIBLOCK ) ?1 :65535 ;
@@ -1380,7 +1380,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq) present_state =sdhci_readl (host,SDHCI_PRESENT_STATE );/* -* Check if the re-tuning timer has already expired and there +* Check if the re-tuning timeout has already expired and there * is no on-going data transfer. If so, we need to execute * tuning procedure before sending command. */
@@ -2002,32 +2002,33 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) out:/* * If this is the very first time we are here, we start the retuning -* timer. Since only during the first time, SDHCI_NEEDS_RETUNING -* flag won't be set, we check this condition before actually starting -* the timer. +* timeout workqueue. Since only during the first time, +* SDHCI_NEEDS_RETUNING flag won't be set, we check this condition +* before actually starting the timeout worqueue. */ if (!(host->flags &SDHCI_NEEDS_RETUNING ) && host->tuning_count && (host->tuning_mode ==SDHCI_TUNING_MODE_1 )) { host->flags |=SDHCI_USING_RETUNING_TIMER ; -mod_timer (&host->tuning_timer, jiffies + +schedule_delayed_work (&host->tuning_timeout_work, host->tuning_count *HZ );/* Tuning mode 1 limits the maximum data length to 4MB */ mmc->max_blk_count = (4 *1024 *1024 ) / mmc->max_blk_size; }else { host->flags &= ~SDHCI_NEEDS_RETUNING ; -/* Reload the new initial value for timer */ +/* Reload the new initial value for timeout workqueue */ if (host->tuning_mode ==SDHCI_TUNING_MODE_1 ) -mod_timer (&host->tuning_timer, jiffies + +schedule_delayed_work (&host->tuning_timeout_work, host->tuning_count *HZ ); }/* * In case tuning fails, host controllers which support re-tuning can -* try tuning again at a later time, when the re-tuning timer expires. +* try tuning again at a later time, when the re-tuning timeout +* workqueue expires. * So for these controllers, we return 0. Since there might be other * controllers who do not have this capability, we return error for * them. SDHCI_USING_RETUNING_TIMER means the host is currently using -* a retuning timer to do the retuning for the card. +* a retuning timeout workqueue to do the retuning for the card. */ if (err && (host->flags &SDHCI_USING_RETUNING_TIMER )) err =0 ;
@@ -2212,12 +2213,12 @@ static void sdhci_timeout_work(struct work_struct *wk)spin_unlock_irqrestore (&host->lock, flags); } -static void sdhci_tuning_timer (unsigned long data) +static void sdhci_tuning_timeout_work (struct work_struct *wk) {struct sdhci_host *host;unsigned long flags; - host = (struct sdhci_host *)data; + host = container_of(wk, struct sdhci_host, tuning_timeout_work.work);spin_lock_irqsave (&host->lock, flags);
@@ -2564,7 +2565,7 @@ int sdhci_suspend_host(struct sdhci_host *host)/* Disable tuning since we are suspending */ if (host->flags &SDHCI_USING_RETUNING_TIMER ) { -del_timer_sync (&host->tuning_timer); +flush_delayed_work (&host->tuning_timeout_work); host->flags &= ~SDHCI_NEEDS_RETUNING ; }
@@ -2572,7 +2573,7 @@ int sdhci_suspend_host(struct sdhci_host *host)if (ret) {if (host->flags &SDHCI_USING_RETUNING_TIMER ) { host->flags |=SDHCI_NEEDS_RETUNING ; -mod_timer (&host->tuning_timer, jiffies + +schedule_delayed_work (&host->tuning_timeout_work, host->tuning_count *HZ ); }
@@ -2676,7 +2677,7 @@ int sdhci_runtime_suspend_host(struct sdhci_host *host)/* Disable tuning since we are suspending */ if (host->flags &SDHCI_USING_RETUNING_TIMER ) { -del_timer_sync (&host->tuning_timer); +flush_delayed_work (&host->tuning_timeout_work); host->flags &= ~SDHCI_NEEDS_RETUNING ; }
@@ -3034,13 +3035,13 @@ int sdhci_add_host(struct sdhci_host *host)if (caps[1 ] &SDHCI_DRIVER_TYPE_D ) mmc->caps |=MMC_CAP_DRIVER_TYPE_D ; -/* Initial value for re-tuning timer count */ +/* Initial value for re-tuning timeout count */ host->tuning_count = (caps[1 ] &SDHCI_RETUNING_TIMER_COUNT_MASK ) >>SDHCI_RETUNING_TIMER_COUNT_SHIFT ;/* -* In case Re-tuning Timer is not disabled, the actual value of -* re-tuning timer will be 2 ^ (n - 1). +* In case Re-tuning Timeout is not disabled, the actual value of +* re-tuning timeout will be 2 ^ (n - 1). */ if (host->tuning_count) host->tuning_count =1 << (host->tuning_count -1 );
@@ -3219,10 +3220,9 @@ int sdhci_add_host(struct sdhci_host *host)if (host->version >=SDHCI_SPEC_300 ) {init_waitqueue_head (&host->buf_ready_int); -/* Initialize re-tuning timer */ -init_timer (&host->tuning_timer); - host->tuning_timer.data = (unsigned long )host; - host->tuning_timer.function = sdhci_tuning_timer; +/* Initialize re-tuning timeout work */ +INIT_DELAYED_WORK (&host->tuning_timeout_work, + sdhci_tuning_timeout_work); }sdhci_init (host,0 );
include/linux/mmc/sdhci.h +1 -1
@@ -180,7 +180,7 @@ struct sdhci_host {unsigned int tuning_count;/* Timer count for re-tuning */ unsigned int tuning_mode;/* Re-tuning mode supported by host */ #define SDHCI_TUNING_MODE_1 0 -struct timer_list tuning_timer;/* Timer for tuning */ +struct delayed_work tuning_timeout_work;/* Work for tuning timeouts */ unsigned long private[0 ]____cacheline_aligned ; };
Signature · 1 line
1.7.10.4
PATCH v2 3/8 sdhci: Use work structs instead of tasklets
In reply to Jeremie SamuelThe driver can happily live without an atomic context and tasklets, so turn the tasklets into the work structs.
Tasklets handlers still grab irqsave spinlocks, but we'll deal with it in a separate patch.
Patch based on: http://thread.gmane.org/gmane.linux.kernel.mmc/2579.
--- drivers/mmc/host/sdhci-bcm-kona.c | 2 +- drivers/mmc/host/sdhci-dove.c | 6 ++-- drivers/mmc/host/sdhci-s3c.c | 2 +- drivers/mmc/host/sdhci-sirf.c | 4 +-- drivers/mmc/host/sdhci-spear.c | 2 +- drivers/mmc/host/sdhci.c | 55 +++++++++++++++++-------------------- include/linux/mmc/sdhci.h | 4 +-- 7 files changed, 35 insertions(+), 40 deletions(-)
drivers/mmc/host/sdhci-bcm-kona.c +1 -1
@@ -120,7 +120,7 @@ static void sdhci_bcm_kona_sd_init(struct sdhci_host *host)* Software emulation of the SD card insertion/removal. Set insert=1 for insert * and insert=0 for removal. The card detection is done by GPIO. For Broadcom * IP to function properly the bit 0 of CORESTAT register needs to be set/reset -* to generate the CD IRQ handled in sdhci.c which schedules card_tasklet. +* to generate the CD IRQ handled in sdhci.c which schedules card_detect_work. */ static int sdhci_bcm_kona_sd_card_emulate (struct sdhci_host *host,int insert) {
drivers/mmc/host/sdhci-dove.c +3 -3
@@ -39,7 +39,7 @@ static irqreturn_t sdhci_dove_carddetect_irq(int irq, void *data) {struct sdhci_host *host = data; -tasklet_schedule (&host->card_tasklet); +schedule_schedule (&host->card_detect_work);return IRQ_HANDLED ; }
@@ -149,8 +149,8 @@ static int sdhci_dove_probe(struct platform_device *pdev) goto err_sdhci_add;/* -* We must request the IRQ after sdhci_add_host(), as the tasklet only -* gets setup in sdhci_add_host() and we oops. +* We must request the IRQ after sdhci_add_host(), as the workqueue +* only gets setup in sdhci_add_host() and we oops. */ if (gpio_is_valid (priv->gpio_cd)) { ret = request_irq(gpio_to_irq(priv->gpio_cd),
drivers/mmc/host/sdhci-s3c.c +1 -1
@@ -395,7 +395,7 @@ static void sdhci_s3c_notify_change(struct platform_device *dev, int state)clk_disable_unprepare (sc->clk_io);#endif } -tasklet_schedule (&host->card_tasklet); +schedule_work (&host->card_detect_work);spin_unlock_irqrestore (&host->lock, flags); } }
drivers/mmc/host/sdhci-sirf.c +2 -2
@@ -80,8 +80,8 @@ static int sdhci_sirf_probe(struct platform_device *pdev) goto err_sdhci_add;/* -* We must request the IRQ after sdhci_add_host(), as the tasklet only -* gets setup in sdhci_add_host() and we oops. +* We must request the IRQ after sdhci_add_host(), as the workqueue +* only gets setup in sdhci_add_host() and we oops. */ if (gpio_is_valid (priv->gpio_cd)) { ret =mmc_gpio_request_cd (host->mmc, priv->gpio_cd,0 );
drivers/mmc/host/sdhci-spear.c +1 -1
@@ -65,7 +65,7 @@ static irqreturn_t sdhci_gpio_irq(int irq, void *dev_id) }/* inform sdhci driver about card insertion/removal */ -tasklet_schedule (&host->card_tasklet); +schedule_work (&host->card_detect_work);return IRQ_HANDLED ; }
drivers/mmc/host/sdhci.c +25 -30
161 lines not on this page · raw message
include/linux/mmc/sdhci.h +2 -2
@@ -161,8 +161,8 @@ struct sdhci_host {dma_addr_t adma_addr;/* Mapped ADMA descr. table */ dma_addr_t align_addr;/* Mapped bounce buffer */ -struct tasklet_struct card_tasklet;/* Tasklet structures */ -struct tasklet_struct finish_tasklet; +struct work_struct card_detect_work; +struct work_struct finish_work;struct delayed_work timeout_work;/* Work for timeouts */
Signature · 1 line
1.7.10.4
PATCH v2 4/8 sdhci: Use threaded IRQ handler
In reply to Jeremie SamuelWe only need atomic context to disable SDHCI interrupts, after that we can run in a kernel thread.
Note that irq handler still grabs an irqsave spinlock, we'll deal with it in a subsequent patch.
Patch based on: http://thread.gmane.org/gmane.linux.kernel.mmc/2579.
--- drivers/mmc/host/sdhci.c | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-)
drivers/mmc/host/sdhci.c +28 -18
@@ -2413,9 +2413,8 @@ static void sdhci_data_irq(struct sdhci_host *host, u32 intmask) } } -static irqreturn_t sdhci_irq (int irq,void *dev_id) +static irqreturn_t sdhci_irq_thread (int irq,void *dev_id) { -irqreturn_t result;struct sdhci_host *host = dev_id;u32 intmask, unexpected =0 ;int cardint =0 , max_loops =16 ;
@@ -2431,15 +2430,7 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id) intmask =sdhci_readl (host,SDHCI_INT_STATUS ); -if (!intmask || intmask ==0xffffffff ) { - result =IRQ_NONE ; - goto out; - } - again: -DBG ("*** %s got interrupt: 0x%08x\n" , -mmc_hostname (host->mmc), intmask); -if (intmask & (SDHCI_INT_CARD_INSERT |SDHCI_INT_CARD_REMOVE )) {u32 present =sdhci_readl (host,SDHCI_PRESENT_STATE ) &SDHCI_CARD_PRESENT ;
@@ -2499,12 +2490,10 @@ again:sdhci_writel (host, intmask,SDHCI_INT_STATUS ); } - result =IRQ_HANDLED ; - intmask =sdhci_readl (host,SDHCI_INT_STATUS );if (intmask && --max_loops) goto again; -out: +spin_unlock (&host->lock);if (unexpected) {
@@ -2518,7 +2507,27 @@ out:if (cardint)mmc_signal_sdio_irq (host->mmc); -return result; + intmask =sdhci_readl (host,SDHCI_INT_ENABLE ); +sdhci_writel (host, intmask,SDHCI_SIGNAL_ENABLE ); + +return IRQ_HANDLED ; +} + +static irqreturn_t sdhci_irq (int irq,void *dev_id) +{ +struct sdhci_host *host = dev_id; +u32 intmask =sdhci_readl (host,SDHCI_INT_STATUS ); + +if (!intmask || intmask ==0xffffffff ) +return IRQ_NONE ; + +/* Disable interrupts */ +sdhci_writel (host,0 ,SDHCI_SIGNAL_ENABLE ); + +DBG ("*** %s got interrupt: 0x%08x\n" , +mmc_hostname (host->mmc), intmask); + +return IRQ_WAKE_THREAD ; }/*****************************************************************************\
@@ -2605,8 +2614,9 @@ int sdhci_resume_host(struct sdhci_host *host) }if (!device_may_wakeup (mmc_dev (host->mmc))) { - ret =request_irq (host->irq, sdhci_irq,IRQF_SHARED , -mmc_hostname (host->mmc), host); + ret =request_threaded_irq (host->irq, sdhci_irq, + sdhci_irq_thread,IRQF_SHARED , +mmc_hostname (host->mmc), host);if (ret)return ret; }else {
@@ -3226,8 +3236,8 @@ int sdhci_add_host(struct sdhci_host *host)sdhci_init (host,0 ); - ret =request_irq (host->irq, sdhci_irq,IRQF_SHARED , -mmc_hostname (mmc), host); + ret =request_threaded_irq (host->irq, sdhci_irq, sdhci_irq_thread, +IRQF_SHARED ,mmc_hostname (host->mmc), host);if (ret) {pr_err ("%s: Failed to request IRQ %d: %d\n" ,mmc_hostname (mmc), host->irq, ret);
Signature · 1 line
1.7.10.4
Re: [PATCH v2 4/8] sdhci: Use threaded IRQ handler
Hi,
Quoted 10 lines · Jeremie Samuel
We only need atomic context to disable SDHCI interrupts, after that we can run in a kernel thread.
Note that irq handler still grabs an irqsave spinlock, we'll deal with it in a subsequent patch.
Patch based on: http://thread.gmane.org/gmane.linux.kernel.mmc/2579.
Signed-off-by Anton Vorontsov <[email protected]>Signed-off-by Jeremie Samuel <[email protected]>
It seems like this patch is causing problems on Marvell Dove (such as SolidRun Cubox). I've bisected a boot issue on that hardware down on linux-next.
A good boot without this patch (i.e. with this patch reverted) contains two spurious interrupts at probe time, both of them with timeout+error set.
Below is the output with MMC_DEBUG turned on. Given the proximity to the merge window, if we can't sort this out I think I'd prefer to see this patch merged and retargeted to 3.14...
Good boot without this patch:
[ 0.962882] sdhci: Secure Digital Host Controller Interface driver [ 0.969045] sdhci: Copyright(c) Pierre Ossman [ 0.973375] sdhci-pltfm: SDHCI platform and OF driver helper [ 0.979149] sdhci [sdhci_add_host()]: mmc0: Auto-CMD23 unavailable [ 0.985320] mmc0: no vqmmc regulator found [ 0.989396] mmc0: no vmmc regulator found [ 0.993409] sdhci: =========== REGISTER DUMP (mmc0)=========== [ 0.999221] sdhci: Sys addr: 0x00000000 | Version: 0x00000000 [ 1.005035] sdhci: Blk size: 0x00000000 | Blk cnt: 0x00000000 [ 1.010840] sdhci: Argument: 0x00000000 | Trn mode: 0x00000001 [ 1.016656] sdhci: Present: 0x017f0000 | Host ctl: 0x00000000 [ 1.022461] sdhci: Power: 0x0000000c | Blk gap: 0x00000000 [ 1.028275] sdhci: Wake-up: 0x00000000 | Clock: 0x00000100 [ 1.034080] sdhci: Timeout: 0x0000000e | Int stat: 0x00000000 [ 1.039888] sdhci: Int enab: 0x00ff0003 | Sig enab: 0x00ff0003 [ 1.045701] sdhci: AC12 err: 0x00000000 | Slot int: 0x00000000 [ 1.051500] sdhci: Caps: 0x01e032b2 | Caps_1: 0x00000000 [ 1.057313] sdhci: Cmd: 0x00000000 | Max curr: 0x00000000 [ 1.063111] sdhci: Host ctl2: 0x00000000 [ 1.067014] sdhci: =========================================== [ 1.073201] mmc0: clock 0Hz busmode 2 powermode 1 cs 0 Vdd 21 width 0 timing 0 [ 1.094119] mmc0: clock 400000Hz busmode 2 powermode 2 cs 0 Vdd 21 width 0 timing 0 [ 1.114117] mmc0: SDHCI controller on f1092000.sdio-host [f1092000.sdio-host] using DMA [ 1.122155] mmc0: mmc_rescan_try_freq: trying to init card at 400000 Hz [ 1.128750] mmc0: starting CMD52 arg 00000c00 flags 00000195 [ 1.134710] sdhci [sdhci_irq()]: *** mmc0 got interrupt: 0x00018000 [ 1.140953] mmc0: Unexpected interrupt 0x00008000. [ 1.145717] sdhci: =========== REGISTER DUMP (mmc0)=========== [ 1.151516] sdhci: Sys addr: 0x00000000 | Version: 0x00000000 [ 1.157313] sdhci: Blk size: 0x00000000 | Blk cnt: 0x00000000 [ 1.163110] sdhci: Argument: 0x00000c00 | Trn mode: 0x00000001 [ 1.168908] sdhci: Present: 0x01ff0000 | Host ctl: 0x00000001 [ 1.174705] sdhci: Power: 0x0000000f | Blk gap: 0x00000000 [ 1.180503] sdhci: Wake-up: 0x00000000 | Clock: 0x00004007 [ 1.186300] sdhci: Timeout: 0x0000000e | Int stat: 0x00000000 [ 1.192099] sdhci: Int enab: 0x00ff0083 | Sig enab: 0x00ff0083 [ 1.197903] sdhci: AC12 err: 0x00000000 | Slot int: 0x00000000 [ 1.203702] sdhci: Caps: 0x01e032b2 | Caps_1: 0x00000000 [ 1.209499] sdhci: Cmd: 0x0000341a | Max curr: 0x00000000 [ 1.215295] sdhci: Host ctl2: 0x00000000 [ 1.219191] sdhci: =========================================== [ 1.225037] mmc0: req done (CMD52): -110: 00000000 00000000 00000000 00000000 [ 1.232144] mmc0: starting CMD52 arg 80000c08 flags 00000195 [ 1.237807] ata1: SATA link down (SStatus 0 SControl F300) [ 1.243266] sdhci [sdhci_irq()]: *** mmc0 got interrupt: 0x00018000 [ 1.249503] mmc0: Unexpected interrupt 0x00008000. [ 1.254270] sdhci: =========== REGISTER DUMP (mmc0)=========== [ 1.260069] sdhci: Sys addr: 0x00000000 | Version: 0x00000000 [ 1.265866] sdhci: Blk size: 0x00000000 | Blk cnt: 0x00000000 [ 1.271664] sdhci: Argument: 0x80000c08 | Trn mode: 0x00000001 [ 1.277462] sdhci: Present: 0x01ff0000 | Host ctl: 0x00000001 [ 1.283266] sdhci: Power: 0x0000000f | Blk gap: 0x00000000 [ 1.289064] sdhci: Wake-up: 0x00000000 | Clock: 0x00004007 [ 1.294862] sdhci: Timeout: 0x0000000e | Int stat: 0x00000000 [ 1.300660] sdhci: Int enab: 0x00ff0083 | Sig enab: 0x00ff0083 [ 1.306456] sdhci: AC12 err: 0x00000000 | Slot int: 0x00000000 [ 1.312255] sdhci: Caps: 0x01e032b2 | Caps_1: 0x00000000 [ 1.318052] sdhci: Cmd: 0x0000341a | Max curr: 0x00000000 [ 1.323848] sdhci: Host ctl2: 0x00000000 [ 1.327745] sdhci: ===========================================
A bad boot with this patch applied instead only gives:
[ 0.962875] sdhci: Secure Digital Host Controller Interface driver [ 0.969042] sdhci: Copyright(c) Pierre Ossman [ 0.973373] sdhci-pltfm: SDHCI platform and OF driver helper [ 0.979146] sdhci [sdhci_add_host()]: mmc0: Auto-CMD23 unavailable [ 0.985318] mmc0: no vqmmc regulator found [ 0.989394] mmc0: no vmmc regulator found [ 0.993478] sdhci: =========== REGISTER DUMP (mmc0)=========== [ 0.999290] sdhci: Sys addr: 0x00000000 | Version: 0x00000000 [ 1.005104] sdhci: Blk size: 0x00000000 | Blk cnt: 0x00000000 [ 1.010907] sdhci: Argument: 0x00000000 | Trn mode: 0x00000001 [ 1.016715] sdhci: Present: 0x017f0000 | Host ctl: 0x00000000 [ 1.022519] sdhci: Power: 0x0000000c | Blk gap: 0x00000000 [ 1.028326] sdhci: Wake-up: 0x00000000 | Clock: 0x00000100 [ 1.034139] sdhci: Timeout: 0x0000000e | Int stat: 0x00000000 [ 1.039938] sdhci: Int enab: 0x00ff0003 | Sig enab: 0x00ff0003 [ 1.045751] sdhci: AC12 err: 0x00000000 | Slot int: 0x00000000 [ 1.051550] sdhci: Caps: 0x01e032b2 | Caps_1: 0x00000000 [ 1.057363] sdhci: Cmd: 0x00000000 | Max curr: 0x00000000 [ 1.063161] sdhci: Host ctl2: 0x00000000 [ 1.067064] sdhci: =========================================== [ 1.073351] mmc0: clock 0Hz busmode 2 powermode 1 cs 0 Vdd 21 width 0 timing 0 [ 1.094093] mmc0: clock 400000Hz busmode 2 powermode 2 cs 0 Vdd 21 width 0 timing 0 [ 1.114093] mmc0: SDHCI controller on f1092000.sdio-host [f1092000.sdio-host] using DMA [ 1.122130] mmc0: mmc_rescan_try_freq: trying to init card at 400000 Hz [ 1.128731] mmc0: starting CMD52 arg 00000c00 flags 00000195 [ 1.134692] sdhci [sdhci_irq()]: *** mmc0 got interrupt: 0x00018000 [ 1.140936] sdhci [sdhci_irq()]: *** mmc0 got interrupt: 0x00018000 [ 1.147183] sdhci [sdhci_irq()]: *** mmc0 got interrupt: 0x00018000
[ ... repeat forever ]
-Olof
PATCH v2 5/8 sdhci: Delay led blinking
In reply to Jeremie SamuelThe function sdhci_led_control is called in an atomic context by the led_trigger driver. So, it is necessary to delay the activation or deactivation of the led.
--- drivers/mmc/host/sdhci.c | 42 ++++++++++++++++++++++-------------------- include/linux/mmc/sdhci.h | 1 + 2 files changed, 23 insertions(+), 20 deletions(-)
drivers/mmc/host/sdhci.c +22 -20
@@ -280,8 +280,14 @@ static void sdhci_activate_led(struct sdhci_host *host)u8 ctrl; ctrl =sdhci_readb (host,SDHCI_HOST_CONTROL ); - ctrl |=SDHCI_CTRL_LED ; -sdhci_writeb (host, ctrl,SDHCI_HOST_CONTROL ); +#ifdef SDHCI_USE_LEDS_CLASS +if (host->brightness && !(ctrl &SDHCI_CTRL_LED )) { +#else +if (!(ctrl &SDHCI_CTRL_LED )) { +#endif + ctrl |=SDHCI_CTRL_LED ; +sdhci_writeb (host, ctrl,SDHCI_HOST_CONTROL ); + } }static void sdhci_deactivate_led (struct sdhci_host *host)
@@ -289,8 +295,14 @@ static void sdhci_deactivate_led(struct sdhci_host *host)u8 ctrl; ctrl =sdhci_readb (host,SDHCI_HOST_CONTROL ); - ctrl &= ~SDHCI_CTRL_LED ; -sdhci_writeb (host, ctrl,SDHCI_HOST_CONTROL ); +#ifdef SDHCI_USE_LEDS_CLASS +if (!host->brightness && (ctrl &SDHCI_CTRL_LED )) { +#else +if (ctrl &SDHCI_CTRL_LED ) { +#endif + ctrl &= ~SDHCI_CTRL_LED ; +sdhci_writeb (host, ctrl,SDHCI_HOST_CONTROL ); + } }#ifdef SDHCI_USE_LEDS_CLASS
@@ -298,19 +310,11 @@ static void sdhci_led_control(struct led_classdev *led, enum led_brightness brightness) { struct sdhci_host *host = container_of(led, struct sdhci_host, led); -unsigned long flags; - -spin_lock_irqsave (&host->lock, flags);if (host->runtime_suspended) - goto out; +return ; -if (brightness ==LED_OFF ) -sdhci_deactivate_led (host); -else -sdhci_activate_led (host); -out: -spin_unlock_irqrestore (&host->lock, flags); + host->brightness = brightness; }#endif
@@ -1338,9 +1342,7 @@ static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)WARN_ON (host->mrq !=NULL ); -#ifndef SDHCI_USE_LEDS_CLASS sdhci_activate_led (host); -#endif /* * Ensure we don't send the STOP for non-SET_BLOCK_COUNTED
@@ -2172,15 +2174,15 @@ static void sdhci_finish_work(struct work_struct *wk) host->cmd =NULL ; host->data =NULL ; -#ifndef SDHCI_USE_LEDS_CLASS -sdhci_deactivate_led (host); -#endif -mmiowb ();spin_unlock_irqrestore (&host->lock, flags);mmc_request_done (host->mmc, mrq);sdhci_runtime_pm_put (host); + +spin_lock_irqsave (&host->lock, flags); +sdhci_deactivate_led (host); +spin_unlock_irqrestore (&host->lock, flags); }static void sdhci_timeout_work (struct work_struct *wk)
include/linux/mmc/sdhci.h +1 -0
@@ -115,6 +115,7 @@ struct sdhci_host {#if defined(CONFIG_LEDS_CLASS ) || defined(CONFIG_LEDS_CLASS_MODULE )struct led_classdev led;/* LED control */ char led_name[32 ]; +enum led_brightness brightness;#endif spinlock_t lock;/* Mutex */
Signature · 1 line
1.7.10.4
PATCH v2 6/8 sdhci: Turn host->lock into a mutex
In reply to Jeremie SamuelFinally, we can get rid of the host->lock spinlock, and turn it into a mutex.
This patch does just this.
Patch based on: http://thread.gmane.org/gmane.linux.kernel.mmc/2579.
--- drivers/mmc/host/sdhci-esdhc-imx.c | 4 +- drivers/mmc/host/sdhci-pxav3.c | 10 ++-- drivers/mmc/host/sdhci-s3c.c | 5 +- drivers/mmc/host/sdhci.c | 101 ++++++++++++++++-------------------- include/linux/mmc/sdhci.h | 3 +- 5 files changed, 55 insertions(+), 68 deletions(-)
drivers/mmc/host/sdhci-esdhc-imx.c +2 -2
@@ -641,12 +641,12 @@ static int esdhc_send_tuning_cmd(struct sdhci_host *host, u32 opcode)init_completion (&(mrq.completion));disable_irq (host->irq); -spin_lock (&host->lock); +mutex_lock (&host->lock); host->mrq = &mrq;sdhci_send_command (host, mrq.cmd); -spin_unlock (&host->lock); +mutex_unlock (&host->lock);enable_irq (host->irq);wait_for_completion (&mrq.completion);
drivers/mmc/host/sdhci-pxav3.c +4 -6
@@ -378,12 +378,11 @@ static int sdhci_pxav3_runtime_suspend(struct device *dev) {struct sdhci_host *host =dev_get_drvdata (dev);struct sdhci_pltfm_host *pltfm_host =sdhci_priv (host); -unsigned long flags;if (pltfm_host->clk) { -spin_lock_irqsave (&host->lock, flags); +mutex_lock (&host->lock); host->runtime_suspended = true; -spin_unlock_irqrestore (&host->lock, flags); +mutex_unlock (&host->lock);clk_disable_unprepare (pltfm_host->clk); }
@@ -395,14 +394,13 @@ static int sdhci_pxav3_runtime_resume(struct device *dev) {struct sdhci_host *host =dev_get_drvdata (dev);struct sdhci_pltfm_host *pltfm_host =sdhci_priv (host); -unsigned long flags;if (pltfm_host->clk) {clk_prepare_enable (pltfm_host->clk); -spin_lock_irqsave (&host->lock, flags); +mutex_lock (&host->lock); host->runtime_suspended = false; -spin_unlock_irqrestore (&host->lock, flags); +mutex_unlock (&host->lock); }return 0 ;
drivers/mmc/host/sdhci-s3c.c +2 -3
@@ -376,10 +376,9 @@ static void sdhci_s3c_notify_change(struct platform_device *dev, int state)#ifdef CONFIG_PM_RUNTIME struct sdhci_s3c *sc =sdhci_priv (host);#endif -unsigned long flags;if (host) { -spin_lock_irqsave (&host->lock, flags); +mutex_lock (&host->lock);if (state) {dev_dbg (&dev->dev,"card inserted.\n" );#ifdef CONFIG_PM_RUNTIME
@@ -396,7 +395,7 @@ static void sdhci_s3c_notify_change(struct platform_device *dev, int state)#endif }schedule_work (&host->card_detect_work); -spin_unlock_irqrestore (&host->lock, flags); +mutex_unlock (&host->lock); } }
drivers/mmc/host/sdhci.c +45 -56
356 lines not on this page · raw message
include/linux/mmc/sdhci.h +2 -1
@@ -15,6 +15,7 @@#include <linux/workqueue.h> #include <linux/compiler.h> #include <linux/types.h> +#include <linux/mutex.h> #include <linux/io.h> #include <linux/mmc/host.h>
@@ -118,7 +119,7 @@ struct sdhci_host {enum led_brightness brightness;#endif -spinlock_t lock;/* Mutex */ +struct mutex lock;/* Mutex */ int flags;/* Host attributes */ #define SDHCI_USE_SDMA (1<<0)/* Host is SDMA capable */
Signature · 1 line
1.7.10.4
PATCH v2 7/8 sdhci: Get rid of mdelay()s where it is safe and makes sense
In reply to Jeremie SamuelSince we don't run in the atomic context any longer, we can turn mdelay()s into msleep()s.
The only place where the driver is still using mdelay() is sdhci_send_command(). There it is possible to use sleepable delays too, but we don't actually want to force rescheduling in a hot path.
Sure, we might end up calling msleep() there too, just not via this safe patch.
PAtch based on: http://thread.gmane.org/gmane.linux.kernel.mmc/2579.
--- drivers/mmc/host/sdhci.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
drivers/mmc/host/sdhci.c +4 -4
@@ -220,7 +220,7 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask)return ; } timeout--; -mdelay (1 ); +msleep (1 ); }if (host->ops->platform_reset_exit)
@@ -1238,7 +1238,7 @@ clock_set:return ; } timeout--; -mdelay (1 ); +msleep (1 ); } clk |=SDHCI_CLOCK_CARD_EN ;
@@ -1317,7 +1317,7 @@ static int sdhci_set_power(struct sdhci_host *host, unsigned short power)* can apply clock after applying power */ if (host->quirks &SDHCI_QUIRK_DELAY_AFTER_POWER ) -mdelay (10 ); +msleep (10 );return power; }
@@ -1979,7 +1979,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) ctrl =sdhci_readw (host,SDHCI_HOST_CONTROL2 ); tuning_loop_counter--; timeout--; -mdelay (1 ); +msleep (1 ); }while (ctrl &SDHCI_CTRL_EXEC_TUNING );/*
Signature · 1 line
1.7.10.4
PATCH v2 8/8 sdhci: Use jiffies instead of a timeout counter
In reply to Jeremie SamuelJust a cosmetic change, should not affect functionality.
Patch based on: http://thread.gmane.org/gmane.linux.kernel.mmc/2579.
--- drivers/mmc/host/sdhci.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-)
drivers/mmc/host/sdhci.c +9 -13
@@ -209,17 +209,16 @@ static void sdhci_reset(struct sdhci_host *host, u8 mask) }/* Wait max 100 ms */ - timeout =100 ; + timeout = jiffies +msecs_to_jiffies (100 );/* hw clears the bit when it's done */ while (sdhci_readb (host,SDHCI_SOFTWARE_RESET ) & mask) { -if (timeout ==0 ) { +if (time_after (jiffies, timeout)) {pr_err ("%s: Reset 0x%x never completed.\n" ,mmc_hostname (host->mmc), (int )mask);sdhci_dumpregs (host);return ; } - timeout--;msleep (1 ); }
@@ -995,7 +994,7 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)WARN_ON (host->cmd);/* Wait max 10 ms */ - timeout =10 ; + timeout = jiffies +msecs_to_jiffies (10 ); mask =SDHCI_CMD_INHIBIT ;if ((cmd->data !=NULL ) || (cmd->flags &MMC_RSP_BUSY ))
@@ -1007,7 +1006,7 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd) mask &= ~SDHCI_DATA_INHIBIT ;while (sdhci_readl (host,SDHCI_PRESENT_STATE ) & mask) { -if (timeout ==0 ) { +if (time_after (jiffies, timeout)) {pr_err ("%s: Controller never released " "inhibit bit(s).\n" ,mmc_hostname (host->mmc));sdhci_dumpregs (host);
@@ -1015,7 +1014,6 @@ void sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)schedule_work (&host->finish_work);return ; } - timeout--;mdelay (1 ); }
@@ -1228,16 +1226,15 @@ clock_set:sdhci_writew (host, clk,SDHCI_CLOCK_CONTROL );/* Wait max 20 ms */ - timeout =20 ; + timeout = jiffies +msecs_to_jiffies (20 );while (!((clk =sdhci_readw (host,SDHCI_CLOCK_CONTROL )) &SDHCI_CLOCK_INT_STABLE )) { -if (timeout ==0 ) { +if (time_after (jiffies, timeout)) {pr_err ("%s: Internal clock never " "stabilised.\n" ,mmc_hostname (host->mmc));sdhci_dumpregs (host);return ; } - timeout--;msleep (1 ); }
@@ -1902,12 +1899,12 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)* Issue CMD19 repeatedly till Execute Tuning is set to 0 or the number * of loops reaches 40 times or a timeout of 150ms occurs. */ - timeout =150 ; + timeout = jiffies +msecs_to_jiffies (150 ); do { struct mmc_command cmd = {0}; struct mmc_request mrq = {NULL}; - if (!tuning_loop_counter && !timeout) + if (!tuning_loop_counter && time_after(jiffies, timeout)) break; cmd.opcode = opcode;
@@ -1978,7 +1975,6 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode) ctrl =sdhci_readw (host,SDHCI_HOST_CONTROL2 ); tuning_loop_counter--; - timeout--;msleep (1 ); }while (ctrl &SDHCI_CTRL_EXEC_TUNING );
@@ -1986,7 +1982,7 @@ static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)* The Host Driver has exhausted the maximum number of loops allowed, * so use fixed sampling frequency. */ -if (!tuning_loop_counter || !timeout) { +if (!tuning_loop_counter ||time_after (jiffies, timeout)) { ctrl &= ~SDHCI_CTRL_TUNED_CLK ;sdhci_writew (host, ctrl,SDHCI_HOST_CONTROL2 ); }else {
Signature · 1 line
1.7.10.4
Hi Jeremie,
Quoted 6 lines · Jeremie Samuel
Currently the sdhci driver does everything in the atomic context. And what is worse, PIO transfers are made from the IRQ handler.
Some patches were already submitted to solve this issue. But there were rejected because they involved new issues.
Quoted 6 lines
This set of patches is an evolution of an old patch from Anton Vorontsov. I tried to fix all the problems involved by the patches. I tested it for several time now with SD cards and SDIO.
So, this patch set reworks sdhci code to avoid atomic context, almost completely.
Could we get a changelog from patchset v1 to v2, for the record? (And isn't this more like v4 of your series?)
You don't have to resend the patchset, just replying with the changelog here is fine.
Thanks!
- Chris.
Signature · 1 line
Chris Ball <[email protected]> <http://printf.net/>
v2:
Patches on sdhci-bcm-kona.c, sdhci-dove.c, sdhci-s3c.c, sdhci-sirf.c, sdhci-spear.c in order to consider the change tasklet -> workqueue
Version of 16 oct 13:
Patches on sdhci-esdhc-imx.c, sdhci-pxav3.c, sdhci-s3c.c in order to consider the change spinlock -> mutex
Version of 06 jul 13:
Resend in order to include the patches in kernel 3.12
Version of 24 may 13:
First version of my patches (based on Anton Vorontsov's patches http://thread.gmane.org/gmane.linux.kernel.mmc/2579)
Sorry for the issues. It's my first submission on a Linux mailing list.
Cordially,
Signature · 4 lines
Jeremie Samuel Parrot S.A. Software Engineer 14, quai de Jemmapes R&D/OS Platform 75010 Paris, France http://www.parrot.com
Quoted 24 lines · Chris Ball
Hi Jeremie,
Quoted 6 lines · Jeremie Samuel
Currently the sdhci driver does everything in the atomic context. And what is worse, PIO transfers are made from the IRQ handler.
Some patches were already submitted to solve this issue. But there were rejected because they involved new issues.
Quoted 6 lines
This set of patches is an evolution of an old patch from Anton Vorontsov. I tried to fix all the problems involved by the patches. I tested it for several time now with SD cards and SDIO.
So, this patch set reworks sdhci code to avoid atomic context, almost completely.
Could we get a changelog from patchset v1 to v2, for the record? (And isn't this more like v4 of your series?)
You don't have to resend the patchset, just replying with the changelog here is fine.
Thanks!
- Chris.
Chris Ball
AppliedHi Jeremie,
Quoted 6 lines · Jeremie Samuel
Currently the sdhci driver does everything in the atomic context. And what is worse, PIO transfers are made from the IRQ handler.
Some patches were already submitted to solve this issue. But there were rejected because they involved new issues.
Quoted 6 lines
This set of patches is an evolution of an old patch from Anton Vorontsov. I tried to fix all the problems involved by the patches. I tested it for several time now with SD cards and SDIO.
So, this patch set reworks sdhci code to avoid atomic context, almost completely.
I've merged this series to mmc-next for 3.13 now. Thanks very much!
- Chris.
Signature · 1 line
Chris Ball <[email protected]> <http://printf.net/>