Linux驱动开发——gpio(3)

坚强是说给别人听的谎言 提交于 2019-12-28 03:43:54


linux内核字符设备硬件操作接口之ioctl

ioctl系统调用函数的使用

  • 对于ioctl这个系统调用接口,Linux的创始人在2.0版本之前并没有进行添加,仅有write和read两个接口,但是后来发现当需要去控制文件的某些操作的时候,很显然这两个接口根本不够用。所以才有了这个万能控制接口ioctl,但是作为Linux的创始人Linus本人一直排斥该接口,因为这个ioctl接口的在内核中的使用相当于对应用层开设了一个能够直接交互的窗口,很影响内核整体的权限控制,不过由于目前还暂时没有更好可以替代的方法,所以还是继续保留了这个接口的使用。
  • 对应驱动开发来说,ioctl的接口使用真的很方便,能够帮助我们解决很多同设备或者驱动在应用层或者用户态下调用的问题。
  • ioctl函数原型:
    int ioctl(int fd, int request, …)
  • 函数功能:
    • 1.向硬件设备发送控制命令
    • 2.还可以和硬件设备进行读或者写操作
  • 参数:
    • fd:文件描述符
    • request:给硬件设备发送的控制命令,此命令有驱动开发者自行定义,例如我们在LED驱动下可以定义:
      • #define LED_ON 0x100001
      • #define LED_OFF 0x100002
    • 返回值:执行成功返回0,执行失败返回-1。

使用参考:

  • 两个参数调用:
ioctl(fd, LED_ON);//仅仅向硬件设备发送开灯命令
ioctl(fd, LED_OFF);//仅仅向硬件设备发送关灯命令
  • 三个参数调用:
int index = 1;//定义初始化用户缓冲区
ioctl(fd, LED_ON, &index);//不仅仅向硬件设备发送		开灯命令,还向硬件设备写入数据1
也可以用来从设备中读取数据:
int state;
ioctl(fd, LED_ON, &state);//不仅仅向硬件设备发送开灯命令,还从硬件设备读取数据保存在state中
printf("state = %d\n", state);	

对应的底层驱动的ioctl接口

struct file_operations {
  	long (*unlocked_ioctl) (struct file *file, 
  	unsigned int cmd, 
  	unsigned long arg);
 };		
  • 接口功能:

    • 1.向硬件设备发送控制命令
    • 2.还可以和硬件设备进行读或者写操作
  • 调用关系:应用ioctl->软中断->内核的sys_ioctl->驱动的unlocked_ioctl

  • 参数:

    • file:文件指针,和应用ioctl的fd是亲戚关系
    • cmd:保存应用发送来的控制命令,和应用ioctl的第二个参数相等也就是:cmd=LED_ON或者cmd=LED_OFF
    • arg:注意前提:如果应用程序要和硬件设备进行读或者写操作,arg保存的就是用户缓冲区的首地址(arg=&index或者arg=&state),但是驱动程序同样不能直接来访问arg(int kindex=*(int )arg或者(int *)arg = 1),太危险;如果要进行数据拷贝,同样 利用内核提供的内存拷贝函数:copy_from_user/copy_to_user注意arg的数据类型要进行强制转换!
      当然啦:如果应用ioctl传递的是两个参数(仅仅是发送控制命令),arg参数无需搭理!

LED灯使用示例:

对应LED灯控制来说,当使用了ioctl进行控制后,就没必要准备read和write使用,并且对open也不做定义了。

  • led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h> //copy_from_user声明

//声明描述LED硬件信息的数据结构
struct led_resource {
    int gpio; //GPIO编号
    char *name; //LED名称
};

//定义初始化LED的硬件信息对象
static struct led_resource led_info[] = {
    {
        .gpio = PAD_GPIO_C + 12,
        .name = "LED1"
    }
};

//调用关系:应用ioctl->软中断->内核sys_ioctl->驱动led_ioctl
//例子:int index=1;ioctl(fd, LED_ON, &index);//开第1个灯
//参数关系:
//      fd<---->file 亲戚关系
//      cmd=LED_ON或者cmd=LED_OFF
//      arg=(unsigned long)&index

#define LED_ON  0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令
static long led_ioctl(struct file *file,
                        unsigned int cmd,
                        unsigned long arg)
{
    //1.分配内核缓冲区,保存用法发送过来的灯的编号
    int kindex;

    //2.拷贝用户缓冲区数据到内核缓冲区
    //kindex = *(int *)arg; //相当危险
    copy_from_user(&kindex, (int *)arg, sizeof(kindex));

    //3.解析用户发送过来的命令
    switch(cmd) {
        case LED_ON:
            gpio_set_value(led_info[kindex-1].gpio, 0);
            printk("%s:开第%d个灯", __func__, kindex);
            break;
        case LED_OFF:
            gpio_set_value(led_info[kindex-1].gpio, 1);
            printk("%s:关第%d个灯", __func__, kindex);
            break;
        default:
            printk("无效命令!\n");
            return -1;
    }
    return 0; //执行成功返回0,执行失败返回负值
}

//定义初始化LED的硬件操作接口对象
static struct file_operations led_fops = {
    .unlocked_ioctl = led_ioctl, //不仅仅向硬件设备发送控制命令,还能和设备进行读写操作
};

//定义设备号对象
static dev_t dev;

//定义字符设备对象
static struct cdev led_cdev;

static int led_init(void)
{
    int i;
    //1.申请设备号
    alloc_chrdev_region(&dev, 0, 1, "tarena");
    //2.初始化字符设备对象,本质就是给字符设备添加操作接口
    cdev_init(&led_cdev, &led_fops);
    //3.向内核注册字符设备对象并且提供硬件操作接口
    cdev_add(&led_cdev, dev, 1);
    //4.申请GPIO资源配置为输出,输出1
    for(i = 0; i < ARRAY_SIZE(led_info); i++) {
        gpio_request(led_info[i].gpio, 
                        led_info[i].name);
        gpio_direction_output(led_info[i].gpio, 1);
    }
    return 0;
}

static void led_exit(void)
{
    int i;
    //1.输出1,释放GPIO资源
    for (i = 0; i < ARRAY_SIZE(led_info); i++) {
        gpio_set_value(led_info[i].gpio, 1);
        gpio_free(led_info[i].gpio);
    }
    //2.释放设备号
    unregister_chrdev_region(dev, 1);
    //3.卸载字符设备对象
    cdev_del(&led_cdev);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

  • led_test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>


#define LED_ON  0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令

int main(int argc, char *argv[])
{
    int fd;
    int index; //分配用户缓冲区,保存操作灯的编号

    if(argc != 3) {
        printf("用法:%s <on|off> <1|2|3|4>\n", argv[0]);
        return -1;
    }

    //打开设备
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    
    //"1"->1
    index = strtoul(argv[2], NULL, 0);

    //应用ioctl->软中断->内核sys_ioctl->驱动led_ioctl
    if(!strcmp(argv[1], "on"))
        ioctl(fd, LED_ON, &index);
    else if(!strcmp(argv[1], "off"))
        ioctl(fd, LED_OFF, &index);

    //关闭设备
    close(fd);
    return 0;
}
  • Makefile
obj-m += led_drv.o
all:
	make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
	make -C /opt/kernel SUBDIRS=$(PWD) clean

操作流程:

在开发板上执行:

insmod led_drv.ko
cat /proc/devices
244 myled
#	创建设备文件
mknod /dev/myled c 244 0

在这里插入图片描述
测试执行:

./led_test 
./led_test on 3
./led_test off 3

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!