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

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

第二节是/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. Java多线程之银行出纳员仿真

    package concurrent; import java.util.LinkedList; import java.util.PriorityQueue; import java.util.Qu ...

  2. 对 HTTP 304 的理解(转-并增加自己的测试)

    作者:吴俊杰 性别:男 邮箱:sshroot@126.com 文章类型:原创 博客:http://www.cnblogs.com/voiphudong/ 转自: http://www.cnblogs. ...

  3. testng.xml创建及解析

    项目右键---TestNG -----> Convert to TestNG 会自动产生一个testng.xml的文件 http://www.cnblogs.com/choosewang/art ...

  4. jdbc调用sparksql

    将hive-site.xml拷贝到spark目录下conf文件夹 local模式 spark-sql --driver-class-path /usr/local/hive-1.2.1/lib/mys ...

  5. 产生library cache latch原因

    产生library cache latch原因The library cache latches protect the cached SQL statements and objects' defi ...

  6. JavaScript对象的创建之外部属性定义方式(基于已有对象扩充其属性和方法)

    var person = new Object(); person.name = "luogk"; person.age = 33; person.say = function() ...

  7. [转载]python中multiprocessing.pool函数介绍

    原文地址:http://blog.sina.com.cn/s/blog_5fa432b40101kwpi.html 作者:龙峰 摘自:http://hi.baidu.com/xjtukanif/blo ...

  8. 性能测试脚本新玩法---fiddler&&Jmeter

    飞测说:最近接触移动项目,测试app,需要做移动app的性能测试,想通过代理来录制,但是jmeter的代理录制效果真心不是很好,自己一个个请求来写代码,太浪时间了,那么有没有其他的办法呢? 我们都知道 ...

  9. JMeter二次开发(2)-编写 JSON Assertion 插件

    本篇文章主要介绍如何对JMeter进行二次开发,添加自己所需的功能.这里以Json验证为例进行说明.在web接口测试过程中,JSON的应用已经非常普遍,但原声的JMeter并没有提供Json及Json ...

  10. 一步一步学习Unity3d学习笔记系1.4单服模式架构

    单服模式更适合做手游,只有一个服务器,在程序中通过代码模块来实现各功能,而不是物理模块划分. 登录模块实现,账号数据处理, 用户模块,处理角色权限处理, 匹配模块,匹配战斗 好友模块,负责好友管理 战 ...