固件一般存放在设备上的flash存储器中,但出于成本和灵活性考虑,许多设备都将固件的映像(image)以文件的形式存放在硬盘中,设备驱动程序初始化时再装载到设备内部的存储器中。这样,方便了固件的升级,并省略了设备的flash存储器。
本章分析了驱动程序加载固件映像文件的过程。
目录
|
Linux内核对设备固件的装载和清除提供了支持接口,可将固件映像文件加载到设备指定的存储地址,固件映像文件的内容由设备自身来解析,Linux内核只将映像文件当件未知的二进制文件。Linux内核用结构firmware描述固件映像文件的内容,该结构列出如下(在include/linux/firmware.h中):
<span class="kw4">struct</span> firmware <span class="br0">{</span> size_t size<span class="sy0">;</span> <span class="kw4">const</span> u8 <span class="sy0">*</span>data<span class="sy0">;</span> <span class="br0">}</span><span class="sy0">;</span>
固件函数接口原型说明如下:
函数request_firmware向用户空间请求提供一个名为name固件映像文件并等待完成。参数device为固件装载的设备。文件内容存入request_firmware 返回,如果固件请求成功,返回0。该函数从用户空间得到的数据未做任何检查,用户在编写驱动程序时,应对固件映像做数据安全检查,检查方向由设备固件提供商确定,通常有检查标识符、校验和等方法。
int request_firmware(const struct firmware **firmware_p, const char *name, struct device *device)
函数release_firmware在完成固件装载后,释放所申请的内存块fw。
void release_firmware(struct firmware *fw);
函数request_firmware_nowait是函数request_firmware的异步请求版本,用于不能睡眠的内核线程中调用。参数module为请求固件的模块;参数uevent为非0时,表示发送uevent事件用于自动拷贝固件映像,否则,必须人工拷贝映像;参数name为固件映像文件的名字;参数device为装载固件的设备;参数cont为固件请求完成时调用的函数;参数context为函数cont的参数。该函数的原型列出如下:
<span class="kw4">int</span> request_firmware_nowait<span class="br0">(</span> <span class="kw4">struct</span> module <span class="sy0">*</span>module<span class="sy0">,</span> <span class="kw4">int</span> uevent<span class="sy0">,</span> <span class="kw4">const</span> <span class="kw4">char</span> <span class="sy0">*</span>name<span class="sy0">,</span> <span class="kw4">struct</span> device <span class="sy0">*</span>device<span class="sy0">,</span> <span class="kw4">void</span> <span class="sy0">*</span>context<span class="sy0">,</span> <span class="kw4">void</span> <span class="br0">(</span><span class="sy0">*</span>cont<span class="br0">)</span><span class="br0">(</span><span class="kw4">const</span> <span class="kw4">struct</span> firmware <span class="sy0">*</span>fw<span class="sy0">,</span> <span class="kw4">void</span> <span class="sy0">*</span>context<span class="br0">)</span><span class="br0">)</span>
当驱动程序需要使用固件驱动时,在驱动程序的初始化化过程中需要加下如下的代码:
<span class="kw1">if</span><span class="br0">(</span>request_firmware<span class="br0">(</span><span class="sy0">&</span>fw_entry<span class="sy0">,</span> $FIRMWARE<span class="sy0">,</span> device<span class="br0">)</span> <span class="sy0">==</span> <span class="nu0">0</span><span class="br0">)</span> <span class="coMULTI">/*从用户空间请求映像数据*/</span> <span class="coMULTI">/*将固件映像拷贝到硬件的存储器,拷贝函数由用户编写*/</span> copy_fw_to_device<span class="br0">(</span>fw_entry<span class="sy0">-></span>data<span class="sy0">,</span> fw_entry<span class="sy0">-></span>size<span class="br0">)</span><span class="sy0">;</span> release<span class="br0">(</span>fw_entry<span class="br0">)</span><span class="sy0">;</span>
用户还需要在用户空间提供脚本通过文件系统sysfs中的文件data将固件映像文件读入到内核的缓冲区中。脚本样例列出如下:
<span class="co2">#变量$DEVPATH(固件设备的路径)和$FIRMWARE(固件映像名)应已在环境变量中提供</span> HOTPLUG_FW_DIR<span class="sy0">=/</span>usr<span class="sy0">/</span>lib<span class="sy0">/</span>hotplug<span class="sy0">/</span>firmware<span class="sy0">/</span> <span class="co2">#固件映像文件所在目录</span> echo <span class="nu0">1</span> <span class="sy0">></span> <span class="sy0">/</span>sys<span class="sy0">/</span>$DEVPATH<span class="sy0">/</span>loading cat $HOTPLUG_FW_DIR<span class="sy0">/</span>$FIRMWARE <span class="sy0">></span> <span class="sy0">/</span>sysfs<span class="sy0">/</span>$DEVPATH<span class="sy0">/</span>data echo <span class="nu0">0</span> <span class="sy0">></span> <span class="sy0">/</span>sys<span class="sy0">/</span>$DEVPATH<span class="sy0">/</span>loading
函数request_firmware请求从用户空间拷贝固件映像文件到内核缓冲区。该函数的工作流程列出如下:
(1)在文件系统sysfs中创建文件/sys/class/firmware/xxx/loading和data,"xxx"表示固件的名字,给文件loading和data附加读写函数,设置文件属性,文件loading表示开/关固件映像文件装载功能;文件data的写操作将映像文件的数据写入内核缓冲区,读操作从内核缓冲区读取数据。
(2)将添加固件的uevent事件(即"add")通过内核对象模型发送到用户空间。
(3)用户空间管理uevent事件的后台进程udevd接收到事件后,查找udev规则文件,运行规则所定义的动作,与固件相关的规则列出如下:
<span class="sy0">^-^</span>$ <span class="sy0">/</span>etc<span class="sy0">/</span>udev<span class="sy0">/</span>rules.<span class="me1">d</span><span class="sy0">/</span><span class="nu0">50</span><span class="sy0">-</span>udev<span class="sy0">-</span><span class="kw1">default</span>.<span class="me1">rules</span> …… <span class="co2"># firmware class requests</span> SUBSYSTEM<span class="sy0">==</span><span class="st0">"firmware"</span><span class="sy0">,</span> ACTION<span class="sy0">==</span><span class="st0">"add"</span><span class="sy0">,</span> RUN<span class="sy0">+=</span><span class="st0">"firmware.sh"</span> ……
从上述规则可以看出,固件添加事件将引起运行脚本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中):
<span class="kw4">int</span> request_firmware<span class="br0">(</span><span class="kw4">const</span> <span class="kw4">struct</span> firmware <span class="sy0">**</span>firmware_p<span class="sy0">,</span> <span class="kw4">const</span> <span class="kw4">char</span> <span class="sy0">*</span>name<span class="sy0">,</span> <span class="kw4">struct</span> device <span class="sy0">*</span>device<span class="br0">)</span> <span class="br0">{</span> <span class="kw4">int</span> uevent <span class="sy0">=</span> <span class="nu0">1</span><span class="sy0">;</span> <span class="kw1">return</span> _request_firmware<span class="br0">(</span>firmware_p<span class="sy0">,</span> name<span class="sy0">,</span> device<span class="sy0">,</span> uevent<span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> <span class="kw4">static</span> <span class="kw4">int</span> _request_firmware<span class="br0">(</span><span class="kw4">const</span> <span class="kw4">struct</span> firmware <span class="sy0">**</span>firmware_p<span class="sy0">,</span> <span class="kw4">const</span> <span class="kw4">char</span> <span class="sy0">*</span>name<span class="sy0">,</span> <span class="kw4">struct</span> device <span class="sy0">*</span>device<span class="sy0">,</span> <span class="kw4">int</span> uevent<span class="br0">)</span> <span class="br0">{</span> <span class="kw4">struct</span> device <span class="sy0">*</span>f_dev<span class="sy0">;</span> <span class="kw4">struct</span> firmware_priv <span class="sy0">*</span>fw_priv<span class="sy0">;</span> <span class="kw4">struct</span> firmware <span class="sy0">*</span>firmware<span class="sy0">;</span> <span class="kw4">struct</span> builtin_fw <span class="sy0">*</span>builtin<span class="sy0">;</span> <span class="kw4">int</span> retval<span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span><span class="sy0">!</span>firmware_p<span class="br0">)</span> <span class="kw1">return</span> <span class="sy0">-</span>EINVAL<span class="sy0">;</span> <span class="sy0">*</span>firmware_p <span class="sy0">=</span> firmware <span class="sy0">=</span> kzalloc<span class="br0">(</span><span class="kw4">sizeof</span><span class="br0">(</span><span class="sy0">*</span>firmware<span class="br0">)</span><span class="sy0">,</span> GFP_KERNEL<span class="br0">)</span><span class="sy0">;</span> …… <span class="co1">//省略出错保护</span> <span class="coMULTI">/*如果固件映像在内部__start_builtin_fw指向的地址,拷贝数据到缓冲区*/</span> <span class="kw1">for</span> <span class="br0">(</span>builtin <span class="sy0">=</span> __start_builtin_fw<span class="sy0">;</span> builtin <span class="sy0">!=</span> __end_builtin_fw<span class="sy0">;</span> builtin<span class="sy0">++</span><span class="br0">)</span> <span class="br0">{</span> <span class="kw1">if</span> <span class="br0">(</span>strcmp<span class="br0">(</span>name<span class="sy0">,</span> builtin<span class="sy0">-></span>name<span class="br0">)</span><span class="br0">)</span> <span class="kw1">continue</span><span class="sy0">;</span> dev_info<span class="br0">(</span>device<span class="sy0">,</span> <span class="st0">"firmware: using built-in firmware %s<span class="es1">\n</span>"</span><span class="sy0">,</span> name<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*打印信息*/</span> firmware<span class="sy0">-></span>size <span class="sy0">=</span> builtin<span class="sy0">-></span>size<span class="sy0">;</span> firmware<span class="sy0">-></span>data <span class="sy0">=</span> builtin<span class="sy0">-></span>data<span class="sy0">;</span> <span class="kw1">return</span> <span class="nu0">0</span><span class="sy0">;</span> <span class="br0">}</span> ……<span class="co1">//省略打印信息</span> <span class="coMULTI">/*在文件系统sysfs建立xxx目录及文件*/</span> retval <span class="sy0">=</span> fw_setup_device<span class="br0">(</span>firmware<span class="sy0">,</span> <span class="sy0">&</span>f_dev<span class="sy0">,</span> name<span class="sy0">,</span> device<span class="sy0">,</span> uevent<span class="br0">)</span><span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span>retval<span class="br0">)</span> <span class="kw1">goto</span> error_kfree_fw<span class="sy0">;</span> fw_priv <span class="sy0">=</span> dev_get_drvdata<span class="br0">(</span>f_dev<span class="br0">)</span><span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span>uevent<span class="br0">)</span> <span class="br0">{</span> <span class="kw1">if</span> <span class="br0">(</span>loading_timeout <span class="sy0">></span> <span class="nu0">0</span><span class="br0">)</span> <span class="br0">{</span> <span class="coMULTI">/*加载定时器*/</span> fw_priv<span class="sy0">-></span>timeout.<span class="me1">expires</span> <span class="sy0">=</span> jiffies <span class="sy0">+</span> loading_timeout <span class="sy0">*</span> HZ<span class="sy0">;</span> add_timer<span class="br0">(</span><span class="sy0">&</span>fw_priv<span class="sy0">-></span>timeout<span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> kobject_uevent<span class="br0">(</span><span class="sy0">&</span>f_dev<span class="sy0">-></span>kobj<span class="sy0">,</span> KOBJ_ADD<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*发送事件KOBJ_ADD*/</span> wait_for_completion<span class="br0">(</span><span class="sy0">&</span>fw_priv<span class="sy0">-></span>completion<span class="br0">)</span><span class="sy0">;</span> set_bit<span class="br0">(</span>FW_STATUS_DONE<span class="sy0">,</span> <span class="sy0">&</span>fw_priv<span class="sy0">-></span>status<span class="br0">)</span><span class="sy0">;</span> del_timer_sync<span class="br0">(</span><span class="sy0">&</span>fw_priv<span class="sy0">-></span>timeout<span class="br0">)</span><span class="sy0">;</span> <span class="br0">}</span> <span class="kw1">else</span> wait_for_completion<span class="br0">(</span><span class="sy0">&</span>fw_priv<span class="sy0">-></span>completion<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*等待完成固件映像数据的装载*/</span> mutex_lock<span class="br0">(</span><span class="sy0">&</span>fw_lock<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*如果装载出错,释放缓冲区*/</span> <span class="kw1">if</span> <span class="br0">(</span><span class="sy0">!</span>fw_priv<span class="sy0">-></span>fw<span class="sy0">-></span>size <span class="sy0">||</span> test_bit<span class="br0">(</span>FW_STATUS_ABORT<span class="sy0">,</span> <span class="sy0">&</span>fw_priv<span class="sy0">-></span>status<span class="br0">)</span><span class="br0">)</span> <span class="br0">{</span> retval <span class="sy0">=</span> <span class="sy0">-</span>ENOENT<span class="sy0">;</span> release_firmware<span class="br0">(</span>fw_priv<span class="sy0">-></span>fw<span class="br0">)</span><span class="sy0">;</span> <span class="sy0">*</span>firmware_p <span class="sy0">=</span> NULL<span class="sy0">;</span> <span class="br0">}</span> fw_priv<span class="sy0">-></span>fw <span class="sy0">=</span> NULL<span class="sy0">;</span> mutex_unlock<span class="br0">(</span><span class="sy0">&</span>fw_lock<span class="br0">)</span><span class="sy0">;</span> device_unregister<span class="br0">(</span>f_dev<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*在文件系统sysfs注销xxx目录*/</span> <span class="kw1">goto</span> out<span class="sy0">;</span> error_kfree_fw<span class="sy0">:</span> kfree<span class="br0">(</span>firmware<span class="br0">)</span><span class="sy0">;</span> <span class="sy0">*</span>firmware_p <span class="sy0">=</span> NULL<span class="sy0">;</span> out<span class="sy0">:</span> <span class="kw1">return</span> retval<span class="sy0">;</span> <span class="br0">}</span>
函数fw_setup_device在文件系统sysfs中创建固件设备的目录和文件,其列出如下:
<span class="kw4">static</span> <span class="kw4">int</span> fw_setup_device<span class="br0">(</span><span class="kw4">struct</span> firmware <span class="sy0">*</span>fw<span class="sy0">,</span> <span class="kw4">struct</span> device <span class="sy0">**</span>dev_p<span class="sy0">,</span> <span class="kw4">const</span> <span class="kw4">char</span> <span class="sy0">*</span>fw_name<span class="sy0">,</span> <span class="kw4">struct</span> device <span class="sy0">*</span>device<span class="sy0">,</span> <span class="kw4">int</span> uevent<span class="br0">)</span> <span class="br0">{</span> <span class="kw4">struct</span> device <span class="sy0">*</span>f_dev<span class="sy0">;</span> <span class="kw4">struct</span> firmware_priv <span class="sy0">*</span>fw_priv<span class="sy0">;</span> <span class="kw4">int</span> retval<span class="sy0">;</span> <span class="sy0">*</span>dev_p <span class="sy0">=</span> NULL<span class="sy0">;</span> retval <span class="sy0">=</span> fw_register_device<span class="br0">(</span><span class="sy0">&</span>f_dev<span class="sy0">,</span> fw_name<span class="sy0">,</span> device<span class="br0">)</span><span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span>retval<span class="br0">)</span> <span class="kw1">goto</span> out<span class="sy0">;</span> …… fw_priv <span class="sy0">=</span> dev_get_drvdata<span class="br0">(</span>f_dev<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*从设备结构中得到私有数据结构*/</span> fw_priv<span class="sy0">-></span>fw <span class="sy0">=</span> fw<span class="sy0">;</span> retval <span class="sy0">=</span> sysfs_create_bin_file<span class="br0">(</span><span class="sy0">&</span>f_dev<span class="sy0">-></span>kobj<span class="sy0">,</span> <span class="sy0">&</span>fw_priv<span class="sy0">-></span>attr_data<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*在sysfs中创建可执行文件*/</span> …… <span class="co1">//省略出错保护</span> retval <span class="sy0">=</span> device_create_file<span class="br0">(</span>f_dev<span class="sy0">,</span> <span class="sy0">&</span>dev_attr_loading<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*在sysfs中创建一般文件*/</span> …… <span class="co1">//省略出错保护</span> <span class="kw1">if</span> <span class="br0">(</span>uevent<span class="br0">)</span> f_dev<span class="sy0">-></span>uevent_suppress <span class="sy0">=</span> <span class="nu0">0</span><span class="sy0">;</span> <span class="sy0">*</span>dev_p <span class="sy0">=</span> f_dev<span class="sy0">;</span> <span class="kw1">goto</span> out<span class="sy0">;</span> error_unreg<span class="sy0">:</span> device_unregister<span class="br0">(</span>f_dev<span class="br0">)</span><span class="sy0">;</span> out<span class="sy0">:</span> <span class="kw1">return</span> retval<span class="sy0">;</span> <span class="br0">}</span>
函数fw_register_device注册设备,在文件系统sysfs中创建固件设备对应的设备类,存放固件驱动程序私有数据。其列出如下:
<span class="kw4">static</span> <span class="kw4">int</span> fw_register_device<span class="br0">(</span><span class="kw4">struct</span> device <span class="sy0">**</span>dev_p<span class="sy0">,</span> <span class="kw4">const</span> <span class="kw4">char</span> <span class="sy0">*</span>fw_name<span class="sy0">,</span> <span class="kw4">struct</span> device <span class="sy0">*</span>device<span class="br0">)</span> <span class="br0">{</span> <span class="kw4">int</span> retval<span class="sy0">;</span> <span class="kw4">struct</span> firmware_priv <span class="sy0">*</span>fw_priv <span class="sy0">=</span> kzalloc<span class="br0">(</span><span class="kw4">sizeof</span><span class="br0">(</span><span class="sy0">*</span>fw_priv<span class="br0">)</span><span class="sy0">,</span> GFP_KERNEL<span class="br0">)</span><span class="sy0">;</span> <span class="kw4">struct</span> device <span class="sy0">*</span>f_dev <span class="sy0">=</span> kzalloc<span class="br0">(</span><span class="kw4">sizeof</span><span class="br0">(</span><span class="sy0">*</span>f_dev<span class="br0">)</span><span class="sy0">,</span> GFP_KERNEL<span class="br0">)</span><span class="sy0">;</span> <span class="sy0">*</span>dev_p <span class="sy0">=</span> NULL<span class="sy0">;</span> …… <span class="co1">//省略出错保护</span> init_completion<span class="br0">(</span><span class="sy0">&</span>fw_priv<span class="sy0">-></span>completion<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*初始化completion机制的等待队列*/</span> fw_priv<span class="sy0">-></span>attr_data <span class="sy0">=</span> firmware_attr_data_tmpl<span class="sy0">;</span> <span class="coMULTI">/*设置文件的属性结构*/</span> strlcpy<span class="br0">(</span>fw_priv<span class="sy0">-></span>fw_id<span class="sy0">,</span> fw_name<span class="sy0">,</span> FIRMWARE_NAME_MAX<span class="br0">)</span><span class="sy0">;</span> fw_priv<span class="sy0">-></span>timeout.<span class="kw2">function</span> <span class="sy0">=</span> firmware_class_timeout<span class="sy0">;</span> <span class="coMULTI">/*超时装载退出函数*/</span> fw_priv<span class="sy0">-></span>timeout.<span class="me1">data</span> <span class="sy0">=</span> <span class="br0">(</span>u_long<span class="br0">)</span> fw_priv<span class="sy0">;</span> init_timer<span class="br0">(</span><span class="sy0">&</span>fw_priv<span class="sy0">-></span>timeout<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*初始化定时器*/</span> fw_setup_device_id<span class="br0">(</span>f_dev<span class="sy0">,</span> device<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*拷贝device ->bus_id到f_dev中*/</span> f_dev<span class="sy0">-></span>parent <span class="sy0">=</span> device<span class="sy0">;</span> f_dev<span class="sy0">-></span>class <span class="sy0">=</span> <span class="sy0">&</span>firmware_class<span class="sy0">;</span> <span class="coMULTI">/*设备类实例*/</span> dev_set_drvdata<span class="br0">(</span>f_dev<span class="sy0">,</span> fw_priv<span class="br0">)</span><span class="sy0">;</span> <span class="coMULTI">/*存放设备驱动的私有数据:f_dev ->driver_data = fw_priv*/</span> f_dev<span class="sy0">-></span>uevent_suppress <span class="sy0">=</span> <span class="nu0">1</span><span class="sy0">;</span> retval <span class="sy0">=</span> device_register<span class="br0">(</span>f_dev<span class="br0">)</span><span class="sy0">;</span> <span class="kw1">if</span> <span class="br0">(</span>retval<span class="br0">)</span> <span class="br0">{</span> dev_err<span class="br0">(</span>device<span class="sy0">,</span> <span class="st0">"%s: device_register failed<span class="es1">\n</span>"</span><span class="sy0">,</span> __func__<span class="br0">)</span><span class="sy0">;</span> <span class="kw1">goto</span> error_kfree<span class="sy0">;</span> <span class="br0">}</span> <span class="sy0">*</span>dev_p <span class="sy0">=</span> f_dev<span class="sy0">;</span> <span class="kw1">return</span> <span class="nu0">0</span><span class="sy0">;</span> …… <span class="co1">//省略了出错保护</span> <span class="br0">}</span> <span class="coMULTI">/*文件属性结构实例,设置文件系统sysfs中data文件的模式和读/写函数*/</span> <span class="kw4">static</span> <span class="kw4">struct</span> bin_attribute firmware_attr_data_tmpl <span class="sy0">=</span> <span class="br0">{</span> .<span class="me1">attr</span> <span class="sy0">=</span> <span class="br0">{</span>.<span class="me1">name</span> <span class="sy0">=</span> <span class="st0">"data"</span><span class="sy0">,</span> .<span class="me1">mode</span> <span class="sy0">=</span> <span class="nu8">0644</span><span class="br0">}</span><span class="sy0">,</span> .<span class="me1">size</span> <span class="sy0">=</span> <span class="nu0">0</span><span class="sy0">,</span> .<span class="me1">read</span> <span class="sy0">=</span> firmware_data_read<span class="sy0">,</span> <span class="coMULTI">/*从内核缓冲区读出数据*/</span> .<span class="me1">write</span> <span class="sy0">=</span> firmware_data_write<span class="sy0">,</span> <span class="coMULTI">/*用于将固件映像文件的数据写入到内核缓冲区*/</span> <span class="br0">}</span><span class="sy0">;</span> <span class="coMULTI">/*设备类结构实例,含有发送uevent事件函数和释放设备的函数*/</span> <span class="kw4">static</span> <span class="kw4">struct</span> class firmware_class <span class="sy0">=</span> <span class="br0">{</span> .<span class="me1">name</span> <span class="sy0">=</span> <span class="st0">"firmware"</span><span class="sy0">,</span> <span class="coMULTI">/*设备类的名字*/</span> .<span class="me1">dev_uevent</span> <span class="sy0">=</span> firmware_uevent<span class="sy0">,</span> <span class="coMULTI">/*设备发送uevent事件的函数*/</span> .<span class="me1">dev_release</span> <span class="sy0">=</span> fw_dev_release<span class="sy0">,</span> <span class="coMULTI">/*释放设备的函数*/</span> <span class="br0">}</span><span class="sy0">;</span>