Though I'm still not
there yet, I think I'm getting close to it. The .c file can be compiled but I'm getting a warning that I'm sure will be a problem if I try to load the module.
The code is like this right now
Code:
/*
* 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/blkdev.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;
} Device;
/*
* Handle an I/O request.
*/
static void sbd_transfer(struct sbd_device *dev, struct request *q)
{
unsigned long offset = blk_rq_pos(q)*hardsect_size;
unsigned long nbytes = blk_rq_cur_sectors(q)*hardsect_size;
if ((offset + nbytes) > dev->size) {
printk (KERN_NOTICE "sbd: Beyond-end write (%ld %ld)\n", offset, nbytes);
return;
}
if (rq_data_dir(q))
// write
memcpy(dev->data + offset, q->buffer, nbytes);
else
// read
memcpy(q->buffer, dev->data + offset, nbytes);
}
static void sbd_request(struct request_queue *q)
{
struct request *req;
while ((req = blk_fetch_request(q)) != NULL) {
if (! blk_fs_request(req)) {
printk (KERN_NOTICE "Skip non-CMD request\n");
blk_end_request_cur(req, 0);
continue;
}
sbd_transfer(&Device, req);
blk_end_request_cur(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)
{
/*
* 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_logical_block_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);
return 0;
out_unregister:
unregister_blkdev(major_num, "sbd");
out:
vfree(Device.data);
return -ENOMEM;
}
static void __exit sbd_exit(void)
{
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);
This is from sbd.c that I found somewhere from Jonathan Corbet. It's the same sbull driver so we're still talking about the same thing.
Now, if you look carefully, you'll see that I changed the definition of bsd_transfer from
Code:
static void sbd_transfer(struct sbd_device *dev, unsigned long sector, unsigned long nsect, char *buffer, int write)
to
Code:
static void sbd_transfer(struct sbd_device *dev, struct request *q)
That's because everything I needed to pass sbd_transfer was in the request so no point in passing separate things when I had everything in request (I hope not to be messing up).
Now I want to make sure I won't things right in a couple of members I had to change from the request.
Before it was:
Code:
unsigned long offset = sector*hardsect_size;
unsigned long nbytes = nsect*hardsect_size;
[b]sector was taken from the request as
request->current_nr_sectors,
nsect was
req->buffer.
Now I have
Code:
unsigned long offset = blk_rq_pos(q)*hardsect_size;
unsigned long nbytes = blk_rq_cur_sectors(q)*hardsect_size;
[q] being the request as received in sbd_transfer. Those are the equivalent functions, right?
ANd finally, I'm getting a warning when I compile:
Code:
sbd.c:129: warning: initialization from incompatible pointer type
That's when I try to set the fops. I think it's because the definition of .ioctl is this function:
Code:
int sbd_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
But when looking at blkdev.h it is like this:
Code:
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
So the signatures are not the same. How do I correct this? Thanks in advance for your help.
Update
The original code is here:
http://lwn.net/Articles/58720/