获取固件的正确方法是当需要时从用户空间获取它。一定不要试图从内核空间直接打开包含固件的文件,那是一个易出错的操作, 因为它把策略(以文件名的形式)包含进了内核。正确的方法是使用固件接口:
struct firmware;
request_firmware();
request_firmware_nowait();
release_firmware();
注意:要使用firmware,必须要在配置内核时选上:
Device Drivers —>
Generic Driver Options —>
<*> Userspace firmware loading support
否则会出现: Unknown symbol release_firmware 和: Unknown symbol request_firmware 的错误。
struct firmware { size_t size; u8 *data; };
这个结构包含实际的固件,它现在可被下载到设备中。但是请注意:在发送它到硬件之前,必须检查这个文件以确保它是正确的固件映象(设备固件常常包含标识字符串、校验和等等)
#include <linux/firmware.h> //request_firmware: send firmware request and wait for it //@firmware_p: pointer to firmware image //@name: name of firmware file //@device: device for which firmware is being loaded int request_firmware(const struct firmware **fw, const char *name, struct device *device);
该函数从用户空间得到的数据未作任何检查,所以在编写驱动程序的时候,应对固件映像做数据安全检查,通常检查标志符、校验和等。
因为request_firmware需要等待用户空间的操作, 返回前将保持休眠,所以不能用于不能睡眠的线程。
request_firmware_nowait是request_firmware的异步请求版本,可以用于不能睡眠的内核线程,但不能用于原子上下文
//@module: module requesting the firmware //@uevent: sends uevent to copy the firmware image. //if flag is non-zero els the firmware must be copy manually. //@name: name of firmware file //@device: device for which firmware is being loaded //@context: will be passed over to cont //@cont: function will be called asynchronously whe firmware request finished //@fw: may be NULL if firmware request fails int request_firmware_nowait( struct module *module, /*= THIS_MODULE*/ int uevent, const char *name, struct device *device, gfp_t gfp, void *context,/*不由固件子系统使用的私有数据指针*/ void (*cont)(const struct firmware *fw, void *context));
如果一切正常,request_firmware_nowait 开始固件加载过程并返回0。随后会调用驱动程序的回调函数cont并将保存有firmware的fw作为函数cont的参数。
如果找不到对应位置的文件,过了一段时间后(默认60秒),将用fw=NULL作为参数调用cont。
/*释放firmware结构体以及firmware结构体指向的内存*/ void release_firmware(struct firmware *fw);
当调用 request_firmware时, 函数将在 /sys/class/firmware 下创建一个以设备名为目录名的新目录,其中包含 3 个属性:
loading :这个属性应当被加载固件的用户空间进程设置为 1。当加载完毕, 它将被设为 0。被设为 -1 时,将中止固件加载。
data :一个用来接收固件数据的二进制属性。在设置 loading 为1后, 用户空间进程将固件写入这个属性。
device :一个链接到 /sys/devices 下相关入口项的符号链接。
创建了 sysfs 入口项之后, 内核将为设备发送uevent事件(“add”)到用户空间,并传递环境变量 $FIRMWARE(固件image名字)和$DEVPATH(固件设备的路径)给处理热插拔的用户空间程序。用户空间管理uevent事件的后台进程udevd接收到事件后,查找udev规则文件并运行规则所定义的动作。规则文件如下:
# /etc/udev/rules.d/50-udev-default.rules # firmware class requests SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware.sh" ...
根据规则文件,firmware.sh将会被调用,用户空间的脚本firmware.sh参考如下:
#变量$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
这里有个问题:为什么android系统在开机之后/sys/class/firmware目录下只有timeout一个文件,看不到所创建的设备目录呢?
因为android系统在开机的时候会调用init进程,会调用androidSDK/system/core/init/device.c文件中的handle_firmware_event,handle_firmware_event会查找对应位置的firmware文件是否存在并加载到driver层,如果没有找到,会往loading属性文件写-1,导致fw_load_abort函数被执行,从而导致_request_firmware中一直等待的wait_for_completion(&fw_priv->completion)函数结束,然后fw_destroy_instance被执行,里面会删除在sys/class/firmware目录下创建的,被删除了,当然看不到。
另外还有一种情况,不一定是android系统,当系统起来之后,由于长时间没有往/sys/class/firmware/1-0057/data写入数据,超时导致firmware_class_timeout函数被执行,也会调用fw_load_abort,后面的流程就和上面讲的一样了。
头文件: include/linux/firmware.h
代码: drivers/base/firmware_class.c