3.字符设备驱动高级

版权声明:转载请声明 https://blog.csdn.net/qq_40732350/article/details/82937776

参考:

https://blog.csdn.net/zqixiao_09/article/details/50839042

1.注册字符设备驱动新接口1

1.1、新接口与老接口

(1)老接口:register_chrdev

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


(2)新接口:

register_chrdev_region  (dev_t from, unsigned count, const char *name)

函数用于已知起始设备的设备号的情况

  1. #define MYMAJOR        200
  2. mydev = MKDEV(MYMAJOR, 0);
  3. retval = register_chrdev_region(mydev, MYCNT, MYNAME);

alloc_chrdev_region  (dev_t *dev, unsigned baseminor, unsigned count,const char *name);

用于设备号未知,向系统动态申请未被占用的设备号的情况,函数调用成功之后,会把得到的设备号放入第一个参数dev中

unregister_chrdev_region    (dev_t from, unsigned count)

释放原先申请的设备号

1.2、cdev介绍  在cdev.h文件定义

(1)结构体

/*

*内核源码位置

*linux2.6.38/include/linux/cdev.h

*/
struct cdev {
	struct kobject kobj;  //内嵌的内核对象.

	struct module *owner;  //该字符设备所在的内核模块的对象指针,一般初始化为:THIS_MODULE

	const struct file_operations *ops;   //字符设备用到的一个重要的结构体file_operations,cdev初始化时与之绑定

	struct list_head list;  //用来将已经向内核注册的所有字符设备形成链表.

	dev_t dev;  //主设备号24位 与次设备号8位,dev_t为32位整形,每个系统给定义不一样

	unsigned int count;   //隶属于同一主设备号的次设备号的个数.
};


(2)相关函数:

cdev_alloc:用于动态申请一个cdev内存,减少栈的压力

cdev_init:用于初始化cdev的成员,并建立cdev和file_operations之间的连接

cdev_add:注册,它的调用通常发生在字符设备驱动模块加载函数中

cdev_del:注销,它的函数的调用则通常发生在字符设备驱动模块卸载函数中

  1.     cdev = cdev_alloc();
  2.     cdev->owner = fops->owner;
  3.     cdev->ops = fops;

这几步等于:cdev_init(),因为cdev_alloc()里面做一些cdev_init()的工作

1.3、设备号

(1)主设备号和次设备号
(2)dev_t类型

dev设备号的主设备号占几位,在各个系统给定义不一样,所以用下面的宏来操作
(3)MKDEV、MAJOR、MINOR三个宏

1) -- 从设备号中提取major和minor

MAJOR(dev_t dev);                              

MINOR(dev_t dev);

2) -- 通过major和minor构建设备号

MKDEV(int major,int minor);

注:这只是构建设备号。并未注册,需要调用 register_chrdev_region 静态申请;

//宏定义:
#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)
#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))</span>


1.4、编程实践
(1)使用register_chrdev_region + cdev_init + cdev_add进行字符设备驱动注册


2.注册字符设备驱动新接口2

实例代码:

#define MYMAJOR		251
#define MYNAME		"testchar"
#define MYCOUNT         0x1

static struct cdev my_cdev;
static dev_t dev_from;

//第1步:注册/分配主次设备号
dev_from = MKDEV(MYMAJOR, 0);
retval = register_chrdev_region(dev_from, MYCOUNT, MYNAME);
if (retval){
	printk(KERN_INFO "register_chrdev_region error\n");
	return -EINVAL;		
}else{
	printk(KERN_INFO "register number sucess\n");		
}

//第2步:注册字符设备驱动
cdev_init(&my_cdev, &test_fops);

if (cdev_add(&my_cdev, dev_from, MYCOUNT)) {
	printk(KERN_INFO "cdev_add error\n");
	return -EINVAL;
}else{
	printk(KERN_INFO "register driver sucess\n");		
}


3.注册字符设备驱动新接口3

2.1、使用alloc_chrdev_region自动分配设备号

(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。

#define MYMAJOR		251
#define MYNAME		"testchar"
#define MYCOUNT         0x1

static struct cdev *p_my_cdev;
static dev_t dev_from;

//第1步:注册/分配主次设备号	
retval = alloc_chrdev_region(&dev_from, 0, MYCOUNT, MYNAME);  //自动分配设备号
	
if (retval){		
    printk(KERN_INFO "register_chrdev_region error\n");
    goto err;		
}else{		
    printk(KERN_INFO "主设备:= %d ,次设备 = %d\n", MAJOR(dev_from), MINOR(dev_from));
    printk(KERN_INFO "register number sucess\n");		
}

//第2步:注册字符设备驱动
p_my_cdev = cdev_alloc();  //自动分配空间
cdev_init(p_my_cdev, &test_fops);
cdev_add(p_my_cdev, dev_from, MYCOUNT)


2.2、得到分配的主设备号和次设备号

(1)使用MAJOR宏和MINOR宏从dev_t得到major和minor
(2)反过来使用MKDEV宏从major和minor得到dev_t。
(3)使用这些宏的代码具有可移植性


2.3、中途出错的倒影式错误处理方法

(1)内核中很多函数中包含了很多个操作,这些操作每一步都有可能出错,而且出错后后面的步骤就没有进行下去的必要性了。


4.注册字符设备驱动新接口4

4.1、使用cdev_alloc

(1)cdev_alloc的编程实践
(2)从内存角度体会cdev_alloc用与不用的差别
(3)这就是非面向对象的语言和面向对象的代码
(4)再次感叹C语言的博大精深,好好去看《4.C语言高级专题》


4.2、cdev_init的替代

(1)cdev_init源码分析
(2)不使用cdev_init时的编程
(3)为什么讲这个

  1.     cdev = cdev_alloc();
  2.     cdev->owner = fops->owner;
  3.     cdev->ops = fops;

这几步等于:cdev_init(),因为cdev_alloc()里面做一些cdev_init()的工作

5.字符设备驱动注册代码分析1

5.1、老接口分析 
register_chrdev  //   \include\linux\fs.h
    __register_chrdev    //   \fs\char_dev.c
        __register_chrdev_region  //   \fs\char_dev.c
        cdev_alloc                         //
        cdev_add

5.2、新接口分析
register_chrdev_region
    __register_chrdev_region

alloc_chrdev_region
    __register_chrdev_region


6.字符设备驱动注册代码分析2


7.自动创建字符设备驱动的设备文件

7.1、问题描述:
(1)整体流程回顾
(2)使用mknod创建设备文件的缺点
(3)能否自动生成和删除设备文件
7.2、解决方案:udev(嵌入式中用的是mdev)
(1)什么是udev?应用层的一个应用程序
(2)内核驱动和应用层udev之间有一套信息传输机制(netlink协议)
(3)应用层启用udev,内核驱动中使用相应接口
(4)驱动注册和注销时信息会被传给udev,由udev在应用层进行设备文件的创建和删除
7.3、内核驱动设备类相关函数
(1)class_create// 注册字符设备驱动完成后,添加设备类的操作,以让内核帮我们发信息
    // 给udev,让udev自动创建和删除设备文件
(2)device_create// 最后1个参数字符串,就是我们将来要在/dev目录下创建的设备文件的名字
    // 所以我们这里要的文件名是/dev/test
7.4、编程实践

//创建设备文件
test_class = class_create(THIS_MODULE, "ljj_class");  //创建对象
if (IS_ERR(test_class))
	goto error;
device_create(test_class, NULL, dev_from, NULL, "test");  //创建设备文件/dev/test

//销毁设备文件/dev/test
device_destroy(test_class, dev_from);
class_destroy(test_class);


8.设备类相关代码分析1

8.1、sys文件系统简介
(1)sys文件系统的设计思想
(2)设备类的概念
(3)/sys/class/xxx/中的文件的作用
8.2、
(1)
class_create
    __class_create
        __class_register
            kset_register
                kobject_uevent
    
(2)
device_create
    device_create_vargs
        kobject_set_name_vargs
        device_register
            device_add
                kobject_add
                    device_create_file
                    device_create_sys_dev_entry
                    devtmpfs_create_node
                    device_add_class_symlinks
                    device_add_attrs
                    device_pm_add
                    kobject_uevent
                    

9.设备类相关代码分析2


10.静态映射表建立过程分析

10.1、建立映射表的三个关键部分
(1)映射表具体物理地址和虚拟地址的值相关的宏定义
(2)映射表建立函数。该函数负责由(1)中的映射表来建立linux内核的页表映射关系。
在kernel/arch/arm/mach-s5pv210/mach-smdkc110.c中的smdkc110_map_io函数
smdkc110_map_io
    s5p_init_io
        iotable_init
结论:经过分析,真正的内核移植时给定的静态映射表在arch/arm/plat-s5p/cpu.c中的s5p_iodesc,本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被iotable_init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。

(3)开机时调用映射表建立函数
问题:开机时(kernel启动时)smdkc110_map_io怎么被调用的?
start_kernel
    setup_arch
        paging_init
            devicemaps_init
            
if (mdesc->map_io)
        mdesc->map_io();
            

11.动态映射结构体方式操作寄存器

11.1、问题描述
(1)仿效真实驱动中,用结构体封装的方式来进行单次多寄存器的地址映射。来代替我们5.2.17节中讲的多次映射。
11.2、实践编码
11.3、分析和总结


12.内核提供的读写寄存器接口

12.1、前面访问寄存器的方式
优点:方便快捷
缺点:可以移植性差,在不同的CPU架构中,它的直接寻址会不一样,采用writel就会避免这样的情况。
12.2、内核提供的寄存器读写接口
(1)writel和readl

writel(v,  c)   :v为要写的值,c为要写的地址
(2)iowrite32和ioread32

#define readb(c)		({ u8  __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c)		({ u16 __v = readw_relaxed(c); __iormb(); __v; })
#define readl(c)		({ u32 __v = readl_relaxed(c); __iormb(); __v; })

#define writeb(v,c)		({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c)		({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c)		({ __iowmb(); writel_relaxed(v,c); })

12.3、代码实践
12.4、分析和总结

猜你喜欢

转载自blog.csdn.net/qq_40732350/article/details/82937776
今日推荐