Linux 设备驱动的固件加载-转载
作为一个驱动作者, 你可能发现你面对一个设备必须在它能支持工作前下载固件到它里面. 硬件市场的许多地方的竞争是如此得强烈, 以至于甚至一点用作设备控制固件的 EEPROM 的成本制造商都不愿意花费. 因此固件发布在随硬件一起的一张 CD 上, 并且操作系统负责传送固件到设备自身.
硬件越来越复杂,硬件的许多功能使用了程序实现,与直接硬件实现相比,固件拥有处理复杂事物的灵活性和便于升级、维护等优点。固件(firmware)就是这样的一段在设备硬件自身中执行的程序,通过固件标准驱动程序才能实现特定机器的操作,如:光驱、刻录机等都有内部的固件。
固件一般存放在设备上的flash存储器中,但出于成本和灵活性考虑,许多设备都将固件的映像(image)以文件的形式存放在硬盘中,设备驱动程序初始化时再装载到设备内部的存储器中。这样,方便了固件的升级,并省略了设备的flash存储器。
一、驱动和固件的区别
从计算机领域来说,驱动和固件从来没有过明确的定义,就好像今天我们说内存,大部分人用来表示SDRAM,但也有人把Android里的“固化的Flash/Storage"称为“内存”,你不能说这样说就错了,因为这确实是一种“内部存储”。
但在Linux Kernel中,Driver和Firmware是有明确含义的,
1、驱动
Driver是控制被操作系统管理的外部设备(Device)的代码段。很多时候Driver会被实现为LKM,但这不是必要条件。driver通过driver_register()注册到总线(bus_type)上,代表系统具备了驱动某种设备(device)的能力。当某个device被注册到同样的总线的时候(通常是总线枚举的时候发现了这个设备),总线驱动会对driver和device会通过一定的策略进行binding(即进行匹配),如果Binding成功,总线驱动会调用driver的probe()函数,把设备的信息(例如端口,中断号等)传递给驱动,驱动就可以对真实的物理部件进行初始化,并把对该设备的控制接口注册到Linux的其他子系统上(例如字符设备,v4l2子系统等)。这样操作系统的其他部分就可以通过这些通用的接口来访问设备了。
2、固件
Firmware,是表示运行在非“控制处理器”(指不直接运行操作系统的处理器,例如外设中的处理器,或者被用于bare metal的主处理器的其中一些核)中的程序。这些程序很多时候使用和操作系统所运行的处理器完全不同的指令集。这些程序以二进制形式存在于Linux内核的源代码树中,生成目标系统的时候,通常拷贝在/lib/firmware目录下。当driver对device进行初始化的时候,通过request_firmware()等接口,在一个用户态helper程序的帮助下,可以把指定的firmware加载到内存中,由驱动传输到指定的设备上。
所以,总的来说,其实driver和firmware没有什么直接的关系,但firmware通常由驱动去加载。我们讨论的那个OS,一般不需要理解firmware是什么,只是把它当做数据。firmware是什么,只有使用这些数据的那个设备才知道。好比你用一个电话,电话中有一个软件,这个软件你完全不关心如何工作的,你换这个软件的时候,就可以叫这个软件是“固件”,但如果你用了一个智能手机,你要细细关系什么是上面的应用程序,Android平台,插件之类的细节内容,你可能就不叫这个东西叫“固件”了。
如何解决固件问题呢?你可能想解决固件问题使用这样的一个声明:
static char my_firmware[] = { 0x34, 0x78, 0xa4, ... };
但是, 这个方法几乎肯定是一个错误. 将固件编码到一个驱动扩大了驱动的代码, 使固件升级困难, 并且非常可能产生许可问题. 供应商不可能已经发布固件映象在 GPL 之下, 因此和 GPL-许可的代码混合常常是一个错误. 为此, 包含内嵌固件的驱动不可能被接受到主流内核或者被 Linux 发布者包含.
二、内核固件接口
正确的方法是当你需要它时从用户空间获取它. 但是, 请抵制试图从内核空间直接打开包含固件的文件的诱惑; 那是一个易出错的操作, 并且它安放了策略(以一个文件名的形式)到内核. 相反, 正确的方法时使用固件接口, 它就是为此而创建的:
- #include <linux/firmware.h>
- int request_firmware(const struct firmware **fw, char *name, struct device *device);
函数request_firmware向用户空间请求提供一个名为name固件映像文件并等待完成。参数device为固件装载的设备。文件内容存入request_firmware 返回,如果固件请求成功,返回0。该函数从用户空间得到的数据未做任何检查,用户在编写驱动程序时,应对固件映像做数据安全检查,检查方向由设备固件提供商确定,通常有检查标识符、校验和等方法。
调用 request_firmware 要求用户空间定位并提供一个固件映象给内核; 我们一会儿看它如何工作的细节. name 应当标识需要的固件; 正常的用法是供应者提供的固件文件名. 某些象 my_firmware.bin 的名子是典型的. 如果固件被成功加载, 返回值是 0(负责常用的错误码被返回), 并且 fw 参数指向一个这些结构:
- struct firmware {
- size_t size;
- u8 *data;
- };
那个结构包含实际的固件, 它现在可被下载到设备中. 小心这个固件是来自用户空间的未被检查的数据; 你应当在发送它到硬件之前运用任何并且所有的你能够想到的检查来说服你自己它是正确的固件映象. 设备固件常常包含标识串, 校验和, 等等; 在信任数据前全部检查它们.
在你已经发送固件到设备前, 你应当释放 in-kernel 结构, 使用:
void release_firmware(struct firmware *fw);
因为 request_firmware 请求用户空间来帮忙, 它保证在返回前睡眠. 如果你的驱动当它必须请求固件时不在睡眠的位置, 异步的替代方法可能要使用:
- int request_firmware_nowait(struct module *module,
- char *name, struct device *device, void *context,
- void (*cont)(const struct firmware *fw, void *context));
这里额外的参数是 moudle( 它将一直是 THIS_MODULE), context (一个固件子系统不使用的私有数据指针), 和 cont. 如果都进行顺利, request_firmware_nowait 开始固件加载过程并且返回 0. 在将来某个时间, cont 将用加载的结果被调用. 如果由于某些原因固件加载失败, fw 是 NULL.
三、固件如何工作
固件子系统使用 sysfs 和热插拔机制. 当调用 request_firmware, 一个新目录在 /sys/class/firmware 下使用你的驱动的名子被创建. 那个目录包含 3 个属性:
loading
这个属性应当被加载固件的用户空间进程设置为 1. 当加载进程完成, 它应当设为 0. 写一个值 -1 到 loading 会中止固件加载进程.
data
data 是一个二进制的接收固件数据自身的属性. 在设置 loading 后, 用户空间进程应当写固件到这个属性.
device
这个属性是一个符号连接到 /sys/devices 下面的被关联入口项.
一旦创建了 sysfs 入口项, 内核为你的设备产生一个热插拔事件. 传递给热插拔处理者的环境包括一个变量 FIRMWARE, 它被设置为提供给 request_firmware 的名子. 这个处理者应当定位固件文件, 并且拷贝它到内核使用提供的属性. 如果这个文件无法找到, 处理者应当设置 loading 属性为 -1.
如果一个固件请求在 10 秒内没有被服务, 内核就放弃并返回一个失败状态给驱动. 超时周期可通过 sysfs 属性 /sys/class/firmware/timeout 属性改变.
使用 request_firmware 接口允许你随你的驱动发布设备固件. 当正确地集成到热插拔机制, 固件加载子系统允许设备简化工作"在盒子之外" 显然这是处理问题的最好方法.
但是, 请允许我们提出多一条警告: 设备固件没有制造商的许可不应当发布. 许多制造商会同意在合理的条款下许可它们的固件, 如果客气地请求; 一些其他的可能不何在. 无论如何, 在没有许可时拷贝和发布它们的固件是对版权法的破坏并且招致麻烦.
四、固件接口函数的使用方法
当驱动程序需要使用固件驱动时,在驱动程序的初始化化过程中需要加下如下的代码:
- if(request_firmware(&fw_entry, $FIRMWARE, device) == 0) /*从用户空间请求映像数据*/
- /*将固件映像拷贝到硬件的存储器,拷贝函数由用户编写*/
- copy_fw_to_device(fw_entry->data, fw_entry->size);
- release(fw_entry);
用户还需要在用户空间提供脚本通过文件系统sysfs中的文件data将固件映像文件读入到内核的缓冲区中。脚本样例列出如下:
- #变量$DEVPATH(固件设备的路径)和$FIRMWARE(固件映像名)应已在环境变量中提供
- HOTPLUG_FW_DIR=/usr/lib/hotplug/firmware/ #固件映像文件所在目录
- echo 1 > /sys/$DEVPATH/loading
- cat $HOTPLUG_FW_DIR/$FIRMWARE > /sysfs/$DEVPATH/data
- echo 0 > /sys/$DEVPATH/loading
五、固件请求函数request_firmware
函数request_firmware请求从用户空间拷贝固件映像文件到内核缓冲区。该函数的工作流程列出如下:
a -- 在文件系统sysfs中创建文件/sys/class/firmware/xxx/loading和data,"xxx"表示固件的名字,给文件loading和data附加读写函数,设置文件属性,文件loading表示开/关固件映像文件装载功能;文件data的写操作将映像文件的数据写入内核缓冲区,读操作从内核缓冲区读取数据。
b -- 将添加固件的uevent事件(即"add")通过内核对象模型发送到用户空间。
c -- 用户空间管理uevent事件的后台进程udevd接收到事件后,查找udev规则文件,运行规则所定义的动作,与固件相关的规则列出如下:
- $ /etc/udev/rules.d/50-udev-default.rules
- ……
- # firmware class requests
- SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware.sh"
- ……
从上述规则可以看出,固件添加事件将引起运行脚本firmware.sh。
d -- 脚本firmware.sh打开"装载"功能,同命令"cat 映像文件 > /sys/class/firmware/xxx/data"将映像文件数据写入到内核的缓冲区。
e -- 映像数据拷贝完成后,函数request_firmware从文件系统/sysfs注销固件设备对应的目录"xxx"。如果请求成功,函数返回0。
f -- 用户就将内核缓冲区的固件映像数据拷贝到固件的内存中。然后,调用函数release_firmware(fw_entry)释放给固件映像分配的缓冲区。
函数request_firmware列出如下(在drivers/base/firmware_class.c中):
- int request_firmware(const struct firmware **firmware_p, const char *name,
- struct device *device)
- {
- int uevent = 1;
- return _request_firmware(firmware_p, name, device, uevent);
- }
- static int _request_firmware(const struct firmware **firmware_p, const char *name,
- struct device *device, int uevent)
- {
- struct device *f_dev;
- struct firmware_priv *fw_priv;
- struct firmware *firmware;
- struct builtin_fw *builtin;
- int retval;
- if (!firmware_p)
- return -EINVAL;
- *firmware_p = firmware = kzalloc(sizeof(*firmware), GFP_KERNEL);
- …… //省略出错保护
- /*如果固件映像在内部__start_builtin_fw指向的地址,拷贝数据到缓冲区*/
- for (builtin = __start_builtin_fw; builtin != __end_builtin_fw;
- builtin++) {
- if (strcmp(name, builtin->name))
- continue;
- dev_info(device, "firmware: using built-in firmware %s\n", name); /*打印信息*/
- firmware->size = builtin->size;
- firmware->data = builtin->data;
- return 0;
- }
- ……//省略打印信息
- /*在文件系统sysfs建立xxx目录及文件*/
- retval = fw_setup_device(firmware, &f_dev, name, device, uevent);
- if (retval)
- goto error_kfree_fw;
- fw_priv = dev_get_drvdata(f_dev);
- if (uevent) {
- if (loading_timeout > 0) { /*加载定时器*/
- fw_priv->timeout.expires = jiffies + loading_timeout * HZ;
- add_timer(&fw_priv->timeout);
- }
- kobject_uevent(&f_dev->kobj, KOBJ_ADD); /*发送事件KOBJ_ADD*/
- wait_for_completion(&fw_priv->completion);
- set_bit(FW_STATUS_DONE, &fw_priv->status);
- del_timer_sync(&fw_priv->timeout);
- } else
- wait_for_completion(&fw_priv->completion); /*等待完成固件映像数据的装载*/
- mutex_lock(&fw_lock);
- /*如果装载出错,释放缓冲区*/
- if (!fw_priv->fw->size || test_bit(FW_STATUS_ABORT, &fw_priv->status)) {
- retval = -ENOENT;
- release_firmware(fw_priv->fw);
- *firmware_p = NULL;
- }
- fw_priv->fw = NULL;
- mutex_unlock(&fw_lock);
- device_unregister(f_dev); /*在文件系统sysfs注销xxx目录*/
- goto out;
- error_kfree_fw:
- kfree(firmware);
- *firmware_p = NULL;
- out:
- return retval;
- }
![](/assets/blank.gif)
函数fw_setup_device在文件系统sysfs中创建固件设备的目录和文件,其列出如下:
- static int fw_setup_device(struct firmware *fw, struct device **dev_p,
- const char *fw_name, struct device *device,
- int uevent)
- {
- struct device *f_dev;
- struct firmware_priv *fw_priv;
- int retval;
- *dev_p = NULL;
- retval = fw_register_device(&f_dev, fw_name, device);
- if (retval)
- goto out;
- ……
- fw_priv = dev_get_drvdata(f_dev); /*从设备结构中得到私有数据结构*/
- fw_priv->fw = fw;
- retval = sysfs_create_bin_file(&f_dev->kobj, &fw_priv->attr_data); /*在sysfs中创建可执行文件*/
- …… //省略出错保护
- retval = device_create_file(f_dev, &dev_attr_loading); /*在sysfs中创建一般文件*/
- …… //省略出错保护
- if (uevent)
- f_dev->uevent_suppress = 0;
- *dev_p = f_dev;
- goto out;
- error_unreg:
- device_unregister(f_dev);
- out:
- return retval;
- }
![](/assets/blank.gif)
函数fw_register_device注册设备,在文件系统sysfs中创建固件设备对应的设备类,存放固件驱动程序私有数据。其列出如下:
- static int fw_register_device(struct device **dev_p, const char *fw_name,
- struct device *device)
- {
- int retval;
- struct firmware_priv *fw_priv = kzalloc(sizeof(*fw_priv),
- GFP_KERNEL);
- struct device *f_dev = kzalloc(sizeof(*f_dev), GFP_KERNEL);
- *dev_p = NULL;
- …… //省略出错保护
- init_completion(&fw_priv->completion); /*初始化completion机制的等待队列*/
- fw_priv->attr_data = firmware_attr_data_tmpl; /*设置文件的属性结构*/
- strlcpy(fw_priv->fw_id, fw_name, FIRMWARE_NAME_MAX);
- fw_priv->timeout.function = firmware_class_timeout; /*超时装载退出函数*/
- fw_priv->timeout.data = (u_long) fw_priv;
- init_timer(&fw_priv->timeout); /*初始化定时器*/
- fw_setup_device_id(f_dev, device); /*拷贝device ->bus_id到f_dev中*/
- f_dev->parent = device;
- f_dev->class = &firmware_class; /*设备类实例*/
- dev_set_drvdata(f_dev, fw_priv); /*存放设备驱动的私有数据:f_dev ->driver_data = fw_priv*/
- f_dev->uevent_suppress = 1;
- retval = device_register(f_dev);
- if (retval) {
- dev_err(device, "%s: device_register failed\n", __func__);
- goto error_kfree;
- }
- *dev_p = f_dev;
- return 0;
- …… //省略了出错保护
- }
![](/assets/blank.gif)
- /*文件属性结构实例,设置文件系统sysfs中data文件的模式和读/写函数*/
- static struct bin_attribute firmware_attr_data_tmpl = {
- .attr = {.name = "data", .mode = 0644},
- .size = 0,
- .read = firmware_data_read, /*从内核缓冲区读出数据*/
- .write = firmware_data_write, /*用于将固件映像文件的数据写入到内核缓冲区*/
- };
- /*设备类结构实例,含有发送uevent事件函数和释放设备的函数*/
- static struct class firmware_class = {
- .name = "firmware", /*设备类的名字*/
- .dev_uevent = firmware_uevent, /*设备发送uevent事件的函数*/
- .dev_release = fw_dev_release, /*释放设备的函数*/
- };
</article>**Linux 设备驱动的固件加载**
http://www.taodudu.cc/news/show-1837181.html
相关文章:
- Linux内核启动中驱动初始化过程
- jackson 忽略多余字段_Java进阶学习:JSON解析利器JackSon
- 学业水平考试容易过吗_2019年12月贵州省普通高中学业水平考试真题汇总
- 拍照区域遮盖层_真石漆与一般涂料所用外墙腻子层,有什么区别?
- datagrid.columns有没有图片图片属性_天龙八部:86四属性神器在线求超越,原来逍遥扇子还能这么洗...
- javascript释放对象_一文深入理解JavaScript如何运作
- mysql怎么判断多行数据日期是否连续_MySQL学习笔记(一)
- dubbo kryo序列化_Java后端精选技术:序列化框架的选型和比对
- cut最后几位 shell_shell中字符串截取命令:cut,printf,awk,sed
- php课设报告致谢_奇安信CERT发布1月安全监测报告:需警惕这19个高危漏洞
- bios更新工具_5分钟教会您升级bios主板,华硕主板BIOS教程
- android recyclerview item自适应高度_web前端学习:高度自适应知识点
- wordpress获取home_wordpress各种获取路径和URl地址的函数总结
- sonarqube如何导入规则_webpack如何使用Vue
- 提取过程_[论文荐读]石榴皮提取物对生猪肉饼品质的影响
- js 正则中冒号代表什么_是否还在疑惑Vue.js中组件的data为什么是函数类型而不是对象类型...
- 华为u2020操作指南_用手机水平仪检测水平,操作简单,帮助甚大
- 二阶声波正演c语言程序_问:程序员怎么敲代码才不累?答:装上显示器支架之后...
- android按钮点击后闪退_iphone闪退是什么原因?
- datetime对应的jdbc mysql_Java连接MySQL数据库
- centos调整页面大小_新手教程!设置PDF文件的页面大小
- kmeans算法中的sse_《Kmeans的K值确定》
- python桌面程序臃肿_为Python应用构建最精简Docker
- velocity 变量 获取_velocity 怎么得到项目根路径
- layui 自定义request_layuiAdmin pro v1.x 【单页版】开发者文档
- python判断文件格式_Python判断上传文件类型
- dubbo优势_Dubbo的作用和特点
- 双代号网络图节点时间参数_双代号网络图的绘制与6个时间参数的计算,一篇全学会!...
- centos 网络自动连接_自动连接最优信号 腾讯云?云兔解决物联网络连接问题
- 华为开通api服务_入冬第一场技术盛宴!DevRun开发者沙龙华为云武汉专场举办
Linux 设备驱动的固件加载-转载相关推荐
- Linux 设备驱动的固件加载【转】
转自:http://blog.csdn.net/zqixiao_09/article/details/51106663 版权声明:本文为博主原创文章,未经博主允许不得转载. 作为一个驱动作者, 你可能 ...
- Linux 设备驱动的固件加载
作为一个驱动作者, 你可能发现你面对一个设备必须在它能支持工作前下载固件到它里面. 硬件市场的许多地方的竞争是如此得强烈, 以至于甚至一点用作设备控制固件的 EEPROM 的成本制造商都不愿意花费. ...
- Linux内核如何加载固件,驱动是如何工作的_Linux设备驱动的固件加载详解
作为一个驱动作者, 你可能发现你面对一个设备必须在它能支持工作前下载固件到它里面. 硬件市场的许多地方的竞争是如此得强烈, 以至于甚至一点用作设备控制固件的 EEPROM 的成本制造商都不愿意花费. ...
- 《Linux设备驱动开发详解(第2版)》隆重出版
Linux设备驱动开发详解(第2版)(前一版狂销3万册,畅销书最新升级) [新品] 点击看大图 基本信息 * 作者: 宋宝华 * 出版社:人民邮电出版社 * ISBN:97 ...
- Linux设备驱动--块设备(三)之程序设计(转)
http://blog.csdn.net/jianchi88/article/details/7212701 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数 ...
- Linux 设备驱动篇之I2c设备驱动
******************************************************************************************** 装载声明:希望 ...
- Linux设备驱动 IIC驱动
Linux 设备驱动篇之I2c设备驱动 fulinux 一.I2C驱动体系 虽然I2C硬件体系结构和协议都很容易理解,但是Linux I2C驱动体系结构却有相当的复杂度,它主要由3部分组成,即I2C设 ...
- Linux设备驱动篇——[I2C设备驱动-1]
Linux 设备驱动篇之I2c设备驱动 fulinux 一.I2C驱动体系 虽然I2C硬件体系结构和协议都很容易理解,但是Linux I2C驱动体系结构却有相当的复杂度,它主要由3部分组成,即I2C设 ...
- usb升级linux固件,Linux USB驱动(4)---CY68013固件加载驱动
CY68013的固件可以保存在主机上,当其被USB总线识别出来之后,可以通过驱动程序动态的加载USB固件,Linux内核中已经提供了完善的机制来加载设备的固件. 将需要加载的固件存放到/lib/fir ...
- linux设备驱动——bus、device、driver加载顺序与匹配流程
文章目录 1. 前言 2. 概念 2.1. 数据结构 2.2. probe函数 3. bus.device.driver加载顺序 3.1. 加载方式 3.2. 加载顺序 4. device.drive ...
最新文章
- J2EE的13种核心技术
- 使用RunTime添加动态方法、方法交换、获取所有属性来重写归档解档
- java安卓获取mac_android开发分享以编程方式获取Android设备的MAC
- leetcode mysql 排名_(LeetCode:数据库)分数排名
- mysql -- 死锁
- 写得蛮好的linux学习笔记五-认识SHELL(收藏)
- Android 对应的xml配置值,颜色表
- Matlab系列教程_基础知识_运算符
- 魔兽世界模型文件.m2 在D3D下的渲染
- flash for linux安装教程,Flash Player 9 FOR Linux 的安装
- mysql解题器_mysql触发器,答题记录表同步教学跟踪(用户列表)
- Spring初窥门径
- Linux从入门到精通——基本命令
- Python爬虫网站图片并下载到本地
- 电脑怎么重装系统?超简单小白一键重装教程
- 【CODECHEF】Children Trips(分块)
- 修改RK3399 HDMI显示分辨率
- linux的操作系统相关
- 格式化移动硬盘:Windows/Ubuntu/Mac OS系统全兼容,可读写
- 写在2016年的第365天,记录我的2016
热门文章
- 用maven编译spark2.1.0
- JAVA线程池shutdown和shutdownNow的区别
- 【快学springboot】2.Restful简介,SpringBoot构建Restful接口
- Kotlin与Android能做什么?答:Android开发优先语言
- 如何 把 laravel model 的主键修改为字符串类型
- 白帽子也能迎娶白富美:2017八大高薪信息安全认证
- mycat err:java.sql.SQLNonTransientException: find no Route:select日志报错
- 安装和卸载程序时总是出现2502,2503错误代码的解决方法
- DataGridView - Column named XXX cannot be found
- 架构之旅~一个操作的返回要有一个标准,看我的公用消息类