LDD 3「Linuxデバイスドライバ」の中で最も簡単な文字デバイスドライバの実現とテスト

9257 ワード

#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>

#include <linux/cdev.h>
#include <asm/uaccess.h>
//#include "scull.h"

#define SCULL_MAJOR 0   /* dynamic major by default */
#define SCULL_NR_DEVS 4    /* scull0 through scull3 */
#define SCULL_P_NR_DEVS 4  /* scullpipe0 through scullpipe3 */
#define SCULL_QUANTUM 4000
#define SCULL_QSET    1000

struct scull_qset {
	void **data;
	struct scull_qset *next;
};

struct scull_dev {
	struct scull_qset *data;  /* Pointer to first quantum set */
	int quantum;              /* the current quantum size */
	int qset;                 /* the current array size */
	unsigned long size;       /* amount of data stored here */
	unsigned int access_key;  /* used by sculluid and scullpriv */
	struct semaphore sem;     /* mutual exclusion semaphore     */
	struct cdev cdev;	  /* Char device structure		*/
};

int	scull_major = SCULL_MAJOR;
int	scull_minor = 0;
int	scull_nr_devs = SCULL_NR_DEVS;
int	scull_quantum = SCULL_QUANTUM;
int	scull_qset = SCULL_QSET;

module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs,int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset,int,S_IRUGO);

MODULE_AUTHOR("BG2BKK");
MODULE_LICENSE("Dual BSD/GPL");


struct scull_dev *scull_devices;

int	scull_trim(struct scull_dev *dev)
{
	struct	scull_qset *next,*dptr;
	int	qset = dev->qset;
	int 	i;
	for(dptr = dev->data; dptr;dptr = next)
	{
		if(dptr->data){
			for (i=0;i<qset;i++)
				kfree(dptr->data[i]);
			kfree(dptr->data);
			dptr->data = NULL;
		}
		next = dptr->next;
		kfree(dptr);
	}
	dev->size	= 0;
	dev->quantum	= scull_quantum;
	dev->qset	= scull_qset;
	dev->data	= NULL;
	return 0;
}

int	scull_open(struct inode *inode, struct file *filp)
{
	struct scull_dev *dev;
	dev = container_of(inode->i_cdev,struct scull_dev, cdev);
	filp->private_data	= dev;

	if((filp->f_flags & O_ACCMODE) == O_WRONLY){
		scull_trim(dev);
	}

	return 0;
}



int	scull_release(struct inode *inode,struct file *filp)
{
	printk(KERN_ALERT "scullrelease
"); return 0; } struct scull_qset *scull_follow(struct scull_dev *dev,int n) { struct scull_qset *qs = dev->data; if(!qs) { qs = dev->data = kmalloc(sizeof(struct scull_qset),GFP_KERNEL); if(qs == NULL) return NULL; memset(qs,0,sizeof(struct scull_qset)); } while(n--) { if( !qs->next ){ qs->next = kmalloc(sizeof(struct scull_qset),GFP_KERNEL); if(qs->next == NULL) return NULL; memset(qs->next, 0 ,sizeof(struct scull_qset)); } qs = qs->next; continue; } return qs; } ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum*qset; int item,s_pos,q_pos,rest; ssize_t retval = 0; if(down_interruptible(&dev->sem)) return -ERESTARTSYS; // printk("f_pos= %d
",*f_pos); // printk("count= %d
",count); if( *f_pos >= dev->size) goto out; if( *f_pos + count > dev->size) count = dev->size - *f_pos; item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev,item); if(dptr == NULL || !dptr->data || !dptr->data[s_pos]) goto out; if(count > quantum - q_pos) count = quantum - q_pos; if(copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){ retval = -EFAULT; goto out; } // printk("read scull: %d
",count); *f_pos += count; retval = count; out: up(&dev->sem); return retval; } ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum,qset = dev->qset; int itemsize = quantum * qset; int item,s_pos,q_pos,rest; ssize_t retval = -ENOMEM; if(down_interruptible(&dev->sem)) return -ERESTARTSYS; item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if(dptr == NULL) goto out; if(!dptr->data){ dptr->data = kmalloc(qset * sizeof(char *),GFP_KERNEL); if(!dptr->data) goto out; memset(dptr->data, 0, qset*sizeof(char *)); } if(!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if(!dptr->data[s_pos]) goto out; } if(count > quantum - q_pos) count = quantum - q_pos; if(copy_from_user(dptr->data[s_pos] + q_pos, buf,count)){ retval = -EFAULT; goto out; } *f_pos += count; retval = count; if(dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; } int scull_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg) { return 0; } loff_t scull_llseek(struct file *filp,loff_t off,int whence) { struct scull_dev *dev = filp->private_data; loff_t newpos; switch(whence) { case 0: newpos = off; break; case 1: newpos = filp->f_pos + off; break; case 2: newpos = dev->size + off; break; default: return -EINVAL; } if(newpos < 0) return -EINVAL; filp->f_pos = newpos; return newpos; } struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release= scull_release, }; void scull_cleanup_module(void) { int i; dev_t devno = MKDEV(scull_major,scull_minor); if(scull_devices){ for(i=0; i<scull_nr_devs;i++) { scull_trim(scull_devices + i); cdev_del(&scull_devices[i].cdev); } kfree(scull_devices); } #ifdef SCULL_DEBUG // scull_remove_proc(); #endif unregister_chrdev_region(devno,scull_nr_devs); // scull_p_cleanup(); // scull_access_cleanup(); } static void scull_setup_cdev(struct scull_dev *dev, int index) { int err,devno=MKDEV(scull_major,scull_minor+index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add(&dev->cdev, devno,1); if(err) printk(KERN_NOTICE "Error %d adding scull%d",err,index); } int scull_init_module(void) { int result,i; dev_t dev=0; if(scull_major) { dev = MKDEV(scull_major,scull_minor); result = register_chrdev_region(dev,scull_nr_devs,"scull"); } else{ result = alloc_chrdev_region(&dev,scull_minor,scull_nr_devs,"scull"); scull_major = MAJOR(dev); } if(result < 0){ printk(KERN_WARNING "scull: can't get major %d
",scull_major); return result; } printk(KERN_ALERT "hello scull major %d
minor %d
",scull_major,scull_minor); scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev),GFP_KERNEL); if(!scull_devices){ result = -ENOMEM; goto fail; } memset(scull_devices, 0,scull_nr_devs*sizeof(struct scull_dev)); for(i=0; i< scull_nr_devs; i++){ scull_devices[i].quantum = scull_quantum; scull_devices[i].qset = scull_qset; init_MUTEX(&scull_devices[i].sem); scull_setup_cdev(&scull_devices[i],i); } dev=MKDEV(scull_major,scull_minor+scull_nr_devs); // dev += scull_p_init(dev); // dev += scull_access_init(dev); #ifdef SCULL_DEBUG scull_create_proc(); #endif return 0; fail: scull_cleanup_module(); return result; } module_init(scull_init_module); module_exit(scull_cleanup_module)

テストコードは次のとおりです.
/*************************************************************************
 *fileName:    test.c
 *description: test the myscull.c
 *author:      Hzc
 *create time: 2007-04-20
 *modify info: -
*************************************************************************/
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>

/* device path */
char path[] = "/dev/sculldev";
int buf[40];
int rbuf[40];
int i;
int t=1;
int main()
{
int f = open(path, O_WRONLY);
if (f == -1)
{
printf("device open error!
"); return 1; } printf("Input a string to write device
"); //scanf("%s", buf); for(i=0;i<40;i++) { buf[i]=i; write(f, buf+i,1 ); /* device wirte */ } close(f); f= open(path,O_RDONLY); printf("Read the string from device...
"); if (f == -1) { printf("device open error!
"); return 1; } for(i=0;i<40;i++) { read(f, rbuf+i, 1); /* device wirte */ } for(i=0;i<40;i++) { printf("%d\t",rbuf[i]); if((i+1)%10 == 0) printf("
"); } close(f); }

ドライバ・ロード・スクリプトは次のとおりです.
#!/bin/sh

insmod scull.ko
mknod /dev/sculldev c 252 0
ドライバアンインストールスクリプトは次のとおりです.
#!/bin/sh

rmmod scull.ko
rm /dev/sculldev