(本文使用的平台为友善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\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;
}

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

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

一步一步写miscdevice的驱动模块的更多相关文章

  1. 一步一步写平衡二叉树(AVL树)

    平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵 ...

  2. .NET跨平台:在Mac上跟着错误信息一步一步手写ASP.NET 5程序

    今天坐高铁时尝试了一种学习ASP.NET 5的笨方法,从空文件夹开始,根据运行dnx . kestrel命令的错误信息,一步一步写代码,直至将一个最简单的ASP.NET程序运行起来. 尝试的具体步骤如 ...

  3. 《一步一步写嵌入式操作系统》读书笔记1—Skyeye介绍、安装和HelloWorld

    2013-11-14 最近在看<一步一步写嵌入式操作系统>,感觉此书甚好,许多地方讲得很清楚.可操作性强,计划边读边实践边写笔记,希望能够逐步熟悉嵌入式操作系统底层的东西,最终剪裁出一套实 ...

  4. 一步一步写一个简单通用的makefile(三)

    上一篇一步一步写一个简单通用的makefile(二) 里面的makefile 实现对通用的代码进行编译,这一章我将会对上一次的makefile 进行进一步的优化. 优化后的makefile: #Hel ...

  5. Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(异常处理和引用计数)

    我们将继续一步一步动手给Python写扩展,通过上一篇我们学习了如何写扩展,本篇将介绍一些高级话题,如异常,引用计数问题等.强烈建议先看上一篇,Python之美[从菜鸟到高手]--一步一步动手给Pyt ...

  6. 一步一步写算法(之prim算法 下)

    原文:一步一步写算法(之prim算法 下) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前两篇博客我们讨论了prim最小生成树的算法,熟悉 ...

  7. 一步一步写算法(之prim算法 中)

    原文:一步一步写算法(之prim算法 中) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] C)编写最小生成树,涉及创建.挑选和添加过程 MI ...

  8. 一步一步写算法(之prim算法 上)

    原文:一步一步写算法(之prim算法 上) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面我们讨论了图的创建.添加.删除和保存等问题.今 ...

  9. 一步一步写算法(之挑选最大的n个数)

    原文:一步一步写算法(之挑选最大的n个数) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 从一堆数据中挑选n个最大的数,这个问题是网上流传的 ...

随机推荐

  1. [原创] Legato 8.1 oracle full backup skip 奇怪的问题处理过程 -- 非调度日期手工运行调度也不成功(skip)

    转载请注明出处: http://www.cnblogs.com/fengaix6/p/4677024.html 作者:飄ぺ風 环境: a. Server: Legato 8.1.2, aix 6.1 ...

  2. Hessian 初探

    Hessian 是一个序列化协议, 他的优点在于比 Java 原生的对象序列化/反序列化速度更快, 序列化出来以后的数据更小. 序列化协议跟应用层协议无关, 可以将 Hessian 序列化以后的数据放 ...

  3. $watch监听数据变化和run方法

    angular中$watch方法可以监听数据的变化. $scope.$watch('phone',function(){ $scope.phone.fre = $scope.phone.num> ...

  4. Hibernate个人学习笔记(2)

    新增改查的操作 一.cfg.xml配置 <?xml version='1.0' encoding='UTF-8'?><!DOCTYPE hibernate-configuration ...

  5. 图解CSS的padding,margin,border属性

    原文出处:http://hi.baidu.com/sonan/item/af05cf8759810d1cc31627d5 觉得不错,保存以备用. --------------------------- ...

  6. 批量导入Excel存在的问题及解决方案

    许多传统的做法,导入excel就是将excel上传到服务器的某个文件夹里如upload,之后再次读取,导入系统.这边就存在一些问题: 1.服务器需要安装Office,用于读取Excel文件. 2.系统 ...

  7. 【OpenGL】VS2010环境配置 [转]

    基于OpenGL标准开发的应用程序运行时需有动态链接库OpenGL32.DLL.Glu32.DLL,这两个文件在安装Windows NT时已自动装载到C:\WINDOWS\SYSTEM32目录下(这里 ...

  8. Java 字符串用逗号并接

    for (int t = 0; t < memberLen; t++) {  memTemp = stafferMap.get(strMember[t]);  if(memTemp != nul ...

  9. mysql状态查看 QPS/TPS/缓存命中率查看

    运行中的mysql状态查看   对正在运行的mysql进行监控,其中一个方式就是查看mysql运行状态.    (1)QPS(每秒Query量)  QPS = Questions(or Queries ...

  10. 评分视图的封装 (星星 RatingView)

    #import "RatingView.h" #define kRatingScale 10 @implementation RatingView { UIView *_grayS ...