bd788c9665
Enable MMC to service discard requests. In the case of SD and MMC cards that do not support trim, discards become erases. In the case of cards (MMC) that only allow erases in multiples of erase group size, round to the nearest completely discarded erase group. Signed-off-by: Adrian Hunter <adrian.hunter@nokia.com> Acked-by: Jens Axboe <axboe@kernel.dk> Cc: Kyungmin Park <kmpark@infradead.org> Cc: Madhusudhan Chikkature <madhu.cr@ti.com> Cc: Christoph Hellwig <hch@lst.de> Cc: Ben Gardiner <bengardiner@nanometrics.ca> Cc: <linux-mmc@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
376 lines
8.4 KiB
C
376 lines
8.4 KiB
C
/*
|
|
* linux/drivers/mmc/card/queue.c
|
|
*
|
|
* Copyright (C) 2003 Russell King, All Rights Reserved.
|
|
* Copyright 2006-2007 Pierre Ossman
|
|
*
|
|
* 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/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/blkdev.h>
|
|
#include <linux/freezer.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/scatterlist.h>
|
|
|
|
#include <linux/mmc/card.h>
|
|
#include <linux/mmc/host.h>
|
|
#include "queue.h"
|
|
|
|
#define MMC_QUEUE_BOUNCESZ 65536
|
|
|
|
#define MMC_QUEUE_SUSPENDED (1 << 0)
|
|
|
|
/*
|
|
* Prepare a MMC request. This just filters out odd stuff.
|
|
*/
|
|
static int mmc_prep_request(struct request_queue *q, struct request *req)
|
|
{
|
|
/*
|
|
* We only like normal block requests and discards.
|
|
*/
|
|
if (req->cmd_type != REQ_TYPE_FS && !(req->cmd_flags & REQ_DISCARD)) {
|
|
blk_dump_rq_flags(req, "MMC bad request");
|
|
return BLKPREP_KILL;
|
|
}
|
|
|
|
req->cmd_flags |= REQ_DONTPREP;
|
|
|
|
return BLKPREP_OK;
|
|
}
|
|
|
|
static int mmc_queue_thread(void *d)
|
|
{
|
|
struct mmc_queue *mq = d;
|
|
struct request_queue *q = mq->queue;
|
|
|
|
current->flags |= PF_MEMALLOC;
|
|
|
|
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 = blk_fetch_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(struct request_queue *q)
|
|
{
|
|
struct mmc_queue *mq = q->queuedata;
|
|
struct request *req;
|
|
|
|
if (!mq) {
|
|
while ((req = blk_fetch_request(q)) != NULL) {
|
|
req->cmd_flags |= REQ_QUIET;
|
|
__blk_end_request_all(req, -EIO);
|
|
}
|
|
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;
|
|
|
|
mq->queue->queuedata = mq;
|
|
mq->req = NULL;
|
|
|
|
blk_queue_prep_rq(mq->queue, mmc_prep_request);
|
|
blk_queue_ordered(mq->queue, QUEUE_ORDERED_DRAIN);
|
|
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
|
|
if (mmc_can_erase(card)) {
|
|
queue_flag_set_unlocked(QUEUE_FLAG_DISCARD, mq->queue);
|
|
mq->queue->limits.max_discard_sectors = UINT_MAX;
|
|
if (card->erased_byte == 0)
|
|
mq->queue->limits.discard_zeroes_data = 1;
|
|
if (!mmc_can_trim(card) && is_power_of_2(card->erase_size)) {
|
|
mq->queue->limits.discard_granularity =
|
|
card->erase_size << 9;
|
|
mq->queue->limits.discard_alignment =
|
|
card->erase_size << 9;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_MMC_BLOCK_BOUNCE
|
|
if (host->max_hw_segs == 1) {
|
|
unsigned int bouncesz;
|
|
|
|
bouncesz = MMC_QUEUE_BOUNCESZ;
|
|
|
|
if (bouncesz > host->max_req_size)
|
|
bouncesz = host->max_req_size;
|
|
if (bouncesz > host->max_seg_size)
|
|
bouncesz = host->max_seg_size;
|
|
if (bouncesz > (host->max_blk_count * 512))
|
|
bouncesz = host->max_blk_count * 512;
|
|
|
|
if (bouncesz > 512) {
|
|
mq->bounce_buf = kmalloc(bouncesz, GFP_KERNEL);
|
|
if (!mq->bounce_buf) {
|
|
printk(KERN_WARNING "%s: unable to "
|
|
"allocate bounce buffer\n",
|
|
mmc_card_name(card));
|
|
}
|
|
}
|
|
|
|
if (mq->bounce_buf) {
|
|
blk_queue_bounce_limit(mq->queue, BLK_BOUNCE_ANY);
|
|
blk_queue_max_hw_sectors(mq->queue, bouncesz / 512);
|
|
blk_queue_max_segments(mq->queue, bouncesz / 512);
|
|
blk_queue_max_segment_size(mq->queue, bouncesz);
|
|
|
|
mq->sg = kmalloc(sizeof(struct scatterlist),
|
|
GFP_KERNEL);
|
|
if (!mq->sg) {
|
|
ret = -ENOMEM;
|
|
goto cleanup_queue;
|
|
}
|
|
sg_init_table(mq->sg, 1);
|
|
|
|
mq->bounce_sg = kmalloc(sizeof(struct scatterlist) *
|
|
bouncesz / 512, GFP_KERNEL);
|
|
if (!mq->bounce_sg) {
|
|
ret = -ENOMEM;
|
|
goto cleanup_queue;
|
|
}
|
|
sg_init_table(mq->bounce_sg, bouncesz / 512);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!mq->bounce_buf) {
|
|
blk_queue_bounce_limit(mq->queue, limit);
|
|
blk_queue_max_hw_sectors(mq->queue,
|
|
min(host->max_blk_count, host->max_req_size / 512));
|
|
blk_queue_max_segments(mq->queue, host->max_hw_segs);
|
|
blk_queue_max_segment_size(mq->queue, host->max_seg_size);
|
|
|
|
mq->sg = kmalloc(sizeof(struct scatterlist) *
|
|
host->max_phys_segs, GFP_KERNEL);
|
|
if (!mq->sg) {
|
|
ret = -ENOMEM;
|
|
goto cleanup_queue;
|
|
}
|
|
sg_init_table(mq->sg, host->max_phys_segs);
|
|
}
|
|
|
|
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_bounce_sg;
|
|
}
|
|
|
|
return 0;
|
|
free_bounce_sg:
|
|
if (mq->bounce_sg)
|
|
kfree(mq->bounce_sg);
|
|
mq->bounce_sg = NULL;
|
|
cleanup_queue:
|
|
if (mq->sg)
|
|
kfree(mq->sg);
|
|
mq->sg = NULL;
|
|
if (mq->bounce_buf)
|
|
kfree(mq->bounce_buf);
|
|
mq->bounce_buf = NULL;
|
|
blk_cleanup_queue(mq->queue);
|
|
return ret;
|
|
}
|
|
|
|
void mmc_cleanup_queue(struct mmc_queue *mq)
|
|
{
|
|
struct request_queue *q = mq->queue;
|
|
unsigned long flags;
|
|
|
|
/* Make sure the queue isn't suspended, as that will deadlock */
|
|
mmc_queue_resume(mq);
|
|
|
|
/* Then terminate our worker thread */
|
|
kthread_stop(mq->thread);
|
|
|
|
/* Empty the queue */
|
|
spin_lock_irqsave(q->queue_lock, flags);
|
|
q->queuedata = NULL;
|
|
blk_start_queue(q);
|
|
spin_unlock_irqrestore(q->queue_lock, flags);
|
|
|
|
if (mq->bounce_sg)
|
|
kfree(mq->bounce_sg);
|
|
mq->bounce_sg = NULL;
|
|
|
|
kfree(mq->sg);
|
|
mq->sg = NULL;
|
|
|
|
if (mq->bounce_buf)
|
|
kfree(mq->bounce_buf);
|
|
mq->bounce_buf = NULL;
|
|
|
|
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)
|
|
{
|
|
struct request_queue *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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* mmc_queue_resume - resume a previously suspended MMC request queue
|
|
* @mq: MMC queue to resume
|
|
*/
|
|
void mmc_queue_resume(struct mmc_queue *mq)
|
|
{
|
|
struct request_queue *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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prepare the sg list(s) to be handed of to the host driver
|
|
*/
|
|
unsigned int mmc_queue_map_sg(struct mmc_queue *mq)
|
|
{
|
|
unsigned int sg_len;
|
|
size_t buflen;
|
|
struct scatterlist *sg;
|
|
int i;
|
|
|
|
if (!mq->bounce_buf)
|
|
return blk_rq_map_sg(mq->queue, mq->req, mq->sg);
|
|
|
|
BUG_ON(!mq->bounce_sg);
|
|
|
|
sg_len = blk_rq_map_sg(mq->queue, mq->req, mq->bounce_sg);
|
|
|
|
mq->bounce_sg_len = sg_len;
|
|
|
|
buflen = 0;
|
|
for_each_sg(mq->bounce_sg, sg, sg_len, i)
|
|
buflen += sg->length;
|
|
|
|
sg_init_one(mq->sg, mq->bounce_buf, buflen);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* If writing, bounce the data to the buffer before the request
|
|
* is sent to the host driver
|
|
*/
|
|
void mmc_queue_bounce_pre(struct mmc_queue *mq)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (!mq->bounce_buf)
|
|
return;
|
|
|
|
if (rq_data_dir(mq->req) != WRITE)
|
|
return;
|
|
|
|
local_irq_save(flags);
|
|
sg_copy_to_buffer(mq->bounce_sg, mq->bounce_sg_len,
|
|
mq->bounce_buf, mq->sg[0].length);
|
|
local_irq_restore(flags);
|
|
}
|
|
|
|
/*
|
|
* If reading, bounce the data from the buffer after the request
|
|
* has been handled by the host driver
|
|
*/
|
|
void mmc_queue_bounce_post(struct mmc_queue *mq)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (!mq->bounce_buf)
|
|
return;
|
|
|
|
if (rq_data_dir(mq->req) != READ)
|
|
return;
|
|
|
|
local_irq_save(flags);
|
|
sg_copy_from_buffer(mq->bounce_sg, mq->bounce_sg_len,
|
|
mq->bounce_buf, mq->sg[0].length);
|
|
local_irq_restore(flags);
|
|
}
|
|
|