收藏 分享(赏)

Linux字符设备驱动.doc

上传人:wo7103235 文档编号:6250256 上传时间:2019-04-03 格式:DOC 页数:42 大小:360KB
下载 相关 举报
Linux字符设备驱动.doc_第1页
第1页 / 共42页
Linux字符设备驱动.doc_第2页
第2页 / 共42页
Linux字符设备驱动.doc_第3页
第3页 / 共42页
Linux字符设备驱动.doc_第4页
第4页 / 共42页
Linux字符设备驱动.doc_第5页
第5页 / 共42页
点击查看更多>>
资源描述

1、本科毕业论文(科研训练、毕业设计)题 目:Linux 字符设备驱动姓 名:学 院:软件学院系:专 业:软件工程年 级: 学 号: 指导教师(校内): 职称: 指导教师(校外): 职称: 年 月摘 要驱动程序在 Linux 内核里扮演着特殊的角色,它们完全隐藏了设备工作的细节。用户通过一套标准化的调用来操作设备,这些调用是与特定的驱动相互独立的;设备驱动的任务就是将这些调用映射到作用于实际硬件的相关设备操作上。这个编程接口使得驱动可以与内核的其他部分分开建立,并在需要的时候“插入” 。这种模块化的方法使得 Linux 驱动易写,易于修改。本文通过在嵌入式 Linux 上实现一个模块化的 RTC

2、设备驱动实例,分析了Linux 内核中字符设备驱动的运行机制,并着重介绍了 Linux 字符设备驱动的关键过程,包括基本字符设备驱动,I2C 总线驱动,设备底层操作等。同时也展示了通过交叉编译来开发和调试的全过程。关键词:字符设备;设备驱动;I2C;实时时钟;交叉编译。AbstractDevice drivers take on a special role in the Linux kernel; they hide completely the details of how the device works. User activities are performed by means o

3、f a set of standardized calls that are independent of the specific driver; mapping those calls to device-specific operations that act on real hardware is then the role of the device driver. This programming interface is such that drivers can be built separately from the rest of the kernel and “plugg

4、ed in“ at runtime when needed. This modularity makes Linux drivers easy to write, easy to modify.Through an instance of realizing a modularized driver of the RTC device in the embedded Linux, this paper analyzes the function mechanism of the char device driver in detail, and pay more attention on th

5、e key process during the development of the Linux char device drivers, including the basic char device driver, the i2c bus driver, the device bottom operation and so on. The paper also presents the whole debug and development process with the cross compile method Key words: char device; device drive

6、r; I2C; RTC; cross compile.目 录第 1 章 引 言 11.1 Linux 简介 11.2 设备驱动 .11.3 Linux 驱动 .21.4 选题背景 .3第 2 章 字符设备驱动框架 42.1 注册设备文件 .42.1.1 设备号 42.1.2 注册设备号 42.1.3 释放设备号 52.1.4 创建设备节点 52.2 内核设备注册 .62.3 设备操作索引 .62.4 设备操作函数 .72.4.1 Open() .72.4.2 Release()72.4.3 Read()72.4.4 Write() .82.4.5 ioctl() .8第 3 章 设计与实现 9

7、3.1 设计思路 .93.1.1 设计目标 93.1.2 解决方案 93.1.3 最终方案 93.1.4 细节 103.1.5 数据流图 103.2 I2C 总线驱动 .113.2.1 I2C 背景 113.2.2 传输格式 113.2.3 标志位 START - 5 -这里,first 是你要分配的起始设备编号。first 的次编号部分通常是 0。count 是你请求的连续设备的总数。name 是应当连接到这个编号范围的设备的名字,它会出现在 /proc/devices 和 sysfs 中。如果不确定所要的设备号,则通过下面函数获得int alloc_chrdev_region(dev_t

8、*dev, unsigned int firstminor, unsigned int count, char *name)这里,dev 是一个输出参数, 它在函数成功完成时持有你的设备号。firstminor 是请求的第一个要用的次编号,通常设定为 0。count 是你请求的连续设备的总数。name 是应当连接到这个编号范围的设备的名字。2.1.3 释放设备号当从系统中卸载设备时,应该释放相应的主设备号,通过如下函数完成:void unregister_chrdev_region(dev_t first, unsigned int count);2.1.4 创建设备节点如上所述,当不确定设备

9、的设备编号时,将动态分配设备号,但是由于每次分配的主设备号不能保证一致,因此无法预先知道将分配的设备号从而创建节点。为使用动态编号加载一个设备驱动,用一个脚本替换对 insmod 的调用,它通过读取/proc/devices 来创建节点,因为新分配的动态设备号总会注册到/proc/devices 中。脚本可以利用 awk 这类工具从/proc/devices 中获取信息,主要代码如下:module=” mypcf8563”device=” mypcf8563”major=$(awk “$2= =“$module“ print $1“ /proc/devices)在/dev 中创建文件,代码如下

10、:mknod /dev/$device0 c $major 0- 6 -2.2 内核设备注册在 Linux2.6 内核中,注册分配完设备节点,还应该注册字符设备。内核使用类型 struct cdev (include ) 来描述字符设备。在内核调用设备操作之前,必须先分配并定义一个或多个这种结构体,代码如下:pcf8563_devices = kmalloc(mypcf8563_nr_devs * sizeof(struct pcf8563_dev),GFP_KERNEL);cdev_init(pcf8563_devices-cdev.ops = cdev_add (其中 cdev_init

11、函数是注册 cdev 结构体的关键,原型如下:void cdev_init(struct cdev *cdev, struct file_operations *fops);这里,dev 是 cdev 结构,fops 就是设备的操作索引。一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用:int cdev_add(struct cdev *dev, dev_t num, unsigned int count);这里,dev 是 cdev 结构,num 是这个设备响应的第一个设备号,count 是应当关联到设备的设备号的数目,通常是 1。2.3 设备操作索引一旦驱动程序被注册到内核表中

12、,它的操作就和指定的主设备号对应起来,这时候在与主设备号对应的字符设备文件上进行某个操作时,内核就能从file_operations 结构中找到并调用正确的函数。这些函数则是整个设备操作的核心。struct file_operations mypcf8563_fops = .owner = THIS_MODULE,.read = mypcf8563_read,.write = mypcf8563_write,.ioctl = mypcf8563_ioctl,.open = mypcf8563_open,.release = mypcf8563_release,;- 7 -2.4 设备操作 函数

13、2.4.1 Open()对设备文件进行 Open()系统调用时,调用驱动程序的 Open()函数。完成一些初始化操作。Open()的主要任务是a) 检查设备特定的错误b) 如果设备是首次打开,对其进行初始化c) 如果有必要,更新 f_op 指针d) 分配并填写被置于 filp-private_data 里的数据结构部分代码如下:dev = container_of(inode-i_cdev, struct scull_dev, cdev);filp-private_data = dev; /* for other methods */2.4.2 Release()当最后一个打开设备的用户进程执

14、行 Close()系统调用时 ,内核将调用驱动程序的 Release()函数。Release()的主要任务是a) 释放由 open 分配的,保存在 filp-private_data 中的所有内容b) 在最后一次关闭操作时关闭设备2.4.3 Read()当对设备特殊文件进行 Read()系统调用时,将调用驱动程序 read()函数。read()函数的主要功能是从硬件设备或内核内存中读取或复制 count 个字节到 buf 指定的缓冲区中。设备在 Read()时状态较多,因此设计不同的返回值代表相应含义是 Read()设计的重要内容。可以考虑返回正数,0,和代表各种错误号的负值。- 8 -本驱动

15、中 Read()将读取时间,是驱动的关键函数之一。2.4.4 Write()当设备特殊文件进行 Write()系统调用时,将调用驱动程序的 Write()函数。Write()函数的主要功能是将参数 buf 指定的缓冲区中的 count 个字节内容复制到硬件或内核内存中。Write()设备和 Read()设备相似,同样需要设计不同的返回值,同样考虑返回正数,0,和代表各种错误号的负值。本驱动中 Write()将设置时间,是驱动的关键函数之一。2.4.5 ioctl()ioctl 是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息。ioctl 通常是设备驱动程序中对设备的 I/O

16、通道进行管理的函数。所谓对 I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。也经常在这里实现设备的读写。调用如下:int ioctl(int fd, ind cmd, );本驱动中 ioctl()将通过 cmd 读取或设置时间,是驱动的关键函数之一。- 9 -第 3 章 设计与实现3.1 设计思路3.1.1 设计目标在 Linux 开发编译环境下设计 RTC 设备(PCF8563)在 MPC8272 平台上的驱动程序。主要完成 I2C 协议/RTC 原理分析,实现基于 I2C 总线驱动的 RTC 设备的初始化,同时提供时间设置、查询接口。这个字符设备驱动

17、程序应该是完整的模块化的,符合 POSIX 通用 Linux 设备标准,能够正确地初始化设备,实现 char 设备的基本操作 open / release / read / write / ioctl,正确地实现内核与设备间的数据交换,同时还要解决冲突等异常情况。3.1.2 解决方案本设计要实现的是基于 I2C 总线的 PCF8563(RTC)设备驱动,有下列解决方案:1. 应用 Linux 系统内核对 I2C 设备的支持,通过 I2C 总线驱动 I2C core, I2C 控制器驱动 I2C adapter,I2C 设备驱动 I2C driver 的系列模型为 PCF8563 提供 I2C

18、总线的接口,直接读取设备,设置设备。2. 编写基本 I2C 总线规范,根据规则直接对 PCF8563 实现 I2C 规则的读写。3.1.3 最终方案方案 1 的实现相对简单,但是在目标文件中会产生大量冗余代码,与嵌入式开发追求高效的原则不符。而且本设计只针对一个 I2C 设备,完全可以只针对本设备提供需要的 I2C 接口,一方面大大减小了接口的复杂度,另一方面也能加深对 I2C 总线的理解和应用。- 10 -尽管采用方案 2,本设计仍然通过命名(i2c_func,pcf8563_func )将 i2c 驱动与 pcf8563 驱动在逻辑上分隔开,使其支持重用。3.1.4 细节PCF8563 属

19、于 RTC 实时时钟设备。RTC 在内核中通常注册为 misc 设备,即主设备号 10,次设备号 235(RTC_ MINOR),设备写入/proc/misc。由于本设计偏实验研究性质,为了避免与内核 RTC 设备冲突,采用普通字符设备的动态注册。即动态分配主设备号,次设备号。设备写入/proc/devices。设备的底层操作通过通用 I/O 口实现。3.1.5 数据流图用户接口 t e s t . c设备内核中的设备节点I 2 C 驱动o p e n ( )r e l e a s e ( )r e a d ( )w r i t e ( )i o c t l ( )p c f 8 5 6 3

20、设备驱动通过 f i l e _ o p e r a t i o n s 索引图 3-1 PCF8563 数据流图- 11 -3.2 I2C 总线驱动3.2.1 I2C 背景I2C(Intel Integrated Circuit)总线是荷兰的 Philips 公司于八十年代初推出的一种芯片间串行总线扩展技术。它用两根线(数据线 SDA、时钟线 SCL)完成总线上主机与器件的全双工同步数据传送,可方便地构建多主机系统和外围器件扩展系统。I2C 总线支持所有 NMOS、CMOS、TTL 等工艺制造的器件,其上所有的节点都连到同名的 SDA、SCL 上,所有数据传送都有相同的操作模式,所有 I2C

21、的外围器件都有应答能力,而且具有简单、高效,占用空间小,互联成本低等特点。因此广泛适用于嵌入式产品。I2C 总线在连接到总线的器件间传递信息时,每个器件都有各自唯一的地址识别。根据数据传输时的功能不同,把器件分为主机和从机。主机是初始化总线的数据传输并产生允许传输的时钟信号的器件,通常是微控制器。此时,任何被寻址的器件都被认为是从机,例如 LCD 驱动器、E2PROM 等。 3.2.2 传输格式PCF8563 驱动主要基于 I2C 总线驱动实现,因此首先应遵循 I2C 总线标准。I2C 总线协议规定,各设备进行通信时都要有起始、结束、发送数据和应答信号。这些信号都是通信过程中的基本单元。图 3

22、-2 I2C 总线标准如图 3-2 所示,SDA 线上传输的每一个字节都必须是 8-bits 长,启动总线后的第 1 个字节的高 7 位是对从机的寻址地址,第 8 位为方向位(“0”表示主机对从机的写操作;“1”表示主机对从机的读操作),其余的字节为操作数据。- 12 -每一次所传输的字节数不限,传输的数据都是高位优先传输(MSB)的。每传输完一个字节,必须跟随一个 ack 位(ack 方向与数据传输方向相反)。因为本 RTC 驱动仅有一个主机,因此不考虑多主机需要仲裁的情况。3.2.3 标志位 START SET_HIGH (I2C_SDA);SET_HIGH (I2C_SCL);SET_L

23、OW (I2C_SDA);2. STOP当 SCL 线是高电平时,SDA 线从低电平向高电平切换,这个状态代表一个 STOP 标志。相应伪代码实现如下:static void i2c_stop (void)SET_LOW (I2C_SCL);SET_LOW (I2C_SDA);SET_HIGH (I2C_SCL);SET_HIGH (I2C_SDA);udelay (5);- 13 -3.2.4 响应数据传输的过程中,接收器件每接收一个字节数据要产生一个 ACK 信号,向发送器件发出低电平脉冲,表示已经收到数据。1. 读响应相应伪代码实现如下:static int i2c_read_ack (

24、void)SET_LOW (I2C_SCL);while (not receive ack)if( timeout ) printk(“I2C not ACKn“);return -1; /* ack timeout */udelay (5);SET_HIGH (I2C_SCL);SET_LOW (I2C_SCL);SET_HIGH (I2C_SDA);return 0;2. 发送响应相应伪代码实现如下:static void i2c_write_ack (int ack)SET_LOW (I2C_SCL);if (ack = I2C_ACK)SET_LOW (I2C_SDA);else /

25、noackSET_HIGH (I2C_SDA);SET_HIGH (I2C_SCL);udelay (5);SET_LOW (I2C_SCL);- 14 -3.2.5 传输流图如图 3-3 为一次完整的 I2C 传输:图 3-3 I2C 数据传输3.2.6 引脚地址本 I2C 总线架构在 MPC8272 上的 CPM2 上,因此 IO 口 SDA,SCL 等引脚设置都应参照 MPC8272 上 CPM2 的说明。表格 3-1 MPC8272 Data Sheet因此 SDA 和 SCL 引脚分别为 PD15,PD14。宏定义如下:/* I2C Pins */#define I2C_SCL 0x

26、00020000 /* PD 14 0000 0000 0000 0010 0000 0000 0000 0000 */#define I2C_SDA 0x00010000 /* PD 15 0000 0000 0000 0001 0000 0000 0000 0000 */3.2.7 电平设置知道了 SDA,SCL pins 的位置,就可以设置其电平。宏定义如下:#ifndef CPM_MAP_ADDR- 15 -#define CPM_MAP_ADDR (uint)0xF0000000)#endif#define SET_HIGH(bit) do volatile iop_cpm2_t *

27、io; volatile cpm2_map_t* immap = (cpm2_map_t*)CPM_MAP_ADDR; io = (iop_cpm2_t *) io-iop_pdird |= bit;/* set 1 for output */ io-iop_pdatd |= bit;/* set 1 for high */ udelay (5); while (0)通过 cpm2_map_t 结构体获得引脚的地址映射,借用之前定义的 SDA,SCL掩码对其进行相应的 |= ,/* set 0 for general - purpose IO */io-iop_podrd |= (I2C_SC

28、L | I2C_SDA);/* set 1 for Open Drain Output */初始化同时需要发送一个 STOP 信号,中止之前可能正在进行的传输。i2c_stop (); /* stop first */udelay (25);3.2.9 I2C 写数据正确初始化好设备,并定义了正确的 I2C 信号函数之后,就可以通过 I2C标准写入数据。1 写入位在 SCL 低电平时根据要写入的位改变 SDA 电平,然后将 SCL 拉高,通过采样 SDA 线获得写入的位。代码如下:static void i2c_write_bit (int bit)SET_LOW (I2C_SCL);if (

29、bit)SET_HIGH (I2C_SDA);- 16 -elseSET_LOW (I2C_SDA);SET_HIGH (I2C_SCL);2 写入字节I2C 传输的数据都是高位优先传输(MSB)的,因此传输字节时,分别剥离出高位,依次传送,最后等待接收端回送一个 ack 信号。代码如下:static int i2c_write_byte (unsigned char byte)int i;for (i = 0; i 4) - 19 -static unsigned int bin2bcd (unsigned int n)return (n / 10) tm_sec = bcd2bin (se

30、c tmp-tm_min = bcd2bin (min tmp-tm_hour = bcd2bin (hour /* 24 Hour */tmp-tm_wday = bcd2bin (wday tmp-tm_mday = bcd2bin (mday tmp-tm_mon = bcd2bin (mon tmp-tm_year = bcd2bin (year); /* 0-99 1970-2069*/tmp-tm_yday = 0;tmp-tm_isdst = 0;3.3.7 设置时间同上,转换成 BCD 码后写入寄存器。year = bin2bcd (tmp-tm_year % 100);pcf8563_write_reg (0x08, year);pcf8563_write_reg (0x07, mon);pcf8563_write_reg (0x06, wday);

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 学术论文 > 毕业论文

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报