============================      指引     =============================

第一节是最基础的驱动程序;

第二节是/dev应用层接口的使用;

第三节是/sys应用层接口的使用;

第四节是对硬件的操作;

第五节是旧版platform_driver的简易说明;

第六节是设备树与新版platform的简易说明;

===========================   简易驱动程序   ===========================

1.基本框架

这是一个.ko驱动程序最基本、也是最常见的框架,这种框架最大的特点是,它不依赖任何外部代码的限制,随时可以进行insmod操作,通常用于将高实时性的代码、或者某种接口的支持嵌入到内核中。这种框架是所有人必须掌握的。

static __init int ModuleInit(void) //装载时调用的函数

{

......

return 0; //完成初始化则返回0,否则应根据原因返回正确的ERR码

}

static __exit void ModuleExit(void) //卸载时调用的函数

{

......

}

module_init(ModuleInit); //规定ModuleInit为装载时调用的函数

module_exit(ModuleExit); //规定ModuleExit为卸载时调用的函数

MODULE_AUTHOR("your name"); //作者名以及额外的说明,用于维护

MODULE_LICENSE("GPL"); //声明开源or不开源,可选GPL和Proprietary

2.常用函数

1)printk:

printk和printf非常相似,它用于输出驱动程序的调试信息,但是printk的功能更强大,可以规定打印等级,显示打印时的系统时间,此外,printk仅用于输出调试信息,它输出的内容不会进入stdio的缓冲流内。

例子:

printk(KERN_INFO "Kernel message %d", 1);

输出:

[s.mmmμμμ] Kernel message 1

由于调试信息通常是通过串口输出的,因此printk占用的时间很多;

2)kzalloc

kzalloc用于申请内存空间,大部分驱动程序需要开辟一段内存空间作为临时的数据存储区,这个函数使用的频率就非常高了,它和malloc基本一样;

例子:

struct device_type *objet = NULL;

objet = kzalloc(sizeof(*objet), GFP_KERNEL);

3)kfree

与kzalloc相反,用于释放kzalloc申请的内存空间;

例子:

kfree(object);

=========================   /dev 应用层接口   ==========================

之所以将这个抽取出来讲,是因为/dev应用层接口是一个独立的框架,并不依赖于某种特定的驱动框架,任何驱动程序都可以使用/dev框架。

1.前提知识——linux文件系统基本操作

很多人都知道linux下面是将设备当成文件了,但具体的,信息是怎么通过文件从应用层下发到设备的,很多人都没有去了解,甚至很多人连标准的读写操作都分不太清楚,因此有必要在这里非常基本的说一下。

1)open和close

open和close函数是程序获得文件使用权限的途径,open的结果是返回一个文件句柄,close函数则用于释放文件;

int open (__const char *__file, int __oflag, ...);

int close (int __fd);

例:

int fd = open("abc", O_RDWR | O_NONBLOCK);

close(fd);

2)read和write

read和write用于向文件传入或传出数据,返回值为实际写入的数据的size;

ssize_t read (int __fd, void *__buf, size_t __nbytes);

ssize_t write (int __fd, __const void *__buf, size_t __n);

例:

length = write(fd, buffer, strlen(buffer));

length = read(fd, buffer, sizeof(buffer));

3)ioctl

ioctl用于稍复杂的控制,它原本是用于TCP/IP的,但很多驱动程序也使用这个接口实现多种功能共用一个接口,返回值为0表示成功,小于0;

int ioctl( int fd, int cmd, void *arg);

4)fread和fwrite

使用fopen打开的文件的句柄,需要用fread和fwrite操作,但需要注意的是f族函数采用了流控制,数据会在缓冲区进行缓存,到一定数量或者被fflush函数触发后再发送。对于时序严格的设备,是不宜使用f族函数的;

2.struct file_operations 结构体

file_operations 结构体存在于fs.h文件中,该结构体规定了驱动程序在/dev目录下对应用层程序会展现哪一些接口,这些接口与read、write等函数是严格匹配的;因此,在设计驱动程序时,如果想要/dev目录下使用标准文件接口向驱动程序传输数据,就必须在驱动程序中实现file_operations 结构体里对应的属性;

声明如下(节选):

struct file_operations {

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

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

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

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

......

};

例:实现向文件write后回显写入的数据

ssize_t Fops_Write(struct file *file_p, const char __user *buf, size_t size, loff_t *loff)

{

char data[PAGE_SIZE] = {'\0'};

copy_from_user(&data, buf, size);

data[size] = '\0';

printk(KERN_INFO "%s", data);

return size;

};

struct file_operations fops = {

.write = Fops_Write;

};

3.产生/dev下的设备接口

1)过程较为复杂,先给出实例代码:

int dev_id = 0;

struct class *test_class = NULL;

dev_t test_dev;

dev_id = register_chrdev(0, "test_dev", &fops);

test_dev = MKDEV(dev_id, 0);

test_class = class_create(THIS_MODULE, "test_class");

device_create(test_class, NULL, test_dev, NULL, "test_dev");

2)那么这段代码都干了什么?

这段代码涉及到了4个对象,简单来说就是:A、fops结构体;B、dev对象;C、/sys/class下的对象;D、/dev下的对象;

这段代码完成了以下操作:创建/sys/class/test_class文件夹;创建/dev/test_dev设备文件;

这一切的原因是因为device_create创建/dev下的对象时,需要同时对设备驱动以及/sys/class中的一个对象进行绑定;最终在/dev下面出现代表设备的文件。

创建/dev应用层接口的代码一般放在驱动程序的初始化函数中。

=========================   /sys 应用层接口   ==========================

1./sys文件夹

该文件夹提供了整个linux的所有配置选项,对于驱动开发来说,我们需要重点关注的是/sys/class文件夹,因为该文件夹下的对象是创建/dev下设备文件的必要条件之一,并且它通常用于存放驱动程序的配置选项。

以下是该文件夹下的一个例子,以gpio为例:

/sys/class

┗ gpio # class

┣ export # class attribute

┣ unexport # class attribute

┣ ......

┗ pioA # device

┣ active_low   # device attribute

┣ direction   # device attribute

┣ edge # device attribute

┣ value # device attribute

┗ ......

2.和/dev的区别

1)接口差异:

/sys下只支持对read和write操作的定制;

2)读写差异:

/sys下,一旦执行open操作,那么show函数就会被执行并产生返回值,无论重复read多少次,都是同一个返回值,必须在close操作后才会被刷新;write没有经过严格测试,推荐这方面要了解一下。

3.代码样本

这一块的代码层次感是非常强的,但是涉及了非常多的变量,代码量也比较庞大,因此只能使用例子的形式来说明,希望大家能够仔细阅读分析,现在举以前做过的335x的eqep驱动的相应部分:

1)首先是class的实现

// 为class对象指定有哪些attribute,但该驱动不需要,因此为空

static struct class_attribute eqep_class_attrs_gs[] = {

__ATTR_NULL,

};

// 构造class对象

static struct class eqep_class_gs = {

.name = "eqep",

.owner = THIS_MODULE,

.class_attrs = eqep_class_attrs_gs,

};

/* 在设备初始化时,向系统注册class对象 */

static int __init EQEP_ClassInit(void) {

return class_register(&eqep_class_gs);

}

那么当这段程序执行完毕后,系统就会产生 /sys/class/eqep 这个类;

2)device的实现

//构造单个attribute

// 对应read函数

static ssize_t EQEP_ShowPosition(struct device *dev, struct device_attribute *attr, char *buf)

{

...

return sprintf(buf, "...");

}

// 对应write函数

static ssize_t EQEP_StorePosition(struct device *dev,  struct device_attribute *attr,  const char *buf, size_t len)

{

......

return len;

}

// 构造attribute,赋予读写权限和读写操作

static DEVICE_ATTR(position, S_IRUGO | S_IWUSR, EQEP_ShowPosition,        EQEP_StorePosition);

......

// 构造attribute列表,汇总所有的属性

static const struct attribute *eqep_attrs_gs[] = {

&dev_attr_all_regs.attr,

&dev_attr_position.attr,

&dev_attr_mode.attr,

&dev_attr_run.attr,

&dev_attr_timer_period.attr,

NULL,

};

static const struct attribute_group eqep_device_attr_group_gs = {

.attrs = (struct attribute **) eqep_attrs_gs,

};

3)device对象的注册,完成后将出现一系列属性

int EQEP_DeviceCreate(struct EQEP_Chip_t *eqep) {

......

// 创建device对象

sprintf(device_name, "%s.%d", eqep->pdev->name, eqep->pdev->id);   eqep_device = device_create(&eqep_class_gs, NULL, MKDEV(0, 0), NULL, device_name);

......

// 向device对象中注册attributes

ret = sysfs_create_group(&eqep_device->kobj, &eqep_device_attr_group_gs);

......

return 0;

}

=============================   对硬件的操作   ==========================

此处只讲片上设备的操作,因为对外部设备的操作都可以归结为对CPU的片上设备进行操作。本章节只讲片上设备操作的API接口。

1.基本概念——内存映射

内存映射,在这里主要指ioremap函数,作用是将物理地址映射到内核虚拟地址中,提供物理设备的访问途径。

2.申请硬件资源,即内存映射

struct resource  *r;

void __iomem *phy_addr = 0x********;

u32 size = ***;

void __iomem *virt_addr;

r = request_mem_region(phy_addr, size, "dev name");

virt_addr = ioremap(phy_addr, size);

3.读写

读写虚拟内存地址需要用特殊的API函数:

readb, readw, readl, writeb, writew, writel;

====================   旧版platform_driver的简易说明   ===================

待完善,现在这种方式已经逐渐被淘汰了

====================   设备树与新版platform的简易说明   ===================

1)平台设备驱动框架

这种框架的适用范围:依赖于硬件的驱动程序。

只要compatible匹配,那么节点的信息会被传递给驱动程序,若找不到匹配的设备树节点,则驱动不会被启动,这种框架的好处是携带多个驱动程序的同一个系统镜像可以兼容不一样的设备树,不会出现驱动程序找不到硬件的情况。

2)示例:

设备树创建节点:

/ {

new_dev {

compatible = "test,new_dev";

status = "okay";

};

};

编写简易驱动:

static int Test_Probe(struct platform_device *pdev)

{

return 0;

}

static int __devexit Test_Remove(struct platform_device *pdev)

{

return 0;

}

static const struct of_device_id test_of_match[] = {

{

.compatible = "test,new_dev",

},

{ }

};

static struct platform_driver test_driver = {

.driver = {

.name = "new_dev",

.owner = THIS_MODULE,

.of_match_table = of_match_ptr(test_of_match),

},

.probe =    Test_Probe,

.remove =   Test_Remove,

};

linux驱动程序框架基础的更多相关文章

  1. 嵌入式Linux驱动学习之路(九)Linux系统调用、驱动程序框架

    应用程序通过open  read  write close 等函数来操作计算机硬件.类似是一个接口. 当应用程序调用这些接口程序时,计算机是如何进入内核的呢?这是经过了系统调用. 实际上当调用接口函数 ...

  2. .NET面试题系列[1] - .NET框架基础知识(1)

    很明显,CLS是CTS的一个子集,而且是最小的子集. - 张子阳 .NET框架基础知识(1) 参考资料: http://www.tracefact.net/CLR-and-Framework/DotN ...

  3. 18.tty驱动程序框架

    tty驱动程序框架 一.TTY概念解析 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备. 1.1串口终端(/dev/ttyS*) 串口终端是使用计算机 ...

  4. Linux驱动程序学习【转】

    本文转载自: 一直在学习驱动,对于下面这篇文章,本人觉得简洁明了,基本符合我们学习驱动的进度与过程,现转发到自己的博客,希望能与更多的朋友分享. 了解Linux驱动程序技巧学习的方法很重要,学习lin ...

  5. 详细讲解Linux驱动程序

    一  编写Linux驱动程序 1.建立Linux驱动骨架 Linux内核在使用驱动时需要装载与卸载驱动 装载驱动:建立设备文件.分配内存地址空间等:module_init 函数处理驱动初始化 卸载驱动 ...

  6. tty驱动程序框架

    tty驱动程序框架 一.TTY概念解析 在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备. 1.1串口终端(/dev/ttyS*) 串口终端是使用计算机 ...

  7. Linux SPI框架(下)

    分类: Linux驱动程序2012-07-11 20:44 3006人阅读 评论(2) 收藏 举报 linuxstructlistclassdelayprocessing 水平有限,描述不当之处还请之 ...

  8. linux arm mmu基础【转】

    转自:http://blog.csdn.net/xiaojsj111/article/details/11065717 ARM MMU页表框架 先上一张arm mmu的页表结构的通用框图(以下的论述都 ...

  9. 2.5 USB摄像头驱动程序框架

    学习目标:根据vivi驱动架构和linux-2.6.31/linux-2.6.31.14/drivers/media/video/uvc/Uvc_driver.c驱动源码,分析usb摄像头驱动程序框架 ...

随机推荐

  1. Hbase原理

    Hbase原理 概述 HBase是一个构建在HDFS上的分布式列存储系统:HBase是基于Google BigTable模型开发的,典型的key/value系统:HBase是Apache Hadoop ...

  2. Python进阶08 异常处理

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 本文特别感谢kylinfish的纠正,相关讨论见留言区. 异常处理 在项目开发中, ...

  3. oc 中随机数的用法(arc4random( ) 、random( )、CCRANDOM_0_1( )

    来源:http://www.cnblogs.com/jay-dong/archive/2012/07/23/2604916.html 1).arc4random() 比较精确不需要生成随即种子 使用方 ...

  4. DIY--主板跳线接法

    如下图:

  5. list,set,map,数组之间的相互转换详细解析

    1.list转setSet set = new HashSet(new ArrayList()); 2.set转listList list = new ArrayList(new HashSet()) ...

  6. [ActionScript 3.0] AS3.0 Loader加载子swf时是否需要指定新的应用程序域ApplicationDomain

    实际应用中, Loader加载子swf时是否需要指定新的应用程序域ApplicationDomain,需要择情况而定. 1.如果在本地将项目位置添加到flashplayer受信任位置(上一篇文章所述) ...

  7. 九度OJ1468

    这道题其实就是个很简单的静态链表,需要注意的是,地址一共有5位,最后输出的时候如果之前是使用int类型存储地址的话,一定要强制规定输出的位数(5位),否则有可能会将高位省略.(如地址00001输出为1 ...

  8. c语言实现词频统计

    需求: 1.设计一个词频统计软件,统计给定英文文章的单词频率. 2.文章中包含的标点不计入统计. 3.将统计结果以从大到小的排序方式输出. 设计: 1.因为是跨专业0.0···并不会c++和java, ...

  9. Codeforces 631C

    题意:给定n和m. 给定一个长度为n的序列,m次操作. 接下来m次操作,每行第一个数若为1,则增序排列,若为2则降序排列,第二个数是排列的范围,即从第一个数排序到第某个数. 思路: 首先,对于其中范围 ...

  10. (转)log4net的配置详解

    原文地址:http://blog.csdn.net/pfe_nova/article/details/12225349 log4net是一款优秀的第三方日志框架,可以很容易的加载到开发项目中(引用lo ...