在实际开发中,底层驱动往往要暴露一些接口供上层应用,比如需要上层对一个gpio进行操作,应用层没办法直接去控制gpio,只能通过驱动层来间接调用,方式有很多,可以将gpio封装到一个字符设备中,或者直接用misc类注册等等,因linux下一切皆文件,我们也可以在驱动层将gpio封装成文件,让应用层操作文件方式来操作gpio;
想必做过mcu开发的朋友对AT指令不陌生,AT指令后面加 ‘?’ 号表示查询,加 ‘=’ 表示设置,同样的,在应用层或者adb下,我们可以通过cat与echo对一个文件进行读写,非常方便上层应用进行操作,所以本篇是为上层提供这样一个可以这样方便操作文件的方法,并且驱动代码尽量精简,方便模板化使用!
驱动中设备树是常用的,要使用一个gpio,首先要在设备树中添加相应节点,如下所示:
可以将该内容放到根节点下,就描述了下属性为"misc,ctrl",和一个gpio的引脚信息
&misc {
compatible = "misc,ctrl";
boot-gpio = <&pio 28 0>;
};
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//gpio设备结构体
struct gpio_dev{
int major; //设备号
struct class *cls; //类
struct device_node *nd; //设备节点
int boot_gpio; //gpio
};
struct gpio_dev gpio_msg;
//
//写函数,echo操作时调用的是该函数
static ssize_t boot_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int val = 0, rc = 0;
rc = kstrtouint(buf, 0, &val);
if (rc < 0) return rc;
if (val == 1) {
if (gpio_is_valid(gpio_msg.boot_gpio))
gpio_set_value_cansleep(gpio_msg.boot_gpio, 1);
}
else {
if (gpio_is_valid(gpio_msg.boot_gpio))
gpio_set_value_cansleep(gpio_msg.boot_gpio, 0);
}
return count;
}
//读函数,cat操作时调用的是该函数
static ssize_t boot_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t len = 0;
len += snprintf(buf + len, PAGE_SIZE - len, "boot-pin=%d\n",
gpio_get_value(gpio_msg.boot_gpio));
return len;
}
//DEVICE_ATTR是个宏,展开就是定义了一个dev_attr_boot的结构体
//第二个参数是文件所有者/用户组/其它的权限设置,第三个和第四个参数是读写回调函数
static DEVICE_ATTR(boot, S_IRWXU | S_IRWXUGO, boot_show, boot_store);
//
struct file_operations my_test_ops = {
.owner = THIS_MODULE,
};
static int gpio_msg_probe(struct platform_device *dev)
{
int ret = 0;
struct device *mydev;
pr_info("%s: enter\n", __func__);
//从设备树跟节点根据"misc,ctrl"属性查找节点
gpio_msg.nd = of_find_compatible_node(NULL, NULL, "misc,ctrl");
if(gpio_msg.nd == NULL) {
printk("beep node not find!\r\n");
return -EINVAL;
}
//在节点下查找"boot-gpio"名字的gpio号
gpio_msg.boot_gpio = of_get_named_gpio(gpio_msg.nd, "boot-gpio", 0);
if((gpio_msg.boot_gpio < 0)) {
printk("can't get gpio_msg-gpio");
return -EINVAL;
}
//请求使用gpio,也是为了告诉别人改gpio被我承包了,然后设置gpio输出0
ret |= gpio_request(gpio_msg.boot_gpio, "boot_gpio28");
ret |= gpio_direction_output(gpio_msg.boot_gpio, 0);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
//下面就是字符设备申请设备号、创建类、创建设备的那一套
gpio_msg.major = register_chrdev(0, "my_test", &my_test_ops);
gpio_msg.cls = class_create(THIS_MODULE, "gpio_class");
mydev = device_create(gpio_msg.cls, 0, MKDEV(gpio_msg.major,0), NULL, "gpio-ctrl");
//在mydev创建的gpio-ctrl目录下创建文件
if(sysfs_create_file(&(mydev->kobj), &dev_attr_boot.attr)){
printk("%s: sysfs_create_file fail\n", __func__);
return -1;
}
pr_info("%s: success\n", __func__);
return 0;
}
static int gpio_msg_remove(struct platform_device *dev)
{
gpio_set_value(gpio_msg.boot_gpio, 0);
gpio_set_value(gpio_msg.reset_gpio, 0);
return 0;
}
static const struct of_device_id gpio_of_match[] = {
{
.compatible = "misc,ctrl"},
{
/* Sentinel */ }
};
static struct platform_driver gpio_driver = {
.driver = {
.name = "mt8163-gpio", /* 驱动名字,用于和设备匹配 */
.of_match_table = gpio_of_match, /* 设备树匹配表 */
},
.probe = gpio_msg_probe,
.remove = gpio_msg_remove,
};
module_platform_driver(gpio_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("mstar");
source build/envsetup.sh
lunch 项目选项
make bootimage -j8
adb shell
因为我们创建了class,所以如果正常的话在/sys/class下会有我们所创建的文件
cd /sys/class
gpio_class就是我们通过class_create创建的类,gpio-ctrl是我们通过device_create创建的设备,而其下的其它文件则是sysfs_create_file函数创建的,而我们提供给应用层的boot文件也在其中,如下:
ls -l
//驱动中如下这条语句就设置了权限,可读可写可执行
static DEVICE_ATTR(boot, S_IRWXU | S_IRWXUGO, boot_show, boot_store);
可以看到各文件的详细信息,包括所有者权限,注意到了没有
cat boot时会调用boot_show函数
echo x > boot时会调用boot_store
如下所示,读写都正常.