转自:http://blog.chinaunix.net/uid-22028680-id-3157922.html

1、request_firmware在内核使用,需要文件系统支持,就是说,启动的时候如果在驱动里面的probe函数调用 request_firmware ,那么系统将等待30s左右,因为文件系统还没有挂载,当然找不到固件了,所以最好在中断里面启动tasklet,然后request_firmware 。如果不想等待,就用request_firmware_nowait,好像是这样写的。

2、那么用户层怎么用?

实际上这个分x86和嵌入式,比如arm,平台。x86的用到了udev,

比如你要请求固件 fw.hex,那么必须在文件系统中导出环境变量,比如 export FIRMWARE=/lib/firmware,而且目录/lib/firmware不能少,因为busybox要用到。然后把固件fw.hex放到 /lib/firmware目录下即可。内核request_firmware的时候,busybox就知道去FIRMWARE找了。

3、对linux来讲,所谓的固件什么也不是,他只是按fopen()返回二进制文件给你,看看busybox的处理就知道了。所以你的文件随便定 义,比如一个mp3文件,你也可以称为固件:request_firmware(“xxx.mp3″),那么你文件系统里面也要有这个xxx.mp3文 件,只不过系统给你返回二进制数据,具体的处理要在内核进行。

request_firmware 返回二进制firmware文件的地址和大小

//firmware->data和firmware->size来读取有uevent处理程序init加载进来的firmware数据了

request_firmware( & priv- > firmware, fw_name, priv- > hotplug_device) ; 给priv- > hotplug_device设备申请名字为fw_name的firmware  数据, 然后将结果放到& priv- > firmware中,
struct firmware {
size_t size;
u8 * data;
} ;
可以看到, 如果应用层的程序成功load了firmware固件文件, 那么firmware. data将指向固件数据, firmware. size为固件大小.

前段时间移植 wifi 驱动到 android 的内核上,发现 firmware 的加载始终出错,问了几个人,都不是很了解,没办法,只好自己研究一下。

原理分析

从本质上来说, firmware 需要做的事情包括两件:

1,  通知用户态程序,我需要下载 firmware 了;

2,  用户态程序把用户态的数据 copy 到内核层;

3,  内核把内核态的数据写到设备上,比如 wifi 模块里;

其中第三步应该不难,关键是看看, linux 里面是如何实现第一、二步的;

实现机制

简单的说,它的机制分成以下几部分:

1,  通过一定的方式,通知用户态程序,比如 init 程序,如图所示:

显然是通过 kobject_uevent 的方式通知的 应用层,它的机制我有空再详细解释,简单的说,就是往一个 socket 广播一个消息,只需要在应用层打开 socket 监听 NETLINK_KOBJECT_UEVENT 组的消息,就可以收到了。

用户态的 init 是如何做的?

可以看到 init 程序打开了一个 socket ,然后绑定它, 最后通过 select 来监听 socket 上来的数据,最后调用
handle_device_fd 来处理收到的消息;当内核发送一个 KOBJ_ADD 的消息上来的时候,经过过 滤,判断是否是 firmware
要被加载的消息,然后调用

handle_firmware_event 来处理;

2,  用户态的数据如何下载到内核;

本质上它是内核创建了两个文件,一个文件 A 用来标志下载的开始和结 束,另外一个文件 B 用来接收用户层传下来的数据,当用户态的程序往 A
文件写入 1 的时候,标志用户态程序已经往里面写程序来,而往里面写入 0 的时候,就标志下载成功结束,如果写入 -1 就表示下载失败了;下面
看看这两个文件是如何被创建的 , 以及数据是如何写到内核的,请看图:

这个图基本上就是两个文件被创立的过程,以及当这两个文 件被用户态程序访问的时候将要被调用的函数,比如对于标志文件,如果往里面写入数据,将会触发函数 firmware_loading_store 函数,如果往 bin 文件里面写入数据将会触发 bin 文件类型的 write 函数;

用户态写数据的过程大约是这样的:当用户态收到 KOBJ_ADD 消息的时候 最终将会调用 handle_firmware_event 的函数;

它的过程就是:

a, 先往标志文件里面写 1 ;

b, 从用户空间读取数据;

c, 往内核创建的文件里面写数据;

d, 如果成功写入 0 ,否则写入 -1 ;

下面看看内核是如何接受这些文件的,前面提到内核创建了一个 bin 文件,用来接收用户态的数据,下面看 看这个过程:

对于 SYSFS_KOBJ_BIN_ATTR 属 性的文件,在 inode 初始化的时候,将会被赋予 bin_fops
的文件操作函数集,于是当上层调用 write 的时候,将会走到内核的 bin_fops.write
函数;这个函数干的事情很简单,就是把用户态的数据 copyright 到 bb->buffer ,而 bb->buffer 其
实是在 open 的 时候分配的空间,这样的话,就实现了用户态的数据到内核的 copy ;过程是不是完了?

还有一个步骤,这个 bb->buffer 本身是如何与 wifi 驱动交互的呢?这只是一个中间层,它的数据必须要写到 wifi 的驱动才应该算完整,而这一步其实 就是通过 flush_write 来完成的,下面看看这个过程:

这里可以清楚的看到, flush_write 做的事情就是把 bb->buffer 的内容 copy 到 wifi driver 分配的空间 fw->data 里面去了,至此,用户态的数据已经完整的写到了 wifi 的 driver 空间了;

3,  内核态的数据到 wifi 模块

这个就比较简单了,通过函数 sdio_writesb 利用 sdio 总线把数据写到模块 里面去了;

总结

Firmware 的加载主要是利用了 uevent 的通讯机制实现用户态和内核 态的交互,另外还涉及了 sys 文件系统里的文件创建 ,
我加载 wifi firmware 始终出错的原因是 android 的文件系统要求把 wifi 的 firmware helper 放到
/etc/firmware 里面,而把真正 的 firmware sd8686.bin 放到 /etc/firmware/mrvl 里面,估计是
marvel 修改后的结果,结论就是,这个设计真丑;

获取固件的正确方法是当需要时从用户空间获取它。一定不要试图从内核空间直接打开包含固件的文件,那是一个易出错的操作, 因为它把策略(以文件名的形式)包含进了内核。正确的方法是使用固件接口:

#include
int request_firmware(const struct firmware **fw,                      const char *name, /* name 为固件文件名*/

struct device *device);
/*要求用户空间定位并提供一个固件映象给内核;若成功加载, 返回值是 0(否则返回错误码)*/

/*因为 request_firmware 需要用户空间的操作, 所以返回前将保持休眠。若驱动必须使用固件而不能进入休眠时,可使用以下异步函数:*/
int request_firmware_nowait(
struct module *module, /* = THIS_MODULE*/
int uevent,
const char *name,
struct device *device,
void *context,/*不由固件子系统使用的私有数据指针*/
void (*cont)(const struct firmware *fw, void *context));
/*如果一切正常,request_firmware_nowait 开始固件加载过程并返回 0. 过了一段时间后(默认10秒),将用加载的结果(若加载失败, fw 为 NULL)作为参数调用 cont。*/

/* fw 参数指向以下结构体:*/
struct firmware {
size_t size;
u8 *data;
};
/*那个结构包含实际的固件, 它现在可被下载到设备中.但是请注意:在发送它到硬件之前,必须检查这个文件以确保它是正确的固件映象(设备固件常常包含标识字符串、 校验和等等)*/

/*当固件已经发送到设备后,应当释放 firmware 结构体, 使用:*/
void release_firmware(struct firmware *fw);

注意:要使用firmware,必须要在配置内核时选上:

Device Drivers  --->

Generic Driver Options  --->

<*> Userspace firmware loading support

否则会出现: Unknown symbol release_firmware 和: Unknown symbol request_firmware 的错误。

当调用 request_firmware时, 函数将在 /sys/class/firmware 下创建一个以设备名为目录名的新目录,其中包含 3 个属性:

loading :这个属性应当被加载固件的用户空间进程设置为 1。当加载完毕, 它将被设为 0。被设为 -1 时,将中止固件加载。
data :一个用来接收固件数据的二进制属性。在设置 loading 为1后, 用户空间进程将固件写入这个属性。
device :一个链接到 /sys/devices 下相关入口项的符号链接。

一旦创建了 sysfs 入口项, 内核将为设备产生一个热插拔事件,并传递包括变量 FIRMWARE 的环境变量给处理热插拔的用户空间程序。FIRMWARE 被设置为提供给 request_firmware 的固件文件名。

用户空间程序定位固件文件, 并将其拷贝到内核提供的二进制属性;若无法定位文件, 用户空间程序设置 loading 属性为 -1。

若固件请求在 10 秒内没有被服务, 内核就放弃并返回一个失败状态给驱动。超时周期可通过 sysfs 属性 /sys/class/firmware/timeout 属性改变。

request_firmware 接口允许使用驱动发布设备固件。当正确地集成进热插拔机制后, 固件加载子系统允许设备不受干扰地工作。显然这是处理问题的最好方法,但固件受版权保护,小心违反版权法。

这里主要介绍硬件驱动使用 Linux kernel 提供Firmware load 功能的方法;
(1) kernel source code :
drivers/base/firmware_class.c // linux 2.6.11
(2) header file:

(3) document
Document/firmware/
(4) 使用例子
Documentation/firmware_class/firmware_sample_driver.c

1 /*
  2  * firmware_sample_driver.c -
  3  *
  4  * Copyright (c) 2003 Manuel Estrada Sainz <ranty@debian.org>
  5  *
  6  * Sample code on how to use request_firmware() from drivers.
  7  *
  8  * Note that register_firmware() is currently useless.
  9  *
 10  */
 11
 12 #include
 13 #include
 14 #include
 15 #include
 16
 17 #include “linux/firmware.h”
 18
 19 #define WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE
 20 #ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE
 21 char __init inkernel_firmware[] = “let’s say that this is firmware\n”;
 22 #endif
 23
 24 static struct device ghost_device = {
 25         .name      = “Ghost Device”,
 26         .bus_id    = “ghost0″,
 27 };
 28
 29
 30 static void sample_firmware_load(char *firmware, int size)
 31 {
 32         u8 buf[size+1];
 33         memcpy(buf, firmware, size);
 34         buf[size] = ‘\0′;
 35         printk(“firmware_sample_driver: firmware: %s\n”, buf);
 36 }
 37
 38 static void sample_probe_default(void)
 39 {
 40         /* uses the default method to get the firmware */
 41         const struct firmware *fw_entry;
 42         printk(“firmware_sample_driver: a ghost device got inserted \n”);
 43
 44         if(request_firmware(&fw_entry, “sample_driver_fw”, &ghost_device)!=0)
 45         {
 46                 printk(KERN_ERR
 47                        “firmware_sample_driver: Firmware not available\n”);
 48                 return;
 49         }
 50        
 51         sample_firmware_load(fw_entry->data, fw_entry->size);
 52
 53         release_firmware(fw_entry);
 54
 55         /* finish setting up the device */
 56 }
 57 static void sample_probe_specific(void)
 58 {
 59         /* Uses some specific hotplug support to get the firmware from
 60          * userspace  directly into the hardware, or via some sysfs file */
 61
 62         /* NOTE: This currently doesn’t work */
 63
 64         printk(“firmware_sample_driver: a ghost device got inserted \n”);
 65
 66         if(request_firmware(NULL, “sample_driver_fw”, &ghost_device)!=0)
 67         {
 68                 printk(KERN_ERR
 69                        “firmware_sample_driver: Firmware load failed\n”);
 70                 return;
 71         }
 72        
 73         /* request_firmware blocks until userspace finished, so at
 74          * this point the firmware should be already in the device */
 75
 76         /* finish setting up the device */
 77 }
 78 static void sample_probe_async_cont(const struct firmware *fw, void *context)
 79 {
 80         if(!fw){
 81                 printk(KERN_ERR
 82                        “firmware_sample_driver: firmware load failed\n”);
 83                 return;
 84         }
 85
 86         printk(“firmware_sample_driver: device pointer \”%s\”\n”,
 87                (char *)context);
 88         sample_firmware_load(fw->data, fw->size);
 89 }
 90 static void sample_probe_async(void)
 91 {
 92         /* Let’s say that I can’t sleep */
 93         int error;
 94         error = request_firmware_nowait (THIS_MODULE,
 95                                          “sample_driver_fw”, &ghost_device,
 96                                          “my device pointer”,
 97                                          sample_probe_async_cont);
 98         if(error){
 99                 printk(KERN_ERR
100                        “firmware_sample_driver:”
101                        ” request_firmware_nowait failed\n”);
102         }
103 }
104
105 static int sample_init(void)
106 {
107 #ifdef WE_CAN_NEED_FIRMWARE_BEFORE_USERSPACE_IS_AVAILABLE
108         register_firmware(“sample_driver_fw”, inkernel_firmware,
109                           sizeof(inkernel_firmware));
110 #endif
111         device_initialize(&ghost_device);
112         /* since there is no real hardware insertion I just call the
113          * sample probe functions here */
114         sample_probe_specific();
115         sample_probe_default();
116         sample_probe_async();
117         return 0;
118 }
119 static void __exit sample_exit(void)
120 {
121 }
122
123 module_init (sample_init);
124 module_exit (sample_exit);
125
126 MODULE_LICENSE(“GPL”);

The kernel doesn’t actually load any firmware at all. It simply informs userspace, “I want a firmware by the name of xxx“, and waits for userspace to pipe the firmware image back to the kernel.

udev is configured to run firmware_helper when the kernel asks for firmware
If
you read the source, you’ll find that Ubuntu wrote a firmware_helper
which is hard-coded to first look for /lib/modules/$(uname
-r)/$FIRMWARE, then /lib/modules/$FIRMWARE, and no other locations.
Translating it to sh, it does approximately this:

echo -n 1 > /sys/$DEVPATH/loading
cat /lib/firmware/$(uname -r)/$FIRMWARE > /sys/$DEVPATH/data \
    || cat /lib/firmware/$FIRMWARE      > /sys/$DEVPATH/data
if [ $? = 0 ]; then
    echo -n  1 > /sys/$DEVPATH/loading
    echo -n -1 > /sys/$DEVPATH/loading
fi
which is exactly the format the kernel expects.

Linux firmware 加载【转】的更多相关文章

  1. Firmware 加载原理分析【转】

    转自:http://blog.csdn.net/dxdxsmy/article/details/8669840 [-] 原理分析 实现机制 总结   前言 前段时间移植 wifi 驱动到 Androi ...

  2. bootrom/spl/uboot/linux逐级加载是如何实现的?

    关键词:bootrom.spl.uboot.linux.mksheader.sb_header.mkimage.image_header_t等等. 首先看一个典型的bootrom->spl-&g ...

  3. Linux内核加载全流程

    无论是Linux还是Windows,在加电后的第一步都是先运行BIOS(Basic Input/Output System)程序——不知道是不是所以的电脑系统都是如此.BIOS保存在主板上的一个non ...

  4. linux配置加载顺序

    linux加载配置项时通过下面方式 首先 加载/etc/profile配置 然后 加载/ect/profile.d/下面的所有脚本 然后 加载当前用户 .bash_profile 然后 加载.bash ...

  5. [ARM-Linux开发]Linux下加载.ko驱动模块的两种方法:insmod与modprobe

    假设要加载的驱动程序模块名为SHT21.ko 加载驱动模块 方法一:  进入SHT21.ko驱动模块文件所在的目录,然后直接  insmod SHT21.ko  即可 方法二:  将SHT21.ko文 ...

  6. linux C 加载so文件 指定路径

    在Linux C中动态加载.so文件用dlopen("libdemo.so", RTLD_NOW); 但是默认的so搜索目录不包括当前程序目录,所以必须复制到系统的so目录 才能运 ...

  7. Linux开机加载过程

    2015-01-06 10:29:13   目录 1 开机加载简介 2 常规加载流程 2.1 加载BIOS 2.2 读取MBR 2.3 boot loader 2.4 加载内核 2.5 init依据i ...

  8. linux模块加载竞争

    到目前, 我们的讨论已来到一个模块加载的重要方面: 竞争情况. 如果你在如何编写你的 初始化函数上不小心, 你可能造成威胁到整个系统的稳定的情形. 我们将在本书稍后讨论 竞争情况; 现在, 快速提几点 ...

  9. windows/Linux动态加载链接库问题

    windows: LoadLibraryA 指定的可执行模块映射到调用进程的地址空间并返回该 DLL 的句柄 HMODULE LoadLibraryA( LPCTSTR lpLibFileName// ...

随机推荐

  1. 描述符应用 -- 让python变成一个强类型的语言

    众所周知,python是一门弱类型的语言,变量可以随意赋值成任意类型,但是通过描述符,我们可以把数据变成强类型的. 我们为数据设置数据描述符,因为数据描述的优先级大于实例属性,所以在给数据赋值的时候会 ...

  2. Neon Lights in Hong Kong【香港霓虹灯】

    Neon Lights in Hong Kong Neon is to Hong Kong as red phone booths are to London and fog is to San Fr ...

  3. C++ vector 实例

    #include <string> #include <iostream> #include <vector> #include <algorithm> ...

  4. UVA:11297-Census(二维线段树)

    Census Time Limit: 8 sec Description This year, there have been many problems with population calcul ...

  5. (WPF&Silverlight)silverlight自定义控件

    2个半小时弄懂了自定义控件是怎么回事儿. 在silverlight中创建一个UserControl,把上面sliderbar的外观和功能都封装在里面. 以自定义控件mapslider控件为例: 1.首 ...

  6. TCP/IP网络编程之多进程服务端(一)

    进程概念及应用 我们知道,监听套接字会有一个等待队列,里面存放着不同客户端的连接请求,如果有一百个客户端,每个客户端的请求处理是0.5s,第一个客户端当然不会不满,但第一百个客户端就会有相当大的意见了 ...

  7. Java EnumSet工作原理初窥

    EnumSet是Java枚举类型的泛型容器,Java既然有了SortedSet.TreeSet.HashSet等容器,为何还要多一个EnumSet<T>呢?答案肯定是EnumSet有一定的 ...

  8. jmeter+ANT+Jekins性能自动生成测试报告脚本(模板),加入:Median TIme、90%、95%、99%、QPS、以及流量显示

    <?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/T ...

  9. idea使用maven逆向mybitis的文件

    引用自 http://blog.csdn.net/for_my_life/article/details/51228098 本文介绍一下用Maven工具如何生成Mybatis的代码及映射的文件. 一. ...

  10. BZOJ 1564 :[NOI2009]二叉查找树(树型DP)

    二叉查找树 [题目描述] 已知一棵特殊的二叉查找树.根据定义,该二叉查找树中每个结点的数据值都比它左儿子结点的数据值大,而比它右儿子结点的数据值小. 另一方面,这棵查找树中每个结点都有一个权值,每个结 ...