<4D F736F F D20C7B6C8EBCABDCFB5CDB3BFAAB7A2CAB5D1E9CBC42E646F63>

Similar documents
static struct file_operations gpio_ctl_fops={ ioctl: gpio_ctl_ioctl, open : gpio_open, release: gpio_release, ; #defineled1_on() (GPBDAT &= ~0x1) #def

华恒家庭网关方案

嵌入式Linux知识培训

C/C++ - 文件IO

uClinux for blackfin

DVK530/531扩展板

Microsoft Word - 在VMWare-5.5+RedHat-9下建立本机QTopia-2.1.1虚拟平台a.doc

Microsoft Word - 实用案例.doc

1.01

新・明解C言語入門編『索引』

Guava学习之Resources

CC213

C 1

chap07.key

六域链联盟 SDChain-Matrix 节点搭建指南 2018/07/26 Version : 1.0.0

Microsoft Word - linux命令及建议.doc

<4D F736F F D20B5DAC8FDCBC4D5C2D7F7D2B5B4F0B0B82E646F63>

Linux服务器构建与运维管理

<4D F736F F D20B5DA36D5C22020D7D6B7FBC9E8B1B8C7FDB6AF>

ebook15-12

C语言的应用.PDF

嵌入式Linux块设备驱动开发解析

ebook15-C

C

Microsoft PowerPoint - Chapter7-DriverDevices.ppt

新版 明解C++入門編

嵌入式系统实验报告之一

untitled

C++ 程序设计 告别 OJ1 - 参考答案 MASTER 2019 年 5 月 3 日 1

C C

. Outline 编译 Linux 在 QEMU 模拟器上运行制作带 grub 启动的磁盘映像...1 编译 Linux 在 QEMU 模拟器上运行...2 制作带 grub 启动的磁盘映像

C++ 程式設計

epub 33-8

ebook35-14

Abstract arm linux tool-chain root NET-Start! 2

提纲 1 2 OS Examples for 3

DVK530/531扩展板

嵌入式系统原理及应用教程 ( 第 2 版 )/ 清华大学出版社 EL-ARM-860 V1.2 一 实验目的 实验二 Boot Loader 引导程序 1. 了解 Boot Loader 的作用, 掌握 Boot Loader 的编程思想 二 实验设备 1. Pentium II 以上的 PC 机,

ebook15-4

ebook35-21

Linux 操作系统分析 Chapter 9-2 Linux 中程序的执行 陈香兰 苏州研究院中国科学技术大学 Fall 2014 November 4,

C/C++语言 - C/C++数据

第11章 可调内核参数

Microsoft PowerPoint - soc_fpga_software_dev.ppt [相容模式]

c_cpp

Microsoft PowerPoint - 嵌入式系统设计课件第五讲.ppt

Ioncube Php Encoder 8 3 Crack 4. llamaba octobre traslado General Search colony

untitled

1

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc

Andes Technology PPT Temp

Microsoft Word - 36.doc

S3C6410 ARM11开发板Linux BSP构建

FY.DOC

Linux内核的移植技术剖析

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

SEP4020 Linux2

<4D F736F F D20C7B6C8EBCABD6C696E7578BBF9B4A1CAB5D1E92E646F63>

F515_CS_Book.book

目录 1 IPv6 快速转发 IPv6 快速转发配置命令 display ipv6 fast-forwarding aging-time display ipv6 fast-forwarding cache ipv6 fas

6 C51 ANSI C Turbo C C51 Turbo C C51 C51 C51 C51 C51 C51 C51 C51 C C C51 C51 ANSI C MCS-51 C51 ANSI C C C51 bit Byte bit sbit


untitled

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例

普通高級中學課程

C/C++程序设计 - 字符串与格式化输入/输出

Microsoft Word - 扉页

2013 C 1 # include <stdio.h> 2 int main ( void ) 3 { 4 int cases, a, b, i; 5 scanf ("%d", & cases ); 6 for (i = 0;i < cases ;i ++) 7 { 8 scanf ("%d %d

1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File

C PICC C++ C++ C C #include<pic.h> C static volatile unsigned char 0x01; static volatile unsigned char 0x02; static volatile unsigned cha

SEP4020 Linux2

手册 doc

<4D F736F F D204C696E7578CFB5CDB3B5F7D3C3C1D0B1ED>

_汪_文前新ok[3.1].doc

标题

字元設備字元設備 (char device) 和普通檔案系統的區別 : 普通檔案系統可以來回讀 / 寫, 而大多字元設備僅僅是資料通道, 只能順序讀 / 寫 應用程式使用標準系統調用打開 (open) 讀取(read) 寫(write) 和關閉 (close), 完全好像這個設備是一個普通檔一樣 初

Microsoft Word - 正文.doc

1 LINUX IDE Emacs gcc gdb Emacs + gcc + gdb IDE Emacs IDE C Emacs Emacs IDE ICE Integrated Computing Environment Emacs Unix Linux Emacs Emacs Emacs Un

JLX

本文由筱驀釹贡献

X713_CS_Book.book

int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++;

Chapter #

untitled

<4D F736F F F696E74202D20C7B6C8EBCABDC7FDB6AFBFAAB7A22DD2D7CBC9BBAA2E BBCE6C8DDC4A3CABD5D>

ebook15-10

第一次寫Linux Driver就上手

CC213

NOWOER.OM m/n m/=n m/n m%=n m%n m%=n m%n m/=n 4. enum string x1, x2, x3=10, x4, x5, x; 函数外部问 x 等于什么? 随机值 5. unsigned char *p1; unsigned long *p

第11章、嵌入式Linux设备驱动开发

《C语言程序设计》教材习题参考答案

学习MSP430单片机推荐参考书

EK-STM32F

Outline 制作带 grub 启动的磁盘映像 利用 qemu+gdb 来调试 linux

幻灯片 1

目录 1 IPv6 快速转发 IPv6 快速转发配置命令 display ipv6 fast-forwarding aging-time display ipv6 fast-forwarding cache ipv6 fas

第3章.doc

/3/15 1, linux. linux,,. : 1.NAT ; 2. (load balance, virtual server);; 3. ; 4. ; 5. 6.VPN; 7. ; 8. ; 9.. (,

Oracle Solaris Studio makefile C C++ Fortran IDE Solaris Linux C/C++/Fortran IDE "Project Properties" IDE makefile 1.

Transcription:

嵌入式系统实验四 Linux 下设备驱动程序的开发 3.1 设备驱动程序的开发流程 进行嵌入式 Linux 系统的开发, 很大的工作量是为各种设备编写驱动程序 在 ARM 平台上开发嵌入式 Linux 的设备驱动程序与在其他平台上开发是一样的 总的来说, 实现一个嵌入式 Linux 设备驱动的大致流程如下 : (1) 查看原理图, 理解设备的工作原理 (2) 定义主设备号 (3) 在驱动程序中实现驱动的初始化 如果驱动程序采用模块的方式, 则要实现模块初始化 (4) 设计所要实现的文件操作, 定义 file_operations 结构 (5) 实现中断服务 ( 中断并不是每个设备驱动所必须的 ) (6) 编译该驱动程序到内核中, 或者用 insmod 命令加载 (7) 测试该设备 3.2 linux 下字符设备的驱动开发实例 ----LED 驱动 ( 可参考 FS2410P 实验指导手册 v2.1.2.pdf,302-313) (1) 实验内容 :4 个 LED 灯轮流闪烁 本节要求实现在一个字符设备驱动里面实现对 GPIO 端口的操作 在模块加载的时候跑马灯运行起来 模块卸载的时候, 跑马灯停止 FS2410P 上的 4 个 LED 指示灯由 4 个 I/O 口控制, 它们分别是 :GPF4~GPF7, 输出低电平时候, 相应的 LED 指示灯亮 (2)LED 的原理图 FS2410P 带有 4 个用户可编程 I/O 方式 LED, 如图 1 所示 LED 硬件原理图, 下表为 LED 对应的 I/O 口 表 1 用户指示灯占用 CPU 资源列表 序号名字 CPU 端口资源 1 LED1 GPF4 2 LED2 GPF5 3 LED3 GPF6

4 LED4 GPF7 (3) LED 驱动源代码及说明 图 1 LED 原理图 在 /s3c2410 下新建一个目录 :gpiodrv #mkdir /s3c2410/gpiodrv #cd /s3c2410/gpiodrv 在 /s3c2410/gpiodrv 目录下用 vi 编辑器编写符合上面功能的驱动源程序 gpiodrv.c #cd /s3c2410/gpiodrv #vi gpiodrv.c #include <linux/config.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/iobuf.h> #include <linux/major.h> #include <asm/uaccess.h> #include <asm/hardware.h> #include <asm/arch/cpu_s3c2410.h> #include <asm/io.h> #include <linux/vmalloc.h> #include <linux/delay.h> #define IOPORT_MAJOR 220 int magic_leds_open(struct inode *inode, struct file *filp);

int magic_leds_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); int magic_leds_release(struct inode *inode, struct file *filp); static struct file_operations magic_leds_fops = ioctl: magic_leds_ioctl, open: magic_leds_open, release: magic_leds_release, ; #define LED1_ON() (GPFDAT &= ~0x10) #define LED2_ON() (GPFDAT &= ~0x20) #define LED3_ON() (GPFDAT &= ~0x40) #define LED4_ON() (GPFDAT &= ~0x80) #define LED1_OFF() (GPFDAT = 0x10) #define LED2_OFF() (GPFDAT = 0x20) #define LED3_OFF() (GPFDAT = 0x40) #define LED4_OFF() (GPFDAT = 0x80) static int ledstatus; void LedSet(int led) ledstatus = led; if (ledstatus & 1) LED1_ON(); else LED1_OFF(); if (ledstatus & 2) LED2_ON(); else LED2_OFF(); if (ledstatus & 4) LED3_ON(); else LED3_OFF(); if (ledstatus & 8) LED4_ON(); else LED4_OFF();

void LedDisy(void) LedSet(0x08); udelay(0x500000); LedSet(0x04); udelay(0x500000); LedSet(0x02); udelay(0x500000); LedSet(0x01); udelay(0x500000); LedSet(0x02); udelay(0x500000); LedSet(0x04); udelay(0x500000); LedSet(0x08); udelay(0x500000); static int init magic_leds_init(void) int result = 0; printk("magic_leds_init\n"); result = register_chrdev(ioport_major, "gpio", &magic_leds_fops); if (result < 0) printk( "Failed to register major.\n"); return result; printk("success to register\n"); return 0; int magic_leds_open(struct inode *inode, struct file *filp) GPFCON = 0x5500; GPFUP = 0xff; printk( "open gpio devices\n"); return 0;

void exit magic_leds_exit(void) unregister_chrdev(ioport_major, "gpio"); int exit magic_leds_release(struct inode *inode, struct file *filp) printk("release this device\n"); return 0; int magic_leds_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) int err = 0; if (cmd == 1) while(arg--) LedDisy(); printk("..."); printk("\n"); return 0; return err; module_init(magic_leds_init); module_exit(magic_leds_exit); (3) 编译安装 LED 驱动 同样, 在 /s3c2410/gpiodrv 目录下用 vi 编辑器编写该驱动程序的 Makefile 文件 :(Makefile 的编写可参考 Makefile 中文教程.pdf) #vi Makefile

输入以下内容 : CROSS = arm-linux-gcc CFLAGS=-D KERNEL CFLAGS+=-DMODULE CFLAGS+=-I/s3c2410/2.4.18-rmk7/include CFLAGS+=-I/s3c2410/2.4.18-rmk7/include/linux CFLAGS+=-I/usr/local/arm/2.95.3/include CFLAGS+=-Wall -Wstrict-prototypes -Wno-trigraphs -Os -mapcs CFLAGS+=-fno-strict-aliasing -fno-common -fno-common -pipe -mapcs-32 CFLAGS+=-march=armv4 -mtune=arm9tdmi -mshort-load-bytes -msoft-float CFLAGS+=-DKBUILD_BASENAME=gpiodrv all: gpiodrv.o gpiodrv.o: gpiodrv.c $(CROSS) $(CFLAGS) -o gpiodrv.o -c gpiodrv.c clean: -rm -f $(EXEC) *.o *~ core 将 gpiodrv.c 和 Makefile 这个放置在同一个新建目录下 gpiodrv 下, 进入这个目录, 输入 make 后, 编绎成功后将在这个目录下生成一个 gpiodrv.o 文件 #cd /s3c2410/gpiodrv #make 3.3 linux 下字符设备的驱动开发实例 测试 LED (1) 在 /s3c2410/gpiodrv 目录下用 vi 编辑器编写 led 驱动程序相应的测试程序 gpio_test.c #vi gpio_test.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcnt1.h> #include <errno.h> #include <linux/delay.h> #include <sys/ioctl.h> int main(int argc, char **argv) int i; int fd;

fd = open("/dev/gpio", 0); if (fd < 0) perror("failed to open device"); exit(1); while(1) printf("please select number to run program\n"); printf("1:led on \n2:quit"); scanf("%d",&val); if(val == 1) ioct1(fd,1,10); else if(val == 2) close(fd); return 0; 编译 gpio_test.c, 得到可执行文件 gpio_test 即用下面的命令 : #arm-linux-gcc o gpio_test gpio_test.c 3.4 linux 下字符设备的驱动开发实例 实验步骤 ( 也可参考 FS2410P 实验指导手册 v2.1.2.pdf,311-313 上的方法 ) (1)PC 机进入 LINUX 系统, 配置好 minicom, 连接好串口线, 让 FS2410P 教学实验平台进入 LINUX 环境, 利用 minicom 来显示 (2) 将编绎生成的 gpiodrv.o 和 gpio_test 用 NFS mount 到 /tmp 目录下 ( 方法参考实验三 --- 通过 NFS 进入映射 ) # mount 192.168.3.111:/s3c2410 /tmp # cd /tmp # cd /gpiodrv (3) 加载设备驱动 gpiodrv.o 模块 : insmod gpiodrv.o 如果加载成功, 可以通过 cat /proc/devices 命令查看该设备的相关信息 卸载该设备驱动模块的命令是 :rmmod gpiodrv (4) 建立 gpio 设备节点 :mknod /dev/gpio c 220 0 /dev/gpio 为该设备驱动程序的设备名,C 表明该设备为字符设备,220 为该设备的主设备好,0 为从设备号

(5) 执行 gpio_test 程序 :./gpio_test (6) 在 minicon 终端选择 1, 回车, 可以看到 4 个 LED 灯轮流闪烁 选择 2, 则退出程序的运行 具体可看下图 (7) 将应用程序添加根文件系统, 并烧写到开发板 将 FS2410XP_camare_demo.cramfs 拷贝到 /s3c2410 目录下 在该目录下建立两个文件 : # cd /s3c2410/ # mkdir chang # mkdir guo 将 FS2410XP_camare_demo.cramfs 挂接到 chang 目录 # mount -o loop FS2410XP_camare_demo.cramfs chang 将 chang 目录下的内容压缩 # cd chang # tar -cvf /s3c2410/1.tar./ 这时, 将在 chang 的目录产生一个 1.tar 的包 # cd.. # mv 1.tar guo # cd guo # tar -xvf 1.tar

# rm 1.tar rm: 是否删除一般文件 1.tar? y 将自已的 gpiodrv.o 和 gpio_test 拷贝到相应的目录下 将 gpiodrv.o 拷贝到 guo/usr/ 目录下将 gpio_test 拷贝到 guo/bin 下 现在开始制作 cramfs 根文件系统./mkcramfs /s3c2410/guo FS2410XP_camare_demo.cramfs 下载 FS2410XP_camare_demo.cramfs 根文件系统到开发板 : 使用 tftpcmd 网络传输, 设置宿主机 IP 地址, 将其地址与开发平台的 IP 地址设置在同一网段内 这里, 将 PC 的 IP 设为 192.168.0.121 并把 tftpcmd 复制到 /bin 文件夹下 #cd /s3c2410/ guo 新建一个 down 文件 #vi down tftpcmd 202.193.9.21 69 put FS2410XP_camare_demo.cramfs 改变 down 的属性 #chmod 777 down 改变 tftpcmd 的属性 #chmod 777 /bin/tftpcmd 将开发板与 PC 机用交叉网线连接好, 复位开发板, 按住 A 键, 进入 BIOS 命令行状态提示符 :(minicom) \>netload #./down 或是双击 down 批处理文件, 选择在终端运行, 可以看到内核映像下载到了开发板 传输完后, 再输入命令 nfprog, 然后回车, 然后输入 2 选择第二个区块, 输入 Y 确认将文件烧写到 nandflash 中 重复操作 (3),(4),(5),(6), 可看到实验结果 Linux 设备驱动程序介绍 Linux 操作系统将所有的设备 ( 而不仅是存储器里的文件 ) 全部都看成文件, 都纳入文件系统的范畴, 都通过文件的操作界面进行操作 这意味着 : (l) 每一个设备都至少由文件系统的一个文件代表, 因而都有一个 文件名 每个这样的 设备文件 都唯一地确定了系统中地一项设备 应用程序通过设备地文件寻找访问具体地设备, 而设备则象普通文件一样受到文件系统访问权限控制机制地保护 (2) 应用程序通常可以通过系统调用 open() 打开 这个设备文件, 建立起与目标设备的连接 代表着该设备的文件节点中记载着建立这种连接所需的信息 对于执行该应用程序的进程而言, 建立起的连接就表现为一个已经打开的文件 (3) 打开了代表着目标设备的文件, 即建立起与设备的连接后, 就可以通过 read() write() ioctl() 等常规的文件操作对目标设备进行操作

Linux 将设备分成两大类 一类是像磁盘那样以记录块或 扇区 为单位, 成块进行输入 / 输出设备, 称为 块设备 ; 另一类是像键盘那样以字符 ( 字节 ) 为单位, 逐个进行输入 / 输出的设备, 称为 字符设备 文件系统通常都建立在块设备上 网路设备是介于块设备和字符设备之间的一种特殊设备 设备文件的属性由三部分信息组成 : 第一部分是文件的类型 (c/b), 第二部分是一个 主设备号, 第三部分是一个 次设备号 其中设备类型和主设备号结合在一起唯一地确定了设备文件地驱动程序及其界面, 而次设备号则说明目标设备是同类设备中的第几个 1 Linux 设备驱动程序概述 在 Linux 操作系统中, 驱动程序是操作系统内核与硬件设备的直接接口, 驱动程序屏蔽了硬件的细节, 驱动程序是内核的一部分, 他完成以下功能 : (l) 对设备初始化和释放 (2) 对设备进行管理, 包括实时参数设置以及提供对设备的操作接口 (3) 读取应用程序传送给设备文件的数据并回送应用程序请求的数据 (4) 检测是处理设备出现的错误 图 1 Linux 内核体系结构 如图 1 所示, 应用程序通过 Linux 的系统调用与内核通信 由于 Linux 中将设备当作文件处理, 所以对设备进行操作的调用和对文件操作的操作类似, 主要包括 open() read() write() ioctl() close() 等 应用程序发出系统调用命令后, 会从用户态转到内核态, 通过内核将 open() 这样的系统调用转换成对物理设备的操作 在 Linux 中通过分层实现对物理设备的调用, 并使得内核的结构清晰, 提高了模块化的独立性

2 驱动程序的结构 一般 Linux 设备驱动程序可以分为 3 个主要组成部分 : (1) 自动配置和初始化子程序, 负责检测所要驱动的硬件设备是否存在和能否正常工作 如果设备正常则对这个设备及其相关的设备驱动程序需要的软件状态进行初始化 这部分驱动程序仅在初始化时被调用一次 (2) 服务于 I/O 请求的子程序, 又称为驱动程序的上半部 调用这部分程序是由于系统调用的结果 这部分程序在执行时, 系统仍认为是与进行调用的进程属于同一个进程, 只是由用户态变成了核心态, 具有进行此系统调用的用户程序的运行环境, 因而可以在其中调用 sleep() 等与进程运行环境有关的函数 (3) 中断服务程序, 又称为驱动程序的下半部 在 Linux 系统中并不是直接从中断向量表调用设备驱动程序的中断服务子程序, 而是由 Linux 系统来接收硬件中断, 再由系统调用中断服务子程序 中断可以在任何一个进程运行时产生, 因而在中断服务程序被调用时, 不能依赖于任何进程的状态, 也就不能调用任何与进程运行环境有关的函数 因为设备驱动程序一般支持同一类型的若干设备, 所以一般在系统调用中断服务子程序时, 都带有一个或多个参数, 以唯一标志请求服务的设备 在系统内部,I/O 设备的存 / 取通过一组固定的入口点来进行, 这组入口点是由每个设备的设备驱动程序提供的 具体到 Linux 系统, 设备驱动程序所提供的这组入口点由一个文件操作结构来向系统进行说明 file_operations 结构定义于 linux/fs.h 文件中, 随着内核的不断升级,file_operations 结构也越来越大, 不同版本的内核会稍有不同 struct file_operations struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); 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); ; file_operations 结构中的成员全部是函数指针, 所以实质上就是函数跳转表 每个进程对设备的操作, 都会根据 major minor 设备号, 转换成对 file_operations 结构的访问 常用的操作包括以下几种 : lseek, 移动文件指针的位置, 只能用于可以随机存取的设备 read, 进行读操作, 参数 buf 为存放读取结果的缓冲区,count 为所要读取的数据长度 返回值为负表示读取操作发生错误 ; 否则, 返回实际读取的字节数 对于字符型, 要求读取的字节数和返回的实际读取字节数都必须是 inode-i_blksize 的倍数 write, 进行写操作, 与 read 类似 select, 进行选择操作 如果驱动程序没有提供 select 入口,select 操作将会认为已经准备好进行任何的 I/O 操作 ioctl, 进行读 写以外的其他操作, 参数 cmd 为自定义的命令 mmap, 用于把设备的内容映射到地址空间, 一般只有块设备驱动程序使用 open, 打开设备进行 I/O 操作 返回 0 表示成功, 返回负数表示失败 如果驱动程序没有提供 open 入口, 则只要 /dev/device 文件存在就认为打开成功 release, 即 close 操作 在用户自己的驱动程序中, 首先要根据驱动程序的功能, 完成 file_operations 结构中函数实现 不需要的函数接口可以直接在 file_operations 结构中初始化为 NULL file_operations 变量会在驱动程序初始化时, 注册到系统内部 当操作系统对设备进行操作时, 会调用驱动程序注册的 file_operations 结构中的函数指针

3 Linux 对中断的处理 在 Linux 系统中, 对中断的处理是属于系统核心部分, 因而如果设备与系统之间以中断方式进行数据交换, 就必须把该设备的驱动程序作为系统核心的一部分 设备驱动程序通过调用 request_irq 函数来申请中断, 通过 free_irq 来释放中断, 它们被定义为 : #include <linux/sched.h> int request_irq(unsigned int irq, void (*handler)(int irq, void dev_id, struct pt_regs *regs), unsigned long flags, const char *device, void *dev_id); void free_irq(unsigned int irq, void *dev_id); 参数 irq 表示所要申请的硬件中断号 ;handler 为向系统登记的中断处理子程序, 中断产生时由系统来调用, 调用时所带参数 irq 为中断号 ;dev_id 为申请时告诉系统的设备标识 ;regs 为中断产生时的寄存器内容 ;device 为设备名, 将会出现在 /proc/interrupts 文件里 ;flag 是申请时的选项, 它决定中断处理程序的一些特性, 其中最重要的是中断处理程序是快速处理程序还是慢速处理程序 快速处理程序运行时, 所有中断都被屏蔽, 而慢速处理程序运行时, 除了正在运行的中断外, 其他中断都没有被屏蔽 在 Linux 系统中, 中断可以被不同的中断处理程序共享 作为系统核心的一部分, 设备驱动程序在申请和释放内存时不是调用 malloc 和 free, 而是 kmalloc 和 kfree, 它们被定义为 : #include <linux/kernel.h> void *kmalloc(unsigned int len, int priority); void kfree(void *obj); 参数 len 为希望申请的字节数 ;obj 为要释放的内存指针 ;priority 为分配内存操作的优先级, 即在没有空闲内存时如何操作, 一般用 GFP_KERNEL 与中断和内存不同, 使用一个没有申请的 I/O 端口不会使系统产生异常, 也就不会导致诸如 segmentation fault 一类的错误发生 任何进程都可以访问任何一个 I/O 端口, 此时系统无法保证对 I/O 端口的操作不会发生冲突, 甚至因此而使系统崩溃, 因此, 在使用 I/O 端口前, 也应该检查此 I/O 端口是否已经有别的程序在使用 若没有, 再把此端口标识为正在使用, 在使用完以后释放它 在设备驱动程序中, 可以调用 printk 来打印一些调试信息, 用法与 printf 类似 Printf 打印的信息不仅出现在屏幕上, 同时还记录在文件 syslog 里

4 设备驱动的初始化 设备驱动程序所提供的入口点, 在设备驱动程序初始化时向系统进行登记, 以便系统在适当的时候调用 Linux 系统里, 通过调用 register_chrdev 向系统注册字符型设备驱动程序 register_chrdev 定义为 : #include <linux/fs.h> #include <linux/errno.h> int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); 其中,major 是为设备驱动程序向系统申请的主设备号, 如果为 0, 则系统为此驱动程序动态分配一个主设备号 Name 是设备名 Fops 即上述对各个调用的入口点说明 此函数返回 0 时表示成功 返回 -EINVAL 表示申请的主设备号非法, 一般来说是主设备号大于系统所允许的最大设备号 返回 -EBUSY 表示所申请的主设备号正在被其他设备程序使用 如果动态分配主设备号成功, 此函数将返回所分配的主设备号 如果 register_chrdev 操作成功, 设备名就会出现在 /proc/dvices 文件中 Linux 为每个设备在 /dev 目录中建立一个文件, 若用 ls l 命令列出函数返回值, 则小于 0 表示注册失败 ; 返回 0 或者大于 0 的值表示注册成功 Linux kernel 2.0 支持 128 个主设备号 Linux kernel 2.2 和 2.4 支持 256 个主设备号 (0 和 255 保留 ) 注册以后,Linux 把设备名和主 / 次设备号联系起来 当有对此设备名的访问时,Linux 通过请求访问的设备名得到主 / 次设备号, 然后把此访问分发到对应的设备驱动, 设备驱动再根据次设备号调用不同的函数 当设备驱动模块从 Linux 内核中卸载, 对应的主设备号必须被释放 在模块卸载调用 cleanup_module() 函数时, 应该调用下面的函数卸载设备驱动 : int unregister_chrdev(unsigned int major, const char *name); 此函数的参数为主设备号 major 和设备名 name Linux 内核把 name 和 major 在内核注册的名称对比, 如果不相等, 卸载失败, 并返回 -EINVAL; 如果 major 大于最大的设备号, 也返回 -EINVAL 初始化部分一般还负责给设备驱动程序申请系统资源, 包括内存 中断 时钟 I/O 端口等, 这些资源也可以在 open 子程序或者其他地方申请 这些资源不用时, 应该释放, 以利于资源的共享 设备驱动的初始化函数只要完成的功能是 : (1) 对驱动程序管理的硬件进行必要的初始化 对硬件寄存器进行设置 比如设置中断掩码, 设置串口的工作方式 并口的数据方向等 (2) 初始化设备驱动相关的参数

一般说来, 每个设备都要定义一个设备变量, 用以保存设备相关的参数 在这里可以对设置变量中的项进行初始化 (3) 在内核注册设备 Linux 内核通过设备的主设备号和从设备号来访问设备驱动, 每个驱动程序都有唯一的主设备号 设备号可以自动获取, 内核会分配一个独一无二的主设备号, 但这样每次获得的主设备号可能不一样, 设备文件必须重新建立, 所有最好手工给设备分配一个主设备号 可以查看 Linux 文件系统中 /proc 下的 devices 文件, 该文件记录内核中已经使用的主设备号和相应的设备名, 选择一个没有被使用的主设备号, 调用下面的函数来注册设备 : int register_chrdev(unsigned int, const char *, struct filr_operations *) 其中三个参数分别表示主设备号 设备名称和上面定义的 filr_operation 结构地址 该函数是在 /linux/include/linux/fs.h 中定义的 (4) 注册中断 如果设备需要 IRQ 支持, 则要注册中断 注册中断使用函数 : in request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *device, void *dev_id); (5) 其他初始化工作 比如给设备分配 I/O 申请 DMA 通道等 若驱动程序是内核的一部分, 则要按如下方式 : int init chr_driver_init(void); 声明, 注意不能缺少 init 在系统启动时会由内核调用 chr_driver_init, 完成驱动程序的初始化 当驱动程序是以模块的形式编写时, 则要按照如下方式 : int init_module(void) 注 : 当运行 insmod 命令插入模块时, 会调用 init_module 函数完成初始化工作