zoukankan      html  css  js  c++  java
  • 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
    ");
        
        return 0;
    }
    
    static int led_dev_release(struct inode *inode, struct file *file)
    {
        printk(KERN_INFO "led_dev close
    ");
        
        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
    ");
        }
        printk(KERN_INFO "led device read success
    ");
    }
    
    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
    ");
            return -EINVAL;
        }
        
        printk(KERN_INFO "led device write success
    ");
        
        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
    ");
        
        
        //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
        err = register_chrdev_region(MKDEV(MYDEV,0), LED_COUNT, "MY_LED_DEV");
        
        if(err)
        {
            printk(KERN_ERR " register_chrdev_region failed
    ");
            
            return -EINVAL;
        }
        
        printk(KERN_INFO "leddev_dev regist success
    ");
        
        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
    ");
        
        //注销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
    ");
    
        
    }
    
    
    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
    ");
        
        
        //在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
        err = alloc_chrdev_region(&mydev, 0, LED_COUNT, "MY_LED_DEV");
        
        if(err)
        {
            printk(KERN_ERR " register_chrdev_region failed
    ");
            
            return -EINVAL;
        }
        
        printk(KERN_INFO "leddev_dev regist success
    ");
        
        printk(KERN_INFO "major device NO. is %u,
    ", MAJOR(mydev));
        printk(KERN_INFO "minor device NO. is %u,
    ", 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
    ");
        
        //注销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
    ");
    
        
    }
    
    
    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函数;

  • 相关阅读:
    BZOJ3156 防御准备
    BZOJ1257 余数之和
    BZOJ1879 Bill的挑战
    BZOJ1811 mea
    phpcms列表页内容如何替换?
    如何解决wamp中数据库读取数据是???的情况?
    如何用phpcms将静态网页生成动态网页?
    简单介绍phpcms以及phpcms如何安装?
    cms基本概念(dedecms,phpcms)
    用php+mysql+ajax实现淘宝客服或阿里旺旺聊天功能 之 后台页面
  • 原文地址:https://www.cnblogs.com/biaohc/p/6597122.html
Copyright © 2011-2022 走看看