Block Device Operations

This chapter covers block_device_operations for open/release, ioctl, and disk geometry.

block_device_operations Structure

#include <linux/blkdev.h>

static const struct block_device_operations my_fops = {
    .owner       = THIS_MODULE,
    .open        = my_open,
    .release     = my_release,
    .ioctl       = my_ioctl,
    .getgeo      = my_getgeo,
};

/* In init: */
disk->fops = &my_fops;

Open and Release

Called when the block device is opened/closed:

static int my_open(struct gendisk *disk, blk_mode_t mode)
{
    struct my_device *dev = disk->private_data;

    /* Check if device is ready */
    if (!dev->data)
        return -ENXIO;

    /* Track open count (optional) */
    atomic_inc(&dev->open_count);

    /* Check for exclusive access */
    if (mode & BLK_OPEN_EXCL) {
        if (atomic_read(&dev->open_count) > 1) {
            atomic_dec(&dev->open_count);
            return -EBUSY;
        }
    }

    return 0;
}

static void my_release(struct gendisk *disk)
{
    struct my_device *dev = disk->private_data;

    atomic_dec(&dev->open_count);

    /* Flush any cached data if needed */
}

release is called when the last user closes the device, not on every close().

Disk Geometry

Report geometry for legacy tools and partitioning:

static int my_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
    struct my_device *dev = bdev->bd_disk->private_data;
    sector_t capacity = get_capacity(bdev->bd_disk);

    /* Standard fake geometry for modern drives */
    geo->heads = 16;
    geo->sectors = 63;
    geo->cylinders = capacity / (geo->heads * geo->sectors);

    /* For small disks (< 16MB) */
    if (capacity < 16 * 2048) {
        geo->heads = 1;
        geo->sectors = 32;
        geo->cylinders = capacity / 32;
    }

    geo->start = 0;
    return 0;
}

Result of fdisk -l /dev/myblk0:

Disk /dev/myblk0: 16 MiB, ...
Geometry: 16 heads, 63 sectors/track, 32 cylinders

IOCTL Operations

Handle custom device commands:

#define MY_IOCTL_GET_SIZE _IOR('B', 1, unsigned long)
#define MY_IOCTL_CLEAR    _IO('B', 2)
#define MY_IOCTL_SET_RO   _IOW('B', 3, int)

static int my_ioctl(struct block_device *bdev, blk_mode_t mode,
                    unsigned int cmd, unsigned long arg)
{
    struct my_device *dev = bdev->bd_disk->private_data;
    void __user *argp = (void __user *)arg;

    switch (cmd) {
    case MY_IOCTL_GET_SIZE:
        return put_user(dev->size, (unsigned long __user *)argp);

    case MY_IOCTL_CLEAR:
        if (!(mode & BLK_OPEN_WRITE))
            return -EACCES;
        memset(dev->data, 0, dev->size);
        return 0;

    case MY_IOCTL_SET_RO:
    {
        int ro;
        if (get_user(ro, (int __user *)argp))
            return -EFAULT;
        set_disk_ro(bdev->bd_disk, ro);
        return 0;
    }

    default:
        return -ENOTTY;
    }
}

Return -ENOTTY for unknown ioctls, not -EINVAL. This follows convention and allows stacking.

Read-Only Mode

Set or check read-only status:

/* Set disk read-only */
set_disk_ro(disk, 1);

/* Check if read-only */
if (get_disk_ro(disk)) {
    /* Reject writes */
}

/* In queue_rq: enforce read-only */
if (rq_data_dir(rq) && get_disk_ro(rq->q->disk)) {
    blk_mq_end_request(rq, BLK_STS_IOERR);
    return BLK_STS_OK;
}

Handling Media Changes

For removable media (like virtual disk images):

static unsigned int my_check_events(struct gendisk *disk,
                                     unsigned int clearing)
{
    struct my_device *dev = disk->private_data;

    if (dev->media_changed) {
        dev->media_changed = false;
        return DISK_EVENT_MEDIA_CHANGE;
    }
    return 0;
}

static int my_revalidate_disk(struct gendisk *disk)
{
    struct my_device *dev = disk->private_data;

    /* Re-read capacity, etc. */
    set_capacity(disk, dev->new_size);

    return 0;
}

static const struct block_device_operations my_fops = {
    .owner           = THIS_MODULE,
    .check_events    = my_check_events,
    .revalidate_disk = my_revalidate_disk,
    /* ... */
};

/* Enable event polling */
disk->events = DISK_EVENT_MEDIA_CHANGE;
disk->event_flags = DISK_EVENT_FLAG_POLL;

Queue Limits

Configure I/O constraints:

static int my_init(void)
{
    /* ... allocate disk ... */

    struct queue_limits *lim = &disk->queue->limits;

    /* Logical block size (smallest addressable unit) */
    blk_queue_logical_block_size(disk->queue, 512);

    /* Physical block size (optimal I/O size) */
    blk_queue_physical_block_size(disk->queue, 4096);

    /* Maximum sectors per request */
    blk_queue_max_hw_sectors(disk->queue, 256);

    /* Discard (TRIM) support */
    blk_queue_max_discard_sectors(disk->queue, UINT_MAX);
    disk->queue->limits.discard_granularity = 512;

    /* ... */
}

Handling Discard (TRIM)

Support discard requests for SSDs or sparse storage:

static blk_status_t my_queue_rq(struct blk_mq_hw_ctx *hctx,
                                 const struct blk_mq_queue_data *bd)
{
    struct request *rq = bd->rq;
    struct my_device *dev = rq->q->queuedata;

    blk_mq_start_request(rq);

    /* Check for discard request */
    if (req_op(rq) == REQ_OP_DISCARD) {
        sector_t sector = blk_rq_pos(rq);
        unsigned int len = blk_rq_bytes(rq);

        /* Zero the discarded region (or mark sparse) */
        memset(dev->data + (sector << SECTOR_SHIFT), 0, len);

        blk_mq_end_request(rq, BLK_STS_OK);
        return BLK_STS_OK;
    }

    /* Handle normal read/write */
    /* ... */
}

Complete block_device_operations

static const struct block_device_operations my_fops = {
    .owner           = THIS_MODULE,
    .open            = my_open,
    .release         = my_release,
    .ioctl           = my_ioctl,
    .getgeo          = my_getgeo,
    .check_events    = my_check_events,     /* Removable media */
    .revalidate_disk = my_revalidate_disk,  /* Media changed */
};

Summary

Operation Purpose
open Device opened (check access, increment count)
release Last user closed device
ioctl Custom commands
getgeo Report disk geometry
check_events Poll for media changes
revalidate_disk Update after media change

Further Reading


Back to top

Linux Driver Development Guide is a community resource for learning kernel driver development. Not affiliated with the Linux Foundation. Content provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.