blob: 9a09e7e94964b6db9a41fab97b7711c0977a2db3 [file] [log] [blame]
/*
* linux/kernel/blk_dev/ll_rw.c
*
* (C) 1991 Linus Torvalds
*/
/*
* This handles all read/write requests to block devices
*/
#include <errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include "blk.h"
extern long rd_init(long mem_start, int length);
/*
* The request-struct contains all necessary data
* to load a nr of sectors into memory
*/
struct request request[NR_REQUEST];
/*
* used to wait on when there are no free requests
*/
struct task_struct * wait_for_request = NULL;
/* blk_dev_struct is:
* do_request-address
* next-request
*/
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
{ NULL, NULL }, /* no_dev */
{ NULL, NULL }, /* dev mem */
{ NULL, NULL }, /* dev fd */
{ NULL, NULL }, /* dev hd */
{ NULL, NULL }, /* dev ttyx */
{ NULL, NULL }, /* dev tty */
{ NULL, NULL }, /* dev lp */
{ NULL, NULL }, /* dev pipes */
{ NULL, NULL }, /* dev sd */
{ NULL, NULL } /* dev st */
};
/*
* blk_size contains the size of all block-devices:
*
* blk_size[MAJOR][MINOR]
*
* if (!blk_size[MAJOR]) then no minor size checking is done.
*/
int * blk_size[NR_BLK_DEV] = { NULL, NULL, };
static inline void lock_buffer(struct buffer_head * bh)
{
cli();
while (bh->b_lock)
sleep_on(&bh->b_wait);
bh->b_lock=1;
sti();
}
static inline void unlock_buffer(struct buffer_head * bh)
{
if (!bh->b_lock)
printk("ll_rw_block.c: buffer not locked\n\r");
bh->b_lock = 0;
wake_up(&bh->b_wait);
}
/*
* add-request adds a request to the linked list.
* It disables interrupts so that it can muck with the
* request-lists in peace.
*
* Note that swapping requests always go before other requests,
* and are done in the order they appear.
*/
static void add_request(struct blk_dev_struct * dev, struct request * req)
{
struct request * tmp;
req->next = NULL;
cli();
if (req->bh)
req->bh->b_dirt = 0;
if (!(tmp = dev->current_request)) {
dev->current_request = req;
(dev->request_fn)();
sti();
return;
}
for ( ; tmp->next ; tmp = tmp->next) {
if (!req->bh)
if (tmp->next->bh)
break;
else
continue;
if ((IN_ORDER(tmp,req) ||
!IN_ORDER(tmp,tmp->next)) &&
IN_ORDER(req,tmp->next))
break;
}
req->next = tmp->next;
tmp->next = req;
sti();
}
static void make_request(int major,int rw, struct buffer_head * bh)
{
struct request * req;
int rw_ahead;
/* WRITEA/READA is special case - it is not really needed, so if the */
/* buffer is locked, we just forget about it, else it's a normal read */
if (rw_ahead = (rw == READA || rw == WRITEA)) {
if (bh->b_lock)
return;
if (rw == READA)
rw = READ;
else
rw = WRITE;
}
if (rw!=READ && rw!=WRITE) {
printk("Bad block dev command, must be R/W/RA/WA\n");
return;
}
if (blk_size[major])
if (blk_size[major][MINOR(bh->b_dev)] <= bh->b_blocknr) {
bh->b_dirt = bh->b_uptodate = 0;
return;
}
lock_buffer(bh);
if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {
unlock_buffer(bh);
return;
}
repeat:
cli();
if (major == 3 && (req = blk_dev[major].current_request)) {
while (req = req->next) {
if (req->dev == bh->b_dev &&
!req->waiting &&
req->cmd == rw &&
req->sector + req->nr_sectors == bh->b_blocknr << 1 &&
req->nr_sectors < 254) {
req->bhtail->b_reqnext = bh;
req->bhtail = bh;
req->nr_sectors += 2;
bh->b_dirt = 0;
sti();
return;
}
}
}
/* we don't allow the write-requests to fill up the queue completely:
* we want some room for reads: they take precedence. The last third
* of the requests are only for reads.
*/
if (rw == READ)
req = request+NR_REQUEST;
else
req = request+(NR_REQUEST/2);
/* find an empty request */
while (--req >= request)
if (req->dev < 0)
goto found;
/* if none found, sleep on new requests: check for rw_ahead */
if (rw_ahead) {
sti();
unlock_buffer(bh);
return;
}
sleep_on(&wait_for_request);
sti();
goto repeat;
found: sti();
/* fill up the request-info, and add it to the queue */
req->dev = bh->b_dev;
req->cmd = rw;
req->errors=0;
req->sector = bh->b_blocknr<<1;
req->nr_sectors = 2;
req->buffer = bh->b_data;
req->waiting = NULL;
req->bh = bh;
req->bhtail = bh;
req->next = NULL;
add_request(major+blk_dev,req);
}
void ll_rw_page(int rw, int dev, int page, char * buffer)
{
struct request * req;
unsigned int major = MAJOR(dev);
if (major >= NR_BLK_DEV || !(blk_dev[major].request_fn)) {
printk("Trying to read nonexistent block-device\n\r");
return;
}
if (rw!=READ && rw!=WRITE)
panic("Bad block dev command, must be R/W");
cli();
repeat:
req = request+NR_REQUEST;
while (--req >= request)
if (req->dev<0)
break;
if (req < request) {
sleep_on(&wait_for_request);
goto repeat;
}
sti();
/* fill up the request-info, and add it to the queue */
req->dev = dev;
req->cmd = rw;
req->errors = 0;
req->sector = page<<3;
req->nr_sectors = 8;
req->buffer = buffer;
req->waiting = current;
req->bh = NULL;
req->next = NULL;
current->state = TASK_UNINTERRUPTIBLE;
add_request(major+blk_dev,req);
schedule();
}
void ll_rw_block(int rw, struct buffer_head * bh)
{
unsigned int major;
if (!bh)
return;
if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||
!(blk_dev[major].request_fn)) {
printk("ll_rw_block: Trying to read nonexistent block-device\n\r");
return;
}
make_request(major,rw,bh);
}
long blk_dev_init(long mem_start, long mem_end)
{
int i;
for (i=0 ; i<NR_REQUEST ; i++) {
request[i].dev = -1;
request[i].next = NULL;
}
#ifdef RAMDISK
mem_start += rd_init(mem_start, RAMDISK*1024);
#endif
return mem_start;
}
void ll_rw_swap_file(int rw, int dev, unsigned int *b, int nb, char *buf)
{
int i;
struct request * req;
unsigned int major = MAJOR(dev);
if (major >= NR_BLK_DEV || !(blk_dev[major].request_fn)) {
printk("ll_rw_swap_file: trying to swap nonexistent block-device\n\r");
return;
}
if (rw!=READ && rw!=WRITE) {
printk("ll_rw_swap: bad block dev command, must be R/W");
return;
}
for (i=0; i<nb; i++, buf += BLOCK_SIZE)
{
repeat:
req = request+NR_REQUEST;
while (--req >= request)
if (req->dev<0)
break;
if (req < request) {
sleep_on(&wait_for_request);
goto repeat;
}
req->dev = dev;
req->cmd = rw;
req->errors = 0;
req->sector = b[i] << 1;
req->nr_sectors = 2;
req->buffer = buf;
req->waiting = current;
req->bh = NULL;
req->next = NULL;
current->state = TASK_UNINTERRUPTIBLE;
add_request(major+blk_dev,req);
schedule();
}
}