精华内容
下载资源
问答
  • 1. 设备号 主设备号:用来标识与设备文件相关的驱动程序, ——反应设备类型 次设备号:为内核所用,被驱动程序用来辨别操作那个设备文件 ——区分同类型的具体某个设备

    1. 设备号

        主设备号:用来标识与设备文件相关的驱动程序,        ——反应设备类型
        次设备号:为内核所用,被驱动程序用来辨别操作那个设备文件    ——区分同类型的具体某个设备
     
    1.1 设备号的内部表达
        在内核中,保存设备号(包括主设备号和此设备好)使用类型
        dev_t   (<linux/types.h>) 
        这是一个unsigned int  是一个32位的无符号整型。。
        主设备号——高12位
        此设备号——低12位
        我们可以使用宏来取一个设备号(dev)的主设备号和此设备号    
         定义在 <linux/kdev_t.h>
          MAJOR(dev_t dev)        取主设备号
          MINOR(dev_t dev)         取次设备号
        也可以将主次设备号合成一个完整的dev_t类型的设备号
        MKDEV(int major, int minor)       将主次设备号转换成dev_t
     
     1.2 分配主次设备号
        linux可以采用静态申请和动态申请两种方法来分配主次设备号
        
        静态申请
          1. 根据Documentation/devices.txt, 确定一个没有使用的主设备号
          2. 使用register_chrdev_region(dev_t first, unsigned int count, char *name)
           定义在<linux/fs.h>    
            count 为所请求的连续设备编号个数, 如果count过大,可以会各下一个主设备号重叠。
              name 为设备名, 注册后 出现在/proc/devices和sysfs中
     
        动态分配
        作为一个新的驱动程序,应该使用动态分配机制获取主设备号
        alloc_chrdev_region(dev_t *dev, unsigned int first, unsigned int count, char *name)
     
        不管用何种方法分配, 不用时都要释放掉
         void unregister_chrdev_region(dev_t first, unsigned int count);
     
        静态申请与动态申请的优缺点:
        静态申请——简单(优);  一旦驱动程序被广泛命使用, 随机选定的主设备号可以造成冲突,使驱动程序无法注册。(劣)
        动态申请——简单,易于驱动推广(优);无法在驱动安装前创建设备文件, 因为不能保证分配的主设备号始终一致。(劣)

     

    2 创建设备文件

        设备文件的创建有    
         1. 使用mknod命令手工创建
         2. 自动创建
        两种方法。
        

        2.1 mknod手工创建

         mknod  用法:
        mknod  filename type  major  minor
     
            filename :  设备文件名
            type        :  设备文件类型
            major      :   主设备号
            minor      :   次设备号
        

    2.2 自动创建

        如果我们在驱动里面动态创建的话需要这样做
        struct class *cdev_class; 
        cdev_class = class_create(owner,name)
        device_create(_cls,_parent,_devt,_device,_fmt)


     

    2.3 模块退出时要销毁设备文件

        
        device_destroy(_cls,_device)
        class_destroy(struct class * cls)
     

     

    3. 一些重要的结构体

        
            大部分的基础性的驱动操作包括 3 个重要的内核数据结构, 称为 file_operations, file, 和 inode.
    •   文件结构 struct file
                 定义于<linux/fs.h>,   是一个内核结构, 不会出现在用户空间
       代表一个打开的文件。系统中每个找开的文件在内核空间一个关联的
       struct file, 它由内核在打开文件时创建, 在文件关闭后释放
    重要成员
    loff_t  f_ops     /* 文件读写位置 */
    struct file_operations  *f_op    /*  文件关联的操作 */
    mode_t  f_mode        /* 模式确定文件可读或者可写 */
    unsigned int f_flags /* 文件标志,一般用来判断是否请求非阻塞操  作, 标志定义<linux/fcntl.h> */  
    void *private_data; 

         open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它.
    • 文件操作 struct file_operation
    file_operation 结构是一个字符驱动如何建立这个连接. 这个结构, 定
    义在 <linux/fs.h>, 是一个函数指针的集合. 每个打开文件(内部用一个 file 结构来代
    表, 稍后我们会查看)与它自身的函数集合相关连( 通过包含一个称为 f_op 的成员, 它指
    向一个 file_operations 结构). 这些操作大部分负责实现系统调用, 因此, 命名为 open,
    read, 等等. 我们可以认为文件是一个"对象"并且其上的函数操作称为它的"方法", 使用
    面向对象编程的术语来表示一个对象声明的用来操作对象的动作.
     
    下面是一个file_operationd的声明:
            struct file_operations my_fops = {
            .owner =  THIS_MODULE, 
            .llseek =  my_llseek, 
            .read =  my_read, 
            .write =  my_write, 
            .ioctl =  my_ioctl, 
             .open =  my_open, 
             .release =  my_release,  
         };  


    该声明使用标准的 C 标记式结构初始化语法. 这个语法是首选的, 因为它使驱动在结构定义的改变之间更加可移植  。
    下面列出file_operationd部分成员的含义:(其他成员自行百度) 
    struct module *owner 
         第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.
    loff_t (*llseek) (struct file *, loff_t, int); 
    llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32 位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).  
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 
         用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -
        EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 
        发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
                int (*open) (struct inode *, struct file *); 
          尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知
    int (*release) (struct inode *, struct file *); 
         在文件结构被释放时引用这个操作. 相当于close
    • struct inode
            由内核在内部用来表示文件。因些,它和代表打开文件的file结构是不同的。一个文件可以对应多个file结构, 但只有一个inode结构
            
    重要成员
    dev_t   i_rdev:  / * 对于代表设备文件的节点, 这个成员包含实际的设备编号 */    
    struct cdev *i_cdev;  /* struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.*/
     
     

    4. 字符设备的注册

            Linux2.6内核中,字符设备使用struct  cdev来描述字符设备驱动的注册。
            字符设备驱动的注册主要有三个步骤
            (1) 分配cdev
            (2)初始化cdev
            (3)添加cdev
            
    分配
        struct cdev *my_cdev = cdev_alloc();
    初始化
        void cdev_init(struct  cdev *cdev,  const struct file_operations *fops);
        cdev:  待初始化的cdev结构
        fops:  设备对应的操作函数集
    注册, 告诉内核
        int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
        dev: 添加到内核的字符设备结构
        num: 设备响应的第一个设备号
        count: 关联到设备的设备号数目,通常为1
    去除字符设备
        void cdev_del(struct cdev *dev);
     
     
    使用 cdev_add 是有几个重要事情要记住
        1.第一个是这个调用可能失败. 如果它返回一个负的错误码, 你的设备没有增加到系统中.
     
        2. cdev_add 一返回成功, 你的设备就是"活的"并且内核可以调用它的操作.
            所以除非你的驱动完全准备好处理设备上的操作, 你不应当调用 cdev_add.
     
     
     

    5 注册字符设备的一个例子

     
    1. 还是线上源代码:
    //memdev.h
    #ifndef _MEMDEV_H_
    #define _MEMDEV_H_
     
    #ifndef MEMDEV_MAJOR
    #define MEMDEV_MAJOR 200
    #endif
     
    #ifndef MEMDEV_NR_DEVS
    #define MEMDEV_NR_DEVS 2
    #endif
     
    #ifndef MEMDEV_SIZE
    #define MEMDEV_SIZE 4096
    #endif
     
    struct mem_dev{
        char* data;
        unsigned long size;
     
    };
     
    #endif

    //memdev.c  
    # include < linux / module.h >
    # include < linux / types.h >
    # include < linux / fs.h >
    # include < linux / errno.h >
    # include < linux / mm.h >
    # include < linux / sched.h >
    # include < linux / init.h >
    # include < linux / cdev.h >
    # include < asm / io.h >
    # include < asm / system.h >
    # include < asm / uaccess.h >
    # include < linux / wait.h >
    # include < linux / completion.h >
     
    # include "memdev.h"
     
    MODULE_LICENSE( "Dual BSD/GPL" );
     
    static int   mem_major = MEMDEV_MAJOR;
     
    struct mem_dev * mem_devp; /*设备结构体指针*/
     
    struct cdev cdev;
     
    /*文件打开函数*/
    int mem_open( struct inode * inode, struct file * filp)
    {
    printk( "open own file\n" );
         return 0 ;
    }
     
    /*文件操作结构体*/
    static const struct file_operations mem_fops =
    {
      .owner = THIS_MODULE,
      .open = mem_open,
    };
     
    /*设备驱动模块加载函数*/
    static int memdev_init( void )
    {
       int result;
       int i;
     
      dev_t devno = MKDEV(mem_major, 0 );
     
       /* 静态申请设备号*/
        result = register_chrdev_region(devno, 2 , "memdev" );
       if (result < 0 )
         return result;
     
       /*初始化cdev结构*/
      cdev_init( & cdev, & mem_fops);
     
       /* 注册字符设备 */
      cdev_add( & cdev, MKDEV(mem_major, 0 ), MEMDEV_NR_DEVS);
     
       return result;
    }
     
    /*模块卸载函数*/
    static void memdev_exit( void )
    {
      cdev_del( & cdev);    /*注销设备*/
      unregister_chrdev_region(MKDEV(mem_major, 0 ), 2 ); /*释放设备号*/
    }
     
    module_init(memdev_init);
    module_exit(memdev_exit);
     
    #Makefile
    ifneq ($(KERNELRELEASE),)
        obj-m := memdev.o
    else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD = $(shell pwd)
    default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    clean:
        rm memdev.mod*  module* memdev.o memdev.ko Module.*
    endif
     
     
    2. 测试
        首先先make下,生成memdev.ko
        然后insmod memdev.ko生成memdev模块
        创建设备节点:sudo mknod /dev/memdev_t c 200 0
        接下开使用设备文件
        下面是一个测试程序
    
      // memusr.c
    #include <stdio.h>
    #include <string.h>
     
    int main()
    {
        FILE *fp0;
        /*打开设备文件*/
        fp0 = fopen("/dev/memdev_t","r+");
        if (fp0 == NULL) {
            printf("Open Memdev0 Error!\n");
            return -1;
        }
    }


     
    编译运行,然后使用dmesg可以看到日志文件里输出
    [38439.741816] Hello World!
    [38657.654345] Goodbye
    [40393.039520] open own file


     
    记得要使用sudo 运行memusr   否则会显示设备打开失败。

    展开全文
  • Linux字符设备驱动注册三种方法以及内核分析

    千次阅读 多人点赞 2018-10-07 15:59:38
    其中最多的是字符设备,其中字符设备的注册方法主要有三种:杂项设备注册、早期字符设备注册、标准字符设备注册。以及详细介绍各类方法注册。 开发环境: PC:VMworkstation 12 运行Ubuntu12 32位虚拟机 开发板:...

           Linux驱动是用户访问底层硬件的桥梁,驱动有可以简单分成三类:字符设备、块设备、网络设备。其中最多的是字符设备,其中字符设备的注册方法主要有三种:杂项设备注册、早期字符设备注册、标准字符设备注册。以及详细介绍各类方法注册。

    开发环境:

    PC:VMworkstation 12  运行Ubuntu12 32位虚拟机

    开发板:友善之臂Tiny4412 (EXYNOS4412 Cortex-A9)

    Linux内核版本:Linux 3.5

    PC内核阅读器:SourceInsight 

     

    一、杂项设备(misc device):

    在内核路径下include\linux\miscdevice.h文件有以下内容:

    struct miscdevice  {
        int minor;
        const char *name;
        const struct file_operations *fops;
        struct list_head list;
        struct device *parent;
        struct device *this_device;
        const char *nodename;
        umode_t mode;
    };

    extern int misc_register(struct miscdevice * misc);
    extern int misc_deregister(struct miscdevice *misc);

    通过上述函数的声明可知杂项设备的注册函数为:misc_register,注销函数为misc_deregister,通过sourceinsight搜索参考代码:

    可知要使用一个杂项设备必须要有一个struct miscdevice 结构体,根据miscdevice.h结构体的定义和相关内核代码可知:

    需要至少实现结构体内部三个参数:

       int minor;//次设备号(主设备号默认为10,其中杂项设备是通过早期字符设备静态添加到内核中,在早期设备说明)
       const char *name;//设备名称,dev下创建的设备节点名称
       const struct file_operations *fops;//文件操作指针,为用户层与驱动层访问的接口

    以上很明显的阐述了驱动注册之前需要的准备工作:对struct miscdevice结构体进行定义并且赋值,然而内部参数并非这么简单:struct file_operations 还嵌套一层结构体,为文件操作集合结构体。根据内核代码,我们可以找到相关所有文件操作集合的函数接口:

    位于内核根目录下:include\linux下的Fs.h文件下定义了文件操作集合函数接口。通过在驱动层实现这些接口以便对驱动层的访问。通过上述分析,杂项设备注册方式很简单的暂时概括为:1、准备工作:结构体的定义与赋值、接口函数的实现。2、将结构体传入杂项设备注册函数,实现注册。3、用户层函数的编写。

    讲到这里可能还不理解用户层如何通过文件操作集合定义的函数接口去实现访问内核驱动,以一个简单的代码为例:

    static struct file_operations fops_led=
    {
        .open=open_led,
        .write=write_led,
        .read=read_led,
        .release=release_led,
    };

    static struct miscdevice misc_led=
    {
        .minor=MISC_DYNAMIC_MINOR, /*自动分配次设备号*/
        .name="tiny4412_led",      /*设备节点的名称*/
        .fops=&fops_led           /*文件操作集合*/
    };

    在驱动层定义的文件操作集合为:open、write、read、release。

    open:当用户层通过open(dev\设备结点)时对应驱动层实现的open函数会被调用,当用户层通过open会产生一个文件描述符,用户层再通过文件描述符去read、write操作时,会对应调用驱动层的read、write,通过这些函数的来访问驱动。相关代码:

    驱动层:

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/fs.h>
    static int open_led (struct inode *my_inode, struct file *my_file)
    {
    	printk("open_led调用成功!\n");
    	return 0;	
    }
    static ssize_t read_led(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("read_led调用成功!\n");
    	return 0;
    }
    
    static ssize_t write_led(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("write_led调用成功!\n");
    	return 0;
    }
    
    static int release_led(struct inode *my_inode, struct file *my_file)
    {
        printk("release_led调用成功!\n");
    	return 0;
    }
    
    static struct file_operations fops_led=
    {
    	.open=open_led,
    	.write=write_led,
    	.read=read_led,
    	.release=release_led,
    };
    
    static struct miscdevice misc_led=
    {
    	.minor=MISC_DYNAMIC_MINOR, /*自动分配次设备号*/
    	.name="tiny4412_led",      /*设备节点的名称*/
    	.fops=&fops_led           /*文件操作集合*/
    };
    
    static int __init tiny4412_led_init(void)
    {
    	int err;
    	err=misc_register(&misc_led);  //杂项设备注册函数
    	if(err<0)
    	{
    		printk("提示: 驱动安装失败!\n");
    		return err;
    	}
        printk("提示: 驱动安装成功!\n");
        return err;
    }
    
    static void __exit tiny4412_led_exit(void)
    {
    	int err;
    	err=misc_deregister(&misc_led);  //杂项设备注销函数
    	if(err<0)
    	{
    		printk("提示: 驱动卸载失败!\n");
    	}
        printk("提示: 驱动卸载成功!\n");
    }
    module_init(tiny4412_led_init);  /*指定驱动的入口函数*/
    module_exit(tiny4412_led_exit); /*指定驱动的出口函数*/
    MODULE_LICENSE("GPL");       /*指定驱动许可证*/
    

    用户层:

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    int main(int argc,char **argv)
    {
    	int fd;
    	if(argc!=2)
    	{
    		printf("运行方式: ./app <设备文件>\n");
    		return 0;
    	}
    	
    	fd=open(argv[1],O_RDWR);
    	if(fd<0)
    	{
    		printf("%s 设备文件打开失败!\n",argv[1]);
    		return 0;
    	}
    	
    	int data;
    	read(fd,&data,4);
    	write(fd,&data,4);
    	close(fd);
    	return 0;
    }
    

    那么问题又来了,misc_register怎么工作的呢?跳到misc_register函数里面:

    在driver\char下的misc.c文件中,主要任务是先初始化链表,由于整个链表在文件开始已经静态初始化了:

    所以第一步只需判断新添加的设备是否已经存在,通过list_for_each_entry遍历链表与新添加的设备对比,这个是个宏原型是一个for循环主要是遍历作用。当设备是新的再执行第二步是否是自动注册次设备号,通过传入结构体成员minor 是否为 MISC_DYNAMIC_MINOR宏来判断,是的话找到定位一个未被注册的次设备号,并且对minor赋值。第三步通过MKDEV函数将主设备号与次设备号合成为设备的设备号。第四步创建设备节点,设备节点是访问驱动的一个接口,通过函数device_create创建, 将misc->name成员作为节点名称,所以对结构体赋值时最好能体现设备的特征。第五步也是最后一步将此设备添加到内核通过函数list_add。

    然后精彩的地方来了,在misc.c文件中发现了一个系统初始化自动执行的函数:subsys_initcall(misc_init);很显然,这个初始化关乎到了杂项设备能否正常执行,在这里先埋下伏笔,其函数为:

    二、早期字符设备的注册

    在刚刚的include\linux下Fs.h里面还有一个函数用于注册字符设备,这个函数也在misc.c里面调用过,可知杂项设备也是通过这个函数来实现注册:

    static inline int register_chrdev(unsigned int major,//主设备号

                                                    const char *name,//设备结点名称
                                                    const struct file_operations *fops)

    函数的返回值为:主设备号

    通过sourceinsight可以查看函数的函数内部内容:

    static inline int register_chrdev(unsigned int major, const char *name,  const struct file_operations *fops)
    {
        return __register_chrdev(major, 0, 256, name, fops);
    }

    可知在Fs.h里面只是再调用了一个__register_chrdev函数并且把所有的参数都给了这另一个函数。在跳转到__register_chrdev函数里面:

    里面又调用了__register_chrdev_region返回了一个cd,这个cd是干嘛呢?再跳转到__register_chrdev_region函数:

    static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor,  int minorct, const char *name)
    {
        struct char_device_struct *cd, **cp;
        int ret = 0;
        int i;

        cd= kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
        if (cd == NULL)
            return ERR_PTR(-ENOMEM);

        mutex_lock(&chrdevs_lock);

        /* temporary */
        if (major == 0) {
            for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {/*  chrdevs 存放字符设备的总个数结构体 */
                if (chrdevs[i] == NULL)/*  没有被用过*/
                    break;
            }

            if (i == 0) {/* 满了 */
                ret = -EBUSY;
                goto out;
            }
            major = i;
            ret = major;
        }

        cd->major = major;/*  装载到结构体*/
        cd->baseminor = baseminor;
        cd->minorct = minorct;
        strlcpy(cd->name, name, sizeof(cd->name));

        i = major_to_index(major);

        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
            if ((*cp)->major > major ||
                ((*cp)->major == major &&
                 (((*cp)->baseminor >= baseminor) ||
                  ((*cp)->baseminor + (*cp)->minorct > baseminor))))
                break;

        /* Check for overlapping minor ranges.  */
        if (*cp && (*cp)->major == major) {
            int old_min = (*cp)->baseminor;
            int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
            int new_min = baseminor;
            int new_max = baseminor + minorct - 1;

            /* New driver overlaps from the left.  */
            if (new_max >= old_min && new_max <= old_max) {
                ret = -EBUSY;
                goto out;
            }

            /* New driver overlaps from the right.  */
            if (new_min <= old_max && new_min >= old_min) {
                ret = -EBUSY;
                goto out;
            }
        }

        cd->next = *cp;
        *cp = cd;
        mutex_unlock(&chrdevs_lock);
        return cd;
    out:
        mutex_unlock(&chrdevs_lock);
        kfree(cd);
        return ERR_PTR(ret);
    这个函数有点长,其实主要功能就是以下部分:

    如果传入的major为0,则经过一个for循环遍历所有的chrdevs结构体数组:

    static struct char_device_struct {
        struct char_device_struct *next;
        unsigned int major;
        unsigned int baseminor;
        int minorct;
        char name[64];
        struct cdev *cdev;        /* will die */
    } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
    每一位成员为一个设备的节点信息,组成一个链表通过这个for循环遍历这个链表来找到没有使用的主设备号,那很明显,当传入进来的major为0,会自动分配主设备号,这个major也就是register_chrdev的第一个参数。并且当没找到时会报错。接着这个函数将主设备号装载在struct char_device_struct结构体内部进行返回,继续在__register_chrdev函数里面操作:

    再次判断得到的结构体,并且再次封装结构体,通过cdev_add添加到内核。通过上面的分析,register_chrdev主要是用于生成一个主设备号,由杂项设备可知杂项设备的主设备号为10,那是否也是通过register_chrdev函数注册的主设备号,在misc.c文件里面我们发现了一个杂项设备初始化的函数:misc_init其函数体为:

    static int __init misc_init(void)
    {
        int err;

    #ifdef CONFIG_PROC_FS
        proc_create("misc", 0, NULL, &misc_proc_fops);/*    交互式文件系统  */
    #endif
        misc_class = class_create(THIS_MODULE, "misc");/* 创建一个设备类  */
        err = PTR_ERR(misc_class);
        if (IS_ERR(misc_class))
            goto fail_remove;

        err = -EIO;
        if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))/* 注册字符设备 在/sys/class/创建子目录*/
            goto fail_printk;
        misc_class->devnode = misc_devnode;
        return 0;

    fail_printk:
        printk("unable to get major %d for misc devices\n", MISC_MAJOR);
        class_destroy(misc_class);
    fail_remove:
        remove_proc_entry("misc", NULL);
        return err;
    }

    很显然,里面调用了register_chrdev在major.h文件夹里面定义#define MISC_MAJOR        10,这就是杂项设备的主设备号为10的原因了,在前面我们介绍过早期字符设备register_chrdev注册他并没有像杂项设备一样调用device_create来产生一个设备节点,所以,如何使用这个函数产生的主设备号呢?

    这个问题想要达成的效果就是在/dev/下生成设备节点,刚好在linux下有一个创建设备结点的命令:mknod,用法:

    mknod Name {b|c} major minor    :Name设备名称   b|c块设备还是字符设备 major主设备号 minor次设备号

    那么,问题又来了设备号是个坑,但是我们可以通过打印函数打印创建的主设备号,然后通过mknod创建节点,测试代码如下:

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <linux/gpio.h>
    #include <mach/gpio.h>
    #include <plat/gpio-cfg.h>
    static int open_led (struct inode *my_inode, struct file *my_file)
    {
    	printk("open_led调用成功!\n");
    	return 0;	
    }
    static ssize_t read_led(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("read_led调用成功!\n");
    	return cnt;
    }
    static ssize_t write_led(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("write_led调用成功!\n");
    	return cnt;
    }
    static int release_led(struct inode *my_inode, struct file *my_file)
    {
    
        printk("release_led调用成功!\n");
    	return 0;
    }
    static struct file_operations fops_led=
    {
    	.open=open_led,
    	.write=write_led,
    	.read=read_led,
    	.release=release_led,
    };
    static unsigned int major;
    static int __init tiny4412_led_init(void)  /*insmod xxx.ko*/
    {
    	major=register_chrdev(0,"led", &fops_led);
    	printk("major:%d\n",major);
        printk("提示:驱动安装成功!\n");
    }
    static void __exit tiny4412_led_exit(void)
    {
    	unregister_chrdev(major,"led");
        printk("提示: 驱动卸载成功!\n");
    }
    module_init(tiny4412_led_init);    /*指定驱动的入口函数*/
    module_exit(tiny4412_led_exit);   /*指定驱动的出口函数*/
    MODULE_LICENSE("GPL");       /*指定驱动许可证*/
    
    

    将此生成.ko文件上传到开发板insmod安装驱动后出行:

    提示生成的主设备号为250,现在我们用mknod生成两个设备节点:

    通过ls /dev/查看生成的设备节点:

    再生成一个节点,其中主设备号一致,次设备号不一致

    生成的两个设备节点,现在通过用户层去访问驱动,用户层代码为:

    #include<stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    
    int main(int argc ,char ** argv)
    {
    	int i=0,count,err;
    	if(argc!=2)
    	{
    		printf("usage: ./app <device>\n");
    		return 0;
    	}
    	err=open(argv[1],2);
    	if(err<0)
    	{
    		printf("device open fail!\n");
    		return 0;
    	}
    	while(1)
    	{
    		read(err,&count,1);
    		sleep(1);
    		
    		write(err,&count,1);
    		sleep(1);
    	}
    	close(err);
    }
    
    

    上述运行结果为可得到以下结论:

    结论:register_chrdev只是单纯地创建了一个可以使用到的主设备号并且没有创建任何设备节点,当通过mknod产生的节点只是通过主设备号去访问驱动,次设备号不一样仍然是访问主设备号的驱动。

    上面可以看得出,如果使用这种方式去注册的话一个设备占用一个主设备号,相比杂项设备来说有点浪费资源,因为理论来说杂项设备可以注册255个并且它只占用一个主设备号,其实我们通过杂项设备可以看出我们可以模仿内核杂项设备注册方式来使得早期字符设备更加高效性,那要模仿杂项设备,那就在来分析下杂项设备的函数。由上述结论可知倘若杂项设备采用的是早期字符设备注册方式去注册驱动的话他是怎样做到在相同的主设备号的情况下去访问不同的驱动,下面我们在misc.c文件夹里面先分析下杂项设备怎样通过主设备号10来区分不同的驱动。

    首先,在misc_register函数里面主要执行的是通过创建一个数据结构:链表,用链表来存储各个次设备的信息,并且每次注册新的设备的时候都会遍历链表去找到未被使用的设备号,当设备号合成之后便将这个新的链表节点添加到链表中。这便完成一次新的设备添加,但是这里只是体现了对次设备号的新建并没有如何去实现区分次设备。

    其次我们用户层对驱动的接口操作一般是先由open函数得来的文件描述符,也就是说当打开设备节点时,一定是访问杂项设备10的主驱动,用上面的例子来说就是都是访问注册时填入的文件操作集合实现的底层接口函数,由此可以提出一个推论:杂项设备一定实现某种函数接口,如open。到在这里,重点来来了,通过查看misc.c文件发现有文件操作集合结构体:

    static const struct file_operations misc_fops = {
        .owner        = THIS_MODULE,
        .open        = misc_open,
        .llseek        = noop_llseek,
    };老铁,没看错,就是open函数的实现,那就看看这个open到底写了啥。

    先说明一点,注册的杂项设备一定会访问这个open函数,为啥呢?因为上面的例子充分说明主设备号都是访问同一个驱动。那接着看open函数有什么文章。忘了介绍一个知识点,就是open函数的形参:struct inode * inode可以通过这个来获取当前打开的次设备号(用户层open的那个设备节点的次设备号),获得的方法为:minor = iminor(inode);函数传入这个形参便可以获得,再通过下面的for循环:

    通过这个for循环遍历整个链表找到这个链表中与当前打开的次设备号一致的设备结构体指针,当存在时,将这个设备的文件操作集合调出来赋值给new_fops。之后做的东西更加厉害了通过open的第二个形参可以获得当前注册的次设备号的fops,也就是说这个参数的成员fops为对应注册时某个次设备的文件操作集合,这便是我们想要的次设备对应的驱动代码,也就是说在这里调用改变文件操作集合的指向,将共有的驱动里面通过打开的次设备号找到注册时写入的信息里面的文件操作集合,然后在共有的驱动里面改变当前的文件集合指向对应次设备的文件集合便可以,具体操作如下:

    此时本来是调用了公共的open现在通过这个open将文件操作集合修改了,并且有通过这个open跳转到次设备号注册的open,由于其余的文件操作集合函数形参都有一个struct file结构体,其指向都以发生改变,这边使得指向了不同的驱动程序。以下便是形象的总结:

    下面我们写一个早期设备框架的代码:

    早期字符设备底层框架代码:

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    static unsigned int char_dev_major; /*存放主设备号*/
    static struct class *char_dev_class;   /*定义类*/
    static LIST_HEAD(char_device_list); /*创建链表头*/
    /*
    自定义字符设备结构体
    */
    static struct char_device
    {
    	int minor;
    	const char *name;
    	const struct file_operations *fops;
    	struct list_head list;
    };
    static int open_char_dev (struct inode *my_inode, struct file *my_file)
    {
    	/*1. 获取次设备号*/
    	int minor = iminor(my_inode); /*得到次设备号*/
    	/*2. 查找次设备号对应的文件操作集合*/
    	struct char_device *c;
    	list_for_each_entry(c, &char_device_list, list)  
    	{
    		if (c->minor == minor) 
    		{
    			my_file->f_op=c->fops;	 /*改变文件操作集合的指向*/
    			my_file->f_op->open(my_inode,my_file); /*调用底层用户写的open函数*/
    		}
    	}
    	return 0;	
    }
    static struct file_operations fops_char_dev=
    {
    	.open=open_char_dev
    };
    /*
    向外提供的字符设备注册函数
    */
    int char_device_register(struct char_device * char_device)
    {
    	struct char_device *c;
    	dev_t dev;
    	/*1. 初始化链表头*/
    	INIT_LIST_HEAD(&char_device->list); 
    	/*2. 遍历链表*/
    	list_for_each_entry(c, &char_device_list, list)  
    	{
    		if (c->minor == char_device->minor) 
    		{
    			printk("次设备号%d冲突!\n",c->minor);
    			return -1;
    		}
    	}
    	/*3. 合成设备号*/
    	dev = MKDEV(char_dev_major, char_device->minor);
    	printk("主设备号:%d,次设备号:%d\n",char_dev_major,char_device->minor);
    	/*4. 在/dev下生成设备节点文件*/
    	device_create(char_dev_class,NULL,dev,NULL, "%s", char_device->name,char_device->minor);
    	/*5. 添加设备到链表*/
    	list_add(&char_device->list, &char_device_list);
    }
    /*
    向外提供的字符设备注销函数
    */
    int char_device_deregister(struct char_device *char_device)
    {
    	/*1. 删除链表节点*/
    	list_del(&char_device->list);
    	/*2. 删除设备节点文件*/
    	device_destroy(char_dev_class, MKDEV(char_dev_major, char_device->minor));
    }
    EXPORT_SYMBOL(char_device_register);
    EXPORT_SYMBOL(char_device_deregister);
    static int __init char_dev_init(void)  /*insmod xxx.ko*/
    {
    	int err;
    	/*1.早期设备注册方式*/
    	char_dev_major=register_chrdev(0,"char_device",&fops_char_dev); 
    	/*2. 创建类*/
    	char_dev_class = class_create(THIS_MODULE, "tiny4412_char_dev");
    	printk("char_dev_major=%d\n",char_dev_major);
        printk("提示: 字符设备驱动框架安装成功!\n");
        return err;
    }
    static void __exit char_dev_exit(void)
    {
    	int err;
    	/*1. 设备注销*/
    	unregister_chrdev(char_dev_major,"char_dev");
    	/*2. 注销类*/
    	class_destroy(char_dev_class);
        printk("提示: 字符设备驱动框架卸载成功!\n");
    }
    module_init(char_dev_init);    /*指定驱动的入口函数*/
    module_exit(char_dev_exit);   /*指定驱动的出口函数*/
    MODULE_LICENSE("GPL");       /*指定驱动许可证*/
    
    

    两个使用早期字符设备注册的驱动代码,两个独立的驱动函数:

    device1.c:

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <linux/cdev.h>
    #include <linux/slab.h> 
    #include <linux/gpio.h>
    #include <mach/gpio.h>
    #include <plat/gpio-cfg.h>
    extern int char_device_register(struct char_device * char_device);
    extern int char_device_deregister(struct char_device *char_device);
    static struct char_device
    {
    	int minor;
    	const char *name;
    	const struct file_operations *fops;
    	struct list_head list;
    };
    static int device1_open(struct inode *my_inode, struct file *my_file)
    {
    	printk("device1_open调用成功1!\n");
    	return 0;	
    }
    
    static ssize_t device1_read(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("device1_read调用成功!1\n");
    	return cnt;
    }
    
    static ssize_t device1_write(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("device1_write调用成功!\n");
    	return cnt;
    }
    
    static int device1_release(struct inode *my_inode, struct file *my_file)
    {
        printk("device1_release调用成功1!\n");
    	return 0;
    }
    static struct file_operations fops=
    {
    	.open=device1_open,
    	.write=device1_write,
    	.read=device1_read,
    	.release=device1_release,
    };
    static struct char_device misc_beep=
    {
    	.minor=10,
    	.name="device1",
    	.fops=&fops,
    };
    static int __init device1_init(void) 
    {
    	char_device_register(&misc_beep);
     	printk("提示:device1驱动安装成功!\n");
        return 0;
    }
    static void __exit device1_exit(void)
    {
    	char_device_deregister(&misc_beep);
        printk("提示: device1驱动卸载成功!\n");
    }
    module_init(device1_init);    /*指定驱动的入口函数*/
    module_exit(device1_exit);   /*指定驱动的出口函数*/
    MODULE_LICENSE("GPL");       /*指定驱动许可证*/
    
    

    device2.c:

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <linux/cdev.h>
    #include <linux/slab.h> 
    #include <linux/gpio.h>
    #include <mach/gpio.h>
    #include <plat/gpio-cfg.h>
    extern int char_device_register(struct char_device * char_device);
    extern int char_device_deregister(struct char_device *char_device);
    static struct char_device
    {
    	int minor;
    	const char *name;
    	const struct file_operations *fops;
    	struct list_head list;
    };
    static int device2_open(struct inode *my_inode, struct file *my_file)
    {
    	printk("device2_open调用成功1!\n");
    	return 0;	
    }
    
    static ssize_t device2_read(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("device2_read调用成功!1\n");
    	return cnt;
    }
    
    static ssize_t device2_write(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("device2_write调用成功!\n");
    	return cnt;
    }
    
    static int device2_release(struct inode *my_inode, struct file *my_file)
    {
        printk("device2_release调用成功1!\n");
    	return 0;
    }
    static struct file_operations fops=
    {
    	.open=device2_open,
    	.write=device2_write,
    	.read=device2_read,
    	.release=device2_release,
    };
    static struct char_device misc_beep=
    {
    	.minor=11,
    	.name="device2",
    	.fops=&fops,
    };
    static int __init device2_init(void) 
    {
    	char_device_register(&misc_beep);
     	printk("提示:device2驱动安装成功!\n");
        return 0;
    }
    static void __exit device2_exit(void)
    {
    	char_device_deregister(&misc_beep);
        printk("提示: device2驱动卸载成功!\n");
    }
    module_init(device2_init);    /*指定驱动的入口函数*/
    module_exit(device2_exit);   /*指定驱动的出口函数*/
    MODULE_LICENSE("GPL");       /*指定驱动许可证*/
    
    

    用户层函数跟上面的一样,现在开始运行程序了:

    首先第一步:对驱动框架进行安装:

    安装框架成功,系统给的主设备号为250,并且查看dev下的设备节点并没我们想要创建的设备节点,这是因为我们还没有调用早期字符设备的注册函数,这个函数在device1.c和device2.c里面调用。接着安装device1.ko和device2.ko:

    可以看到成功按照程序添加的信息去注册,并且获得次设备号产生对应的设别节点device1和device2,这里能够体现杂项设备是通过注册函数产生的节点。现在最具有对比性的操作来了,上面有个例子是用mknod来创建设备节点的,忘记了可以翻上去看一下,那个例子正好创建的设备号一致,但是他用同样的用户层代码打开不同的设备节点访问的都是同样的一个驱动,那这个呢?那就看结果:

    这结果就很nice了,他访问的是两个不一样的驱动程序,实现的次设备号的独立程序访问,这样节省了早期设备的资源,其实这个程序还是不能够验证是先调用公共的open函数来达到切换fops(文件操作集合),我们char_device.c的open进行修改:

    然后重新编译安装驱动:

    用户层app运行打开相同主设备号的设备节点前都是访问同一个公共的驱动open,再进行分支切换。

    结论:杂项设备的驱动其实是完善的早期字符设备的一个特例,固定主设备号为10,也是通过早期字符设备注册的,对早期字符设备封装后再内核加载时初始化,其实上面的框架也可以添加在内核,每一套都是自己的“杂项设备”。

    上述分析中利用__register_chrdev_region函数来得到主设备号,在char_dev.c中还设有几个函数也是通过调用__register_chrdev_region函数来实现注册的:

    那这两个API函数是干嘛用的呢,这就是标准字符设备的注册函数。

    三、标准字符设备注册

    在早期字符设备注册框架里面有涉及到cdev结构体,在__register_chrdev里面,__register_chrdev_region获得主设备号后通过cdev_alloc函数创建一个struct cdev结构,将所有的注册信息保存在这个结构体里面再通过cdev_add添加向内核注册字符设备,在创建cdev结构体后对结构体赋值有一个初始化函数:cdev_init通过这个函数添加到cdev链表里面去,也就是说,标准字符设备是通过cdev链表来保存所有的注册信息,在char_dev.c文件里面:

    通过这个函数将cdev结构初始化,就是对结构体成员赋值,其中一个成员是文件操作集合,所以这里面包括了对ops成员的赋值,添加链表之后,可以通过alloc_chrdev_region来动态申请设备号,第二个传参:baseminor 次设备号的起始地址,第三个传参:count是连续申请的个数。也就是说他可以连续申请几个设备并且可以通过device_create连续创建节点:示例代码:

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/miscdevice.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <linux/cdev.h>
    #include <linux/slab.h>  
    #include <linux/gpio.h>
    #include <mach/gpio.h>
    #include <plat/gpio-cfg.h>
    static struct class *dev_class; 
    static dev_t dev_num;    /*设备号*/
    static struct cdev *cdev;
    static int device1_open(struct inode *my_inode, struct file *my_file)
    {
    	printk("device1_open调用成功1!\n");
    	return 0;	
    }
    
    static ssize_t device1_read(struct file *my_file, char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("device1_read调用成功!1\n");
    	return cnt;
    }
    
    static ssize_t device1_write(struct file *my_file, const char __user *buff, size_t cnt, loff_t *loff)
    {
    	printk("device1_write调用成功!\n");
    	return cnt;
    }
    
    static int device1_release(struct inode *my_inode, struct file *my_file)
    {
        printk("device1_release调用成功1!\n");
    	return 0;
    }
    
    static struct file_operations device1_fops=
    {
    	.open=device1_open,
    	.read=device1_read,
    	.write=device1_write,
    	.release=device1_release,
    };
    /*static struct file_operations device2_fops=
    {
    	.open=device2_open,
    	.read=device2_read,
    	.write=device2_write,
    	.release=device2_release,
    };*/
    
    static int __init std_device_init(void)  
    {
    	int i; 
        /*1. 动态分配cdev结构*/
    	cdev=cdev_alloc();
    	/*2. cdev初始化*/
    	cdev_init(cdev,&device1_fops);
    	/*3. 动态分配设备号*/
    	alloc_chrdev_region(&dev_num,10,2,"led_dev");
    	printk("dev_num:%d\n",dev_num);
    	/*4. 添加字符设备到内核*/
    	cdev_add(cdev,dev_num,2);  //添加设备到内核	
    	/*5. 创建设备类,创建成功会在: /sys/class  目录下创建子目录*/
    	dev_class = class_create(THIS_MODULE, "exynos4412");
    	for(i=0;i<2;i++)
    	{
    		/*6. 在/dev下生成设备节点文件*/
    		device_create(dev_class,NULL,dev_num+i,NULL, "device%d",i+1);
    	}
    	if(IS_ERR(dev_class))
    	{
    		printk("dev_class error!\n");
    		goto Class_Error;
    	}
    	printk("提示: 设备号分配成功!\n");
    Class_Error:
        return 0;
    }
    static void __exit std_device_exit(void)
    {
    	int i;
    	for(i=0;i<2;i++)
    	{
    		device_destroy(dev_class,dev_num+i);//删除设备节点
    	}
    	class_destroy(dev_class);//注销类
    	unregister_chrdev_region(dev_num,2);//注销设备
    	kfree(cdev);//释放空间
        printk("提示: 设备号驱动卸载成功!\n");
    }
    
    module_init(std_device_init);    /*指定驱动的入口函数*/
    module_exit(std_device_exit);    /*指定驱动的出口函数*/
    MODULE_LICENSE("GPL");       /*指定驱动许可证*/
    

    用户层代码不变,结果:

    没有安装驱动之前没有设备节点,现在通过insmod安装驱动:

    安装后在/dev/下生成两个设备节点:device1和device2,现在通过用户层app打开设备节点:

    这个结果有在程序上理解很简单,因为在cdev结构体初始化的时候只使用了一个fops文件操作集合,所以两个都是访问同一个文件操作集合的函数,可以在对应文件从操作集合里面创建分支或者和早期字符设备一样实现优化,这里不再重复。

    结论:标准字符设备注册相对前面几个注册方式而言底层内容丰富的多,基本上都是调用相对比较底层的函数,相比于杂项设备,他能够自己分配主设备号,比杂项设备灵活,并且能连续注册好几个设备也可以注册一个设备;相对早期字符设备,没有个繁琐的框架,简单单一,但是使用还是要灵活一点才可以。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 一、i2c设备驱动注册 i2c设备驱动注册的调用流程为:xxx_init(设备驱动里所定义)—–&gt;i2c_add_driver—–&gt;i2c_register_driver 下面直接看源码,xxx_init(设备驱动里所定义)源码如下: static int ...

    一、i2c设备驱动注册
    i2c设备驱动注册的调用流程为:xxx_init(设备驱动里所定义)—–>i2c_add_driver—–>i2c_register_driver
    下面直接看源码,xxx_init(设备驱动里所定义)源码如下:

    static int egalax_i2c_ts_init(void)
    {
        int result;
    
        result = misc_register(&egalax_misc_dev);
        if(result) 
        {
            EGALAX_DBG(DBG_MODULE, " misc device register failed\n");
            goto fail;
        }
    
        p_char_dev = setup_chardev(); // allocate the character device
        if(!p_char_dev) 
        {
            result = -ENOMEM;
            goto fail;
        }
    
        dbgProcFile = proc_create(PROC_FS_NAME, S_IRUGO|S_IWUGO, NULL, &egalax_proc_fops);
        if (dbgProcFile == NULL) 
        {
            remove_proc_entry(PROC_FS_NAME, NULL);
            EGALAX_DBG(DBG_MODULE, " Could not initialize /proc/%s\n", PROC_FS_NAME);
        }
    
        EGALAX_DBG(DBG_MODULE, " Driver init done!\n");
    
        return i2c_add_driver(&egalax_i2c_driver);//调用i2c_add_driver函数
    
    
    fail:   
        egalax_i2c_ts_exit();
        return result;
    }
    
    module_init(egalax_i2c_ts_init);//定义驱动的init函数,也是加载驱动的时候第一个执行的函数

    i2c_add_driver函数又会调用i2c_register_driver函数。下面看i2c_register_driver函数的源码:

    int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
    {
        int res;
    
        /* Can't register until after driver model init */
        if (unlikely(WARN_ON(!i2c_bus_type.p)))
            return -EAGAIN;
    
        /* add the driver to the list of i2c drivers in the driver core */
        driver->driver.owner = owner;
        driver->driver.bus = &i2c_bus_type;//设定这个driver说依附的总线
    
        /* When registration returns, the driver core
         * will have called probe() for all matching-but-unbound devices.
         */
        res = driver_register(&driver->driver);//向i2c_bus注册driver
        /*注意这里调用的driver_register函数,这个函数又会调用其他函数对driver和device进行匹配(其中的匹配过程会在下一篇中详细接收),若匹配成功,i2c_register_driver返回res,结束执行;如不成功,则会往下执行i2c_for_each_dev函数(这个函数接下来调用的其他函数会动态注册i2c设备,这个动态注册设备的过程会在下面介绍);
        if (res)
            return res;
    
        /* Drivers should switch to dev_pm_ops instead. */
        if (driver->suspend)
            pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
                driver->driver.name);
        if (driver->resume)
            pr_warn("i2c-core: driver [%s] using legacy resume method\n",
                driver->driver.name);
    
        pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
    
        INIT_LIST_HEAD(&driver->clients);
        /* Walk the adapters that are already present */
        i2c_for_each_dev(driver, __process_new_driver);//当driver_register中device和driver匹配不成功的时候,会执行此函数;
    
        return 0;
    }

    至此设备驱动的注册就完成了。

    二、i2c的设备注册

    1.静态注册

    • 在没有出现dts之前,linux会将一些板级信息写在arch/arm下和你的板所匹配的c文件里,一般定义一个struct i2c_board_info结构体,将i2c的地址以及i2c的名称信息写入到此结构体中。如:
    static struct i2c_board_info i2c_ina219_devs[] __initdata = {
        { I2C_BOARD_INFO("egalax_i2c", 0x2a), },
        }
    然后在同一c文件里调用i2c_register_board_info函数将设备信息加入到内核的设备链表里,i2c_register_board_info源码如下(源码位于driver/i2c/i2c-boardinfo.c),
    int __init i2c_register_board_info(int busnum,
        struct i2c_board_info const *info, unsigned len)
    {
        int status;
    
        down_write(&__i2c_board_lock);
    
        /* dynamic bus numbers will be assigned after the last static one */
        if (busnum >= __i2c_first_dynamic_bus_num)
            __i2c_first_dynamic_bus_num = busnum + 1;
    
        for (status = 0; len; len--, info++) {
            struct i2c_devinfo  *devinfo;
    
            devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
            if (!devinfo) {
                pr_debug("i2c-core: can't register boardinfo!\n");
                status = -ENOMEM;
                break;
            }
    
            devinfo->busnum = busnum;
            devinfo->board_info = *info;
            list_add_tail(&devinfo->list, &__i2c_board_list);//将设备信息添加到__i2c_board_tail设备链表中
        }
    
        up_write(&__i2c_board_lock);
    
        return status;
    }
    函数第一个参数表示添加的i2c总线的组号,第二个参数是要注册的i2c_board_info的结构体指针,第三个参数是指i2c_board_info中成员的个数。
    
    • 在出现了dts之后,为了去耦合,将这些的板级信息全部都定义在设备树中,在移植的时候只要修改设备树的硬件信息即可,设备树信息位于arch/arm/boots/dts中,以i2c设备为例,其在设备树中的信息如下图:
      这里写图片描述
      此图为在第二组i2c总线下挂接的4个硬件设备信息;
      linux在启动uboot的时候,会自动展开dts上的硬件信息,自动调用i2c_register_board_info函数将设备注册进设备链表中。现在多数的平台都采用dts的方法在定义这些板级的信息;
    • 将硬件设备添加到设备链表之后,现在就分析内核是怎么向i2c总线动态注册这些设备的,下面区分几个概念:
      i2c的硬件设备在内核中表示为:i2c_client;
      i2c的设备驱动程序在内核中表示为:i2c_driver;
      每一组i2c总线对应一个i2c控制器,即i2c_adapter;
      系统中对i2c_client的注册是在i2c_adapter的注册过程中完成的:
      i2c_add_numbered_adapter()函数在注册I2C_adapter驱动的同时会扫描前面提到的I2C设备链表__i2c_board_list,如果该总线上有对应的I2C设备,则创建相应的i2c_client,并将其注册到I2C core中。调用流程如下所示:
      i2c_add_numbered_adapter—–>i2c_register_adapter—–>i2c_scan_static_board_info—–>i2c_new_device—–>i2c_attach_client—–>device_register;
      下面分析源码(源码位置位于driver/i2c/i2c-core.c):
    int i2c_add_numbered_adapter(struct i2c_adapter *adap)
    {
        int id;
        int status;
    
        if (adap->nr == -1) /* -1 means dynamically assign bus id */i2c总线的组有动态分配和静态之分,当adap->nr == -1的时候为静态注册;
            return i2c_add_adapter(adap);
        if (adap->nr & ~MAX_ID_MASK)
            return -EINVAL;
    
    retry:
        if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
            return -ENOMEM;
    
        mutex_lock(&core_lock);//互斥锁
        /* "above" here means "above or equal to", sigh;
         * we need the "equal to" result to force the result
         */
        status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
        if (status == 0 && id != adap->nr) {
            status = -EBUSY;
            idr_remove(&i2c_adapter_idr, id);
        }
        mutex_unlock(&core_lock);//释放互斥锁
        if (status == -EAGAIN)
            goto retry;
    
        if (status == 0)
            status = i2c_register_adapter(adap);//调用i2c_register_adapter
        return status;
    }
    
    下面看i2c_register_adapter函数源码:
    static int i2c_register_adapter(struct i2c_adapter *adap)
    {
        int res = 0;
    
        /* Can't register until after driver model init */
        if (unlikely(WARN_ON(!i2c_bus_type.p))) {
            res = -EAGAIN;
            goto out_list;
        }
    
        /* Sanity checks */
        if (unlikely(adap->name[0] == '\0')) {
            pr_err("i2c-core: Attempt to register an adapter with "
                   "no name!\n");
            return -EINVAL;
        }
        if (unlikely(!adap->algo)) {
            pr_err("i2c-core: Attempt to register adapter '%s' with "
                   "no algo!\n", adap->name);
            return -EINVAL;
        }
        //这里的likely()指很大几率执行if分支的代码,编译时提醒编译器放在前面;unlikel指很大几率执行else分支的代码,编译时提醒编译器将else的代码放在前面。这样做为了提高cpu的运行速率;
        rt_mutex_init(&adap->bus_lock);
        mutex_init(&adap->userspace_clients_lock);
        INIT_LIST_HEAD(&adap->userspace_clients);
    
        /* Set default timeout to 1 second if not already set */
        if (adap->timeout == 0)
            adap->timeout = HZ;
    
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);//设置 adap->dev.kobj.name 为 i2c-0 ,它将出现在 sysfs 中
        adap->dev.bus = &i2c_bus_type;       //此设备依附的总线类型是i2c总线类型;
        adap->dev.type = &i2c_adapter_type;  //此设备的设备类型是i2c_adapter;
        res = device_register(&adap->dev);  //注册i2c_adapter设备;
        if (res)
            goto out_list;
    
        dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
    
    #ifdef CONFIG_I2C_COMPAT
        res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
                           adap->dev.parent);
        if (res)
            dev_warn(&adap->dev,
                 "Failed to create compatibility class link\n");
    #endif
    
        /* create pre-declared device nodes */
        if (adap->nr < __i2c_first_dynamic_bus_num)
            i2c_scan_static_board_info(adap);//扫描设备链表上的所有硬件设备
    
        /* Notify drivers */
        mutex_lock(&core_lock);
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
        mutex_unlock(&core_lock);
    
        return 0;
    
    out_list:
        mutex_lock(&core_lock);
        idr_remove(&i2c_adapter_idr, adap->nr);
        mutex_unlock(&core_lock);
        return res;
    }
    i2c_register_adapter这个函数主要做两件事:
    1.注册自己的i2c_adapter设备;
    2.调用i2c_scan_static_board_info函数,创建与i2c_adapter总线号相同的其他设备;
    
    下面主要看i2c_scan_static_board_info函数,源码如下:
    static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
    {
        struct i2c_devinfo  *devinfo;
    
        down_read(&__i2c_board_lock);
        list_for_each_entry(devinfo, &__i2c_board_list, list) { //遍历__i2c_board_list,并以此取出每个设备
            if (devinfo->busnum == adapter->nr//adapter->nr==0,当取出设备的busnum==0时,调用i2c_new_device函数;
                    && !i2c_new_device(adapter,
                            &devinfo->board_info))
                dev_err(&adapter->dev,
                    "Can't create device at 0x%02x\n",
                    devinfo->board_info.addr);
        }
        up_read(&__i2c_board_lock);
    }
    
    
    下面来看i2c_new_device函数。源码如下:
    struct i2c_client *
    i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
    {
        printk("********************wbx:i2c_new_device begin\n"); 
        struct i2c_client   *client;
        int         status;
    
        client = kzalloc(sizeof *client, GFP_KERNEL);
        if (!client)
            return NULL;
    
        client->adapter = adap; //要创建的i2c_client依附到当前的i2c_adapter控制器上
        client->dev.platform_data = info->platform_data;
    
        if (info->archdata)
            client->dev.archdata = *info->archdata;
    
        client->flags = info->flags;
        client->addr = info->addr;//设置设备的地址
        client->irq = info->irq;//中断编号默认为0,在启动设备中通过gpio_to_irq会对irq重新赋值
    
        strlcpy(client->name, info->type, sizeof(client->name));//对i2c_client的名字进行赋值,名字很重要,靠这个名字和驱动的id_table进行匹配
    
        /* Check for address validity */
        status = i2c_check_client_addr_validity(client);
        if (status) {
            dev_err(&adap->dev, "Invalid %d-bit I2C address 0x%02hx\n",
                client->flags & I2C_CLIENT_TEN ? 10 : 7, client->addr);
            goto out_err_silent;
        }
    
        /* Check for address business */
        status = i2c_check_addr_busy(adap, client->addr);
        if (status)
            goto out_err;
    
        client->dev.parent = &client->adapter->dev;//设置其父设备为:i2c_adapter设备
        client->dev.bus = &i2c_bus_type; //设备其依附的总线
        client->dev.type = &i2c_client_type;//其属性设置为设备属性
        client->dev.of_node = info->of_node;
    
        /* For 10-bit clients, add an arbitrary offset to avoid collisions */
        dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adap),
                 client->addr | ((client->flags & I2C_CLIENT_TEN)
                         ? 0xa000 : 0));
        status = device_register(&client->dev);//注册这个设备
        if (status)
            goto out_err;
    
        printk("client [%s] registered with bus id %s\n",
            client->name, dev_name(&client->dev));
    
        return client;
    
    out_err:
        dev_err(&adap->dev, "Failed to register i2c client %s at 0x%02x "
            "(%d)\n", client->name, client->addr, status);
    out_err_silent:
        kfree(client);
        return NULL;
    }
    此函数主要调用device_register函数来进行设备注册;

    到这里静态注册设备的分析以及完成了,通过i2c_board_info这个结构体或者dts来配置硬件信息,再通过调用i2c_register_board_info函数来向设备链表来注册硬件设备,然后系统在注册i2c_adapter完成后,取出链表中的每一个设备,当设备的i2c的编号和i2c_adapter相同时,在调用device_register对此设备进行注册;

    二、动态注册(以i2c设备为例)
    另外还有一种设备注册的方法是动态注册,这种动态注册方法需要配合设备驱动程序进行,如果没有第三方的的配置文件的话,要将部分硬件信息写到设备驱动中,这里以eeti公司的egalax_i2c.c触摸屏设备驱动程序移植到全志R16平台上例:
    前面以及分析过了i2c_register_driver这个函数,在i2c_register_driver函数中会调用driver_register函数,若此函数不能正确的匹配dirver和device,则向下执行i2c_for_each_dev这个函数;下面就以i2c_for_each_dev这个函数为入口点,来看设备是怎么进行动态注册的。
    先看i2c_register_driver源码(driver/i2c/i2c-core.c):

    int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
    {
        int res;
        printk("********************wbx:i2c_register_driver begin\n");  
        /* Can't register until after driver model init */
        if (unlikely(WARN_ON(!i2c_bus_type.p)))
            return -EAGAIN;
    
        /* add the driver to the list of i2c drivers in the driver core */
        driver->driver.owner = owner;
        driver->driver.bus = &i2c_bus_type;
    
        /* When registration returns, the driver core
         * will have called probe() for all matching-but-unbound devices.
         */
        res = driver_register(&driver->driver);
        if (res)
            return res;
    
        /* Drivers should switch to dev_pm_ops instead. */
        if (driver->suspend)
            pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
                driver->driver.name);
        if (driver->resume)
            pr_warn("i2c-core: driver [%s] using legacy resume method\n",
                driver->driver.name);
    
        pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
    
        INIT_LIST_HEAD(&driver->clients);
        /* Walk the adapters that are already present */
        i2c_for_each_dev(driver, __process_new_driver);//driver_register中device和driver匹配不错的时候,会调用此函数,**注意这里的__process_new_driver,这是一个函数指针**,也传递进了i2c_for_each_dev函数,下面会讲解这边函数指针的用法,这里先提醒
    
        return 0;
    }

    再看i2c_for_each_dev源码:

    int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *))
    {
        //函数的形参data就是上层函数i2c_register_driver传递过来的driver
        int res;
        mutex_lock(&core_lock);
        res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn);//在调用此函数
        mutex_unlock(&core_lock);
    
        return res;
    }

    看bus_for_each_dev函数源码(driver/base/bus.c):

    int bus_for_each_dev(struct bus_type *bus, struct device *start,
                 void *data, int (*fn)(struct device *, void *))
    {
        struct klist_iter i;
        struct device *dev;
        int error = 0;
    
        if (!bus || !bus->p)
            return -EINVAL;
    
        klist_iter_init_node(&bus->p->klist_devices, &i,
                     (start ? &start->p->knode_bus : NULL));//遍历链表上的所有设备
        //通过next_device函数将链表上的设备一样取出然后传递给fn,也就是前面提到的__process_new_driver函数
        while ((dev = next_device(&i)) && !error)
            error = fn(dev, data);
        klist_iter_exit(&i);
        return error;
    }

    下面再看__process_new_driver源码拿到driver和device做了什么(这部分源码在driver/i2c/i2c-core.c):

    static int __process_new_driver(struct device *dev, void *data)
    {
    //判断拿到的设备是否是i2c总线设备,即是不是i2c_adapter,返回0,结束词函数;继续拿下一个device,在进行判断是不是i2c_adapter,如实是则执行i2c_do_add_adapter函数
        if (dev->type != &i2c_adapter_type)
            return 0;}
        return i2c_do_add_adapter(data, to_i2c_adapter(dev));
    }

    下面看调用的i2c_do_add_adapter源码:

    static int i2c_do_add_adapter(struct i2c_driver *driver,
                      struct i2c_adapter *adap)
    {
    
        /* Detect supported devices on that bus, and instantiate them */
        //进入i2c_do_add_adapter函数后先调用i2c_detect函数
        i2c_detect(adap, driver);
    
        /* Let legacy drivers scan this bus for matching devices */
        if (driver->attach_adapter) {
            dev_warn(&adap->dev, "%s: attach_adapter method is deprecated\n",
                 driver->driver.name);
            dev_warn(&adap->dev, "Please use another way to instantiate "
                 "your i2c_client\n");
            /* We ignore the return code; if it fails, too bad */
            driver->attach_adapter(adap);
        }
        return 0;
    }

    看i2c_detect的源码:

    static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
    {
    
        const unsigned short *address_list;
        struct i2c_client *temp_client;
        int i, err = 0;
        //获取i2c_adapter设备的i2c总线号
        int adap_id = i2c_adapter_id(adapter);
        //获取设备驱动程序中定义的address_list 
        address_list = driver->address_list;
        if (!driver->detect || !address_list)
            { 
            return 0;}
    //这里在判断驱动程序中是定义了detect 函数和address_list,其中一个没定义,则失败,返回0;
        /* Stop here if the classes do not match */
        if (!(adapter->class & driver->class))
            {
            printk("********************wbx:2_error\n");
            return 0;}
    //这里在判断驱动程序中定义的class和adapter中定义的class是否相同,不相同则失败返回0;
    /*我在移植egalax_i2c这个驱动程序时,驱动中没有定义address_list,也没有定义detect 函数和class,我是仿照默认驱动的gt82x.c,进行添加的。
        /* Set up a temporary client to help detect callback */
        temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
        if (!temp_client)
            return -ENOMEM;
        temp_client->adapter = adapter;//这里定义了一个i2c_client,下面的程序会对这个进行赋值,然后再注册
    
        for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
            printk("found normal entry for adapter %d, "
                "addr 0x%02x\n", adap_id, address_list[i]);
            temp_client->addr = address_list[i];//将检测到的i2c地址赋值给i2c_client的addr
            err = i2c_detect_address(temp_client, driver);//调用此函数
            if (unlikely(err))
                break;
        }
    
        kfree(temp_client);
        return err;
    }
    

    下面看i2c_detect_address的源码:

    static int i2c_detect_address(struct i2c_client *temp_client,
                      struct i2c_driver *driver)
    {
        struct i2c_board_info info;
        struct i2c_adapter *adapter = temp_client->adapter;
        int addr = temp_client->addr;
        int err;
    
        /* Make sure the address is valid */
        err = i2c_check_addr_validity(addr);//检测i2c地址是否有效
        if (err) {
            printk(&adapter->dev, "Invalid probe address 0x%02x\n",
                 addr);
            return err;
        }
    
        /* Skip if already in use */
        if (i2c_check_addr_busy(adapter, addr))//检测i2c地址是否busy
            return 0;
    
    #ifndef CONFIG_ARCH_SUNXI
        /* Make sure there is something at this address */
        if (!i2c_default_probe(adapter, addr))
            return 0;
    #endif
    
        /* Finally call the custom detection function */
        memset(&info, 0, sizeof(struct i2c_board_info));
        info.addr = addr;
        err = driver->detect(temp_client, &info);
        /*这里定义了结构体i2c_board_info info,看了静态注册设备的话,对这个结构体很熟悉了,没有使用dts的时候必须要用i2c_board_info 结构体存放硬件信息,动态注册也一样,只是定义这个结构体的位置不同而已。
        这里把在i2c_detect函数中检测到的i2c地址赋值给了info这个结构体,并将temp_client和info一起传入了驱动程序中定义的detect函数,那么detect函数拿到这两个参数做了什么呢?,看下**驱动程序中**detect函数的源码:
    
    `static int ctp_detect(struct i2c_client *client, struct i2c_board_info *info)
    {
        //首先定义了一个结构体i2c_adapter 保存传递进来的temp_client结构体中的adapter成员
        struct i2c_adapter *adapter = client->adapter;
    
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)){
            printk("======return=====\n");
            return -ENODEV;
        }//检测i2c_apater(i2c控制器的驱动能力)
        /*twi_id == adapter->nr这个判断语句是重点,我们都知道每一组i2c总线挂接的有自己的i2c设备这是由硬件连接决定的,那么决定了在软件上每一个i2c_driver挂载到相应的i2c_adapter,这里进行判读,传入进来的i2c_adapter的总线号是否和我这个i2c_client设备要挂载的总线号一致;
        if(twi_id == adapter->nr){
                    strlcpy(info->type, "egalax_i2c", 11);//若总线号相同,赋值名称,这里的名称一定要和id_table当中的相同;
                    return 0;
        }else{
            return -ENODEV;
        }
    }`//这里的detect函数主要就是判断总线号,然后对即将注册的i2c_client的名称赋值
    
    
        if (err) {
             /*-ENODEV is returned if the detection fails. We catch it
               here as this isn't an error. */
               printk("******************error\n");
            return err == -ENODEV ? 0 : err;
        }
    
    
    
        /* Consistency check */
        if (info.type[0] == '\0') {
            printk( "%s detection function provided "
                "no name for 0x%x\n", driver->driver.name,
                addr);
        } else {
            struct i2c_client *client;
    
            /* Detection succeeded, instantiate the device */
            printk("Creating %s at 0x%02x\n",
                info.type, info.addr);
            client = i2c_new_device(adapter, &info);
            /*然后在调用i2c_new_device函数,i2c_new_device在静态注册中已经分析过了,主要是调用driver_register向i2c总线注册设备;
            if (client)
                {list_add_tail(&client->detected, &driver->clients);
                printk("add_client\n");
                }//注册成功后将i2c_client加到设备链表中
            else
                printk(&adapter->dev, "Failed creating %s at 0x%02x\n",
                    info.type, info.addr);
        }
        return 0;
    }
    

    至此动态注册分析完成。全志的R16平台是采用此种方法进行注册硬件设备的,但是这种方法需要在驱动中写入相应的硬件信息,而且在移植的时候不一目了然。我是花了好几天才跟踪到这种动态注册的方法;

    展开全文
  • 首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向内核注册了一个字符设备。第一个参数是主设备号,0代表动态分配,这里的MEM_MAJOR是1。第二个参数是设备的名字,第三个参数是文件...

    首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向内核注册了一个字符设备

    第一个参数是主设备号,0代表动态分配,这里的MEM_MAJOR是1。第二个参数是设备的名字,第三个参数是文件操作指针。<

    展开全文
  • 获取mac地址和设备号的时候,需要注册权限.不然会报错 所以权限很重要.记得加权限,记得加权限,记得加权限 /** * 2015年12月7日TODOimgod */ package com.bodao.aibang.utils; import java.net.InetAddress; i
  • JmDNS是multi-cast DNS的java实现,可以用来在局域网中查找设备注册设备,而且不需要额外的配置。它可以获取到设备的IP, 端口号,服务类型,名称等信息。通过这些信息,就可以方便地实现设备互联。 这里有一个...
  • 副标题:设备注册相关的基本结构的原理框架 设备注册与删除时间  设备在下列两种情况下进行注册: 1)加载NIC驱动时 2)插入热插拔设备时  这里NIC与热插拔设备有些不同。a.对于非热插拔NIC来说,NIC的注册是伴随...
  • Android 使用JmDNS查找和注册设备

    千次阅读 2016-06-27 10:55:03
    JmDNS是multi-cast DNS的java实现,可以用来在局域网中查找设备注册设备,而且不需要额外的配置。它可以获取到设备的IP, 端口号,服务类型,名称等信息。通过这些信息,就可以方便地实现设备互联。  这里有一个...
  • 设备驱动注册和注销 块设备驱动的第一个任务就是将他们自己注册到内核中,其函数原型如下:  int register_blkdev(unsigned int major, const char* name); major参数是块设备要使用的主设备号,name...
  • v4l2 video设备注册和调用过程

    千次阅读 2013-12-31 15:14:28
    一、 注册一个video_device设备 它代表系统/dev/videox设备节点的实际的物理设备。 下边一内核版本2.6.32种成熟的omap2432处理器摄像头控制器模块驱动为例分析: 下边的代码在driver/media/video/omap24xxcam.c...
  • 设备注册于设备除名  设备注册与设备除名一般有 register_netdev和unregister_netdev完成。这两个是包裹函数,负责上锁,真正起作用的是其调用的register_netdevice和unregister_netdevice。参见:net/core/dev.c...
  • 3.video_register_device注册过程 static inline int __must_check video_register_device(struct video_device *vdev, int type, int nr) { return __video_register_device(vdev, type, nr, 1, vdev->...
  • 2. 通过控制台在网关下面先添加一个设备 DeviceName = devicename3. 获得设备的三元参数: ProductKey = pk; DeivceName = devicename; DeviceSecret =..........;4. 获得该设备的登录名及状态; ClientID...
  • 不少相似的问题,但大多只是需要获取当前登录用户的用户名,这个只要在action里setAttrubibu()就可以了,但是我现在想将登录用户所有的注册信息都存进session,比如性别、邮箱、手机号什么的,但是在登录验证的...
  • #include #include //获得设备型号 + (NSString *)getCurrentDeviceModel { int mib[2]; size_t len; char *machine; mib[0] = CTL_HW; mib[1] = HW_MACHINE; sysctl(mib, 2, NULL, &len, NULL
  • 海康威视摄像头开发第一次做确实有点恶心,我把我用到的写一下,方便以后学习 ...2.64位系统,将lib动态库所有文件上传/usr/lib64下,重名文件跳不要覆盖,否则可能服务器重启失败 3.将日志文件HCNetSD...
  • linux内核cdev_init系列函数(字符设备注册

    万次阅读 多人点赞 2012-06-08 16:48:57
    内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义: linux-2.6.22/include/linux/cdev.h struct cdev {  struct kobject kobj; // 每个 cdev 都是一个 kobject  struct module *owner; // ...
  • 绝对干货~~学习Linux设备驱动开发的过程中自然会遇到字符设备驱动、平台设备驱动、设备驱动模型和sysfs等相关概念和技术。对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术...
  • 【翻译】设备注册和初始化 第5章和第6章中,我们了解了内核是如何识别网卡以及内核执行初始化过程以使网卡能够和驱动程序通讯。本章中,我们讨论初始化的其它阶段:l 网络设备什么时候,如何注册到内核l 网络设备...
  • 设备是与字符设备并列的概念,这两类设备在Linux中驱动的结构有较大差异,总体而言,块设备驱动比字符设备驱动要复杂得多,在I/O操作上表现出极大的不同,缓冲、I/O调度、请求队列等都是与块设备驱动相关的概念。...
  • Linux字符设备驱动剖析

    千次阅读 2015-05-23 23:09:13
    忠于源码,讲述linux字符设备驱动的那些事儿,重点讲述字符设备的创建和访问过程。
  • 深入理解Linux字符设备驱动

    千次阅读 2016-03-20 11:09:13
    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次、组成框架和交互、如何编写驱动、设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解。
  • 修改网络制式需要用到高通的QXDM工具,该工具包下载链接如下: ... 具体操作步骤如下: 1、安装工具包里的QPST和QXDM软件。 ...2、打开工具包里的3.dmc文件来启动QXDM软件,启动...3、打开设备的开发者选项和USB调试选...
  • Linux中的设备文件与设备

    千次阅读 2014-08-29 15:49:35
    设备文件与设备号 在Linux下,一切皆文件,设备也不例外,为了管理这些设备,系统为...一个Linux系统,当前所有注册设备的主设备号可以通过/proc接口查看: [root@localhost lenky]# cat /proc/devices Charac
  • linux设备模型二(kobject)

    万次阅读 多人点赞 2018-08-03 01:20:14
    1. 前言 ...由上一节可知,Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳...
  • 设备树第七节中,我们知道了在内核启动时,是把dtb文件所在的内存块保留了下来。 而这个内存块是可以在内核中查看的。 查看的位置在sys文件系统中。 因为dtb文件是二进制文件,所以要用hexdump命令来查看。 ...
  • 从前面 的例子可以看到,我们在操作一个调和时都 是通过open系统调用先去打开这个设备,不管是设备还是文件,我们要访问它都要称通过open函数来先打开, 这样才能调用其它的函数如read、write来操作它,即通知内核...
  • 总线设备驱动模型

    千次阅读 2016-05-17 21:52:45
     随着技术的不断进步,系统的拓扑结构也越来越复杂,对热插拔,跨平台移植性的要求也越来越高,2.4内核已经难以满足这些需求,为适应这宗形势的需求,从linux2.6内核开始提供了全新的设备模型 2. 总线  2.1 描述...
  • USB 设备热插拔的检测

    千次阅读 2016-08-28 09:58:58
    此时需要枚举当前设备列表中所有的设备,并过滤指定的USB 设备。 2. 打开软件后, USB 热插拔 。 此时用WM_DEVICECHANGE 消息去获取设备信息。 当设备插入或者移除时,系统给当前主窗口发送WM_DEVICECHANGE 消息 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 154,280
精华内容 61,712
关键字:

当前设备已经注册过了