Linux驱动开发——(Linux内核字符设备文件的自动创建)gpio(4)


Linux内核字符设备文件的自动创建

前面已经描述过通过mknod命令可以手动创建字符设备文件,那么如何实现自动创建设备文件呢?

自动创建字符设备文件概述

字符设备文件的自动创建只需要“三个保证”+“四个函数”

三个保证

  1. 保证根文件系统rootfs中必须有mdev可执行程序,例如:执行命令
#确认根文件系统中有mdev,mdev可以帮助自动创建设备文件
which is mdev 
  1. 保证根文件系统rootfs的启动脚本rcS中必须有以下内容:
#将来解析fstab文件
mount -a
#驱动程序解析hotplug来确认谁来帮驱动创建设备文件(需要mdev可执行程序)
echo /sbin/mdev > /proc/sys/kernel/hotplug
  1. 保证根文件系统rootfs的配置文件fstab中必须有以下三句话:
    • /proc作为procfs虚拟文件系统的入口
    • /sys作为sysfs虚拟文件系统的入口
    • /dev作为tmpfs虚拟文件系统的入口
    • 这三种文件系统的内容都是由内核创建并且创建在内存中,掉电丢失。
proc   /proc proc  defaults 0 0
sysfs  /sys  syfs  defaluts 0 0
tmpfs  /dev  tmpfs defulats 0 0

四个函数

驱动程序接下来只需要调用以下四个函数即可完成设备文件的创建:
(编写驱动时记得添加头文件 #include <linux/device.h>)

  1. 定义一个设备类指针:
struct class *cls;
  1. 创建一个设备类对象,cls指向这个对象,并且设定这个对象类名:
cls = class_create(THIS_MODULE, "myclass");
  1. 正式创建设备文件,(暂时还是利用led等示例来说明)设备文件为/dev/myled,其中dev为设备号,myled为设备文件名。
device_create(cls, NULL, dev, NULL, "myled");
  1. 删除设备文件(在卸载驱动的时候自动删除设备文件)
device_destroy(cls, dev);
  1. 删除设备类(卸载驱动时用到)。
class_destroy(cls);

修改之前LED驱动代码

  • 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.分配内核缓冲区,保存用法发送过来的灯的编号
    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 struct class *cls;

static int led_init(void)
{
    int i;
    //1.申请设备号
    alloc_chrdev_region(&dev, 0, 1, "myled");
    //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);
    }
    //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 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

在这里插入图片描述

发布了78 篇原创文章 · 获赞 25 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_37596943/article/details/103748123