linux驱动基础知识讲解
Linuxlinux硬件驱动架构
Linuxlinux硬件驱动架构linux硬件驱动(usb)??模块:模块用来装载到内核中,用来实现设备驱动程序。
??linux对于一个硬件的驱动,采用两种方式读取:??1.直接加载到内核代码中,启动内核时就会驱动此硬件设备??2.以模块方式读取,编程分解成一个.o的文件,当应用程序须要时读取入内核空间运转??so(通常说的硬件驱动其实就是一个硬件驱动模块及.o文件)??设备文件(设备节点):??设备文件(设备节点)指定(主设备号)&&(次设备号)??主设备号:对应着确认的驱动程序。
??(声明设备所使用的驱动程序,设备号相当于硬件驱动程序的一个标识)??次设备号:区分相同属性,相同采用方法,相同边线,相同操作方式??设备号从/proc/drives中获取,so先有驱动程序在内核中,才有设备节点在目录中。
??scsi(并口):通常采用的的usb存储设备,就是演示scsi硬盘而展开设计的。
??linux硬件驱动架构:??.o驱动模块文件--(如果须要采用这个驱动程序,首先必须读取运转它)-->insmod*.o--(驱动程序根据字符设备类型或块设备类型(鼠标属字符设备,硬盘属块设备))向系统登记注册-->登记注册顺利之后系统回到一个主设备号---(根据主设备号建立一个置放在/dev目录下的设备文件)-->(mknod用以建立设备文件须要使用设备号这个参数)----->我们出访硬件时,就可以对设备文件通过open,read,write等命令展开,而驱动就可以发送至适当的read,write操作方式而根据自己模块中的适当函数展开。
??上层调用api.o驱动drive.o??与模块有关的一些东西:??1./lib/modules/2.6.**目录,下面是针对当前内核版本的模块。
??2.查阅模块的倚赖关系与否恰当(depmod设置)??3.加载模块而不需要知道具体的模块位置(modprobe)??4.文件/etc/modules.conf文件,当kernel须要时轻易回去该文件中搜寻别称读取??modprobe用于加载系统已经通过depmod登记过的模块,insmod一般是针对具体的.o进行文件的加载。
嵌入式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 驱动 内核态与用户态的方法
linux驱动内核态与用户态的方法一、引言Linux操作系统以其高效、稳定和开源的特点,广泛应用于各种硬件设备。
驱动程序作为操作系统与硬件设备之间的桥梁,对于系统稳定性和性能至关重要。
在驱动程序的开发过程中,了解内核态与用户态的方法是至关重要的。
本文将详细介绍这两种状态下进行驱动程序开发的方法。
二、内核态开发1.权限与状态:在Linux中,内核态是操作系统内核空间,需要以root权限运行。
驱动程序在内核态下运行,可以对硬件设备进行直接操作。
2.内存管理:在内核态下,驱动程序可以直接访问物理内存,因此需要熟练掌握内存管理技巧,包括内存分配、释放、共享和保护等。
3.设备驱动模型:Linux提供了设备驱动模型,通过它可以方便地编写与硬件设备交互的代码。
了解设备驱动模型的结构和机制,是内核态驱动程序开发的基础。
4.中断与轮询:中断和轮询是驱动程序与硬件设备交互的主要方式。
了解这两种机制的工作原理,能够更好地编写驱动程序。
5.模块加载与卸载:内核态下的驱动程序通常以模块形式加载,了解模块加载与卸载的机制,能够更方便地编写和管理驱动程序。
三、用户态开发1.权限与状态:在Linux中,用户态是用户空间,需要以普通用户身份运行。
驱动程序在用户态下运行,只能通过系统调用与内核态交互。
2.系统调用:系统调用是用户程序与内核态交互的主要方式。
了解系统调用的机制和接口,能够更好地编写用户态驱动程序。
3.内存管理:用户态下的驱动程序需要通过系统调用访问物理内存,因此需要熟练掌握内存管理的技巧,包括内存分配、释放、共享和保护等。
4.设备驱动模型:虽然用户态下的驱动程序无法直接访问硬件设备,但通过设备驱动模型,可以间接地控制硬件设备。
了解设备驱动模型的结构和机制,对于用户态驱动程序的开发也很有帮助。
四、注意事项1.安全问题:在内核态下开发驱动程序时,需要注意避免安全漏洞,如缓冲区溢出、权限提升等。
2.稳定性问题:驱动程序的稳定性直接影响到整个系统的稳定性。
linux系统基础知识
linux系统基础知识Linux系统基础知识Linux是一种自由和开放源代码的类Unix操作系统,它是由Linus Torvalds在1991年首次发布的。
Linux系统具有高度的可定制性和灵活性,因此在服务器、超级计算机、移动设备等领域得到了广泛的应用。
本文将介绍Linux系统的基础知识,包括Linux的发行版、文件系统、用户和权限、命令行和图形界面等方面。
一、Linux的发行版Linux系统有许多不同的发行版,每个发行版都有自己的特点和用途。
常见的Linux发行版有Ubuntu、Debian、Fedora、CentOS、Red Hat等。
这些发行版都是基于Linux内核开发的,但它们的软件包管理、安装方式、默认桌面环境等方面有所不同。
选择适合自己的Linux发行版可以提高工作效率和使用体验。
二、文件系统Linux系统的文件系统采用树形结构,根目录为/。
在根目录下有许多子目录,如bin、etc、home、usr等。
其中,/bin目录存放系统命令,/etc目录存放系统配置文件,/home目录存放用户的主目录,/usr目录存放系统软件和库文件等。
Linux系统支持多种文件系统,如ext4、NTFS、FAT32等。
文件系统的选择取决于使用场景和需求。
三、用户和权限Linux系统是一个多用户系统,每个用户都有自己的用户名和密码。
用户可以通过命令行或图形界面登录系统,并执行各种操作。
Linux 系统采用权限控制机制,每个文件和目录都有自己的权限。
权限分为读、写、执行三种,分别对应数字4、2、1。
文件和目录的权限可以通过chmod命令进行修改。
Linux系统还有超级用户root,拥有系统的最高权限,可以执行任何操作。
四、命令行Linux系统的命令行界面是其最基本的界面,也是最强大的界面。
通过命令行可以执行各种操作,如创建文件、修改权限、安装软件等。
Linux系统的命令行界面有许多命令,如ls、cd、mkdir、rm、chmod等。
《Linux设备驱动开发详解:基于最新的Linux 4.0内核》19. Linux电源管理系统架构和驱动
以下电子书来源于宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》第19章《Linux电源管理系统架构和驱动》本章导读Linux在消费电子领域的应用已经铺天盖地,而对于消费电子产品而言,省电是一个重要的议题。
本章将介绍Linux设备树(Device Tree)的起源、结构和因为设备树而引起的驱动和BSP 变更。
19.1节阐述了Linux电源管理的总体架构。
19.2~19.8节分别论述了CPUFreq、CPUIdle、CPU热插拔以及底层的基础设施Regulator、OPP以及电源管理的调试工具PowerTop。
19.9节讲解了系统Suspend to RAM的过程以及设备驱动如何提供对Suspend to RAM的支持。
19.10节讲解了设备驱动的Runtime suspend。
本章是相对《Linux设备驱动开发详解(第2版)》全新的一章内容,也是Linux设备驱动工程师必备的知识体系。
第十九章Linux电源管理系统架构和驱动1.Linux电源管理全局架构Linux电源管理非常复杂,牵扯到系统级的待机、频率电压变换、系统空闲时的处理以及每个设备驱动对于系统待机的支持和每个设备的运行时电源管理,可以说和系统中的每个设备驱动都息息相关。
对于消费电子产品来说,电源管理相当重要。
因此,这部分工作往往在开发周期中占据相当大的比重,图19.1呈现了Linux内核电源管理的整体架构。
大体可以归纳为如下几类:1.CPU在运行时根据系统负载进行动态电压和频率变换的CPUFreq2.CPU在系统空闲时根据空闲的情况进行低功耗模式的CPUIdle3.多核系统下CPU的热插拔支持4.系统和设备对于延迟的特别需求而提出申请的PM QoS,它会作用于CPUIdle的具体策略5.设备驱动针对系统Suspend to RAM/Disk的一系列入口函数6.SoC进入suspend状态、SDRAM自刷新的入口7.设备的runtime(运行时)动态电源管理,根据使用情况动态开关设备8.底层的时钟、稳压器、频率/电压表(OPP模块完成)支撑,各驱动子系统都可能用到图19.1 Linux电源管理系统架构2.CPUFreq驱动CPUFreq子系统位于drivers/cpufreq目录,负责进行运行过程中CPU频率和电压的动态调整,即DVFS(Dynamic Voltage Frequency Scaling,动态电压频率调整)。
Linux设备驱动程序原理及框架-内核模块入门篇
Linux设备驱动程序原理及框架-内核模块入门篇内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块内核模块介绍Linux采用的是整体式的内核结构,这种结构采用的是整体式的内核结构,采用的是整体式的内核结构的内核一般不能动态的增加新的功能。
为此,的内核一般不能动态的增加新的功能。
为此,Linux提供了一种全新的机制,叫(可安装) 提供了一种全新的机制,可安装) 提供了一种全新的机制模块” )。
利用这个机制“模块”(module)。
利用这个机制,可以)。
利用这个机制,根据需要,根据需要,在不必对内核重新编译链接的条件将可安装模块动态的插入运行中的内核,下,将可安装模块动态的插入运行中的内核,成为内核的一个有机组成部分;成为内核的一个有机组成部分;或者从内核移走已经安装的模块。
正是这种机制,走已经安装的模块。
正是这种机制,使得内核的内存映像保持最小,的内存映像保持最小,但却具有很大的灵活性和可扩充性。
和可扩充性。
内核模块内核模块介绍可安装模块是可以在系统运行时动态地安装和卸载的内核软件。
严格来说,卸载的内核软件。
严格来说,这种软件的作用并不限于设备驱动,并不限于设备驱动,例如有些文件系统就是以可安装模块的形式实现的。
但是,另一方面,可安装模块的形式实现的。
但是,另一方面,它主要用来实现设备驱动程序或者与设备驱动密切相关的部分(如文件系统等)。
密切相关的部分(如文件系统等)。
课程内容内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块应用层加载模块操作过程内核引导的过程中,会识别出所有已经安装的硬件设备,内核引导的过程中,会识别出所有已经安装的硬件设备,并且创建好该系统中的硬件设备的列表树:文件系统。
且创建好该系统中的硬件设备的列表树:/sys 文件系统。
(udev 服务就是通过读取该文件系统内容来创建必要的设备文件的。
)。
Linux下的硬件驱动——USB设备
Linux下的硬件驱动——USB设备什么是USB设备?USB即Universal Serial Bus,翻译过来就是通用串行总线。
它是一种规范化的、快速的、热插拔的串行输入/输出接口。
USB接口常被用于连接鼠标、键盘、打印机、扫描仪、音频设备、存储设备等外围设备。
Linux下的USB驱动在Linux系统中,每个USB设备都需要一个相应的驱动程序来驱动。
从Linux 2.4开始,内核提供了完整的USB设备支持。
对于每个USB设备,内核都会自动加载对应的驱动程序。
Linux下的USB设备驱动程序主要分为以下几个部分:USB核心驱动程序USB核心驱动程序是操作系统内核中处理USB设备的核心模块,负责与各种类型的USB设备进行通信,包括主机控制器、USB总线、USB设备等。
它与驱动程序和应用程序之间起到了桥梁的作用,为驱动程序提供了USB设备的基础支持。
USB设备驱动程序USB设备驱动程序是与特定USB设备相对应的驱动程序,为USB设备提供具体的读写功能和其他控制功能。
USB核心驱动程序和USB设备驱动程序之间的接口USB核心驱动程序和USB设备驱动程序之间的接口是指USB层和应用程序层之间的接口,负责传递各种USB操作的命令和数据。
如何编译一个USB设备驱动编译一个USB设备驱动程序需要按照以下步骤进行:步骤一:安装必要的软件包首先需要安装编译和调试USB设备驱动所需的软件包,包括编译工具链、内核源代码、内核头文件等。
sudo apt-get install build-essential linux-source linux-headers-`una me -r`步骤二:编写代码现在可以编写USB设备驱动程序的代码,此处不做详细介绍。
步骤三:编译代码在终端窗口中进入USB设备驱动程序所在的目录下,输入以下命令进行编译:make此命令将会编译USB设备驱动程序,并生成一个将驱动程序与内核进行连接的模块文件。
linux usb wifi驱动开发原理
linux usb wifi驱动开发原理Linux USB WiFi驱动开发原理一、引言随着无线网络的普及,WiFi成为了人们生活中不可或缺的一部分。
而在Linux操作系统中,为了支持各种WiFi设备,需要进行对应的驱动开发。
本文将介绍Linux USB WiFi驱动开发的原理和过程。
二、USB WiFi驱动开发的基本原理1. USB接口USB(Universal Serial Bus)是一种通用的串行总线标准,用于连接计算机与外部设备。
USB WiFi设备通过USB接口与计算机通信,传输数据和控制命令。
2. 驱动程序驱动程序是用于操作和控制硬件设备的软件。
USB WiFi驱动程序负责与USB WiFi设备进行通信,实现数据的传输和接收。
驱动程序需要与操作系统紧密结合,通过操作系统提供的API接口与设备进行交互。
三、USB WiFi驱动开发的过程1. 设备识别与初始化USB WiFi设备插入计算机后,操作系统会通过USB子系统进行设备的识别和初始化。
在Linux系统中,USB设备的识别和初始化由USB核心驱动完成。
核心驱动会根据设备的VID(Vendor ID)和PID (Product ID)来匹配对应的驱动程序。
2. 驱动程序注册驱动程序需要在Linux系统中进行注册,以便系统能够正确识别和加载驱动。
注册过程通常包括向系统注册设备类型、设备ID等信息。
3. 设备操作接口的实现驱动程序需要实现设备操作接口,包括设备的打开、关闭、读取数据、写入数据等功能。
这些操作接口是通过USB子系统提供的API 来实现的。
4. 数据传输与控制USB WiFi驱动程序需要实现数据的传输和控制功能。
数据传输主要包括从设备读取数据和向设备写入数据,而控制功能包括设置设备参数、配置网络等操作。
5. 错误处理与调试在USB WiFi驱动开发中,错误处理和调试是非常重要的一部分。
驱动程序需要处理各种异常情况,如设备断开连接、传输错误等。
Linux设备驱动开发详解讲座
华清远见
自旋锁:
• •
自旋锁 VS 信号量
忙等待,无调度开销 进程抢占被禁止 锁定期间不能睡觉
spinlock_t lock; spin_lock_init(&lock);
• • •
spin_lock (&lock) ; // 获取自旋锁,保护临界区 . . ./ / 临界区 spin_unlock (&lock) ; // 解锁
信号量
• • • • • • •
拿不到就切换进程,有调度开销 锁定期间可以睡觉,不用于中断上下文
// 定义信号量 DECLARE_MUTEX(mount_sem); down(&mount_sem);// 获取信号量,保护临界区 . . . critical section // 临界区 . . . up(&mount_sem);// 释放信号量
•
•
•
•
•
•
•
•
signal ()绑定
用户空间
f c n t l( f d, F _ S E T O W N, g e t p i d( ) ) f c n t l( f d , F _GE T F L)
信号处理函数
执行 导致
信号
内核设置 filp>f_owner
设备驱动 fasync()函数
等待队列:进程等待被唤醒的一种机制 阻塞与非阻塞使用模板
阻塞非阻塞
华清远见
• • • • • • • • • • • • • • • • • • •
polling
驱动中 POLL 模板
1 static unsigned int xxx_poll(struct file *filp, poll_table *wait) 2 { 3 unsigned int mask = 0; 4 struct xxx_dev *dev = filp>private_data; /* 获得设备结构体指针 */ 6 ... 8 poll_wait(filp, &dev>wait, wait); 9 10 if (...)// 可读 11 { 12 mask |= POLLIN | POLLRDNORM; /* 标示数据可获得 */ 13 } 15 if (...)// 可写 16 { 17 mask |= POLLOUT | POLLWRNORM; /* 标示数据可写入 */ 18 } 19 20 ... 21 return mask; 22 }
基于rk3568的linux驱动开发——gpio知识点
基于rk3568的linux驱动开发——gpio知识点基于rk3568的Linux驱动开发——GPIO知识点一、引言GPIO(General Purpose Input/Output)通用输入/输出,是现代计算机系统中的一种常用接口,它可以根据需要配置为输入或输出。
通过GPIO 接口,我们可以与各种外设进行通信,如LED灯、按键、传感器等。
在基于Linux系统的嵌入式设备上开发驱动程序时,熟悉GPIO的使用是非常重要的一环。
本文将以RK3568芯片为例,详细介绍GPIO的相关知识点和在Linux驱动开发中的应用。
二、GPIO概述GPIO是系统中的一个基本的硬件资源,它可以通过软件的方式对其进行配置和控制。
在嵌入式设备中,通常将一部分GPIO引脚连接到外部可编程电路,以实现与外部设备的交互。
在Linux中,GPIO是以字符设备的形式存在,对应的设备驱动为"gpiolib"。
三、GPIO的驱动开发流程1. 导入头文件在驱动程序中,首先需要导入与GPIO相关的头文件。
对于基于RK3568芯片的开发,需要导入头文件"gpiolib.h"。
2. 分配GPIO资源在驱动程序中,需要使用到GPIO资源,如GPIO所在的GPIO Bank和GPIO Index等。
在RK3568芯片中,GPIO资源的分配是通过设备树(Device Tree)来进行的。
在设备树文件中,可以定义GPIO Bank和GPIO Index等信息,以及对应的GPIO方向(输入或输出)、电平(高电平或低电平)等属性。
在驱动程序中,可以通过设备树接口(Device Tree API)来获取这些GPIO资源。
3. GPIO的配置与控制在驱动程序中,首先要进行GPIO的初始化与配置。
可以通过函数"gpiod_get()"来打开指定的GPIO,并判断其是否有效。
如果成功打开GPIO,则可以使用函数"gpiod_direction_output()"或"gpiod_direction_input()"来设置GPIO的方向,分别作为输出或输入。
Linux设备驱动之HID驱动---非常全面而且深刻
Linux设备驱动之HID驱动---⾮常全⾯⽽且深刻本⽂系本站原创,欢迎转载!转载请注明出处:/------------------------------------------⼀:前⾔继前⾯分析过UHCI和HUB驱动之后,接下来以HID设备驱动为例来做⼀个具体的USB设备驱动分析的例⼦.HID是Human Interface Devices的缩写.翻译成中⽂即为⼈机交互设备.这⾥的⼈机交互设备是⼀个宏观上⾯的概念,任何设备,只要符合HID spec,都⼆:HID驱动⼊⼝分析USB HID设备驱动⼊⼝位于linux-2.6.25/drivers/hid/usbhid/hid-core.c中.该module的⼊⼝为hid_init().代码如下:static int __init hid_init(void){int retval;retval = usbhid_quirks_init(quirks_param);if (retval)goto usbhid_quirks_init_fail;retval = hiddev_init();if (retval)goto hiddev_init_fail;retval = usb_register(&hid_driver);if (retval)goto usb_register_fail;info(DRIVER_VERSION ":" DRIVER_DESC);return0;usb_register_fail:hiddev_exit();hiddev_init_fail:usbhid_quirks_exit();usbhid_quirks_init_fail:return retval;}⾸先来看usbhid_quirks_init()函数.quirks我们在分析UHCI和HUB的时候也接触过,表⽰需要做某种修正的设备.该函数调⽤的参数是quirks_param.定义如下:static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };module_param_array_named(quirks, quirks_param, charp, NULL, 0444);从此可以看出, quirks_param是MAX_USBHID_BOOT_QUIRKS元素的字符串数组.并且在加载module的时候,可以动态的指定这些值.分析到这⾥.有⼈可以反应过来了,usbhid_quirks_init()是⼀种动态进⾏HID设备修正的⽅式.具体要修正哪些设备,要修正设备的那些⽅⾯,都可以由加载模块是所带参数来决定.usbhid_quirks_init()的代码如下:int usbhid_quirks_init(char **quirks_param){u16 idVendor, idProduct;u32 quirks;int n = 0, m;for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",&idVendor, &idProduct, &quirks);if (m != 3 ||usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {printk(KERN_WARNING"Could not parse HID quirk module param %s\n",quirks_param[n]);}}return0;}由此可以看出, quirks_param数组中的每⼀项可以分为三个部份,分别是要修正设备的VendorID,ProductID和要修正的功能.⽐如0x1000 0x0001 0x0004就表⽰:要忽略掉VendorID为0x1000,ProductID为0x0004的设备.(在代码中,有#define HID_QUIRK_跟进usbhid_modify_dquirk()函数,代码如下:int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,const u32 quirks){struct quirks_list_struct *q_new, *q;int list_edited = 0;if (!idVendor) {dbg_hid("Cannot add a quirk with idVendor = 0\n");return -EINVAL;}q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);if (!q_new) {dbg_hid("Could not allocate quirks_list_struct\n");return -ENOMEM;}q_new->hid_bl_item.idVendor = idVendor;q_new->hid_bl_item.idProduct = idProduct;q_new->hid_bl_item.quirks = quirks;down_write(&dquirks_rwsem);list_for_each_entry(q, &dquirks_list, node) {if (q->hid_bl_item.idVendor == idVendor &&q->hid_bl_item.idProduct == idProduct) {list_replace(&q->node, &q_new->node);kfree(q);list_edited = 1;break;}}if (!list_edited)list_add_tail(&q_new->node, &dquirks_list);up_write(&dquirks_rwsem);return0;}这个函数⽐较简单,就把quirks_param数组项中的三个部份存⼊⼀个封装结构.然后将其结构挂载到dquirks_list表.如果dquirks_list有重复的VendorId和ProductID就更新其quirks信息.经过usbhid_quirks_init()之后,所有要修正的设备的相关操作都会存放在dquirks_list中.返回到hid_init(),继续往下⾯分析.hiddev_init()是⼀个⽆关的操作,不会影响到后⾯的操作.忽略后⾯就是我们今天要分析的重点了,如下:retval = usb_register(&hid_driver);通过前⾯对HUB的驱动分析,相信对usb_redister()应该很熟悉了.hid_driver定义如下:static struct usb_driver hid_driver = {.name = "usbhid",.probe = hid_probe,.disconnect = hid_disconnect,.suspend = hid_suspend,.resume = hid_resume,.reset_resume = hid_post_reset,.pre_reset = hid_pre_reset,.post_reset = hid_post_reset,.id_table = hid_usb_ids,.supports_autosuspend = 1,};其中,id_table的结构为hid_usb_ids.定义如下:static struct usb_device_id hid_usb_ids [] = {{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,.bInterfaceClass = USB_INTERFACE_CLASS_HID },{ } /* Terminating entry */};也就是说,该驱动会匹配interface的ClassID,所有ClassID为USB_INTERFACE_CLASS_HID的设备都会被这个驱动所匹配.所以,所有USB HID设备都会由这个module来驱动.三:HID驱动的probe过程从上⾯的分析可看到,probe接⼝为hid_probe().定义如下:static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id){struct hid_device *hid;char path[64];int i;char *c;dbg_hid("HID probe called for ifnum %d\n",intf->altsetting->desc.bInterfaceNumber);//config the hid deviceif (!(hid = usb_hid_configure(intf)))return -ENODEV;usbhid_init_reports(hid);hid_dump_device(hid);if (hid->quirks & HID_QUIRK_RESET_LEDS)usbhid_set_leds(hid);if (!hidinput_connect(hid))hid->claimed |= HID_CLAIMED_INPUT;if (!hiddev_connect(hid))hid->claimed |= HID_CLAIMED_HIDDEV;if (!hidraw_connect(hid))hid->claimed |= HID_CLAIMED_HIDRAW;usb_set_intfdata(intf, hid);if (!hid->claimed) {printk ("HID device claimed by neither input, hiddev nor hidraw\n");hid_disconnect(intf);return -ENODEV;}if ((hid->claimed & HID_CLAIMED_INPUT))hid_ff_init(hid);if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER)hid_fixup_sony_ps3_controller(interface_to_usbdev(intf),intf->cur_altsetting->desc.bInterfaceNumber);printk(KERN_INFO);if (hid->claimed & HID_CLAIMED_INPUT)printk("input");if ((hid->claimed & HID_CLAIMED_INPUT) && ((hid->claimed & HID_CLAIMED_HIDDEV) ||hid->claimed & HID_CLAIMED_HIDRAW))printk(",");if (hid->claimed & HID_CLAIMED_HIDDEV)printk("hiddev%d", hid->minor);if ((hid->claimed & HID_CLAIMED_INPUT) && (hid->claimed & HID_CLAIMED_HIDDEV) &&(hid->claimed & HID_CLAIMED_HIDRAW))printk(",");if (hid->claimed & HID_CLAIMED_HIDRAW)printk("hidraw%d", ((struct hidraw*)hid->hidraw)->minor);c = "Device";for (i = 0; i < hid->maxcollection; i++) {if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&(hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&(hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) {c = hid_types[hid->collection[i].usage & 0xffff];break;}}usb_make_path(interface_to_usbdev(intf), path, 63);printk(": USB HID v%x.%02x %s [%s] on %s\n",hid->version >> 8, hid->version & 0xff, c, hid->name, path);return0;}这个函数看起来是不是让⼈⼼慌慌?其实这个函数的最后⼀部份就是打印出⼀个Debug信息,我们根本就不需要去看. hiddev_connect()和hidraw_connect()是⼀个选择编译的操作,也不可以不要去理会.然后,剩下的就没多少了.3.1:usb_hid_configure()函数分析先来看usb_hid_configure().顾名思义,该接⼝⽤来配置hid设备.怎么配置呢?还是深⼊到代码来分析,该函数有⼀点长,分段分析如下:static struct hid_device *usb_hid_configure(struct usb_interface *intf){struct usb_host_interface *interface = intf->cur_altsetting;struct usb_device *dev = interface_to_usbdev (intf);struct hid_descriptor *hdesc;struct hid_device *hid;u32 quirks = 0;unsigned rsize = 0;char *rdesc;int n, len, insize = 0;struct usbhid_device *usbhid;quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));/* Many keyboards and mice don't like to be polled for reports,* so we will always set the HID_QUIRK_NOGET flag for them. *///如果是boot设备,跳出.不由此驱动处理if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)quirks |= HID_QUIRK_NOGET;}//如果是要忽略的if (quirks & HID_QUIRK_IGNORE)return NULL;if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&(interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE))return NULL;⾸先找到该接⼝需要修正的操作,也就是上⾯代码中的quirks值,如果没有修正操作,则quirks为0.另外,根据usb hid spec中的定义,subclass如果为1,则说明该设备是⼀个boot阶段使⽤的hid设备,然后Protocol Code为1和2时分别代表Keyboard和Mouse. 如//get hid descriptorsif (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&(!interface->desc.bNumEndpoints ||usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {dbg_hid("class descriptor not present\n");return NULL;}//bNumDescriptors:⽀持的附属描述符数⽬for (n = 0; n < hdesc->bNumDescriptors; n++)if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);//如果Report_Descriptors长度不合法if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {dbg_hid("weird size of report descriptor (%u)\n", rsize);return NULL;}if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {dbg_hid("couldn't allocate rdesc memory\n");return NULL;}//Set idle_time = 0hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);//Get Report_Descriptorsif ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {dbg_hid("reading report descriptor failed\n");kfree(rdesc);return NULL;}//是否属于fixup?usbhid_fixup_report_descriptor(le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct), rdesc,rsize, rdesc_quirks_param);dbg_hid("report descriptor (size %u, read %d) = ", rsize, n);for (n = 0; n < rsize; n++)dbg_hid_line(" %02x", (unsigned char) rdesc[n]);dbg_hid_line("\n");对于HID设备来说,在interface description之后会附加⼀个hid description, hid description中的最后部份包含有Report description或者Physical Descriptors的长度.在上⾯的代码中,⾸先取得附加在interface description之后的hid description,然后,再从hid description中取得report description的长度.最后,取得report description的详细信息.在这⾥,还会将idle时间设备为0,表⽰⽆限时,即,从上⼀次报表传输后,只有在报表发⽣改变时,才会传送此报表内容,否则,传送NAK.这段代码的最后⼀部份是相关的fixup操作,不做详细分析.//pasrse the report_descriptorif (!(hid = hid_parse_report(rdesc, n))) {dbg_hid("parsing report descriptor failed\n");kfree(rdesc);return NULL;}kfree(rdesc);hid->quirks = quirks;if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL)))goto fail_no_usbhid;hid->driver_data = usbhid;usbhid->hid = hid;解析获得的report description,解析之后的信息,存放在hid_device->collection和hid_device->report_enum[ ]中,这个解析过程之后会做详细分析.然后,初始化⼀个usbhid_device结构,使usbhid_device->hid指向刚解析report description获得的hid_device.同 usbhid->bufsize = HID_MIN_BUFFER_SIZE;//计算各传输⽅向的最⼤bufferhid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)usbhid->bufsize = HID_MAX_BUFFER_SIZE;//in⽅向的传输最⼤值hid_find_max_report(hid, HID_INPUT_REPORT, &insize);if (insize > HID_MAX_BUFFER_SIZE)insize = HID_MAX_BUFFER_SIZE;if (hid_alloc_buffers(dev, hid)) {hid_free_buffers(dev, hid);goto fail;}计算传输数据的最⼤缓存区,并以这个⼤⼩为了hid设备的urb传输分配空间.另外,这⾥有⼀个最⼩值限制即代码中所看到的HID_MIN_BUFFER_SIZE,为64, 即⼀个⾼速设备的⼀个端点⼀次传输的数据量.在这⾥定义最⼩值为64是为了照顾低速然后,调⽤hid_alloc_buffers()为hid的urb传输初始化传输缓冲区.另外,需要注意的是,insize为INPUT⽅向的最⼤数据传输量.// 初始化usbhid->urbin和usbhid->usboutfor (n = 0; n < interface->desc.bNumEndpoints; n++) {struct usb_endpoint_descriptor *endpoint;int pipe;int interval;endpoint = &interface->endpoint[n].desc;//不是中断传输退出if ((endpoint->bmAttributes & 3) != 3) /* Not an interrupt endpoint */continue;interval = endpoint->bInterval;/* Change the polling interval of mice. *///修正⿏标的双击时间if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)interval = hid_mousepoll_interval;if (usb_endpoint_dir_in(endpoint)) {if (usbhid->urbin)continue;if (!(usbhid->urbin = usb_alloc_urb(0, GFP_KERNEL)))goto fail;pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);usb_fill_int_urb(usbhid->urbin, dev, pipe, usbhid->inbuf, insize,hid_irq_in, hid, interval);usbhid->urbin->transfer_dma = usbhid->inbuf_dma;usbhid->urbin->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;} else {if (usbhid->urbout)continue;if (!(usbhid->urbout = usb_alloc_urb(0, GFP_KERNEL)))goto fail;pipe = usb_sndintpipe(dev, endpoint->bEndpointAddress);usb_fill_int_urb(usbhid->urbout, dev, pipe, usbhid->outbuf, 0,hid_irq_out, hid, interval);usbhid->urbout->transfer_dma = usbhid->outbuf_dma;usbhid->urbout->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;}}if (!usbhid->urbin) {err_hid("couldn't find an input interrupt endpoint");goto fail;}遍历接⼝中的所有endpoint,并初始化in中断传输⽅向和out中断⽅向的urb.如果⼀个hid设备没有in⽅向的中断传输,⾮法.另外,在这⾥要值得注意的是, 在为OUT⽅向urb初始化的时候,它的传输缓存区⼤⼩被设为了0.IN⽅向的中断传输缓存区⼤⼩被设为了insize,传输缓存区⼤⼩在submit的时候会修正的. init_waitqueue_head(&hid->wait);INIT_WORK(&usbhid->reset_work, hid_reset);setup_timer(&usbhid->io_retry, hid_retry_timeout, (unsigned long) hid);spin_lock_init(&usbhid->inlock);spin_lock_init(&usbhid->outlock);spin_lock_init(&usbhid->ctrllock);hid->version = le16_to_cpu(hdesc->bcdHID);hid->country = hdesc->bCountryCode;hid->dev = &intf->dev;usbhid->intf = intf;usbhid->ifnum = interface->desc.bInterfaceNumber;hid->name[0] = 0;if (dev->manufacturer)strlcpy(hid->name, dev->manufacturer, sizeof(hid->name));if (dev->product) {if (dev->manufacturer)strlcat(hid->name, "", sizeof(hid->name));strlcat(hid->name, dev->product, sizeof(hid->name));}if (!strlen(hid->name))snprintf(hid->name, sizeof(hid->name), "HID %04x:%04x",le16_to_cpu(dev->descriptor.idVendor),le16_to_cpu(dev->descriptor.idProduct));hid->bus = BUS_USB;hid->vendor = le16_to_cpu(dev->descriptor.idVendor);hid->product = le16_to_cpu(dev->descriptor.idProduct);usb_make_path(dev, hid->phys, sizeof(hid->phys));strlcat(hid->phys, "/input", sizeof(hid->phys));len = strlen(hid->phys);if (len < sizeof(hid->phys) - 1)snprintf(hid->phys + len, sizeof(hid->phys) - len,"%d", intf->altsetting[0].desc.bInterfaceNumber);if (usb_string(dev, dev->descriptor.iSerialNumber, hid->uniq, 64) <= 0)hid->uniq[0] = 0;初始化hid的相关信息.//初始化hid 的ctrl传输usbhid->urbctrl = usb_alloc_urb(0, GFP_KERNEL);if (!usbhid->urbctrl)goto fail;usb_fill_control_urb(usbhid->urbctrl, dev, 0, (void *) usbhid->cr,usbhid->ctrlbuf, 1, hid_ctrl, hid);usbhid->urbctrl->setup_dma = usbhid->cr_dma;usbhid->urbctrl->transfer_dma = usbhid->ctrlbuf_dma;usbhid->urbctrl->transfer_flags |= (URB_NO_TRANSFER_DMA_MAP | URB_NO_SETUP_DMA_MAP);hid->hidinput_input_event = usb_hidinput_input_event;hid->hid_open = usbhid_open;hid->hid_close = usbhid_close;#ifdef CONFIG_USB_HIDDEVhid->hiddev_hid_event = hiddev_hid_event;hid->hiddev_report_event = hiddev_report_event;#endifhid->hid_output_raw_report = usbhid_output_raw_report;return hid;初始化usbhid的控制传输urb,之后⼜初始化了usbhid的⼏个操作函数.这个操作有什么⽤途,等⽤到的时候再来进⾏分析.fail:usb_free_urb(usbhid->urbin);usb_free_urb(usbhid->urbout);usb_free_urb(usbhid->urbctrl);hid_free_buffers(dev, hid);kfree(usbhid);fail_no_usbhid:hid_free_device(hid);return NULL;}经过上⾯的分析之后,我们对这个函数的⼤概操作有了⼀定的了解.现在分析⾥⾯调⽤的⼀些重要的⼦调函数.等这些⼦函数全部分析完了之后,不妨回过头看下这个函数.3.1.1:hid_parse_report()分析第⼀个要分析的函数是hid_parse_report().该函数⽤来解析report description.解析report description是⼀个繁杂的过程,对这个描述符不太清楚的,仔细看⼀下spec.在这⾥我们只会做代码上的分析.代码如下:struct hid_device *hid_parse_report(__u8 *start, unsigned size){struct hid_device *device;struct hid_parser *parser;struct hid_item item;__u8 *end;unsigned i;static int (*dispatch_type[])(struct hid_parser *parser,struct hid_item *item) = {hid_parser_main,hid_parser_global,hid_parser_local,hid_parser_reserved};if (!(device = kzalloc(sizeof(struct hid_device), GFP_KERNEL)))return NULL;//默认HID_DEFAULT_NUM_COLLECTIONS 项if (!(device->collection = kzalloc(sizeof(struct hid_collection) *HID_DEFAULT_NUM_COLLECTIONS, GFP_KERNEL))) {kfree(device);return NULL;}//hid_device->collection_size: collection的项数device->collection_size = HID_DEFAULT_NUM_COLLECTIONS;for (i = 0; i < HID_REPORT_TYPES; i++)INIT_LIST_HEAD(&device->report_enum[i].report_list);if (!(device->rdesc = kmalloc(size, GFP_KERNEL))) {kfree(device->collection);kfree(device);return NULL;}//hid_device->rdesc存放report_descriptor,hid_device->size存放这个描述符的⼤⼩memcpy(device->rdesc, start, size);device->rsize = size;if (!(parser = vmalloc(sizeof(struct hid_parser)))) {kfree(device->rdesc);kfree(device->collection);kfree(device);return NULL;}memset(parser, 0, sizeof(struct hid_parser));parser->device = device;end = start + size;while ((start = fetch_item(start, end, &item)) != NULL) {//long item在这⾥暂不做parseif (item.format != HID_ITEM_FORMAT_SHORT) {dbg_hid("unexpected long global item\n");hid_free_device(device);vfree(parser);return NULL;}//parse the short itemif (dispatch_type[item.type](parser, &item)) {dbg_hid("item %u %u %u %u parsing failed\n",item.format, (unsigned)item.size, (unsigned)item.type, (unsigned)item.tag);hid_free_device(device);vfree(parser);return NULL;}//如果全部解析完了if (start == end) {if (parser->collection_stack_ptr) {dbg_hid("unbalanced collection at end of report description\n");hid_free_device(device);vfree(parser);return NULL;}if (parser->local.delimiter_depth) {dbg_hid("unbalanced delimiter at end of report description\n");hid_free_device(device);vfree(parser);return NULL;}vfree(parser);return device;}}dbg_hid("item fetching failed at offset %d\n", (int)(end - start));hid_free_device(device);vfree(parser);return NULL;}进⼊到这个函数,我们⾸先看到的是Main,Globa,Local标签的解析函数.然后,分配并初始化了hid_device结构和hid_ parser.在代码中我们看到,hid_ parser-> device指向了hid_device.后hid_device没有任何域指向hid_parser. 实际上hid_parser只是⼀个辅另外,hid_device-> rdesc保存了⼀份report description副本.然后,就开始对report description的解析.函数fetch_item()⽤来取出report description的⼀项数据.代码如下:static u8 *fetch_item(__u8 *start, __u8 *end, struct hid_item *item){u8 b;//合法性检测if ((end - start) <= 0)return NULL;//取前⾯⼀个字节.对于短项.它的⾸个字节定义了bsize,bType,bTag.⽽对于长项,它的值为0xFEb = *start++;item->type = (b >> 2) & 3;item->tag = (b >> 4) & 15;//如果为长项.它的Type和Tag在其后的⼆个字节中.item->data.longdata指向数据的起始位置if (item->tag == HID_ITEM_TAG_LONG) {item->format = HID_ITEM_FORMAT_LONG;if ((end - start) < 2)return NULL;item->size = *start++;item->tag = *start++;if ((end - start) < item->size)return NULL;item->data.longdata = start;start += item->size;return start;}//对于短项的情况.取得size值.并根据size值取得它的data域item->format = HID_ITEM_FORMAT_SHORT;item->size = b & 3;switch (item->size) {case0:return start;case1:if ((end - start) < 1)return NULL;item->data.u8 = *start++;return start;case2:if ((end - start) < 2)return NULL;item->data.u16 = le16_to_cpu(get_unaligned((__le16*)start));start = (__u8 *)((__le16 *)start + 1);return start;case3:item->size++;if ((end - start) < 4)return NULL;item->data.u32 = le32_to_cpu(get_unaligned((__le32*)start));start = (__u8 *)((__le32 *)start + 1);return start;}return NULL;}对照代码中的注释,应该很容易看懂这个函数,不再详细分析.返回到hid_parse_report()中,取得相应项之后,如果是长项,这⾥不会做处理.对于短项.为不同的type调⽤不同的解析函数.3.1.1.1:Global项解析Global的解析⼊⼝是hid_parser_global().代码如下:static int hid_parser_global(struct hid_parser *parser, struct hid_item *item){switch (item->tag) {//PUSH项case HID_GLOBAL_ITEM_TAG_PUSH:if (parser->global_stack_ptr == HID_GLOBAL_STACK_SIZE) {dbg_hid("global enviroment stack overflow\n");return -1;}memcpy(parser->global_stack + parser->global_stack_ptr++,&parser->global, sizeof(struct hid_global));return0;//POP项case HID_GLOBAL_ITEM_TAG_POP:if (!parser->global_stack_ptr) {dbg_hid("global enviroment stack underflow\n");return -1;}memcpy(&parser->global, parser->global_stack + --parser->global_stack_ptr,sizeof(struct hid_global));return0;case HID_GLOBAL_ITEM_TAG_USAGE_PAGE:parser->age_page = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_LOGICAL_MINIMUM:parser->global.logical_minimum = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_LOGICAL_MAXIMUM:if (parser->global.logical_minimum < 0)parser->global.logical_maximum = item_sdata(item);elseparser->global.logical_maximum = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_PHYSICAL_MINIMUM:parser->global.physical_minimum = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_PHYSICAL_MAXIMUM:if (parser->global.physical_minimum < 0)parser->global.physical_maximum = item_sdata(item);elseparser->global.physical_maximum = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_UNIT_EXPONENT:parser->global.unit_exponent = item_sdata(item);return0;case HID_GLOBAL_ITEM_TAG_UNIT:parser->global.unit = item_udata(item);return0;case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:if ((parser->global.report_size = item_udata(item)) > 32) {dbg_hid("invalid report_size %d\n", parser->global.report_size);return -1;}return0;case HID_GLOBAL_ITEM_TAG_REPORT_COUNT:if ((parser->global.report_count = item_udata(item)) > HID_MAX_USAGES) {dbg_hid("invalid report_count %d\n", parser->global.report_count);return -1;}return0;case HID_GLOBAL_ITEM_TAG_REPORT_ID:if ((parser->global.report_id = item_udata(item)) == 0) {dbg_hid("report_id 0 is invalid\n");return -1;}return0;default:dbg_hid("unknown global tag 0x%x\n", item->tag);return -1;}}这个函数虽然长,但是逻辑很简单,对于global信息,存放在hid_parse->global中.如果遇到了PUSH项,将当前的global项⼊栈,栈即为hid_parse-> global_stack[ ].当前的栈顶位置由hid_parse-> global_stack_ptr指定.如果遇到了POP项,就将栈中的global信息出栈.3.1.1.2:Local项解析Local项解析的相应接⼝为hid_parser_local().代码如下:static int hid_parser_local(struct hid_parser *parser, struct hid_item *item){__u32 data;unsigned n;if (item->size == 0) {dbg_hid("item data expected for local item\n");return -1;}data = item_udata(item);switch (item->tag) {//DELIMITER项,定义⼀个Local项的开始case HID_LOCAL_ITEM_TAG_DELIMITER://data>1:⼀个local项开始,0:⼀个local项结束//parse->local.delimiter_branch:表⽰local项计数.//进⼊⼀个local项时,local.delimiter_depth为1,退出⼀个local项时local.delimiter_depth为0// TODO: Local项不能嵌套if (data) {/** We treat items before the first delimiter* as global to all usage sets (branch 0).* In the moment we process only these global* items and the first delimiter set.*/if (parser->local.delimiter_depth != 0) {dbg_hid("nested delimiters\n");return -1;}parser->local.delimiter_depth++;parser->local.delimiter_branch++;} else {if (parser->local.delimiter_depth < 1) {dbg_hid("bogus close delimiter\n");return -1;}parser->local.delimiter_depth--;}return1;//以下各项不能出现在有DELIMITER标签的地⽅case HID_LOCAL_ITEM_TAG_USAGE:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}//local的usage项有扩展⽤法,它的⾼16可以定义usage_page.如果⾼16为空,它的//usage_page则定义在global中的usage_page if (item->size <= 2)data = (parser->age_page << 16) + data;//然后添加到parse->local的usage列表return hid_add_usage(parser, data);//对于有usage_min和usage_max的情况,将usage_min和usage_max之间的usage添加到//parse=>local的usage列表case HID_LOCAL_ITEM_TAG_USAGE_MINIMUM:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}if (item->size <= 2)data = (parser->age_page << 16) + data;parser->age_minimum = data;return0;case HID_LOCAL_ITEM_TAG_USAGE_MAXIMUM:if (parser->local.delimiter_branch > 1) {dbg_hid("alternative usage ignored\n");return0;}if (item->size <= 2)data = (parser->age_page << 16) + data;for (n = parser->age_minimum; n <= data; n++)if (hid_add_usage(parser, n)) {dbg_hid("hid_add_usage failed\n");return -1;}return0;default:dbg_hid("unknown local item tag 0x%x\n", item->tag);return0;}return0;}详细分析⼀下hid_add_usage().代码如下:static int hid_add_usage(struct hid_parser *parser, unsigned usage){if (parser->age_index >= HID_MAX_USAGES) {dbg_hid("usage index exceeded\n");return -1;}parser->age[parser->age_index] = usage;parser->local.collection_index[parser->age_index] =parser->collection_stack_ptr ?parser->collection_stack[parser->collection_stack_ptr - 1] : 0;parser->age_index++;return0;}如果usage项超过了HID_MAX_USAGES,为⾮法.最⼤为8192项.Parse->age_index表⽰local的项数,当然也表⽰了parse->age[ ]数组中的下⼀个可⽤项.parser->local.collection_index表⽰该usage所在的collection项序号.具体的collection信息存放在hid_deivce->collection[ ]中.关于collection我们在分析Main项解析的时候会详细分析.3.1.1.3:Main项解析Main项解析的⼊⼝为hid_parser_main().代码如下:static int hid_parser_main(struct hid_parser *parser, struct hid_item *item){__u32 data;int ret;//data域data = item_udata(item);switch (item->tag) {//Collectioncase HID_MAIN_ITEM_TAG_BEGIN_COLLECTION:ret = open_collection(parser, data & 0xff);break;//End Collectioncase HID_MAIN_ITEM_TAG_END_COLLECTION:ret = close_collection(parser);break;//Inputcase HID_MAIN_ITEM_TAG_INPUT:ret = hid_add_field(parser, HID_INPUT_REPORT, data);break;//Outpputcase HID_MAIN_ITEM_TAG_OUTPUT:ret = hid_add_field(parser, HID_OUTPUT_REPORT, data);break;//Featurecase HID_MAIN_ITEM_TAG_FEATURE:ret = hid_add_field(parser, HID_FEATURE_REPORT, data);break;default:dbg_hid("unknown main item tag 0x%x\n", item->tag);ret = 0;}memset(&parser->local, 0, sizeof(parser->local)); /* Reset the local parser environment */return ret;}对Main项的解析要稍微复杂⼀点,Main项主要有两个部份,⼀个是Collection,⼀个是Input/Output/Feature项.先来看Collection项的解析.所有的collection信息都存放在hid_device->collection[ ]中.⽽Collection项⼜有嵌套的情况,每遇到⼀个Collection项就将collection的序号⼊栈,栈为parser_device->collection_stack[ ].栈顶指针为parser_device->collection_stack_ptr .遇到了⼀个end coll 熟悉这个⼤概的情况之后,就可以跟进open_collection()了.代码如下://所有的collection都存放在hid_dev->collection 中, ⽽hid_dev->maxcollection 表⽰collection[]中的下⼀个空闲位置//paser->collection_stack[ ]存放的是当前解析的collection在hid_dev->collection[ ]中的序号static int open_collection(struct hid_parser *parser, unsigned type){struct hid_collection *collection;unsigned usage;usage = parser->age[0];//colletcion嵌套过多if (parser->collection_stack_ptr == HID_COLLECTION_STACK_SIZE) {dbg_hid("collection stack overflow\n");return -1;}//device->maxcollection:存放的collection个数//device->collection[ ]太⼩,必须扩⼤存放空间if (parser->device->maxcollection == parser->device->collection_size) {collection = kmalloc(sizeof(struct hid_collection) *parser->device->collection_size * 2, GFP_KERNEL);if (collection == NULL) {dbg_hid("failed to reallocate collection array\n");return -1;}memcpy(collection, parser->device->collection,sizeof(struct hid_collection) *parser->device->collection_size);memset(collection + parser->device->collection_size, 0,sizeof(struct hid_collection) *parser->device->collection_size);kfree(parser->device->collection);parser->device->collection = collection;parser->device->collection_size *= 2;}//将collection序号⼊栈parser->collection_stack[parser->collection_stack_ptr++] =parser->device->maxcollection;//存⼊hid_device->collection[]collection = parser->device->collection +parser->device->maxcollection++;collection->type = type;collection->usage = usage;//collection的深度collection->level = parser->collection_stack_ptr - 1;if (type == HID_COLLECTION_APPLICATION)parser->device->maxapplication++;return0;}对照上⾯的分析和函数中的注释,理解这个函数应该很简单,不做详细分析.对于Input/Output/Feature项的解析:先来看⼀下hid_device结构的定义⽚段:struct hid_device{…………struct hid_report_enum report_enum[HID_REPORT_TYPES];……}对于INPUT/OUTPUT/FEATURE,每种类型都对应report_enum[ ]中的⼀项.Struct hid_report_enum定义如下:struct hid_report_enum {unsigned numbered;struct list_head report_list;struct hid_report *report_id_hash[256];};对于每⼀个report_id,对应report_id_hash[ ]中的⼀项,同时,将所对应的hid_report添加到report_list链表中.如果有多个report_id 的情况,numbered被赋为1.Struct hid_report定义如下:struct hid_report {struct list_head list;unsigned id; /* id of this report */unsigned type; /* report type */struct hid_field *field[HID_MAX_FIELDS]; /* fields of the report */unsigned maxfield; /* maximum valid field index */unsigned size; /* size of the report (bits) */struct hid_device *device; /* associated device */}List:⽤来形成链表Id:表⽰report_idType: INPUT/OUTPUT/FEATUREField[ ]:成员列表,对应⼀个report_id有多个INPUT(OUTPUT/FEATURE)项Maxfield: field[ ]中的有效项数Size: 该report的⼤⼩Device:所属的hid_device了解了这些之后,就可以来看⼀下代码了:如下:static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsigned flags){struct hid_report *report;struct hid_field *field;int usages;unsigned offset;int i;//找到类型和对应report_id所在的report.如果不存在,则新建之if (!(report = hid_register_report(parser->device, report_type, parser->global.report_id))) {dbg_hid("hid_register_report failed\n");return -1;}//对当前global数据的有效性判断。
2-Linux驱动和内核模块编程
设备驱动的Hello World模块 设备驱动的 模块
模块卸载函数
static void __exit cleanup_function(void) { /* 释放资源 */ } module_exit(cleanup_function);
在模块被移除前注销接口并 释放所有所占用的系统资源
标识这个代码是只用于模块卸载( 标识这个代码是只用于模块卸载 通过使编译器把它放在 特殊的 ELF 段) 原型: 原型:#define __exit __attribute__ ((__section__(“.exit.text”)))
查看已加载模块
lsmod cat /proc/modules.
卸载驱动模块 卸载模块
从内核中卸载模块可以用rmmod工具.
注意,如果内核认为该模块任然在使用状态, 注意,如果内核认为该模块任然在使用状态,或 者内核被禁止移除该模块,则无法移除该模块。 者内核被禁止移除该模块,则无法移除该模块。
内核打印函数
隐藏硬件细节,提高应用软件的可移植性 提供安全性 开发模式 内核态驱动 用户态驱动
提供机制,而不是提供策略
机制:驱动程序能实现什么功能 策略:用户如何使用这些功能
设备的分类和特点Biblioteka 设备分类字符设备(char device) 字符设备 块设备(block device) 块设备 网络设备(network device) 网络设备
MODULE_LICENSE()---模块许可证声明 模块许可证声明
模块许可证(LICENSE)声明描述内核模块的许可权限 如果不声明LICENSE,模块被加载时,将收到内核被污染(kernel tainted)的警告
动手写一个内核模块
linux驱动编程初级+makefile
驱动编程1 模块的概述 (2)2 source insight 加载内核源码方法 (2)3 模块makefile的编写 (3)4 模块makefile编写方法 (4)5 在X86上运行模块: (5)6 编写模块 (5)7 模块的加载进内核命令 (5)8 最简单的上层调用+ 调用驱动方法 (6)9 复杂框架上层应用+驱动调用方法 (7)10 复杂框架字符设备创建并注册过程 (7)11 file_operations常用函数 (9)12 同步互斥操作 (10)13 同步互斥函数总结 (10)14 阻塞IO编程流程 (11)15 轮询操作上层select 下层poll (12)16 信号处理 (12)17 中断 (13)18 中断新模型--上半部中断和下半部中断的实现 (14)19 内核定时器编程 (15)20 内核延时函数 (15)21 内核源代码中头文件分配方式 (15)22 linux内核管理和内核的内存管理 (16)23 设备io端口和io内存访问–如何控制led的亮灭 (16)24 * 驱动-设备分离思想编程————内核进阶 (18)25 驱动-设备分离-核心最小架构 (18)26 驱动设备分离思想- 上层架构(基于封装) (20)27 头文件总结 (24)28 设置系统自启动命令u-boot (24)第一天需要理清的东西1)模块的概念,模块与应用的区别2)模块主要的组成头文件、module_init() modoule_exit() module_lisence()3)模块的如何编辑,如何编译,如何加载到内核中运行使用makefile4)模块驱动编写,必须通过上层应用程序调用。
1模块的概述模块是内核的一部分,为了防止内核太大,把它放在文件系统里面。
也可以在编译内核的直接编译进内核。
1,存储位置可以在开始时编译进内核,也可以编译进模块,最后加载2、运行时环境在哪个内核树下编译,就对应这个运行环境3、模块的编译问题:前提条件是需要对应的内核源码树,或者必须有对应的内核版本匹配4、模块编译使用makefile 注意makefile的编写2source insight 加载内核源码方法在windows下创建工程,使用source insight查看内核代码:2.1 先将内核源码拷到对应的文件夹2.2 在source insight 里添加工程,筛选需要添加的文件注意选择按照树来添加,然后按照remove来踢出不需要的文件夹2.3 最后同步3模块makefile的编写模块的编译:1)、模块编译的核心语句:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules-C :进入内核源码树M= : 返回到当前目录,再次执行该目录下的makefileeg: /tnt/abc.c -----> abc.ko1、在/tnt目录下敲make,只有/tnt目录下的Makefile被调用2、目的是要进入到内核源码树中,一进一回,-C来进,M=带着内核源码树中的makefile的功能回来了-------内核源码树中Makefile的目标:obj-y:汇集了所有编译进内核的目标文件obj-m:汇集了所有编译成模块的目标文件3、回来过后,我们只有确定obj-m变量的集合4、make modules告诉内核的makefile,只做编译模块的功能4模块makefile编写方法ifeq ($(KERNELRELEASE),)KERNELDIR := /work/linux-2.6.35-farsightPWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesinstall:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_installclean:rm -rf .tmp_versions *.ko *.o .*.cmd *.mod.c *.order *.symvers.PHONY: modules cleanelseobj-m := ex1.oendif以上是makefile的内容,●注意原来的内核目录树不要进行make clean 或者make distclean●KERNELDIR 表示模块加载在哪个内核的文件夹(又叫内核源码树),●$(MAKE) -C $(KERNELDIR) M=$(PWD) modules 表示进入该内核文件夹,将顶层makefile 中的内容带回,再重新执行一次该makefile 将obj-m := ex1.o 编译,并执行make modules (并只编译ex1.c ,不编译其它模块)●$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install 表示执行顶层makefile的modules install 标签下的命令●安装的位置原来默认在/lib 下面,所以需要修改其到我们制作的根文件系统下/work/rootfs/在顶层Makefile位置搜索:MODLIB修改为:●obj-m := ex1.o 你需要编译的.c的文件名****************************此时简单的编译环境已经搭建完毕******************* ****************************执行make ***********************************************执行make install *******************在/work/rootfs/lib/modules/2.6.35/extra即可找到该模块.ko*****************************************************************************搭建好环境,保证虚拟机与板子与计算机网络连通,并设置板子u-boot 从nfs挂载,启动内核,并成功通过nfs 加载rootfs,此时环境完毕,进入/work/rootfs/lib/modules/2.6.35/extra ,找到模块,加载卸装模块操纵5在X86上运行模块:修改Makefile中的内核源码树的目录X86下的内核源码树:/lib/modules/2.6.35-22-generic/build如果没有在控制台上交互,默认是看不到信息的,需要dmesg这个命令去查看6编写模块模块最小组成如下:●注意:module_init module_exit 必须放在末尾●注意:函数的原型返回值●头文件7模块的加载进内核命令insmodrmmodlsmod8最简单的上层调用+ 调用驱动方法8.1 首先在module_init(abc) abc函数中注册设备register_chrdev(注册设备号,上层可见的设备名,操作封装)该函数完成设备注册,在板子上用cat /proc/devices 便可以看见该设备8.2 完成fops 操作的封装●注意格式●必须在函数后面声明该结构体●头文件#include <linux/fs.h>8.3 查看到该字符设备后,创建设备节点,则上层通过设备字符名与该设备号绑定mknod /dev/hf_char c 245 0ls /dev/ 可以查看注册的所有设备节点8.4 此时上层应用的open(”hf_char”,O_RDWR),即可完成该设备的打开,即可以完成上层应用于下层驱动相关fops 的操作。
Linux教程基础知识大全
Linux教程基础知识大全Linu__操作系统在短短的几年之内得到了非常迅猛的发展,这与linu__具有的良好特性是分不开的。
Linu__包含了UNI__的全部功能和特性。
简单来说,linu__具有以下主要特性:遵循GNU GPL,开放性,多任务,多用户,设备独立性,提供了丰富的网络功能,可靠的系统安全,良好的可移植性。
下面就让小编带你去看看Linu__教程基础知识大全,希望对你有所帮助吧!Linu__运维学习路线,实用Linu__教程,推荐学习收藏1、掌握Linu__基础俗话说“思则有备,有备无患”,学习之前你先要搭建好学习环境(红帽RHEL7)然后在虚拟机安装它,开始使用它。
学习Linu__,命令使用是学习的前提,就像你和外国人说话就得说英语不然别人怎么知道你说的是什么。
例如:命令名,选项,各个参数都作为命令的输入项,都是独立的项,他们之间必须用空格隔开。
Linu__中命令格式如下:命令名【选项】【参数1】【参数2】……学习Linu__,要熟练掌握命令的使用,虽然命令多,但是熟能生巧,在以后的学习运用中能大大节约你的时间。
还有学习linu__切不可粗心大意,往往一个小小的空格也会报错,当然在linu__中,大小写是很敏感的,切记!2、从基础入手,切勿眼高手低linu__如果一旦学习一段时间之后你会发现其实也没想象中那么难,甚至比windows更简单已操作,通常认为GLI没有GUL那么方面用户操作。
因为命令行界面的软件通常需要用户记忆操作来完成命令,但是,由于其本身的特点,命令行界面要较图形用户界面节约计算机系统的资源。
所以,熟记命令的前提下,使用命令行界面往往要较使用图形用户界面的操作速度要快,这也是你以后玩转Linu__的基本条件之一。
3、多总结,勤思考,多记笔记如果想学好Linu__系统知识,不是一天两天就能学会的,也是经过一朝一夕,慢慢积累的,这个过程中要学会去总结,多思考,多动手去练,去实践,在理解的基础上去记忆,把原理搞懂,把重要的知识点积累下来,以便在以后的工作中方便查找,当做查询手册,同时也能锻炼我们编写文档的能力,对以后都是有很大帮助的。
基于rk3568的linux驱动开发——gpio知识点 -回复
基于rk3568的linux驱动开发——gpio知识点-回复什么是波形单位换算?在物理学中,波是一种传播能量和信息的方式。
波动现象广泛存在于我们生活的各个方面,比如光、声音、水波等等。
而波的传播过程中,我们往往需要对波的性质进行度量和换算,这就需要用到波形单位换算。
波形单位换算是一种将不同的波长、频率、速度等物理量互相转换的方法,帮助我们更好地理解和处理波动现象。
波形单位的基本概念在进行波形单位换算之前,我们首先需要了解一些基本概念。
其中最重要的三个概念是波长、频率和波速。
波长(λ)是指在一个完整的波周期中,波的传播距离。
以正弦波为例,波长就是峰值或谷值之间的距离。
频率(f)是指波的周期性,也就是波动过程中的重复次数。
频率用赫兹(Hz)作为单位,表示每秒钟内波动的周期数。
波速(v)是指波动传播的速度,也可以理解为波动能量在单位时间内传播的距离。
波速和波长、频率有着密切的关系,可以用以下公式表示:v = f * λ。
波形单位换算的步骤进行波形单位换算时,我们可以根据不同的物理量之间的关系,通过一系列的换算步骤来完成。
步骤1:确定已知量和要求量在进行波形单位换算时,首先需要明确已知量和要求量。
已知量是我们已知的物理量,而要求量是我们需要计算或转换的物理量。
例如,已知波长为100米,要求计算对应的频率。
步骤2:查找单位换算公式根据已知量和要求量的性质,我们可以查找相应的单位换算公式。
对于波长和频率的转换,我们可以使用波速公式进行计算。
波速公式:v = f * λ步骤3:进行单位换算计算根据波速公式,可以得到频率的计算公式:f = v / λ。
将已知量带入公式中,得到频率的计算表达式,然后进行计算。
例如,在已知波长为100米的情况下,需要计算对应的频率,假设波速为300000000米/秒。
则可以使用公式:f = 300000000 / 100,计算得到频率为3,000,000赫兹。
通过这一系列的换算步骤,我们可以将波长转换为频率,从而更好地理解和处理波动现象。
linux驱动面试题及答案
linux驱动面试题及答案一、概述在Linux开发领域,驱动程序是至关重要的组成部分。
为了帮助读者更好地准备Linux驱动开发面试,本文将介绍一些常见的Linux驱动面试题及其答案。
二、Linux驱动基础知识1. 什么是Linux驱动?答:Linux驱动是一段软件程序,用于与特定硬件设备进行通信,实现对硬件设备的控制和数据传输。
2. Linux驱动由哪些组成部分构成?答:Linux驱动由多个组成部分构成,包括设备和驱动模块。
设备代表硬件设备,而驱动模块负责驱动设备并与内核进行交互。
3. 内核态和用户态之间的区别是什么?答:内核态是操作系统的核心部分,具有最高的权限。
用户态是应用程序运行的环境,权限较低。
在内核态中,驱动可以直接访问硬件设备。
4. 请解释Linux设备树(Device Tree)是什么?答:Linux设备树是一种描述硬件设备及其连接方式的数据结构,用于在启动时为设备提供必要的参数和配置信息。
5. 使用哪个命令来加载和卸载Linux驱动?答:insmod命令用于加载驱动模块,rmmod命令用于卸载驱动模块。
三、Linux驱动开发相关问题6. 在Linux驱动中,什么是Platform驱动?答:Platform驱动是一种Linux内核驱动,用于支持与硬件设备直接连接的平台设备。
其驱动模块通过设备树(Device Tree)来识别和初始化设备。
7. 请解释字符设备驱动是什么?答:字符设备驱动是一种Linux驱动,用于支持以字符为单位进行I/O操作的设备,如串口、终端等。
8. 什么是中断处理程序?如何在Linux驱动中实现中断处理程序?答:中断处理程序是在CPU接收到硬件设备发出的中断信号时执行的函数。
在Linux驱动中,可以通过注册中断处理程序的方式来实现,通常使用request_irq函数来注册中断处理函数。
9. 在Linux驱动中,如何进行内存管理?答:在Linux驱动中,可以使用kmalloc和kfree函数来进行动态内存的分配和释放。
Linux设备驱动开发详解-第6章字符设备驱动(一)-globalmem
Linux设备驱动开发详解-第6章字符设备驱动(⼀)-globalmem1 驱动程序设计之前奏 (2)1.1 应⽤程序、库、内核、驱动程序的关系 (2)1.2 设备类型 (2)1.3 设备⽂件 (2)1.4 主设备号和从设备号 (2)1.5 驱动程序与应⽤程序的区别 (3)1.6 ⽤户态与内核态 (3)1.7 Linux驱动程序功能 (3)2 字符设备驱动程序框架 (3)2.1 file_operations结构体 (4)2.2 驱动程序初始化和退出 (5)2.3 将驱动程序模块注册到内核 (5)2.4 应⽤字符设备驱动程序 (5)3 globalmem虚拟设备实例描述 (6)3.1 头⽂件、宏及设备结构体 (6)3.2 加载与卸载设备驱动 (6)3.3 读写函数 (8)3.4 seek()函数 (9)3.5 ioctl()函数 (10)3.6 globalmem完整实例 (12)4 测试应⽤程序 (17)4.1 应⽤程序接⼝函数 (17)4.2 应⽤程序 (18)5 实验步骤 (19)5.1 编译加载globalmem 模块 (19)5.2 编译测试应⽤程序 (20)6 扩展 (21)1 驱动程序设计之前奏㈠应⽤程序、库、内核、驱动程序的关系㈡设备类型㈢设备⽂件㈣主设备号与从设备号㈤驱动程序与应⽤程序的区别㈥⽤户态与内核态㈦Linux驱动程序功能1.1 应⽤程序、库、内核、驱动程序的关系■应⽤程序调⽤应⽤程序函数库完成功能■应⽤程序以⽂件形式访问各种资源■应⽤程序函数库部分函数直接完成功能部分函数通过系统调⽤由内核完成■内核处理系统调⽤,调⽤设备驱动程序■设备驱动直接与硬件通信1.2 设备类型■字符设备对字符设备发出读/写请求时,实际的硬件I/O操作⼀般紧接着发⽣■块设备块设备与之相反,它利⽤系统内存作为缓冲区■⽹络设备⽹络设备是⼀类特殊的设备,它不像字符设备或块设备那样通过对应的设备⽂件节点访问,也不能直接通过read或write进⾏数据访问请求1.3 设备⽂件■设备类型、主从设备号是内核与设备驱动程序通信时使⽤的■应⽤程序使⽤设备⽂件节点访问对应设备■每个主从设备号确定的设备都对应⼀个⽂件节点■每个设备⽂件都有其⽂件属性(c或者b)■每个设备⽂件都有2个设备号(后⾯详述)主设备号:⽤于标识驱动程序从设备号:⽤于标识同⼀驱动程序的不同硬件■设备⽂件的主设备号必须与设备驱动程序在登记时申请的主设备号⼀致■系统调⽤是内核与应⽤程序之间的接⼝■设备驱动程序是内核与硬件之间的接⼝1.4 主设备号和从设备号■在设备管理中,除了设备类型外,内核还需要⼀对被称为主从设备号的参数,才能唯⼀标识⼀个设备■主设备号相同的设备使⽤相同的驱动程序■从设备号⽤于区分具体设备的实例例:PC的IDE设备,主设备号⽤于标识该硬盘,从设备号⽤于标识每个分区■在/dev⽬录下使⽤ll命令(ls -l)可以查看各个设备的设备类型、主从设备号等■cat /proc/devices可以查看系统中所有设备对应的主设备号1.5 驱动程序与应⽤程序的区别■应⽤程序以main开始■驱动程序没有main,它以⼀个模块初始化函数作为⼊⼝■应⽤程序从头到尾执⾏⼀个任务■驱动程序完成初始化之后不再运⾏,等待系统调⽤■应⽤程序可以使⽤GLIBC等标准C函数库■驱动程序不能使⽤标准C库1.6 ⽤户态与内核态■驱动程序是内核的⼀部分,⼯作在内核态■应⽤程序⼯作在⽤户态■数据空间访问问题★⽆法通过指针直接将⼆者的数据地址进⾏传递★系统提供⼀系列函数帮助完成数据空间转换get_userput_usercopy_from_usercopy_to_user1.7 Linux驱动程序功能■对设备初始化和释放■把数据从内核传送到硬件和从硬件读取数据■读取应⽤程序传送给设备⽂件的数据和回送应⽤程序请求的数据■检测和处理设备出现的错误2 字符设备驱动程序框架①Linux各种设备驱动程序都是以模块的形式存在的,驱动程序同样遵循模块编程的各项原则②字符设备是最基本、最常⽤的设备,其本质就是将千差万别的各种硬件设备采⽤⼀个统⼀的接⼝封装起来,屏蔽了不同设备之间使⽤上的差异性,简化了应⽤层对硬件的操作③字符设备将各底层硬件设备封装成统⼀的结构体,并采⽤相同的函数操作,如下等:open/close/read/write/ioctl④添加⼀个字符设备驱动程序,实际上是给上述操作添加对应的代码⑤Linux对所有的硬件操作统⼀做以下抽象抽象file_operations结构体规定了驱动程序向应⽤程序提供的操作接⼝struct file_operations ext2_file_operations ={.llseek = generic_file_llseek,.read = generic_file_read,.write = generic_file_write,.aio_read = generic_file_aio_read,.aio_write = generic_file_aio_write,.ioctl = ext2_ioctl,.mmap = generic_file_mmap,.open = generic_file_open,.release = ext2_release_file,.fsync = ext2_sync_file,.readv = generic_file_readv,.writev = generic_file_writev,.sendfile = generic_file_sendfile,};⑥⽤户态与内核态数据的交互⽤户应⽤程序与驱动程序分属于不同的进程空间,因此⼆者之间的数据应当采⽤以下函数进⾏交换long copy_to_user(kernel_buffer, user_buffer,n)//从内核空间拷贝n字节数据到⽤户空间copy_from_user(kernel_buffer, user_buffer,n)//从⽤户空间拷贝n字节数据到内核空间put_user(kernel_value, user_buffer)//从内核空间拷贝⼀数据变量到⽤户空间get_user(kernel_value, user_buffer)//从⽤户空间拷贝⼀数据变量到内核空间(内核空间数据可是任意类型)2.1 file_operations结构体⑴write函数■从应⽤程序接收数据送到硬件ssize_t (*write)(struct file*, const char __user *, size_t, loff_t*);⑵read函数■从硬件读取数据并交给应⽤程序ssize_t (*read)(struct file *, char __user *, size_t, loff_t*); /// 从设备中同步读取数据⑶ioctl函数■为应⽤程序提供对硬件⾏为的相关配置int (*ioctl)(struct inode *, struct file *, unsigned int, unsigned long);⑷open函数■当应⽤程序打开设备时对设备进⾏初始化■使⽤MOD_INC_USE_COUNT增加驱动程序的使⽤次数,当模块使⽤次数不为0时,禁⽌卸载模块Int (*open)(struct inode *, struct file*);⑸release函数■当应⽤程序关闭设备时处理设备的关闭操作■使⽤MOD_DEC_USE_COUNT减少驱动程序的使⽤次数,配合open使⽤,来对模块使⽤次数进⾏计数int (*release)(struct inode *, struct file*);⑹⑻⑻⑼⑽2.2 驱动程序初始化和退出①驱动程序初始化函数■Linux在加载内核模块时会调⽤初始化函数■在初始化函数中⾸先进⾏资源申请等⼯作■使⽤register_chrdev向内核注册驱动程序②驱动程序退出函数■Linux在卸载内核模块时会调⽤退出函数■释放驱动程序使⽤的资源■使⽤unregister_chrdev从内核中卸载驱动程序2.3 将驱动程序模块注册到内核内核需要知道模块的初始化函数和退出函数,才能将模块放⼊⾃⼰的管理队列中①module_init()向内核声明当前模块的初始化函数②module_exit()向内核声明当前模块的退出函数2.4 应⽤字符设备驱动程序㈠加载驱动程序■insmod 内核模块⽂件名■cat /proc/devices 查看当前系统中所有设备驱动程序及其主设备号㈡⼿动建⽴设备⽂件■设备⽂件⼀般建⽴/dev⽬录下■mknod ⽂件路径c [主设备号] [从设备号]㈢应⽤程序接⼝函数■编写应⽤层测试程序■可以使⽤标准C的⽂件操作函数来完成①int open(const char *path, int oflag,…);★打开名为path的⽂件或设备★成功打开后返回⽂件句柄★常⽤oflag:O_RDONLY, O_WRONLY, O_RDWR②int close(int fd);★关闭之前被打开的⽂件或设备★成功关闭返回0,否则返回错误代号③ssize_t read(int fd, void *buffer, size_t count)★从已经打开的⽂件或设备中读取数据★buffer表⽰应⽤程序缓冲区★count表⽰应⽤程序希望读取的数据长度★成功读取后返回读取的字节数,否则返回-1④ssize_t write(int fd, void *buffer, size_t count);★向已经打开的⽂件或设备中写⼊数据★buffer表⽰应⽤程序缓冲区★count表⽰应⽤程序希望写⼊的数据长度★成功写⼊后返回写⼊的字节数,否则返回-1④int ioctl(int fd, unsigned int cmd, unsigned long arg);★向驱动程序发送控制命令★cmd:⽤来定义⽤户向驱动分配的命令例如G PF驱动中:设置指定管脚的⾼低电平、输⼊输出特性等为了规范化及错误检查常⽤_IO宏合成该命令:_IO(MAGIC, num) ★arg:配置命令参数配合cmd命令完成指定功能3 globalmem虚拟设备实例描述3.1 头⽂件、宏及设备结构体在globalmem字符设备驱动中,应包含它要使⽤的头⽂件,并定义globalmem设备结构体及相关宏。
linux驱动面试题
linux驱动面试题1. 概述在Linux系统中,驱动程序的作用是使硬件设备与操作系统能够有效地通信和合作。
在Linux驱动面试中,考察的内容主要包括对Linux驱动的基本原理和相关技术的理解、驱动开发经验以及解决实际问题的能力等方面。
2. 驱动开发基础2.1 驱动与内核Linux驱动是在内核中运行的模块,通过向内核注册相应的设备驱动接口,实现设备与操作系统的交互。
驱动开发需要对内核的基本原理和架构有一定的了解。
2.2 设备模型Linux采用了设备树(Device Tree)来描述硬件设备,驱动开发者需要理解设备树的基本概念和使用方法。
此外,掌握相关的API接口,如设备注册和资源管理等也是重要的。
2.3 驱动开发工具驱动开发通常需要使用一些工具来辅助开发和调试,如GCC编译器、Makefile、Kconfig等。
熟悉这些工具的使用可以提高开发效率。
3. 驱动开发技巧3.1 驱动加载与卸载了解驱动的加载和卸载过程是驱动开发的基础,掌握相关的函数和接口,如module_init()、module_exit()等。
3.2 设备操作对于设备操作,驱动开发者需要实现相应的接口函数,如open()、read()、write()和release()等。
同时,需要注意多个进程对设备的并发访问问题。
3.3 中断处理了解中断的基本原理,驱动开发者需要实现中断处理函数,通过适当地使能和屏蔽中断,确保设备的稳定工作。
3.4 内存管理驱动开发过程中需要对内存进行分配和释放,应注意内存的合理管理,避免内存泄漏和越界访问问题。
4. 驱动性能与调试4.1 性能优化优化驱动程序可以提高系统的效率和响应速度。
常见的性能优化方法包括减少不必要的资源竞争、提高中断处理效率等。
4.2 调试技巧在驱动开发过程中,面对各种问题,掌握一些调试技巧可以快速定位和解决问题。
例如,使用printk()打印调试信息、使用GDB调试等。
5. 驱动安全与稳定性5.1 安全性考虑驱动程序需要处理来自用户空间的输入,必须保证输入的合法性,防止恶意代码等对系统造成危害。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Flash 从低地址到高地址方向
MTK的Bootloader 在嵌入式操作系统中,BootLoader是在操作系统内 核运行之前运行。可以初始化硬件设备、建立内存空间 ቤተ መጻሕፍቲ ባይዱ射图,从而将系统的软硬件环境带到一个合适状态, 以便为最终调用操作系统内核准备好正确的环境。 MTK的bootloader有两部分组成:
(4) BootROM跳转到pre-loader的入口处并执行。
(5) Pre-loader初始化DRAM和加载LK到RAM中。
(6) Pre-loader跳转到LK中并执行,然后LK做一些初始化,比如显示的 初始化等。 (7) LK从存储器中加载引导镜像(boot image),包括linux内核和 ramdisk(Android呢?) (8) LK跳转到linux内核并执行。
MTK的Bootloader
正常启动的主要工作如下: (1) 设备上电后,Boot ROM开始运行。 (2) BootROM初始化软件堆栈(software stack)、通信端口和可引导存储 设备(比如NAND/EMMC)。
(3) BootROM从存储器中加载pre-loader到内部SRAM(ISRAM)中,因 为这时候还没有初始化外部的DRAM。
(8) USB模块 当USB线插入时,它初始化来和外部工具通信,比如用于升级 系统的下载工具或是META模式触发器的META工具。 (9) NAND模块 (10) MSDC模块 Pre-loader可以从NAND flash或是EMMC中加载LK,这两者只 能选择其中一种来启动。
LK中涉及的硬件部分
MTK的Bootloader
pre-loaders中涉及的硬件部分
(1) PLL模块 1) PLL模块用于调整处理器和外部内存的频率。 2) 在PLL模块初始化后,处理器和外部内存的频率可由 26MHZ/26MHZ增加到1GHZ/192MHZ。 (2) UART模块 1) UART模块用于调试或是META(Mobile Engineering Testing Architecture)模式下的握手。 2) 默认情况下,UART4初始化波特率为9216000bps和用于调试 信息的输出,UART1初始化为115200bps和作为UART META端口。 但也可以使用UART1作为调试或是UART META端口。 (3) 计时器(timer)模块 这是个基本的模块,用来计算硬件模块所需要的延时或是超时时间。
(4) 内存模块
1) Pre-loader由boot ROM加载和在芯片组内部的SRAM中执行,因 为外部的DRAM还没有初始化。 2) 为了准备软件整个可执行环境,pre-loader采用内置的内存设置来 初始化DRAM(DRAM is initialized upon pre-loader built-inmemory settigns)。这样,LK就能够被加载到DRAM中并执行。 (5) GPIO模块 (6) PMIC模块 为了提供一些基本的硬件功能,比如控制外设电源,pre-loader初始化 上层模块(upper modules)。 (7) RTC模块 1) 当通过power按键开机后,pre-loader拉高RTC的PWBB来保持设 备一直有电(keep the device alive)和继续引导LK。 2) RTC闹钟(alarm)有可能是设备开机的启动源,对于这种情况,设 备部需要按power按键就可自动启动。
(3) I2C模块 (4) PWM模块 (5) PMIC模块 (6) RTC模块 和计时器模块一样,在U-Boot中,I2C/PMIC/RTC重新复位寄存器来 复位这些模块。 (7) LED模块 通过这power off charging个模块,设备能够通知用户当前的充电状态。
(8) 充电模块 这个模块负责关机充电(power off charging)、低电压充电(lower charging in the system)。
(1) 第1部分bootloader,也就是MTK内部(in-house)的preloader,这部分依赖平台。
(2) 第2部分bootloader,也就是 Little Kernel ,这部分依赖 操作系统,负责引导linux操作系统和Android框架。 源码位置: \vendor\mediatek\proprietary\bootable\bootloader
Linux 驱动学习总结汇报
2016年11月12日
内核模块 Bootloder 并发控制 中断处理 设备驱动的结构
Linux内核重要子系统
1. 系统调用接口 2. 进程管理 3. 内存管理 4. 虚拟文件系统 5. 网络堆栈 6. 设备驱动
最简单的嵌入式系统
Bootloader
参数
系统内核
根文件系统
(9) LCD模块 使用这个模块,设备能够显示logo或是任何通知的消息。 (10) NAND模块 因为U-Boot也需要从flash读取镜像(比如内核或是ramdisk), 所以有必要在U-Boot中初始化NAND相关的功能。 (11) MSDC模块 支持MSDC启动
一些重要的数据结构
1. 大部分驱动程序涉及三个重要的内核数据结构: • 文件操作file_operations结构体 • 文件对象file结构体 • 索引节点inode结构体