A class-aware SBD driver
[Posted November 25, 2003 by corbet]
/*
* A sample, extra-simple block driver.
*
* Copyright 2003 Eklektix, Inc. Redistributable under the terms
* of the GNU GPL.
*/
#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/hdreg.h>
#include <linux/device.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "1.3";
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);
/*
* We can tweak our hardware sector size, but the kernel talks to us
* in terms of small sectors, always.
*/
#define KERNEL_SECTOR_SIZE 512
/*
* 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 class_device classdev;
} Device;
/*
* The "sbd" class.
*/
static void sbd_class_release(struct class_device *cd)
{
/* A real class with dynamic devices would free them here */
printk (KERN_NOTICE "sbd_class_release()\n");
}
static struct class sbd_class = {
.name = "sbd",
.release = sbd_class_release
};
/*
* Create some class device attributes.
*/
static ssize_t show_version(struct class_device *cd, char *buf)
{
sprintf(buf, "%s\n", Version);
return strlen(buf) + 1;
}
static CLASS_DEVICE_ATTR(version, S_IRUGO, show_version, NULL);
static ssize_t show_devnum(struct class_device *cd, char *buf)
{
sprintf (buf, "%d:%d\n", major_num, 0);
return strlen(buf) + 1;
}
static CLASS_DEVICE_ATTR(device, S_IRUGO, show_devnum, NULL);
static ssize_t show_hardsect(struct class_device *cd, char *buf)
{
sprintf (buf, "%d\n", hardsect_size);
return strlen(buf) + 1;
}
static CLASS_DEVICE_ATTR(hardsect, S_IRUGO, show_hardsect, NULL);
static ssize_t show_nsectors(struct class_device *cd, char *buf)
{
sprintf (buf, "%d\n", nsectors);
return strlen(buf) + 1;
}
static CLASS_DEVICE_ATTR(nsectors, S_IRUGO, show_nsectors, NULL);
static struct class_device_attribute *sbd_attrs[] = {
&class_device_attr_version,
&class_device_attr_device,
&class_device_attr_hardsect,
&class_device_attr_nsectors,
NULL
};
/*
* 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);
}
}
/*
* Ioctl.
*/
int sbd_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
long size;
struct hd_geometry geo;
switch(cmd) {
/*
* The only command we need to interpret is HDIO_GETGEO, since
* we can't partition the drive otherwise. We have no real
* geometry, of course, so make something up.
*/
case HDIO_GETGEO:
size = Device.size*(hardsect_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; /* unknown command */
}
/*
* The device operations structure.
*/
static struct block_device_operations sbd_ops = {
.owner = THIS_MODULE,
.ioctl = sbd_ioctl
};
static int __init sbd_init(void)
{
int i;
/*
* 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;
/*
* Get a request queue.
*/
Queue = blk_init_queue(sbd_request, &Device.lock);
if (Queue == NULL)
goto out;
blk_queue_hardsect_size(Queue, hardsect_size);
/*
* 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(16);
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;
strcpy (Device.gd->disk_name, "sbd0");
set_capacity(Device.gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
Device.gd->queue = Queue;
add_disk(Device.gd);
/*
* And the class info.
*/
class_register(&sbd_class);
Device.classdev.class = &sbd_class;
strcpy (Device.classdev.class_id, "sbd0");
if (class_device_register(&Device.classdev))
printk(KERN_ERR "Unable to register sbd class device");
for (i = 0; sbd_attrs[i]; i++)
class_device_create_file(&Device.classdev, sbd_attrs[i]);
return 0;
out_unregister:
unregister_blkdev(major_num, "sbd");
out:
vfree(Device.data);
return -ENOMEM;
}
static void __exit sbd_exit(void)
{
class_device_unregister(&Device.classdev);
class_unregister(&sbd_class);
del_gendisk(Device.gd);
put_disk(Device.gd);
unregister_blkdev(major_num, "sbd");
blk_cleanup_queue(Queue);
vfree(Device.data);
}
module_init(sbd_init);
module_exit(sbd_exit);