platform-GPIO-driver分析使用
一、gpio-user.c代码分析
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#define GPIO_U_IOCTL_BASE 'x'
#define GPIOC_OPS _IOWR(GPIO_U_IOCTL_BASE,0,int)
#define MAX_GPIO_NR 32
/* 设置数据结构体 */
struct gpio_user_data{
const char *label;
bool input;
unsigned gpio;
unsigned dft;
};
/* 设置杂项设备结构体 */
static struct gpio_misc{
struct miscdevice misc;
struct gpio_user_data *data;
int gpio_count;
} *gpio_misc;
/* open函数 */
static int gpio_user_open(struct inode *inodp, struct file *filp)
{
if(!gpio_misc)
return -ENODEV;
return 0;
}
/* 释放函数 */
static int gpio_user_release(struct inode *inodp, struct file *filp)
{
if(!gpio_misc)
return -ENODEV;
return 0;
}
/* ioctl函数 */
static long gpio_user_ioctl(struct file *filp, unsigned int cmd,unsigned long arg)
{
int no, offset;
unsigned long val;
unsigned long __user *p = (void __user *)arg;
struct gpio_user_data *data;
unsigned long get_value;
if(!gpio_misc)
return -ENODEV;
data = gpio_misc->data;
if(_IOC_TYPE(cmd) != GPIO_U_IOCTL_BASE) //反向解析用户空间的数据,幻术是否存在。
return -EINVAL;
switch(_IOC_NR(cmd)){ //反向解析用户空间传入的编号,0执行下面的函数,其他返回报错
case 0:
if(get_user(val,p)) //get_user 将p指向的地址的值赋给val,成功返回0
return -EFAULT;
//这的操作比较sao,用了一个隐藏规则,在32位系统下,16进制表示下的最高位32在数组下标里被忽略,猜测是符号位被忽略。16进制可一转化为10进制,效果相同,举例见附录
if(data[val].input){ //第1个gpio传入的值如果为0x80000001,则val=1,将最高位32位作为value值,很妙
val = gpio_get_value(data[val].gpio); //得到对应gpio的值
put_user(val,p); //传给用户空间
} else {
gpio_set_value(data[val].gpio,val >> 31); //如果val值为0x80000001,那么替换后为data[1].gpio,1
}
break;
default:
return -ENOTTY;
}
return 0;
}
static const struct file_operations gpio_user_fops = {
.owner = THIS_MODULE,
.open = gpio_user_open,
.release = gpio_user_release,
.unlocked_ioctl = gpio_user_ioctl,
};
static void gpio_user_init_default(void)
{
int i,ret;
struct gpio_user_data *data; //构建一个结构体
data = gpio_misc->data; //data指向设备的数据
for(i = 0;i < gpio_misc->gpio_count;i++){ //有几个gpio执行几遍,首先request,然后设置方向
if(!gpio_is_valid(data[i].gpio)){ //检查合法性,不合法返回零
continue;
}
ret = gpio_request(data[i].gpio,data[i].label); //请求gpio资源
if(ret < 0){
continue;
}
if(data[i].input)
{
gpio_direction_input(data[i].gpio); //设置为输入
}
else
{
gpio_direction_output(data[i].gpio,data[i].dft); //设置为输出
}
}
}
static void gpio_user_free_default(void)
{
int i;
struct gpio_user_data *data;
data = gpio_misc->data;
for(i = 0;i < gpio_misc->gpio_count;i++){
if(!gpio_is_valid(data[i].gpio)){
continue;
}
gpio_free(data[i].gpio); //释放声明的gpio
}
}
static int gpio_user_probe(struct platform_device *pdev)
{
int index;
struct device_node *node = pdev->dev.of_node, *child; //创建一个设备节点结构体,指向platform_device里的节点(即设备树节点)
gpio_misc = devm_kzalloc(&pdev->dev,sizeof(*gpio_misc),GFP_KERNEL); //为杂项设备开辟内存空间,返回一个开辟的空间地址
if(!gpio_misc){ //开辟失败返回0,成功返回非0,如果失败则返回错误
return -ENOMEM;
}
gpio_misc->gpio_count = of_get_available_child_count(node); //获得node里的子节点个数
if(!gpio_misc->gpio_count){ //获取失败返回0,成功返回非0,如果失败则返回错误
return -ENODEV;
}
if(gpio_misc->gpio_count > MAX_GPIO_NR){ //子节点个数不超过32个
gpio_misc->gpio_count = MAX_GPIO_NR;
}
/* 分配空间大小为struct gpio_user_data结构体大小 乘以 gpio_misc->gpio_count子节点个数,返回一个开辟的空间地址*/
gpio_misc->data = devm_kzalloc(&pdev->dev,sizeof(struct gpio_user_data) * gpio_misc->gpio_count,GFP_KERNEL);
if(!gpio_misc->data){
return -ENOMEM;
}
index = 0;
for_each_available_child_of_node(node,child){ //这个函数遍历子节点,有几个子节点执行几遍
const char *input;
struct gpio_user_data *data = &gpio_misc->data[index++]; //index++,先等于0,然后再加1,data指向gpio_misc->data0
data->label = of_get_property(child,"label",NULL) ? : child->name; //获得label后,返回label值。如果没有获得,则data->label = child->name,即等于子节点名字
input = of_get_property(child,"default-direction",NULL) ? : "in"; //有设置default-direction则返回给input,没有设置默认为in
if(strcmp(input,"in") == 0) //input等于in就设置为true
data->input = true;
data->gpio = of_get_gpio_flags(child,0,&data->dft); //获取gpio编号给data->gpio
}
gpio_user_init_default();
gpio_misc->misc.name = "gpio"; //注册杂项设备
gpio_misc->misc.minor = MISC_DYNAMIC_MINOR;
gpio_misc->misc.fops = &gpio_user_fops;
return misc_register(&gpio_misc->misc);
}
static int gpio_user_remove(struct platform_device *pdev)
{
gpio_user_free_default();
misc_deregister(&gpio_misc->misc);
return 0;
}
static const struct of_device_id of_gpio_user_id_table[] = {
{ .compatible = "gpio-user",},
{},
};
MODULE_DEVICE_TABLE(of,of_gpio_user_id_table);
static struct platform_driver gpio_user_driver = {
.probe = gpio_user_probe,
.remove = gpio_user_remove,
.driver = {
.owner = THIS_MODULE,
.name = "gpio-user",
.of_match_table = of_gpio_user_id_table,
},
};
module_platform_driver(gpio_user_driver);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:gpio-user");
二、gpio-test.c应用代码分析
#include <stdio.h>
#include <stdlib.h>
#include <linux/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#define GPIO_U_IOCTL_BASE 'x' //宏定义幻数
#define GPIOC_OPS _IOWR(GPIO_U_IOCTL_BASE,0,int) /*_IOWR(type,nr,size), type可以为任意 char 型字符,例如
‘x’,其主要作用是使 ioctl 命令有唯一的设备标识;
nr命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
第三个参数 arg ,14bit,指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;*/
#define GPIO_SET(no,state) ( no | (state << 31))
#define GPIO_GET(val) (val >> 31)
void gpio_set_value(int fd,int gpio_no,int state)
{
unsigned long val;
val = (!!state << 31) | gpio_no; //将第三个参数的值判断真假,真为1,假为0,左移31位或上第四个参数(在内核驱动中会做处理)
if(ioctl(fd,GPIOC_OPS,&val) < 0){ //用ioctl命令将值封装发到内核
perror("ioctl");
}
}
int gpio_get_value(int fd,int gpio_no)
{
unsigned long val = gpio_no;
if(ioctl(fd,GPIOC_OPS,&val) < 0){
perror("ioctl");
}
return val;
}
int main(int argc,char **argv)
{
int gpio;
gpio = open("/dev/gpio",O_RDWR); //打开节点
if(gpio < 0){
perror("open");
exit(1);
}
int no,state;
unsigned long val;
no = atoi(argv[2]); //获取第三个参数赋给no
if(strcmp(argv[1],"in") == 0){ //如果第二个参数是in,则打印现在的值
printf("gpio %d state %d\n",no,gpio_get_value(gpio,no));
} else {
state = atoi(argv[3]); //获取第三个参数
gpio_set_value(gpio,no,state); //设置第三个参数值为第三个参数。
}
close(gpio);
return 0;
}
附录:
因为是64位,所以左移63,可以验证猜想。
三、下面对GPIO-platform-driver做一个整体分析:
- 首先需要搭建平台驱动框架
static const struct of_device_id of_gpio_user_id_table[] = {
{ .compatible = "gpio-user",}, //设备树匹配名字
{},
};
MODULE_DEVICE_TABLE(of,of_gpio_user_id_table);
static struct platform_driver gpio_user_driver = {
.probe = gpio_user_probe, //加载时执行函数
.remove = gpio_user_remove, //卸载是执行函数
.driver = {
.owner = THIS_MODULE,
.name = "gpio-user", //设备树匹配名字,优先级第二
.of_match_table = of_gpio_user_id_table, //设备树匹配,优先级最高
},
};
module_platform_driver(gpio_user_driver);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:gpio-user");
-
下面需要设置必要的结构体:
- 设置gpio杂项设备结构体
/* 设置杂项设备结构体 */ static struct gpio_misc{ struct miscdevice misc; struct gpio_user_data *data; int gpio_count; } *gpio_misc;
里面需要有:杂项设备,gpio的相关数据,gpio个数。其实总结就是两个,一个杂项设备,一个gpio的数据
- 设置gpio的数据结构体,里面设置会用到的数据,包括设备树里面要获取的数据
struct gpio_user_data{ const char *label; bool input; unsigned gpio; unsigned dft; };
-
写probe
/* 分配 */
struct device_node *node = pdev->dev.of_node, *child; //创建一个设备节点结构体,指向platform_device里的节点(即设备树节点) gpio_misc = devm_kzalloc(&pdev->dev,sizeof(*gpio_misc),GFP_KERNEL); //为杂项设备platform-device的dev开辟内存空间,返回一个开辟的空间地址 /* 分配空间大小为struct gpio_user_data结构体大小 乘以 gpio_misc->gpio_count子节点个数,返回一个开辟的空间地址*/ gpio_misc->data = devm_kzalloc(&pdev->dev,sizeof(struct gpio_user_data) * gpio_misc->gpio_count,GFP_KERNEL);
/* 设置 */
//从设备树里面获取信息,填充到杂项设备结构体成员 gpio_misc->gpio_count = of_get_available_child_count(node); //获得node里的子节点个数 if(!gpio_misc->gpio_count){ //获取失败返回0,成功返回非0,如果失败则返回错误 return -ENODEV; } if(gpio_misc->gpio_count > MAX_GPIO_NR){ //子节点个数不超过32个 gpio_misc->gpio_count = MAX_GPIO_NR; } for_each_available_child_of_node(node,child){ //这个函数遍历子节点,有几个子节点执行几遍 const char *input; struct gpio_user_data *data = &gpio_misc->data[index++]; //index++,先等于0,然后再加1,data指向gpio_misc->data0 data->label = of_get_property(child,"label",NULL) ? : child->name; //获得label后,返回label值。如果没有获得,则data->label = child->name,即等于子节点名字 input = of_get_property(child,"default-direction",NULL) ? : "in"; //有设置default-direction则返回给input,没有设置默认为in if(strcmp(input,"in") == 0) //input等于in就设置为true data->input = true; data->gpio = of_get_gpio_flags(child,0,&data->dft); //获取gpio编号给data->gpio } //做相关硬件的初始化 gpio_user_init_default();
/* 注册 */
gpio_misc->misc.name = "gpio"; //注册杂项设备 gpio_misc->misc.minor = MISC_DYNAMIC_MINOR; //设备号分配宏 gpio_misc->misc.fops = &gpio_user_fops; //设置文件操作函数
-
编写文件操作函数
//用到什么函数就加上什么函数 static const struct file_operations gpio_user_fops = { .owner = THIS_MODULE, .open = gpio_user_open, //打开 .release = gpio_user_release, //释放 .unlocked_ioctl = gpio_user_ioctl, //ioctl };
file_operations函数如下:
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*mremap)(struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif };
添加自己需要用到的函数
本次讲解没有用到gpio中断,后续可能会更新相关部分