从零开始之驱动发开、linux驱动(五、字符驱动之led驱动改进)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82355575

上一节的代码有两个使用不是很方便问题:

1.写驱动时,每个次设备号都要自己指定。(这样就不能作为移植性强的软件了)

2.引用层使用时不但要手动创建设备节点,还要知道其设备信息和设备号。

我们先来解决第一个问题:

其实很简单,主设备号为0,则系统就会为我们自动分配未使用的主设备号。

static unsigned int major;

static int __init first_drv_init(void)
{
    printk("first_drv_init\n");
    major = register_chrdev(0,"first_drv",&first_drv_file_operation);

    /* 映射io寄存器 */
    p_gpj0 =  ioremap(GPJ0CON_PA, 0x08);
    if(NULL == p_gpj0)
    {   
        printk("ioremap fail\n");
        return -1; 
    }   
    return 0;
}

之后编译安装,查看其自动分配的主设备号为254。至于为什么为254,可以查看我下面文章的分析。

https://blog.csdn.net/qq_16777851/article/details/80723572

接下来看如何自动创建设备节点

#include <linux/fs.h>       /* 包含file_operation结构体 */
#include <linux/init.h>     /* 包含module_init module_exit */
#include <linux/module.h>   /* 包含LICENSE的宏 */
#include <asm/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>


#define GPJ0CON_PA  0xe0200240

static unsigned int major;
static struct class *first_class;
static struct device *led_dev;

struct gpj0 {
    unsigned int gpj0con;
    unsigned int gpj0dat;
};

static struct gpj0* p_gpj0 = NULL;

static int first_drv_open(struct inode *inodep, struct file *filep)
{
    printk("first_drv_open\n");
    
    /* 初始化gpj0_3为输出 */
    p_gpj0->gpj0con &= ~(0xf << 12);
    p_gpj0->gpj0con |= (1 << 12);
    
    /* 默认熄灭led灯 */
    p_gpj0->gpj0dat |= (1<<3);
    return 0;
}


static ssize_t first_drv_write(struct file *filep, const char __user * from, size_t len, loff_t *ppos)
{
    char buf[10];
    int ret;

    printk("first_drv_write\n");
    memset(buf, 0,10);
    printk("from = 0x%p\n", from);

    ret = copy_from_user(buf, from, 1);
    if(ret)
    {
        printk("copy_from_user fail\n");
    }

    if('0' == buf[0])
    {
        p_gpj0->gpj0dat |= (1<<3);
    }
    else if('1' == buf[0])
    {
        p_gpj0->gpj0dat &= ~(1<<3);
    }
    else
    {
        printk("please input 1/0 ?");
    }


    return 0;
}
   
static const struct file_operations first_drv_file_operation = {
    .owner = THIS_MODULE,
    .open  = first_drv_open,
    .write = first_drv_write,
};

static int __init first_drv_init(void)
{
    printk("first_drv_init\n");
    major =  register_chrdev(0,"first_drv",&first_drv_file_operation);

    /* 映射io寄存器 */
    p_gpj0 =  ioremap(GPJ0CON_PA, 0x08);
    if(NULL == p_gpj0)
    {
        printk("ioremap fail\n");
        return -1;
    }

    /* 创建一个类 */
    first_class = class_create(THIS_MODULE, "first_class");
    if(!first_class)
    {
        if (IS_ERR(first_class))
            return PTR_ERR(first_class);
    }

    /* 创建从属这个类的设备,主设备号使用自动分配的major,次设备号我们自己指定为0 */
    led_dev = device_create(first_class,NULL,MKDEV(major, 0), NULL, "led");
    if (IS_ERR(led_dev))
        return PTR_ERR(led_dev);

    return 0;
}


static void __exit first_drv_exit(void)
{
    unregister_chrdev(major,"first_drv");
    printk("first_drv_exit\n");
    /* 使用完取消映射 */
    iounmap(p_gpj0);

    /* 注销类里面的设备 */
    device_unregister(led_dev);
    /* 注销类 */
    class_destroy(first_class);
}

module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");
  

其中class的知识可以参考我的设备模型相关文章

https://blog.csdn.net/qq_16777851/article/details/81488020

device的知识可以参考我的设备模型相关文章

https://blog.csdn.net/qq_16777851/article/details/81437352

如果有分析,device的注册过程,就可以发现,它最终其实只是调用来uevent来发送一个action.

真正的创建设备节点在在应用程序中创建。

通常是一个叫做udev的应用程序来创建,在嵌入式系统中我们是用busybox的mdev。

我们在创建根文件系统时,曾经在/etc/init.d/rcS文件中曾经加入了这么两句命令。

echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s

查看mdev的帮助信息可以知道。

其中 mdev -s的作用是  在引导期间运行以扫描/ sys下设备的并根据扫描到的设备信息在 /dev创建设备节点。

mdev是一个应用程序,所以肯定是在内核启动,内核中的驱动程序注册后,再运行的,所以上面的引导是指在系统启动完,执行rsS文件时,扫描一遍/sys文件系统,将扫描到的每个设备都加入到/dev目录下。

echo /sbin/mdev > /proc/sys/kernel/hotplug

上面这条命令则是,使用/sbin/mdev应用程序来激活内核的热插拔功能(即指明热插拔功能是用这个应用程序来处理)。

在pc机上默认是使用netlink机制, /proc/sys/kernel/hotplug文件默认是空。我们在里面写入mdev应用程序的命令,即配置为用mdev来创建设备节点。

即 mdev -s是在系统启动阶段使用来在/dev下创建设备节点。

而echo /sbin/mdev > /proc/sys/kernel/hotplug 是在系统的运行起来后通过insmod方式安装的设备在/dev下创建设备节点。

从/sys/kernel/下可以看到,uevent的一些信息,包括从开机开始到目前的事件数量。

而启动节点的扫描主要是扫描/sys/dev/目录下

其中每一个都是一个主从设备号的组合。

而它里面的uevent里面就包含着创建设备节点的信息,包括设备号,名字,(char,block进入文件夹时就可以知道)

关于uevent的相关知识,可以查看我的驱动模型章节中对uevent的分许,链接如下

https://blog.csdn.net/qq_16777851/article/details/81395281

真正的运行时安装卸载程序,则就靠前面的uevnet函数中,最后的call_usermodehelper函数来来执行用户空间应用程序

其作用是fork一个进程,以env为参数,执行uevent_helper(我们这里的就是/dev/mdev应用程序)

可见,mdev其实是一个很简单的应用程序,它主要实现mdev -s扫描 /sys/dev/下的 char和block目录下的每一个设备中的uevent.

利用uevnet中得到的信息创建设备节点。

而动态插拔的就更简单了,在驱动注册时通过时通过内核的call_usermodehelper函数调用应用程序mdev,并直接传给它新设备在/sys下的目录,进而mdev直接得到设备的uevent里面的信息,直接创建设备节点。

注释掉rcS中的mdev -s后,启动阶段将不再扫描注册设备。

自己手动执行这条命令后,当然也可以通过mdev应用程序,扫描/sys下来创建设备节点。

启动后的激活热插拔,注释那条命令,自己对比insmod实践一下就可以看出区别了。

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82355575