(本文使用的平台为友善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. JS中的this对象详解

    JS中this关键字很常见,但是它似乎变幻莫测,让人抓狂.这篇文章就来揭示其中的奥秘. 借助阮一峰老师的话:它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用.这句话看似平常,可是要非常注意 ...

  2. Markdown常用基本语法

    现在是我在学习Markdown时做的笔记.学完这些Markdown的基本使用已经不成问题. 1. 标题设置(让字体变大,和word的标题意思一样)在Markdown当中设置标题,有两种方式:第一种:通 ...

  3. C++矩阵运算库armadillo配置笔记

    前言 最近在用C++实现神经网络模型,优化算法需要用到矩阵操作,一开始我用的是boost的ublas库,但用着用着感觉很不习惯,接口不够友好.于是上网搜索矩阵运算哪家强,大神们都推荐armadillo ...

  4. webapi返回json格式,并定义日期解析格式

    1.webapi返回json格式 var json = config.Formatters.JsonFormatter; json.SerializerSettings.PreserveReferen ...

  5. ns3模拟无线Ad hoc 网络通信

    Ad hoc网络 Ad hoc网是一种多跳的.无中心的.自组织无线网络,又称为多跳网(Multi-hop Network).无基础设施网(Infrastructureless Network)或自组织 ...

  6. 个人博客作业week5

    请阅读下述关于敏捷开发方法文章,并在个人博客上写一篇读后感. Martin Fowler:  http://martinfowler.com/agile.html 截止时间:10月20日前.

  7. [Chapter 3 Process]Practice 3.3 Discuss three major complications that concurrent processing adds to an operating system.

    3.3  Original version of Apple's mobile iOS operating system provied no means of concurrent processi ...

  8. Reapter控件的特殊使用:使用EVAL调取asp:Repeater里面绑定的值来进行判断 根据从数据库获取的数据进行判断 ,进而显示成想要的内容

    1.这个判断的过程你可以写在后台,如先在后台写一个public类型的方法:public bool CheckAduit(string code){ //根据你传入的code来判断,并返回true或者f ...

  9. Html/Css(新手入门第一篇)

    一.理解web标准含义--为什么采用web标准*****将内容与样式分离1.web标准是一系列标准,就是一系列技术标准在使用时,是组合应用[1].结构化内容 html[2].表现化内容 css[3]. ...

  10. asp控件Repeater运用

    双层repeater嵌套 <asp:Repeater ID="rpt_dataRepeatgroup" runat="server" OnItemData ...