linux驱动开发(二)
1:上一章我们使用了register_chrdev这个函数来向内核注册字符设备
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
这个函数需要三个参数:主设备号、name、file_operations结构体
使用register_chrdev这个函数无法设置次设备号;
下面我们介绍一个新的函数:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
这个函数也需要三个参数:设备号、同类设备的个数、name比如说我们led设备s5pv210中有4颗led设备,
我们led设备的主设备号可以设置为250,个数4个,name:led_dev
注意这里的参数from ,因为主设备号与次设备号在linux内核中共同组成一个4字节的int类型的数,第16位或者其它为次设备号,高16位或者其它为主设备号;
linux内核为我们提供了三个宏来确定from、主设备号、次设备号
MKDEV、MAJOR、NIMOR,这么是这三个宏在linux内核中定义的用法:
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
比如说我们的主设备号为250 第一个次设备号为0,那么from应该赋值的参数为MKDEV(250, 0)
知道dev的话 主设备号为 MAJOR(dev)、次设备号为:MINOR(dev)
这个函数中只设置了主设备号、次设备号、与name还没有绑定file_operations结构体,所以我们还需要一个函数来把主设备号,与file_operations绑定起来;
这里就要用到:cdev、cdev_alloc、cdv_init、cdev_add、cdev_del
cdev类型是下面这个结构体中,两个变量很重要一个是dev_t dev设备号,file_operations
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};
void cdev_init(struct cdev *, const struct file_operations *);
cdev_init函数,这个函数是吧cdev与我们写的file_operations绑定起来完成注册;
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	  memset(cdev, 0, sizeof *cdev);
	  INIT_LIST_HEAD(&cdev->list);
	  kobject_init(&cdev->kobj, &ktype_cdev_default);
	  cdev->ops = fops;
}
cdev_add函数,这个函数才是真正的注册函数
这个函数有三个参数:cdev的结构体指针,dev设备号,count几个设备;
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	p->dev = dev;
	p->count = count;
	return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
以上几个是驱动设备注册的几个函数;
下面要到设备的注销,也是分两步的
涉及到两个函数:
cdev_del这个函数只需要一个参数就是cdev指针
void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count);
	kobject_put(&p->kobj);
}
unregister_chrdev_region函数
这个函数需要两个参数一个是设备号,一个是注册的同类设备的个数
void unregister_chrdev_region(dev_t from, unsigned count)
{
	dev_t to = from + count;
	dev_t n, next;
	for (n = from; n < to; n = next) {
		next = MKDEV(MAJOR(n)+1, 0);
		if (next > to)
			next = to;
		kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
	}
上面为进行注册的必须要提前知道主设备号,
下面我们来进行一下代码实战:
#include <linux/module.h>        // module_init  module_exit
#include <linux/init.h>            // __init   __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <plat/map-base.h>
#include <plat/map-s5p.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <asm/io.h>
#include <linux/cdev.h>
//#define MYMAJOR            200
//#define MYNAME            "LED_DEVICE"
#define MYDEV             250
#define LED_COUNT        1
#define GPJ0_PA_base        0xE0200240        
#define GPJ0CON_PA_OFFSET    0x0
struct cdev my_led_cdev;
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
static char kbuf[100];
static int mymojor;
static int led_dev_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_dev open\n");
    
    return 0;
}
static int led_dev_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_dev close\n");
    
    return 0;
}
ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
    int ret = -1;
        
    ret = copy_to_user(buf, kbuf, sizeof(kbuf));
    if(ret) {
        printk(KERN_ERR "kernel led read error\n");
    }
    printk(KERN_INFO "led device read success\n");
}
static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
    int ret = -1;
    
    //首先把kbuf清零
    memset(kbuf, 0, sizeof(kbuf));
    
    ret = copy_from_user(kbuf, user_buf, count);
    if(ret) {
        printk(KERN_ERR "kernel led write error\n");
        return -EINVAL;
    }
    
    printk(KERN_INFO "led device write success\n");
    
    if (kbuf[0] == '1') {
        *pGPJ0CON = 0x11111111;
        *(pGPJ0CON + 1) = ((0<<3) | (0<<4) | (0<<5));
    }
    
    if (kbuf[0] == '0') {
        *(pGPJ0CON + 1) = ((1<<3) | (1<<4) | (1<<5));
    }
    
    return 0;
}
static const struct file_operations led_dev_fops = {
    .open = led_dev_open,
    .write = led_dev_write,
    .read = led_dev_read,    
    .release = led_dev_release,
    .owner = THIS_MODULE,
};
// 模块安装函数
static int __init leddev_init(void)
{    
    int err = 0;
    printk(KERN_INFO "led_device init\n");
    
    
    //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
    err = register_chrdev_region(MKDEV(MYDEV,0), LED_COUNT, "MY_LED_DEV");
    
    if(err)
    {
        printk(KERN_ERR " register_chrdev_region failed\n");
        
        return -EINVAL;
    }
    
    printk(KERN_INFO "leddev_dev regist success\n");
    
    cdev_init(&my_led_cdev, &led_dev_fops);
    
    cdev_add(&my_led_cdev, MKDEV(MYDEV,0), LED_COUNT);
    
    
    if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) {
        return -EINVAL;
    }
    
    pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4);
        
    return 0;
}
// 模块下载函数
static void __exit leddev_exit(void)
{
    printk(KERN_INFO "leddev_dev  exit\n");
    
    //注销led设备驱动
    cdev_del(&my_led_cdev);
    
    unregister_chrdev_region(MKDEV(MYDEV,0), LED_COUNT);
    
    iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET);
    
    release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8);
    
    printk(KERN_INFO "leddev_dev  unregist success\n");
    
}
module_init(leddev_init);
module_exit(leddev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("bhc");                // 描述模块的作者
MODULE_DESCRIPTION("led test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息
上面使用到的函数:
这里关于cdev_init在内核中的具体实现可以参考下面这篇博客
linux内核cdev_init系列函数(字符设备的注册)
module_init:
register_chrdev_region
cdev_init
cdev_add
module_exit
cdev_del
unregister_chrdev_region
中间层:
file_operations
mknod /dev/led1 c 250 0
mknod /dev/led1 c 250 1
mknod /dev/led1 c 250 2
mknod /dev/led1 c 250 3
这里注意安装驱动以后,设备文件没有自动创建需要我们手动来创建;
下面我们介绍一下自动分配设备号的注册函数:
alloc_chrdev_regio
这个函数需要4个参数:把dev_t dev的地址传进去,次设备号的最小值一般为0,几个设备,名字 四个参数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
{
	  struct char_device_struct *cd;
	  cd = __register_chrdev_region(0, baseminor, count, name);
	  if (IS_ERR(cd))
		  return PTR_ERR(cd);
	  *dev = MKDEV(cd->major, cd->baseminor);
	  return 0;
}
测试代码如下:
// 模块安装函数
static int __init leddev_init(void)
{    
    int err = 0;
    printk(KERN_INFO "led_device init\n");
    
    
    //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
    err = alloc_chrdev_region(&mydev, 0, LED_COUNT, "MY_LED_DEV");
    
    if(err)
    {
        printk(KERN_ERR " register_chrdev_region failed\n");
        
        return -EINVAL;
    }
    
    printk(KERN_INFO "leddev_dev regist success\n");
    
    printk(KERN_INFO "major device NO. is %u,\n", MAJOR(mydev));
    printk(KERN_INFO "minor device NO. is %u,\n", MINOR(mydev));
    
    cdev_init(&my_led_cdev, &led_dev_fops);
    
    cdev_add(&my_led_cdev, mydev, LED_COUNT);
    
    
    if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) {
        return -EINVAL;
    }
    
    pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4);
        
    return 0;
}
// 模块下载函数
static void __exit leddev_exit(void)
{
    printk(KERN_INFO "leddev_dev  exit\n");
    
    //注销led设备驱动
    cdev_del(&my_led_cdev);
    
    unregister_chrdev_region(mydev, LED_COUNT);
    
    iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET);
    
    release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8);
    
    printk(KERN_INFO "leddev_dev  unregist success\n");
    
}
module_init(leddev_init);
module_exit(leddev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("bhc");                // 描述模块的作者
MODULE_DESCRIPTION("led test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息
cdev_alloc函数:
struct cdev *cdev_alloc(void)
{
	  struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
	  if (p) {
		  INIT_LIST_HEAD(&p->list);
		  kobject_init(&p->kobj, &ktype_cdev_dynamic);
	  }
	  return p;
}
cdev_alloc函数用来自动分配内存空间,并初始化链表;
module_exit的时候用 cdev_del来释放就可以;
这里要注意到cdev_alloc分配一段内存,
void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count);
	kobject_put(&p->kobj);
}
static void cdev_unmap(dev_t dev, unsigned count)
{
	kobj_unmap(cdev_map, dev, count);
}
void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range)
{
	unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
	unsigned index = MAJOR(dev);
	unsigned i;
	struct probe *found = NULL;
	if (n > 255)
		n = 255;
	mutex_lock(domain->lock);
	for (i = 0; i < n; i++, index++) {
		struct probe **s;
		for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) {
			struct probe *p = *s;
			if (p->dev == dev && p->range == range) {
				*s = p->next;
				if (!found)
					found = p;
				break;
			}
		}
	}
	mutex_unlock(domain->lock);
	kfree(found);
中调用的
cdev_del
cdev_unmap
kobj_unmap
kfree
cdev_del中调用cdev_unmap在调用kobj_unmap在调用kfree来释放我们用cdev_alloc申请的这段内存,所以卸载模块的时候不用自己来释放这段内存了;
还有一般用 cdev_alloc的时候直接
pcdev->owner = THIS_MODULE;
pcdev->opt = &led_dev_fops; 有时候用这两句来代替cdev_init函数;
 
                    
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号