目录
2.1分配/设置/注册 platform_device 结构体(board_A_led.c)
2.2分配/设置/注册 platform_driver 结构体(chip_demo_gpio.c)
一.总线驱动框架模型介绍
1.1介绍
如果CPU和外设或者外设想要进行通讯或操作,需要将通信双方挂载到同一条总线上,这里的总线可以使具体的总线,如IIC,CAN总线等也可以是虚拟的总线,platform平台总线。
总线设备驱动可分为总线,设备和驱动三个部分,当将一个设备加入到总线上时,内核会在这条总线上寻找该设备对应的驱动;当将一个驱动加入到一条总线上时,内核会在该总线上寻找与该驱动对应的设备。匹配的规则根据不同类型的总线及设备特征进行定义。
1.2总线的匹配规则
总线驱动结构体:
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
总线设备结构体:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
1.2.1最先比较
platform_device.driver_override 和 platform_driver.driver.name可以设置 platform_device 的 driver_override,强制选择某个 platform_driver。若定义了driver_override ,最先使用它去比较
1.2.2然后比较
platform_device. name 和 platform_driver.id_table[i].name Platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个 device,它里面列出了各个 device 的{.name, .driver_data},其中的“name”表示该drv 支持的设备的名字,driver_data 是些提供给该 device 的私有数据
1.2.3最后比较
platform_device.name 和 platform_driver.driver.name platform_driver.id_table 可能为空,这时可以根据 platform_driver.driver.name 来寻找同名的 platform_device。
1.2.4函数从上层到下层的调用关系
1.2.5常用函数
这些函数可查看内核源码:drivers/base/platform.c,根据函数名即可知道其含义。
下面摘取常用的几个函数。
注册/反注册
platform_device_register/ platform_device_unregister
platform_driver_register/ platform_driver_unregister
platform_add_devices // 注册多个 device
platform_device_unregister//注销
platform_driver_unregister
获得资源:
返回该dev中某类型(type)中的第几个(num),拿到资源结构体
struct resource *platform_get_resource(struct platform_device *dev, unsigned int
type,unsigned int num)
返回该 dev 所用的第几个(num)中断:
int platform_get_irq(struct platform_device *dev, unsigned int num)
通过名字(name)返回该 dev 的某类型(type)资源:
struct resource *platform_get_resource_byname(struct platform_device *dev,
unsigned int type,const char *name)
通过名字(name)返回该 dev 的中断号:
int platform_get_irq_byname(struct platform_device *dev, const char *name)
二.程序编写思路
2.1分配/设置/注册 platform_device 结构体(board_A_led.c)
在里面定义资源(引脚,中断), 指定设备的名字。
2.2分配/设置/注册 platform_driver 结构体(chip_demo_gpio.c)
里面probe函数在总线上的设备和驱动匹配成功之后会执行,注册设备,得到资源那边的存储资源的结构体。
注册/配置platform_driver结构体,注册总线。
三.具体程序
应用程序:ledtest.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
* ./ledtest /dev/100ask_led0 on
* ./ledtest /dev/100ask_led0 off
*/
int main(int argc, char **argv)
{
int fd;
char status;
/* 1. 判断参数 */
if (argc != 3)
{
printf("Usage: %s <dev> <on | off>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
/* 3. 写文件 */
if (0 == strcmp(argv[2], "on"))
{
status = 1;
write(fd, &status, 1);
}
else
{
status = 0;
write(fd, &status, 1);
}
close(fd);
return 0;
}
上层驱动代码:(leddrv.c)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include "leddrv.h"
#include "led_opr.h"
#define LED_NUM 2
/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;
#define MIN(a, b) (a < b ? a : b)
/*个人理解这里为什么要在这里单独再写一个API去注册设备
major和led_class设备类是在上层驱动定义的,要在下层用的话太麻烦
*/
void led_class_create_device(int minor)
{
device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
}
void led_class_destroy_device(int minor)
{
device_destroy(led_class, MKDEV(major, minor));
}
void register_led_operations(struct led_operations *opr)
{
p_led_opr = opr;
}
EXPORT_SYMBOL(led_class_create_device);//导出到内核的全局符号表中,让其它的内核模块也能使用这个函数
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
struct inode *inode = file_inode(file);
int minor = iminor(inode);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
/* 根据次设备号和status控制LED */
p_led_opr->ctl(minor, status);
return 1;
}
static int led_drv_open (struct inode *node, struct file *file)
{
int minor = iminor(node);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 根据次设备号初始化LED */
p_led_opr->init(minor);
return 0;
}
static int led_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 led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */
led_class = class_create(THIS_MODULE, "100ask_led_class");
err = PTR_ERR(led_class);
if (IS_ERR(led_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "100ask_led");
return -1;
}
// p_led_opr = get_board_led_opr();
return 0;
}
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
上层驱动头文件:(leddrv.h)
#ifndef _LEDDRV_H
#define _LEDDRV_H
#include "led_opr.h"
void led_class_create_device(int minor);
void led_class_destroy_device(int minor);
void register_led_operations(struct led_operations *opr);
//void led_device_create(int minor);
//void led_device_destory(int minor);
#endif /* _LEDDRV_H */
下层驱动资源文件(board_A_led.c)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include "led_resource.h"
static struct led_resource board_A_led = {
.pin = GROUP_PIN(5,3),
};
struct led_resource *get_led_resource(void)
{
return &board_A_led;
}
static struct resource resources[] = {
{
.start = GROUP_PIN(3, 1),
.flags = IORESOURCE_IRQ,
},
{
.start = GROUP_PIN(5,8),
.flags = IORESOURCE_IRQ,
}
};
static struct platform_device board_A_dev = {
.name = "100ask_led",
.num_resources = ARRAY_SIZE(resources),
.resource = resources,
};
static int led_dev_init(void)
{
int err;
err = platform_device_register(&board_A_dev);
return 0;
}
static void led_dev_exit(void)
{
platform_device_unregister(&board_A_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
下层驱动,驱动操作文件(chip_demo_gpio.c)
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include "leddrv.h"
#include "led_opr.h"
#include "led_resource.h"
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/module.h>
#include <asm/io.h>
//static struct led_resource *led_rsc = NULL;
static int g_ledpins[100];
static int g_ledcnt = 0;
static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
{
printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, GROUP(g_ledpins[which]));
switch (GROUP(g_ledpins[which]))
{
case 0:
{
printk("set pin of group 0 ....\n");
break;
}
case 1:
{
printk("set pin of group 1 ....\n");
break;
}
case 2:
{
printk("set pin of group 2 ....\n");
break;
}
case 3 :
{
printk("set pin of group 3 ....\n");
break;
}
case 5:
{
printk("set pin of group 5 ...\n");
break;
}
}
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, %s\n", __FILE__, __FUNCTION__, __LINE__, g_ledpins[which], status ? "on" : "off");
switch (GROUP(g_ledpins[which]))
{
case 0:
{
printk("set pin of group 0 ....\n");
break;
}
case 1:
{
printk("set pin of group 1 ....\n");
break;
}
case 2:
{
printk("set pin of group 2 ....\n");
break;
}
case 3 :
{
printk("set pin of group 3 ....\n");
break;
}
case 5:
{
printk("set pin of group 5 ...\n");
break;
}
case 8:
{
printk("set pin 8\n");
break;
}
}
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;
}
/*系统检测到驱动和设备都注册了之后会调用这个函数*/
static int chip_demo_gpio_led_probe(struct platform_device *dev)
{
int i = 0;
struct resource *res;
while(1)
{
/*
platform_get_resource() 函数的定义位于 linux/platform_device.h 头文件中。其函数原型如下:
c
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type,
unsigned int num);
该函数的参数依次为:
dev: 指向要获取资源的设备对象。
type: 要获取的资源的类型,通常可以使用宏定义 IORESOURCE_MEM 或 IORESOURCE_IRQ。
num: 获取指定类型资源的索引,从 0 开始。
其函数实现具体流程如下:
首先通过调用 dev->resource 获取该设备的第一个资源(如果有的话)。
然后循环查找 dev->resource 链表,找到所需的资源节点。
最后返回找到的资源节点的指针。
*/
res = platform_get_resource(dev, IORESOURCE_IRQ, i++);//相当于就是获得board_A_led.c那边的resource结构体
if(!res)
break;
/*记录引脚*/
g_ledpins[g_ledcnt] = res->start;//对应boar_A_led.c中的结构体,得到引脚
/*device_creat*/
led_class_create_device(g_ledcnt);//创建设备
g_ledcnt++;
}
return 0;
}
/*删除模块时会自动执行这个函数,内核自动调用*/
static int chip_demo_gpio_led_remove(struct platform_device *dev)
{
struct resource *res;
int i = 0;
while (1)
{
res = platform_get_resource(dev, IORESOURCE_IRQ, i);//获得board_A_led.c那边的resource结构体
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_led_probe,
.remove = chip_demo_gpio_led_remove,
.driver = {
.name = "100ask_led",
}
};
static int 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 chip_demo_gpio_dev_exit(void)
{
platform_driver_unregister(&chip_demo_gpio_driver);//注销设备
}
module_init(chip_demo_gpio_drv_init);
module_exit(chip_demo_gpio_dev_exit);
MODULE_LICENSE("GPL");
led_resource.h
#ifndef _LED_RESOURCE_H
#define _LED_RESOURCE_H
/*GPIO3_0*/
/* Ibit[31:16] = group*/
/*bit[15:0] = which pin*/
#define GROUP(x) (x >> 16)
#define PIN(x) (x & 0XFFFF)
#define GROUP_PIN(g,p) ((g<<16) | (p))//这里将引脚转换成16进制数通过上面式子运算可以得出想要结果
struct led_resource {
int pin;
};
struct led_resource *get_led_resource(void);//要进行外部调用所以声明一下
#endif
led_opr.h
#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
四.总结与踩坑
4.1符号声明外部函数调用以及交叉依赖
EXPORT_SYMBOL(led_class_create_device);//导出到内核的全局符号表中,让其它的内核模块也能使用这个函数
EXPORT_SYMBOL(led_class_destroy_device);
EXPORT_SYMBOL(register_led_operations);
相当于让其它文件也能够使用这几个函数,但是也需要在头文件里面进行声明才能够使用
避免交叉依赖,我需要调用你的API才能行,这里面的交叉依赖指的是,我加载模块的时候需要你的函数,所以需要你先加载,而你又需要我的模块这样导致两个模块都没有办法加载。
4.2驱动与设备树的关系
4.2.1设备树的由来:
随着 ARM 芯片的流行,内核中针对这些 ARM 板保存有大量的、没有技术含量的文件。
Linus(linux研发设计者) 大发雷霆:"this whole ARM thing is a f*cking pain in theass"。
于是,Linux 内核开始引入设备树。设备树并不是重新发明出来的,在 Linux 内核中其他平台如 PowerPC,早就使用设备树来描述硬件了。Linus 发火之后,内核开始全面使用设备树来改造,神人就神人。
4.2.2设备树现在理解前期介绍:
有一种错误的观点,说“新驱动都是用设备树来写了”。设备树不可能用来写
驱动。
想要操作硬件就需要操作复杂的寄存器,如果设备树可以操作寄存器,那么它就是驱动了。