一步一步写miscdevice的驱动模块

来源:互联网 发布:中国大数据信息 编辑:程序博客网 时间:2024/06/07 16:50

http://blog.csdn.net/tong646591/article/details/8301925

定义:字符设备的一种,它们共享一个主设备号(10),但次设备号不同,所有的混杂设备形成一个链表,对设备访问时内核根据次设备号查找到相应的miscdevice设备。

例如:触摸屏,LED,按键,串口。

即:为了节约主设备号,将某些设备用链表的形式连接在一起,最后通过查找次设备区分。这里用主设备无法匹配出设备驱动,只能找到链表,再通过次设备号,才能找到设备驱动。而之前所学的,一般字符设备,通过主设备号,就能找到设备驱动了。

混杂设备驱动内置有自动创建设备节点的代码,所以编译好之后,能自动创建设备节点。

相关的宏,定义在 kernel/include/Linux/miscdevice.h

杂项设备的核心函数的定义位于:kernel/drivers/char/misc.c

创建自动设备节点相关代码

 "kernel/drivers/base/core.c"

struct device *device_create(struct class *class, struct device *parent,  dev_t devt, void *drvdata, const char *fmt, ...)
{
    va_list vargs;
    struct device *dev;

    va_start(vargs, fmt);
    dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
    va_end(vargs);
    return dev;
}
EXPORT_SYMBOL_GPL(device_create);

static int __match_devt(struct device *dev, void *data)
{
    dev_t *devt = data;

    return dev->devt == *devt;
}

/**
 * device_destroy - removes a device that was created with device_create()
 * @class: pointer to the struct class that this device was registered with
 * @devt: the dev_t of the device that was previously registered
 *
 * This call unregisters and cleans up a device that was created with a
 * call to device_create().
 */
void device_destroy(struct class *class, dev_t devt)
{
    struct device *dev;

    dev = class_find_device(class, NULL, &devt, __match_devt);
    if (dev) {
        put_device(dev);
        device_unregister(dev);
    }
}
EXPORT_SYMBOL_GPL(device_destroy);


写miscdevice的驱动模块

http://www.cnblogs.com/snake-hand/p/3212483.html

对于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_NUMARRAY_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\n", 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\n", 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"\tinitialized\n");return ret;}


 

gpio_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");

 

 

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


基于NanoPi2的Linux3.4内核GPIO驱动

http://blog.csdn.net/Tony_Shen/article/details/52536396

硬件环境

开发板:nanopi2 (cpu:A9 s5p4418 )

软件环境

内核版本: linux3.4.39 
交叉编译器:arm-Linux-gcc version 4.9.3 (ctng-1.21.0-229g-FA) 64位系统版本

Linux3.4内核GPIO驱动说明

Kernel 2.6.32版本以上提供了gpio口管理的库文件/kernel/drivers/gpio/gpiolib.c。

相关的接口:1.int gpio_request(unsigned gpio, const char *label)申请一个pin脚作为gpio口,命名为 * label,如果经过判断空闲的 申请成功了做一些初始的bit位设置。2.void gpio_free(unsigned gpio)释放这个gpio口3.int gpio_direction_input(unsigned gpio)设置gpio口为输入模式4.int gpio_direction_output(unsigned gpio, int value)设置gpio口为输出模式 value为初始值 0为高电平/1为低电平5.void __gpio_set_value(unsigned gpio, int value)设置gpio口的值6.int __gpio_get_value(unsigned gpio)获取gpio口的值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

底层芯片具体实现

在drivers/gpio下实现了通用的基于gpiolib的GPIO驱动,其中定义了一个通用的用于描述底层GPIO控制器的gpio_chip结构体,并要求具体的SoC实现gpio_chip结构体的成员函数,最后透过gpiochip_add()注册gpio_chip。
  • 1
  • 1

驱动程序源码

#include <linux/module.h>#include <linux/gpio.h>#include <linux/delay.h>#include <linux/kernel.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/hrtimer.h>#include <linux/ktime.h>#include <linux/device.h>#include <linux/kdev_t.h>#include <linux/interrupt.h> #include <linux/sched.h>#include <linux/miscdevice.h>#include <mach/platform.h>#include <mach/devices.h>#define DEVICE_NAME "4418_relay"//nanopi2 4418unsigned int J1_GPIO = PAD_GPIO_C + 11;//模块GPIO脚unsigned int J2_GPIO = PAD_GPIO_C + 12;//模块GPIO脚#define J1_OFF 0x00#define J1_ON  0x01#define J2_OFF 0x10#define J2_ON  0x11char drv_buf[2];static int update_relay(void){    switch(drv_buf[0]) {    case J1_ON:        gpio_set_value(J1_GPIO, 0);  //输出低电平        return 0;    case J1_OFF:        gpio_set_value(J1_GPIO, 1);  //输出高电平        return 0;    case J2_ON:        gpio_set_value(J2_GPIO, 0);  //输出低电平        return 0;    case J2_OFF:        gpio_set_value(J2_GPIO, 1);  //输出高电平        return 0;    default:        return -EINVAL;    }}static int relay_write(struct file *file, const char * buffer, size_t count, loff_t * ppos){    unsigned long err;              err = copy_from_user(drv_buf, buffer, 1);    update_relay();    return 1;}static struct file_operations dev_fops={    write:relay_write,};static struct miscdevice misc = {    .minor = MISC_DYNAMIC_MINOR,    .name = DEVICE_NAME,    .fops = &dev_fops,};static int __init my_relay_init(void){    int ret;    gpio_direction_output(J1_GPIO, 1);//设置输出    gpio_direction_output(J2_GPIO, 1);//设置输出    ret = misc_register(&misc);    printk (DEVICE_NAME"\t#NanoPi2 J1 J2 initialized\n");     return ret; }static void __exit my_relay_exit(void){    misc_deregister(&misc);}module_init(my_relay_init);module_exit(my_relay_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("TONY");MODULE_DESCRIPTION("91arm.com Relay Driver");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97

修改内核配置菜单,增加当前驱动配置。

内核配置参考文档

内核模块编译

make CROSS_COMPILE=arm-linux- modules
  • 1
  • 1

内核模块不能加载问题

insmod 加载出现如下问题

root@nanopi2:/home/fa# insmod 4418_relay.ko insmod: ERROR: could not insert module 4418_relay.ko: Invalid module format
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

查看错误信息,version magic驱动程序同开发板内核不匹配。

root@nanopi2:/home/fa# dmesg |tail[ 2589.164000] 4418_relay: version magic '3.4.39-s5p4418 SMP preempt mod_unload ARMv7 p2v8 ' should be '3.4.39-FriendlyARM SMP preempt mod_unload ARMv7 p2v8 '
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

修改内核版本信息,-s5p4418改成内核的FriendlyARM

这里写图片描述

测试程序源码

#include     <stdio.h>    #include     <stdlib.h>     #include     <unistd.h>     #include     <sys/types.h>  #include     <sys/stat.h>   #include     <fcntl.h>      #include     <errno.h> #define DEV_FILE "/dev/4418_relay"#define J1_OFF  0x00#define J1_ON   0x01#define J2_OFF  0x10#define J2_ON   0x11int main(){    int fd_dev=-1;    char dat[2];    int cmd;    printf("nanoPi driver Test\n");    fd_dev = open(DEV_FILE,O_RDWR);    if(fd_dev<0){        printf("open device err\n");        return 0;    }    while(1){        printf("1:J1 OFF 2:J1 ON 3:J2 OFF 4:J2 ON\n");        printf("Please input:");         scanf("%d",&cmd);        switch(cmd){            case 1:                     dat[0] = J1_OFF;                break;            case 2:                dat[0] = J1_ON;                break;            case 3:                dat[0] = J2_OFF;                break;            case 4:                dat[0] = J2_ON;                break;            default:                break;        }        write(fd_dev,dat,1);    }    return 0;}

原创粉丝点击