Enhanced simple block driver example
[Posted May 7, 2003 by corbet]
The following is the full source for a working simple block driver with
rudimentary device model and sysfs support.
/*
* A sample, extra-simple block driver.
*
* Copyright 2003 Eklektix, Inc. This file may be distributed under
* the terms of the BSD or GNU GPL licenses.
*/
#ifndef __KERNEL__
# define __KERNEL__
#endif
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/blk.h>
#include <linux/buffer_head.h> /* invalidate_bdev */
#include <linux/device.h>
#include <linux/stat.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "1.2";
static int major_num = 0;
module_param(major_num, int, 0);
static int hardsect_size = 512;
module_param(hardsect_size, int, 0);
static int nsectors = 1024; /* How big the drive is */
module_param(nsectors, int, 0);
/*
* Our request queue.
*/
static struct request_queue Queue;
/*
* The internal representation of our device.
*/
static struct sbd_device {
unsigned long size;
spinlock_t lock;
u8 *data;
struct gendisk *gd;
struct block_device *bdev;
} Device;
/*
* Handle an I/O request.
*/
static void sbd_transfer(struct sbd_device *dev, unsigned long sector,
unsigned long nsect, char *buffer, int write)
{
unsigned long offset = sector*hardsect_size;
unsigned long nbytes = nsect*hardsect_size;
if ((offset + nbytes) > dev->size) {
printk (KERN_NOTICE "sbd: Beyond-end write (%ld %ld)\n", offset, nbytes);
return;
}
if (write)
memcpy(dev->data + offset, buffer, nbytes);
else
memcpy(buffer, dev->data + offset, nbytes);
}
static void sbd_request(request_queue_t *q)
{
struct request *req;
while ((req = elv_next_request(q)) != NULL) {
if (! blk_fs_request(req)) {
printk (KERN_NOTICE "Skip non-CMD request\n");
end_request(req, 0);
continue;
}
sbd_transfer(&Device, req->sector, req->current_nr_sectors,
req->buffer, rq_data_dir(req));
end_request(req, 1);
}
}
/*
* Open and close.
*/
static int sbd_open(struct inode *inode, struct file *filp)
{
/*
* Remember the block_device pointer - but only once.
*/
if (! Device.bdev) {
Device.bdev = inode->i_bdev;
atomic_inc(&Device.bdev->bd_count);
}
return 0;
}
static int sbd_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* Look for a media change.
*/
int sbd_media_changed(struct gendisk *gd)
{
/* Look in gd->private_data for our disk structure */
return 0;
}
/*
* Revalidate.
*/
int sbd_revalidate(struct gendisk *gd)
{
return 0;
}
/*
* Device model stuff.
*/
static struct device_driver sbd_driver = {
.name = "sbd",
.bus = &system_bus_type,
};
static struct sys_device sbd_sys_device = {
.name = "sbd",
.dev = {
.name = "Simple block device",
.driver = &sbd_driver
},
};
/*
* Set up "version" as a driver attribute.
*/
static ssize_t show_version(struct device_driver *drv, char *buf)
{
strcpy(buf, Version);
return strlen(Version) + 1;
}
static DRIVER_ATTR(version, S_IRUGO, show_version, NULL);
/*
* Return device number as a device attribute.
*/
static ssize_t show_devnum(struct device *dev, char *buf)
{
return sprintf(buf, "%02x00", major_num);
}
DEVICE_ATTR(device, S_IRUGO, show_devnum, 0);
/*
* The device operations structure.
*/
static struct block_device_operations sbd_ops = {
.owner = THIS_MODULE,
.open = sbd_open,
.release = sbd_release,
.media_changed = sbd_media_changed,
.revalidate_disk = sbd_revalidate
};
static int __init sbd_init(void)
{
/*
* Set up our internal device.
*/
Device.size = nsectors*hardsect_size;
spin_lock_init(&Device.lock);
Device.data = vmalloc(Device.size);
if (Device.data == NULL)
return -ENOMEM;
Device.bdev = NULL; /* we'll get it at open time */
/*
* Now we'll get set up with sysfs.
*/
driver_register(&sbd_driver);
driver_create_file(&sbd_driver, &driver_attr_version);
sys_device_register(&sbd_sys_device);
device_create_file(&sbd_sys_device.dev, &dev_attr_device);
/*
* Get registered.
*/
major_num = register_blkdev(major_num, "sbd");
if (major_num <= 0) {
printk(KERN_WARNING "sbd: unable to get major number\n");
goto out;
}
/*
* And the gendisk structure.
*/
Device.gd = alloc_disk(1);
if (! Device.gd)
goto out_unregister;
Device.gd->major = major_num;
Device.gd->first_minor = 0;
Device.gd->fops = &sbd_ops;
Device.gd->private_data = &Device;
Device.gd->driverfs_dev = &sbd_sys_device.dev;
strcpy (Device.gd->disk_name, "sbd0");
set_capacity(Device.gd, nsectors);
blk_init_queue(&Queue, sbd_request, &Device.lock);
Device.gd->queue = &Queue;
add_disk(Device.gd);
return 0;
out_unregister:
unregister_blkdev(major_num, "sbd");
out:
vfree(Device.data);
return -ENOMEM;
}
static void sbd_exit(void)
{
driver_unregister(&sbd_driver);
sys_device_unregister(&sbd_sys_device);
if (Device.bdev) {
invalidate_bdev(Device.bdev, 1);
bdput(Device.bdev);
}
del_gendisk(Device.gd);
put_disk(Device.gd);
unregister_blkdev(major_num, "sbd");
vfree(Device.data);
}
module_init(sbd_init);
module_exit(sbd_exit);
(
Log in to post comments)