文章目录
本人学习完韦老师的视频,因而来备考巩固,写以笔记记之。
韦老师的课比较难,第一遍不晓得在说哪些,并且坚持看完一遍,再来备考,基本上就水到渠成了。
看完视频备考的朋友观看最佳!
基于IMX6ULL-PRO
参考视频Linux快速入门到精通视频
参考资料:01_嵌入式Linux应用开发完全指南V5.1_IMX6ULL_Pro开发板.pdf
2024.4.18更新借助图解方法探求内核函数调用
代码上传到git网站驱动代码
一、Hello驱动编程1-1APP打开的文件在内核中怎样表示
APP打开文件时,得到一个整数fd,被称为文件句柄。对于APP的每一个文件句柄,在内核上面都有一个structfile结构体(include/linux/fs.h)与之对应。
open打开文件时,传入的flags、mode等参数会被记录在内核中对应的structfile结构体里f_flags、f_mode。
去读写文件时,文件的当前偏斜地址也会保存在f_pos成员里。
int open(const char *pathname, int flags, mode_t mode);
1-2打开字符设备节点时,内核中也有对应的structfile
structfile_operations*f_op,由驱动程序提供。structfile_operations结构体是Linux内核中用于描述文件操作函数集合的结构体。它包含了许多函数表针成员,每位成员对应于不同的文件操作。
1-3Hello驱动程序
怎么编撰驱动程序步骤
1、确定主设备号,也可让内核分配
2、定义自己的file_operations结构体
3、实现drv_open()、drv_read()、drv_write()等函数,写入file_operations结体
4、file_operations结构体告诉内核:register_chrdev()
5、谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,才会去调用这个入口函数
6、有入口函数就应当有出口函数:卸载驱动程序时linux嵌入式开发基础,出口函数调用unregister_chrdev
7、其他建立:提供设备信息,手动创建设备节点:class_create,device_create
剖析Hello驱动程序代码
/*头文件省略,记得找到源码添加即可*/
#define MIN(a,b) ((a < b) ? a: b)
/*驱动程序*/
/*1、确定主设备号,也可以让内核分配*/
static int major = 0; //设备号为0 系统自动分配
static char kernel_buf[1024];
static struct class *hello_class;
/*3、实现对应的 drv_open/drv_read/drv_write等函数,填入 file_operations结构体*/
static ssize_t hello_drv_read(struct file *file, char __user * buf, size_t size, loff_t * offset)
{
int err;
/*驱动调试 屏幕打印文件名,函数名,行号*/
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
err = copy_to_user(buf, kernel_buf, MIN(1024, size)); //内核存储的数据读到用户buf中
return MIN(1024, size);
}
static ssize_t hello_drv_write(struct file *file, const char __user * buf, size_t size, loff_t * offset)
{
int err;
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(kernel_buf, buf, MIN(1024, size)); //用户buf的数据写到内核中
return MIN(1024, size);
}
static int hello_drv_open(struct inode *node, struct file *file)
{
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int hello_drv_close(struct inode *node, struct file *file)
{
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*2、定义自己的 file_operations结构体*/
static struct file_operations hell_drv = {
.owner = THIS_MODULE,
.open = hello_drv_open,
.read = hello_drv_read,
.write = hello_drv_write,
.release = hello_drv_close,
};
/*4、把 file_operations结构体告诉内核: register_chrdev 谁来注册驱动程序啊?*/
/*5、得有一个入口函数:安装驱动程序时,就会去调用这个入口函数*/
static int __init hello_init(void)
{
int err;
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
/*注册驱动程序*/
major = register_chrdev(0, "hello", &hell_drv);
/*创建设备节点*/
hello_class = class_create(THIS_MODULE, "hello_class");
err = PTR_ERR(hello_class); /*错误处理函数*/
if (IS_ERR(hello_class)){
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(0, "hello");
return -1;
}
//device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
return 0;
}
/*6、有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev */
static void __exit hello_exit(void)
{
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
device_destroy(hello_class, MKDEV(major, 0)); //MKDEV(major, 0)主设备号 次设备号
class_destroy(hello_class);
unregister_chrdev(0, "hello");
}
/*7、其他完善:提供设备信息,自动创建设备节点: class_create, device_create*/
module_init(hello_init); //修饰为入口函数
module_exit(hello_exit);
MODULE_LICENSE("GPL"); //遵守GPL协议
①register_chrdev()是Linux内核中用于注册字符设备驱动程序的函数。在Linux系统中,字符设备是一种提供面向字符的I/O操作的设备,比如终端、打印机、鼠标、键盘、LED、I2C、SPI等。
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
major = register_chrdev(0, "hello", &hell_drv);
其中参数涵义如下:
major:主设备号,用于惟一标示该字符设备驱动程序。
name:设备名称,用于在/proc/devices中显示。
fops:指向structfile_operations结构体的表针,包含了该字符设备支持的操作函数。
当调用register_chrdev()函数时,内核会为指定的字符设备注册一个主设备号,并将其与对应的操作函数关联上去。这样,在用户空间通过设备文件和系统调用就才能访问和操作这个字符设备了。
②class_create()是Linux内核中用于创建一个新的设备类的函数。在Linux系统中,设备类是一种将相关设备实例进行组织和分类的机制。
struct class *class_create(struct module *owner, const char *name);
hello_class = class_create(THIS_MODULE, "hello_class");
其中参数涵义如下:
owner:指向structmodule类型的表针,表示拥有该设备类的模块。
name:设备类的名称,用于在/sys/class/目录下创建相应的子目录。
成功调用class_create()函数后,会在/sys/class/目录下创建一个与指定名称对应的子目录,用于储存属于该设备类的设备实例。
③device_create()是Linux内核中用于创建一个设备实例的函数。在Linux系统中,设备实例是设备类中的具体设备对象。通过device_create()函数可以创建一个新的设备实例,并将其与指定的设备类进行关联。
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *format, ...);
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
其中参数涵义如下:
class:指向structclass类型的表针,表示要创建设备实例所属的设备类。
parent:指向structdevice类型的表针,表示要创建设备实例的父设备。一般为NULL,表示没有父设备。
devt:设备号,用于标示设备实例的惟一性。
drvdata:指向设备驱动程序特定的数据结构的表针,可以传递给设备实例。
format:设备名称的低格字符串,用于在/sys/class//目录下创建相应的设备实例目录。
成功调用device_create()函数后,会在/sys/class//目录下创建一个与指定低格字符串对应的设备实例目录,通过在/dev目录下创建相应的设备节点。
1-4测试
(1)更改makefile文件,内核路径对应于自己系统上的
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
(2)复印内核信息,在并口处设置快捷方法
echo "7 4 1 7" > /proc/sys/kernel/printk
控制内核复印信息级别,>7的才输出。即不输出内核复印信息
/include/linux/kern_levels.h
#define KERN_SOH "01" /* ASCII Start Of Header */
#define KERN_EMERG KERN_SOH "0" /* system is unusable 系统崩溃前的信息*/
#define KERN_ALERT KERN_SOH "1" /* action must be taken immediately 需要立即处理的消息 */
#define KERN_CRIT KERN_SOH "2" /* critical conditions 严重情况*/
#define KERN_ERR KERN_SOH "3" /* error conditions 错误*/
#define KERN_WARNING KERN_SOH "4" /* warning conditions 警告*/
#define KERN_NOTICE KERN_SOH "5" /* normal but significant condition 注意 */
#define KERN_INFO KERN_SOH "6" /* informational 普通信息*/
#define KERN_DEBUG KERN_SOH "7" /* debug-level messages 调试*/
(3)装载驱动insmod
(4)显示的当前内核早已加载的模块和驱动lsmod
(5)卸载驱动rmmod
(6)显示主设备号register_chrdev()
cat /proc/devices
(7)创建设备节点class_create()和device_create()
1-5补充知识(1)module_init/module_exit的实现
一个驱动程序有入口函数、出口函数。驱动程序可以被编进内核里,也可以被编译为ko文件后手工加载。
module_init(hello_init)
module_exit(hello_exit);
当编译为ko文件时,使用insmod命令加载驱动时,内核调用init_module函数,实际上就是调用hello_init函数;使用rmmod命令卸载驱动时,内核都是调用cleanup_module函数,实际上就是调用hello_exit函数。
(2)register_chrdev的内部实现
chrdevs[i]字段项是一个数组头,数组里每一个元素都是一个char_device_struct结构体,每位元素表示一个驱动程序。
struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int basem inor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
}*chrdevs[CHRDEV_MAJOR_HASH_SIZE];
它指定了主设备号major、次设备号baseminor、个数minorct,在cdev中富含file_operations结构体。为此,内核通过主、次设备号,找到对应的file_operations结构体”。
APP打开某个字符设备节点时,步入内核。在内核里按照字符设备节点的主、次设备号,通过cdev_add()函数调用在cdev_map数组中快速得到cdev,再从cdev中得到file_operations结构体。
cdev结构体表示一个字符设备对象。
(3)class_destroy/device_create探讨
在/sys目录下创建一些目录、文件,这样Linux系统中的APP就可以按照这种目录或文件来创建设备节点。
1-6拓展知识(重要)(1)register_chrdev_region()
在后续的不断学习中得悉初期的驱动是以register_chrdev()函数的内核注册的方式,如今都是采用register_chrdev_region()以及alloc_chrdev_region()进行注册设备。
register_chrdev_region()为静态注册,指定设备号。alloc_chrdev_region()为动态注册,手动分配设备号。
使用方式指定主设备号和此设备号,之后调用MKDEV合并生成设备ID,调用register_chrdev_region()函数注册一个设备名为dengzj_led的设备,最后编译为.ko文件insmod后,可查看。
#define LED_MA 300 //主设备号 用于区分不同种类的设备
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
int devno = MKDEV(LED_MA,LED_MI); //合并主次设备号,生成设备ID
//1.注册设备号
ret = register_chrdev_region(devno,1,"dengzj_led");
if(ret<0){
printk("register_chrdev_region fail n");
return ret;
}
insmod led.ko
cat /proc/devices
(2)字符设备驱动程序
#include
#include
#include /*for MKDEV register_chrdev_region*/
#include /*字符设备头文件*/
#define LED_MA 300 //主设备号 用于区分不同种类的设备
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
struct cdev cdev; //定义字符设备
int led_open (struct inode * inode, struct file * file){
printk(" led open gon");
return 0;
}
int led_release(struct inode *inode, struct file * file){
printk(" led release gon");
return 0;
}
struct file_operations led_fops={ //实现需要的文件操作
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
};
static int led_init(void)
{
int devno = MKDEV(LED_MA,LED_MI); //合并主次设备号,生成设备ID
int ret;
//1.注册设备号
ret = register_chrdev_region(devno, 1, "dengzj_led");
if(ret < 0){
printk("register_chrdev_region fail n");
return ret;
}
//2.初始化字符设备
cdev_init(&cdev, &led_fops); //字符设备的初始化
ret = cdev_add(&cdev, devno, 1);
if(ret < 0){
printk("cdev_add dengzj n");
return -1;
}
printk("led init deng_zj n");
return 0;
}
static void led_exit(void)
{
//配对 注销设备
int devno = MKDEV(LED_MA,LED_MI);
cdev_del(&cdev);
unregister_chrdev_region(devno,1);
printk("led exit dengzjn");
}
module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明
MODULE_AUTHOR("guangzhou dengzj"); //模块作者声明(可选)
(3)字符设备框架
重要结构体structcdev
二、GPIO基础知识
GPIO:Generalpurposeinput/output,通用的输入输出口
2-1GPIO结构
(1)有多组GPIO,每组有多个GPIO。诸如:GPIO2_IO15。
(2)使能:电源/时钟
(3)模式(Mode):引脚可用于GPIO或其他功能
(4)方向:引脚Mode设置为GPIO时,可以继续设置它是输出引脚,还是输入引脚
(5)数值:
◼对于输出引脚,可以设置寄存器让它输出高、低电平
◼对于输入引脚sogou pinyin linux,可以读取寄存器得到引脚的当前电平
将某一位置1,采用或操作
val = val |(1<<n)
val |= (1<<n)
将某一位清0,用与且取反操作
val = val & ~(1<<n)
val &= ~(1<<n)
GPIO控制涉及CCM、IOMUXC、GPIO模块本身等三个部份。
CCM:ClockControllerModule(时钟控制模块),用于设置是否向GPIO模块提供时钟。参考资料:芯片指南《Chapter18:ClockControllerModule(CCM)》
IOMUXC:IOMUXController,IO复用控制器。引脚的模式和功能,参考资料:芯片指南《Chapter32:IOMUXController(IOMUXC)》。
2-2GPIO模块内部
①GPIOx_GDIR:设置引脚方向,每个对应一个引脚1-output,0-input
②GPIOx_DR:设置输出引脚的电平,每个对应一个引脚,1-高电平,0-低电平
③GPIOx_PSR:读取引脚的电平,每个对应一个引脚1-高电平,0-低电平
GPIO设置步骤
1、使能时钟/电源
2、选择GPIO模式
3、设置IO方向
4、设置高/低电平
三、LED驱动3-1最简单的LED驱动程序
SVC用于生成系统函数调用.比如,用户程序不准许直接访问硬件linux嵌入式开发基础,操作系统可以通过SVC提供对硬件的访问。因而,当用户程序想要使用个别硬件时,可以使用SVC指令,之后执行操作系统中的软件异常处理程序,并提供用户应用程序恳求的服务。
(1)驱动如何操作硬件?
通过ioremap映射寄存器的数学地址得到虚拟地址,读写虚拟地址。
(2)驱动如何和APP传输数据?
通过copyto_user()、copy_from_user()这2个函数。
volatile关键字,避免程序被优化,如某一变量的点灯、灭灯(写操作:*p=0;*p=1读操作亦是这么)
ioremap()可以将化学地址映射为虚拟地址
virt_addr = ioremap(phys_addr, size);
把数学地址phys_addr开始的一段空间,映射为虚拟地址返回值是该段虚拟地址的首地址。实际上,它是按页4096字节进行映射的,即整页整页地映射的。
/*头文件参考源码即可*/
static int major;
static struct class *led_class;
/*register*/
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
//GPIO5_GDIR地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;
//GPIO5_DR地址:0x020AC000
static volatile unsigned int *GPIO5_DR;
static ssize_t led_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
char val;
int ret;
/*copy_from_user : get data from app 用户空间的数据拷贝到内核空间
*status中的数据在buf中
*/
ret = copy_from_user(&val, buf, 1);/*buf里的值是用户空间的state buf=&state*/
/*to set GPIO register:out 1 or 0*/
if(val){
/*set gpio to let led on*/
*GPIO5_DR &= ~(1<<3); //输出0
}
else{
/*set gpio to let led off 熄灭*/
*GPIO5_DR |=(1<<3); //输出1
}
return 1;
}
static int led_open(struct inode *inode, struct file *filp)
{
/*enable gpio
*configure gpio5_io3 as gpio
*configure gpio5_io3 as output
*/
/*配置为GPIO引脚功能*/
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf; //清零
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x05;
/*GPIO引脚配置为输出方式*/
*GPIO5_GDIR |= (1<<3);
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
};
/*入口函数*/
static int __init led_init(void)
{
printk("%s %s %dn", __FILE__, __FUNCTION__, __LINE__);
/*注册字符设备*/
major = register_chrdev(0, "100ask_led", &led_fops);
/*ioremap 将物理地址映射为虚拟地址*/
//IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3地址:0x02290000 + 0x14
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);
//GPIO5_GDIR地址:0x020AC004
GPIO5_GDIR = ioremap(0x020AC004, 4);
//GPIO5_DR地址:0x020AC000
GPIO5_DR = ioremap(0x020AC000, 4);
led_class = class_create(THIS_MODULE, "myled");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /*系统会创建 /dev/myled的设备节点*/
return 0;
}
static void __exit led_exit(void)
{
iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
iounmap(GPIO5_GDIR);
iounmap(GPIO5_DR);
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
};
3-2LED驱动程序框架
字符设备框架
LED驱动框架
应用程序访问驱动程序,会打开一个设备节点,依据设备节点的主、次设备号,在内核里找到file_operations结构体。
LED驱动能支持多个板子的基础:分层思想
这儿的目的似乎就是把硬件部份代码剥离下来linux命令tar,这些构思的方式借此来引出旁边的设备树。
程序解析
(1)led_opr.h面向对象编程,将LED具象为一个led_operations结构体
#ifndef _LED_OPR_H
#define _LED_OPR_H
struct led_operations{
int(*init)(int which); /*初始化LED, which-哪个LED*/ /*函数指针*/
int (*ctl)(int which, char status); /*控制LED,which-哪个LED,status:1 亮,0 灭*/
};
struct led_operations *get_board_led_opr(void); /*指针函数 返回值是结构体类型的指针*/
#endif
/*宏定义作用:防止头文件重复定义*/
#ifndef宏名(_LED_OPR)
#define宏名(_LED_OPR)
…
#endif
作用:避免头文件重复定义出错,即当a.h和b.h引用了该头文件,而且d.c又引用a.h和b.h文件,会造成引用两次。
(2)board_demo.c
#include
#include "led_opr.h"
static int board_demo_led_init(int which) /*初始化LED,which-哪个LED*/
{
printk("%s %s line %d, led %dn", __FILE__,__FUNCTION__, __LINE__, which);
return 0;
}
static int board_demo_led_ctl(int which, char status) /*控制LED,which-哪个LED,status:1 亮 0灭*/
{
printk("%s %s line %d, led %dn", __FILE__,__FUNCTION__, __LINE__, which);
return 0;
}
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};
struct led_operations *get_board_led_opr(void)
{
return &board_demo_led_opr;
}
对led_operations结构体的实例化board_demo_led_opr以及函数体的实现
(3)lederv.c
p_led_opr = get_board_led_opr();
最终p_led_opr指向board_demo_led_opr结构体,通过调用其结构体成员(2个函数表针来实现相应内容)
3-3课后作业1
实现读LED状态的功能:涉及APP和驱动。
思路单独编撰read部份代码
3-4具体单板的LED驱动程序
坐落02_led_drv_for_boards文件夹中
虽然就是在01文件夹中具象的结构体对应的函数中的基础上添加了iMX6ULL具体硬件LED的信息,部份下边代码所示
//效率太低 进行了两次读-修改-写操作
//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf); //清零
//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= (5);
//2、设置GPIO5_IO03用于GPIO
//只进行一次读操作 一次写操作
val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
val &= ~(0xf);
val |= (5);
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
课后作业1:在出口函数处添加iounmap()函数
static void __exit led_exit(void)
{
iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
iounmap(GPIO5_GDIR);
iounmap(GPIO5_DR);
int i;
printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
for(i = 0; i < p_led_opr->num; i++){
device_destroy(led_class, MKDEV(major, i)); //MKDEV(major, i)主设备号 次设备号
}
class_destroy(led_class);
unregister_chrdev(0, "100ask_led"); /*注销驱动*/
}
课后作业2
/*部分代码*/
static int board_demo_led_init(int which) /*初始化LED,which-哪个LED*/
{
unsigned int val;
int i = 0;
//printk("%s %s line %d, led %dn", __FILE__,__FUNCTION__, __LINE__, which);
if(which == 0){ /*which==次设备号*/
if(!i){
CCM_CCGR1 = ioremap(0x20C406C, 4);
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290014, 4);
GPIO5_GDIR = ioremap(0x020AC004, 4);
GPIO5_DR = ioremap(0x020AC000, 4);
i++;
}
//1、使能GPIO5
*CCM_CCGR1 |= (3<<30);
//效率太低 进行了两次读-修改-写操作
//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~(0xf); //清零
//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= (5);
//2、设置GPIO5_IO03用于GPIO
//只进行一次读操作 一次写操作
val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
val &= ~(0xf);
val |= (5);
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
//3、设置GPIO5_IO03作为output引脚
*GPIO5_GDIR |= (1<<3);
}
else{
/*由于IM6ULL只有一个灯可以点,故难以找到对应LED的GIIO口,所以提供思路即可。若有LED,在此处配置第二个LED即可*/
}
return 0;
}
static int board_demo_led_ctl(int which, char status) /*控制LED,which-哪个LED,status:1 亮 0灭*/
{
//printk("%s %s line %d, led %dn", __FILE__,__FUNCTION__, __LINE__, which);
if(which == 0){
if(status){ /*on: output 0 点灯*/
*GPIO5_DR &= ~(1<<3);
}
else{ /*off: output 1 熄灭*/
*GPIO5_DR |= (1<<3);
}
}
else{
if(status){ /*on: output 0 点灯*/
//同理,若有LED灯,在此处配置即可 *GPIO5_DR &= ~(1<<3);
}
else{ /*off: output 1 熄灭*/
//*GPIO5_DR |= (1<<3);
}
}
return 0;
}
static struct led_operations board_demo_led_opr = {
.num = 2,
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};
四、驱动设计的思想
在面向对象时,字符设备驱动程序具象出一个file_operations结构体;LED驱动程序针对硬件部份具象出led_operations结构体。
分层思想:
分离思想:
对于引脚的操作是相同的,那可以针对该芯片写出比较通用的硬件操作代码;
例如board_A.c使用芯片Y,那就可以写出chipY_gpio.c,它实现芯片Y的GPIO操作,适用于芯片Y的所有GPIO引脚。使用时,我们只须要在board_A_led.c手指定使用哪一个引脚即可。
五、总线设备驱动模型5-1简介
对于硬件资源,用platform_device结构体来表示;对于硬件的操作,用platform_driver结构体表示。
内核源码:includelinuxplatform_device.h
在内核中有一个虚拟的总线platform_bus_type,它有2个数组结构,右边是
设备Dev数组,右侧是驱动Drv数组。左侧的设备数组和右侧的驱动数组会进行一一比较(通过platform_match函数),若匹配成功,都会调用platform_driver中的probe函数。
怎样进行匹配?
(1)platform_device结构体
(2)platform_driver结构体
(3)platform_match
总线设备驱动编撰程序步骤
(1)分配、设置、注册platform_device结构体,在上面定义所用资源,指定设备名子。
(2)分配、设置、注册platform_driver结构体,在其中的probe函数里,分配、设置、注册file_operations结构体,并从platform_device中确实所用硬件资源,指定platform_driver的名子。
5-2LED总线设备驱动模型
之前的LED驱动框架
现目标框架如下
解释:
1、leddrv.c注册驱动程序,创建设备节点类,platform_device(相当于前面的设备树)指定硬件信息,platform_driver指定对硬件信息进行操作(初始化、控制)。
2、注册平台设备驱动两个结构体,但是调用register_led_operations(&board_demo_led_opr)()提早为leddrv.c打开插口。平台总线设备驱动匹配后,调用chip_demo_gpio_probe()来获取硬件资源信息,但是再调用led_class_create_device(),创建设备节点。/dev/100ask_led0,1,…
3、此时应用层open函数可以打开设备,在调用驱动程序的led_drv_open(),在函数中初始化硬件p_led_opr->init(minor),最终调用到底层board_demo_led_init()函数。
1、返回该dev中某类型type资源中的第几个num
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num)
2、返回该dev所用的第几个num中断
int platform_get_irq(struct platform_device *dev, unsigned int num)
3、通过名子name)返回该dev的某类型type资源
struct resource *platform_get_resource_byname(struct platform_device *dev, unsigned int type, const char *name)
4、通过名子name)返回该dev的中断号
int platform_get_irq_byname(struct platform_device *dev const char *name)
程序解析
在board_A_led.c中构造platform_device,提供LED硬件相关资源
static void led_dev_release(struct device *dev)
{
}
static struct resource resources[] = {
{
.start = GROUP_PIN(3,1),
.flags = IORESOURCE_IRQ, /*表示是哪一类资源 这里是假设IRQ表示引脚*/
.name = "100ask_led_pin",
},
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
.name = "100ask_led_pin",
},
};
static struct platform_device board_A_led_dev = {
.name = "100ask_led",
.num_resources = ARRAY_SIZE(resources),
.resource = resources,
.dev = {
.release = led_dev_release, /*防止在调用
platform_device_unregister 时会出现警告*/
},
};
static int __init led_dev_init(void)
{
int err;
err = platform_device_register(&board_A_led_dev); /*注册platform_device*/
return 0;
}
static void __exit led_dev_exit(void)
{
platform_device_unregister(&board_A_led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
在chip_demo_gpio.c中构造platform_device结构体与之匹配
static int g_ledpins[100];
static int g_ledcnt = 0;
/*
*省略部分代码
*/
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl,
};
struct led_operations *get_board_led_opr(void)
{
return &board_demo_led_opr;
}
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
struct resource *res;
int i = 0;
while (1){
res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
if (!res)
break;
g_ledpins[g_ledcnt] = res->start; /*记录引脚 */
led_class_create_device(g_ledcnt); /*创建device_create 有多少引脚,创建多少个*/
g_ledcnt++;
}
return 0;
}
static int chip_demo_gpio_remove(struct platform_device *pdev)
{
struct resource *res;
int i = 0;
while (1){
res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if (!res)
break;
led_class_destroy_device(i);
i++;
g_ledcnt--;
}
return 0;
}
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led",
},
};
static int __init chip_demo_gpio_drv_init(void)
{
int err;
err = platform_driver_register(&chip_demo_gpio_driver);
register_led_operations(&board_demo_led_opr);
return 0;
}
static void __exit lchip_demo_gpio_drv_exit(void)
{
platform_driver_unregister(&chip_demo_gpio_driver);
}
module_init(chip_demo_gpio_drv_init);
module_exit(lchip_demo_gpio_drv_exit);
MODULE_LICENSE("GPL");
a.c编译为a.ko,上面定义了func_a;假如它想让b.ko使用该函数,那
么a.c里须要导入此函数。而且,使用时要先加载a.ko。假如先加载b.ko
EXPORT_SYMBOL(led_device_create);
5-3平台总线驱动函数关系调用图解