一个小点
- platform 设备驱动是在驱动的分离与分层这样的软件思路下诞生的。
简介
platform 驱动框架分为总线、设备和驱动。
总线:是 Linux 内核提供的,不需要我们这些驱动程序员去管理。我们在编写驱动的时候只要关注于设备和驱动的具体实现即可。
设备、驱动:在没有设备树的 Linux 内核下,我们需要分别编写并注册 platform_device 和platform_driver,分别代表设备和驱动。在使用设备树的时候,设备的描述被放到了设备树中,因此 platform_device 就不需要我们去编写了,我们只需要实现 platform_driver 即可。当内核在解析设备树的时候会自动帮我们创建一个 platform_device 对象。
在编写基于设备树的 platform 驱动的时候我们需要注意一下几点:
1. 在设备树中创建设备节点
作用:描述设备信息。重点是要设置好compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动。
以LED灯为例:
led {
compatible = "alientek,led";
status = "okay";
default-state = "on";
led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
};
第 2行的 compatible 属性值为“ alientek,led”,因此一会在编写 platform 驱动的时候 of_match_table 属性表中要有“ alientek,led”。
2. 编写 platform 驱动的时候要注意兼容属性
在使用设备树的时候 platform 驱动会通过 of_match_table来保存兼容性值,也就是表明此驱动兼容哪些设备。所以 of_match_table 将会尤为重要。
例如:
static const struct of_device_id leds_of_match[] = {
{ .compatible = "alientek,led" }, /* 兼容属性 */
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, leds_of_match);
static struct platform_driver leds_platform_driver = {
.driver = {
.name = "zynq-led",
.of_match_table = leds_of_match,
},
.probe = leds_probe,
.remove = leds_remove,
};
第 1~4 行, of_device_id 表,也就是驱动的兼容表,是一个数组,每个数组元素为of_device_id 类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备。
第 2 行的 compatible 值为“ alientek,led”,驱动中的 compatible 属性和设备中的compatible 属性相匹配,因此驱动中对应的 probe 函数就会执行。
注意第 3 行是一个空元素,在编写 of_device_id 的时候最后一个元素一定要为空!
第 6 行,通过 MODULE_DEVICE_TABLE 声明一下 leds_of_match 这个设备匹配表。 注意这个宏一般用于热拔插设备动态地进行驱动的加载与卸载,例如 USB 类设备。
第 11 行, 将 leds_of_match 匹配表绑定到 platform 驱动结构体 leds_platform_driver中,至此我们就设置好了 platform 驱动的匹配表了。
3. 编写 platform 驱动
**当驱动和设备匹配成功以后就会执行 probe 函数。**我们需要在 probe 函数里面执行字符设备驱动那一套,当注销驱动模块的时候 remove 函数就会执行。
实验程序编写
1. 修改设备树文件
例如led设备树:
#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1
/dts-v1/;
#include "zynq-7000.dtsi"
#include "pl.dtsi"
#include "pcw.dtsi"
/ {
model = "Alientek ZYNQ Development Board";
chosen {
bootargs = "console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait";
stdout-path = "serial0:115200n8";
};
aliases {
ethernet0 = &gem0;
i2c0 = &i2c_2;
i2c1 = &i2c0;
i2c2 = &i2c1;
serial0 = &uart0;
serial1 = &uart1;
spi0 = &qspi;
};
memory {
device_type = "memory";
reg = <0x0 0x20000000>;
};
led {
compatible = "alientek,led";
status = "okay";
default-state = "on";
led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
};
};
第 1和第 2行,在设备树文件中定义了两个宏,这两个宏用来表示 GPIO 是高电平有效还是低电平有效, GPIO_ACTIVE_HIGH 表示高电平有效。
第 29~34行,加上了一个“ led-gpio”属性,它的值等于“ <&gpio0 7 GPIO_ACTIVE_HIGH>”, led-gpio 属性指定了 LED 灯所使用的 GPIO,在这里就是 GPIO0 的 IO07,低电平有效, 稍后编写驱动程序的时候会获取 led-gpio 属性的内容来得到 GPIO 编号,因为 gpio 子系统的 API 操作函数需要 GPIO 编号。
2. platform 驱动程序编写
新建名为leddriver.c 的驱动文件,在 leddriver.c 中输入如下所示内容:
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : leddriver.c
作者 :
版本 :
描述 : platform总线编程示例之platform驱动模块
其他 : 无
论坛 :
日志 :
***************************************************************/
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#define MYLED_CNT 1 /* 设备号个数 */
#define MYLED_NAME "myled" /* 名字 */
/* LED设备结构体 */
struct myled_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev结构体 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int led_gpio; /* GPIO号 */
};
static struct myled_dev myled; /* led设备 */
/*
* @description : 打开设备
* @param – inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int myled_open(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* @description : 向设备写数据
* @param – filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t myled_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
{
int ret;
char kern_buf[1];
ret = copy_from_user(kern_buf, buf, cnt); // 得到应用层传递过来的数据
if(0 > ret) {
printk(KERN_ERR "myled: Failed to copy data from user buffer\r\n");
return -EFAULT;
}
if (0 == kern_buf[0])
gpio_set_value(myled.led_gpio, 0); // 如果传递过来的数据是0则关闭led
else if (1 == kern_buf[0])
gpio_set_value(myled.led_gpio, 1); // 如果传递过来的数据是1则点亮led
return 0;
}
static int myled_init(struct device_node *nd)
{
const char *str;
int val;
int ret;
/* 从设备树中获取GPIO */
myled.led_gpio = of_get_named_gpio(nd, "led-gpio", 0);
if(!gpio_is_valid(myled.led_gpio)) {
printk(KERN_ERR "myled: Failed to get led-gpio\n");
return -EINVAL;
}
/* 申请使用GPIO */
ret = gpio_request(myled.led_gpio, "PS_LED0 Gpio");
if (ret) {
printk(KERN_ERR "myled: Failed to request led-gpio\n");
return ret;
}
/* 确定LED初始状态 */
ret = of_property_read_string(nd, "default-state", &str);
if(!ret) {
if (!strcmp(str, "on"))
val = 1;
else
val = 0;
} else
val = 0;
/* 将GPIO设置为输出模式并设置GPIO初始电平状态 */
gpio_direction_output(myled.led_gpio, val);
return 0;
}
/* LED设备操作函数 */
static struct file_operations myled_fops = {
.owner = THIS_MODULE,
.open = myled_open,
.write = myled_write,
};
/*
* @description : platform驱动的probe函数,当驱动与设备
* 匹配成功以后此函数就会执行
* @param - pdev : platform设备指针
* @return : 0,成功;其他负值,失败
*/
static int myled_probe(struct platform_device *pdev)
{
int ret;
printk(KERN_INFO "myled: led driver and device has matched!\r\n");
/* led初始化 */
ret = myled_init(pdev->dev.of_node);
if (ret)
return ret;
/* 初始化cdev */
ret = alloc_chrdev_region(&myled.devid, 0, MYLED_CNT, MYLED_NAME);
if (ret)
goto out1;
myled.cdev.owner = THIS_MODULE;
cdev_init(&myled.cdev, &myled_fops);
/* 添加cdev */
ret = cdev_add(&myled.cdev, myled.devid, MYLED_CNT);
if (ret)
goto out2;
/* 创建类class */
myled.class = class_create(THIS_MODULE, MYLED_NAME);
if (IS_ERR(myled.class)) {
ret = PTR_ERR(myled.class);
goto out3;
}
/* 创建设备 */
myled.device = device_create(myled.class, &pdev->dev,
myled.devid, NULL, MYLED_NAME);
if (IS_ERR(myled.device)) {
ret = PTR_ERR(myled.device);
goto out4;
}
return 0;
out4:
class_destroy(myled.class);
out3:
cdev_del(&myled.cdev);
out2:
unregister_chrdev_region(myled.devid, MYLED_CNT);
out1:
gpio_free(myled.led_gpio);
return ret;
}
/*
* @description : platform驱动模块卸载时此函数会执行
* @param - dev : platform设备指针
* @return : 0,成功;其他负值,失败
*/
static int myled_remove(struct platform_device *dev)
{
printk(KERN_INFO "myled: led platform driver remove!\r\n");
/* 注销设备 */
device_destroy(myled.class, myled.devid);
/* 注销类 */
class_destroy(myled.class);
/* 删除cdev */
cdev_del(&myled.cdev);
/* 注销设备号 */
unregister_chrdev_region(myled.devid, MYLED_CNT);
/* 删除地址映射 */
gpio_free(myled.led_gpio);
return 0;
}
/* 匹配列表 */
static const struct of_device_id led_of_match[] = {
{ .compatible = "alientek,led" },
{ /* Sentinel */ }
};
/* platform驱动结构体 */
static struct platform_driver myled_driver = {
.driver = {
.name = "zynq-led", // 驱动名字,用于和设备匹配
.of_match_table = led_of_match, // 设备树匹配表,用于和设备树中定义的设备匹配
},
.probe = myled_probe, // probe函数
.remove = myled_remove, // remove函数
};
/*
* @description : 模块入口函数
* @param : 无
* @return : 无
*/
static int __init myled_driver_init(void)
{
return platform_driver_register(&myled_driver);
}
/*
* @description : 模块出口函数
* @param : 无
* @return : 无
*/
static void __exit myled_driver_exit(void)
{
platform_driver_unregister(&myled_driver);
}
module_init(myled_driver_init);
module_exit(myled_driver_exit);
MODULE_AUTHOR("");
MODULE_DESCRIPTION("Led Platform Driver");
MODULE_LICENSE("GPL");
第 72~106 行,自定义函数 myled_init, 该函数的参数是 struct device_node 类型的指针,也就是 led 对应的设备节点,当调用函数的时候传递进来。
第 121~175 行, platform 驱动的 probe 函数 myled_probe,当设备树中的设备节点与驱动之间匹配成功以后此函数就会执行, 第 128 行调用 myled_init 函数时,将pdev->dev.of_node 作为参数传递到函数中, platform_device 结构体中内置了一个 structdevice 类型的变量 dev,在 struct device 结构体中定义了一个 struct device_node 类型的指针变量 of_node,使用设备树方式进行匹配的情况,当匹配成功之后, of_node 会指向设备树中定义的节点,所以在这里我们不需要通过调用of_find_node_by_path(“/led”)函数得到 led 的节点。我们原来在驱动加载函数里面做的工作现在全部放到 probe 函数里面完成。
第 182~202 行, platform 驱动的 remobe 函数 myled_remove,当 platform 驱动模块被卸载时此函数就会执行。在此函数里面释放内存、注销字符设备等,也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。
第 205~208 行,匹配表,描述了此驱动都和什么样的设备匹配,第 206 行添加了一条值为"alientek,led"的 compatible 属性值,当设备树中某个设备节点的 compatible 属性值也为“ alientek,led”的时候就会与此驱动匹配。
第 211~218 行, platform_driver 驱动结构体变量 myled_driver, 213 行设置这个platform 驱动的名字为“ zynq-led”,因此,当驱动加载成功以后就会在/sys/bus/platform/drivers/目录下存在一个名为“ zynq-led”的文件。第 214 行绑定platform 驱动的 of_match_table 表。
第 225~228 行, platform 驱动模块入口函数,在此函数里面通过platform_driver_register 向 Linux 内核注册一个 platform 驱动 led_driver。
第 235~238 行, platform 驱动驱动模块出口函数,在此函数里面通过platform_driver_unregister 从 Linux 内核卸载一个 platform 驱动 led_driver。
编写测试 APP
文件名ledApp.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
unsigned char buf[1];
if(3 != argc) {
printf("Usage:\n"
"\t./ledApp /dev/myled 1 @ close LED\n"
"\t./ledApp /dev/myled 0 @ open LED\n"
);
return -1;
}
/* 打开设备 */
fd = open(argv[1], O_RDWR);
if(0 > fd) {
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
/* 将字符串转换为int型数据 */
buf[0] = atoi(argv[2]);
/* 向驱动写入数据 */
ret = write(fd, buf, sizeof(buf));
if(0 > ret){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
/* 关闭设备 */
close(fd);
return 0;
}
编译测试文件:
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
MAKEFILE文件
KERN_DIR := /home/zynq/linux/kernel/linux-xlnx-xilinx-v2018.3
obj-m := leddriver.o
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` clean
make编译生成一个名为“ leddriver.o”的驱动模块文件。
测试
使用如下指令加载模块
depmod //第一次加载驱动的时候需要运行此命令
modprobe leddriver.ko //加载驱动模块
输入如下命令打开 LED 灯:文章来源:https://www.toymoban.com/news/detail-426138.html
./ledApp /dev/myled 1 //打开 LED 灯
在输入如下命令关闭 LED 灯:文章来源地址https://www.toymoban.com/news/detail-426138.html
./ledApp /dev/myled 0 //关闭 LED 灯
到了这里,关于基于设备树的platform驱动之LED(平台设备驱动)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!