Linux驱动开发——(次设备号使用及混杂设备驱动开发)gpio(5)

简介: Linux驱动开发——(次设备号使用及混杂设备驱动开发)gpio(5)

文章目录

次设备号使用及混杂设备驱动开发

使用次设备号

字符设备驱动——struct file数据结构

案例:将四个LED灯作为四个相同的设备个体,共享一个驱动,但是对应不同的次设备号。

驱动示例代码实现:

示例运行:

Linux混杂设备驱动开发

Linux内核混杂设备特点

Linux内核描述混杂设备的数据结构

代码示例(gpio-led):

执行结果


次设备号使用及混杂设备驱动开发

使用次设备号

之前我们都是使用一个主设备号创建一个字符设备文件进行使用,但是当具有多个同样设备的时候,就会用到次设备号,例如:使用操作多个LED灯(每个LED灯分别对应一个设备),多个USB设备对象、多个串口设备对象等等情况。

这里只以多个LED灯作为多个设备来实现这种情况下的使用。


字符设备驱动——struct file数据结构

  • 在设备操作接口中open/release/read/write/unlocked_ioctl等接口中的形参struct file *file指向的就是用户空间下应用程序操作的设备文件(例如:/dev/myled)
struct file {
  //指向驱动开发者自己定义初始化的硬件操作接口对象led_fops
  const struct file_operations  *f_op;
  ...
};
  • 功能:描述一个文件被打开(open)后的状态属性。
  • 生命周期:每次open一个文件成功后,内核会用此数据结构定义一个file对象来描述这个文件代开以后的状态属性(O_RDWR),每当close文件时,内核也会删除对应的file对象
  • 所以在硬件操作接口中的形参指向的就是内核创建的file对象。通过这个参数能够获得当前操作的设备文件的设备号等信息。
struct inode *inode = file->f_path.dentry->d_inode;
int minor = iminor(inode); //获取次设备号
int major = imajor(inode); //获取主设备号

案例:将四个LED灯作为四个相同的设备个体,共享一个驱动,但是对应不同的次设备号。

分析:实现以上情况,我们需要编写一个驱动,驱动内申请一个主设备号,4个次设备号分别对应四个设备文件,但是cdev对象只需要一个,因为是同样的设备,设备接口实现一个就可以了。

驱动示例代码实现:

  • 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声明
#include <linux/device.h> //设备文件的自动创建
//声明描述LED硬件信息的数据结构
struct led_resource {
    int gpio; //GPIO编号
    char *name; //LED名称
};
//定义初始化LED的硬件信息对象
static struct led_resource led_info[] = {
    {
        .name = "LED1",
        .gpio = PAD_GPIO_C+12
    },
  {
        .name = "LED2",
        .gpio = PAD_GPIO_C+7
    },
  {
        .name = "LED3",
        .gpio = PAD_GPIO_C+11
    },
  {
        .name = "LED4",
        .gpio = PAD_GPIO_B+26
    }
};
//调用关系:应用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.应用通过fd来获取file,驱动通过file来获取inode
    struct inode *inode = file->f_path.dentry->d_inode;
    //2.通过inode来获取次设备号
    //int minor = iminor(inode);
    int minor = MINOR(inode->i_rdev);
    //3.解析用户发送过来的命令
    switch(cmd) {
        case LED_ON:
            gpio_set_value(led_info[minor].gpio, 0);
            printk("%s:开第%d个灯", __func__, minor+1);
            break;
        case LED_OFF:
            gpio_set_value(led_info[minor].gpio, 1);
            printk("%s:关第%d个灯", __func__, minor+1);
            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 struct class *cls;
static int led_init(void)
{
    int i;
    //1.申请设备号
    //只需要在这里多加此设备号的申请就好了
    alloc_chrdev_region(&dev, 0, 4, "myled");
    //2.初始化字符设备对象,本质就是给字符设备添加操作接口
    cdev_init(&led_cdev, &led_fops);
    //3.向内核注册字符设备对象并且提供硬件操作接口
    cdev_add(&led_cdev, dev, 4);
    //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);
    }
    //5.创建设备类对象,类似长树枝
    //将来会创建:rootfs/sys/class/zhangsan
    cls = class_create(THIS_MODULE, "zhangsan");
    //6.创建设备文件/dev/myled,类似长苹果
    //dev:就是提供创建设备文件时所需的设备号
    //myled:就是提供创建设备文件是所需的设备文件名
    device_create(cls, NULL, dev, NULL, "myled");
    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);
    //4.删除设备文件(摘苹果)和设备类(砍树枝)
    device_destroy(cls, dev);
    class_destroy(cls);
}
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 fd1,fd2,fd3,fd4;
    //打开设备
    fd1 = open("/dev/myled1", O_RDWR);
    if (fd1 < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    fd2 = open("/dev/myled2", O_RDWR);
    if (fd2 < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    fd3 = open("/dev/myled3", O_RDWR);
    if (fd3 < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    fd4 = open("/dev/myled4", O_RDWR);
    if (fd4 < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    while(1) {
        ioctl(fd1, LED_ON);
        sleep(1);
        ioctl(fd2, LED_ON);
        sleep(1);
        ioctl(fd3, LED_ON);
        sleep(1);
        ioctl(fd4, LED_ON);
        sleep(1);
        ioctl(fd1, LED_OFF);
        sleep(1);
        ioctl(fd2, LED_OFF);
        sleep(1);
        ioctl(fd3, LED_OFF);
        sleep(1);
        ioctl(fd4, LED_OFF);
        sleep(1);
    }
    //关闭设备
    close(fd1);
    close(fd2);
    close(fd3);
    close(fd4);
    return 0;
}


示例运行:

  • 加载驱动,分别按照次设备号创建多个设备文件,使用测试程序:
insmod led_drv.ko
cat /proc/devices
mknod /dev/myled1 c 244 0
mknod /dev/myled2 c 244 1
mknod /dev/myled3 c 244 2
mknod /dev/myled4 c 244 3
./led_test

20191228214139324.png20191228220353817.png20191228220420889.png

Linux混杂设备驱动开发

Linux内核混杂设备特点

混杂设备本质就是字符设备,只是混杂设备的主设备号由内核已经定义好了(默认为10),将来各个混杂设备个体(驱动)通过次设备号来进行区分。


Linux内核描述混杂设备的数据结构

struct miscdevice {
  int minor;
  const char *name;
  const struct file_operations *fops;
};


  • 成员说明:
  • minor:混杂设备对应的次设备号,注意哦:主设备号为10,一般指定为宏MISC_DYNAMIC_MINOR,表示让内核来帮你分配一个次设备号。
  • name:就是将来的设备文件名,并且设备文件是自动创建,无需调用四个函数,但是三个保证还是需要滴
  • fops:给混杂设备添加的硬件操作接口
  • 配套函数:
//向内核注册一个混杂设备对象
  //内核会帮你自动创建一个名称为name的设备文件
    并且帮你分配一个次设备号
misc_register(&混杂设备对象)
  //从内核卸载一个混杂设备对象
  //内核会帮你删除创建的设备文件
    内核会帮你释放申请的次设备号
misc_deregister(&混杂设备对象)


代码示例(gpio-led):

  • led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h> //混杂设备
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <mach/platform.h>
//声明描述LED硬件信息的数据结构
struct led_resource {
    int gpio; //GPIO编号
    char *name; //LED名称
};
//定义初始化LED的硬件信息对象
static struct led_resource led_info[] = {
    {
        .name = "LED1",
        .gpio = PAD_GPIO_C+12
    },
  {
        .name = "LED2",
        .gpio = PAD_GPIO_C+7
    },
  {
        .name = "LED3",
        .gpio = PAD_GPIO_C+11
    },
  {
        .name = "LED4",
        .gpio = PAD_GPIO_B+26
    }
};
#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.拷贝用户缓冲区数据到内核缓冲区
    copy_from_user(&kindex, (int *)arg, sizeof(kindex));
    //3.解析用户命令
    switch(cmd) {
        case LED_ON:
            gpio_set_value(led_info[kindex-1].gpio, 0);
            break;
        case LED_OFF:
            gpio_set_value(led_info[kindex-1].gpio, 1);
            break;
        default:
            return -1;
    }
    return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,//模块的所属者
    .unlocked_ioctl = led_ioctl 
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR, //让内核帮你分配次设备号
    .name = "myled",//内核帮你创建设备文件/dev/myled
    .fops = &led_fops //给混杂设备对象添加硬件操作接口
};
static int led_init(void)
{
    int i;
    //1.申请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);
    }
    //2.向内核注册混杂设备对象
    misc_register(&led_misc);
    return 0;
}
static void led_exit(void)
{
    int i;
    //1.卸载混杂设备对象
    misc_deregister(&led_misc);
    //2.输出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);
    }
}
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) cl


执行结果

  • 执行
insmod led_drv.ko
ls /dev/
./led_test
  • 可以看到文件自动创建了/dev/myled

20191228222315330.png

  • 运行测试程序没问题。

20191228222338936.png

  • 看创建的设备文件

20191228222504806.png

执行以下命令可以看到申请的主设备号和次设备号

cat /sys/class/misc/myled/dev

20191228222632172.png

相关文章
|
1天前
|
数据采集 Linux
Linux源码阅读笔记20-PCI设备驱动详解
Linux源码阅读笔记20-PCI设备驱动详解
|
1天前
|
存储 Linux 数据库
Linux源码阅读笔记16-文件系统关联及字符设备操作
Linux源码阅读笔记16-文件系统关联及字符设备操作
|
1月前
|
存储 JSON Linux
|
1月前
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
50 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
1天前
|
存储 IDE Linux
Linux源码阅读笔记14-IO体系结构与访问设备
Linux源码阅读笔记14-IO体系结构与访问设备
|
1月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
1月前
|
Linux 开发者
Linux底层驱动社区饮水机系统详解
在Linux驱动开发中,入门时通常会关注驱动程序的三大核心步骤:入口函数、出口函数和声明许可证。这些步骤构成了驱动程序的基本结构,是驱动与内核交互的基础。下面是对这三个步骤的简要说明:
|
2月前
|
存储 Linux
深入了解Linux设备管理:字符、块和网络设备文件
深入了解Linux设备管理:字符、块和网络设备文件
53 0
|
2月前
|
移动开发 程序员 Linux
老程序员分享:linux驱动开发笔记_ioctl函数
老程序员分享:linux驱动开发笔记_ioctl函数
|
存储 缓存 安全
Linux系统开发: 命令进阶学习(一)
Linux系统开发: 命令进阶学习(一)
293 0
Linux系统开发: 命令进阶学习(一)