mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-28 18:51:56 -04:00
Merge branch 'block-dir' of git://brick.kernel.dk/data/git/linux-2.6-block
This commit is contained in:
commit
333c47c847
16 changed files with 32 additions and 30 deletions
|
|
@ -409,16 +409,6 @@ config BLK_DEV_INITRD
|
|||
for details.
|
||||
|
||||
|
||||
#XXX - it makes sense to enable this only for 32-bit subarch's, not for x86_64
|
||||
#for instance.
|
||||
config LBD
|
||||
bool "Support for Large Block Devices"
|
||||
depends on X86 || (MIPS && 32BIT) || PPC32 || ARCH_S390_31 || SUPERH || UML
|
||||
help
|
||||
Say Y here if you want to attach large (bigger than 2TB) discs to
|
||||
your machine, or if you want to have a raid or loopback device
|
||||
bigger than 2TB. Otherwise say N.
|
||||
|
||||
config CDROM_PKTCDVD
|
||||
tristate "Packet writing on CD/DVD media"
|
||||
depends on !UML
|
||||
|
|
@ -455,8 +445,6 @@ config CDROM_PKTCDVD_WCACHE
|
|||
|
||||
source "drivers/s390/block/Kconfig"
|
||||
|
||||
source "drivers/block/Kconfig.iosched"
|
||||
|
||||
config ATA_OVER_ETH
|
||||
tristate "ATA over Ethernet support"
|
||||
depends on NET
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
|
||||
menu "IO Schedulers"
|
||||
|
||||
config IOSCHED_NOOP
|
||||
bool
|
||||
default y
|
||||
---help---
|
||||
The no-op I/O scheduler is a minimal scheduler that does basic merging
|
||||
and sorting. Its main uses include non-disk based block devices like
|
||||
memory devices, and specialised software or hardware environments
|
||||
that do their own scheduling and require only minimal assistance from
|
||||
the kernel.
|
||||
|
||||
config IOSCHED_AS
|
||||
tristate "Anticipatory I/O scheduler"
|
||||
default y
|
||||
---help---
|
||||
The anticipatory I/O scheduler is the default disk scheduler. It is
|
||||
generally a good choice for most environments, but is quite large and
|
||||
complex when compared to the deadline I/O scheduler, it can also be
|
||||
slower in some cases especially some database loads.
|
||||
|
||||
config IOSCHED_DEADLINE
|
||||
tristate "Deadline I/O scheduler"
|
||||
default y
|
||||
---help---
|
||||
The deadline I/O scheduler is simple and compact, and is often as
|
||||
good as the anticipatory I/O scheduler, and in some database
|
||||
workloads, better. In the case of a single process performing I/O to
|
||||
a disk at any one time, its behaviour is almost identical to the
|
||||
anticipatory I/O scheduler and so is a good choice.
|
||||
|
||||
config IOSCHED_CFQ
|
||||
tristate "CFQ I/O scheduler"
|
||||
default y
|
||||
---help---
|
||||
The CFQ I/O scheduler tries to distribute bandwidth equally
|
||||
among all processes in the system. It should provide a fair
|
||||
working environment, suitable for desktop systems.
|
||||
|
||||
choice
|
||||
prompt "Default I/O scheduler"
|
||||
default DEFAULT_AS
|
||||
help
|
||||
Select the I/O scheduler which will be used by default for all
|
||||
block devices.
|
||||
|
||||
config DEFAULT_AS
|
||||
bool "Anticipatory" if IOSCHED_AS
|
||||
|
||||
config DEFAULT_DEADLINE
|
||||
bool "Deadline" if IOSCHED_DEADLINE
|
||||
|
||||
config DEFAULT_CFQ
|
||||
bool "CFQ" if IOSCHED_CFQ
|
||||
|
||||
config DEFAULT_NOOP
|
||||
bool "No-op"
|
||||
|
||||
endchoice
|
||||
|
||||
config DEFAULT_IOSCHED
|
||||
string
|
||||
default "anticipatory" if DEFAULT_AS
|
||||
default "deadline" if DEFAULT_DEADLINE
|
||||
default "cfq" if DEFAULT_CFQ
|
||||
default "noop" if DEFAULT_NOOP
|
||||
|
||||
endmenu
|
||||
|
|
@ -4,21 +4,7 @@
|
|||
# 12 June 2000, Christoph Hellwig <[email protected]>
|
||||
# Rewritten to use lists instead of if-statements.
|
||||
#
|
||||
# Note : at this point, these files are compiled on all systems.
|
||||
# In the future, some of these should be built conditionally.
|
||||
#
|
||||
|
||||
#
|
||||
# NOTE that ll_rw_blk.c must come early in linkage order - it starts the
|
||||
# kblockd threads
|
||||
#
|
||||
|
||||
obj-y := elevator.o ll_rw_blk.o ioctl.o genhd.o scsi_ioctl.o
|
||||
|
||||
obj-$(CONFIG_IOSCHED_NOOP) += noop-iosched.o
|
||||
obj-$(CONFIG_IOSCHED_AS) += as-iosched.o
|
||||
obj-$(CONFIG_IOSCHED_DEADLINE) += deadline-iosched.o
|
||||
obj-$(CONFIG_IOSCHED_CFQ) += cfq-iosched.o
|
||||
obj-$(CONFIG_MAC_FLOPPY) += swim3.o
|
||||
obj-$(CONFIG_BLK_DEV_FD) += floppy.o
|
||||
obj-$(CONFIG_BLK_DEV_FD98) += floppy98.o
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,878 +0,0 @@
|
|||
/*
|
||||
* linux/drivers/block/deadline-iosched.c
|
||||
*
|
||||
* Deadline i/o scheduler.
|
||||
*
|
||||
* Copyright (C) 2002 Jens Axboe <axboe@suse.de>
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/elevator.h>
|
||||
#include <linux/bio.h>
|
||||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/hash.h>
|
||||
#include <linux/rbtree.h>
|
||||
|
||||
/*
|
||||
* See Documentation/block/deadline-iosched.txt
|
||||
*/
|
||||
static int read_expire = HZ / 2; /* max time before a read is submitted. */
|
||||
static int write_expire = 5 * HZ; /* ditto for writes, these limits are SOFT! */
|
||||
static int writes_starved = 2; /* max times reads can starve a write */
|
||||
static int fifo_batch = 16; /* # of sequential requests treated as one
|
||||
by the above parameters. For throughput. */
|
||||
|
||||
static const int deadline_hash_shift = 5;
|
||||
#define DL_HASH_BLOCK(sec) ((sec) >> 3)
|
||||
#define DL_HASH_FN(sec) (hash_long(DL_HASH_BLOCK((sec)), deadline_hash_shift))
|
||||
#define DL_HASH_ENTRIES (1 << deadline_hash_shift)
|
||||
#define rq_hash_key(rq) ((rq)->sector + (rq)->nr_sectors)
|
||||
#define list_entry_hash(ptr) list_entry((ptr), struct deadline_rq, hash)
|
||||
#define ON_HASH(drq) (drq)->on_hash
|
||||
|
||||
struct deadline_data {
|
||||
/*
|
||||
* run time data
|
||||
*/
|
||||
|
||||
/*
|
||||
* requests (deadline_rq s) are present on both sort_list and fifo_list
|
||||
*/
|
||||
struct rb_root sort_list[2];
|
||||
struct list_head fifo_list[2];
|
||||
|
||||
/*
|
||||
* next in sort order. read, write or both are NULL
|
||||
*/
|
||||
struct deadline_rq *next_drq[2];
|
||||
struct list_head *hash; /* request hash */
|
||||
unsigned int batching; /* number of sequential requests made */
|
||||
sector_t last_sector; /* head position */
|
||||
unsigned int starved; /* times reads have starved writes */
|
||||
|
||||
/*
|
||||
* settings that change how the i/o scheduler behaves
|
||||
*/
|
||||
int fifo_expire[2];
|
||||
int fifo_batch;
|
||||
int writes_starved;
|
||||
int front_merges;
|
||||
|
||||
mempool_t *drq_pool;
|
||||
};
|
||||
|
||||
/*
|
||||
* pre-request data.
|
||||
*/
|
||||
struct deadline_rq {
|
||||
/*
|
||||
* rbtree index, key is the starting offset
|
||||
*/
|
||||
struct rb_node rb_node;
|
||||
sector_t rb_key;
|
||||
|
||||
struct request *request;
|
||||
|
||||
/*
|
||||
* request hash, key is the ending offset (for back merge lookup)
|
||||
*/
|
||||
struct list_head hash;
|
||||
char on_hash;
|
||||
|
||||
/*
|
||||
* expire fifo
|
||||
*/
|
||||
struct list_head fifo;
|
||||
unsigned long expires;
|
||||
};
|
||||
|
||||
static void deadline_move_request(struct deadline_data *dd, struct deadline_rq *drq);
|
||||
|
||||
static kmem_cache_t *drq_pool;
|
||||
|
||||
#define RQ_DATA(rq) ((struct deadline_rq *) (rq)->elevator_private)
|
||||
|
||||
/*
|
||||
* the back merge hash support functions
|
||||
*/
|
||||
static inline void __deadline_del_drq_hash(struct deadline_rq *drq)
|
||||
{
|
||||
drq->on_hash = 0;
|
||||
list_del_init(&drq->hash);
|
||||
}
|
||||
|
||||
static inline void deadline_del_drq_hash(struct deadline_rq *drq)
|
||||
{
|
||||
if (ON_HASH(drq))
|
||||
__deadline_del_drq_hash(drq);
|
||||
}
|
||||
|
||||
static inline void
|
||||
deadline_add_drq_hash(struct deadline_data *dd, struct deadline_rq *drq)
|
||||
{
|
||||
struct request *rq = drq->request;
|
||||
|
||||
BUG_ON(ON_HASH(drq));
|
||||
|
||||
drq->on_hash = 1;
|
||||
list_add(&drq->hash, &dd->hash[DL_HASH_FN(rq_hash_key(rq))]);
|
||||
}
|
||||
|
||||
/*
|
||||
* move hot entry to front of chain
|
||||
*/
|
||||
static inline void
|
||||
deadline_hot_drq_hash(struct deadline_data *dd, struct deadline_rq *drq)
|
||||
{
|
||||
struct request *rq = drq->request;
|
||||
struct list_head *head = &dd->hash[DL_HASH_FN(rq_hash_key(rq))];
|
||||
|
||||
if (ON_HASH(drq) && drq->hash.prev != head) {
|
||||
list_del(&drq->hash);
|
||||
list_add(&drq->hash, head);
|
||||
}
|
||||
}
|
||||
|
||||
static struct request *
|
||||
deadline_find_drq_hash(struct deadline_data *dd, sector_t offset)
|
||||
{
|
||||
struct list_head *hash_list = &dd->hash[DL_HASH_FN(offset)];
|
||||
struct list_head *entry, *next = hash_list->next;
|
||||
|
||||
while ((entry = next) != hash_list) {
|
||||
struct deadline_rq *drq = list_entry_hash(entry);
|
||||
struct request *__rq = drq->request;
|
||||
|
||||
next = entry->next;
|
||||
|
||||
BUG_ON(!ON_HASH(drq));
|
||||
|
||||
if (!rq_mergeable(__rq)) {
|
||||
__deadline_del_drq_hash(drq);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rq_hash_key(__rq) == offset)
|
||||
return __rq;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* rb tree support functions
|
||||
*/
|
||||
#define RB_NONE (2)
|
||||
#define RB_EMPTY(root) ((root)->rb_node == NULL)
|
||||
#define ON_RB(node) ((node)->rb_color != RB_NONE)
|
||||
#define RB_CLEAR(node) ((node)->rb_color = RB_NONE)
|
||||
#define rb_entry_drq(node) rb_entry((node), struct deadline_rq, rb_node)
|
||||
#define DRQ_RB_ROOT(dd, drq) (&(dd)->sort_list[rq_data_dir((drq)->request)])
|
||||
#define rq_rb_key(rq) (rq)->sector
|
||||
|
||||
static struct deadline_rq *
|
||||
__deadline_add_drq_rb(struct deadline_data *dd, struct deadline_rq *drq)
|
||||
{
|
||||
struct rb_node **p = &DRQ_RB_ROOT(dd, drq)->rb_node;
|
||||
struct rb_node *parent = NULL;
|
||||
struct deadline_rq *__drq;
|
||||
|
||||
while (*p) {
|
||||
parent = *p;
|
||||
__drq = rb_entry_drq(parent);
|
||||
|
||||
if (drq->rb_key < __drq->rb_key)
|
||||
p = &(*p)->rb_left;
|
||||
else if (drq->rb_key > __drq->rb_key)
|
||||
p = &(*p)->rb_right;
|
||||
else
|
||||
return __drq;
|
||||
}
|
||||
|
||||
rb_link_node(&drq->rb_node, parent, p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
deadline_add_drq_rb(struct deadline_data *dd, struct deadline_rq *drq)
|
||||
{
|
||||
struct deadline_rq *__alias;
|
||||
|
||||
drq->rb_key = rq_rb_key(drq->request);
|
||||
|
||||
retry:
|
||||
__alias = __deadline_add_drq_rb(dd, drq);
|
||||
if (!__alias) {
|
||||
rb_insert_color(&drq->rb_node, DRQ_RB_ROOT(dd, drq));
|
||||
return;
|
||||
}
|
||||
|
||||
deadline_move_request(dd, __alias);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
static inline void
|
||||
deadline_del_drq_rb(struct deadline_data *dd, struct deadline_rq *drq)
|
||||
{
|
||||
const int data_dir = rq_data_dir(drq->request);
|
||||
|
||||
if (dd->next_drq[data_dir] == drq) {
|
||||
struct rb_node *rbnext = rb_next(&drq->rb_node);
|
||||
|
||||
dd->next_drq[data_dir] = NULL;
|
||||
if (rbnext)
|
||||
dd->next_drq[data_dir] = rb_entry_drq(rbnext);
|
||||
}
|
||||
|
||||
BUG_ON(!ON_RB(&drq->rb_node));
|
||||
rb_erase(&drq->rb_node, DRQ_RB_ROOT(dd, drq));
|
||||
RB_CLEAR(&drq->rb_node);
|
||||
}
|
||||
|
||||
static struct request *
|
||||
deadline_find_drq_rb(struct deadline_data *dd, sector_t sector, int data_dir)
|
||||
{
|
||||
struct rb_node *n = dd->sort_list[data_dir].rb_node;
|
||||
struct deadline_rq *drq;
|
||||
|
||||
while (n) {
|
||||
drq = rb_entry_drq(n);
|
||||
|
||||
if (sector < drq->rb_key)
|
||||
n = n->rb_left;
|
||||
else if (sector > drq->rb_key)
|
||||
n = n->rb_right;
|
||||
else
|
||||
return drq->request;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* deadline_find_first_drq finds the first (lowest sector numbered) request
|
||||
* for the specified data_dir. Used to sweep back to the start of the disk
|
||||
* (1-way elevator) after we process the last (highest sector) request.
|
||||
*/
|
||||
static struct deadline_rq *
|
||||
deadline_find_first_drq(struct deadline_data *dd, int data_dir)
|
||||
{
|
||||
struct rb_node *n = dd->sort_list[data_dir].rb_node;
|
||||
|
||||
for (;;) {
|
||||
if (n->rb_left == NULL)
|
||||
return rb_entry_drq(n);
|
||||
|
||||
n = n->rb_left;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* add drq to rbtree and fifo
|
||||
*/
|
||||
static void
|
||||
deadline_add_request(struct request_queue *q, struct request *rq)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
struct deadline_rq *drq = RQ_DATA(rq);
|
||||
|
||||
const int data_dir = rq_data_dir(drq->request);
|
||||
|
||||
deadline_add_drq_rb(dd, drq);
|
||||
/*
|
||||
* set expire time (only used for reads) and add to fifo list
|
||||
*/
|
||||
drq->expires = jiffies + dd->fifo_expire[data_dir];
|
||||
list_add_tail(&drq->fifo, &dd->fifo_list[data_dir]);
|
||||
|
||||
if (rq_mergeable(rq))
|
||||
deadline_add_drq_hash(dd, drq);
|
||||
}
|
||||
|
||||
/*
|
||||
* remove rq from rbtree, fifo, and hash
|
||||
*/
|
||||
static void deadline_remove_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
struct deadline_rq *drq = RQ_DATA(rq);
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
|
||||
list_del_init(&drq->fifo);
|
||||
deadline_del_drq_rb(dd, drq);
|
||||
deadline_del_drq_hash(drq);
|
||||
}
|
||||
|
||||
static int
|
||||
deadline_merge(request_queue_t *q, struct request **req, struct bio *bio)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
struct request *__rq;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* see if the merge hash can satisfy a back merge
|
||||
*/
|
||||
__rq = deadline_find_drq_hash(dd, bio->bi_sector);
|
||||
if (__rq) {
|
||||
BUG_ON(__rq->sector + __rq->nr_sectors != bio->bi_sector);
|
||||
|
||||
if (elv_rq_merge_ok(__rq, bio)) {
|
||||
ret = ELEVATOR_BACK_MERGE;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* check for front merge
|
||||
*/
|
||||
if (dd->front_merges) {
|
||||
sector_t rb_key = bio->bi_sector + bio_sectors(bio);
|
||||
|
||||
__rq = deadline_find_drq_rb(dd, rb_key, bio_data_dir(bio));
|
||||
if (__rq) {
|
||||
BUG_ON(rb_key != rq_rb_key(__rq));
|
||||
|
||||
if (elv_rq_merge_ok(__rq, bio)) {
|
||||
ret = ELEVATOR_FRONT_MERGE;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ELEVATOR_NO_MERGE;
|
||||
out:
|
||||
if (ret)
|
||||
deadline_hot_drq_hash(dd, RQ_DATA(__rq));
|
||||
*req = __rq;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void deadline_merged_request(request_queue_t *q, struct request *req)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
struct deadline_rq *drq = RQ_DATA(req);
|
||||
|
||||
/*
|
||||
* hash always needs to be repositioned, key is end sector
|
||||
*/
|
||||
deadline_del_drq_hash(drq);
|
||||
deadline_add_drq_hash(dd, drq);
|
||||
|
||||
/*
|
||||
* if the merge was a front merge, we need to reposition request
|
||||
*/
|
||||
if (rq_rb_key(req) != drq->rb_key) {
|
||||
deadline_del_drq_rb(dd, drq);
|
||||
deadline_add_drq_rb(dd, drq);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
deadline_merged_requests(request_queue_t *q, struct request *req,
|
||||
struct request *next)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
struct deadline_rq *drq = RQ_DATA(req);
|
||||
struct deadline_rq *dnext = RQ_DATA(next);
|
||||
|
||||
BUG_ON(!drq);
|
||||
BUG_ON(!dnext);
|
||||
|
||||
/*
|
||||
* reposition drq (this is the merged request) in hash, and in rbtree
|
||||
* in case of a front merge
|
||||
*/
|
||||
deadline_del_drq_hash(drq);
|
||||
deadline_add_drq_hash(dd, drq);
|
||||
|
||||
if (rq_rb_key(req) != drq->rb_key) {
|
||||
deadline_del_drq_rb(dd, drq);
|
||||
deadline_add_drq_rb(dd, drq);
|
||||
}
|
||||
|
||||
/*
|
||||
* if dnext expires before drq, assign its expire time to drq
|
||||
* and move into dnext position (dnext will be deleted) in fifo
|
||||
*/
|
||||
if (!list_empty(&drq->fifo) && !list_empty(&dnext->fifo)) {
|
||||
if (time_before(dnext->expires, drq->expires)) {
|
||||
list_move(&drq->fifo, &dnext->fifo);
|
||||
drq->expires = dnext->expires;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* kill knowledge of next, this one is a goner
|
||||
*/
|
||||
deadline_remove_request(q, next);
|
||||
}
|
||||
|
||||
/*
|
||||
* move request from sort list to dispatch queue.
|
||||
*/
|
||||
static inline void
|
||||
deadline_move_to_dispatch(struct deadline_data *dd, struct deadline_rq *drq)
|
||||
{
|
||||
request_queue_t *q = drq->request->q;
|
||||
|
||||
deadline_remove_request(q, drq->request);
|
||||
elv_dispatch_add_tail(q, drq->request);
|
||||
}
|
||||
|
||||
/*
|
||||
* move an entry to dispatch queue
|
||||
*/
|
||||
static void
|
||||
deadline_move_request(struct deadline_data *dd, struct deadline_rq *drq)
|
||||
{
|
||||
const int data_dir = rq_data_dir(drq->request);
|
||||
struct rb_node *rbnext = rb_next(&drq->rb_node);
|
||||
|
||||
dd->next_drq[READ] = NULL;
|
||||
dd->next_drq[WRITE] = NULL;
|
||||
|
||||
if (rbnext)
|
||||
dd->next_drq[data_dir] = rb_entry_drq(rbnext);
|
||||
|
||||
dd->last_sector = drq->request->sector + drq->request->nr_sectors;
|
||||
|
||||
/*
|
||||
* take it off the sort and fifo list, move
|
||||
* to dispatch queue
|
||||
*/
|
||||
deadline_move_to_dispatch(dd, drq);
|
||||
}
|
||||
|
||||
#define list_entry_fifo(ptr) list_entry((ptr), struct deadline_rq, fifo)
|
||||
|
||||
/*
|
||||
* deadline_check_fifo returns 0 if there are no expired reads on the fifo,
|
||||
* 1 otherwise. Requires !list_empty(&dd->fifo_list[data_dir])
|
||||
*/
|
||||
static inline int deadline_check_fifo(struct deadline_data *dd, int ddir)
|
||||
{
|
||||
struct deadline_rq *drq = list_entry_fifo(dd->fifo_list[ddir].next);
|
||||
|
||||
/*
|
||||
* drq is expired!
|
||||
*/
|
||||
if (time_after(jiffies, drq->expires))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* deadline_dispatch_requests selects the best request according to
|
||||
* read/write expire, fifo_batch, etc
|
||||
*/
|
||||
static int deadline_dispatch_requests(request_queue_t *q, int force)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
const int reads = !list_empty(&dd->fifo_list[READ]);
|
||||
const int writes = !list_empty(&dd->fifo_list[WRITE]);
|
||||
struct deadline_rq *drq;
|
||||
int data_dir;
|
||||
|
||||
/*
|
||||
* batches are currently reads XOR writes
|
||||
*/
|
||||
if (dd->next_drq[WRITE])
|
||||
drq = dd->next_drq[WRITE];
|
||||
else
|
||||
drq = dd->next_drq[READ];
|
||||
|
||||
if (drq) {
|
||||
/* we have a "next request" */
|
||||
|
||||
if (dd->last_sector != drq->request->sector)
|
||||
/* end the batch on a non sequential request */
|
||||
dd->batching += dd->fifo_batch;
|
||||
|
||||
if (dd->batching < dd->fifo_batch)
|
||||
/* we are still entitled to batch */
|
||||
goto dispatch_request;
|
||||
}
|
||||
|
||||
/*
|
||||
* at this point we are not running a batch. select the appropriate
|
||||
* data direction (read / write)
|
||||
*/
|
||||
|
||||
if (reads) {
|
||||
BUG_ON(RB_EMPTY(&dd->sort_list[READ]));
|
||||
|
||||
if (writes && (dd->starved++ >= dd->writes_starved))
|
||||
goto dispatch_writes;
|
||||
|
||||
data_dir = READ;
|
||||
|
||||
goto dispatch_find_request;
|
||||
}
|
||||
|
||||
/*
|
||||
* there are either no reads or writes have been starved
|
||||
*/
|
||||
|
||||
if (writes) {
|
||||
dispatch_writes:
|
||||
BUG_ON(RB_EMPTY(&dd->sort_list[WRITE]));
|
||||
|
||||
dd->starved = 0;
|
||||
|
||||
data_dir = WRITE;
|
||||
|
||||
goto dispatch_find_request;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
dispatch_find_request:
|
||||
/*
|
||||
* we are not running a batch, find best request for selected data_dir
|
||||
*/
|
||||
if (deadline_check_fifo(dd, data_dir)) {
|
||||
/* An expired request exists - satisfy it */
|
||||
dd->batching = 0;
|
||||
drq = list_entry_fifo(dd->fifo_list[data_dir].next);
|
||||
|
||||
} else if (dd->next_drq[data_dir]) {
|
||||
/*
|
||||
* The last req was the same dir and we have a next request in
|
||||
* sort order. No expired requests so continue on from here.
|
||||
*/
|
||||
drq = dd->next_drq[data_dir];
|
||||
} else {
|
||||
/*
|
||||
* The last req was the other direction or we have run out of
|
||||
* higher-sectored requests. Go back to the lowest sectored
|
||||
* request (1 way elevator) and start a new batch.
|
||||
*/
|
||||
dd->batching = 0;
|
||||
drq = deadline_find_first_drq(dd, data_dir);
|
||||
}
|
||||
|
||||
dispatch_request:
|
||||
/*
|
||||
* drq is the selected appropriate request.
|
||||
*/
|
||||
dd->batching++;
|
||||
deadline_move_request(dd, drq);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int deadline_queue_empty(request_queue_t *q)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
|
||||
return list_empty(&dd->fifo_list[WRITE])
|
||||
&& list_empty(&dd->fifo_list[READ]);
|
||||
}
|
||||
|
||||
static struct request *
|
||||
deadline_former_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
struct deadline_rq *drq = RQ_DATA(rq);
|
||||
struct rb_node *rbprev = rb_prev(&drq->rb_node);
|
||||
|
||||
if (rbprev)
|
||||
return rb_entry_drq(rbprev)->request;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct request *
|
||||
deadline_latter_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
struct deadline_rq *drq = RQ_DATA(rq);
|
||||
struct rb_node *rbnext = rb_next(&drq->rb_node);
|
||||
|
||||
if (rbnext)
|
||||
return rb_entry_drq(rbnext)->request;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void deadline_exit_queue(elevator_t *e)
|
||||
{
|
||||
struct deadline_data *dd = e->elevator_data;
|
||||
|
||||
BUG_ON(!list_empty(&dd->fifo_list[READ]));
|
||||
BUG_ON(!list_empty(&dd->fifo_list[WRITE]));
|
||||
|
||||
mempool_destroy(dd->drq_pool);
|
||||
kfree(dd->hash);
|
||||
kfree(dd);
|
||||
}
|
||||
|
||||
/*
|
||||
* initialize elevator private data (deadline_data), and alloc a drq for
|
||||
* each request on the free lists
|
||||
*/
|
||||
static int deadline_init_queue(request_queue_t *q, elevator_t *e)
|
||||
{
|
||||
struct deadline_data *dd;
|
||||
int i;
|
||||
|
||||
if (!drq_pool)
|
||||
return -ENOMEM;
|
||||
|
||||
dd = kmalloc_node(sizeof(*dd), GFP_KERNEL, q->node);
|
||||
if (!dd)
|
||||
return -ENOMEM;
|
||||
memset(dd, 0, sizeof(*dd));
|
||||
|
||||
dd->hash = kmalloc_node(sizeof(struct list_head)*DL_HASH_ENTRIES,
|
||||
GFP_KERNEL, q->node);
|
||||
if (!dd->hash) {
|
||||
kfree(dd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dd->drq_pool = mempool_create_node(BLKDEV_MIN_RQ, mempool_alloc_slab,
|
||||
mempool_free_slab, drq_pool, q->node);
|
||||
if (!dd->drq_pool) {
|
||||
kfree(dd->hash);
|
||||
kfree(dd);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0; i < DL_HASH_ENTRIES; i++)
|
||||
INIT_LIST_HEAD(&dd->hash[i]);
|
||||
|
||||
INIT_LIST_HEAD(&dd->fifo_list[READ]);
|
||||
INIT_LIST_HEAD(&dd->fifo_list[WRITE]);
|
||||
dd->sort_list[READ] = RB_ROOT;
|
||||
dd->sort_list[WRITE] = RB_ROOT;
|
||||
dd->fifo_expire[READ] = read_expire;
|
||||
dd->fifo_expire[WRITE] = write_expire;
|
||||
dd->writes_starved = writes_starved;
|
||||
dd->front_merges = 1;
|
||||
dd->fifo_batch = fifo_batch;
|
||||
e->elevator_data = dd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void deadline_put_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
struct deadline_rq *drq = RQ_DATA(rq);
|
||||
|
||||
mempool_free(drq, dd->drq_pool);
|
||||
rq->elevator_private = NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
deadline_set_request(request_queue_t *q, struct request *rq, struct bio *bio,
|
||||
gfp_t gfp_mask)
|
||||
{
|
||||
struct deadline_data *dd = q->elevator->elevator_data;
|
||||
struct deadline_rq *drq;
|
||||
|
||||
drq = mempool_alloc(dd->drq_pool, gfp_mask);
|
||||
if (drq) {
|
||||
memset(drq, 0, sizeof(*drq));
|
||||
RB_CLEAR(&drq->rb_node);
|
||||
drq->request = rq;
|
||||
|
||||
INIT_LIST_HEAD(&drq->hash);
|
||||
drq->on_hash = 0;
|
||||
|
||||
INIT_LIST_HEAD(&drq->fifo);
|
||||
|
||||
rq->elevator_private = drq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* sysfs parts below
|
||||
*/
|
||||
struct deadline_fs_entry {
|
||||
struct attribute attr;
|
||||
ssize_t (*show)(struct deadline_data *, char *);
|
||||
ssize_t (*store)(struct deadline_data *, const char *, size_t);
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
deadline_var_show(int var, char *page)
|
||||
{
|
||||
return sprintf(page, "%d\n", var);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
deadline_var_store(int *var, const char *page, size_t count)
|
||||
{
|
||||
char *p = (char *) page;
|
||||
|
||||
*var = simple_strtol(p, &p, 10);
|
||||
return count;
|
||||
}
|
||||
|
||||
#define SHOW_FUNCTION(__FUNC, __VAR, __CONV) \
|
||||
static ssize_t __FUNC(struct deadline_data *dd, char *page) \
|
||||
{ \
|
||||
int __data = __VAR; \
|
||||
if (__CONV) \
|
||||
__data = jiffies_to_msecs(__data); \
|
||||
return deadline_var_show(__data, (page)); \
|
||||
}
|
||||
SHOW_FUNCTION(deadline_readexpire_show, dd->fifo_expire[READ], 1);
|
||||
SHOW_FUNCTION(deadline_writeexpire_show, dd->fifo_expire[WRITE], 1);
|
||||
SHOW_FUNCTION(deadline_writesstarved_show, dd->writes_starved, 0);
|
||||
SHOW_FUNCTION(deadline_frontmerges_show, dd->front_merges, 0);
|
||||
SHOW_FUNCTION(deadline_fifobatch_show, dd->fifo_batch, 0);
|
||||
#undef SHOW_FUNCTION
|
||||
|
||||
#define STORE_FUNCTION(__FUNC, __PTR, MIN, MAX, __CONV) \
|
||||
static ssize_t __FUNC(struct deadline_data *dd, const char *page, size_t count) \
|
||||
{ \
|
||||
int __data; \
|
||||
int ret = deadline_var_store(&__data, (page), count); \
|
||||
if (__data < (MIN)) \
|
||||
__data = (MIN); \
|
||||
else if (__data > (MAX)) \
|
||||
__data = (MAX); \
|
||||
if (__CONV) \
|
||||
*(__PTR) = msecs_to_jiffies(__data); \
|
||||
else \
|
||||
*(__PTR) = __data; \
|
||||
return ret; \
|
||||
}
|
||||
STORE_FUNCTION(deadline_readexpire_store, &dd->fifo_expire[READ], 0, INT_MAX, 1);
|
||||
STORE_FUNCTION(deadline_writeexpire_store, &dd->fifo_expire[WRITE], 0, INT_MAX, 1);
|
||||
STORE_FUNCTION(deadline_writesstarved_store, &dd->writes_starved, INT_MIN, INT_MAX, 0);
|
||||
STORE_FUNCTION(deadline_frontmerges_store, &dd->front_merges, 0, 1, 0);
|
||||
STORE_FUNCTION(deadline_fifobatch_store, &dd->fifo_batch, 0, INT_MAX, 0);
|
||||
#undef STORE_FUNCTION
|
||||
|
||||
static struct deadline_fs_entry deadline_readexpire_entry = {
|
||||
.attr = {.name = "read_expire", .mode = S_IRUGO | S_IWUSR },
|
||||
.show = deadline_readexpire_show,
|
||||
.store = deadline_readexpire_store,
|
||||
};
|
||||
static struct deadline_fs_entry deadline_writeexpire_entry = {
|
||||
.attr = {.name = "write_expire", .mode = S_IRUGO | S_IWUSR },
|
||||
.show = deadline_writeexpire_show,
|
||||
.store = deadline_writeexpire_store,
|
||||
};
|
||||
static struct deadline_fs_entry deadline_writesstarved_entry = {
|
||||
.attr = {.name = "writes_starved", .mode = S_IRUGO | S_IWUSR },
|
||||
.show = deadline_writesstarved_show,
|
||||
.store = deadline_writesstarved_store,
|
||||
};
|
||||
static struct deadline_fs_entry deadline_frontmerges_entry = {
|
||||
.attr = {.name = "front_merges", .mode = S_IRUGO | S_IWUSR },
|
||||
.show = deadline_frontmerges_show,
|
||||
.store = deadline_frontmerges_store,
|
||||
};
|
||||
static struct deadline_fs_entry deadline_fifobatch_entry = {
|
||||
.attr = {.name = "fifo_batch", .mode = S_IRUGO | S_IWUSR },
|
||||
.show = deadline_fifobatch_show,
|
||||
.store = deadline_fifobatch_store,
|
||||
};
|
||||
|
||||
static struct attribute *default_attrs[] = {
|
||||
&deadline_readexpire_entry.attr,
|
||||
&deadline_writeexpire_entry.attr,
|
||||
&deadline_writesstarved_entry.attr,
|
||||
&deadline_frontmerges_entry.attr,
|
||||
&deadline_fifobatch_entry.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
#define to_deadline(atr) container_of((atr), struct deadline_fs_entry, attr)
|
||||
|
||||
static ssize_t
|
||||
deadline_attr_show(struct kobject *kobj, struct attribute *attr, char *page)
|
||||
{
|
||||
elevator_t *e = container_of(kobj, elevator_t, kobj);
|
||||
struct deadline_fs_entry *entry = to_deadline(attr);
|
||||
|
||||
if (!entry->show)
|
||||
return -EIO;
|
||||
|
||||
return entry->show(e->elevator_data, page);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
deadline_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *page, size_t length)
|
||||
{
|
||||
elevator_t *e = container_of(kobj, elevator_t, kobj);
|
||||
struct deadline_fs_entry *entry = to_deadline(attr);
|
||||
|
||||
if (!entry->store)
|
||||
return -EIO;
|
||||
|
||||
return entry->store(e->elevator_data, page, length);
|
||||
}
|
||||
|
||||
static struct sysfs_ops deadline_sysfs_ops = {
|
||||
.show = deadline_attr_show,
|
||||
.store = deadline_attr_store,
|
||||
};
|
||||
|
||||
static struct kobj_type deadline_ktype = {
|
||||
.sysfs_ops = &deadline_sysfs_ops,
|
||||
.default_attrs = default_attrs,
|
||||
};
|
||||
|
||||
static struct elevator_type iosched_deadline = {
|
||||
.ops = {
|
||||
.elevator_merge_fn = deadline_merge,
|
||||
.elevator_merged_fn = deadline_merged_request,
|
||||
.elevator_merge_req_fn = deadline_merged_requests,
|
||||
.elevator_dispatch_fn = deadline_dispatch_requests,
|
||||
.elevator_add_req_fn = deadline_add_request,
|
||||
.elevator_queue_empty_fn = deadline_queue_empty,
|
||||
.elevator_former_req_fn = deadline_former_request,
|
||||
.elevator_latter_req_fn = deadline_latter_request,
|
||||
.elevator_set_req_fn = deadline_set_request,
|
||||
.elevator_put_req_fn = deadline_put_request,
|
||||
.elevator_init_fn = deadline_init_queue,
|
||||
.elevator_exit_fn = deadline_exit_queue,
|
||||
},
|
||||
|
||||
.elevator_ktype = &deadline_ktype,
|
||||
.elevator_name = "deadline",
|
||||
.elevator_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init deadline_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
drq_pool = kmem_cache_create("deadline_drq", sizeof(struct deadline_rq),
|
||||
0, 0, NULL, NULL);
|
||||
|
||||
if (!drq_pool)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = elv_register(&iosched_deadline);
|
||||
if (ret)
|
||||
kmem_cache_destroy(drq_pool);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit deadline_exit(void)
|
||||
{
|
||||
kmem_cache_destroy(drq_pool);
|
||||
elv_unregister(&iosched_deadline);
|
||||
}
|
||||
|
||||
module_init(deadline_init);
|
||||
module_exit(deadline_exit);
|
||||
|
||||
MODULE_AUTHOR("Jens Axboe");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("deadline IO scheduler");
|
||||
|
|
@ -1,802 +0,0 @@
|
|||
/*
|
||||
* linux/drivers/block/elevator.c
|
||||
*
|
||||
* Block device elevator/IO-scheduler.
|
||||
*
|
||||
* Copyright (C) 2000 Andrea Arcangeli <andrea@suse.de> SuSE
|
||||
*
|
||||
* 30042000 Jens Axboe <axboe@suse.de> :
|
||||
*
|
||||
* Split the elevator a bit so that it is possible to choose a different
|
||||
* one or even write a new "plug in". There are three pieces:
|
||||
* - elevator_fn, inserts a new request in the queue list
|
||||
* - elevator_merge_fn, decides whether a new buffer can be merged with
|
||||
* an existing request
|
||||
* - elevator_dequeue_fn, called when a request is taken off the active list
|
||||
*
|
||||
* 20082000 Dave Jones <davej@suse.de> :
|
||||
* Removed tests for max-bomb-segments, which was breaking elvtune
|
||||
* when run without -bN
|
||||
*
|
||||
* Jens:
|
||||
* - Rework again to work with bio instead of buffer_heads
|
||||
* - loose bi_dev comparisons, partition handling is right now
|
||||
* - completely modularize elevator setup and teardown
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/elevator.h>
|
||||
#include <linux/bio.h>
|
||||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
static DEFINE_SPINLOCK(elv_list_lock);
|
||||
static LIST_HEAD(elv_list);
|
||||
|
||||
/*
|
||||
* can we safely merge with this request?
|
||||
*/
|
||||
inline int elv_rq_merge_ok(struct request *rq, struct bio *bio)
|
||||
{
|
||||
if (!rq_mergeable(rq))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* different data direction or already started, don't merge
|
||||
*/
|
||||
if (bio_data_dir(bio) != rq_data_dir(rq))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* same device and no special stuff set, merge is ok
|
||||
*/
|
||||
if (rq->rq_disk == bio->bi_bdev->bd_disk &&
|
||||
!rq->waiting && !rq->special)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(elv_rq_merge_ok);
|
||||
|
||||
inline int elv_try_merge(struct request *__rq, struct bio *bio)
|
||||
{
|
||||
int ret = ELEVATOR_NO_MERGE;
|
||||
|
||||
/*
|
||||
* we can merge and sequence is ok, check if it's possible
|
||||
*/
|
||||
if (elv_rq_merge_ok(__rq, bio)) {
|
||||
if (__rq->sector + __rq->nr_sectors == bio->bi_sector)
|
||||
ret = ELEVATOR_BACK_MERGE;
|
||||
else if (__rq->sector - bio_sectors(bio) == bio->bi_sector)
|
||||
ret = ELEVATOR_FRONT_MERGE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(elv_try_merge);
|
||||
|
||||
static struct elevator_type *elevator_find(const char *name)
|
||||
{
|
||||
struct elevator_type *e = NULL;
|
||||
struct list_head *entry;
|
||||
|
||||
list_for_each(entry, &elv_list) {
|
||||
struct elevator_type *__e;
|
||||
|
||||
__e = list_entry(entry, struct elevator_type, list);
|
||||
|
||||
if (!strcmp(__e->elevator_name, name)) {
|
||||
e = __e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static void elevator_put(struct elevator_type *e)
|
||||
{
|
||||
module_put(e->elevator_owner);
|
||||
}
|
||||
|
||||
static struct elevator_type *elevator_get(const char *name)
|
||||
{
|
||||
struct elevator_type *e;
|
||||
|
||||
spin_lock_irq(&elv_list_lock);
|
||||
|
||||
e = elevator_find(name);
|
||||
if (e && !try_module_get(e->elevator_owner))
|
||||
e = NULL;
|
||||
|
||||
spin_unlock_irq(&elv_list_lock);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static int elevator_attach(request_queue_t *q, struct elevator_type *e,
|
||||
struct elevator_queue *eq)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
memset(eq, 0, sizeof(*eq));
|
||||
eq->ops = &e->ops;
|
||||
eq->elevator_type = e;
|
||||
|
||||
q->elevator = eq;
|
||||
|
||||
if (eq->ops->elevator_init_fn)
|
||||
ret = eq->ops->elevator_init_fn(q, eq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char chosen_elevator[16];
|
||||
|
||||
static void elevator_setup_default(void)
|
||||
{
|
||||
struct elevator_type *e;
|
||||
|
||||
/*
|
||||
* If default has not been set, use the compiled-in selection.
|
||||
*/
|
||||
if (!chosen_elevator[0])
|
||||
strcpy(chosen_elevator, CONFIG_DEFAULT_IOSCHED);
|
||||
|
||||
/*
|
||||
* If the given scheduler is not available, fall back to no-op.
|
||||
*/
|
||||
if (!(e = elevator_find(chosen_elevator)))
|
||||
strcpy(chosen_elevator, "noop");
|
||||
elevator_put(e);
|
||||
}
|
||||
|
||||
static int __init elevator_setup(char *str)
|
||||
{
|
||||
strncpy(chosen_elevator, str, sizeof(chosen_elevator) - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
__setup("elevator=", elevator_setup);
|
||||
|
||||
int elevator_init(request_queue_t *q, char *name)
|
||||
{
|
||||
struct elevator_type *e = NULL;
|
||||
struct elevator_queue *eq;
|
||||
int ret = 0;
|
||||
|
||||
INIT_LIST_HEAD(&q->queue_head);
|
||||
q->last_merge = NULL;
|
||||
q->end_sector = 0;
|
||||
q->boundary_rq = NULL;
|
||||
|
||||
elevator_setup_default();
|
||||
|
||||
if (!name)
|
||||
name = chosen_elevator;
|
||||
|
||||
e = elevator_get(name);
|
||||
if (!e)
|
||||
return -EINVAL;
|
||||
|
||||
eq = kmalloc(sizeof(struct elevator_queue), GFP_KERNEL);
|
||||
if (!eq) {
|
||||
elevator_put(e->elevator_type);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = elevator_attach(q, e, eq);
|
||||
if (ret) {
|
||||
kfree(eq);
|
||||
elevator_put(e->elevator_type);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void elevator_exit(elevator_t *e)
|
||||
{
|
||||
if (e->ops->elevator_exit_fn)
|
||||
e->ops->elevator_exit_fn(e);
|
||||
|
||||
elevator_put(e->elevator_type);
|
||||
e->elevator_type = NULL;
|
||||
kfree(e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert rq into dispatch queue of q. Queue lock must be held on
|
||||
* entry. If sort != 0, rq is sort-inserted; otherwise, rq will be
|
||||
* appended to the dispatch queue. To be used by specific elevators.
|
||||
*/
|
||||
void elv_dispatch_sort(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
sector_t boundary;
|
||||
struct list_head *entry;
|
||||
|
||||
if (q->last_merge == rq)
|
||||
q->last_merge = NULL;
|
||||
|
||||
boundary = q->end_sector;
|
||||
|
||||
list_for_each_prev(entry, &q->queue_head) {
|
||||
struct request *pos = list_entry_rq(entry);
|
||||
|
||||
if (pos->flags & (REQ_SOFTBARRIER|REQ_HARDBARRIER|REQ_STARTED))
|
||||
break;
|
||||
if (rq->sector >= boundary) {
|
||||
if (pos->sector < boundary)
|
||||
continue;
|
||||
} else {
|
||||
if (pos->sector >= boundary)
|
||||
break;
|
||||
}
|
||||
if (rq->sector >= pos->sector)
|
||||
break;
|
||||
}
|
||||
|
||||
list_add(&rq->queuelist, entry);
|
||||
}
|
||||
|
||||
int elv_merge(request_queue_t *q, struct request **req, struct bio *bio)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
int ret;
|
||||
|
||||
if (q->last_merge) {
|
||||
ret = elv_try_merge(q->last_merge, bio);
|
||||
if (ret != ELEVATOR_NO_MERGE) {
|
||||
*req = q->last_merge;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (e->ops->elevator_merge_fn)
|
||||
return e->ops->elevator_merge_fn(q, req, bio);
|
||||
|
||||
return ELEVATOR_NO_MERGE;
|
||||
}
|
||||
|
||||
void elv_merged_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (e->ops->elevator_merged_fn)
|
||||
e->ops->elevator_merged_fn(q, rq);
|
||||
|
||||
q->last_merge = rq;
|
||||
}
|
||||
|
||||
void elv_merge_requests(request_queue_t *q, struct request *rq,
|
||||
struct request *next)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (e->ops->elevator_merge_req_fn)
|
||||
e->ops->elevator_merge_req_fn(q, rq, next);
|
||||
|
||||
q->last_merge = rq;
|
||||
}
|
||||
|
||||
void elv_requeue_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
/*
|
||||
* it already went through dequeue, we need to decrement the
|
||||
* in_flight count again
|
||||
*/
|
||||
if (blk_account_rq(rq)) {
|
||||
q->in_flight--;
|
||||
if (blk_sorted_rq(rq) && e->ops->elevator_deactivate_req_fn)
|
||||
e->ops->elevator_deactivate_req_fn(q, rq);
|
||||
}
|
||||
|
||||
rq->flags &= ~REQ_STARTED;
|
||||
|
||||
/*
|
||||
* if this is the flush, requeue the original instead and drop the flush
|
||||
*/
|
||||
if (rq->flags & REQ_BAR_FLUSH) {
|
||||
clear_bit(QUEUE_FLAG_FLUSH, &q->queue_flags);
|
||||
rq = rq->end_io_data;
|
||||
}
|
||||
|
||||
__elv_add_request(q, rq, ELEVATOR_INSERT_FRONT, 0);
|
||||
}
|
||||
|
||||
void __elv_add_request(request_queue_t *q, struct request *rq, int where,
|
||||
int plug)
|
||||
{
|
||||
if (rq->flags & (REQ_SOFTBARRIER | REQ_HARDBARRIER)) {
|
||||
/*
|
||||
* barriers implicitly indicate back insertion
|
||||
*/
|
||||
if (where == ELEVATOR_INSERT_SORT)
|
||||
where = ELEVATOR_INSERT_BACK;
|
||||
|
||||
/*
|
||||
* this request is scheduling boundary, update end_sector
|
||||
*/
|
||||
if (blk_fs_request(rq)) {
|
||||
q->end_sector = rq_end_sector(rq);
|
||||
q->boundary_rq = rq;
|
||||
}
|
||||
} else if (!(rq->flags & REQ_ELVPRIV) && where == ELEVATOR_INSERT_SORT)
|
||||
where = ELEVATOR_INSERT_BACK;
|
||||
|
||||
if (plug)
|
||||
blk_plug_device(q);
|
||||
|
||||
rq->q = q;
|
||||
|
||||
switch (where) {
|
||||
case ELEVATOR_INSERT_FRONT:
|
||||
rq->flags |= REQ_SOFTBARRIER;
|
||||
|
||||
list_add(&rq->queuelist, &q->queue_head);
|
||||
break;
|
||||
|
||||
case ELEVATOR_INSERT_BACK:
|
||||
rq->flags |= REQ_SOFTBARRIER;
|
||||
|
||||
while (q->elevator->ops->elevator_dispatch_fn(q, 1))
|
||||
;
|
||||
list_add_tail(&rq->queuelist, &q->queue_head);
|
||||
/*
|
||||
* We kick the queue here for the following reasons.
|
||||
* - The elevator might have returned NULL previously
|
||||
* to delay requests and returned them now. As the
|
||||
* queue wasn't empty before this request, ll_rw_blk
|
||||
* won't run the queue on return, resulting in hang.
|
||||
* - Usually, back inserted requests won't be merged
|
||||
* with anything. There's no point in delaying queue
|
||||
* processing.
|
||||
*/
|
||||
blk_remove_plug(q);
|
||||
q->request_fn(q);
|
||||
break;
|
||||
|
||||
case ELEVATOR_INSERT_SORT:
|
||||
BUG_ON(!blk_fs_request(rq));
|
||||
rq->flags |= REQ_SORTED;
|
||||
if (q->last_merge == NULL && rq_mergeable(rq))
|
||||
q->last_merge = rq;
|
||||
/*
|
||||
* Some ioscheds (cfq) run q->request_fn directly, so
|
||||
* rq cannot be accessed after calling
|
||||
* elevator_add_req_fn.
|
||||
*/
|
||||
q->elevator->ops->elevator_add_req_fn(q, rq);
|
||||
break;
|
||||
|
||||
default:
|
||||
printk(KERN_ERR "%s: bad insertion point %d\n",
|
||||
__FUNCTION__, where);
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (blk_queue_plugged(q)) {
|
||||
int nrq = q->rq.count[READ] + q->rq.count[WRITE]
|
||||
- q->in_flight;
|
||||
|
||||
if (nrq >= q->unplug_thresh)
|
||||
__generic_unplug_device(q);
|
||||
}
|
||||
}
|
||||
|
||||
void elv_add_request(request_queue_t *q, struct request *rq, int where,
|
||||
int plug)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(q->queue_lock, flags);
|
||||
__elv_add_request(q, rq, where, plug);
|
||||
spin_unlock_irqrestore(q->queue_lock, flags);
|
||||
}
|
||||
|
||||
static inline struct request *__elv_next_request(request_queue_t *q)
|
||||
{
|
||||
struct request *rq;
|
||||
|
||||
if (unlikely(list_empty(&q->queue_head) &&
|
||||
!q->elevator->ops->elevator_dispatch_fn(q, 0)))
|
||||
return NULL;
|
||||
|
||||
rq = list_entry_rq(q->queue_head.next);
|
||||
|
||||
/*
|
||||
* if this is a barrier write and the device has to issue a
|
||||
* flush sequence to support it, check how far we are
|
||||
*/
|
||||
if (blk_fs_request(rq) && blk_barrier_rq(rq)) {
|
||||
BUG_ON(q->ordered == QUEUE_ORDERED_NONE);
|
||||
|
||||
if (q->ordered == QUEUE_ORDERED_FLUSH &&
|
||||
!blk_barrier_preflush(rq))
|
||||
rq = blk_start_pre_flush(q, rq);
|
||||
}
|
||||
|
||||
return rq;
|
||||
}
|
||||
|
||||
struct request *elv_next_request(request_queue_t *q)
|
||||
{
|
||||
struct request *rq;
|
||||
int ret;
|
||||
|
||||
while ((rq = __elv_next_request(q)) != NULL) {
|
||||
if (!(rq->flags & REQ_STARTED)) {
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
/*
|
||||
* This is the first time the device driver
|
||||
* sees this request (possibly after
|
||||
* requeueing). Notify IO scheduler.
|
||||
*/
|
||||
if (blk_sorted_rq(rq) &&
|
||||
e->ops->elevator_activate_req_fn)
|
||||
e->ops->elevator_activate_req_fn(q, rq);
|
||||
|
||||
/*
|
||||
* just mark as started even if we don't start
|
||||
* it, a request that has been delayed should
|
||||
* not be passed by new incoming requests
|
||||
*/
|
||||
rq->flags |= REQ_STARTED;
|
||||
}
|
||||
|
||||
if (!q->boundary_rq || q->boundary_rq == rq) {
|
||||
q->end_sector = rq_end_sector(rq);
|
||||
q->boundary_rq = NULL;
|
||||
}
|
||||
|
||||
if ((rq->flags & REQ_DONTPREP) || !q->prep_rq_fn)
|
||||
break;
|
||||
|
||||
ret = q->prep_rq_fn(q, rq);
|
||||
if (ret == BLKPREP_OK) {
|
||||
break;
|
||||
} else if (ret == BLKPREP_DEFER) {
|
||||
/*
|
||||
* the request may have been (partially) prepped.
|
||||
* we need to keep this request in the front to
|
||||
* avoid resource deadlock. REQ_STARTED will
|
||||
* prevent other fs requests from passing this one.
|
||||
*/
|
||||
rq = NULL;
|
||||
break;
|
||||
} else if (ret == BLKPREP_KILL) {
|
||||
int nr_bytes = rq->hard_nr_sectors << 9;
|
||||
|
||||
if (!nr_bytes)
|
||||
nr_bytes = rq->data_len;
|
||||
|
||||
blkdev_dequeue_request(rq);
|
||||
rq->flags |= REQ_QUIET;
|
||||
end_that_request_chunk(rq, 0, nr_bytes);
|
||||
end_that_request_last(rq);
|
||||
} else {
|
||||
printk(KERN_ERR "%s: bad return=%d\n", __FUNCTION__,
|
||||
ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rq;
|
||||
}
|
||||
|
||||
void elv_dequeue_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
BUG_ON(list_empty(&rq->queuelist));
|
||||
|
||||
list_del_init(&rq->queuelist);
|
||||
|
||||
/*
|
||||
* the time frame between a request being removed from the lists
|
||||
* and to it is freed is accounted as io that is in progress at
|
||||
* the driver side.
|
||||
*/
|
||||
if (blk_account_rq(rq))
|
||||
q->in_flight++;
|
||||
}
|
||||
|
||||
int elv_queue_empty(request_queue_t *q)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (!list_empty(&q->queue_head))
|
||||
return 0;
|
||||
|
||||
if (e->ops->elevator_queue_empty_fn)
|
||||
return e->ops->elevator_queue_empty_fn(q);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct request *elv_latter_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
struct list_head *next;
|
||||
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (e->ops->elevator_latter_req_fn)
|
||||
return e->ops->elevator_latter_req_fn(q, rq);
|
||||
|
||||
next = rq->queuelist.next;
|
||||
if (next != &q->queue_head && next != &rq->queuelist)
|
||||
return list_entry_rq(next);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct request *elv_former_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
struct list_head *prev;
|
||||
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (e->ops->elevator_former_req_fn)
|
||||
return e->ops->elevator_former_req_fn(q, rq);
|
||||
|
||||
prev = rq->queuelist.prev;
|
||||
if (prev != &q->queue_head && prev != &rq->queuelist)
|
||||
return list_entry_rq(prev);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int elv_set_request(request_queue_t *q, struct request *rq, struct bio *bio,
|
||||
gfp_t gfp_mask)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (e->ops->elevator_set_req_fn)
|
||||
return e->ops->elevator_set_req_fn(q, rq, bio, gfp_mask);
|
||||
|
||||
rq->elevator_private = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void elv_put_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (e->ops->elevator_put_req_fn)
|
||||
e->ops->elevator_put_req_fn(q, rq);
|
||||
}
|
||||
|
||||
int elv_may_queue(request_queue_t *q, int rw, struct bio *bio)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
if (e->ops->elevator_may_queue_fn)
|
||||
return e->ops->elevator_may_queue_fn(q, rw, bio);
|
||||
|
||||
return ELV_MQUEUE_MAY;
|
||||
}
|
||||
|
||||
void elv_completed_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
/*
|
||||
* request is released from the driver, io must be done
|
||||
*/
|
||||
if (blk_account_rq(rq)) {
|
||||
q->in_flight--;
|
||||
if (blk_sorted_rq(rq) && e->ops->elevator_completed_req_fn)
|
||||
e->ops->elevator_completed_req_fn(q, rq);
|
||||
}
|
||||
}
|
||||
|
||||
int elv_register_queue(struct request_queue *q)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
|
||||
e->kobj.parent = kobject_get(&q->kobj);
|
||||
if (!e->kobj.parent)
|
||||
return -EBUSY;
|
||||
|
||||
snprintf(e->kobj.name, KOBJ_NAME_LEN, "%s", "iosched");
|
||||
e->kobj.ktype = e->elevator_type->elevator_ktype;
|
||||
|
||||
return kobject_register(&e->kobj);
|
||||
}
|
||||
|
||||
void elv_unregister_queue(struct request_queue *q)
|
||||
{
|
||||
if (q) {
|
||||
elevator_t *e = q->elevator;
|
||||
kobject_unregister(&e->kobj);
|
||||
kobject_put(&q->kobj);
|
||||
}
|
||||
}
|
||||
|
||||
int elv_register(struct elevator_type *e)
|
||||
{
|
||||
spin_lock_irq(&elv_list_lock);
|
||||
if (elevator_find(e->elevator_name))
|
||||
BUG();
|
||||
list_add_tail(&e->list, &elv_list);
|
||||
spin_unlock_irq(&elv_list_lock);
|
||||
|
||||
printk(KERN_INFO "io scheduler %s registered", e->elevator_name);
|
||||
if (!strcmp(e->elevator_name, chosen_elevator))
|
||||
printk(" (default)");
|
||||
printk("\n");
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(elv_register);
|
||||
|
||||
void elv_unregister(struct elevator_type *e)
|
||||
{
|
||||
struct task_struct *g, *p;
|
||||
|
||||
/*
|
||||
* Iterate every thread in the process to remove the io contexts.
|
||||
*/
|
||||
read_lock(&tasklist_lock);
|
||||
do_each_thread(g, p) {
|
||||
struct io_context *ioc = p->io_context;
|
||||
if (ioc && ioc->cic) {
|
||||
ioc->cic->exit(ioc->cic);
|
||||
ioc->cic->dtor(ioc->cic);
|
||||
ioc->cic = NULL;
|
||||
}
|
||||
if (ioc && ioc->aic) {
|
||||
ioc->aic->exit(ioc->aic);
|
||||
ioc->aic->dtor(ioc->aic);
|
||||
ioc->aic = NULL;
|
||||
}
|
||||
} while_each_thread(g, p);
|
||||
read_unlock(&tasklist_lock);
|
||||
|
||||
spin_lock_irq(&elv_list_lock);
|
||||
list_del_init(&e->list);
|
||||
spin_unlock_irq(&elv_list_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(elv_unregister);
|
||||
|
||||
/*
|
||||
* switch to new_e io scheduler. be careful not to introduce deadlocks -
|
||||
* we don't free the old io scheduler, before we have allocated what we
|
||||
* need for the new one. this way we have a chance of going back to the old
|
||||
* one, if the new one fails init for some reason.
|
||||
*/
|
||||
static void elevator_switch(request_queue_t *q, struct elevator_type *new_e)
|
||||
{
|
||||
elevator_t *old_elevator, *e;
|
||||
|
||||
/*
|
||||
* Allocate new elevator
|
||||
*/
|
||||
e = kmalloc(sizeof(elevator_t), GFP_KERNEL);
|
||||
if (!e)
|
||||
goto error;
|
||||
|
||||
/*
|
||||
* Turn on BYPASS and drain all requests w/ elevator private data
|
||||
*/
|
||||
spin_lock_irq(q->queue_lock);
|
||||
|
||||
set_bit(QUEUE_FLAG_ELVSWITCH, &q->queue_flags);
|
||||
|
||||
while (q->elevator->ops->elevator_dispatch_fn(q, 1))
|
||||
;
|
||||
|
||||
while (q->rq.elvpriv) {
|
||||
spin_unlock_irq(q->queue_lock);
|
||||
msleep(10);
|
||||
spin_lock_irq(q->queue_lock);
|
||||
}
|
||||
|
||||
spin_unlock_irq(q->queue_lock);
|
||||
|
||||
/*
|
||||
* unregister old elevator data
|
||||
*/
|
||||
elv_unregister_queue(q);
|
||||
old_elevator = q->elevator;
|
||||
|
||||
/*
|
||||
* attach and start new elevator
|
||||
*/
|
||||
if (elevator_attach(q, new_e, e))
|
||||
goto fail;
|
||||
|
||||
if (elv_register_queue(q))
|
||||
goto fail_register;
|
||||
|
||||
/*
|
||||
* finally exit old elevator and turn off BYPASS.
|
||||
*/
|
||||
elevator_exit(old_elevator);
|
||||
clear_bit(QUEUE_FLAG_ELVSWITCH, &q->queue_flags);
|
||||
return;
|
||||
|
||||
fail_register:
|
||||
/*
|
||||
* switch failed, exit the new io scheduler and reattach the old
|
||||
* one again (along with re-adding the sysfs dir)
|
||||
*/
|
||||
elevator_exit(e);
|
||||
e = NULL;
|
||||
fail:
|
||||
q->elevator = old_elevator;
|
||||
elv_register_queue(q);
|
||||
clear_bit(QUEUE_FLAG_ELVSWITCH, &q->queue_flags);
|
||||
kfree(e);
|
||||
error:
|
||||
elevator_put(new_e);
|
||||
printk(KERN_ERR "elevator: switch to %s failed\n",new_e->elevator_name);
|
||||
}
|
||||
|
||||
ssize_t elv_iosched_store(request_queue_t *q, const char *name, size_t count)
|
||||
{
|
||||
char elevator_name[ELV_NAME_MAX];
|
||||
struct elevator_type *e;
|
||||
|
||||
memset(elevator_name, 0, sizeof(elevator_name));
|
||||
strncpy(elevator_name, name, sizeof(elevator_name));
|
||||
|
||||
if (elevator_name[strlen(elevator_name) - 1] == '\n')
|
||||
elevator_name[strlen(elevator_name) - 1] = '\0';
|
||||
|
||||
e = elevator_get(elevator_name);
|
||||
if (!e) {
|
||||
printk(KERN_ERR "elevator: type %s not found\n", elevator_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!strcmp(elevator_name, q->elevator->elevator_type->elevator_name)) {
|
||||
elevator_put(e);
|
||||
return count;
|
||||
}
|
||||
|
||||
elevator_switch(q, e);
|
||||
return count;
|
||||
}
|
||||
|
||||
ssize_t elv_iosched_show(request_queue_t *q, char *name)
|
||||
{
|
||||
elevator_t *e = q->elevator;
|
||||
struct elevator_type *elv = e->elevator_type;
|
||||
struct list_head *entry;
|
||||
int len = 0;
|
||||
|
||||
spin_lock_irq(q->queue_lock);
|
||||
list_for_each(entry, &elv_list) {
|
||||
struct elevator_type *__e;
|
||||
|
||||
__e = list_entry(entry, struct elevator_type, list);
|
||||
if (!strcmp(elv->elevator_name, __e->elevator_name))
|
||||
len += sprintf(name+len, "[%s] ", elv->elevator_name);
|
||||
else
|
||||
len += sprintf(name+len, "%s ", __e->elevator_name);
|
||||
}
|
||||
spin_unlock_irq(q->queue_lock);
|
||||
|
||||
len += sprintf(len+name, "\n");
|
||||
return len;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(elv_dispatch_sort);
|
||||
EXPORT_SYMBOL(elv_add_request);
|
||||
EXPORT_SYMBOL(__elv_add_request);
|
||||
EXPORT_SYMBOL(elv_requeue_request);
|
||||
EXPORT_SYMBOL(elv_next_request);
|
||||
EXPORT_SYMBOL(elv_dequeue_request);
|
||||
EXPORT_SYMBOL(elv_queue_empty);
|
||||
EXPORT_SYMBOL(elv_completed_request);
|
||||
EXPORT_SYMBOL(elevator_exit);
|
||||
EXPORT_SYMBOL(elevator_init);
|
||||
|
|
@ -1,726 +0,0 @@
|
|||
/*
|
||||
* gendisk handling
|
||||
*/
|
||||
|
||||
#include <linux/config.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/genhd.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/kobj_map.h>
|
||||
#include <linux/buffer_head.h>
|
||||
|
||||
#define MAX_PROBE_HASH 255 /* random */
|
||||
|
||||
static struct subsystem block_subsys;
|
||||
|
||||
static DECLARE_MUTEX(block_subsys_sem);
|
||||
|
||||
/*
|
||||
* Can be deleted altogether. Later.
|
||||
*
|
||||
*/
|
||||
static struct blk_major_name {
|
||||
struct blk_major_name *next;
|
||||
int major;
|
||||
char name[16];
|
||||
} *major_names[MAX_PROBE_HASH];
|
||||
|
||||
/* index in the above - for now: assume no multimajor ranges */
|
||||
static inline int major_to_index(int major)
|
||||
{
|
||||
return major % MAX_PROBE_HASH;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/* get block device names in somewhat random order */
|
||||
int get_blkdev_list(char *p, int used)
|
||||
{
|
||||
struct blk_major_name *n;
|
||||
int i, len;
|
||||
|
||||
len = snprintf(p, (PAGE_SIZE-used), "\nBlock devices:\n");
|
||||
|
||||
down(&block_subsys_sem);
|
||||
for (i = 0; i < ARRAY_SIZE(major_names); i++) {
|
||||
for (n = major_names[i]; n; n = n->next) {
|
||||
/*
|
||||
* If the curent string plus the 5 extra characters
|
||||
* in the line would run us off the page, then we're done
|
||||
*/
|
||||
if ((len + used + strlen(n->name) + 5) >= PAGE_SIZE)
|
||||
goto page_full;
|
||||
len += sprintf(p+len, "%3d %s\n",
|
||||
n->major, n->name);
|
||||
}
|
||||
}
|
||||
page_full:
|
||||
up(&block_subsys_sem);
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
int register_blkdev(unsigned int major, const char *name)
|
||||
{
|
||||
struct blk_major_name **n, *p;
|
||||
int index, ret = 0;
|
||||
|
||||
down(&block_subsys_sem);
|
||||
|
||||
/* temporary */
|
||||
if (major == 0) {
|
||||
for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {
|
||||
if (major_names[index] == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
printk("register_blkdev: failed to get major for %s\n",
|
||||
name);
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
major = index;
|
||||
ret = major;
|
||||
}
|
||||
|
||||
p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);
|
||||
if (p == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
p->major = major;
|
||||
strlcpy(p->name, name, sizeof(p->name));
|
||||
p->next = NULL;
|
||||
index = major_to_index(major);
|
||||
|
||||
for (n = &major_names[index]; *n; n = &(*n)->next) {
|
||||
if ((*n)->major == major)
|
||||
break;
|
||||
}
|
||||
if (!*n)
|
||||
*n = p;
|
||||
else
|
||||
ret = -EBUSY;
|
||||
|
||||
if (ret < 0) {
|
||||
printk("register_blkdev: cannot get major %d for %s\n",
|
||||
major, name);
|
||||
kfree(p);
|
||||
}
|
||||
out:
|
||||
up(&block_subsys_sem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(register_blkdev);
|
||||
|
||||
/* todo: make void - error printk here */
|
||||
int unregister_blkdev(unsigned int major, const char *name)
|
||||
{
|
||||
struct blk_major_name **n;
|
||||
struct blk_major_name *p = NULL;
|
||||
int index = major_to_index(major);
|
||||
int ret = 0;
|
||||
|
||||
down(&block_subsys_sem);
|
||||
for (n = &major_names[index]; *n; n = &(*n)->next)
|
||||
if ((*n)->major == major)
|
||||
break;
|
||||
if (!*n || strcmp((*n)->name, name))
|
||||
ret = -EINVAL;
|
||||
else {
|
||||
p = *n;
|
||||
*n = p->next;
|
||||
}
|
||||
up(&block_subsys_sem);
|
||||
kfree(p);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(unregister_blkdev);
|
||||
|
||||
static struct kobj_map *bdev_map;
|
||||
|
||||
/*
|
||||
* Register device numbers dev..(dev+range-1)
|
||||
* range must be nonzero
|
||||
* The hash chain is sorted on range, so that subranges can override.
|
||||
*/
|
||||
void blk_register_region(dev_t dev, unsigned long range, struct module *module,
|
||||
struct kobject *(*probe)(dev_t, int *, void *),
|
||||
int (*lock)(dev_t, void *), void *data)
|
||||
{
|
||||
kobj_map(bdev_map, dev, range, module, probe, lock, data);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(blk_register_region);
|
||||
|
||||
void blk_unregister_region(dev_t dev, unsigned long range)
|
||||
{
|
||||
kobj_unmap(bdev_map, dev, range);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(blk_unregister_region);
|
||||
|
||||
static struct kobject *exact_match(dev_t dev, int *part, void *data)
|
||||
{
|
||||
struct gendisk *p = data;
|
||||
return &p->kobj;
|
||||
}
|
||||
|
||||
static int exact_lock(dev_t dev, void *data)
|
||||
{
|
||||
struct gendisk *p = data;
|
||||
|
||||
if (!get_disk(p))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* add_disk - add partitioning information to kernel list
|
||||
* @disk: per-device partitioning information
|
||||
*
|
||||
* This function registers the partitioning information in @disk
|
||||
* with the kernel.
|
||||
*/
|
||||
void add_disk(struct gendisk *disk)
|
||||
{
|
||||
disk->flags |= GENHD_FL_UP;
|
||||
blk_register_region(MKDEV(disk->major, disk->first_minor),
|
||||
disk->minors, NULL, exact_match, exact_lock, disk);
|
||||
register_disk(disk);
|
||||
blk_register_queue(disk);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(add_disk);
|
||||
EXPORT_SYMBOL(del_gendisk); /* in partitions/check.c */
|
||||
|
||||
void unlink_gendisk(struct gendisk *disk)
|
||||
{
|
||||
blk_unregister_queue(disk);
|
||||
blk_unregister_region(MKDEV(disk->major, disk->first_minor),
|
||||
disk->minors);
|
||||
}
|
||||
|
||||
#define to_disk(obj) container_of(obj,struct gendisk,kobj)
|
||||
|
||||
/**
|
||||
* get_gendisk - get partitioning information for a given device
|
||||
* @dev: device to get partitioning information for
|
||||
*
|
||||
* This function gets the structure containing partitioning
|
||||
* information for the given device @dev.
|
||||
*/
|
||||
struct gendisk *get_gendisk(dev_t dev, int *part)
|
||||
{
|
||||
struct kobject *kobj = kobj_lookup(bdev_map, dev, part);
|
||||
return kobj ? to_disk(kobj) : NULL;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/* iterator */
|
||||
static void *part_start(struct seq_file *part, loff_t *pos)
|
||||
{
|
||||
struct list_head *p;
|
||||
loff_t l = *pos;
|
||||
|
||||
down(&block_subsys_sem);
|
||||
list_for_each(p, &block_subsys.kset.list)
|
||||
if (!l--)
|
||||
return list_entry(p, struct gendisk, kobj.entry);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *part_next(struct seq_file *part, void *v, loff_t *pos)
|
||||
{
|
||||
struct list_head *p = ((struct gendisk *)v)->kobj.entry.next;
|
||||
++*pos;
|
||||
return p==&block_subsys.kset.list ? NULL :
|
||||
list_entry(p, struct gendisk, kobj.entry);
|
||||
}
|
||||
|
||||
static void part_stop(struct seq_file *part, void *v)
|
||||
{
|
||||
up(&block_subsys_sem);
|
||||
}
|
||||
|
||||
static int show_partition(struct seq_file *part, void *v)
|
||||
{
|
||||
struct gendisk *sgp = v;
|
||||
int n;
|
||||
char buf[BDEVNAME_SIZE];
|
||||
|
||||
if (&sgp->kobj.entry == block_subsys.kset.list.next)
|
||||
seq_puts(part, "major minor #blocks name\n\n");
|
||||
|
||||
/* Don't show non-partitionable removeable devices or empty devices */
|
||||
if (!get_capacity(sgp) ||
|
||||
(sgp->minors == 1 && (sgp->flags & GENHD_FL_REMOVABLE)))
|
||||
return 0;
|
||||
if (sgp->flags & GENHD_FL_SUPPRESS_PARTITION_INFO)
|
||||
return 0;
|
||||
|
||||
/* show the full disk and all non-0 size partitions of it */
|
||||
seq_printf(part, "%4d %4d %10llu %s\n",
|
||||
sgp->major, sgp->first_minor,
|
||||
(unsigned long long)get_capacity(sgp) >> 1,
|
||||
disk_name(sgp, 0, buf));
|
||||
for (n = 0; n < sgp->minors - 1; n++) {
|
||||
if (!sgp->part[n])
|
||||
continue;
|
||||
if (sgp->part[n]->nr_sects == 0)
|
||||
continue;
|
||||
seq_printf(part, "%4d %4d %10llu %s\n",
|
||||
sgp->major, n + 1 + sgp->first_minor,
|
||||
(unsigned long long)sgp->part[n]->nr_sects >> 1 ,
|
||||
disk_name(sgp, n + 1, buf));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct seq_operations partitions_op = {
|
||||
.start =part_start,
|
||||
.next = part_next,
|
||||
.stop = part_stop,
|
||||
.show = show_partition
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
extern int blk_dev_init(void);
|
||||
|
||||
static struct kobject *base_probe(dev_t dev, int *part, void *data)
|
||||
{
|
||||
if (request_module("block-major-%d-%d", MAJOR(dev), MINOR(dev)) > 0)
|
||||
/* Make old-style 2.4 aliases work */
|
||||
request_module("block-major-%d", MAJOR(dev));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int __init genhd_device_init(void)
|
||||
{
|
||||
bdev_map = kobj_map_init(base_probe, &block_subsys_sem);
|
||||
blk_dev_init();
|
||||
subsystem_register(&block_subsys);
|
||||
return 0;
|
||||
}
|
||||
|
||||
subsys_initcall(genhd_device_init);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* kobject & sysfs bindings for block devices
|
||||
*/
|
||||
static ssize_t disk_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *page)
|
||||
{
|
||||
struct gendisk *disk = to_disk(kobj);
|
||||
struct disk_attribute *disk_attr =
|
||||
container_of(attr,struct disk_attribute,attr);
|
||||
ssize_t ret = -EIO;
|
||||
|
||||
if (disk_attr->show)
|
||||
ret = disk_attr->show(disk,page);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t disk_attr_store(struct kobject * kobj, struct attribute * attr,
|
||||
const char *page, size_t count)
|
||||
{
|
||||
struct gendisk *disk = to_disk(kobj);
|
||||
struct disk_attribute *disk_attr =
|
||||
container_of(attr,struct disk_attribute,attr);
|
||||
ssize_t ret = 0;
|
||||
|
||||
if (disk_attr->store)
|
||||
ret = disk_attr->store(disk, page, count);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sysfs_ops disk_sysfs_ops = {
|
||||
.show = &disk_attr_show,
|
||||
.store = &disk_attr_store,
|
||||
};
|
||||
|
||||
static ssize_t disk_uevent_store(struct gendisk * disk,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
kobject_hotplug(&disk->kobj, KOBJ_ADD);
|
||||
return count;
|
||||
}
|
||||
static ssize_t disk_dev_read(struct gendisk * disk, char *page)
|
||||
{
|
||||
dev_t base = MKDEV(disk->major, disk->first_minor);
|
||||
return print_dev_t(page, base);
|
||||
}
|
||||
static ssize_t disk_range_read(struct gendisk * disk, char *page)
|
||||
{
|
||||
return sprintf(page, "%d\n", disk->minors);
|
||||
}
|
||||
static ssize_t disk_removable_read(struct gendisk * disk, char *page)
|
||||
{
|
||||
return sprintf(page, "%d\n",
|
||||
(disk->flags & GENHD_FL_REMOVABLE ? 1 : 0));
|
||||
|
||||
}
|
||||
static ssize_t disk_size_read(struct gendisk * disk, char *page)
|
||||
{
|
||||
return sprintf(page, "%llu\n", (unsigned long long)get_capacity(disk));
|
||||
}
|
||||
|
||||
static ssize_t disk_stats_read(struct gendisk * disk, char *page)
|
||||
{
|
||||
preempt_disable();
|
||||
disk_round_stats(disk);
|
||||
preempt_enable();
|
||||
return sprintf(page,
|
||||
"%8u %8u %8llu %8u "
|
||||
"%8u %8u %8llu %8u "
|
||||
"%8u %8u %8u"
|
||||
"\n",
|
||||
disk_stat_read(disk, ios[0]), disk_stat_read(disk, merges[0]),
|
||||
(unsigned long long)disk_stat_read(disk, sectors[0]),
|
||||
jiffies_to_msecs(disk_stat_read(disk, ticks[0])),
|
||||
disk_stat_read(disk, ios[1]), disk_stat_read(disk, merges[1]),
|
||||
(unsigned long long)disk_stat_read(disk, sectors[1]),
|
||||
jiffies_to_msecs(disk_stat_read(disk, ticks[1])),
|
||||
disk->in_flight,
|
||||
jiffies_to_msecs(disk_stat_read(disk, io_ticks)),
|
||||
jiffies_to_msecs(disk_stat_read(disk, time_in_queue)));
|
||||
}
|
||||
static struct disk_attribute disk_attr_uevent = {
|
||||
.attr = {.name = "uevent", .mode = S_IWUSR },
|
||||
.store = disk_uevent_store
|
||||
};
|
||||
static struct disk_attribute disk_attr_dev = {
|
||||
.attr = {.name = "dev", .mode = S_IRUGO },
|
||||
.show = disk_dev_read
|
||||
};
|
||||
static struct disk_attribute disk_attr_range = {
|
||||
.attr = {.name = "range", .mode = S_IRUGO },
|
||||
.show = disk_range_read
|
||||
};
|
||||
static struct disk_attribute disk_attr_removable = {
|
||||
.attr = {.name = "removable", .mode = S_IRUGO },
|
||||
.show = disk_removable_read
|
||||
};
|
||||
static struct disk_attribute disk_attr_size = {
|
||||
.attr = {.name = "size", .mode = S_IRUGO },
|
||||
.show = disk_size_read
|
||||
};
|
||||
static struct disk_attribute disk_attr_stat = {
|
||||
.attr = {.name = "stat", .mode = S_IRUGO },
|
||||
.show = disk_stats_read
|
||||
};
|
||||
|
||||
static struct attribute * default_attrs[] = {
|
||||
&disk_attr_uevent.attr,
|
||||
&disk_attr_dev.attr,
|
||||
&disk_attr_range.attr,
|
||||
&disk_attr_removable.attr,
|
||||
&disk_attr_size.attr,
|
||||
&disk_attr_stat.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void disk_release(struct kobject * kobj)
|
||||
{
|
||||
struct gendisk *disk = to_disk(kobj);
|
||||
kfree(disk->random);
|
||||
kfree(disk->part);
|
||||
free_disk_stats(disk);
|
||||
kfree(disk);
|
||||
}
|
||||
|
||||
static struct kobj_type ktype_block = {
|
||||
.release = disk_release,
|
||||
.sysfs_ops = &disk_sysfs_ops,
|
||||
.default_attrs = default_attrs,
|
||||
};
|
||||
|
||||
extern struct kobj_type ktype_part;
|
||||
|
||||
static int block_hotplug_filter(struct kset *kset, struct kobject *kobj)
|
||||
{
|
||||
struct kobj_type *ktype = get_ktype(kobj);
|
||||
|
||||
return ((ktype == &ktype_block) || (ktype == &ktype_part));
|
||||
}
|
||||
|
||||
static int block_hotplug(struct kset *kset, struct kobject *kobj, char **envp,
|
||||
int num_envp, char *buffer, int buffer_size)
|
||||
{
|
||||
struct kobj_type *ktype = get_ktype(kobj);
|
||||
struct device *physdev;
|
||||
struct gendisk *disk;
|
||||
struct hd_struct *part;
|
||||
int length = 0;
|
||||
int i = 0;
|
||||
|
||||
if (ktype == &ktype_block) {
|
||||
disk = container_of(kobj, struct gendisk, kobj);
|
||||
add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size,
|
||||
&length, "MINOR=%u", disk->first_minor);
|
||||
} else if (ktype == &ktype_part) {
|
||||
disk = container_of(kobj->parent, struct gendisk, kobj);
|
||||
part = container_of(kobj, struct hd_struct, kobj);
|
||||
add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size,
|
||||
&length, "MINOR=%u",
|
||||
disk->first_minor + part->partno);
|
||||
} else
|
||||
return 0;
|
||||
|
||||
add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size, &length,
|
||||
"MAJOR=%u", disk->major);
|
||||
|
||||
/* add physical device, backing this device */
|
||||
physdev = disk->driverfs_dev;
|
||||
if (physdev) {
|
||||
char *path = kobject_get_path(&physdev->kobj, GFP_KERNEL);
|
||||
|
||||
add_hotplug_env_var(envp, num_envp, &i, buffer, buffer_size,
|
||||
&length, "PHYSDEVPATH=%s", path);
|
||||
kfree(path);
|
||||
|
||||
if (physdev->bus)
|
||||
add_hotplug_env_var(envp, num_envp, &i,
|
||||
buffer, buffer_size, &length,
|
||||
"PHYSDEVBUS=%s",
|
||||
physdev->bus->name);
|
||||
|
||||
if (physdev->driver)
|
||||
add_hotplug_env_var(envp, num_envp, &i,
|
||||
buffer, buffer_size, &length,
|
||||
"PHYSDEVDRIVER=%s",
|
||||
physdev->driver->name);
|
||||
}
|
||||
|
||||
/* terminate, set to next free slot, shrink available space */
|
||||
envp[i] = NULL;
|
||||
envp = &envp[i];
|
||||
num_envp -= i;
|
||||
buffer = &buffer[length];
|
||||
buffer_size -= length;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct kset_hotplug_ops block_hotplug_ops = {
|
||||
.filter = block_hotplug_filter,
|
||||
.hotplug = block_hotplug,
|
||||
};
|
||||
|
||||
/* declare block_subsys. */
|
||||
static decl_subsys(block, &ktype_block, &block_hotplug_ops);
|
||||
|
||||
|
||||
/*
|
||||
* aggregate disk stat collector. Uses the same stats that the sysfs
|
||||
* entries do, above, but makes them available through one seq_file.
|
||||
* Watching a few disks may be efficient through sysfs, but watching
|
||||
* all of them will be more efficient through this interface.
|
||||
*
|
||||
* The output looks suspiciously like /proc/partitions with a bunch of
|
||||
* extra fields.
|
||||
*/
|
||||
|
||||
/* iterator */
|
||||
static void *diskstats_start(struct seq_file *part, loff_t *pos)
|
||||
{
|
||||
loff_t k = *pos;
|
||||
struct list_head *p;
|
||||
|
||||
down(&block_subsys_sem);
|
||||
list_for_each(p, &block_subsys.kset.list)
|
||||
if (!k--)
|
||||
return list_entry(p, struct gendisk, kobj.entry);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *diskstats_next(struct seq_file *part, void *v, loff_t *pos)
|
||||
{
|
||||
struct list_head *p = ((struct gendisk *)v)->kobj.entry.next;
|
||||
++*pos;
|
||||
return p==&block_subsys.kset.list ? NULL :
|
||||
list_entry(p, struct gendisk, kobj.entry);
|
||||
}
|
||||
|
||||
static void diskstats_stop(struct seq_file *part, void *v)
|
||||
{
|
||||
up(&block_subsys_sem);
|
||||
}
|
||||
|
||||
static int diskstats_show(struct seq_file *s, void *v)
|
||||
{
|
||||
struct gendisk *gp = v;
|
||||
char buf[BDEVNAME_SIZE];
|
||||
int n = 0;
|
||||
|
||||
/*
|
||||
if (&sgp->kobj.entry == block_subsys.kset.list.next)
|
||||
seq_puts(s, "major minor name"
|
||||
" rio rmerge rsect ruse wio wmerge "
|
||||
"wsect wuse running use aveq"
|
||||
"\n\n");
|
||||
*/
|
||||
|
||||
preempt_disable();
|
||||
disk_round_stats(gp);
|
||||
preempt_enable();
|
||||
seq_printf(s, "%4d %4d %s %u %u %llu %u %u %u %llu %u %u %u %u\n",
|
||||
gp->major, n + gp->first_minor, disk_name(gp, n, buf),
|
||||
disk_stat_read(gp, ios[0]), disk_stat_read(gp, merges[0]),
|
||||
(unsigned long long)disk_stat_read(gp, sectors[0]),
|
||||
jiffies_to_msecs(disk_stat_read(gp, ticks[0])),
|
||||
disk_stat_read(gp, ios[1]), disk_stat_read(gp, merges[1]),
|
||||
(unsigned long long)disk_stat_read(gp, sectors[1]),
|
||||
jiffies_to_msecs(disk_stat_read(gp, ticks[1])),
|
||||
gp->in_flight,
|
||||
jiffies_to_msecs(disk_stat_read(gp, io_ticks)),
|
||||
jiffies_to_msecs(disk_stat_read(gp, time_in_queue)));
|
||||
|
||||
/* now show all non-0 size partitions of it */
|
||||
for (n = 0; n < gp->minors - 1; n++) {
|
||||
struct hd_struct *hd = gp->part[n];
|
||||
|
||||
if (hd && hd->nr_sects)
|
||||
seq_printf(s, "%4d %4d %s %u %u %u %u\n",
|
||||
gp->major, n + gp->first_minor + 1,
|
||||
disk_name(gp, n + 1, buf),
|
||||
hd->ios[0], hd->sectors[0],
|
||||
hd->ios[1], hd->sectors[1]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct seq_operations diskstats_op = {
|
||||
.start = diskstats_start,
|
||||
.next = diskstats_next,
|
||||
.stop = diskstats_stop,
|
||||
.show = diskstats_show
|
||||
};
|
||||
|
||||
struct gendisk *alloc_disk(int minors)
|
||||
{
|
||||
return alloc_disk_node(minors, -1);
|
||||
}
|
||||
|
||||
struct gendisk *alloc_disk_node(int minors, int node_id)
|
||||
{
|
||||
struct gendisk *disk;
|
||||
|
||||
disk = kmalloc_node(sizeof(struct gendisk), GFP_KERNEL, node_id);
|
||||
if (disk) {
|
||||
memset(disk, 0, sizeof(struct gendisk));
|
||||
if (!init_disk_stats(disk)) {
|
||||
kfree(disk);
|
||||
return NULL;
|
||||
}
|
||||
if (minors > 1) {
|
||||
int size = (minors - 1) * sizeof(struct hd_struct *);
|
||||
disk->part = kmalloc_node(size, GFP_KERNEL, node_id);
|
||||
if (!disk->part) {
|
||||
kfree(disk);
|
||||
return NULL;
|
||||
}
|
||||
memset(disk->part, 0, size);
|
||||
}
|
||||
disk->minors = minors;
|
||||
kobj_set_kset_s(disk,block_subsys);
|
||||
kobject_init(&disk->kobj);
|
||||
rand_initialize_disk(disk);
|
||||
}
|
||||
return disk;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(alloc_disk);
|
||||
EXPORT_SYMBOL(alloc_disk_node);
|
||||
|
||||
struct kobject *get_disk(struct gendisk *disk)
|
||||
{
|
||||
struct module *owner;
|
||||
struct kobject *kobj;
|
||||
|
||||
if (!disk->fops)
|
||||
return NULL;
|
||||
owner = disk->fops->owner;
|
||||
if (owner && !try_module_get(owner))
|
||||
return NULL;
|
||||
kobj = kobject_get(&disk->kobj);
|
||||
if (kobj == NULL) {
|
||||
module_put(owner);
|
||||
return NULL;
|
||||
}
|
||||
return kobj;
|
||||
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(get_disk);
|
||||
|
||||
void put_disk(struct gendisk *disk)
|
||||
{
|
||||
if (disk)
|
||||
kobject_put(&disk->kobj);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(put_disk);
|
||||
|
||||
void set_device_ro(struct block_device *bdev, int flag)
|
||||
{
|
||||
if (bdev->bd_contains != bdev)
|
||||
bdev->bd_part->policy = flag;
|
||||
else
|
||||
bdev->bd_disk->policy = flag;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(set_device_ro);
|
||||
|
||||
void set_disk_ro(struct gendisk *disk, int flag)
|
||||
{
|
||||
int i;
|
||||
disk->policy = flag;
|
||||
for (i = 0; i < disk->minors - 1; i++)
|
||||
if (disk->part[i]) disk->part[i]->policy = flag;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(set_disk_ro);
|
||||
|
||||
int bdev_read_only(struct block_device *bdev)
|
||||
{
|
||||
if (!bdev)
|
||||
return 0;
|
||||
else if (bdev->bd_contains != bdev)
|
||||
return bdev->bd_part->policy;
|
||||
else
|
||||
return bdev->bd_disk->policy;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(bdev_read_only);
|
||||
|
||||
int invalidate_partition(struct gendisk *disk, int index)
|
||||
{
|
||||
int res = 0;
|
||||
struct block_device *bdev = bdget_disk(disk, index);
|
||||
if (bdev) {
|
||||
fsync_bdev(bdev);
|
||||
res = __invalidate_device(bdev);
|
||||
bdput(bdev);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(invalidate_partition);
|
||||
|
|
@ -1,275 +0,0 @@
|
|||
#include <linux/sched.h> /* for capable() */
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/blkpg.h>
|
||||
#include <linux/backing-dev.h>
|
||||
#include <linux/buffer_head.h>
|
||||
#include <linux/smp_lock.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
static int blkpg_ioctl(struct block_device *bdev, struct blkpg_ioctl_arg __user *arg)
|
||||
{
|
||||
struct block_device *bdevp;
|
||||
struct gendisk *disk;
|
||||
struct blkpg_ioctl_arg a;
|
||||
struct blkpg_partition p;
|
||||
long long start, length;
|
||||
int part;
|
||||
int i;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
if (copy_from_user(&a, arg, sizeof(struct blkpg_ioctl_arg)))
|
||||
return -EFAULT;
|
||||
if (copy_from_user(&p, a.data, sizeof(struct blkpg_partition)))
|
||||
return -EFAULT;
|
||||
disk = bdev->bd_disk;
|
||||
if (bdev != bdev->bd_contains)
|
||||
return -EINVAL;
|
||||
part = p.pno;
|
||||
if (part <= 0 || part >= disk->minors)
|
||||
return -EINVAL;
|
||||
switch (a.op) {
|
||||
case BLKPG_ADD_PARTITION:
|
||||
start = p.start >> 9;
|
||||
length = p.length >> 9;
|
||||
/* check for fit in a hd_struct */
|
||||
if (sizeof(sector_t) == sizeof(long) &&
|
||||
sizeof(long long) > sizeof(long)) {
|
||||
long pstart = start, plength = length;
|
||||
if (pstart != start || plength != length
|
||||
|| pstart < 0 || plength < 0)
|
||||
return -EINVAL;
|
||||
}
|
||||
/* partition number in use? */
|
||||
down(&bdev->bd_sem);
|
||||
if (disk->part[part - 1]) {
|
||||
up(&bdev->bd_sem);
|
||||
return -EBUSY;
|
||||
}
|
||||
/* overlap? */
|
||||
for (i = 0; i < disk->minors - 1; i++) {
|
||||
struct hd_struct *s = disk->part[i];
|
||||
|
||||
if (!s)
|
||||
continue;
|
||||
if (!(start+length <= s->start_sect ||
|
||||
start >= s->start_sect + s->nr_sects)) {
|
||||
up(&bdev->bd_sem);
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
/* all seems OK */
|
||||
add_partition(disk, part, start, length);
|
||||
up(&bdev->bd_sem);
|
||||
return 0;
|
||||
case BLKPG_DEL_PARTITION:
|
||||
if (!disk->part[part-1])
|
||||
return -ENXIO;
|
||||
if (disk->part[part - 1]->nr_sects == 0)
|
||||
return -ENXIO;
|
||||
bdevp = bdget_disk(disk, part);
|
||||
if (!bdevp)
|
||||
return -ENOMEM;
|
||||
down(&bdevp->bd_sem);
|
||||
if (bdevp->bd_openers) {
|
||||
up(&bdevp->bd_sem);
|
||||
bdput(bdevp);
|
||||
return -EBUSY;
|
||||
}
|
||||
/* all seems OK */
|
||||
fsync_bdev(bdevp);
|
||||
invalidate_bdev(bdevp, 0);
|
||||
|
||||
down(&bdev->bd_sem);
|
||||
delete_partition(disk, part);
|
||||
up(&bdev->bd_sem);
|
||||
up(&bdevp->bd_sem);
|
||||
bdput(bdevp);
|
||||
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int blkdev_reread_part(struct block_device *bdev)
|
||||
{
|
||||
struct gendisk *disk = bdev->bd_disk;
|
||||
int res;
|
||||
|
||||
if (disk->minors == 1 || bdev != bdev->bd_contains)
|
||||
return -EINVAL;
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
if (down_trylock(&bdev->bd_sem))
|
||||
return -EBUSY;
|
||||
res = rescan_partitions(disk, bdev);
|
||||
up(&bdev->bd_sem);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int put_ushort(unsigned long arg, unsigned short val)
|
||||
{
|
||||
return put_user(val, (unsigned short __user *)arg);
|
||||
}
|
||||
|
||||
static int put_int(unsigned long arg, int val)
|
||||
{
|
||||
return put_user(val, (int __user *)arg);
|
||||
}
|
||||
|
||||
static int put_long(unsigned long arg, long val)
|
||||
{
|
||||
return put_user(val, (long __user *)arg);
|
||||
}
|
||||
|
||||
static int put_ulong(unsigned long arg, unsigned long val)
|
||||
{
|
||||
return put_user(val, (unsigned long __user *)arg);
|
||||
}
|
||||
|
||||
static int put_u64(unsigned long arg, u64 val)
|
||||
{
|
||||
return put_user(val, (u64 __user *)arg);
|
||||
}
|
||||
|
||||
static int blkdev_locked_ioctl(struct file *file, struct block_device *bdev,
|
||||
unsigned cmd, unsigned long arg)
|
||||
{
|
||||
struct backing_dev_info *bdi;
|
||||
int ret, n;
|
||||
|
||||
switch (cmd) {
|
||||
case BLKRAGET:
|
||||
case BLKFRAGET:
|
||||
if (!arg)
|
||||
return -EINVAL;
|
||||
bdi = blk_get_backing_dev_info(bdev);
|
||||
if (bdi == NULL)
|
||||
return -ENOTTY;
|
||||
return put_long(arg, (bdi->ra_pages * PAGE_CACHE_SIZE) / 512);
|
||||
case BLKROGET:
|
||||
return put_int(arg, bdev_read_only(bdev) != 0);
|
||||
case BLKBSZGET: /* get the logical block size (cf. BLKSSZGET) */
|
||||
return put_int(arg, block_size(bdev));
|
||||
case BLKSSZGET: /* get block device hardware sector size */
|
||||
return put_int(arg, bdev_hardsect_size(bdev));
|
||||
case BLKSECTGET:
|
||||
return put_ushort(arg, bdev_get_queue(bdev)->max_sectors);
|
||||
case BLKRASET:
|
||||
case BLKFRASET:
|
||||
if(!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
bdi = blk_get_backing_dev_info(bdev);
|
||||
if (bdi == NULL)
|
||||
return -ENOTTY;
|
||||
bdi->ra_pages = (arg * 512) / PAGE_CACHE_SIZE;
|
||||
return 0;
|
||||
case BLKBSZSET:
|
||||
/* set the logical block size */
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
if (!arg)
|
||||
return -EINVAL;
|
||||
if (get_user(n, (int __user *) arg))
|
||||
return -EFAULT;
|
||||
if (bd_claim(bdev, file) < 0)
|
||||
return -EBUSY;
|
||||
ret = set_blocksize(bdev, n);
|
||||
bd_release(bdev);
|
||||
return ret;
|
||||
case BLKPG:
|
||||
return blkpg_ioctl(bdev, (struct blkpg_ioctl_arg __user *) arg);
|
||||
case BLKRRPART:
|
||||
return blkdev_reread_part(bdev);
|
||||
case BLKGETSIZE:
|
||||
if ((bdev->bd_inode->i_size >> 9) > ~0UL)
|
||||
return -EFBIG;
|
||||
return put_ulong(arg, bdev->bd_inode->i_size >> 9);
|
||||
case BLKGETSIZE64:
|
||||
return put_u64(arg, bdev->bd_inode->i_size);
|
||||
}
|
||||
return -ENOIOCTLCMD;
|
||||
}
|
||||
|
||||
static int blkdev_driver_ioctl(struct inode *inode, struct file *file,
|
||||
struct gendisk *disk, unsigned cmd, unsigned long arg)
|
||||
{
|
||||
int ret;
|
||||
if (disk->fops->unlocked_ioctl)
|
||||
return disk->fops->unlocked_ioctl(file, cmd, arg);
|
||||
|
||||
if (disk->fops->ioctl) {
|
||||
lock_kernel();
|
||||
ret = disk->fops->ioctl(inode, file, cmd, arg);
|
||||
unlock_kernel();
|
||||
return ret;
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
int blkdev_ioctl(struct inode *inode, struct file *file, unsigned cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct block_device *bdev = inode->i_bdev;
|
||||
struct gendisk *disk = bdev->bd_disk;
|
||||
int ret, n;
|
||||
|
||||
switch(cmd) {
|
||||
case BLKFLSBUF:
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
|
||||
ret = blkdev_driver_ioctl(inode, file, disk, cmd, arg);
|
||||
/* -EINVAL to handle old uncorrected drivers */
|
||||
if (ret != -EINVAL && ret != -ENOTTY)
|
||||
return ret;
|
||||
|
||||
lock_kernel();
|
||||
fsync_bdev(bdev);
|
||||
invalidate_bdev(bdev, 0);
|
||||
unlock_kernel();
|
||||
return 0;
|
||||
|
||||
case BLKROSET:
|
||||
ret = blkdev_driver_ioctl(inode, file, disk, cmd, arg);
|
||||
/* -EINVAL to handle old uncorrected drivers */
|
||||
if (ret != -EINVAL && ret != -ENOTTY)
|
||||
return ret;
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EACCES;
|
||||
if (get_user(n, (int __user *)(arg)))
|
||||
return -EFAULT;
|
||||
lock_kernel();
|
||||
set_device_ro(bdev, n);
|
||||
unlock_kernel();
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock_kernel();
|
||||
ret = blkdev_locked_ioctl(file, bdev, cmd, arg);
|
||||
unlock_kernel();
|
||||
if (ret != -ENOIOCTLCMD)
|
||||
return ret;
|
||||
|
||||
return blkdev_driver_ioctl(inode, file, disk, cmd, arg);
|
||||
}
|
||||
|
||||
/* Most of the generic ioctls are handled in the normal fallback path.
|
||||
This assumes the blkdev's low level compat_ioctl always returns
|
||||
ENOIOCTLCMD for unknown ioctls. */
|
||||
long compat_blkdev_ioctl(struct file *file, unsigned cmd, unsigned long arg)
|
||||
{
|
||||
struct block_device *bdev = file->f_dentry->d_inode->i_bdev;
|
||||
struct gendisk *disk = bdev->bd_disk;
|
||||
int ret = -ENOIOCTLCMD;
|
||||
if (disk->fops->compat_ioctl) {
|
||||
lock_kernel();
|
||||
ret = disk->fops->compat_ioctl(file, cmd, arg);
|
||||
unlock_kernel();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(blkdev_ioctl);
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* elevator noop
|
||||
*/
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/elevator.h>
|
||||
#include <linux/bio.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
|
||||
static void elevator_noop_add_request(request_queue_t *q, struct request *rq)
|
||||
{
|
||||
rq->flags |= REQ_NOMERGE;
|
||||
elv_dispatch_add_tail(q, rq);
|
||||
}
|
||||
|
||||
static int elevator_noop_dispatch(request_queue_t *q, int force)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct elevator_type elevator_noop = {
|
||||
.ops = {
|
||||
.elevator_dispatch_fn = elevator_noop_dispatch,
|
||||
.elevator_add_req_fn = elevator_noop_add_request,
|
||||
},
|
||||
.elevator_name = "noop",
|
||||
.elevator_owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int __init noop_init(void)
|
||||
{
|
||||
return elv_register(&elevator_noop);
|
||||
}
|
||||
|
||||
static void __exit noop_exit(void)
|
||||
{
|
||||
elv_unregister(&elevator_noop);
|
||||
}
|
||||
|
||||
module_init(noop_init);
|
||||
module_exit(noop_exit);
|
||||
|
||||
|
||||
MODULE_AUTHOR("Jens Axboe");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("No-op IO scheduler");
|
||||
|
|
@ -1,589 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2001 Jens Axboe <axboe@suse.de>
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 Licens
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/cdrom.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/times.h>
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
#include <scsi/scsi.h>
|
||||
#include <scsi/scsi_ioctl.h>
|
||||
#include <scsi/scsi_cmnd.h>
|
||||
|
||||
/* Command group 3 is reserved and should never be used. */
|
||||
const unsigned char scsi_command_size[8] =
|
||||
{
|
||||
6, 10, 10, 12,
|
||||
16, 12, 10, 10
|
||||
};
|
||||
|
||||
EXPORT_SYMBOL(scsi_command_size);
|
||||
|
||||
#define BLK_DEFAULT_TIMEOUT (60 * HZ)
|
||||
|
||||
#include <scsi/sg.h>
|
||||
|
||||
static int sg_get_version(int __user *p)
|
||||
{
|
||||
static int sg_version_num = 30527;
|
||||
return put_user(sg_version_num, p);
|
||||
}
|
||||
|
||||
static int scsi_get_idlun(request_queue_t *q, int __user *p)
|
||||
{
|
||||
return put_user(0, p);
|
||||
}
|
||||
|
||||
static int scsi_get_bus(request_queue_t *q, int __user *p)
|
||||
{
|
||||
return put_user(0, p);
|
||||
}
|
||||
|
||||
static int sg_get_timeout(request_queue_t *q)
|
||||
{
|
||||
return q->sg_timeout / (HZ / USER_HZ);
|
||||
}
|
||||
|
||||
static int sg_set_timeout(request_queue_t *q, int __user *p)
|
||||
{
|
||||
int timeout, err = get_user(timeout, p);
|
||||
|
||||
if (!err)
|
||||
q->sg_timeout = timeout * (HZ / USER_HZ);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int sg_get_reserved_size(request_queue_t *q, int __user *p)
|
||||
{
|
||||
return put_user(q->sg_reserved_size, p);
|
||||
}
|
||||
|
||||
static int sg_set_reserved_size(request_queue_t *q, int __user *p)
|
||||
{
|
||||
int size, err = get_user(size, p);
|
||||
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (size < 0)
|
||||
return -EINVAL;
|
||||
if (size > (q->max_sectors << 9))
|
||||
size = q->max_sectors << 9;
|
||||
|
||||
q->sg_reserved_size = size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* will always return that we are ATAPI even for a real SCSI drive, I'm not
|
||||
* so sure this is worth doing anything about (why would you care??)
|
||||
*/
|
||||
static int sg_emulated_host(request_queue_t *q, int __user *p)
|
||||
{
|
||||
return put_user(1, p);
|
||||
}
|
||||
|
||||
#define CMD_READ_SAFE 0x01
|
||||
#define CMD_WRITE_SAFE 0x02
|
||||
#define CMD_WARNED 0x04
|
||||
#define safe_for_read(cmd) [cmd] = CMD_READ_SAFE
|
||||
#define safe_for_write(cmd) [cmd] = CMD_WRITE_SAFE
|
||||
|
||||
static int verify_command(struct file *file, unsigned char *cmd)
|
||||
{
|
||||
static unsigned char cmd_type[256] = {
|
||||
|
||||
/* Basic read-only commands */
|
||||
safe_for_read(TEST_UNIT_READY),
|
||||
safe_for_read(REQUEST_SENSE),
|
||||
safe_for_read(READ_6),
|
||||
safe_for_read(READ_10),
|
||||
safe_for_read(READ_12),
|
||||
safe_for_read(READ_16),
|
||||
safe_for_read(READ_BUFFER),
|
||||
safe_for_read(READ_DEFECT_DATA),
|
||||
safe_for_read(READ_LONG),
|
||||
safe_for_read(INQUIRY),
|
||||
safe_for_read(MODE_SENSE),
|
||||
safe_for_read(MODE_SENSE_10),
|
||||
safe_for_read(LOG_SENSE),
|
||||
safe_for_read(START_STOP),
|
||||
safe_for_read(GPCMD_VERIFY_10),
|
||||
safe_for_read(VERIFY_16),
|
||||
|
||||
/* Audio CD commands */
|
||||
safe_for_read(GPCMD_PLAY_CD),
|
||||
safe_for_read(GPCMD_PLAY_AUDIO_10),
|
||||
safe_for_read(GPCMD_PLAY_AUDIO_MSF),
|
||||
safe_for_read(GPCMD_PLAY_AUDIO_TI),
|
||||
safe_for_read(GPCMD_PAUSE_RESUME),
|
||||
|
||||
/* CD/DVD data reading */
|
||||
safe_for_read(GPCMD_READ_BUFFER_CAPACITY),
|
||||
safe_for_read(GPCMD_READ_CD),
|
||||
safe_for_read(GPCMD_READ_CD_MSF),
|
||||
safe_for_read(GPCMD_READ_DISC_INFO),
|
||||
safe_for_read(GPCMD_READ_CDVD_CAPACITY),
|
||||
safe_for_read(GPCMD_READ_DVD_STRUCTURE),
|
||||
safe_for_read(GPCMD_READ_HEADER),
|
||||
safe_for_read(GPCMD_READ_TRACK_RZONE_INFO),
|
||||
safe_for_read(GPCMD_READ_SUBCHANNEL),
|
||||
safe_for_read(GPCMD_READ_TOC_PMA_ATIP),
|
||||
safe_for_read(GPCMD_REPORT_KEY),
|
||||
safe_for_read(GPCMD_SCAN),
|
||||
safe_for_read(GPCMD_GET_CONFIGURATION),
|
||||
safe_for_read(GPCMD_READ_FORMAT_CAPACITIES),
|
||||
safe_for_read(GPCMD_GET_EVENT_STATUS_NOTIFICATION),
|
||||
safe_for_read(GPCMD_GET_PERFORMANCE),
|
||||
safe_for_read(GPCMD_SEEK),
|
||||
safe_for_read(GPCMD_STOP_PLAY_SCAN),
|
||||
|
||||
/* Basic writing commands */
|
||||
safe_for_write(WRITE_6),
|
||||
safe_for_write(WRITE_10),
|
||||
safe_for_write(WRITE_VERIFY),
|
||||
safe_for_write(WRITE_12),
|
||||
safe_for_write(WRITE_VERIFY_12),
|
||||
safe_for_write(WRITE_16),
|
||||
safe_for_write(WRITE_LONG),
|
||||
safe_for_write(WRITE_LONG_2),
|
||||
safe_for_write(ERASE),
|
||||
safe_for_write(GPCMD_MODE_SELECT_10),
|
||||
safe_for_write(MODE_SELECT),
|
||||
safe_for_write(LOG_SELECT),
|
||||
safe_for_write(GPCMD_BLANK),
|
||||
safe_for_write(GPCMD_CLOSE_TRACK),
|
||||
safe_for_write(GPCMD_FLUSH_CACHE),
|
||||
safe_for_write(GPCMD_FORMAT_UNIT),
|
||||
safe_for_write(GPCMD_REPAIR_RZONE_TRACK),
|
||||
safe_for_write(GPCMD_RESERVE_RZONE_TRACK),
|
||||
safe_for_write(GPCMD_SEND_DVD_STRUCTURE),
|
||||
safe_for_write(GPCMD_SEND_EVENT),
|
||||
safe_for_write(GPCMD_SEND_KEY),
|
||||
safe_for_write(GPCMD_SEND_OPC),
|
||||
safe_for_write(GPCMD_SEND_CUE_SHEET),
|
||||
safe_for_write(GPCMD_SET_SPEED),
|
||||
safe_for_write(GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL),
|
||||
safe_for_write(GPCMD_LOAD_UNLOAD),
|
||||
safe_for_write(GPCMD_SET_STREAMING),
|
||||
};
|
||||
unsigned char type = cmd_type[cmd[0]];
|
||||
|
||||
/* Anybody who can open the device can do a read-safe command */
|
||||
if (type & CMD_READ_SAFE)
|
||||
return 0;
|
||||
|
||||
/* Write-safe commands just require a writable open.. */
|
||||
if (type & CMD_WRITE_SAFE) {
|
||||
if (file->f_mode & FMODE_WRITE)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* And root can do any command.. */
|
||||
if (capable(CAP_SYS_RAWIO))
|
||||
return 0;
|
||||
|
||||
if (!type) {
|
||||
cmd_type[cmd[0]] = CMD_WARNED;
|
||||
printk(KERN_WARNING "scsi: unknown opcode 0x%02x\n", cmd[0]);
|
||||
}
|
||||
|
||||
/* Otherwise fail it with an "Operation not permitted" */
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
static int sg_io(struct file *file, request_queue_t *q,
|
||||
struct gendisk *bd_disk, struct sg_io_hdr *hdr)
|
||||
{
|
||||
unsigned long start_time;
|
||||
int writing = 0, ret = 0;
|
||||
struct request *rq;
|
||||
struct bio *bio;
|
||||
char sense[SCSI_SENSE_BUFFERSIZE];
|
||||
unsigned char cmd[BLK_MAX_CDB];
|
||||
|
||||
if (hdr->interface_id != 'S')
|
||||
return -EINVAL;
|
||||
if (hdr->cmd_len > BLK_MAX_CDB)
|
||||
return -EINVAL;
|
||||
if (copy_from_user(cmd, hdr->cmdp, hdr->cmd_len))
|
||||
return -EFAULT;
|
||||
if (verify_command(file, cmd))
|
||||
return -EPERM;
|
||||
|
||||
if (hdr->dxfer_len > (q->max_sectors << 9))
|
||||
return -EIO;
|
||||
|
||||
if (hdr->dxfer_len)
|
||||
switch (hdr->dxfer_direction) {
|
||||
default:
|
||||
return -EINVAL;
|
||||
case SG_DXFER_TO_FROM_DEV:
|
||||
case SG_DXFER_TO_DEV:
|
||||
writing = 1;
|
||||
break;
|
||||
case SG_DXFER_FROM_DEV:
|
||||
break;
|
||||
}
|
||||
|
||||
rq = blk_get_request(q, writing ? WRITE : READ, GFP_KERNEL);
|
||||
if (!rq)
|
||||
return -ENOMEM;
|
||||
|
||||
if (hdr->iovec_count) {
|
||||
const int size = sizeof(struct sg_iovec) * hdr->iovec_count;
|
||||
struct sg_iovec *iov;
|
||||
|
||||
iov = kmalloc(size, GFP_KERNEL);
|
||||
if (!iov) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (copy_from_user(iov, hdr->dxferp, size)) {
|
||||
kfree(iov);
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = blk_rq_map_user_iov(q, rq, iov, hdr->iovec_count);
|
||||
kfree(iov);
|
||||
} else if (hdr->dxfer_len)
|
||||
ret = blk_rq_map_user(q, rq, hdr->dxferp, hdr->dxfer_len);
|
||||
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* fill in request structure
|
||||
*/
|
||||
rq->cmd_len = hdr->cmd_len;
|
||||
memcpy(rq->cmd, cmd, hdr->cmd_len);
|
||||
if (sizeof(rq->cmd) != hdr->cmd_len)
|
||||
memset(rq->cmd + hdr->cmd_len, 0, sizeof(rq->cmd) - hdr->cmd_len);
|
||||
|
||||
memset(sense, 0, sizeof(sense));
|
||||
rq->sense = sense;
|
||||
rq->sense_len = 0;
|
||||
|
||||
rq->flags |= REQ_BLOCK_PC;
|
||||
bio = rq->bio;
|
||||
|
||||
/*
|
||||
* bounce this after holding a reference to the original bio, it's
|
||||
* needed for proper unmapping
|
||||
*/
|
||||
if (rq->bio)
|
||||
blk_queue_bounce(q, &rq->bio);
|
||||
|
||||
rq->timeout = (hdr->timeout * HZ) / 1000;
|
||||
if (!rq->timeout)
|
||||
rq->timeout = q->sg_timeout;
|
||||
if (!rq->timeout)
|
||||
rq->timeout = BLK_DEFAULT_TIMEOUT;
|
||||
|
||||
start_time = jiffies;
|
||||
|
||||
/* ignore return value. All information is passed back to caller
|
||||
* (if he doesn't check that is his problem).
|
||||
* N.B. a non-zero SCSI status is _not_ necessarily an error.
|
||||
*/
|
||||
blk_execute_rq(q, bd_disk, rq, 0);
|
||||
|
||||
/* write to all output members */
|
||||
hdr->status = 0xff & rq->errors;
|
||||
hdr->masked_status = status_byte(rq->errors);
|
||||
hdr->msg_status = msg_byte(rq->errors);
|
||||
hdr->host_status = host_byte(rq->errors);
|
||||
hdr->driver_status = driver_byte(rq->errors);
|
||||
hdr->info = 0;
|
||||
if (hdr->masked_status || hdr->host_status || hdr->driver_status)
|
||||
hdr->info |= SG_INFO_CHECK;
|
||||
hdr->resid = rq->data_len;
|
||||
hdr->duration = ((jiffies - start_time) * 1000) / HZ;
|
||||
hdr->sb_len_wr = 0;
|
||||
|
||||
if (rq->sense_len && hdr->sbp) {
|
||||
int len = min((unsigned int) hdr->mx_sb_len, rq->sense_len);
|
||||
|
||||
if (!copy_to_user(hdr->sbp, rq->sense, len))
|
||||
hdr->sb_len_wr = len;
|
||||
}
|
||||
|
||||
if (blk_rq_unmap_user(bio, hdr->dxfer_len))
|
||||
ret = -EFAULT;
|
||||
|
||||
/* may not have succeeded, but output values written to control
|
||||
* structure (struct sg_io_hdr). */
|
||||
out:
|
||||
blk_put_request(rq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define OMAX_SB_LEN 16 /* For backward compatibility */
|
||||
|
||||
static int sg_scsi_ioctl(struct file *file, request_queue_t *q,
|
||||
struct gendisk *bd_disk, Scsi_Ioctl_Command __user *sic)
|
||||
{
|
||||
struct request *rq;
|
||||
int err;
|
||||
unsigned int in_len, out_len, bytes, opcode, cmdlen;
|
||||
char *buffer = NULL, sense[SCSI_SENSE_BUFFERSIZE];
|
||||
|
||||
/*
|
||||
* get in an out lengths, verify they don't exceed a page worth of data
|
||||
*/
|
||||
if (get_user(in_len, &sic->inlen))
|
||||
return -EFAULT;
|
||||
if (get_user(out_len, &sic->outlen))
|
||||
return -EFAULT;
|
||||
if (in_len > PAGE_SIZE || out_len > PAGE_SIZE)
|
||||
return -EINVAL;
|
||||
if (get_user(opcode, sic->data))
|
||||
return -EFAULT;
|
||||
|
||||
bytes = max(in_len, out_len);
|
||||
if (bytes) {
|
||||
buffer = kmalloc(bytes, q->bounce_gfp | GFP_USER| __GFP_NOWARN);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(buffer, 0, bytes);
|
||||
}
|
||||
|
||||
rq = blk_get_request(q, in_len ? WRITE : READ, __GFP_WAIT);
|
||||
|
||||
cmdlen = COMMAND_SIZE(opcode);
|
||||
|
||||
/*
|
||||
* get command and data to send to device, if any
|
||||
*/
|
||||
err = -EFAULT;
|
||||
rq->cmd_len = cmdlen;
|
||||
if (copy_from_user(rq->cmd, sic->data, cmdlen))
|
||||
goto error;
|
||||
|
||||
if (copy_from_user(buffer, sic->data + cmdlen, in_len))
|
||||
goto error;
|
||||
|
||||
err = verify_command(file, rq->cmd);
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
switch (opcode) {
|
||||
case SEND_DIAGNOSTIC:
|
||||
case FORMAT_UNIT:
|
||||
rq->timeout = FORMAT_UNIT_TIMEOUT;
|
||||
break;
|
||||
case START_STOP:
|
||||
rq->timeout = START_STOP_TIMEOUT;
|
||||
break;
|
||||
case MOVE_MEDIUM:
|
||||
rq->timeout = MOVE_MEDIUM_TIMEOUT;
|
||||
break;
|
||||
case READ_ELEMENT_STATUS:
|
||||
rq->timeout = READ_ELEMENT_STATUS_TIMEOUT;
|
||||
break;
|
||||
case READ_DEFECT_DATA:
|
||||
rq->timeout = READ_DEFECT_DATA_TIMEOUT;
|
||||
break;
|
||||
default:
|
||||
rq->timeout = BLK_DEFAULT_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
memset(sense, 0, sizeof(sense));
|
||||
rq->sense = sense;
|
||||
rq->sense_len = 0;
|
||||
|
||||
rq->data = buffer;
|
||||
rq->data_len = bytes;
|
||||
rq->flags |= REQ_BLOCK_PC;
|
||||
|
||||
blk_execute_rq(q, bd_disk, rq, 0);
|
||||
err = rq->errors & 0xff; /* only 8 bit SCSI status */
|
||||
if (err) {
|
||||
if (rq->sense_len && rq->sense) {
|
||||
bytes = (OMAX_SB_LEN > rq->sense_len) ?
|
||||
rq->sense_len : OMAX_SB_LEN;
|
||||
if (copy_to_user(sic->data, rq->sense, bytes))
|
||||
err = -EFAULT;
|
||||
}
|
||||
} else {
|
||||
if (copy_to_user(sic->data, buffer, out_len))
|
||||
err = -EFAULT;
|
||||
}
|
||||
|
||||
error:
|
||||
kfree(buffer);
|
||||
blk_put_request(rq);
|
||||
return err;
|
||||
}
|
||||
|
||||
int scsi_cmd_ioctl(struct file *file, struct gendisk *bd_disk, unsigned int cmd, void __user *arg)
|
||||
{
|
||||
request_queue_t *q;
|
||||
struct request *rq;
|
||||
int close = 0, err;
|
||||
|
||||
q = bd_disk->queue;
|
||||
if (!q)
|
||||
return -ENXIO;
|
||||
|
||||
if (blk_get_queue(q))
|
||||
return -ENXIO;
|
||||
|
||||
switch (cmd) {
|
||||
/*
|
||||
* new sgv3 interface
|
||||
*/
|
||||
case SG_GET_VERSION_NUM:
|
||||
err = sg_get_version(arg);
|
||||
break;
|
||||
case SCSI_IOCTL_GET_IDLUN:
|
||||
err = scsi_get_idlun(q, arg);
|
||||
break;
|
||||
case SCSI_IOCTL_GET_BUS_NUMBER:
|
||||
err = scsi_get_bus(q, arg);
|
||||
break;
|
||||
case SG_SET_TIMEOUT:
|
||||
err = sg_set_timeout(q, arg);
|
||||
break;
|
||||
case SG_GET_TIMEOUT:
|
||||
err = sg_get_timeout(q);
|
||||
break;
|
||||
case SG_GET_RESERVED_SIZE:
|
||||
err = sg_get_reserved_size(q, arg);
|
||||
break;
|
||||
case SG_SET_RESERVED_SIZE:
|
||||
err = sg_set_reserved_size(q, arg);
|
||||
break;
|
||||
case SG_EMULATED_HOST:
|
||||
err = sg_emulated_host(q, arg);
|
||||
break;
|
||||
case SG_IO: {
|
||||
struct sg_io_hdr hdr;
|
||||
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(&hdr, arg, sizeof(hdr)))
|
||||
break;
|
||||
err = sg_io(file, q, bd_disk, &hdr);
|
||||
if (err == -EFAULT)
|
||||
break;
|
||||
|
||||
if (copy_to_user(arg, &hdr, sizeof(hdr)))
|
||||
err = -EFAULT;
|
||||
break;
|
||||
}
|
||||
case CDROM_SEND_PACKET: {
|
||||
struct cdrom_generic_command cgc;
|
||||
struct sg_io_hdr hdr;
|
||||
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(&cgc, arg, sizeof(cgc)))
|
||||
break;
|
||||
cgc.timeout = clock_t_to_jiffies(cgc.timeout);
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
hdr.interface_id = 'S';
|
||||
hdr.cmd_len = sizeof(cgc.cmd);
|
||||
hdr.dxfer_len = cgc.buflen;
|
||||
err = 0;
|
||||
switch (cgc.data_direction) {
|
||||
case CGC_DATA_UNKNOWN:
|
||||
hdr.dxfer_direction = SG_DXFER_UNKNOWN;
|
||||
break;
|
||||
case CGC_DATA_WRITE:
|
||||
hdr.dxfer_direction = SG_DXFER_TO_DEV;
|
||||
break;
|
||||
case CGC_DATA_READ:
|
||||
hdr.dxfer_direction = SG_DXFER_FROM_DEV;
|
||||
break;
|
||||
case CGC_DATA_NONE:
|
||||
hdr.dxfer_direction = SG_DXFER_NONE;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
}
|
||||
if (err)
|
||||
break;
|
||||
|
||||
hdr.dxferp = cgc.buffer;
|
||||
hdr.sbp = cgc.sense;
|
||||
if (hdr.sbp)
|
||||
hdr.mx_sb_len = sizeof(struct request_sense);
|
||||
hdr.timeout = cgc.timeout;
|
||||
hdr.cmdp = ((struct cdrom_generic_command __user*) arg)->cmd;
|
||||
hdr.cmd_len = sizeof(cgc.cmd);
|
||||
|
||||
err = sg_io(file, q, bd_disk, &hdr);
|
||||
if (err == -EFAULT)
|
||||
break;
|
||||
|
||||
if (hdr.status)
|
||||
err = -EIO;
|
||||
|
||||
cgc.stat = err;
|
||||
cgc.buflen = hdr.resid;
|
||||
if (copy_to_user(arg, &cgc, sizeof(cgc)))
|
||||
err = -EFAULT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* old junk scsi send command ioctl
|
||||
*/
|
||||
case SCSI_IOCTL_SEND_COMMAND:
|
||||
printk(KERN_WARNING "program %s is using a deprecated SCSI ioctl, please convert it to SG_IO\n", current->comm);
|
||||
err = -EINVAL;
|
||||
if (!arg)
|
||||
break;
|
||||
|
||||
err = sg_scsi_ioctl(file, q, bd_disk, arg);
|
||||
break;
|
||||
case CDROMCLOSETRAY:
|
||||
close = 1;
|
||||
case CDROMEJECT:
|
||||
rq = blk_get_request(q, WRITE, __GFP_WAIT);
|
||||
rq->flags |= REQ_BLOCK_PC;
|
||||
rq->data = NULL;
|
||||
rq->data_len = 0;
|
||||
rq->timeout = BLK_DEFAULT_TIMEOUT;
|
||||
memset(rq->cmd, 0, sizeof(rq->cmd));
|
||||
rq->cmd[0] = GPCMD_START_STOP_UNIT;
|
||||
rq->cmd[4] = 0x02 + (close != 0);
|
||||
rq->cmd_len = 6;
|
||||
err = blk_execute_rq(q, bd_disk, rq, 0);
|
||||
blk_put_request(rq);
|
||||
break;
|
||||
default:
|
||||
err = -ENOTTY;
|
||||
}
|
||||
|
||||
blk_put_queue(q);
|
||||
return err;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(scsi_cmd_ioctl);
|
||||
Loading…
Reference in a new issue