STM32MP157A单片机移植Linux驱动深入版
需求整理
在Linux设备树中新增leds节点,其有3个gpio属性,分别表示PE10对应led1,PF10对应led2,PE8对应led3,设备树键值对如下:
leds {
led1-gpio = <&gpioe 10 0>;
led2-gpio = <&gpiof 10 0>;
led3-gpio = <&gpioe 8 0>;
};
内核驱动实现对灯控模块的初始化函数、模块退出函数、灯控模块各回调函数(open/release/unlocked_ioctl/read/write)。
应用程序实现对灯控模块的控制,通过ioctl函数控制led亮灭。
驱动开发逻辑分析
1.驱动初始化
注册字符设备 --> register_chrdev
申请一个struct class结构体,保存当前设备类的信息 --> class_create
申请一个struct device结构体,保存当前设备节点的信息 --> device_create
通过名称查找设备节点 --> of_find_node_by_name
2.初始化GPIO
获取GPIO编号 --> of_get_named_gpio
请求GPIO --> gpio_request
设置GPIO方向为输出 --> gpio_direction_output
3.ioctl回调函数
获取应用程序发送的值 --> copy_from_user
处理应用程序发送的功能码 --> 回调函数中的第二个参数的值(一般有应用程序通过ioctl命令发送)
4.驱动退出
释放GPIO --> gpio_free
注销字符设备文件 --> device_destroy
注销字符设备类 --> class_destroy
注销字符设备 --> unregister_chrdev
5.指定模块
指定模块初始化函数 --> module_init
指定模块注销函数 --> module_exit
应用程序开发逻辑分析
1.打开字符设备文件 --> open
2.发送功能码和值给驱动 --> ioctl
3.功能码 --> _IO() / _IOW / _IOR / _IOWR ...
详细代码
驱动程序 --> leds.c
#include <linux/init.h> // 包含内核初始化相关的头文件
#include <linux/module.h> // 包含内核模块相关的头文件
#include <linux/of.h> // 包含设备树操作相关的头文件
#include <linux/gpio.h> // 包含 GPIO 操作相关的头文件
#include <linux/of_gpio.h> // 包含设备树 GPIO 相关的头文件
#include <linux/fs.h> // 包含文件操作相关的头文件
#include <linux/uaccess.h> // 包含用户空间访问内核空间相关的头文件
#include <linux/device.h> // 包含设备相关的头文件
#include "leds.h" // 包含自定义头文件
/* 设备树节点定义
leds {
led1-gpio = <&gpioe 10 0>;
led2-gpio = <&gpiof 10 0>;
led3-gpio = <&gpioe 8 0>;
};
*/
static struct class *led_class;
static struct device *led_device;
static struct device_node *leds_node; // 定义设备节点指针
static char kernel_buf[100]; // 定义缓冲区
int led1_id,led2_id,led3_id; // 定义 GPIO 编号
int led_major; // 定义主设备号
static int led_open(struct inode *inode, struct file *file);
static int led_close(struct inode *inode, struct file *file);
static int led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
static int led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
struct file_operations fops = {
.open = led_open,
.release = led_close,
.unlocked_ioctl = led_ioctl,
.read = led_read,
.write = led_write,
};
static int led_open(struct inode *inode, struct file *file)
{
printk("led_open\n");
return 0;
}
static int led_close(struct inode *inode, struct file *file)
{
printk("led_close\n");
return 0;
}
static int led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
uint32_t n = copy_to_user(buf, kernel_buf, count);
if(n)
{
printk("copy_to_user failed\n");
return -EFAULT;
}
printk("led_read\n");
return 0;
}
static int led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
uint32_t n = copy_from_user(kernel_buf, buf, count);
if(n)
{
printk("copy_from_user failed\n");
return -EFAULT;
}
printk("led_write\n");
return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
//获取arg的值
unsigned int value;
if(copy_from_user(&value, (unsigned int *)arg, sizeof(unsigned int)))
{
printk("copy_from_user failed\n");
return -EFAULT;
}
switch(cmd)
{
case LED_ON:
switch(value)
{
case 1:
gpio_set_value(led1_id, 1);
break;
case 2:
gpio_set_value(led2_id, 1);
break;
case 3:
gpio_set_value(led3_id, 1);
break;
default:
printk("cmd error\n");
return -EINVAL;
}
break;
case LED_OFF:
switch(value)
{
case 1:
gpio_set_value(led1_id, 0);
break;
case 2:
gpio_set_value(led2_id, 0);
break;
case 3:
gpio_set_value(led3_id, 0);
break;
default:
printk("cmd error\n");
return -EINVAL;
}
break;
default:
printk("cmd error\n");
return -EINVAL;
}
printk("led_ioctl\n");
return 0;
}
//通过设备树的属性名查找gpio,并初始化gpio
static int mygpio_init(struct device_node *np ,const char *name)
{
int id;
printk("name=%s\n", name); // 打印属性名
id = of_get_named_gpio(np, name, 0); // 获取 GPIO 编号
if (id < 0) // 如果获取 GPIO 编号失败
{
printk("get gpio number failed\n"); // 打印获取 GPIO 编号失败的消息
return -ENODEV; // 返回错误码
}
printk("get gpio number success\n"); // 打印获取 GPIO 编号成功的消息
if(gpio_request(id, NULL)) // 请求 GPIO
{
printk("request gpio failed\n"); // 打印请求 GPIO 失败的消息
return -ENODEV; // 返回错误码
}
printk("request gpio success\n"); // 打印请求 GPIO 成功的消息
if(gpio_direction_output(id, 0)) // 设置 GPIO 方向为输出
{
printk("set gpio direction failed\n"); // 打印设置 GPIO 方向失败的消息
return -ENODEV; // 返回错误码
}
printk("set gpio direction success\n"); // 打印设置 GPIO 方向成功的消息
return id; // 返回 GPIO 编号
}
static int __init leds_init(void) // 模块初始化函数
{
//字符设备注册
led_major = register_chrdev(0, "leds_control", &fops);
if(led_major < 0)
{
printk("register_chrdev failed\n");
return -ENODEV;
}
printk("register_chrdev success:led_major = %d\n", led_major);
//申请一个struct class结构体,保存当前设备类的信息
led_class = class_create(THIS_MODULE, "leds_control");
if(IS_ERR(led_class))
{
printk("class_create failed\n");
unregister_chrdev(led_major, "leds_control"); // 注销字符设备
return -ENODEV;
}
printk("class_create success\n");
//申请一个struct device结构体,保存当前设备节点的信息
led_device = device_create(led_class, NULL, MKDEV(led_major, 0), NULL, "leds_control");
if(IS_ERR(led_device))
{
printk("device_create failed\n");
class_destroy(led_class); // 注销类
unregister_chrdev(led_major, "leds_control"); // 注销字符设备
return -ENODEV;
}
printk("device_create success\n");
leds_node = of_find_node_by_name(NULL, "leds"); // 通过名称查找设备节点
//leds_node = of_find_compatible_node(NULL, NULL, "sjh,mynode"); // 通过兼容字符串查找设备节点
if (!leds_node) // 如果未找到设备节点
{
printk("mynode node not found\n"); // 打印未找到节点的消息
device_destroy(led_class, MKDEV(led_major, 0)); // 注销设备
class_destroy(led_class); // 注销类
unregister_chrdev(led_major, "leds_control"); // 注销字符设备
return -ENODEV; // 返回错误码
}
printk("mynode node found\n"); // 打印找到节点的消息
led1_id = mygpio_init(leds_node, "led1_gpio"); // 控制 GPIO
led2_id = mygpio_init(leds_node, "led2_gpio"); // 控制 GPIO
led3_id = mygpio_init(leds_node, "led3_gpio"); // 控制 GPIO
/* printk("name=%s,value=%s\n", leds_node->properties->name, (char *)leds_node->properties->value); // 打印第一个属性的名称和值
printk("name=%s,value=%s\n", leds_node->properties->next->name, (char *)leds_node->properties->next->value); // 打印第二个属性的名称和值
printk("name=%s,value=%x %x\n", leds_node->properties->next->next->name, __be32_to_cpup((uint32_t *)leds_node->properties->next->next->value), __be32_to_cpup((uint32_t *)leds_node->properties->next->next->value + 1)); // 打印第三个属性的名称和值(无符号整数)
// 解析设备树的属性
binarry = of_find_property(leds_node, "binarry", &len); // 查找名为 "binarry" 的属性
if (!binarry) // 如果未找到属性
{
printk("binarry property not found\n"); // 打印未找到属性的消息
return -ENODEV; // 返回错误码
}
for (i = 0; i < len; i++) // 遍历属性值
{
printk("%02x ", *((unsigned char *)binarry->value + i)); // 打印属性值的每个字节
} */
return 0; // 返回成功
}
static void __exit leds_exit(void) // 模块退出函数
{
// 退出时执行的清理操作(当前为空)
gpio_free(led1_id); // 释放 GPIO
gpio_free(led2_id); // 释放 GPIO
gpio_free(led3_id); // 释放 GPIO
//字符设备注销
device_destroy(led_class, MKDEV(led_major, 0)); // 注销设备
class_destroy(led_class); // 注销类
unregister_chrdev(led_major, "leds_control"); // 注销字符设备
printk("exit\n"); // 打印退出消息
}
module_init(leds_init); // 指定模块初始化函数
module_exit(leds_exit); // 指定模块退出函数
MODULE_LICENSE("GPL"); // 指定模块许可证为 GPL
MODULE_AUTHOR("Johnson"); // 指定模块作者
MODULE_DESCRIPTION("leds driver"); // 指定模块描述
MODULE_VERSION("V1.0"); // 指定模块版本
应用程序 --> test_app.c
实现按1s间隔控制三个led灯亮灭
#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>
#include "leds.h" // 包含自定义头文件
int main(int argc,const char * argv[])
{
int fd;
int ret[3] = {1,2,3}; // 定义返回值
int value; // 定义变量
fd = open("/dev/leds_control", O_RDWR); // 打开设备文件
if(fd < 0) // 如果打开设备文件失败
{
perror("open"); // 打印错误信息
return -1; // 返回错误码
}
printf("open success\n"); // 打印打开设备文件成功的消息
while(1)
{
ioctl(fd, LED_ON, ret); // 打开 LED1
ioctl(fd, LED_ON, ret+1); // 打开 LED2
ioctl(fd, LED_ON, ret+2); // 打开 LED3
sleep(1); // 等待 1 秒
ioctl(fd, LED_OFF, ret); // 关闭 LED1
ioctl(fd, LED_OFF, ret+1); // 关闭 LED2
ioctl(fd, LED_OFF, ret+2); // 关闭 LED3
sleep(1); // 等待 1 秒
}
return 0;
}
头文件 --> leds.h
#ifndef __LEDS_H__
#define __LEDS_H__
#define LED_ON _IOW('l', 1, int)
#define LED_OFF _IOW('l', 0, int)
#endif