前言
本篇章主要实现GPIO驱动,并控制led灯,同理可以实现类似的蜂鸣器或者继电器的控制。内容涉及到如何定制化/sys/xxx/xxx节点接口,以及字符设备的注册使用,并些出测试层序测试验证驱动。
一、如何定制化:/sys/xxx/xxx接口
1.kobject_create_and_add函数的使用
Linux2.6内核以后引入了sysfs文件系统。sysfs被看成是与proc同类型的文件系统。sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问。sysfs 文件系统总是被挂载在 /sys 挂载点上。kobject_create_and_add函数动态创建一个kobject结构并注册到sysfs。实际可以理解成在 /sys下新添加了一个文件夹。
1.1 在/sys下新添加一个文件:
在 /sys下新添加了一个名字为“test”文件夹
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>
static struct kobject *kobj;
static int kobject_test_init(void){
kobj = kobject_create_and_add("test", NULL);
return 0;
}
static void kobject_test_exit(void){
kobject_del(kobj);
}
module_init(kobject_test_init);
module_exit(kobject_test_exit);
MODULE_LICENSE("GPL v2");
测试结果:
rk3566_rgo:/sys # ls -l
drwxr-xr-x root root 2023-06-13 09:31 test
drwxr-xr-x root root 2023-06-13 05:02 class
1.2 在/sys下新添加嵌套文件:
在/sys下新添加嵌套文件,目录下创建"test/test"目录,修改代码如下
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/init.h>
static struct kobject *kobj;
static struct kobject *ckobj;
static int kobject_test_init(void){
kobj = kobject_create_and_add("test", NULL);
ckobj = kobject_create_and_add("test", kobj);
return 0;
}
static void kobject_test_exit(void){
kobject_del(ckobj);
kobject_del(kobj);
}
module_init(kobject_test_init);
module_exit(kobject_test_exit);
MODULE_LICENSE("GPL v2");
测试结果:
rk3566_rgo:/sys/test # ls
test
上述只是测试一些demo,实际的我实现的部分代码如下:
struct kobject * gpio_obj;
gpio_obj = kobject_create_and_add("rk3566_led", NULL);
if(gpio_obj == NULL){
DBG("kobject_create_and_add error\n");
return -EINVAL;
}
最后在sys文件下新添加了rk3566_led的文件夹:
rk3566_rgo:ls /sys
rk3566_led
我们现在只是添加了文件夹但是具体的接口上是没有可以控制的节点的,还需要借助于sysfs_create_file函数创建控制节点。
2. sysfs_create_file函数的使用
sysfs 操作表包括 store 和 show 两个函数,在用户态读取数据时,调用 show 函数将指定属性值存入 buffer 中返回给用户态,与之相反 store 存储用户态传入的属性值。在 /sys 文件系统中,通过 echo/cat 的操作,最终会调用到 show/store 函数,而这两个函数的具体实现可以是可以放置到驱动程序,调用情况如下:
获取该文件的句柄 | fd=open(“dev”)->vfs_open(“dev”)->sysfs_open(“dev”) |
---|---|
读操作 | read()->vfs_read()->sysfs_read_file()->sysfs_ops->show() |
写操作 | write()->vfs_write()->sysfs_write_file()->sysfs_ops->store () |
/sys挂载了sysfs文件系统,因此所有对/sys目录下的文件或者目录的操作都会通过sysfs文件的接口进行访问。
2.1 部分代码实现
static ssize_t gpio_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) {
unsigned long on = simple_strtoul(buf, NULL, 10);
if(!strcmp(attr->attr.name, "rk3566_led_value")){
if(on){
gpio_direction_output(rk3566_led_ID, 1);
DBG("gpio_direction_output is %d \n",rk3566_led_ID);
}
else{
gpio_direction_output(rk3566_led_ID, 0);
DBG("gpio_direction_output is 0\n");
}
}
return count;
}
static ssize_t gpio_show(struct device *dev, struct device_attribute *attr,char *buf){
int tmp = 0;
if(!strcmp(attr->attr.name, "rk3566_led_value")){
tmp = gpio_get_value(rk3566_led_ID);
DBG("gpio_get_value is %d \n",tmp);
if(tmp>0)
return strlcpy(buf, "1\n", 3);
else
return strlcpy(buf, "0\n", 3);
}
return 0;
}
// S_IRWXU | S_IRWXG 读写权限
static DEVICE_ATTR(rk3566_led_value, S_IRWXU | S_IRWXG, gpio_show,gpio_store);
static int rk3566_led_probe(struct platform_device *pdev)
{
int ret;
struct kobject * gpio_obj;
rk3566_led_ID = get_gpio_num(rk3566_led);
if(!gpio_is_valid(rk3566_led_ID)){
dev_err(&pdev->dev, "invalid power gpio%d\n", rk3566_led_ID);
}
ret = devm_gpio_request(&pdev->dev, rk3566_led_ID, "rk3566_led");
if (ret) {
dev_err(&pdev->dev,"failed to request GPIO%d for rk3566_led \n",rk3566_led_ID);
//return -EINVAL;
}
gpio_obj = kobject_create_and_add("rk3566_led", NULL);
if(gpio_obj == NULL){
DBG("kobject_create_and_add error\n");
return -EINVAL;
}
ret = sysfs_create_file(gpio_obj,&dev_attr_rk3566_led_value.attr);
if (ret) {
DBG("sysfs_create_file error\n");
return ret;
}
CharDevInit("1234");
return 0;
}
最后在sys文件下新添加了rk3566_led的文件夹并且有一个控制节点“rk3566_led_value”。
rk3566_rgo:ls /sys/rk3566_led
rk3566_led_value
通过echo和cat命令可以控制和获取gpio状态。
rk3566_rgo:echo 1 > /sys/rk3566_led/rk3566_led_value
rk3566_rgo:cat /sys/rk3566_led/rk3566_led_value
二、字符设备注册:/dev/xxx
2.1 字符设备注册过程
2.1.1、为字符设备申请设备号,包括主设备号和次设备号:
a、使用内核自动分配设备号函数:
alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name);
b、向内核注册自定义设备号
register_chrdev_region(dev_t from,unsigned count,const char * name);
分配设备号函数alloc_chrdev_region与register_chrdev_region使用其中一个即可。
c、卸载模块时,注销设备号
unregister_chrdev_region(dev_t from,unsigned count);
2.1.2、向内核申请cdev结构体空间,用于描述字符设备
struct cdev *cdev_alloc(void)
2.1.3、初始化cdev结构体,初始化操作函数集
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
2.1.4、向内核注册字符设备,count表示注册的个数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
2.1.5、驱动卸载时,注销字符cdev结构体,释放系统资源
void cdev_del(struct cdev *p)
2.2 部分代码:
dev_t devNo;
struct cdev *pCDev;
struct class *pClass;
struct device *pDevice;
static int rk3566_led_ID;
static int Rk3566_led_Open(struct inode *inode, struct file *filp){
DBG("CharDevOpen\n");
return 0;
}
static ssize_t Rk3566_led_Read (struct file *file, char __user *buf,size_t cnt, loff_t *offt){
int ret = 0;
int len = 3;
char Buf[len];
memset(Buf, '0',len);//初始化数组元素全部为'0'
Buf[0] = gpio_get_value(rk3566_led_ID)+'0';
ret = copy_to_user(buf,Buf,len);
if (!ret) {
DBG("kernel copy_to_user \n");
} else {
DBG("kernel copy_to_user fail, %d\n", ret);
}
return len;
}
static ssize_t Rk3566_led_Write (struct file *file, const char __user *buf,size_t cnt, loff_t *offt){
int ret = 0;
int len = 3;
char kernelBuf[len];
memset(kernelBuf, '0',len);//初始化数组元素全部为'0'
ret = copy_from_user(kernelBuf, buf, len);
if (!ret) {
ret = len;
DBG("kernel copy_from_user %s \n", kernelBuf);
} else {
ret = 0;
DBG("kernel copy_from_user fail, %d\n", ret);
}
if (kernelBuf[0]=='o' && kernelBuf[1]=='n'){
gpio_direction_output(rk3566_led_ID, 1);
DBG("led on !!\n");
}else{
gpio_direction_output(rk3566_led_ID, 0);
DBG("led off !!\n");
}
return ret;
}
static int Rk3566_led_Release(struct inode *inode, struct file *filp){
DBG("Rk3566 led Release");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = Rk3566_led_Open,
.read = Rk3566_led_Read,
.write = Rk3566_led_Write,
.release = Rk3566_led_Release,
};
static int CharDevInit(char *DEV_NAME)
{
int ret;
#if 1 //动态申请
ret = alloc_chrdev_region(&devNo, 0, 1, DEV_NAME);
if (0 > ret) {
DBG( " alloc_chrdev_region fail\n");
goto err1;
}
#else //静态申请
#define CHRDEV_MAJOR 200
devNo = MKDEV(CHRDEV_MAJOR, 0);
ret = register_chrdev_region(devNo, 1, DEV_NAME);
if (0 > ret) {
DBG( " register_chrdev_region fail\n");
goto err1;
}
#endif
pCDev = cdev_alloc();
if (NULL == pCDev) {
DBG( " cdev_alloc fail\n");
ret = (-ENOMEM);
goto err2;
}
cdev_init(pCDev, &fops);
pCDev->owner = THIS_MODULE;
ret = cdev_add(pCDev, devNo, 1);
if (0 > ret) {
DBG( " cdev_alloc fail\n");
goto err3;
}
pClass = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(pDevice)){
DBG( " class_create fail\n");
ret = PTR_ERR(pClass);
goto err4;
}
pDevice = device_create(pClass, NULL, devNo, NULL, DEV_NAME);
if (IS_ERR(pDevice)) {
DBG( " device_create fail\n");
ret = PTR_ERR(pDevice);
goto err5;
}
DBG(" Init success dev:%s, MAJOR:%d, MINOR:%d.\n",
DEV_NAME, MAJOR(devNo), MINOR(devNo));
return 0;
err5:
class_destroy(pClass);
err4:
cdev_del(pCDev);
err3:
kfree(pCDev);
err2:
unregister_chrdev_region(devNo, 1);
err1:
return ret;
}
static int get_gpio_num(char *s){
int gpio_num = 0;
if(s[6]<91){
gpio_num = (s[4]-'0')*32+(s[6]-'A')*8+s[7]-'0';//大写
}
else{
gpio_num = (s[4]-'0')*32+(s[6]-'a')*8+s[7]-'0';//小写
}
DBG("gpio_num = %d\n",gpio_num);
return gpio_num;
}
卸载程序注销设备号
static void __exit rk3566_led_exit(void)
{
class_destroy(pClass);
cdev_del(pCDev);
kfree(pCDev);
unregister_chrdev_region(devNo, 1);
***
***
}
字符设备中的名字通过函数参数的方式封装起来,比较方便使用。最后在probe函数中调用。
static int rk3566_led_probe(struct platform_device *pdev)
{
****
****
CharDevInit("rk3566");
return 0;
}
字符设备注册的节点:
rk3566_rgo: ls /dev/1234
2.3 完整驱动程序
/* SPDX-License-Identifier: GPL-2.0 */
#include <dt-bindings/gpio/gpio.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <linux/slab.h>
#include <linux/fs.h> //alloc_chrdev_region() file_operations()
#include <linux/cdev.h> //cdev_alloc()、cdev_init()、cdev_add()、cdev_del()
#include <linux/device.h> //class_create()
#include <linux/kdev_t.h> //MKDEV MINOR MAJORs
#include <linux/ide.h> //copy_to_user()、copy_from_user()、kfree()
#define rk3566_led "GPIO4_A6"
#define DEBUG
#ifdef DEBUG
#define DBG(fmt,arg...) printk("rk3566gpio: "fmt,##arg)
#else
#define DBG(fmt,arg...) do { } while (0)
#endif
dev_t devNo; //设备号 由主设备号12bit + 20bit次设备组成
struct cdev *pCDev; //字符设备
struct class *pClass;
struct device *pDevice;
static int rk3566_led_ID;
static int Rk3566_led_Open(struct inode *inode, struct file *filp){
DBG("CharDevOpen\n");
return 0;
}
static ssize_t Rk3566_led_Read (struct file *file, char __user *buf,size_t cnt, loff_t *offt){
int ret = 0;
int len = 3;
char Buf[len];
memset(Buf, '0',len);//初始化数组元素全部为'0'
Buf[0] = gpio_get_value(rk3566_led_ID)+'0';
ret = copy_to_user(buf,Buf,len);
if (!ret) {
DBG("kernel copy_to_user \n");
} else {
DBG("kernel copy_to_user fail, %d\n", ret);
}
return len;
}
static ssize_t Rk3566_led_Write (struct file *file, const char __user *buf,size_t cnt, loff_t *offt){
int ret = 0;
int len = 3;
char kernelBuf[len];
memset(kernelBuf, '0',len);//初始化数组元素全部为'0'
ret = copy_from_user(kernelBuf, buf, len);
if (!ret) {
ret = len;
DBG("kernel copy_from_user %s \n", kernelBuf);
} else {
ret = 0;
DBG("kernel copy_from_user fail, %d\n", ret);
}
if (kernelBuf[0]=='o' && kernelBuf[1]=='n'){
gpio_direction_output(rk3566_led_ID, 1);
DBG("led on !!\n");
}else{
gpio_direction_output(rk3566_led_ID, 0);
DBG("led off !!\n");
}
return ret;
}
static int Rk3566_led_Release(struct inode *inode, struct file *filp){
DBG("Rk3566 led Release");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = Rk3566_led_Open,
.read = Rk3566_led_Read,
.write = Rk3566_led_Write,
.release = Rk3566_led_Release,
};
static int CharDevInit(char *DEV_NAME)
{
int ret;
#if 1 //动态申请
ret = alloc_chrdev_region(&devNo, 0, 1, DEV_NAME);
if (0 > ret) {
DBG( " alloc_chrdev_region fail\n");
goto err1;
}
#else //静态申请
#define CHRDEV_MAJOR 200
devNo = MKDEV(CHRDEV_MAJOR, 0);
ret = register_chrdev_region(devNo, 1, DEV_NAME);
if (0 > ret) {
DBG( " register_chrdev_region fail\n");
goto err1;
}
#endif
pCDev = cdev_alloc();
if (NULL == pCDev) {
DBG( " cdev_alloc fail\n");
ret = (-ENOMEM);
goto err2;
}
cdev_init(pCDev, &fops);
pCDev->owner = THIS_MODULE;
ret = cdev_add(pCDev, devNo, 1);
if (0 > ret) {
DBG( " cdev_alloc fail\n");
goto err3;
}
pClass = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(pDevice)){
DBG( " class_create fail\n");
ret = PTR_ERR(pClass);
goto err4;
}
pDevice = device_create(pClass, NULL, devNo, NULL, DEV_NAME);
if (IS_ERR(pDevice)) {
DBG( " device_create fail\n");
ret = PTR_ERR(pDevice);
goto err5;
}
DBG(" Init success dev:%s, MAJOR:%d, MINOR:%d.\n",
DEV_NAME, MAJOR(devNo), MINOR(devNo));
return 0;
err5:
class_destroy(pClass);
err4:
cdev_del(pCDev);
err3:
kfree(pCDev);
err2:
unregister_chrdev_region(devNo, 1);
err1:
return ret;
}
static int get_gpio_num(char *s){
int gpio_num = 0;
if(s[6]<91){
gpio_num = (s[4]-'0')*32+(s[6]-'A')*8+s[7]-'0';//大写
}
else{
gpio_num = (s[4]-'0')*32+(s[6]-'a')*8+s[7]-'0';//小写
}
DBG("gpio_num = %d\n",gpio_num);
return gpio_num;
}
static ssize_t gpio_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) {
unsigned long on = simple_strtoul(buf, NULL, 10);
if(!strcmp(attr->attr.name, "rk3566_led_value")){
if(on){
gpio_direction_output(rk3566_led_ID, 1);
DBG("gpio_direction_output is %d \n",rk3566_led_ID);
}
else{
gpio_direction_output(rk3566_led_ID, 0);
DBG("gpio_direction_output is 0\n");
}
}
return count;
}
static ssize_t gpio_show(struct device *dev, struct device_attribute *attr,char *buf){
int tmp = 0;
if(!strcmp(attr->attr.name, "rk3566_led_value")){
tmp = gpio_get_value(rk3566_led_ID);
DBG("gpio_get_value is %d \n",tmp);
if(tmp>0)
return strlcpy(buf, "1\n", 3);
else
return strlcpy(buf, "0\n", 3);
}
return 0;
}
// S_IRWXU | S_IRWXG 读写权限
static DEVICE_ATTR(rk3566_led_value, S_IRWXU | S_IRWXG, gpio_show,gpio_store);
static int rk3566_led_probe(struct platform_device *pdev)
{
int ret;
struct kobject * gpio_obj;
rk3566_led_ID = get_gpio_num(rk3566_led);
if(!gpio_is_valid(rk3566_led_ID)){
dev_err(&pdev->dev, "invalid power gpio%d\n", rk3566_led_ID);
}
ret = devm_gpio_request(&pdev->dev, rk3566_led_ID, "rk3566_led");
if (ret) {
dev_err(&pdev->dev,"failed to request GPIO%d for rk3566_led \n",rk3566_led_ID);
//return -EINVAL;
}
gpio_obj = kobject_create_and_add("rk3566_led", NULL);
if(gpio_obj == NULL){
DBG("kobject_create_and_add error\n");
return -EINVAL;
}
ret = sysfs_create_file(gpio_obj,&dev_attr_rk3566_led_value.attr);
if (ret) {
DBG("sysfs_create_file error\n");
return ret;
}
CharDevInit("rk3566");
return 0;
}
static struct platform_device rk3566_led_device = {//无设备树下使用
.name = "led",
.id = -1,
};
static int rk3566_led_remove(struct platform_device *dev)
{
DBG("rk3566 led remove !\n");
return 0;
}
static struct platform_driver rk3566_led_driver= {
.driver = {
.name = "led",
.owner = THIS_MODULE,
},
.probe = rk3566_led_probe,
.remove = rk3566_led_remove,
};
static int __init rk3566_led_init(void)
{
platform_driver_register(&rk3566_led_driver);
platform_device_register(&rk3566_led_device);
return 0;
}
static void __exit rk3566_led_exit(void)
{
class_destroy(pClass);
cdev_del(pCDev);
kfree(pCDev);
unregister_chrdev_region(devNo, 1);
platform_driver_unregister(&rk3566_led_driver);
platform_device_unregister(&rk3566_led_device);
}
module_init(rk3566_led_init);
module_exit(rk3566_led_exit);
MODULE_AUTHOR("RK3566");
MODULE_DESCRIPTION("GPIO ctrl driver");
MODULE_LICENSE("GPL");
三、编写测试程序驱动功能
3.1 测试程序代码
rk3566ledtest.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define DEV_NAME "/dev/rk3566"
int main(int argc, char *argv[])
{
int ret;
int fd;
char buf[256] = {0};
fd = open(DEV_NAME, O_RDWR);
if (0 > fd) {
printf("open dev fail: %s\r\n", DEV_NAME);
perror("cause:");
return -1;
} else {
printf("open dev success: %s\r\n", DEV_NAME);
}
//1 读一下当前数据
ret = read(fd, buf, sizeof(buf));
printf("1 read ret:%d, buf:[%s]\r\n", ret, buf);
//2 写入自己的数据
if(atoi(argv[1])==1)
snprintf(buf, sizeof(buf), "on");
else
snprintf(buf, sizeof(buf), "off");
ret = write(fd, buf, strlen(buf));
printf("2 write ret:%d, len:%d, data:%s\r\n", ret, strlen(buf), buf);
//3 再次读数据,应该是步骤2写入的数据
memset(buf, 0x0, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
printf("3 read ret:%d, buf:[%s]\r\n", ret, buf);
close(fd);
return 0;
}
交叉编译可执行文件
ubuntu@ubuntu-desktop:~/rk/rk3566$ arm-linux-gnueabihf-gcc rk3566ledtest.c -o rk3566ledtest -static
将rk3566ledtest 文件p用adb工具push到vendor 分区,然后给权限 chmod 0777 rk3566ledtest
打开led:
./rk3566ledtest 1
关闭led:
./rk3566ledtest 0
3.2 测试结果
rk3566_rgo:/vendor # ./rk3566ledtest 1
open dev success: /dev/rk3566
1 read ret:3, buf:[000] //设置gpio前读取一次gpio状态与后面做对比
2 write ret:3, len:2, data:on//设置gpio的状态1
3 read ret:3, buf:[100]//从驱动读取回来的gpio的状态为1
rk3566_rgo:/vendor # ./1 0
open dev success: /dev/rk3566
1 read ret:3, buf:[100] //设置gpio前读取一次gpio状态与后面做对比
2 write ret:3, len:3, data:off //设置gpio的状态0
3 read ret:3, buf:[000] //从驱动读取回来的gpio的状态为0