• 一步一步写miscdevice的驱动模块


    (本文使用的平台为友善tiny210SDKv2)


    对于linux的驱动程序来说,主要分为三种:miscdevice、platform_device、platform_driver 。

    这三个结构体关系:
    (基类)
    kobject --------------------
    /                         
    /                            
    device     cdev                   driver
    /      (设备驱动操作方法)           
    /                                     
    miscdevice         platform_device       platform_driver
    (设备驱动操作方法)    (设备的资源)          (设备驱动)  

    这时,我们先不讨论这几个间的关系与驱别,对于新手来说,上手最重要!

    首先我们先看看混杂项:

    在Linux驱动中把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体表述)。miscdevice共享一个主设备号MISC_MAJOR(即10),但次设备号不同。 所有的miscdevice设备形成了一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。 在内核中用struct miscdevice表示miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。miscdevice的API实现在drivers/char/misc.c中。 

    第二,我们再看看混杂项设备驱动的程序组织架构:

    新建一个first_led.c,先可能用到的头文件都引用上吧!


    #include <linux/kernel.h>
    #include <linux/module.h>//驱动模块必需要加的个头文件
    #include <linux/miscdevice.h>
    #include <linux/fs.h>
    #include <linux/types.h>
    #include <linux/moduleparam.h>
    #include <linux/slab.h>
    #include <linux/ioctl.h>
    #include <linux/cdev.h>
    #include <linux/delay.h>.
    
    //对应着相应机器平台的头文件
    #include <mach/gpio.h>
    #include <mach/regs-gpio.h>
    #include <plat/gpio-cfg.h>
    
    
    //给自己设备驱动定义一个名字
    
    #define DEVICE_NAME "First_led"

    名字有了,但样子是怎样的呢?现在就开始定义一个“样子”!

    如果一个字符设备驱动要驱动多个设备,那么它就不应该用

    misc设备来实现。

     

    通常情况下,一个字符设备都不得不在初始化的过程中进行下面的步骤: 

    通过alloc_chrdev_region()分配主次设备号。

    使用cdev_init()和cdev_add()来以一个字符设备注册自己。

     

    而一个misc驱动,则可以只用一个调用misc_register()

    来完成这所有的步骤。(所以miscdevice是一种特殊的chrdev字符设备驱动)

    所有的miscdevice设备形成一个链表,对设备访问时,内核根据次设备号查找

    对应的miscdevice设备,然后调用其file_operations中注册的文件操作方法进行操作。

     在Linux内核中,使用struct miscdevice来表示miscdevice。这个结构体的定义为:

     

    struct miscdevice  

    {

    int minor;

    const char *name;

    const struct file_operations *fops;

    struct list_head list;

    struct device *parent;

    struct device *this_device;

    const char *nodename;

    mode_t mode;

    };

    minor是这个混杂设备的次设备号,若由系统自动配置,则可以设置为

    MISC_DYNANIC_MINOR,name是设备名 

    为了容易理解,我们先打大概的“样子”做好。只做minor、name、fops;

    定义一个myfirst_led_dev设备:

    static struct miscdevice myfirst_led_dev = {
    	.minor			=	 MISC_DYNAMIC_MINOR,
    	.name			=	 DEVICE_NAME,
    	.fops				=	 &myfirst_led_dev_fops,
    };


    Minor  name   都已经定义好了。那么接下来实现一下myfirst_led_dev_fops方法。

    内核中关于file_operations的结构体如下:

    struct file_operations {

    struct module *owner;

    loff_t (*llseek) (struct file *, loff_t, int);

    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    int (*readdir) (struct file *, void *, filldir_t);

    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);

    int (*open) (struct inode *, struct file *);

    int (*flush) (struct file *, fl_owner_t id);

    int (*release) (struct inode *, struct file *);

    int (*fsync) (struct file *, int datasync);

    int (*aio_fsync) (struct kiocb *, int datasync);

    int (*fasync) (int, struct file *, int);

    int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

    int (*check_flags)(int);

    int (*flock) (struct file *, int, struct file_lock *);

    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

    int (*setlease)(struct file *, long, struct file_lock **);

    long (*fallocate)(struct file *file, int mode, loff_t offset,

      loff_t len);

    };

    对于LED的操作,只需要简单实现io操作就可以了,所以只实现

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    该函数是在linux2.6.5以后才出现在设备的操作方法中的。

    函数参数为文件节点、命令、参数

    static struct file_operations myfirst_led_dev_fops = {
    	.owner			= THIS_MODULE,
    	.unlocked_ioctl	= myfirst_led_ioctl,
    };

    到了这里,我们就考虑一下LED的物理层面是怎样的实现了,通过开发板的引脚我们可以知道,四个LED是分别接到了GPJ2的0~3号管脚上。因此,我们定义一个数组来引用这几个管脚(当然不能像祼机那样对IO的物理地址进行操作了,是需要经过内核的内存映射得来的IO内存操作!而内核把ARM的IO管脚地址按一个线性地址进行了编排)

    static int led_gpios[] = {
    	S5PV210_GPJ2(0),
    	S5PV210_GPJ2(1),
    	S5PV210_GPJ2(2),
    	S5PV210_GPJ2(3),
    };
    #define LED_NUM	ARRAY_SIZE(led_gpios)//判断led_gpio有多少个

    S5PV210_GPJ2(*)的定义如下

    #define S5PV210_GPJ2(_nr)  (S5PV210_GPIO_J2_START + (_nr))

    enum s5p_gpio_number {

    S5PV210_GPIO_A0_START = 0,

    ...................................

    S5PV210_GPIO_J2_START = S5PV210_GPIO_NEXT(S5PV210_GPIO_J1),

    .....................................

    }

    #define S5PV210_GPIO_NEXT(__gpio) 

    ((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)

    注:##是粘贴运算,具体用法请自行找度娘或谷哥

    给用户空间的接口操作:

    static long myfirst_led_ioctl(struct file *filp, unsigned int cmd,
    		unsigned long arg)
    {
    	switch(cmd) {
    		case 0:
    		case 1:
    			if (arg > LED_NUM) {
    				return -EINVAL;//判读用户的参数是否有误
    			}
    
    			gpio_set_value(led_gpios[arg], !cmd);//用户选定的LED并设置值
    			//printk(DEVICE_NAME": %d %d
    ", arg, cmd);
    			break;
    		default:
    			return -EINVAL;
    	}
    	return 0;
    }

    对于gpio_set_value(unsigned int gpio, int value),内核有以下定义:

    static inline void gpio_set_value(unsigned int gpio, int value)

    {

    __gpio_set_value(gpio, value);

    }

    void __gpio_set_value(unsigned gpio, int value)

    {

    struct gpio_chip *chip;

    chip = gpio_to_chip(gpio);

    WARN_ON(chip->can_sleep);

    trace_gpio_value(gpio, 0, value);

    chip->set(chip, gpio - chip->base, value);

    }//到这里我们就不再分析下去了 ,无非就是判定是哪一个芯片

    程序写到这里,对于用户空间来说,已经有了完整的操作方法接口,但对于内核模块来说,还缺少驱动模块的进入与退出。以下接着写驱动模块的初始化(即进入)和退出。

    static int __init myfirst_led_dev_init(void) {;}

    static void __exit myfirst_led_dev_exit(void) {;}

    函数如上。双下划线表示模块在内核启动和关闭时自动运行和退出

    对于驱动模块的初始化函数,要写些什么呢?我们这样考虑:

    对于用户空间接口来说,我们的实现函数只是给出了IO的值设置的,但是ARM的IO管脚使用还是需要配置方向、上拉下拉.....才能正常使用的,并且所有的硬件资源,都是受内核所支配的,驱动程序必需向内核申请硬件资源才能对硬件进行操作。另外还需要对设备进行注册,内核才知道你这个设备是什么东东,用到哪些东西。这些操作,我们安排在init里实现!

    static int __init myfirst_led_dev_init(void) 
    {
    	int ret;
    	int i;
    
    	for (i = 0; i < LED_NUM; i++) 
      {
    		ret = gpio_request(led_gpios[i], "LED");//申请IO引脚
    		if (ret) {
    				printk("%s: request GPIO %d for LED failed, ret = %d
    ", DEVICE_NAME,
    					led_gpios[i], ret);
    				return ret;
    				}
    		s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);
    		gpio_set_value(led_gpios[i], 1);
    	}
    	ret = misc_register(&myfirst_led_dev);
    	printk(DEVICE_NAME"	initialized
    ");
    	return ret;
    }


    pio_request(unsigned gpio, const char *label)  

    gpio则为你要申请的哪一个管脚,label为其名字 。

    int s3c_gpio_cfgpin(unsigned int pin, unsigned int config);

    对芯片进行判断,并设置引脚的方向。

    ret = misc_register(&myfirst_led_dev);.

    该函数中、内核会自动为你的设备创建一个设备节点

    对设备进行注册

    到这里,设备的初始化与注册已经完成!

    当用户不再需要该驱动资源时,我们必需在驱动模块中,对占用内核的资源进行主动的释放!

    因此在驱动模块退出时,完成这些工作!

    static void __exit myfirst_led_dev_exit(void) {
    	int i;
    
    	for (i = 0; i < LED_NUM; i++) {
    		gpio_free(led_gpios[i]);
    	}
    
    	misc_deregister(&myfirst_led_dev);
    }

    gpio_free(led_gpios[i]);

    释放IO资源

    misc_deregister(&myfirst_led_dev);

    注销设备

    还需要模块的初始化与退出函数声明

    module_init(myfirst_led_dev_init);

    module_exit(myfirst_led_dev_init);

    最后,为了保持内核驱动模块的风格,我们还要加上相应的许可跟作者

    MODULE_LICENSE("GPL");

    MODULE_AUTHOR("jjvip136@163.com");

    好了,程序已经打好出来了(黄色代码),我们把它整理好,试下编译一下试试效果(晚点补上效果)。













  • 相关阅读:
    Linux内核调试方法
    linux查看系统的日志------健康检查特性
    检测磁盘驱动的健康程度SMART
    用十条命令在一分钟内检查Linux服务器性能
    Nginx安装及配置
    getopts的使用
    grub rescue 主引导修复
    linux C中调用shell命令和运行shell脚本
    Makefile基础---编译
    OVMF基础
  • 原文地址:https://www.cnblogs.com/snake-hand/p/3212483.html
Copyright © 2020-2023  润新知