本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
看完视频复习的同学观看最佳!
基于 IMX6ULL-PRO
参考视频 Linux快速入门到精通视频
参考资料:01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板.pdf
2024.4.18更新 利用图解方式探索内核函数调用
代码上传到git网站驱动代码
一、Hello 驱动编程
1-1 APP 打开的文件在内核中如何表示
APP打开文件时,得到一个整数fd,被称为文件句柄。对于APP的每一个文件句柄,在内核里面都有一个struct file
结构体(include/linux/fs.h)与之对应。
open打开文件时,传入的flags、mode等参数会被记录在内核中对应的struct file 结构体里f_flags、f _mode。
去读写文件时,文件的当前偏移地址也会保存在f _pos 成员里。
int open(const char *pathname, int flags, mode_t mode);
1-2 打开字符设备节点时,内核中也有对应的 struct file
struct file_operations *f_op,由驱动程序提供。struct file_operations结构体是Linux内核中用于描述文件操作函数集合的结构体。它包含了许多函数指针成员,每个成员对应于不同的文件操作。
1-3 Hello 驱动程序
如何编写驱动程序步骤
1、确定主设备号,也可让内核分配
2、定义自己的file_operations
结构体
3、实现drv_open()、drv_read()、drv_write()等函数,写入file_operations结体
4、file_operations结构体告诉内核:register_chrdev()
5、谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
6、有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用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 %d\n", __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 %d\n", __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 %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int hello_drv_close(struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __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 %d\n", __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 %d\n", __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 %d\n", __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: 指向struct file_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: 指向struct module类型的指针,表示拥有该设备类的模块。
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: 指向struct class类型的指针,表示要创建设备实例所属的设备类。
parent: 指向struct device类型的指针,表示要创建设备实例的父设备。通常为NULL,表示没有父设备。
devt: 设备号,用于标识设备实例的唯一性。
drvdata: 指向设备驱动程序特定的数据结构的指针,可以传递给设备实例。
format: 设备名称的格式化字符串,用于在/sys/class/<class_name>/目录下创建相应的设备实例目录。
成功调用device_create()函数后,会在/sys/class/<class_name>/目录下创建一个与指定格式化字符串对应的设备实例目录,通过在/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 "\001" /* 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结构体。
(3) class_destroy/device_create 浅析
在/sys目录下创建一些目录、文件,这样 Linux 系统中的 APP就可以根据这些目录或文件来创建设备节点。
二、GPIO基础知识
GPIO: General purpose input/output ,通用的输入输出口
2-1 GPIO结构
(1) 有多组GPIO,每组有多个GPIO。例如:GPIO2_IO15。
(2) 使能:电源/时钟
(3) 模式(Mode):引脚可用于GPIO或其他功能
(4) 方向:引脚Mode设置为GPIO时,可以继续设置它是输出引脚,还是输入引脚
(5) 数值:
◼ 对于输出引脚,可以设置寄存器让它输出高、低电平
◼ 对于输入引脚,可以读取寄存器得到引脚的当前电平
将某一位置1,采用或操作
val = val |(1<<n)
val |= (1<<n)
将某一位清0,用与且取反操作
val = val & ~(1<<n)
val &= ~(1<<n)
GPIO控制涉及CCM、IOMUXC、GPIO模块本身等三个部分。
CCM:Clock Controller Module (时钟控制模块),用于设置是否向GPIO模块提供时钟。参考资料:芯片手册《Chapter 18: Clock Controller Module (CCM) 》
IOMUXC:IOMUX Controller,IO复用控制器。引脚的模式和功能,参考资料:芯片手册《Chapter 32: IOMUX Controller (IOMUXC) 》。
2-2 GPIO模块内部
① 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用于生成系统函数调用.例如,用户程序不允许直接访问硬件,操作系统可以通过SVC提供对硬件的访问。 因此,当用户程序想要使用某些硬件时,可以使用SVC指令,然后执行操作系统中的软件异常处理程序,并提供用户应用程序请求的服务。
(1) 驱动怎么操作硬件?
通过ioremap映射寄存器的物理地址得到虚拟地址,读写虚拟地址。
(2) 驱动怎么和APP传输数据?
通过 copy to_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 %d\n", __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-2 LED驱动程序框架
字符设备框架
LED驱动框架
应用程序访问驱动程序,会打开一个设备节点,根据设备节点的主、次设备号,在内核里找到file_operations结构体。
LED 驱动能支持多个板子的基础: 分层 思想
这里的目的其实就是把硬件部分代码剥离出来
,这种构思的方法以此来引出后面的设备树。
程序解析
(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 <linux/gfp.h>
#include "led_opr.h"
static int board_demo_led_init(int which) /*初始化LED,which-哪个LED*/
{
printk("%s %s line %d, led %d\n", __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 %d\n", __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 %d\n", __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 %d\n", __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 %d\n", __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
结构体表示。
内核源码: include\linux\platform_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-2 LED总线设备驱动模型
之前的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文章来源:https://www.toymoban.com/news/detail-857277.html
EXPORT_SYMBOL(led_device_create);
5-3 平台总线驱动函数关系调用图解
文章来源地址https://www.toymoban.com/news/detail-857277.html
到了这里,关于韦东山嵌入式Liunx入门驱动开发一(Hello 驱动编程、GPIO基础知识、LED驱动、总线设备驱动模型)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!