本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、前言
平台总线模型也交platform总线模型,他是Linux虚拟出来的一条总线,他并不是真实的一条真实的电气总线;平台总线模型就是把原来的驱动C文件给分成了两个文件一个是device文件对应我们的设备文件,一个是driver文件对应我们的驱动文件,平台总线的优点有: 1)提高代码重用性 2)减少重复性代码 3)区分设备与驱动 4)更方便管理我们的设备 把稳定不变的驱动放在driver里面,需要做改动的设备部分放在device文件中;当我们注册device或者driver的时候会通过name进行匹配,实际上就是结构体中一个字符串变量的对比。
二、注册Device文件
device.c文件里面存放的是硬件资源,这里的硬件资源指的是寄存器地址、中断号、时钟等硬件资源,在Linux内核里面我们使用一个结构体来描述;
#include <linux/platform_device.h> //平台设备所需要的头文件
struct platform_device {
const char *name; // 平台总线匹配需要用到的名称
int id; // 对相同设备进行编号,这里如果只有一个设备填-1即可
bool id_auto;
struct device dev; // 内嵌的device结构体
u32 num_resources; // 资源的个数ARRAY_SIZE(struct resource)
struct resource *resource; // device对应的硬件资源:寄存器地址、中断号等
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;
};
设备资源结构体
struct resource {
resource_size_t start; // 寄存器开始地址
resource_size_t end; // 寄存器结束地址
const char *name; // 资源名称
unsigned long flags; // 资源类型
unsigned long desc;
struct resource *parent, *sibling, *child;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
资源类型有:
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */ // io的内存
#define IORESOURCE_MEM 0x00000200 // 物理内存地址 (常用)
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400 // 中断号 (常用)
#define IORESOURCE_DMA 0x00000800 // DMA的地址
#define IORESOURCE_BUS 0x00001000 // 总线号:SPI 、 IIC
示例
// 设备资源信息,也就是 BEEP 所使用的所有寄存器
struct resource led_res[] = {
[0] = {
.start = 0xfdd60000, // 对应开始地址
.end = 0xfdd60003, // 对应结束地址
.flags = IORESOURCE_MEM, // 表示GPIO内存
.name = "GPIO_DR", // 表示DR寄存器随意取
}
[1] = {
.start = 0xffffff0, // 对应第二个驱动使用的开始地址
.end = 0xfffffff, // 对应第二个驱动使用的结束地址
.flags = IORESOURCE_IO, // 表示GPIO内存
.name = "GPIOA7", // 表示DR寄存器随意取
}
};
// platform 设备结构体
struct platform_device led_device = {
.name = "led_work",// 驱动匹配的名称
.id = -1,
.resource = led_res, // 填充设备资源结构体
.num_resources = ARRAY_SIZE(led_res), // 获取资源个数
.dev = {
.release = led_release
} // device设备接口
};
platform总线的注册和卸载使用到的函数
platform_device_register(&led_device); // 注册
platform_device_unregister(&led_device); // 卸载
当我们将上述的device注册到内核后在开发板中的/sys/bus/platform/devices路径中生成我们的led_work节点
三、注册Driver文件
编写driver的思路,首先定义一个platform_driver变量,然后实现结构体中的各个成员变量,当我们的driver或者device注册的时候就会匹配name当匹配成功后就会执行probe函数,所以我们的driver重点是编写probe函数,这节我们先编写driver的框架;
struct platform_device_id led_id_table = {
.name = "led_work", // 匹配优先级更高,若是他匹配失败则直接失败
};
// platform 驱动结构体
struct platform_driver led_driver = {
.probe = led_probe, // 设备与驱动匹配成功后调用
.remove = led_remove, // 驱动移除后调用
.driver={
.owner = THIS_MODULE,
.name = "led_work" // 设备匹配名称
},
// 通过platform的设备ID进行匹配(优先级更高,使用该匹配方法匹配driver的name属性将无效)
.id_table = &led_idtable
};
总线驱动注册与卸载函数
platform_driver_register(&led_driver);
platform_driver_unregister(&led_driver);
四、编写Probe函数
当我们的设备与驱动匹配成功后我们的驱动部分就可以通过函数获取设备中的资源数据,然后我们可以使用该数据来进行设备的操作,例如字符设备的注册、杂项设备的注册、SPI资源的使用、IIC设备的使用、中断的使用等等;
struct resource *led_mem;
int led_probe(struct platform_device *pdev)
{
int ret;
// 获取该函数内存地址类型的第0个资源 ,如果有多个资源依次增加,如果中间夹杂着一个其他类型的资源不计数
// 例如
// [0] IORESOURCE_MEM [1]IORESOURCE_IRQ [2]IORESOURCE_MEM
// 如果我们要获取下方的[2]资源:platform_get_resource(pdev, IORESOURCE_MEM, 1)
// 如果我们要获取下方的[1]资源:platform_get_resource(pdev, IORESOURCE_IRQ, 0)
led_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (led_mem == NULL)
{
printk("platform_get_resource is error\n");
return -EBUSY;
}
// 当我们资源获取成功后接受返回的mem就等于device中定义的resource,我们可以直接使用
printk("led_res start is 0x%x \n", led_mem->start);
printk("led_res end is 0x%x \n", led_mem->end);
return 0;
}
五、完整代码示例
驱动部分
sdevices.c
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块
#include <linux/platform_device.h>//平台设备所需要的头文件
#include <linux/mod_devicetable.h>
// #include <linux/gpio.h>
void led_release(struct device *dev)
{
printk("led_release \n");
}
// 设备资源信息,也就是 BEEP 所使用的所有寄存器
struct resource led_res[] = {
[0] = {
.start = 0xfdd60000,
.end = 0xfdd60003 ,
.flags = IORESOURCE_MEM,
.name = "led_work",
}
};
// platform 设备结构体
struct platform_device led_device = {
.name = "led_work",
.id = -1,
.resource = led_res,
.num_resources = ARRAY_SIZE(led_res),
.dev = {
.release = led_release
}
};
static int device_init(void) {
// 设备信息注册到 Linux 内核
platform_device_register(&led_device);
return 0;
}
static void device_exit(void) {
// 设备信息卸载
platform_device_unregister(&led_device);
}
module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");
sdrivers.c
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块
#include <linux/platform_device.h>//平台设备所需要的头文件
#include <linux/mod_devicetable.h>
#include <linux/ioport.h> /*注册杂项设备头文件*/
#include <linux/miscdevice.h> //文件系统头文件,定义文件表结构(file,buffer_head,m_inode 等)
#include <linux/fs.h> //包含了 copy_to_user、copy_from_user 等内核访问用户进程内存地址的函数定义。
#include <linux/uaccess.h> //包含了 ioremap、iowrite 等内核访问 IO 内存等函数的定义。
#include <linux/io.h> //寄存器地址定义
#include <linux/gpio.h>
//存放映射完的虚拟地址的首地址
unsigned int *vir_gpio_dr;
struct resource *led_mem;
long misc_ioctl(struct file *files, unsigned int cmd, unsigned long arg)
{
printk("cmd is %d,arg is %d \n",cmd,(unsigned int)(arg));
switch(cmd)
{
case 0:
{
printk(KERN_INFO "LEDON \n");
*vir_gpio_dr = 0x80008000;
break;
}
case 1:
{
printk(KERN_INFO "LEDOFF\n");
*vir_gpio_dr = 0x80000000;
break;
}
}
return 0; // 必须添加返回之否则报错 error: control reaches end of non-void function [-Werror=return-type]
}
struct file_operations misc_fops={ //文件操作集
.owner = THIS_MODULE,
.unlocked_ioctl = misc_ioctl,
};
struct miscdevice misc_dev = { //杂项设备结构体
.minor = MISC_DYNAMIC_MINOR, //动态申请的次设备号
.name = "platform_misc", //杂项设备名字是hello_misc
.fops = &misc_fops, //文件操作集
};
int led_remove(struct platform_device *pdev)
{
iounmap(vir_gpio_dr);
misc_deregister(&misc_dev);
return 0;
}
int led_probe(struct platform_device *pdev)
{
int ret;
led_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (led_mem == NULL)
{
printk("platform_get_resource is error\n");
return -EBUSY;
}
printk("led_res start is 0x%llx \n", led_mem->start);
printk("led_res end is 0x%llx \n", led_mem->end);
ret = misc_register(&misc_dev);
if(ret<0)
{
printk("misc registe is error \n");
}
printk("misc registe is succeed \n");
//将物理地址转化为虚拟地址
vir_gpio_dr = ioremap(led_mem->start,4);
if(vir_gpio_dr == NULL)
{
printk("GPIO_DR ioremap is error \n");
return EBUSY;
}
printk("GPIO_DR ioremap is ok \n");
return 0;
}
struct platform_device_id led_id_table = {
.name = "led_work", // 匹配优先级更高,若是他匹配失败则直接失败
};
struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver={
.owner = THIS_MODULE,
.name = "led"
},
.id_table = &led_id_table,
};
static int led_driver_init(void)
{
int ret = 0; // platform 驱动注册到 Linux 内核
ret = platform_driver_register(&led_driver);
if(ret<0) {
printk("platform_driver_register error \n");
}
printk("platform_driver_register ok \n");
return 0;
}
static void led_driver_exit(void)
{
// platform 驱动卸载
platform_driver_unregister(&led_driver);
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");
makefile
obj-m += sdrivers.o // 驱动和设备对应修改
KDIR =/home/topeet/Linux/02.sdk/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules modules ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
clean:
rm -rf modules.order *.o workqueue.o Module.symvers *.mod.c *.ko
使用方法:部分前后顺序
insmod sdrivers.ko
insmod sdevices.ko
应用部分
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define LEDON 1 // 根据驱动的switch编写
#define LEDOFF 0
int main(int argc,char *argv[])
{
int fd;
//打开设备节点
fd = open("/dev/platform_misc",O_RDWR);
if(fd < 0)
{
//打开设备节点失败
perror("open error \n");
return fd;
}
while(1)
{
ioctl(fd,LEDON,0);
sleep(1);
ioctl(fd,LEDOFF,0);
sleep(1);
}
close(fd);
}
实验效果:LED灯闪烁