首页 > 代码库 > Linux 设备驱动的固件载入

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()函数,把设备的信息(比如port,中断号等)传递给驱动,驱动就能够对真实的物理部件进行初始化,并把对该设备的控制接口注冊到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 = http://www.mamicode.com/builtin->data;>

函数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;
}

函数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 = http://www.mamicode.com/firmware_attr_data_tmpl;   /*设置文件的属性结构*/>

/*文件属性结构实例,设置文件系统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, /*释放设备的函数*/
};


Linux 设备驱动的固件载入