The ioctl Method

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\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:
        /*
	 * 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.

Get Linux Device Drivers, Second Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.