mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 21:02:19 +00:00
55db890a83
Many controllers have an upper limit on the number of blocks that can be transferred in one request. Allow the host drivers to specify this and make sure we avoid hitting this limit. Also change the max_sectors field to avoid confusion. This makes it map less directly to the block layer limits, but as they didn't apply directly on MMC cards anyway, this isn't a great loss. Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>
251 lines
5.6 KiB
C
251 lines
5.6 KiB
C
/*
|
|
* linux/drivers/mmc/mmc_queue.c
|
|
*
|
|
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
*/
|
|
#include <linux/module.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/host.h>
|
|
#include "mmc_queue.h"
|
|
|
|
#define MMC_QUEUE_SUSPENDED (1 << 0)
|
|
|
|
/*
|
|
* Prepare a MMC request. Essentially, this means passing the
|
|
* preparation off to the media driver. The media driver will
|
|
* create a mmc_io_request in req->special.
|
|
*/
|
|
static int mmc_prep_request(struct request_queue *q, struct request *req)
|
|
{
|
|
struct mmc_queue *mq = q->queuedata;
|
|
int ret = BLKPREP_KILL;
|
|
|
|
if (blk_special_request(req)) {
|
|
/*
|
|
* Special commands already have the command
|
|
* blocks already setup in req->special.
|
|
*/
|
|
BUG_ON(!req->special);
|
|
|
|
ret = BLKPREP_OK;
|
|
} else if (blk_fs_request(req) || blk_pc_request(req)) {
|
|
/*
|
|
* Block I/O requests need translating according
|
|
* to the protocol.
|
|
*/
|
|
ret = mq->prep_fn(mq, req);
|
|
} else {
|
|
/*
|
|
* Everything else is invalid.
|
|
*/
|
|
blk_dump_rq_flags(req, "MMC bad request");
|
|
}
|
|
|
|
if (ret == BLKPREP_OK)
|
|
req->cmd_flags |= REQ_DONTPREP;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mmc_queue_thread(void *d)
|
|
{
|
|
struct mmc_queue *mq = d;
|
|
struct request_queue *q = mq->queue;
|
|
|
|
/*
|
|
* Set iothread to ensure that we aren't put to sleep by
|
|
* the process freezing. We handle suspension ourselves.
|
|
*/
|
|
current->flags |= PF_MEMALLOC|PF_NOFREEZE;
|
|
|
|
down(&mq->thread_sem);
|
|
do {
|
|
struct request *req = NULL;
|
|
|
|
spin_lock_irq(q->queue_lock);
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
if (!blk_queue_plugged(q))
|
|
req = elv_next_request(q);
|
|
mq->req = req;
|
|
spin_unlock_irq(q->queue_lock);
|
|
|
|
if (!req) {
|
|
if (kthread_should_stop()) {
|
|
set_current_state(TASK_RUNNING);
|
|
break;
|
|
}
|
|
up(&mq->thread_sem);
|
|
schedule();
|
|
down(&mq->thread_sem);
|
|
continue;
|
|
}
|
|
set_current_state(TASK_RUNNING);
|
|
|
|
mq->issue_fn(mq, req);
|
|
} while (1);
|
|
up(&mq->thread_sem);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Generic MMC request handler. This is called for any queue on a
|
|
* particular host. When the host is not busy, we look for a request
|
|
* on any queue on this host, and attempt to issue it. This may
|
|
* not be the queue we were asked to process.
|
|
*/
|
|
static void mmc_request(request_queue_t *q)
|
|
{
|
|
struct mmc_queue *mq = q->queuedata;
|
|
struct request *req;
|
|
int ret;
|
|
|
|
if (!mq) {
|
|
printk(KERN_ERR "MMC: killing requests for dead queue\n");
|
|
while ((req = elv_next_request(q)) != NULL) {
|
|
do {
|
|
ret = end_that_request_chunk(req, 0,
|
|
req->current_nr_sectors << 9);
|
|
} while (ret);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!mq->req)
|
|
wake_up_process(mq->thread);
|
|
}
|
|
|
|
/**
|
|
* mmc_init_queue - initialise a queue structure.
|
|
* @mq: mmc queue
|
|
* @card: mmc card to attach this queue
|
|
* @lock: queue lock
|
|
*
|
|
* Initialise a MMC card request queue.
|
|
*/
|
|
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card, spinlock_t *lock)
|
|
{
|
|
struct mmc_host *host = card->host;
|
|
u64 limit = BLK_BOUNCE_HIGH;
|
|
int ret;
|
|
|
|
if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
|
|
limit = *mmc_dev(host)->dma_mask;
|
|
|
|
mq->card = card;
|
|
mq->queue = blk_init_queue(mmc_request, lock);
|
|
if (!mq->queue)
|
|
return -ENOMEM;
|
|
|
|
blk_queue_prep_rq(mq->queue, mmc_prep_request);
|
|
blk_queue_bounce_limit(mq->queue, limit);
|
|
blk_queue_max_sectors(mq->queue, host->max_req_size / 512);
|
|
blk_queue_max_phys_segments(mq->queue, host->max_phys_segs);
|
|
blk_queue_max_hw_segments(mq->queue, host->max_hw_segs);
|
|
blk_queue_max_segment_size(mq->queue, host->max_seg_size);
|
|
|
|
mq->queue->queuedata = mq;
|
|
mq->req = NULL;
|
|
|
|
mq->sg = kmalloc(sizeof(struct scatterlist) * host->max_phys_segs,
|
|
GFP_KERNEL);
|
|
if (!mq->sg) {
|
|
ret = -ENOMEM;
|
|
goto cleanup_queue;
|
|
}
|
|
|
|
init_MUTEX(&mq->thread_sem);
|
|
|
|
mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd");
|
|
if (IS_ERR(mq->thread)) {
|
|
ret = PTR_ERR(mq->thread);
|
|
goto free_sg;
|
|
}
|
|
|
|
return 0;
|
|
|
|
free_sg:
|
|
kfree(mq->sg);
|
|
mq->sg = NULL;
|
|
cleanup_queue:
|
|
blk_cleanup_queue(mq->queue);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(mmc_init_queue);
|
|
|
|
void mmc_cleanup_queue(struct mmc_queue *mq)
|
|
{
|
|
request_queue_t *q = mq->queue;
|
|
unsigned long flags;
|
|
|
|
/* Mark that we should start throwing out stragglers */
|
|
spin_lock_irqsave(q->queue_lock, flags);
|
|
q->queuedata = NULL;
|
|
spin_unlock_irqrestore(q->queue_lock, flags);
|
|
|
|
/* Then terminate our worker thread */
|
|
kthread_stop(mq->thread);
|
|
|
|
kfree(mq->sg);
|
|
mq->sg = NULL;
|
|
|
|
blk_cleanup_queue(mq->queue);
|
|
|
|
mq->card = NULL;
|
|
}
|
|
EXPORT_SYMBOL(mmc_cleanup_queue);
|
|
|
|
/**
|
|
* mmc_queue_suspend - suspend a MMC request queue
|
|
* @mq: MMC queue to suspend
|
|
*
|
|
* Stop the block request queue, and wait for our thread to
|
|
* complete any outstanding requests. This ensures that we
|
|
* won't suspend while a request is being processed.
|
|
*/
|
|
void mmc_queue_suspend(struct mmc_queue *mq)
|
|
{
|
|
request_queue_t *q = mq->queue;
|
|
unsigned long flags;
|
|
|
|
if (!(mq->flags & MMC_QUEUE_SUSPENDED)) {
|
|
mq->flags |= MMC_QUEUE_SUSPENDED;
|
|
|
|
spin_lock_irqsave(q->queue_lock, flags);
|
|
blk_stop_queue(q);
|
|
spin_unlock_irqrestore(q->queue_lock, flags);
|
|
|
|
down(&mq->thread_sem);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(mmc_queue_suspend);
|
|
|
|
/**
|
|
* mmc_queue_resume - resume a previously suspended MMC request queue
|
|
* @mq: MMC queue to resume
|
|
*/
|
|
void mmc_queue_resume(struct mmc_queue *mq)
|
|
{
|
|
request_queue_t *q = mq->queue;
|
|
unsigned long flags;
|
|
|
|
if (mq->flags & MMC_QUEUE_SUSPENDED) {
|
|
mq->flags &= ~MMC_QUEUE_SUSPENDED;
|
|
|
|
up(&mq->thread_sem);
|
|
|
|
spin_lock_irqsave(q->queue_lock, flags);
|
|
blk_start_queue(q);
|
|
spin_unlock_irqrestore(q->queue_lock, flags);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(mmc_queue_resume);
|