linux LED 驱动

合集下载

linux 驱动 list 使用方法

linux 驱动 list 使用方法

linux 驱动 list 使用方法1. Linux驱动list是一个重要的命令,可以列出系统中加载的所有驱动程序。

The Linux driver list is an important command that canlist all the loaded drivers in the system.2.通过使用lsmod命令,可以查看当前系统中加载的驱动程序列表。

By using the lsmod command, you can view the list of loaded drivers in the current system.3.驱动程序列表包括了每个驱动程序的名称、大小、使用次数等详细信息。

The driver list includes detailed information such as the name, size, and number of uses for each driver.4.使用modprobe命令可以动态加载或卸载驱动程序。

The modprobe command can be used to dynamically load or unload drivers.5.通过在命令后加入驱动程序的名称,可以具体操作某一个驱动程序。

By adding the name of the driver after the command, you can specifically operate on a particular driver.6.驱动程序列表对于系统管理员和开发人员来说非常有用。

The driver list is very useful for system administrators and developers.7.你可以使用grep命令来筛选特定的驱动程序。

You can use the grep command to filter specific drivers.8.使用insmod命令可以手动加载某个内核模块。

linux中的led的控制方式

linux中的led的控制方式

linux中的led的控制方式LED,全称为Light Emitting Diode,是一种半导体器件,具有低功耗、长寿命、高亮度等特点,因而被广泛应用于各种电子设备中。

在Linux系统中,我们可以通过控制GPIO(General Purpose Input/Output)引脚的电平状态来控制LED的亮灭。

在Linux系统中,GPIO引脚的控制需要通过文件系统来实现。

常见的GPIO文件路径为/sys/class/gpio/gpioX,其中X代表GPIO引脚的编号。

在控制LED之前,我们需要先将对应的GPIO引脚导出,即将GPIO引脚的控制权交给用户空间。

导出GPIO引脚可以通过以下命令实现:echo X > /sys/class/gpio/export其中X为GPIO引脚的编号。

执行完该命令后,在/sys/class/gpio 路径下就会出现一个gpioX文件夹,其中包含了GPIO引脚的相关信息,如方向、电平等。

接下来,我们需要设置GPIO引脚的方向,即输入或输出。

对于控制LED来说,我们需要将GPIO引脚的方向设置为输出。

可以通过以下命令实现:echo out > /sys/class/gpio/gpioX/direction其中out表示输出方向。

如果需要将GPIO引脚设置为输入方向,则将out替换为in即可。

设置完GPIO引脚的方向后,就可以通过修改GPIO引脚的电平来控制LED的亮灭了。

将GPIO引脚的电平设置为高电平,LED就会亮起来;将GPIO引脚的电平设置为低电平,LED就会熄灭。

可以通过以下命令来修改GPIO引脚的电平:echo 1 > /sys/class/gpio/gpioX/value其中1表示高电平,0表示低电平。

执行完该命令后,即可控制对应GPIO引脚的电平,从而控制LED的亮灭。

为了避免在控制LED时频繁执行上述命令,我们可以编写一个简单的脚本来实现LED的控制。

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇

Linux设备驱动程序原理及框架-内核模块入门篇内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块内核模块介绍Linux采用的是整体式的内核结构,这种结构采用的是整体式的内核结构,采用的是整体式的内核结构的内核一般不能动态的增加新的功能。

为此,的内核一般不能动态的增加新的功能。

为此,Linux提供了一种全新的机制,叫(可安装) 提供了一种全新的机制,可安装) 提供了一种全新的机制模块” )。

利用这个机制“模块”(module)。

利用这个机制,可以)。

利用这个机制,根据需要,根据需要,在不必对内核重新编译链接的条件将可安装模块动态的插入运行中的内核,下,将可安装模块动态的插入运行中的内核,成为内核的一个有机组成部分;成为内核的一个有机组成部分;或者从内核移走已经安装的模块。

正是这种机制,走已经安装的模块。

正是这种机制,使得内核的内存映像保持最小,的内存映像保持最小,但却具有很大的灵活性和可扩充性。

和可扩充性。

内核模块内核模块介绍可安装模块是可以在系统运行时动态地安装和卸载的内核软件。

严格来说,卸载的内核软件。

严格来说,这种软件的作用并不限于设备驱动,并不限于设备驱动,例如有些文件系统就是以可安装模块的形式实现的。

但是,另一方面,可安装模块的形式实现的。

但是,另一方面,它主要用来实现设备驱动程序或者与设备驱动密切相关的部分(如文件系统等)。

密切相关的部分(如文件系统等)。

课程内容内核模块介绍应用层加载模块操作过程内核如何支持可安装模块内核提供的接口及作用模块实例内核模块应用层加载模块操作过程内核引导的过程中,会识别出所有已经安装的硬件设备,内核引导的过程中,会识别出所有已经安装的硬件设备,并且创建好该系统中的硬件设备的列表树:文件系统。

且创建好该系统中的硬件设备的列表树:/sys 文件系统。

(udev 服务就是通过读取该文件系统内容来创建必要的设备文件的。

)。

如何在Linux系统中安装驱动程序

如何在Linux系统中安装驱动程序

如何在Linux系统中安装驱动程序Linux系统作为一个开源的操作系统,广泛应用于各种设备和领域。

而安装驱动程序是在Linux系统中使用外部硬件设备的关键步骤之一。

在本文中,我们将学习如何在Linux系统中安装驱动程序的方法和步骤。

1. 检查硬件设备在安装驱动程序之前,首先需要确定硬件设备的型号和制造商。

可以通过查询设备的型号或者查看设备的相关文档来获取这些信息。

这是非常重要的,因为不同的设备可能需要不同的驱动程序来正确地工作。

2. 更新系统在安装驱动程序之前,确保你的Linux系统已经是最新的状态。

可以通过在终端中运行以下命令来更新系统:```sudo apt-get updatesudo apt-get upgrade```更新系统可以确保你拥有最新的软件包和驱动程序,以获得更好的兼容性和性能。

3. 查找合适的驱动程序一般来说,大部分硬件设备的驱动程序都可以在Linux系统的软件仓库中找到。

可以通过使用包管理器(如apt、yum等)来查找并安装合适的驱动程序。

运行以下命令来搜索并安装特定的驱动程序:```sudo apt-cache search 驱动程序名称sudo apt-get install 驱动程序名称```注意替换“驱动程序名称”为具体的驱动程序名称。

安装驱动程序可能需要输入管理员密码和确认安装。

如果你无法在软件仓库中找到合适的驱动程序,可以转向设备的制造商网站或者开源社区来获取。

下载驱动程序后,根据驱动程序提供的文档和说明来安装。

4. 编译和安装驱动程序有些驱动程序可能需要手动编译和安装。

在这种情况下,你需要确保你的系统已经安装了编译工具(如GCC、make等)。

在终端中切换到驱动程序所在的目录,并按照以下步骤进行编译和安装:```./configuremakesudo make install```以上命令将分别进行配置、编译和安装驱动程序。

在进行安装之前,可能需要输入一些配置选项或者确认安装。

Linux设备驱动模型与sysfs---platform总线设备驱动

Linux设备驱动模型与sysfs---platform总线设备驱动

Linux在2.6版本引入了设备驱动模型,设备驱动模型负责统一实现和维护一些特性,诸如:热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施1.设备驱动模型基本概念设备驱动模型主要包含:类(class)、总线(bus)、设备(device)、驱动(driver),它们的本质都是内核中的几种数据结构的“实例”∙类的本质是class结构体类型,各种不同的类其实就是class的各种实例∙总线的本质是bus_type结构体类型,各种不同的总线其实就是bus_type的各种实例∙设备的本质是device结构体类型,各种不同的设备其实就是device的各种实例∙驱动的本质是device_driver结构体类型,各种不同的驱动其实就是device_driver的各种实例2.sysfs基本概念sysfs其实就是/sys目录,其主要作用就是:展示设备驱动模型中各组件的层次关系,并将各组件的本体——内核中的数据结构以文件形式呈现,方便用户层查看及操作3./sys目录结构与设备驱动模型∙/sys目录结构很好的展示了驱动设备模型,如图:∙注意:同一个设备可能在/sys中存在多个设备文件,比如一颗led的设备文件可能在/sys/bus/platform/devices/led1,同时还有一个在/sys/class/leds/led1。

虽然他们都是同一颗led的设备文件,但是他们的来源、机制、原理都是不同的,不能混为一谈4.各组件的特性与联系∙kobject:设备驱动模型各实例的最基本单元,提供一些公用型服务如:提供该实例在sysfs中的操作方法(show和store);提供在sysfs中以文件形式存在的属性,其实就是应用接口;提供各个实例的层次架构,让sysfs中弄出目录结构。

设备驱动模型中每个实例内部都会包含一个kobject∙总线、设备、驱动,这三者有着密切的联系。

在内核中,设备和驱动是分开注册的,注册设备的时候,并不需要驱动已经存在,而驱动被注册的时候,也不需要对应的设备已经被注册。

基于Linux的LED点阵应用程序设计

基于Linux的LED点阵应用程序设计
(. 1 迁西县气象局,河北 唐山 04 0 ;2石家庄铁道 大学 经 济管理学院,河北 石家庄 630 . 0 0 4 ;3石家庄铁道 大学 材 503 .
030 ) 6 00 料 学 院 ,河 北 石家 庄 0 04 ;4唐 山市 气 象 局 科 技服 务 中心 ,河 北 唐 山 503 .


卟 戋 卟 奖 。
卟 ‘


明 Байду номын сангаас
驱动程序作为可加载的模块 ,由系统管理员动态加载,使之
成为核心的一部分。 编写驱动程序的主要工作就是编 写子 函
数,并填充 feop r to s各个域 。 i e ai n l

此应用程序 是在 L D 驱动加载之后使用 ,否则无法正 E 常运行 。本设计首先将 L D 点阵驱动起来 ,通过编写测试 E

(. ax e ooo i l ueuT n sa 6 3 0 C ia2C l g f c nmis n n ae n, h i ha g i a nvr 1 n i t rlgc ra, aghn0 4 0 , hn ; .ol e E o o c d Qi M e aB e o a Ma gme tS ia u e oU i - jz n T d e sy S iah ag0 04 , hn; .ol e f a r l c n e n n ier g S iah agTea nvri , h i h ag i, h i un 50 3 C i 3 lg M t i i c d gn ei , h i u idoU ie t S ia un t jz a C e o eaS e a E n jz n sy j z 0 0 3 C i ; . tooo i l cec dT cn lg et , aghnMeerl i l ueu T n sa 3 0 , hn) 50 , hn 4 4 a Meerl c i e eh o yC ne T n sa tooo c ra , agh 0 00 C ia g aS n a n o r g aB n 6

实验二三合

实验二三合

嵌入式系统实验报告嵌入式Linux设备驱动实验学院电子与信息学院专业电子科学与技术学生姓名李泓鑫黄勇实验台号9指导教师提交日期 2015 年 4月 22日1. 了解Linux驱动程序的结构2. 初步掌握Linux驱动程序的编写方法及过程3. 掌握Linux驱动程序的加载方法。

二、实验内容1. 杂项驱动框架的编程实现;2. 具有Led控制功能的驱动编程实现;三、实验原理要写led的设备驱动,首先要了解其硬件电路的设计,才能使之正常工作。

从Tiny210的用户手册中,我们可以找到其led的设计说明。

由此,我们明确LED1~LED4分别由GPJ2_0 ~ GPJ2_3这四个io引脚来控制。

并且由Tiny210的用书手册说明,可知此GPIO输出是低电平有效。

当GPJ作为输出io口的时候,GPJ0 ~ GPJ3的输出,对应于此数据寄存器(0xE0200284)的bit0 ~ bit3. 在这四个bits上,0对应于输出低电平,会使对应的led点亮;1对应于输出高电平,会使对应的led熄灭。

杂项设备是比较简单的字符设备,主设备固定为10,次设备号由linux内核分配。

不需手动创建设备节点,注册和注销设备都比较简单。

框架中,定义了内核模块入口函数TestChar_init和出口函数TestChar_exit分别进行杂项设备的注册misc_register和注销misc_deregister。

file_operations定义了此设备的操作接口,只定义了读和写两个接口函数,分别为TestRead和TestWrite。

这两个函数中,把一个内部整型全局变量myData 和用户进行拷贝传递。

测试程序会打开TestMisc设备,然后读取该设备,再写入该设备,并进行打印显示。

1、杂项设备是比较简单的字符设备。

主设备固定为10,次设备号由linux 内核分配。

不需手动创建设备节点。

注册和注销设备都比较简单。

比较适合用于led驱动。

基于rk3568的linux驱动开发——gpio知识点

基于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驱动---非常全面而且深刻

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数据的有效性判断。

Linux内核背光backlight驱动架构文档

Linux内核背光backlight驱动架构文档
}
static void backlight_generate_event(struct backlight_device *bd, enum backlight_update_reason reason)
{ char *envp[2];
switch (reason) { case BACKLIGHT_UPDATE_SYSFS:
Backlight 子系统初始化驱动架构
本文要点: 1. input 子系统重要数据结构及关系图 2. input 子系统初始化驱动架构
背光控制原理
电源管理芯片是一个 LED 驱动器,相当于一个 LED 开关。MPU 通过操作 PWM 相关寄存器 来产生不同的波形,从而间接控制 LCD 背光灯的亮度。如下图:
backlight_class->dev_attrsdev_attrs = bl_device_attributes
backlight_class->dev_attrssuspend = backlight_suspend
backlight_class->dev_attrs =backlight_resume;
ppwwmm__bbll__ddaattaa{{}} .*pwm .period .*notify()
bbaacckklliigghhtt__pprrooppeerrttiieess{{}} .brightness .max_brightness .power .fb_blank .state
// data->pwm_id= smdk_backlight_device ->dev.platform_data->pwm_id pwm_bl_data ->pwm = pwm_request(data->pwm_id, "backlight"); // data->pwm_period_ns =smdk_backlight_device ->dev.platform_data->pwm_period_ns pb->period = data->pwm_period_ns

基于嵌入式Linux的LED驱动开发与应用

基于嵌入式Linux的LED驱动开发与应用

基于嵌入式Linux的LED驱动开发与应用摘要:简要介绍了基于嵌入式ARM处理器芯片LPC3250的嵌入式Linux的LED驱动程序的开发原理、流程以及相关主要接口硬件电路的设计。

实际运行结果表明,该设计完全达到预期效果。

关键词:嵌入式Linux;LED;硬件;驱动程序0引言随着IT技术和嵌入式技术的快速发展,嵌入式产品已经广泛应用于工业、能源、环保、通信等各个行业,显示出其强大的生命力。

Linux是当今流行的操作系统之一,具有源代码开放、内核稳定、功能强大和可裁减等优点而成为众多应用的首选。

同样嵌入式Linux也继承了Linux的诸多优点。

对Linux应用程序来说,由于设备驱动程序屏蔽了硬件的细节,其硬件设备将作为一个特殊的文件,因此应用程序可以像操作普通文件一样对硬件设备进行操作。

本设计中驱动的设备是基于NXP公司的LPC3250微处理器开发的LED信号指示灯,利用这些指示灯来显示仪器的运行状态,方便用户了解仪器的工作状况。

1LPC3250简介及接口电路设计本设计中主控芯片采用LPC3250微处理器,具有高集成度、高性能、低功耗等特点。

它采用90nm工艺和ARM926EJS内核,主频最高为208MHz,具有全系列标准外设。

其中包括带专用DMA控制器的24位LCD控制器,可支持STN和TFT面板。

充分满足本设计的需要,外部只需加入很少芯片就可实现系统功能<sup>[1]</sup>。

LPC3250共有296个管脚。

对于4个LED灯来说需要用到4个引脚,这里使用GPIO端口来设计,GPM1~GPM3作为LED灯的控制端口,另外还需要为LED提供电源,这里需要3.3V的直流电源。

接口电路设计如图1所示。

GPM0~GPM3分别与电阻、LED连接,当GPM0~GPM3置为低电平时,相应的LED灯点亮。

2驱动程序设计在嵌入式Linux操作系统下,有三类主要的设备文件类型:字符设备、块设备和网络设备<sup>[2]</sup>。

嵌入式linux小项目实例

嵌入式linux小项目实例

嵌入式linux小项目实例以下是一个嵌入式Linux小项目的实例:控制LED灯。

项目描述:实现一个嵌入式Linux系统,通过控制GPIO口来控制LED灯的开关状态。

当输入一个命令时,LED灯会根据命令的参数进行相应的操作,例如点亮、熄灭或闪烁。

所需硬件:1. 嵌入式开发板(支持Linux系统)2. LED灯3. 面包板4. 杜邦线步骤:1. 连接硬件:将LED灯的正极连接到GPIO口,将负极连接到地线,确保电路连接正确。

2. 在嵌入式开发板上安装Linux系统,并配置好相应的开发环境(交叉编译工具链、GPIO驱动等)。

3. 创建一个C语言源文件,该文件包含LED灯的控制代码。

在代码中,需要通过GPIO驱动控制LED灯的开关状态。

4. 使用交叉编译工具链编译源文件生成可执行文件。

5. 将可执行文件拷贝到嵌入式开发板上。

6. 在嵌入式开发板上打开终端,运行可执行文件,通过命令行输入参数来控制LED灯的开关状态。

示例代码:```c#include <stdio.h>#include <fcntl.h>#include <unistd.h>#define LED_GPIO_PIN 17int main(int argc, char *argv[]) {int fd;char buf[2];fd = open("/sys/class/gpio/export", O_WRONLY);write(fd, "17", 2);close(fd);fd = open("/sys/class/gpio/gpio17/direction", O_WRONLY); write(fd, "out", 3);close(fd);fd = open("/sys/class/gpio/gpio17/value", O_WRONLY);if (strcmp(argv[1], "on") == 0) {write(fd, "1", 1);printf("LED turned on.\n");} else if (strcmp(argv[1], "off") == 0) {write(fd, "0", 1);printf("LED turned off.\n");} else if (strcmp(argv[1], "blink") == 0) {int i;for (i = 0; i < 10; i++) {write(fd, "1", 1);sleep(1);write(fd, "0", 1);sleep(1);}printf("LED blinked.\n");} else {printf("Invalid command.\n");}close(fd);fd = open("/sys/class/gpio/unexport", O_WRONLY);write(fd, "17", 2);close(fd);return 0;}```编译和运行:1. 使用交叉编译工具链编译源文件:```$ arm-linux-gnueabi-gcc -o led_control led_control.c```2. 将可执行文件拷贝到嵌入式开发板上。

Tiny-S3C6410_Linux下LED灯驱动移植过程

Tiny-S3C6410_Linux下LED灯驱动移植过程

UT-S3C6410 ARM11 Linux 下的LED驱动一、实验环境操作系统:fedora13交叉编译环境:arm-Linux-gcc 或以上,6410板子内核源码路径在:忘了,需要厂家给的内核源代码硬件平台:S3C6410开发板(其他类型的开发板也可以注意配置GPIO)注:交叉编译环境一定要装好,一般的开发板给的配套资料中都会有,安装过程也都有详细的过程,如果没有,亲,你只有自己解决了。

也可以联系我(****************),泪奔支持你们。

二、实验原理控制LED是最简单的一件事情,就像学C语言时候写的“hello world”程序一样,是一个入门的程序。

首先来了解一下相关的硬件知识:UT-S3C6410LED原理图UT-S3C6410LED外部引脚图从上面的原理图可以得知,LED与CPU引脚的连接方法如下,高电平点亮。

LED1 -GPM0LED2 -GPM1LED3 -GPM2LED4 -GPM3从数据手册可以找到相应的控制方法。

这里我们以LED1为例,介绍一下LED1的操作方法,其他的类似,请大家自行分析。

通过上面可以得知,需要先将GPM0设置为输出方式。

将寄存器GPMCON低四位配置成0001。

然后将GPMDAT寄存器的第0位置1灯亮,置LED0灯亮,开发板上有四个LED所以要对GPMDAT的低四位进行操作,就可以实现对灯的亮灭操作了。

三、实验步骤1、编写驱动程序mini6410_leds.c#include <linux/miscdevice.h>#include <linux/delay.h>#include <asm/irq.h>//#include <mach/regs-gpio.h>#include <mach/hardware.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/fs.h>#include <linux/types.h>#include <linux/delay.h>#include <linux/moduleparam.h>#include <linux/slab.h>#include <linux/errno.h>#include <linux/ioctl.h>#include <linux/cdev.h>#include <linux/string.h>#include <linux/list.h>#include <linux/pci.h>#include <asm/uaccess.h>#include <asm/atomic.h>#include <asm/unistd.h>#include <mach/map.h>#include <mach/regs-clock.h>#include <mach/regs-gpio.h>#include <plat/gpio-cfg.h>#include <mach/gpio-bank-e.h>#include <mach/gpio-bank-k.h>#define DEVICE_NAME "leds"static long sbc2440_leds_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {switch(cmd) {unsigned tmp;case 0:case 1:if (arg > 4) {return -EINVAL;}tmp = readl(S3C64XX_GPKDAT);tmp &= ~(1 << (4 + arg));tmp |= ( (!cmd) << (4 + arg) );writel(tmp, S3C64XX_GPKDAT);//printk (DEVICE_NAME": %d %d\n", arg, cmd); return 0;default:return -EINVAL;}}static struct file_operations dev_fops = {.owner = THIS_MODULE,.unlocked_ioctl = sbc2440_leds_ioctl,};static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR,.name = DEVICE_NAME,.fops = &dev_fops,};static int __init dev_init(void){int ret;{unsigned tmp;tmp = readl(S3C64XX_GPKCON);tmp = (tmp & ~(0xffffU<<16))|(0x1111U<<16); writel(tmp, S3C64XX_GPKCON);tmp = readl(S3C64XX_GPKDAT);tmp |= (0xF << 4);writel(tmp, S3C64XX_GPKDAT);}ret = misc_register(&misc);printk (DEVICE_NAME"\tinitialized\n");return ret;}static void __exit dev_exit(void){misc_deregister(&misc);}module_init(dev_init);module_exit(dev_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("FriendlyARM Inc.");(1)把Hello,Module 加入内核代码树,并编译一般编译2.6 版本的驱动模块需要把驱动代码加入内核代码树,并做相应的配置,如下步骤(注意:实际上以下步骤均已经做好,你只需要打开检查一下直接编译就可以了):Step1:编辑配置文件Kconfig,加入驱动选项,使之在make menuconfig 的时候出现打开linux-2.6.38/drivers/char/Kconfig 文件,添加如图所示:#====================cgf add===================================== config MINI6410_LEDStristate "LED Support for Mini6410 GPIO LEDs"depends on CPU_S3C6410default yhelpThis option enables support for LEDs connected to GPIO lineson Mini6410 boards.#================================================================== 保存退出,这时在linux-2.6.38 目录位置运行一下make menuconfig 就可以在DeviceDrivers Character devices 菜单中看到刚才所添加的选项了,按下空格键将会选择为<M>,此意为要把该选项编译为模块方式;再按下空格会变为<*>,意为要把该选项编译到内核中,在此我们选择<M>,如图,如果没有出现,请检查你是否已经装载了缺省的内核配置文件,(2)Makefile文件Step2:通过上一步,我们虽然可以在配置内核的时候进行选择,但实际上此时执行编译内核还是不能把mini6410_leds.c编译进去的,还需要在Makefile 中把内核配置选项和真正的源代码联系起来,打开linux-2.6.38-cgf/drivers/char/Makefile,obj-$(CONFIG_MINI6410_LEDS) += mini6410_leds.o添加并保存退出Step3:这时回到linux-2.6.38 源代码根目录位置,执行make modules,就可以生成我们所需要的内核模块文件drivers/char/mini6410_leds.ko 了,注意:执行make modules 之前,必须先执行make zImage,只需一次就可以了。

LED_KEY_Driver

LED_KEY_Driver

LED设备驱动
static int __init dev_init(void) { int ret; int i; for (i = 0; i < 4; i++) { s3c2410_gpio_cfgpin(led_table[i], led_cfg_ table[i]); s3c2410_gpio_setpin(led_table[i], 0); } ret = misc_register(&misc); printk (DEVICE_NAME"\tinitialized\n"); return ret;
LED设备驱动
static int sbc2440_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { case 0: case 1: if (arg > 4) { return -EINVAL; } s3c2410_gpio_setpin(led_table[arg], !cmd); return 0; default: return -EINVAL; } }
KEY 设备驱动
int main(void) { int buttons_fd; char buttons[6] = {'0', '0', '0', '0', '0', '0'}; /*定义按键值变量*/ buttons_fd = open("/dev/buttons", 0); /*打开按键设备/dev/buttons*/ if (buttons_fd < 0) { perror("open device buttons"); /*打开失败则退出*/ exit(1); } /*永读按键并打印键值和状态*/

Linux驱动之LCD驱动编写

Linux驱动之LCD驱动编写

Linux驱动之LCD驱动编写在这篇博客中已经分析了编写LCD驱动的步骤,接下来就按照这个步骤来字尝试字节编写LCD驱动。

⽤的LCD屏幕为tft 屏,每个像素点为16bit。

对应与红绿蓝分别为565。

1、分配⼀个fb_info结构2、设置fb_info结构3、硬件相关的操作,配置LCD时钟、配置IO端⼝、配置LCD寄存器。

4、最终注册fbinfo结构到registered_fb数组要理解LCD的⼯作原理,需要了解LCD的时钟,在TFT的LCD中有如下的时钟。

这个⼏个时钟数据在配置LCD寄存器时都说需要设置的。

1、VCLK:两个像素之间的时钟,即两个像素隔多长时间才能显⽰下⼀个像素2、HSYNC:⽔平同步时钟,即第⼀⾏像素点显⽰完成之后隔多长时间才能开始下⼀⾏的显⽰3、VSYNC:垂直⽅向的同步时钟,也叫帧同步信号,即⼀帧数据显⽰完成之后(⼀帧数据表⽰⼀个屏幕显⽰完成,即⼀个显存的数据全部取完),过多长下⼀帧数据才开始显⽰本节需要⽤到的函数:void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp); //分配DMA缓存区给显存//返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表⽰分配失败,则需要使⽤dma_free_writecombine()释放内存,避免内存泄漏//参数如下://*dev:指针,这⾥填0,表⽰这个申请的缓冲区⾥没有内容//size:分配的地址⼤⼩(字节单位)//*handle:申请到的物理起始地址//gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常⽤标志如下://GFP_ATOMIC ⽤来从中断处理和进程上下⽂之外的其他代码中分配内存. 从不睡眠.//GFP_KERNEL 内核内存的正常分配. 可能睡眠.//GFP_USER ⽤来为⽤户空间页来分配内存; 它可能睡眠.分配⼀段DMA缓存区,分配出来的内存会禁⽌cache缓存(因为DMA传输不需要CPU)它和 dma_alloc_coherent ()函数相似,不过 dma_alloc_coherent ()函数是分配出来的内存会禁⽌cache缓存以及禁⽌写⼊缓冲区dma_free_writecombine(dev,size,cpu_addr,handle); //释放缓存//cpu_addr:虚拟地址,//handle:物理地址释放DMA缓冲区, dev和size参数和上⾯的⼀样struct fb_info *framebuffer_alloc(size_t size, struct device *dev); //申请⼀个fb_info结构体,//size:额外的内存,//*dev:指针, 这⾥填0,表⽰这个申请的结构体⾥没有内容int register_framebuffer(struct fb_info *fb_info);//向内核中注册fb_info结构体,若内存不够,注册失败会返回负数int unregister_framebuffer(struct fb_info *fb_info) ;//注销内核中fb_info结构体本节需要⽤到的结构体:fb_info结构体如下:struct fb_info {... ...struct fb_var_screeninfo var; //可变的参数struct fb_fix_screeninfo fix; //固定的参数... ...struct fb_ops *fbops; //操作函数... ...char __iomem *screen_base; //显存虚拟起始地址unsigned long screen_size; //显存虚拟地址长度void *pseudo_palette;//假的16⾊调⾊板,⾥⾯存放了16⾊的数据,可以通过8bpp数据来找到调⾊板⾥⾯的16⾊颜⾊索引值,模拟出16⾊颜⾊来,节省内存,不需要的话就指向⼀个不⽤的数组即可 ... ...};其中操作函数fb_info-> fbops 结构体写法如下:static struct fb_ops s3c_lcdfb_ops = {.owner = THIS_MODULE,.fb_setcolreg = my_lcdfb_setcolreg,//设置调⾊板fb_info-> pseudo_palette,⾃⼰构造该函数.fb_fillrect = cfb_fillrect, //填充矩形,⽤/drivers/video/ cfbfillrect.c⾥的函数即可.fb_copyarea = cfb_copyarea, //复制数据, ⽤/drivers/video/cfbcopyarea.c⾥的函数即可.fb_imageblit = cfb_imageblit, //绘画图形, ⽤/drivers/video/imageblit.c⾥的函数即可};固定的参数fb_info-> fix 结构体如下:struct fb_fix_screeninfo {char id[16]; //id名字unsigned long smem_start; //framebuffer物理起始地址__u32 smem_len; //framebuffer长度,字节为单位__u32 type; //lcd类型,默认值0即可__u32 type_aux; //附加类型,为0__u32 visual; //画⾯设置,常⽤参数如下// FB_VISUAL_MONO01 0 单⾊,0:⽩⾊,1:⿊⾊// FB_VISUAL_MONO10 1 单⾊,1:⽩⾊,0:⿊⾊// FB_VISUAL_TRUECOLOR 2 真彩(TFT:真彩)// FB_VISUAL_PSEUDOCOLOR 3 伪彩// FB_VISUAL_DIRECTCOLOR 4 直彩 __u16 xpanstep; /*如果没有硬件panning就赋值为0 */ __u16 ypanstep; /*如果没有硬件panning就赋值为0 */ __u16 ywrapstep; /*如果没有硬件ywrap就赋值为0 */ __u32 line_length; /*⼀⾏的字节数 ,例:(RGB565)240*320,那么这⾥就等于240*16/8 */ /*以下成员都可以不需要*/ unsigned long mmio_start; /*内存映射IO的起始地址,⽤于应⽤层直接访问寄存器,可以不需要*/__u32 mmio_len; /* 内存映射IO的长度,可以不需要*/__u32 accel;__u16 reserved[3];};可变的参数fb_info-> var 结构体如下:structfb_var_screeninfo{ __u32xres; /*可见屏幕⼀⾏有多少个像素点*/__u32 yres; /*可见屏幕⼀列有多少个像素点*/__u32 xres_virtual; /*虚拟屏幕⼀⾏有多少个像素点 */__u32 yres_virtual; /*虚拟屏幕⼀列有多少个像素点*/__u32 xoffset; /*虚拟到可见屏幕之间的⾏偏移,若可见和虚拟的分辨率⼀样,就直接设为0*/ __u32 yoffset; /*虚拟到可见屏幕之间的列偏移*/__u32 bits_per_pixel; /*每个像素的位数即BPP,⽐如:RGB565则填⼊16*/__u32 grayscale; /*⾮0时,指的是灰度,真彩直接填0即可*/struct fb_bitfield red; //fb缓存的R位域, fb_bitfield结构体成员如下://__u32 offset; 区域偏移值,⽐如RGB565中的R,就在第11位//__u32 length; 区域长度,⽐如RGB565的R,共有5位//__u32 msb_right; msb_right ==0,表⽰数据左边最⼤, msb_right!=0,表⽰数据右边最⼤struct fb_bitfield green; /*fb缓存的G位域*/struct fb_bitfield blue; /*fb缓存的B位域*/ /*以下参数都可以不填,默认为0*/struct fb_bitfield transp; /*透明度,不需要填0即可*/__u32nonstd; /* != 0表⽰⾮标准像素格式*/__u32 activate; /*设为0即可*/__u32height; /*外设⾼度(单位mm),⼀般不需要填*/__u32width; /*外设宽度(单位mm),⼀般不需要填*/__u32 accel_flags; /*过时的参数,不需要填*//* 除了pixclock本⾝外,其他的都以像素时钟为单位*/__u32pixclock; /*像素时钟(⽪秒)*/__u32 left_margin; /*⾏切换,从同步到绘图之间的延迟*/__u32right_margin; /*⾏切换,从绘图到同步之间的延迟*/__u32upper_margin; /*帧切换,从同步到绘图之间的延迟*/__u32lower_margin; /*帧切换,从绘图到同步之间的延迟*/__u32hsync_len; /*⽔平同步的长度*/__u32 vsync_len; /*垂直同步的长度*/__u32 sync;__u32 vmode;__u32 rotate;__u32reserved[5]; /*保留*/}1.写驱动程序:(驱动设置:参考⾃带的LCD平台驱动drivers/video/s3c2410fb.c )1.1 步骤如下:在驱动init⼊⼝函数中:1)分配⼀个fb_info结构体2)设置fb_info 2.1)设置固定的参数fb_info-> fix 2.2) 设置可变的参数fb_info-> var 2.3) 设置操作函数fb_info-> fbops 2.4) 设置fb_info 其它的成员3)设置硬件相关的操作 3.1)配置LCD引脚 3.2)根据LCD⼿册设置LCD控制器 3.3)分配显存(framebuffer),把地址告诉LCD控制器和fb_info4)开启LCD,并注册fb_info: register_framebuffer() 4.1) 直接在init函数中开启LCD(后⾯讲到电源管理,再来优化) 控制LCDCON5允许PWREN信号, 然后控制LCDCON1输出PWREN信号, 输出GPB0⾼电平来开背光, 4.2) 注册fb_info在驱动exit出⼝函数中:1)卸载内核中的fb_info2) 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址3)释放DMA缓存地址dma_free_writecombine()4)释放注册的fb_info1.2 具体代码如下:#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <asm/io.h> //含有iomap函数iounmap函数#include <asm/uaccess.h>//含有copy_from_user函数#include <linux/device.h>//含有类相关的处理函数#include <linux/fb.h> //含有fb_info结构体定义//#include <asm/dma-mapping.h> //含有dma_free_writecombine宏定义#include <linux/dma-mapping.h> //含有dma_free_writecombine宏定义#include <linux/platform_device.h>//含有平台设备总线模型相关变量#include <linux/mm.h>#include <linux/slab.h>//#include <linux/module.h>//#include <linux/kernel.h>//#include <linux/errno.h>//#include <linux/string.h>//#include <linux/mm.h>//#include <linux/slab.h>//#include <linux/delay.h>//#include <linux/fb.h>//#include <linux/init.h>//#include <linux/dma-mapping.h>//#include <linux/interrupt.h>//#include <linux/workqueue.h>//#include <linux/wait.h>//#include <linux/platform_device.h>//#include <linux/clk.h>//#include <asm/io.h>//#include <asm/uaccess.h>//#include <asm/div64.h>//#include <asm/mach/map.h>//#include <asm/arch/regs-lcd.h>//#include <asm/arch/regs-gpio.h>//#include <asm/arch/fb.h>/*lcd控制寄存器放在⼀个结构体⾥⾯*/struct lcd_regs {unsigned long lcdcon1;unsigned long lcdcon2;unsigned long lcdcon3;unsigned long lcdcon4;unsigned long lcdcon5;unsigned long lcdsaddr1;unsigned long lcdsaddr2;unsigned long lcdsaddr3;unsigned long redlut;unsigned long greenlut;unsigned long bluelut;unsigned long reserved[9];unsigned long dithmode;unsigned long tpal;unsigned long lcdintpnd;unsigned long lcdsrcpnd;unsigned long lcdintmsk;unsigned long lpcsel;};static struct fb_info *s3c_mylcdfb_info;//fb_info结构体static volatile unsigned long *gpbcon;//GPB0⽤于lcd背光的控制static volatile unsigned long *gpbdat;//GPB0⽤于lcd背光的控制static volatile unsigned long *gpccon;static volatile unsigned long *gpdcon;static volatile unsigned long *gpgcon;//GPG4⽤于lcd电源static volatile struct lcd_regs* lcd_regs;//lcd寄存器static u32 pseudo_palette[16]; //调⾊板内存/* from pxafb.c */static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf){chan &= 0xffff; //取出16bit的数据chan >>= 16 - bf->length; //return chan << bf->offset;}static int s3c_mylcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info){unsigned int val;if (regno > 16)return1;/* ⽤red,green,blue三原⾊构造出val */val = chan_to_field(red, &info->var.red);val |= chan_to_field(green, &info->var.green);val |= chan_to_field(blue, &info->var.blue);//((u32 *)(info->pseudo_palette))[regno] = val;pseudo_palette[regno] = val;return0;}static struct fb_ops s3c_mylcdfb_ops = { //操作函数结构体.owner = THIS_MODULE,.fb_setcolreg = s3c_mylcdfb_setcolreg,//待会设置,这个是调⾊板,如果使⽤⼩于16bit的像素需要⽤到 .fb_fillrect = cfb_fillrect,.fb_copyarea = cfb_copyarea,.fb_imageblit = cfb_imageblit,};static int lcd_drv_init(void){/*1、分配⼀个fb_info*/s3c_mylcdfb_info = framebuffer_alloc(0,NULL);//size为额外分配的⼤⼩,这⾥不需要,所以设为0if(s3c_mylcdfb_info==NULL){printk("unframebuffer_alloc\n");return1;}/*2、设置*//*2.1 设置固定的参数*/strcpy(s3c_mylcdfb_info->fix.id, "mylcd");//名字//s3c_mylcdfb_info->fix.smem_start = ;//显存的物理起始地址,后⾯设置s3c_mylcdfb_info->fix.smem_len = 480*272*16/8;//单位为字节,每个像素点占⽤16bit :565,显存的⼤⼩ s3c_mylcdfb_info->fix.type = FB_TYPE_PACKED_PIXELS;//LCD类型,填充像素的类型 tft//s3c_mylcdfb_info->fix.type_aux= //附加的LCD类型,不需要设置s3c_mylcdfb_info->fix.visual = FB_VISUAL_TRUECOLOR;//视觉类型,选择真彩⾊s3c_mylcdfb_info->fix.line_length = 480*16/8; //⼀⾏的长度,单位为字节// s3c_mylcdfb_info->fix.mmio_start = //控制lcd的寄存器的物理地址// s3c_mylcdfb_info->fix.mmio_len = //控制lcd的寄存器的⼤⼩/*2.2 设置可变的参数*/s3c_mylcdfb_info->var.xres = 480;//x⽅向的分辨率s3c_mylcdfb_info->var.yres = 272;//y⽅向的分辨率s3c_mylcdfb_info->var.xres_virtual = 480;//x⽅向的虚拟分辨率s3c_mylcdfb_info->var.yres_virtual = 272;//y⽅向的虚拟分辨率s3c_mylcdfb_info->var.bits_per_pixel = 16;//每个像素的⼤⼩,单位为bits3c_mylcdfb_info->var.grayscale = 0;//灰度值s3c_mylcdfb_info->var.red.length = 5;//红⾊像素占⽤的长度,单位bits3c_mylcdfb_info->var.green.length = 6;//绿⾊像素占⽤的长度,单位bits3c_mylcdfb_info->var.blue.length = 5;//蓝⾊像素占⽤的长度,单位bits3c_mylcdfb_info->var.red.offset= 11;//红⾊像素在16bit中的偏移值s3c_mylcdfb_info->var.green.offset= 6;//绿⾊像素在16bit中的偏移值s3c_mylcdfb_info->var.blue.offset=0;//蓝⾊像素在16bit中的偏移值s3c_mylcdfb_info->var.red.msb_right= 0;//低位在前还是⾼位在前,⼀般⾼位在前,也就是⼩端模式s3c_mylcdfb_info->var.green.msb_right= 0;s3c_mylcdfb_info->var.blue.msb_right=0;s3c_mylcdfb_info->var.activate = FB_ACTIVATE_NOW;//使⽤默认参数,显存⽴刻⽣效/*2.3 设置操作函数*/s3c_mylcdfb_info->fbops = &s3c_mylcdfb_ops;/*2.4 其它的⼀些设置 */s3c_mylcdfb_info->pseudo_palette = pseudo_palette;//调⾊板的地址//s3c_mylcdfb_info->screen_base = ;//显存的虚拟基地址s3c_mylcdfb_info->screen_size = 480*272*16/8;//单位为字节,每个像素点占⽤16bit :565,显存的⼤⼩/*3、硬件相关的操作 *//*3.1、配置GPIO⽤于LCD*/gpbcon = ioremap(0x56000010, 8);//将实际的寄存器地址转换为虚拟地址gpccon = ioremap(0x56000020 , 4);gpdcon = ioremap(0x56000030 , 4);gpgcon = ioremap(0x56000060 , 4);gpbdat = gpbcon + 1;*gpccon = 0xaaaaaaaa; /* GPIO管脚⽤于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */*gpdcon = 0xaaaaaaaa; /* GPIO管脚⽤于VD[23:8] */*gpbcon &= ~(3); /* GPB0设置为输出引脚 */*gpbcon |= 1;*gpbdat &= ~1; /* 输出低电平关闭LCD背光 */*gpgcon |= (3<<8); /* GPG4⽤作LCD_PWREN 电源*//*3.2、根据LCD⼿册设置LCD控制器,⽐如VCLK的频率等 */lcd_regs = ioremap(0X4D000000 , sizeof(struct lcd_regs));/** bit[17:8] : VCLK = HCLK / [(CLKVAL+1) x 2]* 10M = 100M/[(CLKVAL+1) x 2]* CLKVAL = 4** bit[6:5] :PNRMODE = 11显⽰模式,选择TFT模式** bit[4:1] :BPPMODE = 1100;像素=16bit 565** bit[0] :ENVID = 0;先关闭LCD控制器*/lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0c<<1);///** [31:24] : VBPD = 帧同步信号发出后,过多长时间开始显⽰数据,单位为⾏,理解为1⾏的时间* 看LCD⼿册tvb = VBPD + 1 = 2;所以VBPD = 1** [23:14]:LINEVAL + 1= 272;,所以LINEVAL = 271;垂直⽅向尺⼨,多少⾏** [13:6]:VFPD = ⼀帧的数据传输完成之后,过多长时间开始下⼀帧数据的帧同步信号,单位为⾏,理解为1⾏的时间 * 看LCD⼿册tvf = VFPD + 1 = 2;所以VFPD = 1** [5:0]:VSPW = 帧同步信号的脉冲宽度,单位为⾏* 看LCD⼿册tvp = VSPW + 1 =10;所以VSPW = 9*/lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9<<0);/** [25:19]:HBPD = ⾏同步信号发出后,经过多少个VCLK,才发送像素的数据,单位为VCLK* 看LCD⼿册thb = HBPD + 1 = 2;所以HBPD=1** [18:8]:HOZVAL + 1 = 480,所以 HOZVAL = 479;⽔平⽅向尺⼨,多少列**[7:0]:HFPD = ⼀⾏的像素数据传输完成之后,经过多长时间,才能发送下⼀个⾏同步信号,单位为VCLK*看LCD⼿册thf = HFPD + 1 = 2;所以HFPD = 1;*/lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);/** [7:0]:HSPW = ⾏同步信号的脉冲宽度,单位为VCLK* 看LCD⼿册thp = HSPW + 1 = 41;所以HSPW = 40**/lcd_regs->lcdcon4 = (40<<0);/** [11] :FRM565 = 1;16位模式的格式 R:G:B = 5:6:5* [10] :INVVCLK = 0;VCLK在哪个边沿取数据 = 0表⽰下降沿取数据* [9] :INVVLINE = 1;⾏同步信号是否需要反转= 1需要反转* [8] :INVVFRAME = 1;帧同步信号是否需要反转= 1需要反转* [7] :INVVD = 0; 数据是否需要反转* [6] :INVVDEN = 0; 数据使能信号是否需要反转* [5] :INVPWREN = 0;电源使能信号是否需要反转* [4] :INVLEND = 0;⾏结束信号是否需要反转* [3] :PWREN = 0;电源使能信号,先不使能* [2] :ENLEND = 1;//⾏结束信号先使能* [1:0] :BSWP 、HWSWP = 0 1;字节内部不需要交换,字节间需要交换*/lcd_regs->lcdcon5= (1<<11) | (3<<8) | (1<<2) | (1<<0);/*3.3、显存和调⾊板设置 *//**利⽤dma_alloc_writecombine分配⼀块连续的显存*/s3c_mylcdfb_info->screen_base = dma_alloc_writecombine(NULL,s3c_mylcdfb_info->screen_size,(&(s3c_mylcdfb_info->fix.smem_start)),GFP_KERNEL);//返回虚拟地址if(s3c_mylcdfb_info->screen_base==NULL) //如果显存分配失败,直接返回{printk("undma_alloc_writecombine\n");return1;}/**将显存的地址告诉LCD控制器(物理地址)*/lcd_regs->lcdsaddr1 = (s3c_mylcdfb_info->fix.smem_start >> 1) & (~(3<<30));//起始地址lcd_regs->lcdsaddr2 = ((s3c_mylcdfb_info->fix.smem_start + s3c_mylcdfb_info->screen_size) >> 1) & 0x1fffff;//结束地址lcd_regs->lcdsaddr3 = (480*16/16); /* ⼀⾏的长度(单位: 2字节) *///s3c_lcd->fix.smem_start = xxx; /* 显存的物理地址 *//* 启动LCD */lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本⾝电源 */*gpbdat |= 1; /* 输出⾼电平, 使能背光 *//*4、注册LCD*/register_framebuffer(s3c_mylcdfb_info);printk("register_framebuffer\n");return0;}static void lcd_drv_exit(void){unregister_framebuffer(s3c_mylcdfb_info);lcd_regs->lcdcon1 &= ~(1<<0); /* 关闭LCD本⾝ */*gpbdat &= ~1; /* 关闭背光 */dma_free_writecombine(NULL, s3c_mylcdfb_info->fix.smem_len, s3c_mylcdfb_info->screen_base, s3c_mylcdfb_info->fix.smem_start);iounmap(lcd_regs);iounmap(gpbcon);iounmap(gpccon);iounmap(gpdcon);iounmap(gpgcon);framebuffer_release(s3c_mylcdfb_info);}module_init(lcd_drv_init);module_exit(lcd_drv_exit);MODULE_LICENSE("GPL");2.重新编译内核,去掉默认的LCDmake menuconfig ,进⼊menu菜单重新设置内核参数:进⼊Device Drivers-> Graphics support:<M> S3C2410 LCD framebuffer support //将⾃带的LCD驱动设为模块, 不编进内核中然后make uImage 编译内核make modules 编译模块为什么要编译模块?因为LCD驱动相关的⽂件也没有编进内核,⽽fb_ops⾥的成员fb_fillrect(), fb_copyarea(), fb_imageblit()⽤的都是drivers/video下⾯的3个⽂件,所以需要这3个的.ko模块,如下图所⽰:3.挂载驱动将编译好的LCD驱动模块和drivers/video⾥的3个.ko模块放⼊nfs⽂件系统⽬录中然后烧写内核, 先装载3个/drivers/video下编译好的模块,再来装载LCD驱动模块挂载LCD驱动后, 如下图,可以通过 ls -l /dev/fb* 命令查看已挂载的LCD设备节点:4.测试运⾏测试有两种:echo hello> /dev/tty1 // LCD上便显⽰hello字段cat Makefile>/dev/tty1 // LCD上便显⽰Makeflie⽂件的内容4.1使⽤上节的键盘驱动在LCD终端打印命令⾏vi /etc/inittab //修改inittab, inittab:配置⽂件,⽤于启动init进程时,读取inittab添加->tty1::askfirst:-/bin/sh //将sh进程(命令⾏)输出到tty1⾥,也就是使LCD输出信息然后重启,insmod装载3个/drivers/video下编译好的模块,再来insmod装载LCD驱动模块,tty1设备便有了,就能看到提⽰信息:如下图,我们insmod上⼀节的键盘驱动后,按下enter键,便能在LCD终端上操作linux了从上图可以看到按下enter键,它就启动了⼀个进程号772的-sh进程,如下图发现这个-sh的描述符都指向了tty1:以上内容转载⾃。

基于linux的跑马灯应用程序的编写

基于linux的跑马灯应用程序的编写

《嵌入式系统设计》课程论文学生姓名:学号:系部:物理系专业:电子信息科学与技术班级:10-4完成日期:2013 年12 月24 日基于linux的跑马灯应用程序的编写一、设计目的1、学习linux下跑马灯应用程序的编写方法。

二、设计内容1、编写跑马灯应用程序的程序,实现LED灯的轮流显示。

三、设计原理应用程序名为LED.c,详细代码见实验程序/linux/user_led/led.c。

四、设计步骤1、本实验使用实验教学系统,在进行本实验时,LCD电源开关应处在关闭状态。

2、在PC串口和实验箱的CPU之间,连接串口直连电缆,在PC网口和实验箱的CPU网口之间,连接网口交叉电缆。

3、在linux系统下,把 led放到共享的文件夹内,在linux系统下,把 kbd 放到共享的文件夹内,如/home/nfs,nfs为一个新建的文件夹,在终端下使用命令 chmod777/home/nfs改变/home/nfs文件夹的属性为共享,在终端下输入minicom-s,配置minicom为波特率为115200,无奇偶位校验,8bit。

之后,在minicom下,给系统上电,系统正常起来后,利用ifconfig eth0 xxx.xxx.xxx.xxx 来改变实验系统的IP地址,让该地址的前三段和主机的前三段一致,最后的一段,可以选择和主机不重复的小于255的任意值。

4、挂载成功后,在终端下,键入执行命令./led,则在终端中首先输出LED round show in the EL_ARM860,please enter the number 1 or 2 or 3 or 4 and L or R then Enter!以及Such as 1L or 2L or 3L or 4L or 1R or 2R or 3R or 4R,then Enter !等输出。

其中1,2,3,4,是选择led闪烁的时间间隔,数值越小,闪烁间隔越短。

Linux设备驱动程序DF

Linux设备驱动程序DF
可以申请所有的端口地址 • 必须以root运行 • 用 “gcc -02 –o xxx.elf xxx.c” 编译 • outb(value, port); inb(port); // 8-bit • outw(value, port); inw(port); // 16-bit • 访问时间大约1us
Linux设备驱动程序
04/05/2006 应忍冬
内容
• • • • • • • • • 设备分类 设备驱动程序的框架 字符型设备 网络设备 文件系统
– User Spacuffer例子和使用 Debug原理和Debug方法 常用设备/fb/ram/loopback/zero
设备驱动程序内访问设备地址
• 设备驱动程序可以通过指针访问设备地址 • 设备驱动程序接触到的还是虚拟地址,但 对于外界设备有固定的设备地址映射(设 备的地址在移植Linux时候确定) 设备驱动程序
虚拟地址映射
设备地址映射
设备驱动程序
虚拟地址映射
设备地址映射
物理内存地址空间
设备地址空间
直接访问IO端口 vs 设备驱动程序
设备驱动程序的任务
• • • • 设备初始化 硬件操作和管理 外部硬件和内核空间的数据传递 内核空间和用户空间的数据传递
设备驱动程序的功能
用 户 空 间 内 核 空 间
用户程序
程序
用户态程序 vs 内核态程序
用户程序 • 权限受限 • 虚拟运行环境
–逻辑地址 –关键资源访问受监管
内核程序 • 最高权限 • 实际的运行环境
生成o文件
设备装载和设备文件建立
• chmod +x /tmp/LED.o • /sbin/insmod -f ./LED.o • cat /proc/devices得到装入内核的主 设备号 • mknod /dev/Lamp c Num1 Num2 Num1为主设备号 Num2为次设备号 强制安装,忽略版本检查

C语言嵌入式Linux开发驱动和系统调用

C语言嵌入式Linux开发驱动和系统调用

C语言嵌入式Linux开发驱动和系统调用在嵌入式系统领域中,C语言是最常用的编程语言之一。

它具有高效性、可移植性和灵活性,使得它成为开发嵌入式Linux驱动和系统调用的理想选择。

本文将详细介绍C语言在嵌入式Linux开发中的应用,包括驱动开发和系统调用的实现。

一、驱动开发1.1 驱动的定义和作用驱动是连接硬件和操作系统的关键组件,它允许操作系统与具体的硬件设备进行通信。

驱动的主要作用是提供对硬件设备的控制、管理和数据传输。

在嵌入式Linux系统中,驱动的开发需要使用C语言来编写。

1.2 驱动的开发流程驱动的开发可以分为以下几个步骤:1)了解硬件设备:首先要对驱动所涉及的硬件设备有一定的了解,包括设备的主要功能和寄存器的操作方式等。

2)驱动代码编写:使用C语言编写驱动代码,根据硬件设备的数据发送和接收过程设计函数和数据结构。

3)编译和链接:将驱动代码编译成可执行文件,并将其链接到操作系统的内核中。

4)加载和卸载:通过调用命令加载和卸载驱动,使其生效或失效。

5)测试和调试:进行驱动功能的测试和调试工作,确保驱动的正确性和稳定性。

1.3 驱动示例:LED驱动以一个简单的LED驱动为例,说明驱动的开发过程:1)定义LED设备的数据结构:创建一个结构体来表示LED设备的相关信息,例如设备的名称、设备的状态等。

2)实现LED控制函数:编写LED控制函数,通过操作硬件寄存器来控制LED的开关。

3)注册驱动:将驱动注册到操作系统的驱动框架中,使其与操作系统进行通信。

4)加载和卸载驱动:通过命令加载和卸载驱动,对LED进行控制。

二、系统调用2.1 系统调用的定义和作用系统调用是用户程序与操作系统之间的接口,它允许用户程序访问操作系统提供的服务和资源。

系统调用的主要作用是提供对底层硬件和操作系统功能的访问。

2.2 系统调用的分类系统调用可以分为以下几类:1)进程控制:如创建、终止和等待进程等。

2)文件操作:如打开、读取和关闭文件等。

linux 下跟踪led灯是否亮灭的代码

linux 下跟踪led灯是否亮灭的代码

linux 下跟踪led灯是否亮灭的代码在Linux系统中,GPIO(General Purpose Input/Output)是一种通用的输入/输出接口,可以用于控制和读取外部设备的状态。

LED 灯作为一种常见的外部设备,可以通过GPIO接口进行控制。

下面将详细介绍如何使用GPIO接口来跟踪LED灯的亮灭状态。

我们需要确保系统中已经加载了GPIO相关的内核模块。

可以通过以下命令来检查:```lsmod | grep gpio```如果没有输出结果,说明系统中未加载GPIO相关的内核模块。

可以通过以下命令来加载gpio模块:```sudo modprobe gpio```接下来,我们需要确定系统中LED灯连接到的GPIO引脚号。

可以通过查看系统的设备树来获取GPIO引脚号。

设备树是一种描述硬件设备信息的数据结构,可以在/sys/firmware/devicetree/base目录下找到。

```ls /sys/firmware/devicetree/base```在该目录下,可以找到与GPIO相关的节点,通过查找相应的节点,可以获取到LED灯连接的GPIO引脚号。

例如,假设我们找到了一个名为gpio0的节点,表示LED灯连接到了GPIO0引脚。

接下来,我们可以使用sysfs文件系统来控制GPIO引脚的状态。

sysfs是Linux内核提供的一种虚拟文件系统,可以通过文件操作来控制硬件设备。

我们需要将GPIO引脚设置为输出模式。

可以通过以下命令来设置:```echo out > /sys/class/gpio/gpio0/direction```其中,gpio0为LED灯连接的GPIO引脚号。

然后,我们可以通过向GPIO引脚的值文件写入相应的值来控制LED 灯的亮灭状态。

例如,要使LED灯亮起,可以执行以下命令:```echo 1 > /sys/class/gpio/gpio0/value```要使LED灯熄灭,可以执行以下命令:```echo 0 > /sys/class/gpio/gpio0/value```通过读取GPIO引脚的值文件,我们也可以获取LED灯的亮灭状态。

ledctl命令用法(一)

ledctl命令用法(一)

ledctl命令用法(一)ledctl命令用法详解ledctl命令是Linux系统中用于控制LED灯的命令行工具。

它可以用于开关LED灯、改变LED灯的亮度等操作。

下面是ledctl命令的一些常用用法的详细介绍。

开关LED灯•开启LED灯使用ledctl命令开启LED灯的语法为:ledctl -L <LED设备路径> -s on示例:开启LED灯的命令为:ledctl -L /sys/class/leds/led1 -s on•关闭LED灯使用ledctl命令关闭LED灯的语法为:ledctl -L <LED设备路径> -s off示例:关闭LED灯的命令为:ledctl -L /sys/class/leds/led1 -s off改变LED灯亮度•设置LED灯亮度使用ledctl命令设置LED灯亮度的语法为:ledctl -L <LED设备路径> -b <亮度值>示例:设置LED灯亮度为50%的命令为:ledctl -L /sys/class/leds/led1 -b 50•逐渐改变LED灯亮度使用ledctl命令逐渐改变LED灯亮度的语法为:ledctl -L <LED设备路径> -f <起始亮度值> -t <目标亮度值> -d <变化时间>示例:逐渐将LED灯从0%亮度变为100%亮度,变化时间为2秒的命令为:ledctl -L/sys/class/leds/led1 -f 0 -t 100 -d 2其他命令选项•查看LED灯状态使用ledctl命令查看LED灯状态的语法为:ledctl -L <LED设备路径> -g示例:查看LED灯状态的命令为:ledctl -L /sys/class/leds/led1 -g•查看LED灯支持的操作使用ledctl命令查看LED灯支持的操作的语法为:ledctl -L <LED设备路径> -v示例:查看LED灯支持的操作的命令为:ledctl -L /sys/class/leds/led1 -v•显示帮助信息使用ledctl命令显示帮助信息的语法为:ledctl -h示例:显示ledctl命令帮助信息的命令为:ledctl -h以上就是ledctl命令的一些常用用法的详细讲解。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

以下是扬创开发板给的led例程,将对应用程序和驱动程序进行详细注释和分析,并验证!/** LED interface driver for utu2440* This file is subject to the terms and conditions of the GNU General Public* License. See the file "COPYING" in the main directory of this archive* for more details.* bit.lili@ 2007-6*/#include <linux/types.h>#include <linux/ioport.h>#include <linux/fcntl.h>#include <linux/miscdevice.h>#include <linux/sched.h>#include <linux/spinlock.h>#include <linux/errno.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/input.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/device.h>#include <asm/uaccess.h>#include <asm/system.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/arch/regs-gpio.h>#define LED_DRIVER "utu2440 LED Driver v1.00"static unsigned long led_table [] = {S3C2410_GPF4,S3C2410_GPF5,S3C2410_GPF6,S3C2410_GPF7,};static int led_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg);/*ioctl(fd, on, led_number);inode 和filp 指针是对应应用程序传递的文件描述符fd 的值,和传递给open 方法的相同参数*/static int led_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg){switch (cmd) {case 0:case 1:if (arg > 4) {return -EINV AL;}s3c2410_gpio_setpin(led_table[arg], !cmd);/*给寄存器赋值,使某一位置1或0,led亮,给寄存器置0,因此对cmd取反*/ /*通过s3c2410_gpio_setpin()来做,此函数为驱动函数的核心操作*//*case 0和case 1操作一样*/default:return -EINV AL;}return 0;}/*设备驱动文件操作结构体*/static struct file_operations led_fops = {.ioctl = led_ioctl,};static struct miscdevice led_dev = {MISC_DYNAMIC_MINOR,"led",&led_fops};/*struct miscdevice {int minor; //MISC_DYNAMIC_MINORconst char *name;//"led"const struct file_operations *fops; //&led_fopsstruct list_head list;struct device *parent;struct device *this_device;};此结构体是注册混合设备所需要的参数。

主要有:minor:次设备号,所有的misc设备共用一个主设备号,所以注册misc设备时只要次设备号就可以了。

利用次设备号来区分设备的。

name:misc设备名。

*fops:misc设备文件操作结构体。

其它三个参数很少用*/static int led_init(void){pr_info("%s\n", LED_DRIVER);/*printk(KERN_INFO fmt, ##arg)#defineKERN_INFO"<6>" 提示信息,如驱动程序启动时,打印硬件信息没有指定日志级别的printk语句默认采用的级别是DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在linux26/kernel/printk.c中可以找到。

下面是一个比较简单的使用printk(KERN_INFO "INFO\n"); //这里可以使用数字代替KERN_INFO,即可以写成printk(<6> "INFO\n");在这个格式的定义中,日志级别和信息文本之间不能够使用逗号隔开,因为系统在进行编译的时候,将日志级别转换成字符串于后面的文本信息进行连接。

*/misc_register(&led_dev);// misc设备注册/*非标准设备使用misc_register,即一些字符设备不符合预先确定的字符设备范畴,这些设备就用主编号10一起归于"其他类型",misc_register()用主编号10调用register_chrdev(),设备名称和函数表指针通过miscdevice数据结构获得。

同样,miscdevice 数据结构还保存设备驱动程序所使用的次要号码。

*/ return 0;}static void __exit led_exit(void){misc_deregister(&led_dev);/*misc(混合,其他类型,不能严格划分的设备类型)类设备的注销函数,成功返回为0,错误返回一个错误代码*/}module_init(led_init);module_exit(led_exit);MODULE_AUTHOR("lili bit.lili@");MODULE_LICENSE("GPL");应用程序:#include <stdio.h> /*标准输入输出库,像sscanf函数,fprintf函数都是在这个库里*/#include <stdlib.h>#include <unistd.h> /*一些宏的定义在这里面,像stderr*/#include <sys/ioctl.h> /*文件操作控制库,像ioctl函数就在这里*//*执行:./led 1 1*/int main(int argc, char **argv)/*argc表示参数的个数,而参数都存放在argv里,它是指针数组*/{int on; /*led的开关状态,从第三个参数中获取*/int led_number; /*led的编号,从第二个参数中获取*/int fd; /*设备号,将从打开的leds设备获得*//*获取参数,并作参数的检验*//*scanf/sscanf 函数的返回值反映的是按照指定的格式符正确读入的数据的个数(sscanf(argv[1], "%d", &led_number)返回正确,应等于1)。

如果输入数据与指定格式不符,则会产生输入错误。

遇到输入错误,scanf函数会立即终止,返回已经成功读取的数据的个数。

所以,通过scanf函数的返回值和指定输入数据的个数(由格式符决定)的比较,可以判断数据输入是否成功。

*/if (argc != 3 || sscanf(argv[1], "%d", &led_number) != 1 || sscanf(argv[2],"%d", &on) != 1 ||on < 0 || on > 1 || led_number < 0 || led_number > 3) {fprintf(stderr, "Usage:\n");fprintf(stderr, "\t led led_number on|off\n");fprintf(stderr, "Options:\n");fprintf(stderr, "\t led_number from 0 to 3\n");fprintf(stderr, "\t on 1 off 0\n");/*stdout -- 标准输出设备(printf("..")) 同stdout。

stderr -- 标准错误输出设备两者默认向屏幕输出。

但如果用转向标准输出到磁盘文件,则可看出两者区别。

stdout输出到磁盘文件,stderr在屏幕*/exit(1);}fd = open("/dev/led", 0);/*驱动程序可以不实现open这个函数,在这种情况下,设备的打开操作永远成功。

*/if (fd < 0) {perror("open device /dev/led");exit(1);}ioctl(fd, on, led_number);close(fd);return 0;}总结:上面是点亮led的应用程序和驱动程序,在应用程序中,可以根据需要进行修改,比如让led闪烁等等,驱动程序和应用程序分别给了详细注释,如有错误欢迎指正!程序已在扬创开发板上验证,但验证中发现第二个led不亮,还不知为什么,望高手指点!。

相关文档
最新文档