linux固件


硬件越来越复杂,硬件的许多功能使用了程序实现,与直接硬件实现相比,固件拥有处理复杂事物的灵活性和便于升级、维护等优点。固件(firmware)就是这样的一段在设备硬件自身中执行的程序,通过固件标准驱动程序才能实现特定机器的操作,如:光驱、刻录机等都有内部的固件。

固件一般存放在设备上的flash存储器中,但出于成本和灵活性考虑,许多设备都将固件的映像(image)以文件的形式存放在硬盘中,设备驱动程序初始化时再装载到设备内部的存储器中。这样,方便了固件的升级,并省略了设备的flash存储器。

本章分析了驱动程序加载固件映像文件的过程。

目录

[隐藏]

固件函数接口

Linux内核对设备固件的装载和清除提供了支持接口,可将固件映像文件加载到设备指定的存储地址,固件映像文件的内容由设备自身来解析,Linux内核只将映像文件当件未知的二进制文件。Linux内核用结构firmware描述固件映像文件的内容,该结构列出如下(在include/linux/firmware.h中):

struct firmware {
	size_t size;
	const u8 *data;
};


固件函数接口原型说明

固件函数接口原型说明如下:

  • 函数request_firmware

函数request_firmware向用户空间请求提供一个名为name固件映像文件并等待完成。参数device为固件装载的设备。文件内容存入request_firmware 返回,如果固件请求成功,返回0。该函数从用户空间得到的数据未做任何检查,用户在编写驱动程序时,应对固件映像做数据安全检查,检查方向由设备固件提供商确定,通常有检查标识符、校验和等方法。

int request_firmware(const struct firmware **firmware_p, const char *name, struct device *device)

  • 函数release_firmware

函数release_firmware在完成固件装载后,释放所申请的内存块fw。

void release_firmware(struct firmware *fw);

  • 函数request_firmware_nowait

函数request_firmware_nowait是函数request_firmware的异步请求版本,用于不能睡眠的内核线程中调用。参数module为请求固件的模块;参数uevent为非0时,表示发送uevent事件用于自动拷贝固件映像,否则,必须人工拷贝映像;参数name为固件映像文件的名字;参数device为装载固件的设备;参数cont为固件请求完成时调用的函数;参数context为函数cont的参数。该函数的原型列出如下:

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))


固件接口函数的使用方法

当驱动程序需要使用固件驱动时,在驱动程序的初始化化过程中需要加下如下的代码:

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请求从用户空间拷贝固件映像文件到内核缓冲区。该函数的工作流程列出如下:

(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"。


图3 函数request_firmware的调用层次图

函数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, /*释放设备的函数*/
};

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值