Commit 921c87ba authored by Allen Pais's avatar Allen Pais Committed by Ulf Hansson
Browse files

mmc: Convert from tasklet to BH workqueue



The only generic interface to execute asynchronously in the BH context is
tasklet; however, it's marked deprecated and has some design flaws. To
replace tasklets, BH workqueue support was recently added. A BH workqueue
behaves similarly to regular workqueues except that the queued work items
are executed in the BH context.

This patch converts drivers/mmc/* from tasklet to BH workqueue.

Based on the work done by Tejun Heo <tj@kernel.org>

Tested-by: default avatarChristian Loehle <christian.loehle@arm.com>
Tested-by: default avatarAubin Constans <aubin.constans@microchip.com>
Acked-by: default avatarAubin Constans <aubin.constans@microchip.com>
Acked-by: default avatarMichał Mirosław <mirq-linux@rere.qmqm.pl>
Reviewed-by: default avatarChristian Loehle <christian.loehle@arm.com>
Signed-off-by: default avatarAllen Pais <allen.lkml@gmail.com>
Link: https://lore.kernel.org/r/20240701100736.4001658-1-allen.lkml@gmail.com


Signed-off-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
parent 85683fb3
Loading
Loading
Loading
Loading
+18 −17
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/pinctrl/consumer.h>
#include <linux/workqueue.h>

#include <asm/cacheflush.h>
#include <asm/io.h>
@@ -272,12 +273,12 @@ struct atmel_mci_dma {
 *	EVENT_DATA_ERROR is pending.
 * @stop_cmdr: Value to be loaded into CMDR when the stop command is
 *	to be sent.
 * @tasklet: Tasklet running the request state machine.
 * @bh_work: Work running the request state machine.
 * @pending_events: Bitmask of events flagged by the interrupt handler
 *	to be processed by the tasklet.
 *	to be processed by the work.
 * @completed_events: Bitmask of events which the state machine has
 *	processed.
 * @state: Tasklet state.
 * @state: Work state.
 * @queue: List of slots waiting for access to the controller.
 * @need_clock_update: Update the clock rate before the next request.
 * @need_reset: Reset controller before next request.
@@ -352,7 +353,7 @@ struct atmel_mci {
	u32			data_status;
	u32			stop_cmdr;

	struct tasklet_struct	tasklet;
	struct work_struct	bh_work;
	unsigned long		pending_events;
	unsigned long		completed_events;
	enum atmel_mci_state	state;
@@ -735,7 +736,7 @@ static void atmci_timeout_timer(struct timer_list *t)
	host->need_reset = 1;
	host->state = STATE_END_REQUEST;
	smp_wmb();
	tasklet_schedule(&host->tasklet);
	queue_work(system_bh_wq, &host->bh_work);
}

static inline unsigned int atmci_ns_to_clocks(struct atmel_mci *host,
@@ -958,7 +959,7 @@ static void atmci_pdc_complete(struct atmel_mci *host)

	dev_dbg(dev, "(%s) set pending xfer complete\n", __func__);
	atmci_set_pending(host, EVENT_XFER_COMPLETE);
	tasklet_schedule(&host->tasklet);
	queue_work(system_bh_wq, &host->bh_work);
}

static void atmci_dma_cleanup(struct atmel_mci *host)
@@ -972,7 +973,7 @@ static void atmci_dma_cleanup(struct atmel_mci *host)
}

/*
 * This function is called by the DMA driver from tasklet context.
 * This function is called by the DMA driver from bh context.
 */
static void atmci_dma_complete(void *arg)
{
@@ -995,7 +996,7 @@ static void atmci_dma_complete(void *arg)
	if (data) {
		dev_dbg(dev, "(%s) set pending xfer complete\n", __func__);
		atmci_set_pending(host, EVENT_XFER_COMPLETE);
		tasklet_schedule(&host->tasklet);
		queue_work(system_bh_wq, &host->bh_work);

		/*
		 * Regardless of what the documentation says, we have
@@ -1008,7 +1009,7 @@ static void atmci_dma_complete(void *arg)
		 * haven't seen all the potential error bits yet.
		 *
		 * The interrupt handler will schedule a different
		 * tasklet to finish things up when the data transfer
		 * bh work to finish things up when the data transfer
		 * is completely done.
		 *
		 * We may not complete the mmc request here anyway
@@ -1745,9 +1746,9 @@ static void atmci_detect_change(struct timer_list *t)
	}
}

static void atmci_tasklet_func(struct tasklet_struct *t)
static void atmci_work_func(struct work_struct *t)
{
	struct atmel_mci        *host = from_tasklet(host, t, tasklet);
	struct atmel_mci        *host = from_work(host, t, bh_work);
	struct mmc_request	*mrq = host->mrq;
	struct mmc_data		*data = host->data;
	struct device		*dev = host->dev;
@@ -1759,7 +1760,7 @@ static void atmci_tasklet_func(struct tasklet_struct *t)

	state = host->state;

	dev_vdbg(dev, "tasklet: state %u pending/completed/mask %lx/%lx/%x\n",
	dev_vdbg(dev, "bh_work: state %u pending/completed/mask %lx/%lx/%x\n",
		state, host->pending_events, host->completed_events,
		atmci_readl(host, ATMCI_IMR));

@@ -2118,7 +2119,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
			dev_dbg(dev, "set pending data error\n");
			smp_wmb();
			atmci_set_pending(host, EVENT_DATA_ERROR);
			tasklet_schedule(&host->tasklet);
			queue_work(system_bh_wq, &host->bh_work);
		}

		if (pending & ATMCI_TXBUFE) {
@@ -2187,7 +2188,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
			smp_wmb();
			dev_dbg(dev, "set pending notbusy\n");
			atmci_set_pending(host, EVENT_NOTBUSY);
			tasklet_schedule(&host->tasklet);
			queue_work(system_bh_wq, &host->bh_work);
		}

		if (pending & ATMCI_NOTBUSY) {
@@ -2196,7 +2197,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
			smp_wmb();
			dev_dbg(dev, "set pending notbusy\n");
			atmci_set_pending(host, EVENT_NOTBUSY);
			tasklet_schedule(&host->tasklet);
			queue_work(system_bh_wq, &host->bh_work);
		}

		if (pending & ATMCI_RXRDY)
@@ -2211,7 +2212,7 @@ static irqreturn_t atmci_interrupt(int irq, void *dev_id)
			smp_wmb();
			dev_dbg(dev, "set pending cmd rdy\n");
			atmci_set_pending(host, EVENT_CMD_RDY);
			tasklet_schedule(&host->tasklet);
			queue_work(system_bh_wq, &host->bh_work);
		}

		if (pending & (ATMCI_SDIOIRQA | ATMCI_SDIOIRQB))
@@ -2487,7 +2488,7 @@ static int atmci_probe(struct platform_device *pdev)

	host->mapbase = regs->start;

	tasklet_setup(&host->tasklet, atmci_tasklet_func);
	INIT_WORK(&host->bh_work, atmci_work_func);

	ret = request_irq(irq, atmci_interrupt, 0, dev_name(dev), host);
	if (ret) {
+19 −18
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@
#include <linux/leds.h>
#include <linux/mmc/host.h>
#include <linux/slab.h>
#include <linux/workqueue.h>

#include <asm/io.h>
#include <asm/mach-au1x00/au1000.h>
@@ -113,8 +114,8 @@ struct au1xmmc_host {

	int irq;

	struct tasklet_struct finish_task;
	struct tasklet_struct data_task;
	struct work_struct finish_bh_work;
	struct work_struct data_bh_work;
	struct au1xmmc_platform_data *platdata;
	struct platform_device *pdev;
	struct resource *ioarea;
@@ -253,9 +254,9 @@ static void au1xmmc_finish_request(struct au1xmmc_host *host)
	mmc_request_done(host->mmc, mrq);
}

static void au1xmmc_tasklet_finish(struct tasklet_struct *t)
static void au1xmmc_finish_bh_work(struct work_struct *t)
{
	struct au1xmmc_host *host = from_tasklet(host, t, finish_task);
	struct au1xmmc_host *host = from_work(host, t, finish_bh_work);
	au1xmmc_finish_request(host);
}

@@ -363,9 +364,9 @@ static void au1xmmc_data_complete(struct au1xmmc_host *host, u32 status)
	au1xmmc_finish_request(host);
}

static void au1xmmc_tasklet_data(struct tasklet_struct *t)
static void au1xmmc_data_bh_work(struct work_struct *t)
{
	struct au1xmmc_host *host = from_tasklet(host, t, data_task);
	struct au1xmmc_host *host = from_work(host, t, data_bh_work);

	u32 status = __raw_readl(HOST_STATUS(host));
	au1xmmc_data_complete(host, status);
@@ -425,7 +426,7 @@ static void au1xmmc_send_pio(struct au1xmmc_host *host)
		if (host->flags & HOST_F_STOP)
			SEND_STOP(host);

		tasklet_schedule(&host->data_task);
		queue_work(system_bh_wq, &host->data_bh_work);
	}
}

@@ -505,7 +506,7 @@ static void au1xmmc_receive_pio(struct au1xmmc_host *host)
		if (host->flags & HOST_F_STOP)
			SEND_STOP(host);

		tasklet_schedule(&host->data_task);
		queue_work(system_bh_wq, &host->data_bh_work);
	}
}

@@ -561,7 +562,7 @@ static void au1xmmc_cmd_complete(struct au1xmmc_host *host, u32 status)

	if (!trans || cmd->error) {
		IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA | SD_CONFIG_RF);
		tasklet_schedule(&host->finish_task);
		queue_work(system_bh_wq, &host->finish_bh_work);
		return;
	}

@@ -797,7 +798,7 @@ static irqreturn_t au1xmmc_irq(int irq, void *dev_id)
		IRQ_OFF(host, SD_CONFIG_NE | SD_CONFIG_TH);

		/* IRQ_OFF(host, SD_CONFIG_TH | SD_CONFIG_RA | SD_CONFIG_RF); */
		tasklet_schedule(&host->finish_task);
		queue_work(system_bh_wq, &host->finish_bh_work);
	}
#if 0
	else if (status & SD_STATUS_DD) {
@@ -806,7 +807,7 @@ static irqreturn_t au1xmmc_irq(int irq, void *dev_id)
			au1xmmc_receive_pio(host);
		else {
			au1xmmc_data_complete(host, status);
			/* tasklet_schedule(&host->data_task); */
			/* queue_work(system_bh_wq, &host->data_bh_work); */
		}
	}
#endif
@@ -854,7 +855,7 @@ static void au1xmmc_dbdma_callback(int irq, void *dev_id)
	if (host->flags & HOST_F_STOP)
		SEND_STOP(host);

	tasklet_schedule(&host->data_task);
	queue_work(system_bh_wq, &host->data_bh_work);
}

static int au1xmmc_dbdma_init(struct au1xmmc_host *host)
@@ -1039,9 +1040,9 @@ static int au1xmmc_probe(struct platform_device *pdev)
	if (host->platdata)
		mmc->caps &= ~(host->platdata->mask_host_caps);

	tasklet_setup(&host->data_task, au1xmmc_tasklet_data);
	INIT_WORK(&host->data_bh_work, au1xmmc_data_bh_work);

	tasklet_setup(&host->finish_task, au1xmmc_tasklet_finish);
	INIT_WORK(&host->finish_bh_work, au1xmmc_finish_bh_work);

	if (has_dbdma()) {
		ret = au1xmmc_dbdma_init(host);
@@ -1091,8 +1092,8 @@ static int au1xmmc_probe(struct platform_device *pdev)
	if (host->flags & HOST_F_DBDMA)
		au1xmmc_dbdma_shutdown(host);

	tasklet_kill(&host->data_task);
	tasklet_kill(&host->finish_task);
	cancel_work_sync(&host->data_bh_work);
	cancel_work_sync(&host->finish_bh_work);

	if (host->platdata && host->platdata->cd_setup &&
	    !(mmc->caps & MMC_CAP_NEEDS_POLL))
@@ -1135,8 +1136,8 @@ static void au1xmmc_remove(struct platform_device *pdev)
		__raw_writel(0, HOST_CONFIG2(host));
		wmb(); /* drain writebuffer */

		tasklet_kill(&host->data_task);
		tasklet_kill(&host->finish_task);
		cancel_work_sync(&host->data_bh_work);
		cancel_work_sync(&host->finish_bh_work);

		if (host->flags & HOST_F_DBDMA)
			au1xmmc_dbdma_shutdown(host);
+7 −7
Original line number Diff line number Diff line
@@ -493,7 +493,7 @@ static void cb710_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
	if (!cb710_mmc_command(mmc, mrq->cmd) && mrq->stop)
		cb710_mmc_command(mmc, mrq->stop);

	tasklet_schedule(&reader->finish_req_tasklet);
	queue_work(system_bh_wq, &reader->finish_req_bh_work);
}

static int cb710_mmc_powerup(struct cb710_slot *slot)
@@ -646,10 +646,10 @@ static int cb710_mmc_irq_handler(struct cb710_slot *slot)
	return 1;
}

static void cb710_mmc_finish_request_tasklet(struct tasklet_struct *t)
static void cb710_mmc_finish_request_bh_work(struct work_struct *t)
{
	struct cb710_mmc_reader *reader = from_tasklet(reader, t,
						       finish_req_tasklet);
	struct cb710_mmc_reader *reader = from_work(reader, t,
						       finish_req_bh_work);
	struct mmc_request *mrq = reader->mrq;

	reader->mrq = NULL;
@@ -718,8 +718,8 @@ static int cb710_mmc_init(struct platform_device *pdev)

	reader = mmc_priv(mmc);

	tasklet_setup(&reader->finish_req_tasklet,
		      cb710_mmc_finish_request_tasklet);
	INIT_WORK(&reader->finish_req_bh_work,
			cb710_mmc_finish_request_bh_work);
	spin_lock_init(&reader->irq_lock);
	cb710_dump_regs(chip, CB710_DUMP_REGS_MMC);

@@ -763,7 +763,7 @@ static void cb710_mmc_exit(struct platform_device *pdev)
	cb710_write_port_32(slot, CB710_MMC_CONFIG_PORT, 0);
	cb710_write_port_16(slot, CB710_MMC_CONFIGB_PORT, 0);

	tasklet_kill(&reader->finish_req_tasklet);
	cancel_work_sync(&reader->finish_req_bh_work);

	mmc_free_host(mmc);
}
+2 −1
Original line number Diff line number Diff line
@@ -8,10 +8,11 @@
#define LINUX_CB710_MMC_H

#include <linux/cb710.h>
#include <linux/workqueue.h>

/* per-MMC-reader structure */
struct cb710_mmc_reader {
	struct tasklet_struct finish_req_tasklet;
	struct work_struct finish_req_bh_work;
	struct mmc_request *mrq;
	spinlock_t irq_lock;
	unsigned char last_power_mode;
+12 −12
Original line number Diff line number Diff line
@@ -493,7 +493,7 @@ static void dw_mci_dmac_complete_dma(void *arg)
	 */
	if (data) {
		set_bit(EVENT_XFER_COMPLETE, &host->pending_events);
		tasklet_schedule(&host->tasklet);
		queue_work(system_bh_wq, &host->bh_work);
	}
}

@@ -1834,7 +1834,7 @@ static enum hrtimer_restart dw_mci_fault_timer(struct hrtimer *t)
	if (!host->data_status) {
		host->data_status = SDMMC_INT_DCRC;
		set_bit(EVENT_DATA_ERROR, &host->pending_events);
		tasklet_schedule(&host->tasklet);
		queue_work(system_bh_wq, &host->bh_work);
	}

	spin_unlock_irqrestore(&host->irq_lock, flags);
@@ -2056,9 +2056,9 @@ static bool dw_mci_clear_pending_data_complete(struct dw_mci *host)
	return true;
}

static void dw_mci_tasklet_func(struct tasklet_struct *t)
static void dw_mci_work_func(struct work_struct *t)
{
	struct dw_mci *host = from_tasklet(host, t, tasklet);
	struct dw_mci *host = from_work(host, t, bh_work);
	struct mmc_data	*data;
	struct mmc_command *cmd;
	struct mmc_request *mrq;
@@ -2113,7 +2113,7 @@ static void dw_mci_tasklet_func(struct tasklet_struct *t)
				 * will waste a bit of time (we already know
				 * the command was bad), it can't cause any
				 * errors since it's possible it would have
				 * taken place anyway if this tasklet got
				 * taken place anyway if this bh work got
				 * delayed. Allowing the transfer to take place
				 * avoids races and keeps things simple.
				 */
@@ -2706,7 +2706,7 @@ static void dw_mci_cmd_interrupt(struct dw_mci *host, u32 status)
	smp_wmb(); /* drain writebuffer */

	set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
	tasklet_schedule(&host->tasklet);
	queue_work(system_bh_wq, &host->bh_work);

	dw_mci_start_fault_timer(host);
}
@@ -2774,7 +2774,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
				set_bit(EVENT_DATA_COMPLETE,
					&host->pending_events);

			tasklet_schedule(&host->tasklet);
			queue_work(system_bh_wq, &host->bh_work);

			spin_unlock(&host->irq_lock);
		}
@@ -2793,7 +2793,7 @@ static irqreturn_t dw_mci_interrupt(int irq, void *dev_id)
					dw_mci_read_data_pio(host, true);
			}
			set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
			tasklet_schedule(&host->tasklet);
			queue_work(system_bh_wq, &host->bh_work);

			spin_unlock(&host->irq_lock);
		}
@@ -3098,7 +3098,7 @@ static void dw_mci_cmd11_timer(struct timer_list *t)

	host->cmd_status = SDMMC_INT_RTO;
	set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
	tasklet_schedule(&host->tasklet);
	queue_work(system_bh_wq, &host->bh_work);
}

static void dw_mci_cto_timer(struct timer_list *t)
@@ -3144,7 +3144,7 @@ static void dw_mci_cto_timer(struct timer_list *t)
		 */
		host->cmd_status = SDMMC_INT_RTO;
		set_bit(EVENT_CMD_COMPLETE, &host->pending_events);
		tasklet_schedule(&host->tasklet);
		queue_work(system_bh_wq, &host->bh_work);
		break;
	default:
		dev_warn(host->dev, "Unexpected command timeout, state %d\n",
@@ -3195,7 +3195,7 @@ static void dw_mci_dto_timer(struct timer_list *t)
		host->data_status = SDMMC_INT_DRTO;
		set_bit(EVENT_DATA_ERROR, &host->pending_events);
		set_bit(EVENT_DATA_COMPLETE, &host->pending_events);
		tasklet_schedule(&host->tasklet);
		queue_work(system_bh_wq, &host->bh_work);
		break;
	default:
		dev_warn(host->dev, "Unexpected data timeout, state %d\n",
@@ -3435,7 +3435,7 @@ int dw_mci_probe(struct dw_mci *host)
	else
		host->fifo_reg = host->regs + DATA_240A_OFFSET;

	tasklet_setup(&host->tasklet, dw_mci_tasklet_func);
	INIT_WORK(&host->bh_work, dw_mci_work_func);
	ret = devm_request_irq(host->dev, host->irq, dw_mci_interrupt,
			       host->irq_flags, "dw-mci", host);
	if (ret)
Loading