《Linux设备驱动开发详解》源码——vmem_disk.c

/home/lihacker/develop/svn/ldd6410-read-only/training/kernel/drivers/vmem_disk


< vmem_disk.c >

/*

 * Sample disk driver, from the beginning.

 */



#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 vmem_disk_major = 0;

module_param(vmem_disk_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 vmem_disk_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 vmem_disk_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 vmem_disk_dev *Devices = NULL;



/*

 * Handle an I/O request.

 */

static void vmem_disk_transfer(struct vmem_disk_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 vmem_disk_request(struct request_queue *q)

{

	struct request *req;



	while ((req = elv_next_request(q)) != NULL) {

		struct vmem_disk_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;

		}



		vmem_disk_transfer(dev, req->sector, req->current_nr_sectors,

			req->buffer, rq_data_dir(req));



		end_request(req, 1);

	}

}





/*

 * Transfer a single BIO.

 */

static int vmem_disk_xfer_bio(struct vmem_disk_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);

		vmem_disk_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 vmem_disk_xfer_request(struct vmem_disk_dev *dev, struct request *req)

{



	struct req_iterator iter;

	int nsect = 0;

	struct bio_vec *bvec;



	/* Macro rq_for_each_bio is gone.

	 * In most cases one should use rq_for_each_segment.

	 */

	rq_for_each_segment(bvec, req, iter) {

		char *buffer = __bio_kmap_atomic(iter.bio, iter.i, KM_USER0);

		sector_t sector = iter.bio->bi_sector;

		vmem_disk_transfer(dev, sector, bio_cur_sectors(iter.bio),

			buffer, bio_data_dir(iter.bio) == WRITE);

		sector += bio_cur_sectors(iter.bio);

		__bio_kunmap_atomic(iter.bio, KM_USER0);

		nsect += iter.bio->bi_size/KERNEL_SECTOR_SIZE;

	}

	return nsect;

}





/*

 * Smarter request function that "handles clustering".

 */

static void vmem_disk_full_request(struct request_queue *q)

{

	struct request *req;

	int sectors_xferred;

	struct vmem_disk_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 = vmem_disk_xfer_request(dev, req);

		end_request (req, 1);

	}

}



/*

 * The direct make request version.

 */

static int vmem_disk_make_request(struct request_queue *q, struct bio *bio)

{

	struct vmem_disk_dev *dev = q->queuedata;

	int status;



	status = vmem_disk_xfer_bio(dev, bio);

	bio_endio(bio, status);

	return 0;

}





/*

 * Open and close.

 */



static int vmem_disk_open(struct block_device *bdev, fmode_t mode)

{

	struct vmem_disk_dev *dev = bdev->bd_disk->private_data;



	del_timer_sync(&dev->timer);

	spin_lock(&dev->lock);

	dev->users++;

	spin_unlock(&dev->lock);



	return 0;

}



static int vmem_disk_release(struct gendisk *disk, fmode_t mode)

{

	struct vmem_disk_dev *dev = 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 vmem_disk_media_changed(struct gendisk *gd)

{

	struct vmem_disk_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 vmem_disk_revalidate(struct gendisk *gd)

{

	struct vmem_disk_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 vmem_disk_invalidate(unsigned long ldev)

{

	struct vmem_disk_dev *dev = (struct vmem_disk_dev *) ldev;



	spin_lock(&dev->lock);

	if (dev->users || !dev->data)

		printk (KERN_WARNING "vmem_disk: timer sanity check failed\n");

	else

		dev->media_change = 1;

	spin_unlock(&dev->lock);

}



/*

 * The ioctl() implementation

 */



static int vmem_disk_getgeo(struct block_device *bdev, struct hd_geometry *geo)

{

	long size;

	struct vmem_disk_dev *dev = bdev->bd_disk->private_data;



	size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);

	geo->cylinders = (size & ~0x3f) >> 6;

	geo->heads = 4;

	geo->sectors = 16;

	geo->start = 4;



	return 0;

}



/*

 * The device operations structure.

 */

static struct block_device_operations vmem_disk_ops = {

	.owner           = THIS_MODULE,

	.open            = vmem_disk_open,

	.release         = vmem_disk_release,

	.media_changed   = vmem_disk_media_changed,

	.revalidate_disk = vmem_disk_revalidate,

	.getgeo          = vmem_disk_getgeo,

};





/*

 * Set up our internal device.

 */

static void setup_device(struct vmem_disk_dev *dev, int which)

{

	/*

	 * Get some memory.

	 */

	memset (dev, 0, sizeof (struct vmem_disk_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 = vmem_disk_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, vmem_disk_make_request);

		break;



	case RM_FULL:

		dev->queue = blk_init_queue(vmem_disk_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(vmem_disk_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(vmem_disk_MINORS);

	if (! dev->gd) {

		printk (KERN_NOTICE "alloc_disk failure\n");

		goto out_vfree;

	}

	dev->gd->major = vmem_disk_major;

	dev->gd->first_minor = which*vmem_disk_MINORS;

	dev->gd->fops = &vmem_disk_ops;

	dev->gd->queue = dev->queue;

	dev->gd->private_data = dev;

	snprintf (dev->gd->disk_name, 32, "vmem_disk%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 vmem_disk_init(void)

{

	int i;

	/*

	 * Get registered.

	 */

	vmem_disk_major = register_blkdev(vmem_disk_major, "vmem_disk");

	if (vmem_disk_major <= 0) {

		printk(KERN_WARNING "vmem_disk: unable to get major number\n");

		return -EBUSY;

	}

	/*

	 * Allocate the device array, and initialize each one.

	 */

	Devices = kmalloc(ndevices*sizeof (struct vmem_disk_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(vmem_disk_major, "sbd");

	return -ENOMEM;

}



static void vmem_disk_exit(void)

{

	int i;



	for (i = 0; i < ndevices; i++) {

		struct vmem_disk_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)

				kobject_put (&dev->queue->kobj);

			/* blk_put_queue() is no longer an exported symbol */

			else

				blk_cleanup_queue(dev->queue);

		}

		if (dev->data)

			vfree(dev->data);

	}

	unregister_blkdev(vmem_disk_major, "vmem_disk");

	kfree(Devices);

}



module_init(vmem_disk_init);

module_exit(vmem_disk_exit);


< Makefile >

KVERS = $(shell uname -r)

# Kernel modules
obj-m += vmem_disk.o

# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0

build: kernel_modules

kernel_modules:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules

clean:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值