martes, 19 de abril de 2011

Updated minibd driver for linux (sbull.c)

Hi!

I'm embarking myself in a journey to write a block driver for linux (raid5 recovery stuff, anyone? Doesn't ring a bell? Look around this blog).

My firsts steps have involved trying to figure out how linux's block layer api works... so I looked for the simplest example I could find.... and it was in the sbull.c of the minibd drive. It's a dull ram-based block driver where the basics for the API are set in place for people to see how a driver is created.

I started working with it but it was written for older versions of the kernel and the block layer API has received an overhaul that made the sbull.c driver as I got it unusable so I sat down to modify the driver to make it work while trying to figure out how things work... I hopefully did both... but even if I didn't get to understand how the API works, I did make the driver work. How well am I getting the API? Guess will have to wait for my real driver to come out to know for sure.

Here it goes. First, the Makefile so you don't have to reinvent the wheel:
obj-m := sbull.o

PWD := $(shell pwd)
KDIR := /lib/modules/$(shell uname -r)/build

all:
        make -C $(KDIR) M=$(PWD) modules
clean:
        make -C $(KDIR) M=$(PWD) clean


That's it... and now the driver (sbull.c):

/*
* Mini-block driver.
*
* this code is based on one example from LWN.NET: http://lwn.net/Articles/31513/
*
* Copyright 2003 Eklektix, Inc.  Redistributable under the terms
* of the GNU GPL.
*
* Update to new block layer api by Edmundo Carmona
* works on linux 2.6.38.3 tested on UML.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include


MODULE_LICENSE("Dual BSD/GPL");

/*
* some values that could have been configurable... but aren't (at least,
* not in the revision of the driver).
*/
#define KERNEL_SECTOR_SIZE 512
#define MAJOR_NUM 240 /* a free major number, according to devices.txt */
#define MINIBD_MINORS 16 /* number of possible partitions */
#define N_SECTORS 1024 /* number of sectors in our block device */
#define MINIBLOCK_SECTOR_SIZE 512 /* sector size */

/*
* request queue of our mini-block device.
*/
static struct request_queue *miniblock_queue;

/*
* Internal representation of our device.
*/
static struct miniblock_device {
        unsigned long size;
        spinlock_t lock;
        u8 *data;
        struct gendisk *gd;
};

static struct miniblock_device *Miniblock = NULL;

/*
* Real handling in our device of IO Requests.
*/
static void miniblock_transfer(struct miniblock_device *dev, struct request * req)
{
    unsigned long offset = blk_rq_pos(req) * MINIBLOCK_SECTOR_SIZE;
    unsigned long nbytes = blk_rq_cur_bytes(req);

    if ((offset + nbytes) > dev->size) {
        printk (KERN_NOTICE "minibd: working past the end of the block device (%ld %ld)\n", offset, nbytes);
        return;
    }
   
    if (rq_data_dir(req) == WRITE) {
        memcpy(dev->data + offset, req->buffer, nbytes);
    } else {
        memcpy(req->buffer, dev->data + offset, nbytes);
    }
}

/*
* driver handler of requests.
*/
static void miniblock_request(struct request_queue *q)
{
    struct request *req;

    req = blk_fetch_request(q);
    while (req != NULL) {
   
        if (req->cmd_type != REQ_TYPE_FS) {
            printk (KERN_NOTICE "Skip non-CMD request\n");
            blk_end_request_all(req, -EIO);
            continue;
        }
       
        miniblock_transfer(Miniblock, req);
   
    // if there are no more requests, _do not call_ blk_fetch_request
        if (!blk_end_request_cur(req, 0)) {
        req = blk_fetch_request(q);
    }
    }
}

/*
* custom Ioctls for our device.
*/
int miniblock_ioctl (struct inode *inode, struct file *filp,
                unsigned int cmd, unsigned long arg)
{
        long size;
        struct hd_geometry geo;

        switch(cmd) {
            case HDIO_GETGEO:
                size = Miniblock->size*(MINIBLOCK_SECTOR_SIZE/KERNEL_SECTOR_SIZE);
                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;
    }

    return -ENOTTY;
}

/*
* operations associated for our device.
*/
static struct block_device_operations miniblock_ops = {
    .owner          = THIS_MODULE,
    .ioctl          = miniblock_ioctl
};

/*
* Module init.
*/
static int __init miniblock_init(void)
{
        static int ret;

        ret = register_blkdev(MAJOR_NUM, "minibd");
        if (ret < 0) {
                printk(KERN_WARNING "minibd: error assigning major number\n");
                return -EBUSY;
        }

        printk(KERN_DEBUG "minibd: Successfully registeres driver\n");

        Miniblock = kmalloc(sizeof(struct miniblock_device), GFP_KERNEL);
        if (Miniblock == NULL) {
                printk(KERN_WARNING "minidb: error assigning memory with kmalloc\n");
                goto out_unregister;
        }

        /*
        * Initializing our device.
        */
        memset(Miniblock, 0, sizeof(struct miniblock_device));
        Miniblock->size = N_SECTORS*MINIBLOCK_SECTOR_SIZE;
        Miniblock->data = vmalloc(Miniblock->size);
        if (Miniblock->data == NULL) {
                printk(KERN_WARNING "minidb: error assigning memory with vmalloc\n");
                kfree(Miniblock);
                goto out_unregister;
        }
        spin_lock_init(&Miniblock->lock);

        /*
        * Create our request queue (one block per device).
        */
        miniblock_queue = blk_init_queue(miniblock_request, &Miniblock->lock);
        if (miniblock_queue == NULL) {
                printk(KERN_WARNING "minibd: error on blk_init_queue\n");
                goto out_free;
        }

        blk_queue_logical_block_size(miniblock_queue, MINIBLOCK_SECTOR_SIZE);

        /*
        * Fill out our gendisk structure
        */
        Miniblock->gd = alloc_disk(MINIBD_MINORS);
        if (!Miniblock->gd) {
                printk(KERN_WARNING "minibd: error on alloc_disk\n");
                goto out_free;
        }

        Miniblock->gd->major = MAJOR_NUM;
        Miniblock->gd->first_minor = 0;
        Miniblock->gd->fops = &miniblock_ops;
        Miniblock->gd->private_data = Miniblock;
        snprintf(Miniblock->gd->disk_name, 10, "%s", "minibd0");
        set_capacity(Miniblock->gd, N_SECTORS*(MINIBLOCK_SECTOR_SIZE/KERNEL_SECTOR_SIZE));
        Miniblock->gd->queue = miniblock_queue;

        add_disk(Miniblock->gd);

        return 0;

out_free:
        vfree(Miniblock->data);
        kfree(Miniblock);
out_unregister:
        unregister_blkdev(MAJOR_NUM, "minibd");

        return -ENOMEM;
}

/*
* module removal
*/
static void __exit miniblock_exit(void)
{
        del_gendisk(Miniblock->gd);
        put_disk(Miniblock->gd);

        unregister_blkdev(MAJOR_NUM, "minibd");
        blk_cleanup_queue(miniblock_queue);

        vfree(Miniblock->data);
        kfree(Miniblock);

        printk(KERN_DEBUG "minibd: driver removed successfully\n");
}

module_init(miniblock_init);
module_exit(miniblock_exit);

No hay comentarios:

Publicar un comentario en la entrada