字符设备驱动基础笔记


学习朱有鹏朱老师课程总结


一:准备工作:
<1>开发板(可以正常运行linux)
<2>内核源码树----可以编译配置的源码
<3>nfs挂载的rootfs
二:开发步骤
<1>驱动源码编写、Makefile编写、编译
<2>insmod装载模块、测试、rmmod卸载模块
三:模块操作指令(模块包括驱动,驱动属于模块)
<1>lsmod (list modulels)显示当前系统安装的模块(包括驱动模块)
<2>insmod (install module)安装模块 insmod xx.ko(xx.ko驱动模块kernel operate)
<3>modinfo (module information
file)打印模块信息 modinfo xx.ko
|filename 带全路径
|license 许可证GPL
|srcversion 版本号
|depends 所依赖的模块
|*↓vermagic 版本魔数
<4>rmmod (remove module==rm)卸载模块 rmmod xx(/不加ko的后缀/)
*总结: insmod命令----module_init(函数)宏
remod 命令----module_exit(函数)宏
四:驱动模块和内核的版本匹配
<1>modinfo查看模块的vermagic版本魔数
<2>内核中也有一个版本魔数
<3>*确保驱动模块的版本魔数和内核的版本魔数一致,才可以安装成功.
<4>*如何解决:用同一个内核源码树去编译内核和驱动模块->(同出一门)
五:内核驱动源码讲解
<1>添加模块描述信息(MODULE_xxx)[通过modinfo查看]
|*MODULE_LICENSE --(license) 模块许可证:(一般GPL)、(Dual BSD/GPL)
| MODULE_AUTHOR --(author) 作者
| MODULE_DESCRIPTION–(description) 描述模块信息
| MODULE_ALIAS --(alias) 模块别名
<2>修饰函数的关键字和宏
|static 善于用 static 修饰本文件私有函数
|__init __exit (内核中定义的宏)宏在 #include <linux/init.h> 中定义
|__init 将被修饰的函数链接到.init.text段中去(原本在.text代码段)
内核启动时加载.init.text这个段中的安装函数
安装结束后删除这个段以节省内存资源
|exit:
<3>printk详解
|printf和printk区别:
printf:是C库函数内的函数,属于应用层.不可以在内核源码中使用
printk:是内核封装的函数,只能在内核源码中使用printk(宏"
"); *没有","
|printk的宏
>KERN_EMERG “<0>”(kern_emerg) 系统崩溃
>KERN_ALERT “<1>”(kern_alert) 立即采取行动
>KERN_CRIT “<2>”(kern_crit) 临界状态,通常涉及严重的硬件或软件操作失败。
>KERN_ERR “<3>”(kern_err) 错误
>KERN_WARING"<4>"(kern_waring) 警告
>KERN_NOTICE"<5>"(kern_notice) 通知,一般是安全相关的信息提示
>KERN_INFO “<6>”(kern_info) 正常输出信息
>KERN_DEBUG “<7>”(kern_debug) 调试
|内核的命令行有一个级别限定,高于此级别的信息可以被打印出来(数字小)
查看命令行级别cat /proc/sys/kernel/printk查看命令行级别
修改命令行级别echo (级别数字)/proc/sys/kernel/printk
*ubunt中都不可以打印(需要dmesg查看)
<4>头文件
|#include <linux/module.h> //module_init module_exit
|#include <linux/init.h> //__init __exit
|驱动源代码中的头文件和原来应用编程中的头文件的区别
>驱动中的头文件:内核源码目录下的include中的头文件
>应用中的头文件:应用程序的编译器中的(gcc 的头文件路径在/usr/include和操作系统无关)

		/*************module_test.c    第一个简单的驱动源码*************/
			#include <linux/module.h>		// module_init  module_exit
			#include <linux/init.h>			// __init   __exit
			static int __init chrdev_init(void)
			{
				printk(KERN_INFO "chrdev_init helloworld init\n");
				//printk("<7>" "chrdev_init helloworld init\n");
				//printk("<7> chrdev_init helloworld init\n");
				return 0;
			}
			static void __exit chrdev_exit(void)
			{
				printk(KERN_INFO "chrdev_exit helloworld exit\n");
			}
			module_init(chrdev_init);	//module_init是一个宏
										//当insmod执行时则执行这个宏里面的函数
										//所以驱动的安装代码需要我们自己写·
			module_exit(chrdev_exit);
			MODULE_LICENSE("GPL");				
			MODULE_AUTHOR("aston");				
			MODULE_DESCRIPTION("module test");	
			MODULE_ALIAS("alias xxx");			
			------------------------------------------------------
			*ubunt中安装驱动模块,打印信息看不到:使用dmesg命令查看
			------------------------------------------------------
		/********************************************************************/

六:内核驱动Makefile讲解
<1>KERN_DIR变量 :用来编译这个模块的内核源码树的目录
<2>obj-m +=___ :将___编译成模块
|-m:编译成模块
|-y:将___链接进zlmage内核模块中
<3>make -C $(KERN_DIR) M=pwd modules [索引,并不是实际的编译驱动源码,利用内核源码树]
|make modules 编译模块[内核的Makefile中有一个目标叫modules]
|-C $(KERN_DIR) 指定利用内核源码树来编译模块
|M=pwd 编译完后生成的目标放到当前目录下
<4>make -C $(KERN_DIR) M=pwd modules clean 【同上】

		/*************************相对应的Makefile***************************/
			#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这2个
			#KERN_VER = $(shell uname -r)
			#KERN_DIR = /lib/modules/$(KERN_VER)/build	   //ubunt内集成的内核源码树		
															//用于后续驱动的安装
			# 开发板的linux内核的源码树目录
			KERN_DIR = /root/driver/kernel        //内核源码树的目录
			obj-m	+= module_test.o
			all:
				make -C $(KERN_DIR) M=`pwd` modules 
			cp:
				cp *.ko /root/porting_x210/rootfs/rootfs/driver_test
			.PHONY: clean	
			clean:
				make -C $(KERN_DIR) M=`pwd` modules clean
		/********************************了解********************************/
		单模块(多个文件)编程:
			obj-m += dri.o     #驱动目标(原先不存在dri.c)
			dri-objs =led.o fun.o fun1.o fun2.o
		---->最终生成dri.ko这个dri.ko依赖 led.o fun.o fun1.o fun2.o(led.c里面是模块)
		/********************************了解********************************/
		模块之间相互调用(一个模块相当于一个独立的进程,一个项目文件)
			>这样  led.c  和 key.c之间的函数不能用extern相互调用
			>解决办法:
				>EXPORT_SYMBOL(函数名) 本文件中声明本函数(*先加载)
				>在其他模块 声明就可以用了
		/********************************************************************/
		模块传参:		(可以在insmod的时候传参[变量名=val])
			>moudle_param(变量名,变量类型,0644)			传参声明
			
		/********************************************************************/

七:开发板-驱动
<1>设置网络ping通
<2>设置bootcmd
|set bootcmd ‘tftp 0x30008000 zImage;bootm 0x30008000’
<3>用nfs挂载根文件系统
|配置内核支持nfs
|bootargs
>root=/dev/nfs nfsroot=192.168.31.114:/root/rootfs/rootfs ip=192.168.31.10:192.168.31.114:192.168.31.1:255.255.255.0::eth0:off init=linuxrc console=ttySAC2,115200
<4>Freeing init memory: 540K 释放启动内存

-------------------------------------??[module]—[驱动]??-----------------------------------------
module空壳—>file_operations驱动空壳—>mknod设备文件—>api操作设备文件

八:字符设备驱动工作原理
<1>表层
|应用层->API->驱动->硬件
|API:open、close、read、write等 [表层的]
|驱动源码中提供真正的open等指向的函数体 [驱动层]
<2>file_operations结构体
|结构体中定义了一些(open、read)函数指针 -> 驱动中
|每一个驱动都需要一个file_operations结构体
|注册时:设备驱动向内核提供这个结构体变量
<3>注册
|驱动向内核注册
|注册函数:*register_chrdev
static inline int register_chrdev(
unsigned int major, //主设备号(硬件设备、驱动号)1~255
const char *name, //设备驱动的名字
const struct file_operations *fops //file_operations
)
>头文件#include <linux/fs.h>
>作用:驱动向内核注册自己的file_operations
>返回值: 成功返回0,失败返回负数
if magor=0 返回一个成功的设备号
*函数在头文件中被两个文件包含就会重复定义,所以加了 inline
|结构体数组
>*一个萝卜一个坑
>*一个下标一个设备号
|*cat /proc/devices 查看已经注册的驱动设备号
|*内核自动分配主设备号[major 传 0]-----内核成功分配则返回一个设备号
定义一个全局变量接收并在注销中使用
—proc 虚拟文件系统(内核用数据结构虚拟出来的文件,不存在硬盘上)—
九:向module中添加空壳驱动
<1>移植module
<2>添加头文件并定义file_operations且初始化
|[.owner = THIS_MODULE]结构中第一个参数 (固定化)
|.release (= .close)
<3>*注册在__init修饰的函数中(insmod)----register_chrdev
<4>*注销在__exit修饰的函数中( rmmod)----unregister_chrdev (参数只有前两个)
十:设备文件
<1>应用中的API通过[设备文件]调用驱动工作。
<2>主次设备号 = 主设备号(类型) + 次设备号(编号)
LED led0、led1、led2 (默认次设备号为 0)
<3>创建设备文件
|mknod: mknod /dev/xxx c 主设备号 次设备号
>c 字符设备文件
|通过设备号就和file_operation结构体关联起来了。在应用层操作open/ close就可以调用file_opereation结构体中函数指针所指向的那个函数了
<4>自动创建设备文件(udev)
|在init.d/rcS中开启sys
|udev/mdev(嵌入式) 是一个应用程序
|内核驱动和应用层udev之间有一套信息传递机制(netlink协议)
|内核的中间人作用使insmod 和 udev关联起来
|内核的创建接口函数
>class_create + device_create [自动创建insmod]
>class_destroy+ device_destroy [自动删除rmmod ]
十一:填充file_operations
<1>open、release
<2>read、write
|驱动中添加、应用层API使用
|将buff读写到内核中
|应用层和驱动层的地址处于不同的时空,不能完成地址间的简单数据交换,需要使用函数。
不能使用strcpy将write中的buff直接拷贝到内核空间中
>头文件 #include <asm/uaccess.h>
>static inline unsigned long __must_check copy_from_user(
void *to, //一般定义的比应用层大
const void __user *from,
unsigned long n //一般为write中的参数
)
//返回值: 成功返回0
// 返回尚未成功复制的字节数
>static inline unsigned long __must_check copy_to_user(
void __user *to,
const void *from,
unsigned long n
)

<3>在驱动中(write)中添加操作硬件
	|和裸机中的[不同]
		>*寄存器地址的不同						裸机	--->	物理地址
												内核	--->	虚拟地址
		>*编程方法不同							裸机 	--->	函数指针直接操作寄存器
												内核	--->	封装好的io读写函数(可移植性)											

十二:内存的虚拟地址映射方法(2种)
<1>mmu一开全部都是虚拟地址,mmu一关全部都是物理地址
<2>动态和静态
|静态–先修路在用 动态–用的时候修路
|静态
>内核移植时:使用硬编码写死
>如果更改必须重新编译内核
>内核启动时建立静态映射表,关机销毁
|动态
>根据需要,随时->建立、使用、销毁
>短期临时、反复
<3>动态和静态可以同时使用 [一个物理地址可以对应几个虚拟地址]
<4>优缺点
|静态:
>优:效率高
>缺:始终占用虚拟地址空间。其他(动态)不能再使用
|动态
>优:按需使用
>缺:效率低(修路废世间)
每次使用都需要使用 [函数] 去建立映射&销毁映射
十三:静态映射方法
<1>静态映射需要注意
|不同内核版本中静态映射表的位置、文件名可能不同
|不同Soc的静态映射表位置、文件名可能不同
|映射表就是头文件中的宏定义
<2>三星版本内核中的静态映射表[虚拟地址->真正的物理地址]
|虚拟地址基地址定义在:arch/arm/plat-samsung/include/plat/map-base.h
>整个虚拟空间的基地址 #define S3C_ADDR_BASE (0xFD000000)
>各个模块的基地址由这个基地址加上偏移得到
|[模块主映射表]位于:arch/arm/plat-s5p/include/plat/map-s5p.h
>各模块的基地址
>[基地址]+地址偏移
|GPIO相关的主映射表位于:arch/arm/ lude/mach/regs-gpio.h
>GPIO内部各个GPIO_的基地址
|GPIO的具体寄存器定义位于:arch/arm/mach-s5pv210/include/mach/gpio-bank.h
>GPIO_内部各个寄存器的地址
[GPIO静态虚拟映射]
* *
* ??–>UART1 ??–>GPIOA *
* ?? ?? *
* [VA]虚拟空间基地址–>-->GPIO-------->–>GPIOB-------->(各个寄存器的地址) *
* ?? ?? *
* ??–>VIC ??–>GPIO_(GPIO各个端口的基地址) *
* ??–>…(各个模块的基地址) *
*********************************************************************************
十四:动态映射方法
<1>建立
|request_mem_region[内核申请]
|ioremap[真正的建立]
>第一个参数:物理地址
>第二个参数:申请的长度
<2>销毁
|iounmap
>解除映射传递虚拟地址
|release_mem_region
<3>动态结构体
十五:内核提供的读写寄存器的接口
<1>以前操作寄存器
|
((unsigned int *)p) = 0x22221111; p是一个 int 地址
<2>以上只能在统一编制下用,不能用在独立编制(可移植性差)
<3>专用读写寄存器接口:
|不同架构可移植性高
>readl© [32bit读]
>writel(v,c) [32bit写]
>ioread32§
>iowrite32(v,p) [v val]

		*c表示地址,如果使用动态申请的虚拟地址配合结构体直接用结构体操作即可
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值