Linux Device Drivers 2nd Edition (779877), страница 83
Текст из файла (страница 83)
This ioctl command is used, for instance, by mkfs to know the size of the filesystem beingcreated.BLKFLSBUFLiterally, ‘‘flush buffers.’’ The implementation of this command is the same forevery device and is shown later with the sample code for the whole ioctlmethod.BLKRRPARTReread the partition table. This command is meaningful only for partitionabledevices, introduced later in this chapter.BLKRAGETBLKRASETUsed to get and change the current block-level read-ahead value (the onestored in the read_ahead array) for the device. For GET, the current valueshould be written to user space as a long item using the pointer passed toioctl in arg; for SET, the new value is passed as an argument.BLKFRAGETBLKFRASETGet and set the filesystem-level read-ahead value (the one stored inmax_readahead) for this device.BLKROSETBLKROGETThese commands are used to change and check the read-only flag for thedevice.BLKSECTGETBLKSECTSETThese commands retrieve and set the maximum number of sectors per request(as stored in max_sectors).BLKSSZGETReturns the sector size of this block device in the integer variable pointed toby the caller; this size comes directly from the hardsect_size array.BLKPGThe BLKPG command allows user-mode programs to add and delete partitions.
It is implemented by blk_ioctl (described shortly), and no drivers in themainline kernel provide their own implementation.35022 June 2001 16:41http://openlib.org.uaThe ioctl MethodBLKELVGETBLKELVSETThese commands allow some control over how the elevator request sortingalgorithm works. As with BLKPG, no driver implements them directly.HDIO_GETGEODefined in <linux/hdreg.h> and used to retrieve the disk geometry. Thegeometry should be written to user space in a struct hd_geometry,which is declared in hdr eg.h as well. sbull shows the general implementationfor this command.The HDIO_GETGEO command is the most commonly used of a series of HDIO_commands, all defined in <linux/hdreg.h>.
The interested reader can look inide.c and hd.c for more information about these commands.Almost all of these ioctl commands are implemented in the same way for all blockdevices. The 2.4 kernel has provided a function, blk_ioctl, that may be called toimplement the common commands; it is declared in <linux/blkpg.h>. Oftenthe only ones that must be implemented in the driver itself are BLKGETSIZE andHDIO_GETGEO. The driver can then safely pass any other commands to blk_ioctlfor handling.The sbull device supports only the general commands just listed, because implementing device-specific commands is no different from the implementation ofcommands for char drivers.
The ioctl implementation for sbull is as follows:int sbull_ioctl (struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg){int err;long size;struct hd_geometry geo;PDEBUG("ioctl 0x%x 0x%lx\n", cmd, arg);switch(cmd) {case BLKGETSIZE:/* Return the device size, expressed in sectors */if (!arg) return -EINVAL; /* NULL pointer: not valid */err = ! access_ok (VERIFY_WRITE, arg, sizeof(long));if (err) return -EFAULT;size = blksize*sbull_sizes[MINOR(inode->i_rdev)]/ sbull_hardsects[MINOR(inode->i_rdev)];if (copy_to_user((long *) arg, &size, sizeof (long)))return -EFAULT;return 0;case BLKRRPART: /* reread partition table: can’t do it */return -ENOTTY;case HDIO_GETGEO:35122 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block Drivers/** Get geometry: since we are a virtual device, we have to make* up something plausible.
So we claim 16 sectors, four heads,* and calculate the corresponding number of cylinders. We set* the start of data at sector four.*/err = ! access_ok(VERIFY_WRITE, arg, sizeof(geo));if (err) return -EFAULT;size = sbull_size * blksize / sbull_hardsect;geo.cylinders = (size & ˜0x3f) >> 6;geo.heads = 4;geo.sectors = 16;geo.start = 4;if (copy_to_user((void *) arg, &geo, sizeof(geo)))return -EFAULT;return 0;default:/** For ioctls we don’t understand, let the block layer* handle them.*/return blk_ioctl(inode->i_rdev, cmd, arg);}return -ENOTTY; /* unknown command */}The PDEBUG statement at the beginning of the function has been left in so thatwhen you compile the module, you can turn on debugging to see which ioctlcommands are invoked on the device.Removable DevicesThus far, we have ignored the final two file operations in theblock_device_operations structure, which deal with devices that supportremovable media.
It’s now time to look at them; sbull isn’t actually removable butit pretends to be, and therefore it implements these methods.The operations in question are check_media_change and revalidate. The former isused to find out if the device has changed since the last access, and the latter reinitializes the driver’s status after a disk change.As far as sbull is concerned, the data area associated with a device is released halfa minute after its usage count drops to zero. Leaving the device unmounted (orclosed) long enough simulates a disk change, and the next access to the deviceallocates a new memory area.This kind of ‘‘timely expiration’’ is implemented using a kernel timer.35222 June 2001 16:41http://openlib.org.uaRemovable Devicescheck_media_changeThe checking function receives kdev_t as a single argument that identifies thedevice. The return value is 1 if the medium has been changed and 0 otherwise.
Ablock driver that doesn’t support removable devices can avoid declaring the function by setting bdops->check_media_change to NULL.It’s interesting to note that when the device is removable but there is no way toknow if it changed, returning 1 is a safe choice. This is the behavior of the IDEdriver when dealing with removable disks.The implementation in sbull returns 1 if the device has already been removedfrom memory due to the timer expiration, and 0 if the data is still valid.
If debugging is enabled, it also prints a message to the system logger; the user can thusverify when the method is called by the kernel.int sbull_check_change(kdev_t i_rdev){int minor = MINOR(i_rdev);Sbull_Dev *dev = sbull_devices + minor;PDEBUG("check_change for dev %i\n",minor);if (dev->data)return 0; /* still valid */return 1; /* expired */}RevalidationThe validation function is called when a disk change is detected. It is also calledby the various stat system calls implemented in version 2.1 of the kernel. Thereturn value is currently unused; to be safe, return 0 to indicate success and a negative error code in case of error.The action performed by revalidate is device specific, but revalidate usuallyupdates the internal status information to reflect the new device.In sbull, the revalidate method tries to allocate a new data area if there is notalready a valid area.int sbull_revalidate(kdev_t i_rdev){Sbull_Dev *dev = sbull_devices + MINOR(i_rdev);PDEBUG("revalidate for dev %i\n",MINOR(i_rdev));if (dev->data)return 0;35322 June 2001 16:41http://openlib.org.uaChapter 12: Loading Block Driversdev->data = vmalloc(dev->size);if (!dev->data)return -ENOMEM;return 0;}Extra CareDrivers for removable devices should also check for a disk change when thedevice is opened.
The kernel provides a function to cause this check to happen:int check_disk_change(kdev_t dev);The return value is nonzero if a disk change was detected. The kernel automatically calls check_disk_change at mount time, but not at open time.Some programs, however, directly access disk data without mounting the device:fsck, mcopy, and fdisk are examples of such programs.
If the driver keeps statusinformation about removable devices in memory, it should call the kernelcheck_disk_change function when the device is first opened. This function usesthe driver methods (check_media_change and revalidate), so nothing special hasto be implemented in open itself.Here is the sbull implementation of open, which takes care of the case in whichthere’s been a disk change:int sbull_open (struct inode *inode, struct file *filp){Sbull_Dev *dev; /* device information */int num = MINOR(inode->i_rdev);if (num >= sbull_devs) return -ENODEV;dev = sbull_devices + num;spin_lock(&dev->lock);/* revalidate on first open and fail if no data is there */if (!dev->usage) {check_disk_change(inode->i_rdev);if (!dev->data){spin_unlock (&dev->lock);return -ENOMEM;}}dev->usage++;spin_unlock(&dev->lock);MOD_INC_USE_COUNT;return 0;/* success */}Nothing else needs to be done in the driver for a disk change.
Data is corruptedanyway if a disk is changed while its open count is greater than zero. The only35422 June 2001 16:41http://openlib.org.uaPartitionable Devicesway the driver can prevent this problem from happening is for the usage count tocontrol the door lock in those cases where the physical device supports it. Thenopen and close can disable and enable the lock appropriately.Partitionable DevicesMost block devices are not used in one large chunk.