Linux Device Drivers 2nd Edition (779877), страница 85
Текст из файла (страница 85)
There isno function provided to do this work, so it’s done by hand:for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next))if (*gdp == &spull_gendisk) {*gdp = (*gdp)->next;break;}Note that there is no unr egister_disk to complement the register_disk function.Everything done by register_disk is stored in the driver’s own arrays, so there is noadditional cleanup required at unload time.Partition Detection Using initrdIf you want to mount your root filesystem from a device whose driver is availableonly in modularized form, you must use the initrd facility offered by modernLinux kernels. We won’t introduce initrd here; this subsection is aimed at readerswho know about initrd and wonder how it affects block drivers.
More informationon initrd can be found in Documentation/initrd.txt in the kernel source.When you boot a kernel with initrd, it establishes a temporary running environment before it mounts the real root filesystem. Modules are usually loaded fromwithin the RAM disk being used as the temporary root file system.Because the initrd process is run after all boot-time initialization is complete (butbefore the real root filesystem has been mounted), there’s no difference betweenloading a normal module and loading one living in the initrd RAM disk.
If a drivercan be correctly loaded and used as a module, all Linux distributions that haveinitrd available can include the driver on their installation disks without requiringyou to hack in the kernel source.The Device Methods for spullWe have seen how to initialize partitionable devices, but not yet how to accessdata within the partitions. To do that, we need to make use of the partition information stored in the gendisk->part array by register_disk. This array is madeup of hd_struct structures, and is indexed by the minor number. Thehd_struct has two fields of interest: start_sect tells where a given partitionstarts on the disk, and nr_sects gives the size of that partition.Here we will show how spull makes use of that information.
The following codeincludes only those parts of spull that differ from sbull, because most of the codeis exactly the same.36022 June 2001 16:41http://openlib.org.uaPartitionable DevicesFirst of all, open and close must keep track of the usage count for each device.Because the usage count refers to the physical device (unit), the following declaration and assignment is used for the dev variable:Spull_Dev *dev = spull_devices + DEVICE_NR(inode->i_rdev);The DEVICE_NR macro used here is the one that must be declared before<linux/blk.h> is included; it yields the physical device number without takinginto account which partition is being used.Although almost every device method works with the physical device as a whole,ioctl should access specific information for each partition.
For example, when mkfscalls ioctl to retrieve the size of the device on which it will build a filesystem, itshould be told the size of the partition of interest, not the size of the wholedevice. Here is how the BLKGETSIZE ioctl command is affected by the changefrom one minor number per device to multiple minor numbers per device. As youmight expect, spull_gendisk->part is used as the source of the partition size.case BLKGETSIZE:/* Return the device size, expressed in sectors */err = ! access_ok (VERIFY_WRITE, arg, sizeof(long));if (err) return -EFAULT;size = spull_gendisk.part[MINOR(inode->i_rdev)].nr_sects;if (copy_to_user((long *) arg, &size, sizeof (long)))return -EFAULT;return 0;The other ioctl command that is different for partitionable devices is BLKRRPART.Rereading the partition table makes sense for partitionable devices and is equivalent to revalidating a disk after a disk change:case BLKRRPART: /* re-read partition table */return spull_revalidate(inode->i_rdev);But the major difference between sbull and spull is in the request function.
Inspull, the request function needs to use the partition information in order to correctly transfer data for the different minor numbers. Locating the transfer is doneby simply adding the starting sector to that provided in the request; the partitionsize information is then used to be sure the request fits within the partition. Oncethat is done, the implementation is the same as for sbull.Here are the relevant lines in spull_r equest:ptr = device->data +(spull_partitions[minor].start_sect + req->sector)*SPULL_HARDSECT;size = req->current_nr_sectors*SPULL_HARDSECT;/** Make sure that the transfer fits within the device.*/if (req->sector + req->current_nr_sectors >spull_partitions[minor].nr_sects) {36122 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block Driversstatic int count = 0;if (count++ < 5)printk(KERN_WARNING "spull: request past end of partition\n");return 0;}The number of sectors is multiplied by the hardware sector size (which, remember, is hardwired in spull ) to get the size of the partition in bytes.Interrupt-Driven Block DriversWhen a driver controls a real hardware device, operation is usually interruptdriven.
Using interrupts helps system performance by releasing the processor during I/O operations. In order for interrupt-driven I/O to work, the device beingcontrolled must be able to transfer data asynchronously and to generate interrupts.When the driver is interrupt driven, the request function spawns a data transferand returns immediately without calling end_r equest.
However, the kernel doesn’tconsider a request fulfilled unless end_r equest (or its component parts) has beencalled. Therefore, the top-half or the bottom-half interrupt handler callsend_r equest when the device signals that the data transfer is complete.Neither sbull nor spull can transfer data without using the system microprocessor;however, spull is equipped with the capability of simulating interrupt-driven operation if the user specifies the irq=1 option at load time. When irq is not 0, thedriver uses a kernel timer to delay fulfillment of the current request.
The length ofthe delay is the value of irq: the greater the value, the longer the delay.As always, block transfers begin when the kernel calls the driver’s request function. The request function for an interrupt-driven device instructs the hardware toperform the transfer and then returns; it does not wait for the transfer to complete.The spull request function performs the usual error checks and then callsspull_transfer to transfer the data (this is the task that a driver for real hardwareperforms asynchronously). It then delays acknowledgment until interrupt time:void spull_irqdriven_request(request_queue_t *q){Spull_Dev *device;int status;long flags;/* If we are already processing requests, don’t do any more now.
*/if (spull_busy)return;while(1) {INIT_REQUEST;/* returns when queue is empty *//* Which "device" are we using? */device = spull_locate_device (CURRENT);36222 June 2001 16:41http://openlib.org.uaInterrupt-Driven Block Driversif (device == NULL) {end_request(0);continue;}spin_lock_irqsave(&device->lock, flags);/* Perform the transfer and clean up. */status = spull_transfer(device, CURRENT);spin_unlock_irqrestore(&device->lock, flags);/* ... and wait for the timer to expire -- no end_request(1) */spull_timer.expires = jiffies + spull_irq;add_timer(&spull_timer);spull_busy = 1;return;}}New requests can accumulate while the device is dealing with the current one.Because reentrant calls are almost guaranteed in this scenario, the request functionsets a spull_busy flag so that only one transfer happens at any given time.Since the entire function runs with the io_request_lock held (the kernel,remember, obtains this lock before calling the request function), there is no needfor particular care in testing and setting the busy flag.
Otherwise, an atomic_titem should have been used instead of an int variable in order to avoid race conditions.The interrupt handler has a couple of tasks to perform. First, of course, it mustcheck the status of the outstanding transfer and clean up the request. Then, ifthere are further requests to be processed, the interrupt handler is responsible forgetting the next one started.
To avoid code duplication, the handler usually justcalls the request function to start the next transfer. Remember that the requestfunction expects the caller to hold the io_request_lock, so the interrupt handler will have to obtain it. The end_r equest function also requires this lock, ofcourse.In our sample module, the role of the interrupt handler is performed by the function invoked when the timer expires.
That function calls end_r equest and schedules the next data transfer by calling the request function. In the interest of codesimplicity, the spull interrupt handler performs all this work at ‘‘interrupt’’ time; areal driver would almost certainly defer much of this work and run it from a taskqueue or tasklet./* this is invoked when the timer expires */void spull_interrupt(unsigned long unused){unsigned long flagsspin_lock_irqsave(&io_request_lock, flags);end_request(1);/* This request is done - we always succeed */36322 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block Driversspull_busy = 0; /* We have io_request_lock, no request conflict */if (! QUEUE_EMPTY) /* more of them? */spull_irqdriven_request(NULL); /* Start the next transfer */spin_unlock_irqrestore(&io_request_lock, flags);}If you try to run the interrupt-driven flavor of the spull module, you’ll barely noticethe added delay.
The device is almost as fast as it was before because the buffercache avoids most data transfers between memory and the device. If you want toperceive how a slow device behaves, you can specify a bigger value for irq=when loading spull.Backward CompatibilityMuch has changed with the block device layer, and most of those changes happened between the 2.2 and 2.4 stable releases. Here is a quick summary of whatwas different before.
As always, you can look at the drivers in the sample source,which work on 2.0, 2.2, and 2.4, to see how the portability challenges have beenhandled.The block_device_operations structure did not exist in Linux 2.2. Instead,block drivers used a file_operations structure just like char drivers. Thecheck_media_change and revalidate methods used to be a part of that structure.The kernel also provided a set of generic functions—block_r ead, block_write, andblock_fsync—which most drivers used in their file_operations structures. Atypical 2.2 or 2.0 file_operations initialization looked like this:struct file_operations sbull_bdops = {read:block_read,write:block_write,ioctl:sbull_ioctl,open:sbull_open,release:sbull_release,fsync:block_fsync,check_media_change: sbull_check_change,revalidate: sbull_revalidate};Note that block drivers are subject to the same changes in the file_operations prototypes between 2.0 and 2.2 as char drivers.In 2.2 and previous kernels, the request function was stored in the blk_devglobal array.