目录

8.2linux竞争与并发驱动LED灯原子操作实验_csdn

8.2linux竞争与并发驱动LED灯(原子操作实验)_csdn

本例程我们在第二十五章的 基于pinctrl与gpio子系统驱动LED灯实验 的基础上完成。在本节使用中我们使用原子操作来实现对 LED 这个设备的互斥访问,也就是一次只允许一个应用程序可以使用 LED 灯。

原子操作属于临界区,临界区必须保证一次只有一个线程访问。

1.修改设备树文件

这一步骤在前面已经有过,所以跟着前面文章的同学应该很熟悉!

在 stm32mp157d-atk.dts 文件的根节点“/”下创建 LED 灯节点,节点名为“gpioled”,节点内容如下:

https://i-blog.csdnimg.cn/img_convert/deb3f96424ed67b9eaa9b7f354b97d63.png

发现节点已经有了!

https://i-blog.csdnimg.cn/img_convert/81a8c13337c6b4480477fbdd91375acc.png

2.LED 灯驱动程序编写

之前的博客也是跟大家按照肌肉记忆来编写程序!一步一步按照思路来编写!

总代码会放在最后。

为了让大家更能明白,可以先对着总代码,进行对我的写代码流程更加详细得当!

放心,我也是一步一步打的代码,不是复制粘贴!!!

2.1头文件

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

2.2驱动入口函数和出口函数及MODULE函数

https://i-blog.csdnimg.cn/img_convert/be666ce48a1d775874352d2a324c2d2d.png

这里的MODULE_LICENCE要改为 MODULE_LICENSE 。

2.3创建设备结构体

在跟之前一样,为了后续的注册设备字符,要在开发板内核里面显示的话,要申请设备号,便包括主设备号和次设备号;为了显得更专业一点,就要使用结构体来统一有关设备的相关父子类结构体信息。后面也可以添加更多的子类信息!

https://i-blog.csdnimg.cn/img_convert/5fdde6d03e119e1f7451daa75e0e03af.png

这里看总代码就知道我放在哪个位置了!

dev_t 是一个用于表示设备编号的数据类型,它在 <sys/types.h> 头文件中被定义。

gpioled :这是一个变量名,它的类型是 struct gpioled_dev 。也就是说, gpioled 是一个 gpioled_dev 结构体类型的变量,通过这个变量可以存储和操作该结构体所定义的各种数据。

比如gpioled.devid,gpioled.major,gpioled,minor。等等。

后续有很多子类会在这里!

例如:

struct gpioled_dev { 
          dev_t devid; // 设备号
          struct cdev cdev; // 字符设备对象 
          struct class *class; // 设备类(sysfs接口)
          struct device *device; // 设备节点(/dev目录下) 
          int major; // 主设备号 
          int minor; // 次设备号 
          struct device_node *nd; // 设备树节点指针 
          int led_gpio; // GPIO编号(从设备树解析) };

2.4注册字符设备

这个很早就说了,在驱动入口就开始注册字符了,打开驱动就是注册!

https://i-blog.csdnimg.cn/img_convert/4409635fedcdc074b9fad9cd53f6fc4b.png

https://i-blog.csdnimg.cn/img_convert/1d61ceb41ea014fd9105dc744d0a369e.png

33行是先让主设备号为0,防止以前的实验占用及未注销的设备号继续利用。

34-41行分别以给定设备号和未给定设备号的情况进行编程。其中gpioled.major,gpioled.minor便是利用了结构体内的变量子类, MKDEV 是 Linux 内核提供的一个宏,用于根据主设备号和次设备号生成一个 dev_t 类型的设备编号。其定义通常位于 <linux/kdev_t.h> 头文件中, register_chrdev_region 函数的原型定义在 <linux/fs.h> 头文件中,若是给了设备号就是用这个函数,从gpio.devid所赋值的设备号告诉内核进行相应设备号的注册。第二个位置就是设备号个数,第三个位置就是设备号名称。

alloc_chrdev_region 不需要手动指定主设备号,内核会自动分配一个未被使用的主设备号给驱动程序。&gpioled.devid是指针类型,原型是 dev_t *dev ,要通过&取地址来存储设备号,第二个位置是起始的此设备号。

既然有创建字符设备,那就要有删除字符设备。

https://i-blog.csdnimg.cn/img_convert/a7bdaefc1c5d7076ad61de274d7c65ce.png

2.4.1补充设备结构体

完成字符设备注册后,就是在设备结构体中添加字符设备对象。

https://i-blog.csdnimg.cn/img_convert/f315dfe00a0ea4141bb7bfa11279eec1.png

2.5初始化cdev

很显然cdev也是变量gpioled的子类gpioled.cdev。同时在内核里面cdev里面包括很多子类信息。

这里是内核里面的定义。不用管,只要配置cdev的子类即可。

https://i-blog.csdnimg.cn/img_convert/1f9d206382bda6235e72d9059a7e2822.png

对cdev进行初始化,这个是字符设备对象。

https://i-blog.csdnimg.cn/img_convert/fad2390f9a803390c2f28b20be0b2095.png

gpioled.cdev.owner = THIS_MODULE; 的作用

  • 内核利用这个关联来管理模块的引用计数。当有用户空间程序对该字符设备进行操作(如打开、读写)时,内核会增加当前模块的引用计数,以确保在设备被使用期间模块不会被卸载。当所有使用该设备的操作结束后,内核会减少引用计数,当引用计数降为 0 时,模块可以被安全卸载。

cdev_init 函数的核心功能是对一个 struct cdev 结构体实例进行初始化,并且把一组文件操作函数(由 struct file_operations 结构体定义)和该字符设备关联起来。这样一来,当用户空间的程序通过文件操作接口(像 openreadwrite 等)对该字符设备进行操作时,内核就能调用相应的函数来处理这些请求。

2.5.1配置file_operations

同理,上面的&led_fops需要配置。

https://i-blog.csdnimg.cn/img_convert/32a42f47555a249d316884b74c16b79a.png

图片中还有些未进行修改 , 不用管,后面对字符添加操作集子类函数时改,先初始化cdev。

2.6添加cdev

https://i-blog.csdnimg.cn/img_convert/28e5f49a6d1aab2008d4d088c8caba44.png

cdev_add 函数的主要作用是将一个已经初始化好的字符设备( struct cdev 结构体实例)添加到内核的字符设备管理系统中,使该字符设备正式在系统中可用。一旦调用 cdev_add 成功,用户空间的程序就可以通过设备文件(通常位于 /dev 目录下)来访问这个字符设备。

即后续的./ledApp 可以执行到/dev

2.6.1注销字符设备对象

目前已经注册和注销了字符设备,同时也注册了字符设备对象,所以要进行字符设备对象的注销。

依然按照 逻辑顺序 进行注销。

https://i-blog.csdnimg.cn/img_convert/e5c9c623dcdaef99c4b922f68e59c3e8.png

2.7创建设备类和设备节点

2.7.1补充类与节点的定义

https://i-blog.csdnimg.cn/img_convert/284f8603e8a202335b07da8d27c8da4f.png

这两者作用在后续./dev/beep 1/0中创建/dev/beep。方便执行代码程序,传入到设备执行,关联设备号

2.7.2创建类

https://i-blog.csdnimg.cn/img_convert/290a7d9d426beff0cbcbdda2255ca700.png

其中定义

#define class_create(owner, name)

owner和结构体内的owner都是module模块的,一般来说是THIS_MODULE;name就是设备名字。

2.7.3创建设备节点

以便访问设备设备树根节点的信息,与下文的获取设备节点相互呼应。

https://i-blog.csdnimg.cn/img_convert/af9515ebfb88209e20d32e29d0f8b5e3.png

代码如下:

struct device *device_create(struct class *cls, struct device *parent,

                 dev_t devt, void *drvdata,const char *fmt, ...);

struct device *parent ,指向父设备的指针。如果该设备没有父设备,可以传入 NULL

dev_t :即本文的gpioled.devid。

void *drvdata ,是一个指向设备驱动私有数据的指针。可以传入自定义的数据结构指针,用于在设备驱动中存储和管理设备相关的信息,这里同样给NULL。

const char *fmt- fmt 是一个格式化字符串, ... 表示可变参数列表。类似于 printf 函数的用法,用于指定设备节点的名称。作用是指定要创建的设备节点在 /dev 目录下的名称,可以使用格式化字符串动态生成名称。

2.7.4补充错误信息

https://i-blog.csdnimg.cn/img_convert/c2065e13f9a9b1b1df5f3da81b27ef29.png

IS_ERR 宏通过比较指针的值和 (unsigned long)-MAX_ERRNO 的大小来判断该指针是否为错误指针。如果指针的值大于等于 (unsigned long)-MAX_ERRNO ,则认为它是一个==错误指针,返回 true ;==否则返回 false

PTR_ERR :当 IS_ERR(gpioled.cls) 返回 true 时,代码会执行 return PTR_ERR(gpioled.cls); ,这会将 gpioled.cls 转换为对应的错误码并返回给调用者。调用者可以根据这个错误码进行相应的错误处理,例如打印错误信息、释放已经分配的资源等。

同样:

https://i-blog.csdnimg.cn/img_convert/3e6e2b555e20b8377384dd9b70e6e574.png

2.7.5注销类和设备节点

https://i-blog.csdnimg.cn/img_convert/3e8757f2657fce0f4ffd88a5ff145bb1.png

2.7.6配置操作集函数

https://i-blog.csdnimg.cn/img_convert/fa8d101792c1f86b56d5c6c80e44b03f.png

复制上一节的操作函数集
static int dtsled_open(struct inode *inode, struct file *filp)
{
   filp->private_data = &dtsled;
   return 0;
}

static int dtsled_release(struct inode *inode, struct file *filp)
{
   struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
   return 0;
}
static ssize_t dtsled_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
   struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
   int retvalue;
   unsigned char databuf[1]; 
   retvalue = copy_from_user(databuf,buf,count);
   if (retvalue < 0)
   {
      printk("kernel write failed!\r\n");
      return -EFAULT;
   }

   /*判断开灯还是关灯*/
   led_switch(databuf[0]);
     return 0;
}

https://i-blog.csdnimg.cn/img_convert/ebf4acd32364eeeb4e8841afb5ab0e5d.png

2.8获取设备节点(设备树属性)

2.8.1配置设备树结构体

https://i-blog.csdnimg.cn/img_convert/2529aa64915c7774537089e951212107.png

2.8.2获取设备树节点

https://i-blog.csdnimg.cn/img_convert/e55aadb8656755f6d12e3d24a21a9d98.png

2.8.3获取led所对应的GPIO编号

https://i-blog.csdnimg.cn/img_convert/13c15dea8ce4b6ae1b7fea549910975f.png

这里知道了有关led驱动的gpio信息,仅仅是能知道信息,并没有驱动能力,所以要向内核申请权限来驱动gpio口。

2.8.4申请IO

https://i-blog.csdnimg.cn/img_convert/b222c1ca297a7f538be383ae3ed8fbd5.png

相应需要注销函数时释放,不然占用资源。

https://i-blog.csdnimg.cn/img_convert/7deadd8f788c9dc9b85a7bc636bc66ac.png

2.8.5使用IO,设置为输出

https://i-blog.csdnimg.cn/img_convert/9a14c61e54798f26fc75467c71c773d8.png

2.8.6配置错误处理

按照以前的习惯,从后面进行处理错误信息,按照逻辑!!!

https://i-blog.csdnimg.cn/img_convert/a38040a22c308f834a820ac4d55b3929.png

2.9配置私有数据进行write设备

提供宏定义给LEDAPP进行操作。

https://i-blog.csdnimg.cn/img_convert/251f73a60deeaba4f7e6356acd26701e.png

https://i-blog.csdnimg.cn/img_convert/5076b3a4bd151f7f9351bcaa348d883a.png

图中: struct gpioled_dev *dev = filp->private_data;struct gpioled_dev gpioled; /*LED*/

类似,其中可以看作gpioled为全局变量,而dev为局部变量。而私有变量写法要加箭头

dev = filp->private_data 这是个整体,在前面加个指针,后面要用这个就dev->给私有数据。

gpioled.led_gpiodev->led_gpio 一样调用。和全局变量,局部变量一样。

同理要在输出口设置关灯。

https://i-blog.csdnimg.cn/img_convert/7435079956f646142f376e64d588cb31.png

以上就是可以点亮led灯。

下面结合原子操作,编写相关代码程序!

2.10添加原子操作

原子操作属于临界区,临界区必须保证一次只有一个线程访问。

这里我们重点分析一下,因为这里有很多API函数,我们可以多拿几个分析。

2.10.1配置原子操作设备结构体

https://i-blog.csdnimg.cn/img_convert/eb55354b3598ea766fdeb340f532e853.png

2.10.2初始化原子操作

https://i-blog.csdnimg.cn/img_convert/1a08c4442251c17e959d26fcc1d87516.png

2.10.3配置操作函数

https://i-blog.csdnimg.cn/img_convert/98313269e1a68017386ebbc095445380.png

当原子变量没有被使用时,gpioled.lock=1,一旦被上图程序执行,会被减1。后面释放原子变量会回到1。

这里也可以用别的API函数,只要逻辑可以就行!(根据lock初始化为1,所以后面要回到1)

例如 :

static int led_open(struct inode *inode, struct file *filp)
 {
 /* 通过判断原子变量的值来检查 LED 有没有被别的应用使用 */
 if (!atomic_dec_and_test(&gpioled.lock)) {
 atomic_inc(&gpioled.lock);/* 小于 0 的话就加 1,使其原子变量等于 0 */
 return -EBUSY; /* LED 被使用,返回忙 */
 filp->private_data = &gpioled; /* 设置私有数据 */
}
 return 0; 
 }
 /*这里的atomic_dec_and_test(&gpioled.lock),假如没有被使用,gpioled.lock=1,则减1,变为0,返回真,又因为“!”,变为假,就不执行return -EBUSY;但是已经变为0,后面释放会加到1,如果还没释放就又要使用这个原子变量,就会执行这个if,跟上面的相反。*/
 /*这样就保证了每次就只允许一个应用使用 LED 灯。*/

这里加原子变量是因为限制一个应用进来操作。例如gpioled,beep等多个线程同时要执行,就要一个一个来,获得原子变量。原子变量是贯穿内核的。

https://i-blog.csdnimg.cn/img_convert/ad9affcc978a4a187aa9bce87582633b.png

测试结果:

./atomicApp /dev/gpioled 1 &//其中&表示在后台运行 atomicApp 这个软件
./atomicApp /dev/gpioled 0

我发现./atomicApp /dev/gpioled 1不可以执行到原子变量的效果,

但是发现/atomicApp /dev/gpioled 1&可以

2.10.4两种命令的区别
  • ./atomicApp /dev/gpioled 1 :这是一个在前台执行程序的命令。当你运行这个命令时,终端会被该程序占用,直到程序执行完毕才会返回命令提示符,你在程序执行期间无法在同一终端输入其他命令。

  • ./atomicApp /dev/gpioled 1 && 符号的作用是将程序放到后台执行。程序启动后,终端不会被其占用,你可以立即输入其他命令,同时该程序会在后台继续运行。

    https://i-blog.csdnimg.cn/img_convert/93218cec7e81ea2cf548fd38a09f3cd6.png

    可以看出, atomicApp 运行正常,输出了“App running times:1”和“App running times:2”等字符串,这就是模拟 25S 占用,说明 atomicApp 这个软件正在使用 LED 灯,假如在模拟占用25s内再输入命令./atomicApp /dev/gpioled 0关闭 LED 灯,则会失败。

    打开/dev/gpioled 失败!原因运行的 atomicAPP软件正在占用/dev/gpioled,如果再次运行atomicApp 软件去操作/dev/gpioled 肯定会失败。 必须等待atomicApp运行结束,也就是25S结束以后其他软件才能去操作/dev/gpioled 。这个就是采用原子变量实现一次只能有一个应用程序访问 LED 灯。

2.11总代码

gpioled.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_NAME "gpioled"
#define GPIOLED_CNT 1
#define LEDON 1
#define LEDOFF 0
/*gpioled设备结构体*/
struct gpioled_dev{
       dev_t devid; //dev_t 是一个用于表示设备编号的数据类型,设备号
       int major;//主设备号
       int minor;//次设备号
       struct cdev cdev;//设备关联,字符设备对象
       struct class *class;//设备类
       struct device *device;//设备节点
       struct device_node *nd;//设备树节点指针
       int led_gpio;//GPIO编号(从设备树上获取)
       atomic_t lock; /* 原子变量 */
};
struct gpioled_dev gpioled;//设备

static int led_open(struct inode *inode, struct file *filp)
{
   /*通过判断原子变量的值来检查 LED 有没有被别的应用使用*/
   if(atomic_read(&gpioled.lock) <= 0){
        return -EBUSY; /* LED 被使用,返回忙 */
   }else {
      atomic_dec(&gpioled.lock);//LED没有被使用,就减1,表示现在要用这个原子操作
   }
   filp->private_data = &gpioled;
   return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    struct gpioled_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候释放原子变量 */
    atomic_inc(&dev->lock);//加1变成1,表示用完,释放原子变量
   return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
     int ret;
     unsigned char databuf[1];
     struct gpioled_dev *dev=filp->private_data;
     ret = copy_from_user(databuf,buf,count);
     if(ret < 0){
        return -EINVAL;
     }
     if(databuf[0] == LEDON){
        gpio_set_value(dev->led_gpio,0);//开灯
     }else if(databuf[0] == LEDOFF){
        gpio_set_value(dev->led_gpio,1);//关灯
     }
     return 0;
}

/*操作集*/
static const struct file_operations led_fops = {
	.owner   = THIS_MODULE,
	.write   = led_write,
	.open    = led_open,
	.release = led_release,
};


/*驱动入口函数*/
static int __init led_init(void)
{
    int ret;
    /*1.注册字符设备*/
    gpioled.major = 0;
    if(gpioled.major){//若给定主设备号
         gpioled.devid = MKDEV(gpioled.major,0);//0为次设备号
         ret =  register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME);
    }else{//若给定主设备号
         ret =  alloc_chrdev_region(&gpioled.devid,0,GPIOLED_CNT,GPIOLED_NAME);
         //0为次设备号,起始的次设备号
         gpioled.major = MAJOR(gpioled.devid);
         gpioled.minor = MINOR(gpioled.devid);
    }
    if(ret < 0){
        goto fail_devid;
    }
    printk("major=%d minor=%d\r\n",gpioled.major,gpioled.minor);

    /*2.初始化cdev*/
    gpioled.cdev.owner= THIS_MODULE;//模块关联与引用计数管理
    cdev_init(&gpioled.cdev, &led_fops);//字符设备操作函数绑定

    /*3.添加cdev*/
    ret = cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);//即后续的./ledApp 可以执行到/dev
    if(ret < 0){
        goto fail_cdev;
    }
    
    /*4.创建类class*/
    gpioled.class = class_create(THIS_MODULE,GPIOLED_NAME);//创建/dev/beep
    if(IS_ERR(gpioled.class)){
        ret = PTR_ERR(gpioled.class);
        goto fail_class;
    }
    
    /*5.创建设备节点device*/
    gpioled.device = device_create(gpioled.class,NULL,gpioled.devid,NULL,GPIOLED_NAME);
    //创建/dev/beep
    if(IS_ERR(gpioled.device)){
        ret = PTR_ERR(gpioled.device);
        goto fail_device;
    }

    /*6.获取设备树节点*/
    gpioled.nd = of_find_node_by_path("/gpioled");//根节点
    if(gpioled.nd ==NULL ){
        ret = -EINVAL;
        goto fail_findnode;
    }

    /*7.获取gpioled根节点下的属性*/
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpio",0);
    if(gpioled.led_gpio < 0){
        printk("can't find led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
       }
       printk("led gpio num = %d\r\n",gpioled.led_gpio);

    /*8.申请IO*/
    ret = gpio_request(gpioled.led_gpio,"led-gpio");
    if(ret){
        printk("Failed to request the led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }

    /*9.设置IO口电气属性,设置为输出模式*/
    ret = gpio_direction_output(gpioled.led_gpio,1);//默认高电平
    if (ret < 0) {
        ret = -EINVAL;
        goto fail_setoutput;
    }

    /*10.初始化原子变量*/
    gpioled.lock = (atomic_t)ATOMIC_INIT(0);//gpioled.lock=0
    /*11.原子变量初始值为1*/
    atomic_set(&gpioled.lock, 1);
    
    return 0;
fail_setoutput:/*设置输出失败,释放已申请的IO,避免资源浪费*/
    gpio_free(gpioled.led_gpio);
fail_findnode:
    device_destroy(gpioled.class,gpioled.devid);
fail_device:
    class_destroy(gpioled.class);
fail_class:
    cdev_del(&gpioled.cdev);
fail_cdev:
    unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
fail_devid:
    return ret;
}

/*驱动出口函数*/
static void __exit led_exit(void)
{
  /*关灯*/
  gpio_set_value(gpioled.led_gpio,1);
  /*释放IO*/
  gpio_free(gpioled.led_gpio);
  /*注销设备节点*/  
  device_destroy(gpioled.class,gpioled.devid);
  /*注销设备类*/
  class_destroy(gpioled.class);
  /*注销字符设备对象驱动*/
  cdev_del(&gpioled.cdev);
  /*注销字符设备驱动*/
  unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("chensir");
MODULE_INFO(intree,"Y");//INFO,信息

LEDAPP.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"  /*open执行所需头文件,根文件目录终端输入man 2 open*/
#include "sys/stat.h"   /*open执行所需头文件,根文件目录终端输入man 2 open*/
#include "fcntl.h"      /*open执行所需头文件,根文件目录终端输入man 2 open*/
#include "stdlib.h"     
#include "string.h"     /*memcpy执行所需头文件,根文件目录终端输入man 3 memcpy*/

/*
*argc:应用程序参数个数
*argv[]:具体的参数内容,字符串形式
*./ledAPP  <fliename>  <0:1>  0表示关灯,1表示开灯
*./ledAPP /dev/gpioled 0   关灯
*./ledAPP /dev/gpioled 1   开灯
*/

#define LEDOFF   0
#define LEDON    1

int main(int argc,char *argv[])
{
  int fd,retvalue;/*fd:类似文件的ID号;retvalue:返回值。*/
  unsigned char databuf[1];/*这种数组可以存储数字或字符*/
  char *filename;
  unsigned char cnt = 0;
  if(argc !=3)
  {
    printf("Error usage!\r\n");
    return -1;
  }
  filename = argv[1];
  /*打开文件*/
  /* int open (const char *pathname,int flags)*/
  fd = open(filename,O_RDWR);/*以读写模式打开文件,fd用于打开文件获取ID号*/
  if(fd<0)
  {
    printf("file %s open failed!\r\n",filename);
    return -1;
  }
  databuf[0] = atoi(argv[2]);/*将字符转换为数字*/
  retvalue=write(fd,databuf,sizeof(databuf));
  if(retvalue<0)
  {
    printf("LED Control Failed!\r\n");
    close(fd);
    
  }
   while(1) {
   sleep(5);
   cnt++;
   printf("App running times:%d\r\n", cnt);
   if(cnt >= 5) break;
 }
  close(fd);
  return 0;
  }

makefile

KERNELDIR := /home/chensir/linux/atk-mp1/linux/my_linux/linux-5.4.31
CURRENT_PATH := $(shell pwd) 
obj-m := atomic.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

3.总结

1.添加pinctrl信息。

2.检查当前设备树中要使用的IO有没有被其他设备使用,如果有的话要处理。

3.添加设备节点,在设备节点中创建一个属性,吃属性描述所使用的gpio。

4.编写驱动,获取对应的gpio编号,并申请IO,成功后即可使用此IO。

申请失败绝大部分情况是被其他外设给占用了!!!这种情况在根节点或者设备树检查一下pinctrl复用情况。

加油!!!