嵌入式LINUX四按键驱动
嵌入式Linux驱动开发教程PDF

嵌入式Linux驱动开发教程PDF嵌入式Linux驱动开发教程是一本非常重要和实用的教材,它主要介绍了如何在Linux操作系统上开发嵌入式硬件设备的驱动程序。
嵌入式系统是指将计算机系统集成到其他设备或系统中的特定应用领域中。
嵌入式设备的驱动程序是连接操作系统和硬件设备的关键接口,所以对于嵌入式Linux驱动开发的学习和理解非常重要。
嵌入式Linux驱动开发教程通常包括以下几个主要的内容:1. Linux驱动程序的基础知识:介绍了Linux设备模型、Linux内核模块、字符设备驱动、块设备驱动等基本概念和原理。
2. Linux驱动编程的基本步骤:讲解了如何编译和加载Linux内核模块,以及编写和注册设备驱动程序所需的基本代码。
3. 设备驱动的数据传输和操作:阐述了如何通过驱动程序与硬件设备进行数据的传输和操作,包括读写寄存器、中断处理以及与其他设备的通信等。
4. 设备驱动的调试和测试:介绍了常用的驱动调试和测试技术,包括使用调试器进行驱动程序的调试、使用模拟器进行驱动程序的测试、使用硬件调试工具进行硬件和驱动的联合调试等。
通常,嵌入式Linux驱动开发教程的PDF版本会提供示例代码、实验步骤和详细的说明,以帮助读者更好地理解和掌握嵌入式Linux驱动开发的核心技术和要点。
读者可以通过跟随教程中的示例代码进行实际操作和实验,深入了解和体验嵌入式Linux驱动开发的过程和方法。
总之,嵌入式Linux驱动开发教程是一本非常重要和实用的教材,对于想要在嵌入式领域从事驱动开发工作的人员来说,具有非常重要的指导作用。
通过学习嵌入式Linux驱动开发教程,读者可以系统地了解和学习嵌入式Linux驱动开发的基本原理和技术,提高自己在嵌入式Linux驱动开发方面的能力和水平。
基于嵌入式linux的串口自定义键盘驱动开发

291基于嵌入式Linux 的串口自定义键盘驱动开发张士林(江苏自动化研究所,江苏连云港222006)摘要:近年,随着科技发展,嵌入式已成为当今时代产业主流。
而嵌入式Linux 的优势使其成为主要的操作系统之一。
本文介绍了Linux 下驱动开发的一般模式,详细分析了基于串口通讯的自定义键盘驱动的开发方法,对嵌入式Linux 驱动开发有一定程度的指导作用。
关键词:嵌入式Linux 系统;串口键盘;驱动开发中图分类号:TP316.2文献标识码:A 文章编号:1673-1131(2019)12-0291-02Abstract:In recent years,with the development of science and technology,embedded technology has become the mainstream of the industry.The advantages of embedded Linux make it one of the main operating systems.This paper introduces the general mode of driver development under Linux,and analyzes in detail the development method of custom keyboard driver based on serial port communication.This paper has certain reference signification for the research and development of embedded system.Key words:embedded Linux system;serial keyboard driver;driver development0引言嵌入式系统(Embedded system ),是一种“完全嵌入受控器件内部,为特定应用而设计的专用计算机系统”。
嵌入式Linux下键盘驱动的设计与实现

4 l I 动 程 序结构 C驱
Li u n x系统 中, 每个打开 的文件都继承 了某类 文件 操作 表 , 户基 于 该 文件 的 函数 操作 表来 对该 文 件操 用 作 , 于设备 文件来 说 , 对 文件对 象继承 了它 的主 设备号 所在驱 动程 序 的文 件操 作表 。设备 的文 件操作 表 是设
寄存器 中保 存的键值 。
3 I 工作 原理 I C
IC Itr I ) I ( e— C 总线是 P i p 公司推出的一种同步 串 n hl s i
行数据传输的标准总线 , 其标准总线传输速率为 1 0 b k/ 0
备 驱动 的起 点 , 但往往 并不在这个 层次上直接 完成设备 操作 。而是采用 了分层设计 的思 想l 。如果在某个层次 3 】 上对 不 同对象 的某类操 作具有某种共 性 , 么就 可 以像 那 提取 “ 因式”那样将这 类操作抽象 出来 , 公 然后 建立更 低一级 的操作层 次来 区分 不同 的对象 , 这样就形 成 了不 同层 次 的驱 动接 口。
了对 总线及其设备进 行操作 的各种 函数 , 在源代码 中是 I - oeC I cr .文件。I — rc C I po 是在 p o 文件系统中建立 I C rc I C 总线及其设备接人点的文件 , IC— r c C文件构成 。 由 I po . IC d v模块负责在 / e I—e d v文件系统中建立 IC总线及其 I 设备 的接人点 , 它可 以让 HC设备的驱动程序在用户空间
主从系统 。在多主系统 中, 总线本 身提供 了一 种冲突检
测的方法 , 当多个挂载在 总线上 的设备 试 图控 制总线进 行通信时 , C能够有效地 管理多个 挂载在总线上 的设 I I 备。本系统 中 ,I I C总线采用主从结构 , 即总线上只有一
基于rk3568的linux驱动开发——gpio知识点 -回复

基于rk3568的linux驱动开发——gpio知识点-回复基于rk3568的Linux驱动开发——GPIO知识点GPIO(General Purpose Input/Output)是通用输入输出的意思,是嵌入式系统中的常用功能。
在rk3568芯片上,GPIO用于实现与外部设备的通信和控制,比如控制LED灯、键盘、电机等。
本文将介绍rk3568芯片上的GPIO控制器、GPIO驱动的开发以及GPIO 在Linux系统中的应用。
一、GPIO控制器在rk3568芯片中,GPIO控制器是用来控制GPIO端口的硬件模块。
每个GPIO控制器可以管理多个GPIO端口,每个GPIO端口可以被配置为输入或输出。
GPIO控制器通常包含寄存器用于配置和控制GPIO端口的功能,比如方向、电平等。
二、GPIO驱动的开发GPIO驱动是用于控制和管理GPIO功能的软件模块。
在Linux内核中,GPIO驱动通过sysfs接口暴露给用户空间,以便用户可以通过文件系统访问和控制GPIO端口。
以下是GPIO驱动的开发过程:1. 确定GPIO控制器和GPIO端口:首先需要确定要使用的GPIO控制器和GPIO端口。
在rk3568芯片手册中可以找到相应的信息。
2. 创建GPIO设备:在Linux内核中,GPIO驱动是通过GPIO子系统来管理的。
首先需要在设备树中添加GPIO设备描述,并分配一个唯一的GPIO号码。
3. 注册GPIO设备:在驱动的初始化函数中,需要调用相应的函数注册GPIO设备,以便系统能够识别和管理该设备。
4. 设置GPIO模式和方向:通过调用GPIO控制器的寄存器,可以设置GPIO端口的模式和方向。
例如,可以将GPIO端口配置为输入模式或输出模式。
5. 读取和写入GPIO值:读取GPIO值可以通过读取GPIO控制器的寄存器来实现,写入GPIO值可以通过写入GPIO控制器的寄存器来实现。
例如,可以将GPIO端口的电平设置为高或低。
北邮键盘驱动实验报告

一、实验目的1. 理解键盘驱动程序的基本原理和设计流程。
2. 掌握键盘扫描矩阵的原理和实现方法。
3. 学习使用C语言进行键盘扫描驱动程序的开发。
4. 提高嵌入式系统开发能力和实际动手能力。
二、实验环境1. 开发平台:北邮嵌入式实验室提供的STM32开发板。
2. 编译工具:Keil uVision 5。
3. 实验软件:嵌入式Linux操作系统。
三、实验原理键盘扫描矩阵是一种常用的键盘输入方式,它通过行和列的交叉来检测按键的状态。
当按键被按下时,行和列的交叉点会形成一个特定的逻辑地址,该地址对应于键盘上的一个按键。
在嵌入式系统中,键盘驱动程序负责扫描键盘矩阵,识别按键状态,并将按键信息传递给上层应用程序。
本实验中,我们将使用C语言开发键盘驱动程序,实现以下功能:1. 初始化键盘硬件资源。
2. 扫描键盘矩阵,识别按键状态。
3. 将按键信息转换为ASCII码或其他编码格式。
4. 通过中断或轮询方式将按键信息传递给上层应用程序。
四、实验步骤1. 硬件连接将STM32开发板与键盘模块连接,确保键盘模块的行和列引脚正确连接到开发板的GPIO引脚。
2. 编写键盘驱动程序(1)初始化键盘硬件资源在驱动程序中,首先需要初始化键盘硬件资源,包括设置GPIO引脚的模式、上拉/下拉电阻等。
```cvoid keyboard_init(void) {// 设置GPIO引脚模式为输出GPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_GPIOA_CLK_ENABLE();GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);// 设置GPIO引脚模式为输入__HAL_RCC_GPIOB_CLK_ENABLE();GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;GPIO_InitStruct.Mode = GPIO_MODE_INPUT;GPIO_InitStruct.Pull = GPIO_PULLUP;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);}```(2)扫描键盘矩阵在驱动程序中,编写一个函数用于扫描键盘矩阵,识别按键状态。
嵌入式linux按键驱动程序的设计

数 结 有 个, 一 据 构 三 第 个fileo peration 结
构, 这里终端设备为他提供的值是tty_fops 第一个tty_driver , 终端设备为他提供的值
add_timer(&scant imer),/ / 这里启 动时
间队例 。
一层是键盘扫描码 ,这是根据键盘的不同而 是console ,另一个是 t t y l disc 数据结构。 不同的,第二层是系统扫描码,这是对键盘 比较统一的编码,第三层是 ASI C C 码, 从 控制台为他提供的值是ttyl disc_NT TY, 在一个进程需要从键盘上读取值时 , 系统扫描码到 ASI C C 码的转换,我们可以 照抄标准PS / 2 键盘的码表转换函数,从第 li n u x 首先通过控制台的读操作函数,然后 调用tty- disc的read_chan 函数从链路规则中 一层,到第二层的转换 ,我们需要自己提 J 读数据 ,如果终端 设备的缓冲区中有数据 , 供。这里由于我们只设计了8 个按键,因此 可以简单的用一个swich case 语句完成,不 则将数据回显出来 ,如果缓冲区中没有数 需要复杂的转换。值的注意的是这几函数都 据 ,则进程睡眠等待中断发生。
初始化键盘。这里要参考硬件手册,如初始 化LH7A404 的GPIOF 的第一个引脚, 通过 相关的寄存器配置把他设置为中断上升沿触 发方式,然后通过l i n u x 内核提供的函数
/ / wait- for- release () 是定义 好的每
10 秒要执行的函数,就是在这个函数中检测 GP IOF I 引脚是否断开。
是scan-code 的回 数, 须 态 译 调函 必 静 编 进
内核 。
的驱动程序, 以让我们的系统更加的简捷高 效。本文在研究了与终端相关的驱动程序 后, 给出了 在lh 7a404 下设计按键驱动程序
嵌入式linux驱动开发流程

三、设备的中断和轮询处理
对于不支持中断的设备,读写时需要轮询设备状态,以及是否需要继续进行数据传输。例如,打印机。如果设备支持中断,则可按照中断方式进行。
struct file_operations Key7279_fops =
{
.open = Key7279_Open,
.ioctl = Key7279_Ioctl,
.release = Key7279_Close,
.read = Key7279_Read,
};
1、 设备的打开和释放
模块在使用中断前要先请求一个中断通道(或者 IRQ中断请求),并在使用后释放它。通过request_irq()函数来注册中断,free_irq()函数来释放。
四、驱动程序的测试
对驱动程序的调试可以通过打印的方式来进行,就是通过在驱动程序中添加printk()打印函数,来跟踪驱动程序的执行过程,以此来判断问题。
◇ 设备的打开和释放。
ห้องสมุดไป่ตู้◇ 设备的读写操作。
◇ 设备的控制操作。
◇ 设备的中断和轮询处理。
Linux主要将设备分为三类:字符设备、块设备和网络设备。字符设备是指发送和接收数据以字符的形式进行,没有缓冲区的设备;块设备是指发送和接收数据以整个数据缓冲区的形式进行的设备;网络设备是指网络设备访问的BSD socket 接口。下面以字符设备为例,写出其驱动编写框架:
二、 构造file_operations结构中要用到的各个成员函数
Linux操作系统将所有的设备都看成文件,以操作文件的方式访问设备。应用程序不能直接操作硬件,使用统一的接口函数调用硬件驱动程序,这组接口被成为系统调用。每个系统调用中都有一个与之对应的函数(open、release、read、write、ioctl等),在字符驱动程序中,这些函数集合在一个file_operations类型的数据结构中。以一个键盘驱动程序为例:
嵌入式Linux的设备驱动研究与开发

l
I
设 备接 口
l
] [
硬 件
{ u s n d s ot otn tt ; n i e h r B t Sau g o s
un in ar sg ed ch Bot n m p 0; to t =
图 l 设备驱动流程
单位进行读写 , 能够进行随机访 问。网络设备在 Lnx i 里有专 u
门的处理 , 没有被映射 到文件系统 的设备节点 , 它的访 问 它 对 采用 sce 机制 。字符设备与块设备的主要区别是 : okt 在对字符
设备发 出读 / 写请求 时 , 实际的硬件 I / 0一般紧接着发 生 ; 块设
在 Ln x中, iu 几乎所有的 内容都是文件 , 对设备驱动的访 问 也是以文件操作的方式实现。无论是字符设备还 是块设备 , 用户 对设备的操作都是通过虚拟文件系统 ( F ) v s 转化为设备驱动与 硬件操作例程的交互( 图 1。 见 ) 即使是访 问网络设备的 sc e 接 ok t 口, 也是通过 V S实现的。 iu F Ln x通过 V S为用户提供了—个 统 F
备是利用一块系统 内存作缓冲区来进行实际的 I / O操作 。
应 用层
驱动程序用来控制 目 标板 上的一组 L D灯。e_ p 结构体定 E l fs do
义了该设备需要的操作接 口。它的成 员全部是函数指针 , 以 所 实质上就是函数跳转表 。
sr c l op a i s ed fp ={ tu t fe i ert on l os
一
it : n i
B t n tts ( Y_ & 0f : ot Sau =KE _ o CS xf) r获取 当前 8个按键 的状态 ‘ , fr o( i=0 :i<8 +) :+ j { i( otn tts> i 1 = 0 f ( t S au > )& )= ) (B o
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
对一个具有四个按键的按键驱动的分析源代码:/*Headers-------------------------------------------------*/#include <linux/kernel.h>#include <linux/init.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/config.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/interrupt.h>#include <linux/delay.h>#include <linux/string.h>#include <linux/delay.h>#include <asm/arch/regs-gpio.h>#include <asm/arch/regs-irq.h>#include <asm/io.h>#include <asm/uaccess.h>#include <asm/semaphore.h>#ifdef CONFIG_DEVFS_FS#include <linux/devfs_fs_kernel.h>#endif/*V ars----------------------------------------------------*/#define DEVICE_NAME "buttons"#define EXTINT_OFF (IRQ_EINT4 - 4)unsigned int buttons_major=0;unsigned int buttons_minor=0;unsigned int type = IRQT_FALLING;struct button_info {unsigned int irq_no;unsigned int gpio_port;unsigned int IN;int button_no;};struct button_info realarm_button_info[4] = {{ IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG11_INP, 1 }, { IRQ_EINT8, S3C2410_GPG0, S3C2410_GPG0_INP, 2 },{ IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG3_INP, 3 }, { IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_INP, 4 }, };struct realarm_button_dev{struct button_info buttoninfo_tab[4];int extint_num[4];struct semaphore sem;wait_queue_head_t wq;struct cdev buttons_dev;};struct realarm_button_dev *realarm_button_device;void s3c_irq_ack(unsigned int irqno){unsigned long bitval = 1UL << (irqno - IRQ_EINT0);__raw_writel(bitval, S3C2410_SRCPND);__raw_writel(bitval, S3C2410_INTPND);return;}void s3c_irqext_ack(unsigned int irqno){unsigned long req;unsigned long bit;bit = 1UL << (irqno - EXTINT_OFF);__raw_writel(bit, S3C2410_EINTPEND);req = __raw_readl(S3C2410_EINTPEND);if (irqno <= IRQ_EINT7 ){if ((req & 0xf0) == 0)s3c_irq_ack(IRQ_EINT4t7);}else{if ((req >> 8) == 0)s3c_irq_ack(IRQ_EINT8t23);return;}int realarm_interrupt_init(unsigned int irq, unsigned int type) {unsigned long gpcon_reg;unsigned long gpcon_offset;unsigned long extint_reg;unsigned long extint_offset;unsigned long newvalue = 0;unsigned long value;if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3)){gpcon_reg = S3C2410_GPFCON;extint_reg = S3C2410_EXTINT0;gpcon_offset = (irq - IRQ_EINT0) * 2;extint_offset = (irq - IRQ_EINT0) * 4;}else if ((irq >= IRQ_EINT4) && (irq <= IRQ_EINT7)){gpcon_reg = S3C2410_GPFCON;extint_reg = S3C2410_EXTINT0;gpcon_offset = (irq - EXTINT_OFF) * 2;extint_offset = (irq - EXTINT_OFF) * 4;}else if ((irq >= IRQ_EINT8) && (irq <= IRQ_EINT15)) {gpcon_reg = S3C2410_GPGCON;extint_reg = S3C2410_EXTINT1;gpcon_offset = (irq - IRQ_EINT8) * 2;extint_offset = (irq - IRQ_EINT8) * 4;}else if ((irq >= IRQ_EINT16) && (irq <= IRQ_EINT23)) {gpcon_reg = S3C2410_GPGCON;extint_reg = S3C2410_EXTINT2;gpcon_offset = (irq - IRQ_EINT8) * 2;extint_offset = (irq - IRQ_EINT16) * 4;} else{return -1;/* Set the GPIO to external interrupt mode */value = __raw_readl(gpcon_reg);value = (value & ~(3 << gpcon_offset)) | (0x02 << gpcon_offset);__raw_writel(value, gpcon_reg);/* Set the external interrupt to pointed trigger type */switch (type){case IRQT_NOEDGE:printk(KERN_WARNING "No edge setting!\n");break;case IRQT_RISING:newvalue = S3C2410_EXTINT_RISEEDGE;break;case IRQT_FALLING:newvalue = S3C2410_EXTINT_FALLEDGE;break;case IRQT_BOTHEDGE:newvalue = S3C2410_EXTINT_BOTHEDGE;break;case IRQT_LOW:newvalue = S3C2410_EXTINT_LOWLEV;break;case IRQT_HIGH:newvalue = S3C2410_EXTINT_HILEV;break;default:printk(KERN_ERR "No such irq type %d", type);return -1;}value = __raw_readl(extint_reg);value = (value & ~(7 << extint_offset)) | (newvalue << extint_offset); __raw_writel(value, extint_reg);return 0;static irqreturn_t buttons_irq(int irq, void *dev_id, struct pt_regs *req){struct button_info *k;int i;int found = 0;struct realarm_button_dev* dev = (struct realarm_button_dev *) dev_id;int up;for (i = 0; i < sizeof dev->buttoninfo_tab / sizeof dev->buttoninfo_tab[0]; i++) { k = dev->buttoninfo_tab + i;if (k->irq_no == irq) {found = 1;if(irq <= IRQ_EINT3)s3c_irq_ack(irq);elses3c_irqext_ack(irq);disable_irq(irq);mdelay(200);s3c2410_gpio_cfgpin(k->gpio_port, k->IN);up = s3c2410_gpio_getpin(k->gpio_port);if (!up) {dev->extint_num[i]++;wake_up_interruptible(&dev->wq);}realarm_interrupt_init(irq, type);enable_irq(irq);break;}}if (!found) {printk("bad irq %d in button\n", irq);return IRQ_NONE;}return IRQ_HANDLED;}int realarm_request_irq(struct realarm_button_dev* dev){struct button_info *k;int i;int ret;unsigned int irq;for (i = 0; i < sizeof dev->buttoninfo_tab / sizeof dev->buttoninfo_tab[0]; i++) { k = dev->buttoninfo_tab + i;irq = k->irq_no;realarm_interrupt_init(irq, type);ret = request_irq(irq, &buttons_irq, SA_INTERRUPT, DEVICE_NAME, dev);if (ret) {printk(KERN_WARNING "buttons:can't get irq no.\n");return ret;}}return 0;}int buttons_open(struct inode *inode, struct file *filp){struct realarm_button_dev *dev;dev = container_of(inode->i_cdev, struct realarm_button_dev, buttons_dev);filp->private_data = dev;realarm_request_irq(dev);return 0;}int buttons_release(struct inode *inode, struct file *filp){struct realarm_button_dev *dev = (struct realarm_button_dev *)filp->private_data;struct button_info *k;int i;for (i = 0; i < sizeof dev->buttoninfo_tab / sizeof dev->buttoninfo_tab[1]; i++) { k = dev->buttoninfo_tab + i;free_irq(k->irq_no, dev);}return 0;}ssize_t buttons_read(struct file *filp,char __user *buffer,size_t count,loff_t *ppos){struct realarm_button_dev *dev = (struct realarm_button_dev *)filp->private_data;if (down_interruptible(&dev->sem))return -ERESTARTSYS;interruptible_sleep_on(&dev->wq);if(copy_to_user(buffer, (char *)dev->extint_num, sizeof(dev->extint_num))){printk(KERN_ALERT "Copy to user error.\n");return -EFAULT;}up(&dev->sem);return sizeof(dev->extint_num);}struct file_operations buttons_fops = {.owner = THIS_MODULE,.open = buttons_open,.release = buttons_release,.read =buttons_read,};static int __init buttons_init(void){int i;int ret;dev_t dev;printk(KERN_INFO "Initial RealARM Buttons driver!\n");if (buttons_major) {dev = MKDEV(buttons_major, buttons_minor);ret = register_chrdev_region(dev, 1, DEVICE_NAME);} else {ret = alloc_chrdev_region(&dev, buttons_minor, 1, DEVICE_NAME);buttons_major = MAJOR(dev);}if (ret < 0) {printk(KERN_WARNING "Buttons: can't get major %d\n", buttons_major);return ret;}realarm_button_device = kmalloc(sizeof(struct realarm_button_dev), GFP_KERNEL);if (!realarm_button_device) {unregister_chrdev_region(dev, 1);ret = -ENOMEM;return ret;}memset(realarm_button_device, 0, sizeof(struct realarm_button_dev));memcpy(realarm_button_device->buttoninfo_tab, realarm_button_info, sizeof(realarm_button_info));for(i = 0; i < 4; i++){realarm_button_device->extint_num[i] = 0;}init_MUTEX(&realarm_button_device->sem);init_waitqueue_head(&realarm_button_device->wq);cdev_init(&realarm_button_device->buttons_dev, &buttons_fops);realarm_button_device->buttons_dev.owner = THIS_MODULE;realarm_button_device->buttons_dev.ops = &buttons_fops;ret = cdev_add(&realarm_button_device->buttons_dev, dev, 1);if (ret) {unregister_chrdev_region(dev, 1);printk(KERN_NOTICE "Error %d adding buttons device\n",ret);return ret;}#ifdef CONFIG_DEVFS_FSdevfs_mk_cdev(dev, S_IFCHR | S_IRUSR | S_IWUSR, DEVICE_NAME);printk(KERN_INFO"/dev/%s has been added to your system.\n",DEVICE_NAME);#elseprintk(DEVICE_NAME "Initialized\n");printk(KERN_INFO "You must create the dev file manually.\n");printk(KERN_INFO "Todo: mknod c /dev/%s %d 0\n",DEVICE_NAME,buttons_major);#endifreturn 0;}static void __exit buttons_cleanup(void){dev_t dev = MKDEV(buttons_major, buttons_minor);cdev_del(&realarm_button_device->buttons_dev);kfree(realarm_button_device);unregister_chrdev_region(dev, 1);#ifdef CONFIG_DEVFS_FSdevfs_remove(DEVICE_NAME);#endifprintk(KERN_INFO "unregistered the %s\n",DEVICE_NAME); }module_init(buttons_init);module_exit(buttons_cleanup);MODULE_AUTHOR("LiuRui");MODULE_LICENSE("GPL");MODULE_DESCRIPTION("Key driver for RealARM");分析:四个按键信息说明struct button_info realarm_button_info[4] = {{ IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG11_INP, 1 }, { IRQ_EINT8, S3C2410_GPG0, S3C2410_GPG0_INP, 2 }, { IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG3_INP, 3 }, { IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_INP, 4 }, };自定义按键结构把字符设备嵌入其中struct realarm_button_dev{struct button_info buttoninfo_tab[4];//记录每个按键信息int extint_num[4]; //用于记录按键的次数struct semaphore sem;//消息变量wait_queue_head_t wq;//等待队列struct cdev buttons_dev;//嵌入字符设备};程序流程分析:module_init(buttons_init);module_exit(buttons_cleanup);对file_operations 中各个函数的实现分析:struct file_operations buttons_fops = {.owner = THIS_MODULE,.open = buttons_open,.release = buttons_release,.read = buttons_read,};buttons_open先来说一下container_of函数318#define container_of(ptr, type, member) ({ \319const typeof( ((type *)0)->member ) *__mptr = (ptr); \320(type *)( (char *)__mptr - offsetof(type,member) );})指针ptr指向结构体type中的成员member;通过指针ptr,返回结构体type的起始地址。