12.7 Linux设备驱动的固件加载
一个外设的运行可能依赖于固件,如某公司的WiFi 模块,在启动前需要加载固件。传统的设备驱动将固件的二进制码作为一个数组直接编译进目标代码,在 Linux 2.6 中,有一套成熟的固件加载流程。
首先,申请固件的驱动程序发起如下请求:
linux/firmware.h
int request_firmware(const struct firmware **fw, const char *name, struct device *device);
fw: 用于保存申请到的固件
name: 固件名
device:申请固件的设备的结构体指针
发起这个调用后,内核的udevd 会配合将固件通过对应的sysfs 结点写入内核。之后内核将收到的firmware 写入外设,最后通过如下API 释放请求:
void release_firmware(const struct firmware *fw);
一个典型的例子drivers/media/video/cx25840/cx25840-firmware.c 的cx25840_loadfw()函数,如代码清单12.24 所示。
代码清单 12.24 Linux 设备驱动申请 firmware 的例子
#define FWDEV(x) &((x)->dev)
static int check_fw_load(struct i2c_client *client, int size)
{
/* DL_ADDR_HB DL_ADDR_LB */
int s = cx25840_read(client, 0x801) << 8;
s |= cx25840_read(client, 0x800);
if (size != s) {
v4l_err(client, "firmware %s load failed\n",
get_fw_name(client));
return -EINVAL;
}
v4l_info(client, "loaded %s firmware (%d bytes)\n",
get_fw_name(client), size);
return 0;
}
int cx25840_loadfw(struct i2c_client *client)
{
struct cx25840_state *state = to_state(i2c_get_clientdata(client));
const struct firmware *fw = NULL;
u8 buffer[FWSEND];
const u8 *ptr;
const char *fwname = get_fw_name(client);
int size, retval;
int MAX_BUF_SIZE = FWSEND;
u32 gpio_oe = 0, gpio_da = 0;
if (is_cx2388x(state)) {
/* Preserve the GPIO OE and output bits */
gpio_oe = cx25840_read(client, 0x160);
gpio_da = cx25840_read(client, 0x164);
}
if (is_cx231xx(state) && MAX_BUF_SIZE > 16) {
v4l_err(client, " Firmware download size changed to 16 bytes max length\n");
MAX_BUF_SIZE = 16; /* cx231xx cannot accept more than 16 bytes at a time */
}
/* 申请 firmware */
if (request_firmware(&fw, fwname, FWDEV(client)) != 0) {
v4l_err(client, "unable to open firmware %s\n", fwname);
return -EINVAL;
}
/* 开始加载 firmware 到设备 */
start_fw_load(client);
buffer[0] = 0x08;
buffer[1] = 0x02;
size = fw->size;
ptr = fw->data;
while (size > 0) {
int len = min(MAX_BUF_SIZE - 2, size);
memcpy(buffer + 2, ptr, len);
retval = fw_write(client, buffer, len + 2);
if (retval < 0) {
release_firmware(fw);
return retval;
}
size -= len;
ptr += len;
}
/* 结束 firmware加载 */
end_fw_load(client);
size = fw->size;
release_firmware(fw);/* 释放firmware */
if (is_cx2388x(state)) {
/* Restore GPIO configuration after f/w load */
cx25840_write(client, 0x160, gpio_oe);
cx25840_write(client, 0x164, gpio_da);
}
return check_fw_load(client, size);
}