硬件越来越复杂,硬件的许多功能使用了程序实现,与直接硬件实现相比,固件拥有处理复杂事物的灵活性和便于升级、维护等优点。固件(firmware)就是这样的一段在设备硬件自身中执行的程序,通过固件标准驱动程序才能实现特定机器的操作,如:光驱、刻录机等都有内部的固件。
固件一般存放在设备上的flash存储器中,但出于成本和灵活性考虑,许多设备都将固件的映像(image)以文件的形式存放在硬盘中,设备驱动程序初始化时再装载到设备内部的存储器中。这样,方便了固件的升级,并省略了设备的flash存储器。
本章分析了驱动程序加载固件映像文件的过程。
固件函数接口
Linux内核对设备固件的装载和清除提供了支持接口,可将固件映像文件加载到设备指定的存储地址,固件映像文件的内容由设备自身来解析,Linux内核只将映像文件当件未知的二进制文件。Linux内核用结构firmware描述固件映像文件的内容,该结构列出如下(在include/linux/firmware.h中):
struct firmware {
size_t size;
const u8 *data;
};
固件函数接口原型说明
固件函数接口原型说明如下:
- 函数request_firmware
int request_firmware(const struct firmware **firmware_p, const char *name, struct device *device)
函数request_firmware向用户空间请求提供一个名为name固件映像文件并等待完成。
参数device为固件装载的设备。
文件内容存入request_firmware 返回。如果固件请求成功,返回0。
该函数从用户空间得到的数据未做任何检查,用户在编写驱动程序时,应对固件映像做数据安全检查,检查方法由设备固件提供商确定,通常有检查标识符、校验和等方法。
- 函数release_firmware
void release_firmware(struct firmware *fw);
函数release_firmware在完成固件装载后,释放所申请的内存块fw。
- 函数request_firmware_nowait
int request_firmware_nowait(
struct module *module, int uevent,
const char *name, struct device *device, void *context,
void (*cont)(const struct firmware *fw, void *context))
函数request_firmware_nowait是函数request_firmware的异步请求版本,用于不能睡眠的内核线程中调用。
参数module为请求固件的模块;
参数uevent为非0时,表示发送uevent事件用于自动拷贝固件映像,否则,必须人工拷贝映像;
参数name为固件映像文件的名字;
参数device为装载固件的设备;
参数cont为固件请求完成时调用的函数;
参数context为函数cont的参数。
固件接口函数的使用方法
1、当驱动程序需要使用固件驱动时,在驱动程序的初始化化过程中需要加下如下的代码:
if(request_firmware(&fw_entry, $FIRMWARE, device) == 0) /*从用户空间请求映像数据*/
/*将固件映像拷贝到硬件的存储器,拷贝函数由用户编写*/
copy_fw_to_device(fw_entry->data, fw_entry->size);
release(fw_entry);
2、用户还需要在用户空间提供脚本通过文件系统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请求从用户空间拷贝固件映像文件到内核缓冲区。该函数的工作流程列出如下:
(1)在文件系统sysfs中创建文件/sys/class/firmware/xxx/loading和data,"xxx"表示固件的名字,给文件loading和data附加读写函数,设置文件属性。文件loading表示开/关固件映像文件装载功能;文件data的写操作将映像文件的数据(从用户空间)写入内核缓冲区,读操作从内核缓冲区读取数据(到用户空间)。
(2)将添加固件的uevent事件(即"add")通过内核对象模型发送到用户空间。
(3)用户空间管理uevent事件的后台进程udevd接收到事件后,查找udev规则文件,运行规则所定义的动作,与固件相关的规则列出如下:
^-^$ /etc/udev/rules.d/50-udev-default.rules
……
# firmware class requests
SUBSYSTEM=="firmware", ACTION=="add", RUN+="firmware.sh"
……
从上述规则可以看出,固件添加事件将引起运行脚本firmware.sh。
(4)脚本firmware.sh打开"装载"功能,同命令"cat 映像文件 > /sys/class/firmware/xxx/data"将映像文件数据写入到内核的缓冲区。
(5)映像数据拷贝完成后,函数request_firmware从文件系统/sysfs注销固件设备对应的目录"xxx"。如果请求成功,函数返回0。
(6)用户就将内核缓冲区的固件映像数据拷贝到固件的内存中。然后,调用函数release_firmware(fw_entry)释放给固件映像分配的缓冲区。
函数request_firmware的调用层次图如图3所示。它先设置uevent事件为1,然后调用设备驱动程序模型:函数device_register在文件系统sysfs中创建目录"xxx",函数kobject_uevent发送事件,函数device_unregister在装载完固件映像数据后清除目录"xxx"。
函数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;
}
函数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 = 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;
…… //省略了出错保护
}
/*文件属性结构实例,设置文件系统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 Kernel中,Driver和Firmware是有明确含义的:
Driver是控制被操作系统管理的外部设备(Device)的代码段。很多时候Driver会被实现为LKM,但这不是必要条件。driver通过driver_register()注册到总线(bus_type)上,代表系统具备了驱动某种设备(device)的能力。当某个device被注册到同样的总线的时候(通常是总线枚举的时候发现了这个设备),总线驱动会对driver和device会通过一定的策略进行binding(即进行匹配),如果Binding成功,总线驱动会调用driver的probe()函数,把设备的信息(例如端口,中断号等)传递给驱动,驱动就可以对真实的物理部件进行初始化,并把对该设备的控制接口注册到Linux的其他子系统上(例如字符设备,v4l2子系统等)。这样操作系统的其他部分就可以通过这些通用的接口来访问设备了。
而Firmware,是表示 运行在非“控制处理器”(指不直接运行操作系统的处理器,例如外设中的处理器,或者被用于bare metal的主处理器的其中一些核)中的程序。这些程序很多时候使用和操作系统所运行的处理器完全不同的指令集。这些程序以二进制形式存在于Linux内核的源代码树中,生成目标系统的时候,通常拷贝在 /lib/firmware目录下。当driver对device进行初始化的时候,通过request_firmware()等接口,在一个用户态helper程序的帮助下,可以把指定的firmware加载到内存中,由驱动传输到指定的设备上。
所以,总的来说, driver和firmware没有什么直接的关系,但firmware通常由驱动去加载。我们讨论的那个OS,一般不需要理解firmware是什么,只是把它当做数据。firmware是什么,只有使用这些数据的那个设备才知道。好比你用一个电话,电话中有一个软件,这个软件你完全不关心如何工作的,你换这个软件的时候,就可以叫这个软件是“固件”,但如果你用了一个智能手机,你要细细关系什么是上面的应用程序,Android平台,插件之类的细节内容,你可能就不叫这个东西叫“固件”了。
这种情况在计算机领域非常常见,所以大部分Spec都自己重新定义概念。比如说,我们平时写软件,说Component,很多就是只软件的其中一个部分,但在UEFI中,Component的定义是:
An executable image. Components defined in this specification support on elf the defined module types.
这是一个“独立的映像”,和我们一般理解的概念就完全不同,但如果你学计算机,请了解,这是我们的惯例。
在操作系统概念还不明确的时代,二者是没有明显区别的。
但是随着计算机体系结构的发展,硬件的种类开始变多,操作系统的种类也变多了。
这个时候,因为各种技术的、商业的原因,硬件厂商希望自己的硬件能被更多的软件厂商使用,所以就需要 在硬件之上做一些封装,让自己的硬件操纵起来更容易,这个时候就要有firmware这种东西了,它简化了软件与硬件的交互。
但是为什么不把fimware做的很完美,做的不需要驱动支持呢?因为有不同的操作系统。我不知道你对操作系统的理解是到什么程度?只知道Windows?还是还仅仅知道Linux?还是清楚Unix和FreeBSD是不同的系统?知道有RTOS?知道有上百种不同的内核?
不同的操作系统,对于操作硬件的方式完全不同,在Windows里应用态是无法直接写IO端口的,而在嵌入式系统里,一般都不限制直接操作IO端口。所以,硬件厂商一方面为了自己的硬件能被软件更简单的使用,就需要写firmware,而另一方面为了兼容各种操作系统,又不能把firmware写的太死,必须预留足够的余地让软件自由发挥——软件的自由发挥就是驱动。
不同操作系统的驱动是不能兼容的,原因就是驱动是为操作系统服务的,有的操作系统是单线程的,有些操作系统不允许动态申请内存,所以不同的操作系统要操作硬件,就要根据自身的特性编写对应的操作代码, 这就是驱动存在的意义——适应系统需要。
假如世界上只有一种操作系统,并且版本永远不会改变,那么firmware和驱动就可以融合在一起,但这只能一个不现实的梦想,要知道民用操作系统和工业控制操作系统差别是十分巨大的。
名词解释到这,下面举几个例子吧:
属于driver但是不属于firmware的例子:
1. oracle database, .net driver。 所有软件产品的驱动都不属于固件。
2. 打印机、驱动光盘。由于并没有预装入打印机只读记忆体,不属于固件。
属于固件但不属于驱动的:
1. 洗衣机内置电脑程序。此为直接用户交互的系统终端(user interface,UI),并 不和其他应用程序交互(application programming interface, API).
2. 计算器里面的操作系统。
同属于firmware 和 driver的
1. 路由器内置系统——刻录入路由器存储器,用于与其他软件连接交互的(电脑,手机,其他路由器等)
http://blog.csdn.net/mirkerson/article/details/7849453
http://www.zhihu.com/question/22175660