1_1驱动三要素:
入口函数,出口函数,许可证
1_2printk的用法:
和printf基本相同,但是多一个消息等级的参数,可不填,但是消息等级比终端低的话打印的内容将不会显示
驱动安装方法:sudo insmod xxx.ko
1_3 传参:
- Standard types are:
- byte, short, ushort, int, uint, long, ulong
- char *p: a character pointer
- bool: a bool, values 0/1, y/n, Y/N.
- invbool: the above, only sense-reversed (N = true).
#define module_param(name, type, perm)
module_param_named(name, name, type, perm)
功能: 获取加载驱动时给内核传递的参数
参数: name 变量的名字 内核会以这个名字创建一个文件
type 变量的类型 byte类型在传输char时使用 在传参时要以ascii码传递
perm 文件权限 最大0664
#define module_param_array(name, type, nump, perm)
module_param_array_named(name, name, type, nump, perm)
功能:获取加载驱动时给内核传递的数组
参数:name 变量名
type 类型
nump 接收数组的长度 注意需要传递的是一个指针
perm 权限
#define MODULE_PARM_DESC(_parm, desc)
__MODULE_INFO(parm, _parm, #_parm ":" desc)
功能:对变量进行描述
参数: _parm 变量名
desc 描述信息的字串
内核模块传参方式:
对应的变量存放在/sys/module/xxx/parameters目录下,其中xxx为对应模块名字
echo 999 > led_level
sudo insmod demo.ko led_level=255 arr=11,22,33,44,55
//从C99标准开始才支持如下这样定义变量i 内核模块编译通常是C89或C90不支持这样声明变量
for(int i = 0;i < len;i++)
{
printk("arr[%d] = %d \n",i,arr[i]);
}
2_1 字符设备驱动的开发:
设备号:
在linux中 主设备号 + 子设备号 组成的一个32位无符号整数
高12位 低20位
主设备号 代表哪一类设备
子设备号 代表哪一个设备
字符设备相关API
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
功能:注册字符设备
参数:major 主设备号 major>0 静态指定设备号 (要注意设备号是否被占用)
major=0 系统分配设备号
name 设备名字
fops 文件操作结构体
int (*open) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*release) (struct inode *, struct file *);
返回值:major > 0 成功返回 0 失败返回错误码
major = 0 成功返回设备号 , 失败返回错误码
static inline void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备
参数:major 主设备号
name 设备名
返回值:无
//驱动作者信息描述函数
MODULE_AUTHOR("MILLENNIUM");
查看设备号命令
cat /proc/devices
生成设备节点的命令
sudo mknod /dev/设备名 c 主设备号 0
注意:要修改生成节点的文件属性 sudo chmod 777 /dev/设备名
2_5内核空间和用户空间的数据拷贝 :
linux驱动在系统中的位置
应用层(app) 逻辑 0-3G(虚拟内存)
open read write close
/dev/chardev 设备节点和设备号唯一绑定
————————————————————————————————
内核层(kernel) 机制 3-4G(虚拟内存)
向上提供接口 向下控制硬件
driver
设备号和驱动唯一绑定
my_open
my_read
my_write
my_close
————————————————————————————————
硬件层(hal)
led lcd mouse …
内核空间数据和用户空间数据拷贝api
static inline int copy_to_user(void __user volatile *to, const void *from, unsigned long n)
功能:拷贝数据到用户空间
参数:
to 用户空间地址
from 内核空间地址
n 数据大小(字节)
返回值: 成功返回0 失败返回未拷贝的字节数
static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n)
功能:拷贝数据到内核空间
参数:
to 内核空间地址
from 用户空间地址
n 数据大小(字节)
返回值: 成功返回0 失败返回未拷贝的字节数
2_6点灯 :
rk3566 有5个gpio控制器 官方称为bank
每个控制器下 控制32个引脚
32个引脚 被分为4组 A B C D
0-7 8-15 16-23 24-31
基地址
PMU_GRF 0xFDC20000 //gpio0的复用功能挂载在这
SYS_GRF 0xFDC60000 //其他的复用功能挂载在这
PMU_GRF_GPIO3B_IOMUX_H 0x004C //高位寄存器操控的是gpio3 b4-7的复用功能
复用功能寄存器:
基地址 + 偏移地址 = 0xFDC60000 + 0x004C = 0xFDC6004C
0xFDC6004C 0-2位写0(gpio功能) 16-18 写1(写使能)
对寄存器进行操作时一定要先将值读出来 将此值或上我们要写得值 对gpio3_b4引脚初始化复用功能位gpio 0x00004440 | 0x70000
读四个字节的数据
root@linaro-alip:/# io -r -4 0xFDC6004C
fdc6004c: 00004440
写四个字节的数据
root@linaro-alip:/# io -w -4 0xFDC6004C 0x00074440
gpio控制器基地址
GPIO3 0xFE760000
方向寄存器偏移地址
GPIO_SWPORT_DDR_L 0x0008//低位寄存器操控着gpio3的0-15位io,也就是 A B两个组的io
GPIO_SWPORT_DDR_H 0x000C//高位寄存器操控着gpio3的16-31位io,也就是C D两个组的io
A B C D
0-7 8-15 16-23 24-31
b0 b1 b2 b3 b4 b5 b6 b7
8 /9 /10 /11/12 /13 /14 /15
0xFE760000 + 0x0008 = 0xFE760008 12位写1(设置为输出) 28位写1(写使能) 0x00000000 |= 0x10001000
读四个字节的数据
root@linaro-alip:/# io -r -4 0xFE760008
fe760008: 00000000
写四个字节的数据
root@linaro-alip:/# io -w -4 0xFE760008 0x10001000
数据寄存器
GPIO_SWPORT_DR_L 0x0000
0xFE760000 + 0x0000 = 0xFE760000 12位写1(设置输出高电平) 28位写1(写使能) 0x00000000 |= 0x10001000
读四个字节的数据
root@linaro-alip:/# io -r -4 0xFE760000
fe760000: 00000000
写四个字节的数据
root@linaro-alip:/# io -w -4 0xFE760000 0x10001000
寄存器地址 都是物理地址 驱动在内核层 运行在3-4g的虚拟内存中 是不可以直接操作物理地址的 我们要将物理地址映射到虚拟内存中 才可以进行操作
void * ioremap(unsigned long offset, unsigned long size)
功能:将物理内存映射到虚拟内存中
参数:
offset 偏移地址
size 大小 字节
返回值:成返回虚拟地址
失败返回NULL
void iounmap(void __iomem *addr);
功能:注销映射
参数:虚拟地址
返回值:无
0xFDC60000 + 0x0008 =0xFDC60008 gpio3_b4 要对12位写1(设置为输出) 28位写1 (写使能) 0x00001000 |=
root@linaro-alip:/# io -r -4 0xFDC60008
fdc60008: 00001000
数据寄存器:
GPIO_SWPORT_DR_L 0x0000
0xFDC60000 + 0x0000 = 0xFDC60000 gpio3_b4 要对12位写1(设置输出高电平) 28位写1 (写使能)0x00001100 |= 0x10001000
1000 1100
2_7自动创建设备节点
mdev 只是一个应用程序 udev轻量化版本 负责帮助驱动创建设备节点
user
hotplug 回去检测 /sys/class/这个目录下有没有变化 也就对应的是 有没有硬件卸载或者安装
当检测到硬件变化 会通知mdev/udev mdev会通过libsysfs读取sys文件系统 从而去拿到硬件信息
mdev 会fork一个新的进程去创建设备节点
ker
驱动 提交目录信息
(/sys/class/[name])
设备信息 设备号
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
功能:提交目录信息
参数:owner THIS_MODULE (struct module 结构体的首地址 这个结构体存放了驱动的出口入口)
name 目录名/sys/class/
返回值:成功返回结构体首地址
失败返回错误码指针
IS_ERR(cls); //判断是否为错误码指针
PTR_ERR(cls);//将错误码指针转换成错误码
struct class * cls;//创建class类型结构体,用来承载class_create()函数返回的内容
void class_destroy(struct class *cls);
功能:注销目录信息
参数:cls 结构体首地址
返回值:无
struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...);
功能:提交设备信息
参数:cls 结构体首地址
parent NULL
devt 设备号
MKDEV(ma,mi)//计算设备号
MAJOR(dev)//主设备号
MINOR(dev)//子设备号
drvdata NULL
fmt 节点名 对应/dev/节点名
返回值:成功返回结构体首地址
失败返回错误码指针
IS_ERR(cls); //判断是否为错误码指针
PTR_ERR(cls);//将错误码指针转换成错误码
struct device *dev;//创建device类型结构体,用来承载device_create()函数返回的内容
void device_destroy(struct class *cls, dev_t devt);
功能:注销设备信息
参数:cls 结构体首地址
devt 设备号
返回值:无
3_1设备树:
1.什么是设备树
描述硬件信息的树状数据结构, 加载后的本质是链表。
在系统引导阶段,会对硬件进行初始化,这个时候,设备数的硬件信息会传递给操作系统。
设备树存放在.dts和.dtsi中,通过DTC工具编译可以生成DTB二进制文件。
设备树官网
https://elinux.org/Device_Tree_Usage
2.节点名的格式
每个节点的名称都必须为 [@ ]
name: 是一个简单的 ASCII 字符串,长度最多为 31 个字符。通常,节点是根据它所代表的设备类型来命名的。即。3com 以太网适配器的节点将使用名称 ethernet ,而不是 3com509 。
unit-address:如果节点描述具有地址的设备,则包含 unit-address。通常,单元地址是用于访问设备的主地址,并列在节点的 reg 属性中。
3.节点中的基本数据格式
/dts-v1/; //设备树的版本
/ {//根节点
node1 {//根节点的子节点
a-string-property = "A string";
a-string-list-property = "first string", "second string";
// hex is implied in byte arrays. no '0x' prefix is required
a-byte-data-property = [01 23 34 56];
child-node1 { //node1父节点的子节点
first-child-property; //空属性 起标识作用
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {//node2父节点的子节点
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};
单个根节点:“ / ”
几个子节点:“ node1 ”和“ node2 ”
node1 的几个子项:“ child-node1 ”和“ child-node2 ”
一堆属性散落在树上。
属性是简单的键值对,其中值可以为空,也可以包含任意字节流。虽然数据类型未编码到数据结构中,但有一些基本数据表示形式可以在设备树源文件中表示。
文本字符串(以 null 结尾)用双引号表示:
string-property = “a string”;
用尖括号分隔的 32 位无符号整数:
cell-property = <0xbeef 123 0xabcd1234>;
单字节数据用方括号分隔:
binary-property = [01 23 45 67];// hex is implied in byte arrays. no ‘0x’ prefix is required
可以使用逗号将不同表示的数据连接在一起:
mixed-property = “a string”, [0x01 0x23 0x45 0x67], <0x12345678>;
逗号还用于创建字符串列表:
string-list = “red fish”, “blue fish”;
kernel 编译
linux$ ./build.sh kernel
下载到板子
1.下载配置文件
2.导入配置文件 到烧录工具
3.选择boot.img文件
4.板子进入到loader模式
5.勾选 强制写 并 点击执行
验证
进入/proc/device-tree/ 查找我们的节点名