diff options
author | Javier Martinez Canillas <martinez.javier@gmail.com> | 2010-11-27 07:49:17 +0100 |
---|---|---|
committer | Javier Martinez Canillas <martinez.javier@gmail.com> | 2010-11-27 07:49:17 +0100 |
commit | ab121f379a3cff458c90e6f480ba4bb68c8733dd (patch) | |
tree | a9851af109ee83646d108bc247d03b131461b764 /sbull | |
download | ldd3-ab121f379a3cff458c90e6f480ba4bb68c8733dd.tar.gz |
Linux Device Drivers 3 examples
Diffstat (limited to 'sbull')
-rw-r--r-- | sbull/Makefile | 41 | ||||
-rw-r--r-- | sbull/sbull.c | 456 | ||||
-rw-r--r-- | sbull/sbull.h | 71 | ||||
-rw-r--r-- | sbull/sbull_load | 47 | ||||
-rw-r--r-- | sbull/sbull_unload | 14 |
5 files changed, 629 insertions, 0 deletions
diff --git a/sbull/Makefile b/sbull/Makefile new file mode 100644 index 0000000..5bb807c --- /dev/null +++ b/sbull/Makefile @@ -0,0 +1,41 @@ +# Comment/uncomment the following line to disable/enable debugging +#DEBUG = y + + +# Add your debugging flag (or not) to CFLAGS +ifeq ($(DEBUG),y) + DEBFLAGS = -O -g -DSBULL_DEBUG # "-O" is needed to expand inlines +else + DEBFLAGS = -O2 +endif + +CFLAGS += $(DEBFLAGS) +CFLAGS += -I.. + +ifneq ($(KERNELRELEASE),) +# call from kernel build system + +obj-m := sbull.o + +else + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +default: + $(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +endif + + + +clean: + rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions + +depend .depend dep: + $(CC) $(CFLAGS) -M *.c > .depend + + +ifeq (.depend,$(wildcard .depend)) +include .depend +endif diff --git a/sbull/sbull.c b/sbull/sbull.c new file mode 100644 index 0000000..1a040c3 --- /dev/null +++ b/sbull/sbull.c @@ -0,0 +1,456 @@ +/* + * Sample disk driver, from the beginning. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/sched.h> +#include <linux/kernel.h> /* printk() */ +#include <linux/slab.h> /* kmalloc() */ +#include <linux/fs.h> /* everything... */ +#include <linux/errno.h> /* error codes */ +#include <linux/timer.h> +#include <linux/types.h> /* size_t */ +#include <linux/fcntl.h> /* O_ACCMODE */ +#include <linux/hdreg.h> /* HDIO_GETGEO */ +#include <linux/kdev_t.h> +#include <linux/vmalloc.h> +#include <linux/genhd.h> +#include <linux/blkdev.h> +#include <linux/buffer_head.h> /* invalidate_bdev */ +#include <linux/bio.h> + +MODULE_LICENSE("Dual BSD/GPL"); + +static int sbull_major = 0; +module_param(sbull_major, 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); +static int ndevices = 4; +module_param(ndevices, int, 0); + +/* + * The different "request modes" we can use. + */ +enum { + RM_SIMPLE = 0, /* The extra-simple request function */ + RM_FULL = 1, /* The full-blown version */ + RM_NOQUEUE = 2, /* Use make_request */ +}; +static int request_mode = RM_SIMPLE; +module_param(request_mode, int, 0); + +/* + * Minor number and partition management. + */ +#define SBULL_MINORS 16 +#define MINOR_SHIFT 4 +#define DEVNUM(kdevnum) (MINOR(kdev_t_to_nr(kdevnum)) >> MINOR_SHIFT + +/* + * We can tweak our hardware sector size, but the kernel talks to us + * in terms of small sectors, always. + */ +#define KERNEL_SECTOR_SIZE 512 + +/* + * After this much idle time, the driver will simulate a media change. + */ +#define INVALIDATE_DELAY 30*HZ + +/* + * The internal representation of our device. + */ +struct sbull_dev { + int size; /* Device size in sectors */ + u8 *data; /* The data array */ + short users; /* How many users */ + short media_change; /* Flag a media change? */ + spinlock_t lock; /* For mutual exclusion */ + struct request_queue *queue; /* The device request queue */ + struct gendisk *gd; /* The gendisk structure */ + struct timer_list timer; /* For simulated media changes */ +}; + +static struct sbull_dev *Devices = NULL; + +/* + * Handle an I/O request. + */ +static void sbull_transfer(struct sbull_dev *dev, unsigned long sector, + unsigned long nsect, char *buffer, int write) +{ + unsigned long offset = sector*KERNEL_SECTOR_SIZE; + unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE; + + if ((offset + nbytes) > dev->size) { + printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes); + return; + } + if (write) + memcpy(dev->data + offset, buffer, nbytes); + else + memcpy(buffer, dev->data + offset, nbytes); +} + +/* + * The simple form of the request function. + */ +static void sbull_request(request_queue_t *q) +{ + struct request *req; + + while ((req = elv_next_request(q)) != NULL) { + struct sbull_dev *dev = req->rq_disk->private_data; + if (! blk_fs_request(req)) { + printk (KERN_NOTICE "Skip non-fs request\n"); + end_request(req, 0); + continue; + } + // printk (KERN_NOTICE "Req dev %d dir %ld sec %ld, nr %d f %lx\n", + // dev - Devices, rq_data_dir(req), + // req->sector, req->current_nr_sectors, + // req->flags); + sbull_transfer(dev, req->sector, req->current_nr_sectors, + req->buffer, rq_data_dir(req)); + end_request(req, 1); + } +} + + +/* + * Transfer a single BIO. + */ +static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio) +{ + int i; + struct bio_vec *bvec; + sector_t sector = bio->bi_sector; + + /* Do each segment independently. */ + bio_for_each_segment(bvec, bio, i) { + char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); + sbull_transfer(dev, sector, bio_cur_sectors(bio), + buffer, bio_data_dir(bio) == WRITE); + sector += bio_cur_sectors(bio); + __bio_kunmap_atomic(bio, KM_USER0); + } + return 0; /* Always "succeed" */ +} + +/* + * Transfer a full request. + */ +static int sbull_xfer_request(struct sbull_dev *dev, struct request *req) +{ + struct bio *bio; + int nsect = 0; + + rq_for_each_bio(bio, req) { + sbull_xfer_bio(dev, bio); + nsect += bio->bi_size/KERNEL_SECTOR_SIZE; + } + return nsect; +} + + + +/* + * Smarter request function that "handles clustering". + */ +static void sbull_full_request(request_queue_t *q) +{ + struct request *req; + int sectors_xferred; + struct sbull_dev *dev = q->queuedata; + + while ((req = elv_next_request(q)) != NULL) { + if (! blk_fs_request(req)) { + printk (KERN_NOTICE "Skip non-fs request\n"); + end_request(req, 0); + continue; + } + sectors_xferred = sbull_xfer_request(dev, req); + if (! end_that_request_first(req, 1, sectors_xferred)) { + blkdev_dequeue_request(req); + end_that_request_last(req); + } + } +} + + + +/* + * The direct make request version. + */ +static int sbull_make_request(request_queue_t *q, struct bio *bio) +{ + struct sbull_dev *dev = q->queuedata; + int status; + + status = sbull_xfer_bio(dev, bio); + bio_endio(bio, bio->bi_size, status); + return 0; +} + + +/* + * Open and close. + */ + +static int sbull_open(struct inode *inode, struct file *filp) +{ + struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data; + + del_timer_sync(&dev->timer); + filp->private_data = dev; + spin_lock(&dev->lock); + if (! dev->users) + check_disk_change(inode->i_bdev); + dev->users++; + spin_unlock(&dev->lock); + return 0; +} + +static int sbull_release(struct inode *inode, struct file *filp) +{ + struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data; + + spin_lock(&dev->lock); + dev->users--; + + if (!dev->users) { + dev->timer.expires = jiffies + INVALIDATE_DELAY; + add_timer(&dev->timer); + } + spin_unlock(&dev->lock); + + return 0; +} + +/* + * Look for a (simulated) media change. + */ +int sbull_media_changed(struct gendisk *gd) +{ + struct sbull_dev *dev = gd->private_data; + + return dev->media_change; +} + +/* + * Revalidate. WE DO NOT TAKE THE LOCK HERE, for fear of deadlocking + * with open. That needs to be reevaluated. + */ +int sbull_revalidate(struct gendisk *gd) +{ + struct sbull_dev *dev = gd->private_data; + + if (dev->media_change) { + dev->media_change = 0; + memset (dev->data, 0, dev->size); + } + return 0; +} + +/* + * The "invalidate" function runs out of the device timer; it sets + * a flag to simulate the removal of the media. + */ +void sbull_invalidate(unsigned long ldev) +{ + struct sbull_dev *dev = (struct sbull_dev *) ldev; + + spin_lock(&dev->lock); + if (dev->users || !dev->data) + printk (KERN_WARNING "sbull: timer sanity check failed\n"); + else + dev->media_change = 1; + spin_unlock(&dev->lock); +} + +/* + * The ioctl() implementation + */ + +int sbull_ioctl (struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + long size; + struct hd_geometry geo; + struct sbull_dev *dev = filp->private_data; + + switch(cmd) { + case HDIO_GETGEO: + /* + * Get geometry: since we are a virtual device, we have to make + * up something plausible. So we claim 16 sectors, four heads, + * and calculate the corresponding number of cylinders. We set the + * start of data at sector four. + */ + size = dev->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 __user *) arg, &geo, sizeof(geo))) + return -EFAULT; + return 0; + } + + return -ENOTTY; /* unknown command */ +} + + + +/* + * The device operations structure. + */ +static struct block_device_operations sbull_ops = { + .owner = THIS_MODULE, + .open = sbull_open, + .release = sbull_release, + .media_changed = sbull_media_changed, + .revalidate_disk = sbull_revalidate, + .ioctl = sbull_ioctl +}; + + +/* + * Set up our internal device. + */ +static void setup_device(struct sbull_dev *dev, int which) +{ + /* + * Get some memory. + */ + memset (dev, 0, sizeof (struct sbull_dev)); + dev->size = nsectors*hardsect_size; + dev->data = vmalloc(dev->size); + if (dev->data == NULL) { + printk (KERN_NOTICE "vmalloc failure.\n"); + return; + } + spin_lock_init(&dev->lock); + + /* + * The timer which "invalidates" the device. + */ + init_timer(&dev->timer); + dev->timer.data = (unsigned long) dev; + dev->timer.function = sbull_invalidate; + + /* + * The I/O queue, depending on whether we are using our own + * make_request function or not. + */ + switch (request_mode) { + case RM_NOQUEUE: + dev->queue = blk_alloc_queue(GFP_KERNEL); + if (dev->queue == NULL) + goto out_vfree; + blk_queue_make_request(dev->queue, sbull_make_request); + break; + + case RM_FULL: + dev->queue = blk_init_queue(sbull_full_request, &dev->lock); + if (dev->queue == NULL) + goto out_vfree; + break; + + default: + printk(KERN_NOTICE "Bad request mode %d, using simple\n", request_mode); + /* fall into.. */ + + case RM_SIMPLE: + dev->queue = blk_init_queue(sbull_request, &dev->lock); + if (dev->queue == NULL) + goto out_vfree; + break; + } + blk_queue_hardsect_size(dev->queue, hardsect_size); + dev->queue->queuedata = dev; + /* + * And the gendisk structure. + */ + dev->gd = alloc_disk(SBULL_MINORS); + if (! dev->gd) { + printk (KERN_NOTICE "alloc_disk failure\n"); + goto out_vfree; + } + dev->gd->major = sbull_major; + dev->gd->first_minor = which*SBULL_MINORS; + dev->gd->fops = &sbull_ops; + dev->gd->queue = dev->queue; + dev->gd->private_data = dev; + snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a'); + set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE)); + add_disk(dev->gd); + return; + + out_vfree: + if (dev->data) + vfree(dev->data); +} + + + +static int __init sbull_init(void) +{ + int i; + /* + * Get registered. + */ + sbull_major = register_blkdev(sbull_major, "sbull"); + if (sbull_major <= 0) { + printk(KERN_WARNING "sbull: unable to get major number\n"); + return -EBUSY; + } + /* + * Allocate the device array, and initialize each one. + */ + Devices = kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL); + if (Devices == NULL) + goto out_unregister; + for (i = 0; i < ndevices; i++) + setup_device(Devices + i, i); + + return 0; + + out_unregister: + unregister_blkdev(sbull_major, "sbd"); + return -ENOMEM; +} + +static void sbull_exit(void) +{ + int i; + + for (i = 0; i < ndevices; i++) { + struct sbull_dev *dev = Devices + i; + + del_timer_sync(&dev->timer); + if (dev->gd) { + del_gendisk(dev->gd); + put_disk(dev->gd); + } + if (dev->queue) { + if (request_mode == RM_NOQUEUE) + blk_put_queue(dev->queue); + else + blk_cleanup_queue(dev->queue); + } + if (dev->data) + vfree(dev->data); + } + unregister_blkdev(sbull_major, "sbull"); + kfree(Devices); +} + +module_init(sbull_init); +module_exit(sbull_exit); diff --git a/sbull/sbull.h b/sbull/sbull.h new file mode 100644 index 0000000..c443d70 --- /dev/null +++ b/sbull/sbull.h @@ -0,0 +1,71 @@ + +/* + * sbull.h -- definitions for the char module + * + * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet + * Copyright (C) 2001 O'Reilly & Associates + * + * The source code in this file can be freely used, adapted, + * and redistributed in source or binary form, so long as an + * acknowledgment appears in derived source files. The citation + * should list that the code comes from the book "Linux Device + * Drivers" by Alessandro Rubini and Jonathan Corbet, published + * by O'Reilly & Associates. No warranty is attached; + * we cannot take responsibility for errors or fitness for use. + * + */ + + +#include <linux/ioctl.h> + +/* Multiqueue only works on 2.4 */ +#ifdef SBULL_MULTIQUEUE +# warning "Multiqueue only works on 2.4 kernels" +#endif + +/* + * Macros to help debugging + */ + +#undef PDEBUG /* undef it, just in case */ +#ifdef SBULL_DEBUG +# ifdef __KERNEL__ + /* This one if debugging is on, and kernel space */ +# define PDEBUG(fmt, args...) printk( KERN_DEBUG "sbull: " fmt, ## args) +# else + /* This one for user space */ +# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) +# endif +#else +# define PDEBUG(fmt, args...) /* not debugging: nothing */ +#endif + +#undef PDEBUGG +#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ + + +#define SBULL_MAJOR 0 /* dynamic major by default */ +#define SBULL_DEVS 2 /* two disks */ +#define SBULL_RAHEAD 2 /* two sectors */ +#define SBULL_SIZE 2048 /* two megs each */ +#define SBULL_BLKSIZE 1024 /* 1k blocks */ +#define SBULL_HARDSECT 512 /* 2.2 and 2.4 can used different values */ + +#define SBULLR_MAJOR 0 /* Dynamic major for raw device */ +/* + * The sbull device is removable: if it is left closed for more than + * half a minute, it is removed. Thus use a usage count and a + * kernel timer + */ + +typedef struct Sbull_Dev { + int size; + int usage; + struct timer_list timer; + spinlock_t lock; + u8 *data; +#ifdef SBULL_MULTIQUEUE + request_queue_t *queue; + int busy; +#endif +} Sbull_Dev; diff --git a/sbull/sbull_load b/sbull/sbull_load new file mode 100644 index 0000000..8cdec56 --- /dev/null +++ b/sbull/sbull_load @@ -0,0 +1,47 @@ +#!/bin/sh + +function make_minors { + let part=1 + while (($part < $minors)); do + let minor=$part+$2 + mknod $1$part b $major $minor + let part=$part+1 + done +} + + +# FIXME: This isn't handling minors (partitions) at all. +module="sbull" +device="sbull" +mode="664" +chardevice="sbullr" +minors=16 + +# Group: since distributions do it differently, look for wheel or use staff +if grep '^staff:' /etc/group > /dev/null; then + group="staff" +else + group="wheel" +fi + +# invoke insmod with all arguments we got +# and use a pathname, as newer modutils don't look in . by default +/sbin/insmod -f ./$module.ko $* || exit 1 + +major=`cat /proc/devices | awk "\\$2==\"$module\" {print \\$1}"` + +# Remove stale nodes and replace them, then give gid and perms + +rm -f /dev/${device}[a-d]* /dev/${device} + +mknod /dev/${device}a b $major 0 +make_minors /dev/${device}a 0 +mknod /dev/${device}b b $major 16 +make_minors /dev/${device}b 16 +mknod /dev/${device}c b $major 32 +make_minors /dev/${device}c 32 +mknod /dev/${device}d b $major 48 +make_minors /dev/${device}d 48 +ln -sf ${device}a /dev/${device} +chgrp $group /dev/${device}[a-d]* +chmod $mode /dev/${device}[a-d]* diff --git a/sbull/sbull_unload b/sbull/sbull_unload new file mode 100644 index 0000000..aa22dfb --- /dev/null +++ b/sbull/sbull_unload @@ -0,0 +1,14 @@ +#!/bin/sh +module="sbull" +device="sbull" + +# invoke rmmod with all arguments we got +/sbin/rmmod $module $* || exit 1 + +# Remove stale nodes +rm -f /dev/${device}[a-d]* /dev/${device} + + + + + |