| /* |
| * linux/kernel/blk_dev/ll_rw.c |
| * |
| * Copyright (C) 1991, 1992 Linus Torvalds |
| */ |
| |
| /* |
| * This handles all read/write requests to block devices |
| */ |
| #include <linux/sched.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/config.h> |
| #include <linux/locks.h> |
| |
| #include <asm/system.h> |
| |
| #include "blk.h" |
| |
| /* |
| * 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 wait_queue * wait_for_request = NULL; |
| |
| /* This specifies how many sectors to read ahead on the disk. */ |
| |
| int read_ahead[MAX_BLKDEV] = {0, }; |
| |
| /* blk_dev_struct is: |
| * do_request-address |
| * next-request |
| */ |
| struct blk_dev_struct blk_dev[MAX_BLKDEV] = { |
| { 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 in units of 1024 byte |
| * sectors: |
| * |
| * blk_size[MAJOR][MINOR] |
| * |
| * if (!blk_size[MAJOR]) then no minor size checking is done. |
| */ |
| int * blk_size[MAX_BLKDEV] = { NULL, NULL, }; |
| |
| /* |
| * blksize_size contains the size of all block-devices: |
| * |
| * blksize_size[MAJOR][MINOR] |
| * |
| * if (!blksize_size[MAJOR]) then 1024 bytes is assumed. |
| */ |
| int * blksize_size[MAX_BLKDEV] = { NULL, NULL, }; |
| |
| /* RO fail safe mechanism */ |
| |
| static long ro_bits[MAX_BLKDEV][8]; |
| |
| int is_read_only(int dev) |
| { |
| int minor,major; |
| |
| major = MAJOR(dev); |
| minor = MINOR(dev); |
| if (major < 0 || major >= MAX_BLKDEV) return 0; |
| return ro_bits[major][minor >> 5] & (1 << (minor & 31)); |
| } |
| |
| void set_device_ro(int dev,int flag) |
| { |
| int minor,major; |
| |
| major = MAJOR(dev); |
| minor = MINOR(dev); |
| if (major < 0 || major >= MAX_BLKDEV) return; |
| if (flag) ro_bits[major][minor >> 5] |= 1 << (minor & 31); |
| else ro_bits[major][minor >> 5] &= ~(1 << (minor & 31)); |
| } |
| |
| /* |
| * add-request adds a request to the linked list. |
| * It disables interrupts so that it can muck with the |
| * request-lists in peace. |
| */ |
| 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 ((IN_ORDER(tmp,req) || |
| !IN_ORDER(tmp,tmp->next)) && |
| IN_ORDER(req,tmp->next)) |
| break; |
| } |
| req->next = tmp->next; |
| tmp->next = req; |
| |
| /* Scsi devices are treated differently */ |
| if(MAJOR(req->dev) == 8 || |
| MAJOR(req->dev) == 9 || |
| MAJOR(req->dev) == 11) |
| (dev->request_fn)(); |
| |
| sti(); |
| } |
| |
| static void make_request(int major,int rw, struct buffer_head * bh) |
| { |
| unsigned int sector, count; |
| 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 */ |
| rw_ahead = (rw == READA || rw == WRITEA); |
| if (rw_ahead) { |
| 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; |
| } |
| count = bh->b_size >> 9; |
| sector = bh->b_blocknr * count; |
| if (blk_size[major]) |
| if (blk_size[major][MINOR(bh->b_dev)] < (sector + count)>>1) { |
| 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; |
| } |
| /* The scsi disk drivers completely remove the request from the queue when |
| they start processing an entry. For this reason it is safe to continue |
| to add links to the top entry for scsi devices */ |
| |
| repeat: |
| cli(); |
| if ((major == 3 || major == 8 || major == 11)&& (req = blk_dev[major].current_request)) { |
| if(major == 3) req = req->next; |
| while (req) { |
| if (req->dev == bh->b_dev && |
| !req->waiting && |
| req->cmd == rw && |
| req->sector + req->nr_sectors == sector && |
| req->nr_sectors < 254) { |
| req->bhtail->b_reqnext = bh; |
| req->bhtail = bh; |
| req->nr_sectors += count; |
| bh->b_dirt = 0; |
| sti(); |
| return; |
| } |
| else if ( req->dev == bh->b_dev && |
| !req->waiting && |
| req->cmd == rw && |
| req->sector - count == sector && |
| req->nr_sectors < 254) |
| { |
| req->nr_sectors += count; |
| bh->b_reqnext = req->bh; |
| req->buffer = bh->b_data; |
| req->current_nr_sectors = count; |
| req->sector = sector; |
| bh->b_dirt = 0; |
| req->bh = bh; |
| sti(); |
| return; |
| } |
| req = req->next; |
| } |
| } |
| /* 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: |
| /* fill up the request-info, and add it to the queue */ |
| req->dev = bh->b_dev; |
| sti(); |
| req->cmd = rw; |
| req->errors = 0; |
| req->sector = sector; |
| req->nr_sectors = count; |
| req->current_nr_sectors = count; |
| 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 >= MAX_BLKDEV || !(blk_dev[major].request_fn)) { |
| printk("Trying to read nonexistent block-device %04x (%d)\n",dev,page*8); |
| return; |
| } |
| if (rw!=READ && rw!=WRITE) |
| panic("Bad block dev command, must be R/W"); |
| if (rw == WRITE && is_read_only(dev)) { |
| printk("Can't page to read-only device 0x%X\n",dev); |
| return; |
| } |
| 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->current_nr_sectors = 8; |
| req->buffer = buffer; |
| req->waiting = current; |
| req->bh = NULL; |
| req->next = NULL; |
| current->state = TASK_SWAPPING; |
| add_request(major+blk_dev,req); |
| schedule(); |
| } |
| |
| /* This function can be used to request a number of buffers from a block |
| device. Currently the only restriction is that all buffers must belong to |
| the same device */ |
| |
| void ll_rw_block(int rw, int nr, struct buffer_head * bh[]) |
| { |
| unsigned int major; |
| |
| struct request plug; |
| int plugged; |
| int correct_size; |
| struct blk_dev_struct * dev; |
| int i, j; |
| |
| /* Make sure that the first block contains something reasonable */ |
| while(!bh[0]){ |
| bh++; |
| nr--; |
| if (nr <= 0) return; |
| }; |
| |
| if ((major=MAJOR(bh[0]->b_dev)) >= MAX_BLKDEV || |
| !(blk_dev[major].request_fn)) { |
| printk("ll_rw_block: Trying to read nonexistent block-device %04x (%d)\n",bh[0]->b_dev,bh[0]->b_blocknr); |
| for (i=0;i<nr; i++) |
| if (bh[i]) bh[i]->b_dirt = bh[i]->b_uptodate = 0; |
| return; |
| } |
| |
| for(j=0;j<nr; j++){ |
| if(!bh[j]) continue; |
| /* Determine correct block size for this device */ |
| correct_size = BLOCK_SIZE; |
| if(blksize_size[major] && blksize_size[major][MINOR(bh[j]->b_dev)]) |
| correct_size = blksize_size[major][MINOR(bh[j]->b_dev)]; |
| |
| if(bh[j]->b_size != correct_size) { |
| |
| printk("ll_rw_block: only %d-char blocks implemented (%d)\n", |
| correct_size, bh[j]->b_size); |
| |
| for (i=0;i<nr; i++) |
| if (bh[i]) bh[i]->b_dirt = bh[i]->b_uptodate = 0; |
| return; |
| } |
| }; |
| |
| if ((rw == WRITE || rw == WRITEA) && is_read_only(bh[0]->b_dev)) { |
| printk("Can't write to read-only device 0x%X\n",bh[0]->b_dev); |
| for (i=0;i<nr; i++) |
| if (bh[i]) bh[i]->b_dirt = bh[i]->b_uptodate = 0; |
| return; |
| } |
| /* If there are no pending requests for this device, then we insert a dummy |
| request for that device. This will prevent the request from starting until |
| we have shoved all of the blocks into the queue, and then we let it rip */ |
| |
| plugged = 0; |
| cli(); |
| if (!blk_dev[major].current_request && nr > 1) { |
| blk_dev[major].current_request = &plug; |
| plug.dev = -1; |
| plug.next = NULL; |
| plugged = 1; |
| }; |
| sti(); |
| for (i=0;i<nr; i++) |
| if (bh[i]) { |
| bh[i]->b_req = 1; |
| make_request(major, rw, bh[i]); |
| } |
| if(plugged){ |
| cli(); |
| blk_dev[major].current_request = plug.next; |
| dev = major+blk_dev; |
| (dev->request_fn)(); |
| sti(); |
| }; |
| } |
| |
| void ll_rw_swap_file(int rw, int dev, unsigned int *b, int nb, char *buf) |
| { |
| int i; |
| int buffersize; |
| struct request * req; |
| unsigned int major = MAJOR(dev); |
| |
| if (major >= MAX_BLKDEV || !(blk_dev[major].request_fn)) { |
| printk("ll_rw_swap_file: trying to swap nonexistent block-device\n"); |
| return; |
| } |
| |
| if (rw!=READ && rw!=WRITE) { |
| printk("ll_rw_swap: bad block dev command, must be R/W"); |
| return; |
| } |
| if (rw == WRITE && is_read_only(dev)) { |
| printk("Can't swap to read-only device 0x%X\n",dev); |
| return; |
| } |
| |
| buffersize = PAGE_SIZE / nb; |
| |
| for (i=0; i<nb; i++, buf += buffersize) |
| { |
| 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] * buffersize) >> 9; |
| req->nr_sectors = buffersize >> 9; |
| req->current_nr_sectors = buffersize >> 9; |
| req->buffer = buf; |
| req->waiting = current; |
| req->bh = NULL; |
| req->next = NULL; |
| current->state = TASK_UNINTERRUPTIBLE; |
| add_request(major+blk_dev,req); |
| schedule(); |
| } |
| } |
| |
| 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; |
| } |
| memset(ro_bits,0,sizeof(ro_bits)); |
| #ifdef CONFIG_BLK_DEV_HD |
| mem_start = hd_init(mem_start,mem_end); |
| #endif |
| #ifdef CONFIG_BLK_DEV_XD |
| mem_start = xd_init(mem_start,mem_end); |
| #endif |
| #ifdef CONFIG_CDU31A |
| mem_start = cdu31a_init(mem_start,mem_end); |
| #endif |
| #ifdef CONFIG_MCD |
| mem_start = mcd_init(mem_start,mem_end); |
| #endif |
| if (ramdisk_size) |
| mem_start += rd_init(mem_start, ramdisk_size*1024); |
| return mem_start; |
| } |