274 lines
7.3 KiB
C
274 lines
7.3 KiB
C
/*
|
|
* Copyright (c) 2013, TripNDroid Mobile Engineering
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <linux/blkdev.h>
|
|
#include <linux/elevator.h>
|
|
#include <linux/bio.h>
|
|
#include <linux/module.h>
|
|
#include <linux/version.h>
|
|
#include <linux/init.h>
|
|
|
|
enum { ASYNC, SYNC };
|
|
|
|
static const int sync_read_expire = 1 * HZ; /* max time before a sync read is submitted. */
|
|
static const int sync_write_expire = 1 * HZ; /* max time before a sync write is submitted. */
|
|
static const int async_read_expire = 2 * HZ; /* ditto for async, these limits are SOFT! */
|
|
static const int async_write_expire = 2 * HZ; /* ditto for async, these limits are SOFT! */
|
|
|
|
static const int writes_starved = 1; /* max times reads can starve a write */
|
|
static const int fifo_batch = 1; /* # of sequential requests treated as one
|
|
by the above parameters. For throughput. */
|
|
|
|
struct tripndroid_data {
|
|
|
|
struct list_head fifo_list[2][2];
|
|
|
|
unsigned int batched;
|
|
unsigned int starved;
|
|
|
|
int fifo_expire[2][2];
|
|
int fifo_batch;
|
|
int writes_starved;
|
|
};
|
|
|
|
static void tripndroid_merged_requests(struct request_queue *q, struct request *rq,
|
|
struct request *next)
|
|
{
|
|
/*
|
|
* If next expires before rq, assign its expire time to rq
|
|
* and move into next position (next will be deleted) in fifo.
|
|
*/
|
|
if (!list_empty(&rq->queuelist) && !list_empty(&next->queuelist)) {
|
|
if (time_before(next->fifo_time, rq->fifo_time)) {
|
|
list_move(&rq->queuelist, &next->queuelist);
|
|
rq->fifo_time = next->fifo_time;
|
|
}
|
|
}
|
|
|
|
rq_fifo_clear(next);
|
|
}
|
|
|
|
static void tripndroid_add_request(struct request_queue *q, struct request *rq)
|
|
{
|
|
struct tripndroid_data *td = q->elevator->elevator_data;
|
|
const int sync = rq_is_sync(rq);
|
|
const int data_dir = rq_data_dir(rq);
|
|
|
|
rq->fifo_time = jiffies + td->fifo_expire[sync][data_dir];
|
|
list_add(&rq->queuelist, &td->fifo_list[sync][data_dir]);
|
|
}
|
|
|
|
static struct request *tripndroid_expired_request(struct tripndroid_data *td, int sync, int data_dir)
|
|
{
|
|
struct list_head *list = &td->fifo_list[sync][data_dir];
|
|
struct request *rq;
|
|
|
|
if (list_empty(list))
|
|
return NULL;
|
|
|
|
rq = rq_entry_fifo(list->next);
|
|
|
|
if (time_after(jiffies, rq->fifo_time))
|
|
return rq;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct request *tripndroid_choose_expired_request(struct tripndroid_data *td)
|
|
{
|
|
struct request *rq;
|
|
|
|
/* Asynchronous requests have priority over synchronous.
|
|
* Write requests have priority over read. */
|
|
|
|
rq = tripndroid_expired_request(td, ASYNC, WRITE);
|
|
if (rq)
|
|
return rq;
|
|
rq = tripndroid_expired_request(td, ASYNC, READ);
|
|
if (rq)
|
|
return rq;
|
|
|
|
rq = tripndroid_expired_request(td, SYNC, WRITE);
|
|
if (rq)
|
|
return rq;
|
|
rq = tripndroid_expired_request(td, SYNC, READ);
|
|
if (rq)
|
|
return rq;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct request *tripndroid_choose_request(struct tripndroid_data *td, int data_dir)
|
|
{
|
|
struct list_head *sync = td->fifo_list[SYNC];
|
|
struct list_head *async = td->fifo_list[ASYNC];
|
|
|
|
if (!list_empty(&sync[data_dir]))
|
|
return rq_entry_fifo(sync[data_dir].next);
|
|
if (!list_empty(&sync[!data_dir]))
|
|
return rq_entry_fifo(sync[!data_dir].next);
|
|
|
|
if (!list_empty(&async[data_dir]))
|
|
return rq_entry_fifo(async[data_dir].next);
|
|
if (!list_empty(&async[!data_dir]))
|
|
return rq_entry_fifo(async[!data_dir].next);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline void tripndroid_dispatch_request(struct tripndroid_data *td, struct request *rq)
|
|
{
|
|
/* Dispatch the request */
|
|
rq_fifo_clear(rq);
|
|
elv_dispatch_add_tail(rq->q, rq);
|
|
|
|
td->batched++;
|
|
|
|
if (rq_data_dir(rq))
|
|
td->starved = 0;
|
|
else
|
|
td->starved++;
|
|
}
|
|
|
|
static int tripndroid_dispatch_requests(struct request_queue *q, int force)
|
|
{
|
|
struct tripndroid_data *td = q->elevator->elevator_data;
|
|
struct request *rq = NULL;
|
|
int data_dir = READ;
|
|
|
|
if (td->batched > td->fifo_batch) {
|
|
td->batched = 0;
|
|
rq = tripndroid_choose_expired_request(td);
|
|
}
|
|
|
|
if (!rq) {
|
|
if (td->starved > td->writes_starved)
|
|
data_dir = WRITE;
|
|
|
|
rq = tripndroid_choose_request(td, data_dir);
|
|
if (!rq)
|
|
return 0;
|
|
}
|
|
|
|
tripndroid_dispatch_request(td, rq);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static struct request *tripndroid_former_request(struct request_queue *q, struct request *rq)
|
|
{
|
|
struct tripndroid_data *td = q->elevator->elevator_data;
|
|
const int sync = rq_is_sync(rq);
|
|
const int data_dir = rq_data_dir(rq);
|
|
|
|
if (rq->queuelist.prev == &td->fifo_list[sync][data_dir])
|
|
return NULL;
|
|
|
|
return list_entry(rq->queuelist.prev, struct request, queuelist);
|
|
}
|
|
|
|
static struct request *tripndroid_latter_request(struct request_queue *q, struct request *rq)
|
|
{
|
|
struct tripndroid_data *td = q->elevator->elevator_data;
|
|
const int sync = rq_is_sync(rq);
|
|
const int data_dir = rq_data_dir(rq);
|
|
|
|
if (rq->queuelist.next == &td->fifo_list[sync][data_dir])
|
|
return NULL;
|
|
|
|
return list_entry(rq->queuelist.next, struct request, queuelist);
|
|
}
|
|
|
|
static int tripndroid_init_queue(struct request_queue *q, struct elevator_type *e)
|
|
{
|
|
struct tripndroid_data *td;
|
|
struct elevator_queue *eq;
|
|
|
|
eq = elevator_alloc(q, e);
|
|
if (!eq)
|
|
return -ENOMEM;
|
|
|
|
td = kmalloc_node(sizeof(*td), GFP_KERNEL, q->node);
|
|
if (!td) {
|
|
kobject_put(&eq->kobj);
|
|
return -ENOMEM;
|
|
}
|
|
eq->elevator_data = td;
|
|
|
|
INIT_LIST_HEAD(&td->fifo_list[SYNC][READ]);
|
|
INIT_LIST_HEAD(&td->fifo_list[SYNC][WRITE]);
|
|
INIT_LIST_HEAD(&td->fifo_list[ASYNC][READ]);
|
|
INIT_LIST_HEAD(&td->fifo_list[ASYNC][WRITE]);
|
|
|
|
td->batched = 0;
|
|
td->fifo_expire[SYNC][READ] = sync_read_expire;
|
|
td->fifo_expire[SYNC][WRITE] = sync_write_expire;
|
|
td->fifo_expire[ASYNC][READ] = async_read_expire;
|
|
td->fifo_expire[ASYNC][WRITE] = async_write_expire;
|
|
td->fifo_batch = fifo_batch;
|
|
|
|
spin_lock_irq(q->queue_lock);
|
|
q->elevator = eq;
|
|
spin_unlock_irq(q->queue_lock);
|
|
return 0;
|
|
}
|
|
|
|
static void tripndroid_exit_queue(struct elevator_queue *e)
|
|
{
|
|
struct tripndroid_data *td = e->elevator_data;
|
|
|
|
BUG_ON(!list_empty(&td->fifo_list[SYNC][READ]));
|
|
BUG_ON(!list_empty(&td->fifo_list[SYNC][WRITE]));
|
|
BUG_ON(!list_empty(&td->fifo_list[ASYNC][READ]));
|
|
BUG_ON(!list_empty(&td->fifo_list[ASYNC][WRITE]));
|
|
|
|
kfree(td);
|
|
}
|
|
|
|
static struct elevator_type iosched_tripndroid = {
|
|
.ops = {
|
|
.elevator_merge_req_fn = tripndroid_merged_requests,
|
|
.elevator_dispatch_fn = tripndroid_dispatch_requests,
|
|
.elevator_add_req_fn = tripndroid_add_request,
|
|
.elevator_former_req_fn = tripndroid_former_request,
|
|
.elevator_latter_req_fn = tripndroid_latter_request,
|
|
.elevator_init_fn = tripndroid_init_queue,
|
|
.elevator_exit_fn = tripndroid_exit_queue,
|
|
},
|
|
.elevator_name = "tripndroid",
|
|
.elevator_owner = THIS_MODULE,
|
|
};
|
|
|
|
static int __init tripndroid_init(void)
|
|
{
|
|
elv_register(&iosched_tripndroid);
|
|
return 0;
|
|
}
|
|
|
|
static void __exit tripndroid_exit(void)
|
|
{
|
|
elv_unregister(&iosched_tripndroid);
|
|
}
|
|
|
|
module_init(tripndroid_init);
|
|
module_exit(tripndroid_exit);
|
|
|
|
MODULE_AUTHOR("TripNRaVeR");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("TripNDroid IO Scheduler");
|