📄 block.c
字号:
/* * Linux block driver support. * * Copyright (C) 1996 The University of Utah and the Computer Systems * Laboratory at the University of Utah (CSL) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * Author: Shantanu Goel, University of Utah CSL *//* * linux/drivers/block/ll_rw_blk.c * * Copyright (C) 1991, 1992 Linus Torvalds * Copyright (C) 1994, Karl Keyte: Added support for disk statistics *//* * linux/fs/block_dev.c * * Copyright (C) 1991, 1992 Linus Torvalds *//* * linux/fs/buffer.c * * Copyright (C) 1991, 1992 Linus Torvalds */#include <sys/types.h>#include <machine/spl.h>#include <mach/mach_types.h>#include <mach/kern_return.h>#include <mach/mig_errors.h>#include <mach/port.h>#include <mach/vm_param.h>#include <mach/notify.h>#include <ipc/ipc_port.h>#include <ipc/ipc_space.h>#include <vm/vm_map.h>#include <vm/vm_kern.h>#include <vm/vm_page.h>#include <device/device_types.h>#include <device/device_port.h>#include <device/disk_status.h>#include "device_reply.h"#include <linux_emul.h>#define MACH_INCLUDE#include <linux/fs.h>#include <linux/blk.h>#include <linux/string.h>#include <linux/errno.h>#include <linux/fcntl.h>#include <linux/major.h>#include <linux/kdev_t.h>#include <linux/delay.h>#include <linux/malloc.h>#include <linux/hdreg.h>#include <asm/io.h>extern int linux_auto_config;extern int linux_intr_pri;extern int linux_to_mach_error (int);/* This task queue is not used in Mach: just for fixing undefined symbols. */DECLARE_TASK_QUEUE (tq_disk);/* Location of VTOC in units for sectors (512 bytes). */#define PDLOCATION 29/* Linux kernel variables. *//* Temporary data allocated on the stack. */struct temp_data{ struct inode inode; struct file file; struct request req; queue_head_t pages;};/* One of these exists for each driver associated with a major number. */struct device_struct{ const char *name; /* device name */ struct file_operations *fops; /* operations vector */ int busy:1; /* driver is being opened/closed */ int want:1; /* someone wants to open/close driver */ struct gendisk *gd; /* DOS partition information */ int default_slice; /* what slice to use when none is given */ struct disklabel **labels; /* disklabels for each DOS partition */};/* An entry in the Mach name to Linux major number conversion table. */struct name_map{ const char *name; /* Mach name for device */ unsigned major; /* Linux major number */ unsigned unit; /* Linux unit number */ int read_only; /* 1 if device is read only */};/* Driver operation table. */static struct device_struct blkdevs[MAX_BLKDEV];/* Driver request function table. */struct blk_dev_struct blk_dev[MAX_BLKDEV] ={ { NULL, NULL }, /* 0 no_dev */ { NULL, NULL }, /* 1 dev mem */ { NULL, NULL }, /* 2 dev fd */ { NULL, NULL }, /* 3 dev ide0 or hd */ { NULL, NULL }, /* 4 dev ttyx */ { NULL, NULL }, /* 5 dev tty */ { NULL, NULL }, /* 6 dev lp */ { NULL, NULL }, /* 7 dev pipes */ { NULL, NULL }, /* 8 dev sd */ { NULL, NULL }, /* 9 dev st */ { NULL, NULL }, /* 10 */ { NULL, NULL }, /* 11 */ { NULL, NULL }, /* 12 */ { NULL, NULL }, /* 13 */ { NULL, NULL }, /* 14 */ { NULL, NULL }, /* 15 */ { NULL, NULL }, /* 16 */ { NULL, NULL }, /* 17 */ { NULL, NULL }, /* 18 */ { NULL, NULL }, /* 19 */ { NULL, NULL }, /* 20 */ { NULL, NULL }, /* 21 */ { NULL, NULL } /* 22 dev ide1 */};/* * 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, };/* * hardsect_size contains the size of the hardware sector of a device. * * hardsect_size[MAJOR][MINOR] * * if (!hardsect_size[MAJOR]) * then 512 bytes is assumed. * else * sector_size is hardsect_size[MAJOR][MINOR] * This is currently set by some scsi device and read by the msdos fs driver * This might be a some uses later. */int *hardsect_size[MAX_BLKDEV] = { NULL, NULL, };/* This specifies how many sectors to read ahead on the disk. This is unused in Mach. It is here to make drivers compile. */int read_ahead[MAX_BLKDEV] = {0, };/* Use to wait on when there are no free requests. This is unused in Mach. It is here to make drivers compile. */struct wait_queue *wait_for_request = NULL;/* Map for allocating device memory. */extern vm_map_t device_io_map;/* Initialize block drivers. */intblk_dev_init (){#ifdef CONFIG_BLK_DEV_IDE ide_init ();#endif#ifdef CONFIG_BLK_DEV_FD floppy_init ();#else outb_p (0xc, 0x3f2);#endif return 0;}/* Return 1 if major number MAJOR corresponds to a disk device. */static inline intdisk_major (int major){ return (major == IDE0_MAJOR || major == IDE1_MAJOR || major == IDE2_MAJOR || major == IDE3_MAJOR || major == SCSI_DISK_MAJOR);}/* Linux kernel block support routines. *//* Register a driver for major number MAJOR, with name NAME, and operations vector FOPS. */intregister_blkdev (unsigned major, const char *name, struct file_operations *fops){ int err = 0; if (major == 0) { for (major = MAX_BLKDEV - 1; major > 0; major--) if (blkdevs[major].fops == NULL) goto out; return -LINUX_EBUSY; } if (major >= MAX_BLKDEV) return -LINUX_EINVAL; if (blkdevs[major].fops && blkdevs[major].fops != fops) return -LINUX_EBUSY;out: blkdevs[major].name = name; blkdevs[major].fops = fops; blkdevs[major].busy = 0; blkdevs[major].want = 0; blkdevs[major].gd = NULL; blkdevs[major].default_slice = 0; blkdevs[major].labels = NULL; return 0;}/* Unregister the driver associated with major number MAJOR and having the name NAME. */intunregister_blkdev (unsigned major, const char *name){ int err; if (major >= MAX_BLKDEV) return -LINUX_EINVAL; if (! blkdevs[major].fops || strcmp (blkdevs[major].name, name)) return -LINUX_EINVAL; blkdevs[major].fops = NULL; if (blkdevs[major].labels) { assert (blkdevs[major].gd); kfree ((vm_offset_t) blkdevs[major].labels, (sizeof (struct disklabel *) * blkdevs[major].gd->max_p * blkdevs[major].gd->max_nr)); } return 0;}voidset_blocksize (kdev_t dev, int size){ extern int *blksize_size[]; if (! blksize_size[MAJOR (dev)]) return; switch (size) { case 512: case 1024: case 2048: case 4096: break; default: panic ("Invalid blocksize passed to set_blocksize"); break; } blksize_size[MAJOR (dev)][MINOR (dev)] = size;}/* Allocate a buffer SIZE bytes long. */static void *alloc_buffer (int size){ vm_page_t m; struct temp_data *d; assert (size <= PAGE_SIZE); if (! linux_auto_config) { while ((m = vm_page_grab (FALSE)) == 0) VM_PAGE_WAIT (0); d = current_thread ()->pcb->data; assert (d); queue_enter (&d->pages, m, vm_page_t, pageq); return (void *) m->phys_addr; } return (void *) __get_free_pages (GFP_KERNEL, 0, ~0UL);}/* Free buffer P which is SIZE bytes long. */static voidfree_buffer (void *p, int size){ int i; struct temp_data *d; vm_page_t m; assert (size <= PAGE_SIZE); if (! linux_auto_config) { d = current_thread ()->pcb->data; assert (d); queue_iterate (&d->pages, m, vm_page_t, pageq) { if (m->phys_addr == (vm_offset_t) p) { queue_remove (&d->pages, m, vm_page_t, pageq); vm_page_lock_queues (); vm_page_free (m); vm_page_lock_queues (); return; } } panic ("free_buffer"); } free_pages ((unsigned long) p, 0);}/* Allocate a buffer of SIZE bytes and associate it with block number BLOCK of device DEV. */struct buffer_head *getblk (kdev_t dev, int block, int size){ struct buffer_head *bh; assert (size <= PAGE_SIZE); bh = (struct buffer_head *) kalloc (sizeof (struct buffer_head)); if (bh) { memset (bh, 0, sizeof (struct buffer_head)); bh->b_data = alloc_buffer (size); if (! bh->b_data) { kfree ((vm_offset_t) bh, sizeof (struct buffer_head)); return NULL; } bh->b_dev = dev; bh->b_size = size; bh->b_state = 1 << BH_Lock; bh->b_blocknr = block; } return bh;}/* Release buffer BH previously allocated by getblk. */void__brelse (struct buffer_head *bh){ free_buffer (bh->b_data, bh->b_size); kfree ((vm_offset_t) bh, sizeof (*bh));}/* Allocate a buffer of SIZE bytes and fill it with data from device DEV starting at block number BLOCK. */struct buffer_head *bread (kdev_t dev, int block, int size){ int err; struct buffer_head *bh; bh = getblk (dev, block, size); if (bh) { ll_rw_block (READ, 1, &bh); wait_on_buffer (bh); if (! buffer_uptodate (bh)) { __brelse (bh); return NULL; } } return bh;}/* Return the block size for device DEV in *BSIZE and log2(block size) in *BSHIFT. */static voidget_block_size (kdev_t dev, int *bsize, int *bshift){ int i; *bsize = BLOCK_SIZE; if (blksize_size[MAJOR (dev)] && blksize_size[MAJOR (dev)][MINOR (dev)]) *bsize = blksize_size[MAJOR (dev)][MINOR (dev)]; for (i = *bsize, *bshift = 0; i != 1; i >>= 1, (*bshift)++) ;}/* Enqueue request REQ on a driver's queue. */static inline voidenqueue_request (struct request *req){ struct request *tmp; struct blk_dev_struct *dev; dev = blk_dev + MAJOR (req->rq_dev); cli (); tmp = dev->current_request; if (! tmp) { dev->current_request = req; (*dev->request_fn) (); sti (); return; } while (tmp->next) { if ((IN_ORDER (tmp, req) || ! IN_ORDER (tmp, tmp->next)) && IN_ORDER (req, tmp->next)) break; tmp = tmp->next; } req->next = tmp->next; tmp->next = req; if (scsi_blk_major (MAJOR (req->rq_dev))) (*dev->request_fn) (); sti ();}/* Perform the I/O operation RW on the buffer list BH containing NR buffers. */voidll_rw_block (int rw, int nr, struct buffer_head **bh){ int i, bshift, bsize; unsigned major; struct request *r; static struct request req; major = MAJOR (bh[0]->b_dev); assert (major < MAX_BLKDEV); get_block_size (bh[0]->b_dev, &bsize, &bshift); if (! linux_auto_config) { assert (current_thread ()->pcb->data); r = &((struct temp_data *) current_thread ()->pcb->data)->req; } else r = &req; for (i = 0, r->nr_sectors = 0; i < nr - 1; i++) { r->nr_sectors += bh[i]->b_size >> 9; bh[i]->b_reqnext = bh[i + 1]; } r->nr_sectors += bh[i]->b_size >> 9; bh[i]->b_reqnext = NULL; r->rq_status = RQ_ACTIVE; r->rq_dev = bh[0]->b_dev; r->cmd = rw; r->errors = 0; r->sector = bh[0]->b_blocknr << (bshift - 9); r->current_nr_sectors = bh[0]->b_size >> 9; r->buffer = bh[0]->b_data; r->bh = bh[0]; r->bhtail = bh[nr - 1]; r->sem = NULL; r->next = NULL; enqueue_request (r);}#define BSIZE (1 << bshift)#define BMASK (BSIZE - 1)/* Perform read/write operation RW on device DEV starting at *off to/from buffer *BUF of size *RESID. The device block size is given by BSHIFT. *OFF and *RESID may be non-multiples of the block size. *OFF, *BUF and *RESID are updated if the operation completed successfully. */static intrdwr_partial (int rw, kdev_t dev, loff_t *off, char **buf, int *resid, int bshift){ int c, err = 0, o; long sect, nsect; struct buffer_head bhead, *bh = &bhead; struct gendisk *gd; memset (bh, 0, sizeof (struct buffer_head)); bh->b_state = 1 << BH_Lock; bh->b_dev = dev; bh->b_blocknr = *off >> bshift; bh->b_size = BSIZE; /* Check if this device has non even number of blocks. */ for (gd = gendisk_head, nsect = -1; gd; gd = gd->next) if (gd->major == MAJOR (dev)) { nsect = gd->part[MINOR (dev)].nr_sects; break; } if (nsect > 0) { sect = bh->b_blocknr << (bshift - 9); assert ((nsect - sect) > 0); if (nsect - sect < (BSIZE >> 9)) bh->b_size = (nsect - sect) << 9; } bh->b_data = alloc_buffer (bh->b_size); if (! bh->b_data) return -LINUX_ENOMEM; ll_rw_block (READ, 1, &bh); wait_on_buffer (bh); if (buffer_uptodate (bh)) { o = *off & BMASK; c = bh->b_size - o; if (c > *resid) c = *resid; if (rw == READ) memcpy (*buf, bh->b_data + o, c); else { memcpy (bh->b_data + o, *buf, c); bh->b_state = (1 << BH_Dirty) | (1 << BH_Lock); ll_rw_block (WRITE, 1, &bh); wait_on_buffer (bh); if (! buffer_uptodate (bh)) { err = -LINUX_EIO; goto out; } } *buf += c; *resid -= c; *off += c; } else err = -LINUX_EIO;out: free_buffer (bh->b_data, bh->b_size); return err;}#define BH_Bounce 16#define MAX_BUF VM_MAP_COPY_PAGE_LIST_MAX
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -