嵌入式驱动开发学习笔记

5 篇文章 0 订阅
5 篇文章 0 订阅

day1:驱动 模块 模块传参 符号导出

1、驱动
定义:驱动硬件动起来的程序
种类:裸机驱动:需要分析–>查看原理图–>查看芯片手册–>code
系统驱动:需要分析–>查看原理图–>查看芯片手册–>设备树–>code–>安装code到内核中
裸机开发&系统开发的优缺点?
裸机开发:成本低,运行效率高,安全性低,单任务
系统开发:成本高,运行效率低,安全性高,多任务
应用程序和驱动程序的区别?
应用程序 驱动程序
1、加载方式 主动加载 被动加载
2、运行空间 用户空间 kernel空间
3、执行空间 低 高
4、影响力 局部 全局
5、函数来源 自定义/库/系统 内核函数/自定义函数

内核函数:纯内核函数、系统调用函数
2、驱动(驱动模块)
模块:能够单独命名,独立完成一定功能的程序语句的聚合(程序代码和数据结构)
驱动模块:能够单独命名并且独立完成特定外设功能驱动的程序语句的集合
注:一个驱动模块就是一个完整的外设驱动程序。驱动程序安装到操作系统内核,当该驱动程序对应的外设要工作时,该驱动模块被调用。
2.1如何写一个驱动模块?
1)模块初始化函数 int 函数名1(void)
2)模块清除函数 int 函数名2(void)
3)模块加载函数 ,module_init (函数1)–>sudo insmod helllo 时调用
4)模块卸载函数 module_exit(函数名2)
5)声明该驱动遵循GPL–>MODULE_LICENSE(“GPL”);遵循GPL协议

#include
module_init(参数) 模块加载函数(模块初始化函数)
module_exit(参数) 模块卸载函数(模块清除函数)
MODULE_LICENSE(“GPL”);
MODULE_DESCRIPTION(“驱动描述”);
MODULE_AUTHOR("署名);
MODULE_ALIAS(“别名”);

2.2如何编译驱动模块?
需要编写一个makefile实现编译
代码:

3.3将驱动模块安装到linux内核中
查看内核中的驱动模块:lsmod
安装驱动模块到内核中:sudo insmod 驱动模块名称.ko
打印内核缓冲区后10行打印信息:dmesg | tail
打印内核缓冲区后20行打印信息:dmesg |tail -20
查看驱动信息描述:modinfo 驱动模块名称.ko
查看linux内核版本:uname -r
卸载驱动模块/移除驱动模块:sudo rmmod 驱动模块名称.ko

3.4将驱动代码分成两个文件
hello.c–>hello_init.c&hello_exit.c–>hello_init.o

3、模块传参
驱动程序:
传参:安装驱动模块时传:sudo insmod hello.ko gtest=100
接收传参:1)用全局变量
2)声明该全局变量接收传参
如何声明一个全局变量可以接收shell终端的参数传递?用内核函数声明?
module_param(name,type,perm)
name:参数名,即是内部参数名,又是外部参数名
type:参数的数类型
perm:访问权限,0644,0表示该参数在文件系统中不可见测试
测试步骤:
1 sudo insmod hello.ko gtest=100
2 dmesg | tail–>查看gtest的值是否是100?
3 cd /sys/module/hello/paramters //进入这个目录
ls -l -->gtest 100
cat gtest //查看gtest的值
sudo chmod 777 gtest //修改权限
echo 200 > gtest // 修改gtest的值
dmesg
module_param_string(name,string,len,perm)
name:外部参数名
string:内部参数
len:长度
perm:访问权限,0表示该参数在文件系统中不可见

module_param_array(name.type,nump,perm)
name:
type:
nump:
perm:

4、符号导出
1 什么是符号导出?
在内核和驱动中只要是指全局变量和函数(全局函数)
2 为什么要导出符号?

linux内核是以模块化形式管理内核代码的,内核中的每个模块之间是相互独立的,也就是说A模块中的全局变量和函数,B模块是无法访问的,若B模块想要使用A模块已有的符号,那么必须将A模块中的符号做导出,导出的模块符号表中,然后B模块才能使用符号表里的符号。
3 如何做符号导出?
linux内核给我们提供了两个宏:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);
EXPORT_SYMBOL_GPL(gtest);
EXPORT_SYMBOL(Func);
例如:模块A–>符号导出模块
模块B–>使用导出符号的模块
make成功后会生成一个存放符号本地符号表的文件,这个表中存放的就是代码里导出的符号
Module.symvers:
addr 符号名 模块名(绝对路径) 导出符号的宏(导出符号使用的宏)

模块B–>使用导出符号的模块
使用条件:1 将A模块导出符号表拷贝到B模块中
2 在模块的代码里外部声明使用哪个符号,才能使
测试步骤:
sudo insmod hello.ko -->
sudo insmod word.ko
demesg |tail -->
lsmod
sudo rmmod word.ko
sudo rmmod hello.ko
linux内核提供的两类符号表:
1)用户自定义模块导出的符号表,称为本地符号表
2)全局符号表 /proc/kallsyms
查找全局符号:sudo cat /proc/kallsyms |grep printk
c15cd833 T printk
函数指针=0xc15cd833;
int printf(const char *format,…)

day2:字符设备驱动框架用户
1 字符设备驱动框架
1.1 字符设备
定义:只能以一个字节一个字节的方式读写的设备,不能随机的读取设备中的某一段数据,读取数据需要按照先后顺序,(字符设备是面向字节流的)
常见的字符设备:鼠标 键盘 串口 控制台
块设备:可以从设备的任意位置读取一定长度数据的设备
常见的块设备有:磁盘 光盘 U盘 SD卡 TF卡…

1.2 字符设备驱动框架

init流程–>HelloModule
{
1 申请设备号(静态申请和动态申请)
2 创建一个字符设备
3 初始化字符设备
4 将设备号和字符设备关联起来
}

exit流程–>HelloExit
{
1 删除字符设备
2 删除设备号
}
1.2.1 设备号
定义:设备号是设备在内核中的身份和标识,是内核区分不同设备的唯一信息,设备号是由主设备号和次设备号构成,主设备号是标识一类设备,次设备号表示该类设备中的一个设备
设备号:是一个32bit的无符号整数,高12bit存放主设备号,低20bit存放次设备号

<linux/kdev_t.h> --> 头文件
#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))
1.2.2 申请设备号
申请设备号有两种啊方式:静态申请&动态申请
静态申请设备号:

int register_chrdev_region(dev_t from, unsigned count, const char *name)
作用:静态申请设备号
from:设备号(由主次设备号构成)
count:子设备个数
*name:设备名称
返回值:0成功; 非0 失败
卸载设备号:

void unregister_chrdev_region(dev_t from, unsigned count)
作用:从内核中移除设备号
from:设备号(由主次设备号构成)
count:子设备个数
动态申请设备号:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
作用:动态申请设备号
*dev:指向设备号的指针
baseminor:子设备的第一个编号
count:子设备个数
*name:设备名称
返回值:0表示成功;非0表示失败

1.2.3 创建字符设备

struct cdev *cdev_alloc(void);
作用:为字符设备创建一片字符空间,用于存放字符设备
返回值:是指向创建成功的字符设备的指针
在Linux内核中用struct cdev来描述一个字符设备

struct cdev{
struct kobject kobj; -->内嵌的内核对象
struct module *owner; -->该字符设备所在的内核模块的对象指针
const struct file_operations *ops; -->指向操作字符设备的方法集
struct list_head list; -->用来将已向内核注册的所有字符设备形成链表
dev_t dev; -->设备号(主设备号+次设备号)
unsigned int count; -->隶属于同一个主设备号的此设备个数
}

void cdev_del(struct cdev *p)
作用:删除字符设备
*p:指向字符设备的指针
1.2.4初始化字符设备–>绑定驱动方法

void cdev_init(struct cdev cdev,const struct file_operations fops)
作用:初始化字符设备
*cdev:指向字符设备的指针
*fops:指向操作字符设备的函数集的指针
1.2.5将字符设备和设备号关联

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
作用:将字符设备和设备号关联,并将字符设备添加到内核中
*p:指向字符设备的指针
dev:设备号
count:子设备的个数
返回值:成功为0 失败非0
测试步骤:

1 sudo insmod hello.ko
2 dmesg |tail -->250 0
3 cat /proc/devices–>查看设备号 250 0
4 sudo mknod /dev/haha0 c 250 0
5 ls -l /dev/haha*—>查看创建字符设备文件
6 sudo ./test–>open hahao ok!
7 dmesg |tail–>helloopen/helloClose
8 sudo rmmod hello.ko
9 sudo rm /dev/haha0
区分字符设备驱动框架中使用的三个结构体:
struct file:代表内核中一个打开的文件。系统中每个打开的文件在内核中都有一个关联的struct file。
struct inode:用来记录文件在物理上的信息。它和打开文件struct file结构不同,一个文件可以对应多个struct file,但是只有一个struct inode.
struct file_operations:是内核给用户提供的驱动接口函数集,用户可以定义
函数集中的任何驱动方法。(对于不支持的一般不写)

字符设备文件–》250 0<–字符设备(helloopen/helloclose)
mice----------->鼠标
sudo cat mice
2 实现用户空间和内核空间
用户代码对字符设备的任何操作,最终都要落实到设备对应的底层操作函数上
内核空间–》用户空间 read–>HelloRead
用户空间–》内核空间 write–》HelloWrite
应用层 :fd=open(“/dev/haha0”)
read(fd,)
close(fd)
驱动层:
增加HelloRead

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
char g_buf[]=“hellotest----”;
ssize_t HelloRead(struct file *pFile,char __user *buf,size_t count,loff_t *p)
{
copy_to_user(buf,g_buf,count);
}

        unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

作用:从内核空间向用户空间拷贝数据
*to:用户空间指针
*from:内核空间指针(数据源)
n:拷贝的字节数
copy_from_user

测试步骤:

1 sudo insmod hello.ko
2 dmesg |tail
3 sudo mknod /dev/haha0 c 250 0
4 ls -l /dev/hah*
5 sudo ./test–>查看是否读到数据?
6 sudo rmmod hello
7 sudo rm /dev/haha0

空间和字符空间的数据拷贝
day3:自动创建设备文件ioctl 驱动互斥
day4:阻塞操作非阻塞操作poll机制 异步io
day5:mmap平台设备驱动框架 led
day6:ioremap中断中断下半部
day7:块设备驱动
day8:网络设备驱动

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值