| /* cyanblkdev_queue.h - Antioch Linux Block Driver queue source file |
| ## =========================== |
| ## Copyright (C) 2010 Cypress Semiconductor |
| ## |
| ## This program is free software; you can redistribute it and/or |
| ## modify it under the terms of the GNU General Public License |
| ## as published by the Free Software Foundation; either version 2 |
| ## of the License, or (at your option) any later version. |
| ## |
| ## This program is distributed in the hope that it will be useful, |
| ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
| ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| ## GNU General Public License for more details. |
| ## |
| ## You should have received a copy of the GNU General Public License |
| ## along with this program; if not, write to the Free Software |
| ## Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| ## Boston, MA 02110-1301, USA. |
| ## =========================== |
| */ |
| |
| /* |
| * Request queue handling for Antioch block device driver. |
| * Based on the mmc queue handling code by Russell King in the |
| * linux 2.6.10 kernel. |
| */ |
| |
| /* |
| * 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 "cyasblkdev_queue.h" |
| |
| #define CYASBLKDEV_QUEUE_EXIT (1 << 0) |
| #define CYASBLKDEV_QUEUE_SUSPENDED (1 << 1) |
| #define CY_AS_USE_ASYNC_API |
| |
| |
| |
| /* print flags by name */ |
| const char *rq_flag_bit_names[] = { |
| "REQ_RW", /* not set, read. set, write */ |
| "REQ_FAILFAST", /* no low level driver retries */ |
| "REQ_SORTED", /* elevator knows about this request */ |
| "REQ_SOFTBARRIER", /* may not be passed by ioscheduler */ |
| "REQ_HARDBARRIER", /* may not be passed by drive either */ |
| "REQ_FUA", /* forced unit access */ |
| "REQ_NOMERGE", /* don't touch this for merging */ |
| "REQ_STARTED", /* drive already may have started this one */ |
| "REQ_DONTPREP", /* don't call prep for this one */ |
| "REQ_QUEUED", /* uses queueing */ |
| "REQ_ELVPRIV", /* elevator private data attached */ |
| "REQ_FAILED", /* set if the request failed */ |
| "REQ_QUIET", /* don't worry about errors */ |
| "REQ_PREEMPT", /* set for "ide_preempt" requests */ |
| "REQ_ORDERED_COLOR",/* is before or after barrier */ |
| "REQ_RW_SYNC", /* request is sync (O_DIRECT) */ |
| "REQ_ALLOCED", /* request came from our alloc pool */ |
| "REQ_RW_META", /* metadata io request */ |
| "REQ_COPY_USER", /* contains copies of user pages */ |
| "REQ_NR_BITS", /* stops here */ |
| }; |
| |
| void verbose_rq_flags(int flags) |
| { |
| int i; |
| uint32_t j; |
| j = 1; |
| for (i = 0; i < 32; i++) { |
| if (flags & j) |
| DBGPRN("<1>%s", rq_flag_bit_names[i]); |
| j = j << 1; |
| } |
| } |
| |
| |
| /* |
| * Prepare a -BLK_DEV request. Essentially, this means passing the |
| * preparation off to the media driver. The media driver will |
| * create request to CyAsDev. |
| */ |
| static int cyasblkdev_prep_request( |
| struct request_queue *q, struct request *req) |
| { |
| DBGPRN_FUNC_NAME; |
| |
| /* we only like normal block requests.*/ |
| if (req->cmd_type != REQ_TYPE_FS && !(req->cmd_flags & REQ_DISCARD)) { |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message("%s:%x bad request received\n", |
| __func__, current->pid); |
| #endif |
| |
| blk_dump_rq_flags(req, "cyasblkdev bad request"); |
| return BLKPREP_KILL; |
| } |
| |
| req->cmd_flags |= REQ_DONTPREP; |
| |
| return BLKPREP_OK; |
| } |
| |
| /* queue worker thread */ |
| static int cyasblkdev_queue_thread(void *d) |
| { |
| DECLARE_WAITQUEUE(wait, current); |
| struct cyasblkdev_queue *bq = d; |
| struct request_queue *q = bq->queue; |
| u32 qth_pid; |
| |
| DBGPRN_FUNC_NAME; |
| |
| /* |
| * set iothread to ensure that we aren't put to sleep by |
| * the process freezing. we handle suspension ourselves. |
| */ |
| daemonize("cyasblkdev_queue_thread"); |
| |
| /* signal to queue_init() so it could contnue */ |
| complete(&bq->thread_complete); |
| |
| down(&bq->thread_sem); |
| add_wait_queue(&bq->thread_wq, &wait); |
| |
| qth_pid = current->pid; |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s:%x started, bq:%p, q:%p\n", __func__, qth_pid, bq, q); |
| #endif |
| |
| do { |
| struct request *req = NULL; |
| |
| /* the thread wants to be woken up by signals as well */ |
| set_current_state(TASK_INTERRUPTIBLE); |
| |
| spin_lock_irq(q->queue_lock); |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: for bq->queue is null\n", __func__); |
| #endif |
| |
| if (!bq->req) { |
| /* chk if queue is plugged */ |
| if (!blk_queue_plugged(q)) { |
| bq->req = req = blk_fetch_request(q); |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: blk_fetch_request:%x\n", |
| __func__, (uint32_t)req); |
| #endif |
| } else { |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: queue plugged, " |
| "skip blk_fetch()\n", __func__); |
| #endif |
| } |
| } |
| spin_unlock_irq(q->queue_lock); |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: checking if request queue is null\n", __func__); |
| #endif |
| |
| if (!req) { |
| if (bq->flags & CYASBLKDEV_QUEUE_EXIT) { |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s:got QUEUE_EXIT flag\n", __func__); |
| #endif |
| |
| break; |
| } |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: request queue is null, goto sleep, " |
| "thread_sem->count=%d\n", |
| __func__, bq->thread_sem.count); |
| if (spin_is_locked(q->queue_lock)) { |
| cy_as_hal_print_message("%s: queue_lock " |
| "is locked, need to release\n", __func__); |
| spin_unlock(q->queue_lock); |
| |
| if (spin_is_locked(q->queue_lock)) |
| cy_as_hal_print_message( |
| "%s: unlock did not work\n", |
| __func__); |
| } else { |
| cy_as_hal_print_message( |
| "%s: checked lock, is not locked\n", |
| __func__); |
| } |
| #endif |
| |
| up(&bq->thread_sem); |
| |
| /* yields to the next rdytorun proc, |
| * then goes back to sleep*/ |
| schedule(); |
| down(&bq->thread_sem); |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: wake_up,continue\n", |
| __func__); |
| #endif |
| continue; |
| } |
| |
| /* new req received, issue it to the driver */ |
| set_current_state(TASK_RUNNING); |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: issued a RQ:%x\n", |
| __func__, (uint32_t)req); |
| #endif |
| |
| bq->issue_fn(bq, req); |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s: bq->issue_fn() returned\n", |
| __func__); |
| #endif |
| |
| |
| } while (1); |
| |
| set_current_state(TASK_RUNNING); |
| remove_wait_queue(&bq->thread_wq, &wait); |
| up(&bq->thread_sem); |
| |
| complete_and_exit(&bq->thread_complete, 0); |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message("%s: is finished\n", __func__); |
| #endif |
| |
| return 0; |
| } |
| |
| /* |
| * Generic request handler. it 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 cyasblkdev_request(struct request_queue *q) |
| { |
| struct cyasblkdev_queue *bq = q->queuedata; |
| DBGPRN_FUNC_NAME; |
| |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message( |
| "%s new request on cyasblkdev_queue_t bq:=%x\n", |
| __func__, (uint32_t)bq); |
| #endif |
| |
| if (!bq->req) { |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message("%s wake_up(&bq->thread_wq)\n", |
| __func__); |
| #endif |
| |
| /* wake up cyasblkdev_queue worker thread*/ |
| wake_up(&bq->thread_wq); |
| } else { |
| #ifndef WESTBRIDGE_NDEBUG |
| cy_as_hal_print_message("%s: don't wake Q_thr, bq->req:%x\n", |
| __func__, (uint32_t)bq->req); |
| #endif |
| } |
| } |
| |
| /* |
| * cyasblkdev_init_queue - initialise a queue structure. |
| * @bq: cyasblkdev queue |
| * @dev: CyAsDeviceHandle to attach this queue |
| * @lock: queue lock |
| * |
| * Initialise a cyasblkdev_request queue. |
| */ |
| |
| /* MAX NUMBER OF SECTORS PER REQUEST **/ |
| #define Q_MAX_SECTORS 128 |
| |
| /* MAX NUMBER OF PHYS SEGMENTS (entries in the SG list)*/ |
| #define Q_MAX_SGS 16 |
| |
| int cyasblkdev_init_queue(struct cyasblkdev_queue *bq, spinlock_t *lock) |
| { |
| int ret; |
| |
| DBGPRN_FUNC_NAME; |
| |
| /* 1st param is a function that wakes up the queue thread */ |
| bq->queue = blk_init_queue(cyasblkdev_request, lock); |
| if (!bq->queue) |
| return -ENOMEM; |
| |
| blk_queue_prep_rq(bq->queue, cyasblkdev_prep_request); |
| |
| blk_queue_bounce_limit(bq->queue, BLK_BOUNCE_ANY); |
| blk_queue_max_hw_sectors(bq->queue, Q_MAX_SECTORS); |
| |
| /* As of now, we have the HAL/driver support to |
| * merge scattered segments and handle them simultaneously. |
| * so, setting the max_phys_segments to 8. */ |
| /*blk_queue_max_phys_segments(bq->queue, Q_MAX_SGS); |
| blk_queue_max_hw_segments(bq->queue, Q_MAX_SGS);*/ |
| blk_queue_max_segments(bq->queue, Q_MAX_SGS); |
| |
| /* should be < then HAL can handle */ |
| blk_queue_max_segment_size(bq->queue, 512*Q_MAX_SECTORS); |
| |
| bq->queue->queuedata = bq; |
| bq->req = NULL; |
| |
| init_completion(&bq->thread_complete); |
| init_waitqueue_head(&bq->thread_wq); |
| sema_init(&bq->thread_sem, 1); |
| |
| ret = kernel_thread(cyasblkdev_queue_thread, bq, CLONE_KERNEL); |
| if (ret >= 0) { |
| /* wait until the thread is spawned */ |
| wait_for_completion(&bq->thread_complete); |
| |
| /* reinitialize the completion */ |
| init_completion(&bq->thread_complete); |
| ret = 0; |
| goto out; |
| } |
| |
| out: |
| return ret; |
| } |
| EXPORT_SYMBOL(cyasblkdev_init_queue); |
| |
| /*called from blk_put() */ |
| void cyasblkdev_cleanup_queue(struct cyasblkdev_queue *bq) |
| { |
| DBGPRN_FUNC_NAME; |
| |
| bq->flags |= CYASBLKDEV_QUEUE_EXIT; |
| wake_up(&bq->thread_wq); |
| wait_for_completion(&bq->thread_complete); |
| |
| blk_cleanup_queue(bq->queue); |
| } |
| EXPORT_SYMBOL(cyasblkdev_cleanup_queue); |
| |
| |
| /** |
| * cyasblkdev_queue_suspend - suspend a CyAsBlkDev request queue |
| * @bq: CyAsBlkDev 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 cyasblkdev_queue_suspend(struct cyasblkdev_queue *bq) |
| { |
| struct request_queue *q = bq->queue; |
| unsigned long flags; |
| |
| DBGPRN_FUNC_NAME; |
| |
| if (!(bq->flags & CYASBLKDEV_QUEUE_SUSPENDED)) { |
| bq->flags |= CYASBLKDEV_QUEUE_SUSPENDED; |
| |
| spin_lock_irqsave(q->queue_lock, flags); |
| blk_stop_queue(q); |
| spin_unlock_irqrestore(q->queue_lock, flags); |
| |
| down(&bq->thread_sem); |
| } |
| } |
| EXPORT_SYMBOL(cyasblkdev_queue_suspend); |
| |
| /*cyasblkdev_queue_resume - resume a previously suspended |
| * CyAsBlkDev request queue @bq: CyAsBlkDev queue to resume */ |
| void cyasblkdev_queue_resume(struct cyasblkdev_queue *bq) |
| { |
| struct request_queue *q = bq->queue; |
| unsigned long flags; |
| |
| DBGPRN_FUNC_NAME; |
| |
| if (bq->flags & CYASBLKDEV_QUEUE_SUSPENDED) { |
| bq->flags &= ~CYASBLKDEV_QUEUE_SUSPENDED; |
| |
| up(&bq->thread_sem); |
| |
| spin_lock_irqsave(q->queue_lock, flags); |
| blk_start_queue(q); |
| spin_unlock_irqrestore(q->queue_lock, flags); |
| } |
| } |
| EXPORT_SYMBOL(cyasblkdev_queue_resume); |
| |
| /*[]*/ |