嵌入式LINUX四按键驱动
- 格式:pdf
- 大小:109.60 KB
- 文档页数:15
嵌入式Linux驱动开发教程PDF嵌入式Linux驱动开发教程是一本非常重要和实用的教材,它主要介绍了如何在Linux操作系统上开发嵌入式硬件设备的驱动程序。
嵌入式系统是指将计算机系统集成到其他设备或系统中的特定应用领域中。
嵌入式设备的驱动程序是连接操作系统和硬件设备的关键接口,所以对于嵌入式Linux驱动开发的学习和理解非常重要。
嵌入式Linux驱动开发教程通常包括以下几个主要的内容:1. Linux驱动程序的基础知识:介绍了Linux设备模型、Linux内核模块、字符设备驱动、块设备驱动等基本概念和原理。
2. Linux驱动编程的基本步骤:讲解了如何编译和加载Linux内核模块,以及编写和注册设备驱动程序所需的基本代码。
3. 设备驱动的数据传输和操作:阐述了如何通过驱动程序与硬件设备进行数据的传输和操作,包括读写寄存器、中断处理以及与其他设备的通信等。
4. 设备驱动的调试和测试:介绍了常用的驱动调试和测试技术,包括使用调试器进行驱动程序的调试、使用模拟器进行驱动程序的测试、使用硬件调试工具进行硬件和驱动的联合调试等。
通常,嵌入式Linux驱动开发教程的PDF版本会提供示例代码、实验步骤和详细的说明,以帮助读者更好地理解和掌握嵌入式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 ),是一种“完全嵌入受控器件内部,为特定应用而设计的专用计算机系统”。
基于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)扫描键盘矩阵在驱动程序中,编写一个函数用于扫描键盘矩阵,识别按键状态。
基于S3C2440嵌入式Linux的伺服电机控制摘要:介绍一种基于S3C2440微处理器的伺服电机控制方案。
ARM微处理器几乎已经深入到工业控制、无线通讯、网络应用、消费类电子产品、成像和安全产品各个领域。
本文实现基于S3C2440的Linux的伺服电机的按键控制,控制伺服电机转动方向及速度,并通过LED指示灯、蜂鸣器等表征伺服电机的工作状态。
关键词:ARM S3C2440 Linux 伺服电机Linux操作系统将设备都看成文件,以操作文件的方式访问设备,应用程序不能直接操作硬件,而使用统一的接口函数调用驱动程序,驱动程序位于内核中,一方面完成对底层硬件的控制;另一方面将控制底层硬件的函数以标准文件访问的方式提供给上层应用程序。
因此,对伺服电机的控制需要编写不同模块的驱动程序,再调用执行。
运行程序时将程序在交叉编译环境下编译,将可执行程序移植进ARM运行。
1 总体设计方案按键控制直流伺服电机包括按键检测、电机驱动、状态只是三个主要部分,因此,整体控制也分为三个模块:一是伺服电机的驱动与控制;二是指示灯蜂鸣器等的状态控制;三是主函数检测按键执行相应操作。
1.1 伺服电机的驱动与控制直流伺服电机速度由PWM的占空比来决定,占空比越大电机转速越大,设置PWM占空比可以改变电机转动速度。
编写PWM驱动,设置PWM频率、占空比,在主函数中调用ioctl函数改变占空比。
S3c2440A有5个16位的定时器,这里使用了定时器1,并设置PWM频率为10 kHz。
1.2 LED指示灯和蜂鸣器的控制指示灯和蜂鸣器的控制通过引脚输出高低电平信号进行控制,并且直流电机的方向也是用同样的方式控制。
编写引脚信号输出驱动,在主函数中调用ioctl函数改变各引脚状态。
1.3 主函数主函数主要完成的工作为检测判断按下的按键,并调用相应的ioctl函数进行操作。
2 详细设计2.1 PWM驱动本驱动通过读写寄存器设置PWM工作方式,设置占空比,在主函数通过调用函数传入不同参数设置预期的占空比。
Matrix键盘驱动是一种适用于多行多列多按键情况的键盘驱动,与简单的GPIO按键驱动相比,它的结构更复杂。
在Linux内核中,Matrix键盘驱动的实现主要位于`drivers/input/keyboard/matrixkeypad.c`文件中。
以下是对Matrix键盘驱动的解析:
1. 功能实现:
Matrix键盘驱动的具体开发流程包括添加编译选项、添加设备树节点、内核源码中按键映射写法等。
2. 硬件电路:
Matrix键盘驱动的硬件设计需要考虑上拉模式和下拉模式。
上拉模式下,输入GPIO 硬件设计为上拉;下拉模式下,输入GPIO硬件设计为下拉。
3. 调试步骤:
调试Matrix键盘驱动时,可以使用hexdump命令、类hexdump工具源码等调试工具。
调试流程包括大致调试流程和具体步骤描述。
4. 源码中添加调试log小技巧:
在源码中添加调试log可以帮助开发者更好地理解驱动程序的运行过程和问题所在。
5. 内核源码逻辑分析:
Matrix键盘驱动的整体功能逻辑包括键盘Matrixkeypad_scan()函数扫描原理、键盘Matrixkeypadparsedt()函数源码解析、键盘Matrixkeypadinitgpio()函数源码解析等。
gpio-keys 使用方法gpio-keys是Linux内核中的一个子系统,用于处理嵌入式系统中的GPIO按键输入。
它允许开发人员将GPIO引脚配置为输入,并在按键按下或释放时生成相应的键事件。
下面我将从配置、驱动和使用方面来介绍gpio-keys的使用方法。
首先,要使用gpio-keys,你需要确保你的Linux内核已经启用了相应的配置选项。
在内核配置中,你需要启用CONFIG_INPUT_GPIO_KEYS选项。
这个选项通常位于"Device Drivers" -> "Input device support" -> "Keyboards"下。
启用这个选项后,重新编译内核并将其烧录到你的嵌入式设备上。
接下来,你需要在设备树中配置GPIO按键的引脚信息。
你需要指定GPIO引脚的编号、按键的名称以及按键事件的类型(按下或释放)。
这些信息将被用于注册gpio-keys设备,并在按键事件发生时生成输入事件。
在Linux系统启动时,内核会根据设备树中的配置信息注册gpio-keys设备。
一旦设备注册成功,你就可以在用户空间中使用输入事件接口来监控GPIO按键的状态变化了。
你可以使用evtest工具来测试gpio-keys设备是否正常工作,并查看按键事件的生成情况。
除了设备树中的配置外,你还可以在驱动程序中动态注册gpio-keys设备。
这在某些情况下可能更为灵活,但需要在驱动程序中编写相应的注册和初始化代码。
总的来说,要使用gpio-keys,你需要在内核中启用相应的配置选项,配置设备树以注册gpio-keys设备,并在用户空间中使用输入事件接口来监控GPIO按键的状态变化。
希望这些信息能够帮助你更好地理解和使用gpio-keys子系统。
原子嵌入式linux驱动开发详解原子嵌入式Linux驱动开发详解:Linux操作系统一直都是工业控制、物联网、安防等领域中嵌入式设备的首选操作系统。
Linux系统的优良特性使其成为用户和开发者的首选,而Linux内核驱动则是面向嵌入式应用领域核心技术之一。
它是嵌入式设备在硬件及软件之间接口的重要组成部分。
本文将详细介绍使用原子嵌入式Linux驱动进行嵌入式设备驱动的开发,并且介绍使用原子嵌入式Linux驱动实现并行的多线程驱动。
一、嵌入式设备驱动的基本原理:所谓嵌入式设备驱动,就是处理器与外部设备之间进行数据传递的程序,将设备中的信息读取到处理器中,或将处理器中的信息发送至设备中。
嵌入式设备驱动的核心逻辑是控制输入输出模块,以完成外部信息的读取和发送任务。
在Linux系统下,设备驱动一般以内核模块存在,片上驱动是一个相对独立的模块,不妨做一番详细的介绍。
二、原子嵌入式Linux驱动的使用:原子嵌入式Linux驱动根据功能的不同划分成了两类,即原子操作和读写自旋锁。
这两类驱动的使用方法不同,且有自己的特殊应用场景。
1、原子操作:在多线程的情况下,通过锁来保证同一时间只能有一个线程操作共享资源是一种常见的方法。
原子操作则是一种替代锁的方式,在多线程操作共享资源的情况下采用原子操作方式相对于锁来说会更加高效。
原子操作是一种特殊的指令操作,执行完原子操作之后,CPU不允许其他线程读写该地址的值,因此可以避免竞争。
下面是一个使用原子操作的例子:radio_chan = atomic_read(&radio->chan);digital_chan =atomic_read(&radio->digital_chan);radio_write_register(radio, 0x0011, 2,&radio_chan);radio_write_register(radio, 0x5111, 2,&digital_chan);在上述代码中,使用了atomic_read来获得变量radio_chan和digital_chan的值,这两个变量是共享资源,这里使用原子操作来避免竞争和冲突。
简单的I/O接口091180083 刘浩通信工程一.实验目的1.学习嵌入式Linux操作系统设备驱动的方法2.通过动态LED控制、扫描键盘应用、步进电机驱动等控制,掌握简单设备的基本控制原理。
二.实验原理介绍1.嵌入式系统的设备驱动程序设备驱动程序负责将应用程序如读、写等操作正确无误地传递给相关的硬件,并使硬件能够做出正确反应。
因此在编写设备驱动程序时,必须了解相应的硬件设备的寄存器、IO接口及内存的配置参数。
实验的第一步就是要好好了解这个实验中要用到的硬件的接口信息。
2.LED和八段数码管显示详细介绍见《嵌入式实验指导书》P50-P51。
在这里需要提及的是,LED及LED 数码管为共阳结构,当给对应的I/O位输出为0时,LED灯亮。
I/O数据线经过一个D锁存器74HC574,在时钟信号作用下对输入信号进行锁存。
LED的片选信号为LED_CS4。
数码管一和二的片选信号是LED_CS2,数码管三和四的片选信号为LED_CS3。
在数码管显示电路中,8位数据的高位D7及D15用作八段数码的公共选通信号,通过控制PNP管来控制数码管的显示,经分析可以知道当D7和D15为低电平时,数码管才被选通。
下面是要得到以上片选信号的地址。
参考实验指导资料,知道片选信号是通过I/O设备译码得到的。
LED_CS1~LED_CS4以及AD_CS,IDE_CS,RTC_CS八个信号是通过三八地址译码器得到的。
八段译码管的片选信号B_CS4的地址为0x1000 0000,三根地址输入线BA22、BA21、BA20=011、100、101时LED_CS2,LED_CS3,LED_CS4为低电平,此时选通相应数码管。
所以可以得到数码管一二的地址为0x1030 0000,数码管三四的地址为0x1040 0000,LED 灯的地址为0x1050 0000。
3.矩阵键盘目标平台提供了3x4的矩阵键盘和四个单按键键盘。
矩阵键盘的行线分别由GPIO 100~GPIO102控制,列线由GPIO 103~105及108控制。
对一个具有四个按键的按键驱动的分析源代码:/*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的起始地址。