泰山派驱动笔记

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 描述信息的字串

内核模块传参方式

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/ 查找我们的节点名

  • 29
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值