Like char devices, block devices can be acted on by using the ioctl system call. The only relevant difference between block and char ioctl implementations is that block drivers share a number of common ioctl commands that most drivers are expected to support.
The commands that block drivers usually handle are the following,
declared in <linux/fs.h>
.
BLKGETSIZE
Retrieve the size of the current device, expressed as the number of
sectors. The value of arg
passed in by the system
call is a pointer to a long
value and should be
used to copy the size to a user-space variable. This
ioctl command is used, for instance, by
mkfs to know the size of the filesystem
being created.
BLKFLSBUF
Literally, “flush buffers.” The implementation of this command is the same for every device and is shown later with the sample code for the whole ioctl method.
BLKRRPART
Reread the partition table. This command is meaningful only for partitionable devices, introduced later in this chapter.
BLKRAGET
,
BLKRASET
Used to get and change the current block-level read-ahead value (the
one stored in the read_ahead
array) for the
device. For GET
, the current value should be
written to user space as a long
item using the
pointer passed to ioctl in
arg
; for SET
, the new value is
passed as an argument.
BLKFRAGET
,
BLKFRASET
Get and set the filesystem-level read-ahead value (the one stored in
max_readahead
) for this device.
BLKROSET
,
BLKROGET
These commands are used to change and check the read-only flag for the device.
BLKSECTGET
,
BLKSECTSET
These commands retrieve and set the maximum number of sectors per
request (as stored in max_sectors
).
BLKSSZGET
Returns the sector size of this block device in the integer variable
pointed to by the caller; this size comes directly from the
hardsect_size
array.
BLKPG
The BLKPG
command allows user-mode programs to add
and delete partitions. It is implemented by
blk_ioctl (described shortly), and no drivers in
the mainline kernel provide their own implementation.
BLKELVGET
,
BLKELVSET
These commands allow some control over how the elevator request
sorting algorithm works. As with BLKPG
, no driver
implements them directly.
HDIO_GETGEO
Defined in <linux/hdreg.h>
and used to
retrieve the disk geometry. The geometry should be written to user
space in a struct hd_geometry
, which is declared in
hdreg.h
as well.
sbull shows the general implementation for
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 in ide.c
and hd.c
for
more information about these commands.
Almost all of these ioctl commands are
implemented in the same way for all block devices. The 2.4 kernel has
provided a function, blk_ioctl, that may be
called to implement the common commands; it is declared in
<linux/blkpg.h>
. Often the only ones that
must be implemented in the driver itself are
BLKGETSIZE
and HDIO_GETGEO
. The
driver can then safely pass any other commands to
blk_ioctl for handling.
The sbull device supports only the general commands just listed, because implementing device-specific commands is no different from the implementation of commands 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 ", 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: /* * 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 that when you compile the module, you can
turn on debugging to see which ioctl commands are
invoked on the device.