《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》
第 21 章 Linux 设备驱动的固件加载
参考:宋宝华 著,机械工业出版社,2015年版
21.1 固件加载机制
21.1.1 什么是固件
固件(Firmware)是存储在设备内部或由驱动程序加载到设备中的二进制程序或数据,用于控制硬件设备的行为。固件与驱动程序的区别:
固件 vs 驱动程序:
驱动程序(Driver):
- 运行在 CPU(主机)上
- 控制硬件设备的接口
- 以内核模块形式存在(.ko 文件)
- 由 Linux 内核执行
固件(Firmware):
- 运行在设备内部的处理器上(DSP、MCU、FPGA 等)
- 实现设备的具体功能
- 以二进制文件形式存在(.bin、.fw 文件)
- 由驱动程序加载到设备
典型需要固件的设备:
- Wi-Fi 网卡(如 Broadcom BCM4329 需要 brcmfmac.bin)
- 蓝牙芯片(需要 BT 固件)
- GPU(显卡固件)
- DVB 调谐器(数字电视)
- 网络处理器(NPU)
- 音频 DSP
- USB 设备(某些 USB 设备需要先上传固件才能工作)
21.1.2 固件加载的必要性
为什么需要固件加载机制?
问题一:固件不能内置在内核中
- 固件通常是厂商的专有代码,不能开源
- 固件体积较大,内置会增大内核体积
- 不同硬件版本可能需要不同固件
问题二:固件需要在设备初始化时加载
- 设备上电后,内部处理器需要固件才能工作
- 驱动程序必须在设备可用之前完成固件加载
Linux 固件加载机制的解决方案:
1. 固件文件存储在用户空间(/lib/firmware/)
2. 驱动程序通过内核 API 请求固件
3. 内核通过 udev 通知用户空间
4. 用户空间将固件文件内容传递给内核
5. 驱动程序将固件写入设备
21.1.3 固件加载的工作流程
Linux 固件加载完整流程:
驱动程序调用 request_firmware()
↓
内核检查固件缓存(是否已加载过)
↓ 未缓存
内核通过 uevent 通知 udev
↓
udev 在 /lib/firmware/ 中查找固件文件
↓ 找到
udev 将固件内容写入 /sys/class/firmware/xxx/data
↓
内核将固件数据传递给驱动程序
↓
驱动程序将固件写入设备
↓
request_firmware() 返回
固件文件搜索路径(按优先级):
1. /lib/firmware/updates/$(uname -r)/ ← 内核版本特定更新
2. /lib/firmware/updates/ ← 通用更新
3. /lib/firmware/$(uname -r)/ ← 内核版本特定
4. /lib/firmware/ ← 通用固件目录
固件文件命名示例:
/lib/firmware/brcm/brcmfmac43430-sdio.bin ← Broadcom Wi-Fi
/lib/firmware/rtl_nic/rtl8168g-3.fw ← Realtek 网卡
/lib/firmware/iwlwifi-7260-17.ucode ← Intel Wi-Fi
/lib/firmware/ath10k/QCA9377/hw1.0/firmware-5.bin ← Qualcomm Wi-Fi
21.1.4 固件加载的两种模式
固件加载模式:
模式一:同步加载(Synchronous)
驱动调用 request_firmware()
阻塞等待固件加载完成
适用于:probe 函数中,设备初始化时
模式二:异步加载(Asynchronous)
驱动调用 request_firmware_nowait()
立即返回,固件加载完成后调用回调函数
适用于:不能阻塞的上下文,或需要并行初始化
模式三:直接从内核内置(Built-in Firmware)
将固件编译进内核(CONFIG_EXTRA_FIRMWARE)
不需要用户空间参与
适用于:嵌入式系统,固件必须在早期启动时可用
21.2 固件加载 API
21.2.1 核心 API 详解
c
#include <linux/firmware.h>
/*
* struct firmware:固件数据结构
* 包含固件的大小和数据指针
*/
struct firmware {
size_t size; /* 固件大小(字节)*/
const u8 *data; /* 固件数据指针 */
struct page **pages; /* 固件数据的物理页(内部使用)*/
/* ... */
};
/*
* request_firmware:同步请求固件
*
* fw:指向 firmware 指针的指针(输出参数)
* name:固件文件名(相对于 /lib/firmware/)
* device:请求固件的设备(用于 uevent 通知)
*
* 返回:0(成功),负值(失败)
*
* 注意:
* - 此函数会阻塞,直到固件加载完成或超时
* - 只能在进程上下文中调用(不能在中断上下文)
* - 成功后必须调用 release_firmware() 释放
*/
int request_firmware(const struct firmware **fw,
const char *name,
struct device *device);
/*
* release_firmware:释放固件
* 必须在使用完固件后调用,释放内存
*/
void release_firmware(const struct firmware *fw);
/*
* request_firmware_nowait:异步请求固件
*
* module:所属模块(THIS_MODULE)
* uevent:是否发送 uevent(通常为 true)
* name:固件文件名
* device:请求固件的设备
* gfp:内存分配标志
* context:传递给回调函数的私有数据
* cont:固件加载完成后的回调函数
*
* 返回:0(成功提交请求),负值(失败)
*
* 注意:
* - 立即返回,不阻塞
* - 固件加载完成(成功或失败)后调用 cont 回调
* - 回调函数中 fw 为 NULL 表示加载失败
* - 回调函数中不需要调用 release_firmware(),内核自动释放
*/
int request_firmware_nowait(struct module *module,
bool uevent,
const char *name,
struct device *device,
gfp_t gfp,
void *context,
void (*cont)(const struct firmware *fw,
void *context));
/*
* request_firmware_direct:直接从文件系统加载(不通过 udev)
* 适用于早期启动阶段
*/
int request_firmware_direct(const struct firmware **fw,
const char *name,
struct device *device);
/*
* firmware_request_nowarn:请求固件,但固件不存在时不打印警告
* 适用于可选固件(没有固件也能工作)
*/
int firmware_request_nowarn(const struct firmware **fw,
const char *name,
struct device *device);
21.2.2 固件加载的完整使用模式
模式一:同步加载(最常用)
c
/*
* 同步固件加载示例
* 在 probe 函数中加载固件并写入设备
*/
static int my_device_probe(struct platform_device *pdev)
{
const struct firmware *fw;
int ret;
/* 请求固件(阻塞等待)*/
ret = request_firmware(&fw, "my_device/firmware.bin", &pdev->dev);
if (ret) {
dev_err(&pdev->dev, "加载固件失败:%d\n", ret);
dev_err(&pdev->dev, "请确认 /lib/firmware/my_device/firmware.bin 存在\n");
return ret;
}
dev_info(&pdev->dev, "固件加载成功,大小:%zu 字节\n", fw->size);
/* 验证固件(可选)*/
if (fw->size < sizeof(struct fw_header)) {
dev_err(&pdev->dev, "固件文件太小\n");
release_firmware(fw);
return -EINVAL;
}
/* 将固件写入设备 */
ret = my_device_load_firmware(pdev, fw->data, fw->size);
if (ret) {
dev_err(&pdev->dev, "写入固件失败:%d\n", ret);
release_firmware(fw);
return ret;
}
/* 释放固件内存(固件已写入设备,不再需要)*/
release_firmware(fw);
dev_info(&pdev->dev, "固件加载完成,设备就绪\n");
return 0;
}
模式二:异步加载
c
/*
* 异步固件加载示例
* 适用于不能阻塞的场景
*/
/* 固件加载完成回调函数 */
static void my_fw_callback(const struct firmware *fw, void *context)
{
struct my_device *dev = context;
if (!fw) {
dev_err(dev->dev, "固件加载失败(文件不存在或超时)\n");
/* 设置设备为错误状态 */
dev->state = DEV_STATE_ERROR;
return;
}
dev_info(dev->dev, "固件加载成功,大小:%zu 字节\n", fw->size);
/* 将固件写入设备 */
int ret = my_device_load_firmware(dev, fw->data, fw->size);
if (ret) {
dev_err(dev->dev, "写入固件失败:%d\n", ret);
dev->state = DEV_STATE_ERROR;
} else {
dev->state = DEV_STATE_READY;
dev_info(dev->dev, "设备就绪\n");
}
/* 注意:不需要调用 release_firmware(),内核自动释放 */
}
static int my_device_probe(struct platform_device *pdev)
{
struct my_device *dev;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->dev = &pdev->dev;
dev->state = DEV_STATE_LOADING;
/* 异步请求固件(立即返回)*/
ret = request_firmware_nowait(THIS_MODULE,
true, /* 发送 uevent */
"my_device/firmware.bin",
&pdev->dev,
GFP_KERNEL,
dev, /* 传给回调的参数 */
my_fw_callback); /* 回调函数 */
if (ret) {
dev_err(&pdev->dev, "提交固件请求失败:%d\n", ret);
return ret;
}
platform_set_drvdata(pdev, dev);
/* 立即返回,固件加载在后台进行 */
dev_info(&pdev->dev, "固件加载请求已提交,等待完成...\n");
return 0;
}
模式三:可选固件(没有固件也能工作)
c
static int my_device_probe(struct platform_device *pdev)
{
const struct firmware *fw = NULL;
int ret;
/* 尝试加载可选固件(不打印警告)*/
ret = firmware_request_nowarn(&fw, "my_device/optional.bin", &pdev->dev);
if (ret == 0) {
dev_info(&pdev->dev, "可选固件已加载,启用高级功能\n");
my_device_enable_advanced_features(pdev, fw->data, fw->size);
release_firmware(fw);
} else {
dev_info(&pdev->dev, "可选固件未找到,使用基本功能\n");
}
/* 继续初始化(不依赖可选固件)*/
return my_device_basic_init(pdev);
}
21.2.3 固件验证
在将固件写入设备之前,应该验证固件的合法性:
c
/*
* 固件头部结构(自定义格式)
*/
struct my_fw_header {
u8 magic[4]; /* 魔数:{'M', 'Y', 'F', 'W'} */
u32 version; /* 固件版本 */
u32 data_size; /* 数据大小 */
u32 checksum; /* 校验和 */
u8 reserved[16]; /* 保留 */
} __packed;
static int verify_firmware(struct device *dev,
const u8 *data, size_t size)
{
const struct my_fw_header *hdr = (const struct my_fw_header *)data;
u32 checksum = 0;
size_t i;
/* 检查最小大小 */
if (size < sizeof(struct my_fw_header)) {
dev_err(dev, "固件文件太小:%zu < %zu\n",
size, sizeof(struct my_fw_header));
return -EINVAL;
}
/* 验证魔数 */
if (memcmp(hdr->magic, "MYFW", 4) != 0) {
dev_err(dev, "固件魔数错误\n");
return -EINVAL;
}
/* 验证版本 */
if (le32_to_cpu(hdr->version) < MIN_FW_VERSION) {
dev_err(dev, "固件版本太旧:%u < %u\n",
le32_to_cpu(hdr->version), MIN_FW_VERSION);
return -EINVAL;
}
/* 验证数据大小 */
if (size != sizeof(struct my_fw_header) + le32_to_cpu(hdr->data_size)) {
dev_err(dev, "固件大小不匹配\n");
return -EINVAL;
}
/* 计算并验证校验和 */
for (i = 0; i < size - 4; i++)
checksum += data[i];
if (checksum != le32_to_cpu(hdr->checksum)) {
dev_err(dev, "固件校验和错误\n");
return -EINVAL;
}
dev_info(dev, "固件验证通过,版本 %u\n", le32_to_cpu(hdr->version));
return 0;
}
21.2.4 固件缓存机制
Linux 内核提供了固件缓存机制,避免重复加载:
c
/*
* 固件缓存相关 API
*/
/* 将固件添加到缓存(系统挂起前调用,避免挂起后无法访问文件系统)*/
void fw_pm_notify(void);
/* 固件缓存的工作原理:
* 1. 系统正常运行时:固件从 /lib/firmware/ 加载
* 2. 系统挂起前:内核将已加载的固件缓存到内存
* 3. 系统恢复后:驱动可以从缓存中获取固件,无需重新加载
*
* 驱动开发者通常不需要直接操作缓存,内核自动处理
*/
/* 查看固件缓存状态 */
/* ls /sys/class/firmware/ */
/* cat /sys/class/firmware/timeout */ /* 固件加载超时时间(秒)*/
21.3 固件加载实例
21.3.1 实例一:Wi-Fi 网卡固件加载
以 Realtek RTL8723BS Wi-Fi 芯片为例,展示完整的固件加载流程:
c
/*
* rtl8723bs_fw.c ------ RTL8723BS Wi-Fi 固件加载驱动(简化版)
*
* RTL8723BS 是一款 SDIO 接口的 Wi-Fi + 蓝牙组合芯片
* 需要加载固件才能工作
* 固件文件:/lib/firmware/rtlwifi/rtl8723bs_nic.bin
*/
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/mmc/sdio_func.h>
#include <linux/netdevice.h>
#define RTL8723BS_FW_NAME "rtlwifi/rtl8723bs_nic.bin"
#define RTL8723BS_FW_MAGIC 0x8723
#define RTL8723BS_FW_MIN_SIZE 64
/* 固件头部格式(RTL8723BS 专用)*/
struct rtl8723bs_fw_header {
u8 signature[8]; /* 固件签名 */
u32 version; /* 固件版本 */
u32 subversion; /* 固件子版本 */
u16 month; /* 编译月份 */
u8 date; /* 编译日期 */
u8 hour; /* 编译小时 */
u8 minute; /* 编译分钟 */
u16 ramcode_size; /* RAM 代码大小 */
u8 foundry; /* 制造商 */
u8 chip_ver; /* 芯片版本 */
u16 dmem_size; /* DMEM 大小 */
u32 fw_priv_size; /* 固件私有数据大小 */
u16 efuse_size; /* EFUSE 大小 */
u32 fw_size; /* 固件总大小 */
u32 imem_size; /* IMEM 大小 */
u32 emem_size; /* EMEM 大小 */
u8 reserved[4];
} __packed;
/* 驱动私有数据 */
struct rtl8723bs_priv {
struct sdio_func *func;
struct net_device *ndev;
const struct firmware *fw;
bool fw_loaded;
struct work_struct fw_work; /* 固件加载工作 */
};
/* ── 固件写入设备 ──────────────────────────────────────────── */
static int rtl8723bs_write_firmware(struct rtl8723bs_priv *priv,
const u8 *fw_data, size_t fw_size)
{
struct sdio_func *func = priv->func;
const struct rtl8723bs_fw_header *hdr;
const u8 *code_ptr;
size_t code_size;
int ret;
hdr = (const struct rtl8723bs_fw_header *)fw_data;
/* 验证固件头部 */
if (fw_size < sizeof(*hdr)) {
dev_err(&func->dev, "固件文件太小\n");
return -EINVAL;
}
code_ptr = fw_data + sizeof(*hdr);
code_size = fw_size - sizeof(*hdr);
dev_info(&func->dev, "固件版本:%u.%u,大小:%zu 字节\n",
le32_to_cpu(hdr->version),
le32_to_cpu(hdr->subversion),
fw_size);
/* 步骤1:使能固件下载模式 */
ret = sdio_writeb(func, 0x01, 0x0080, NULL); /* 设置 FW_DL_EN */
if (ret) {
dev_err(&func->dev, "使能固件下载模式失败\n");
return ret;
}
/* 步骤2:分块写入固件数据 */
#define FW_BLOCK_SIZE 2048
size_t offset = 0;
while (offset < code_size) {
size_t block_size = min_t(size_t, code_size - offset, FW_BLOCK_SIZE);
ret = sdio_memcpy_toio(func, 0x8000 + offset,
(void *)(code_ptr + offset),
block_size);
if (ret) {
dev_err(&func->dev, "写入固件块失败,偏移 %zu\n", offset);
return ret;
}
offset += block_size;
}
/* 步骤3:等待固件启动完成 */
int timeout = 100; /* 最多等待 100ms */
while (timeout--) {
u8 status = sdio_readb(func, 0x0080, &ret);
if (ret) break;
if (status & 0x02) { /* FW_READY 位 */
dev_info(&func->dev, "固件启动成功\n");
return 0;
}
msleep(1);
}
dev_err(&func->dev, "固件启动超时\n");
return -ETIMEDOUT;
}
/* ── 固件加载工作函数 ──────────────────────────────────────── */
static void rtl8723bs_fw_work(struct work_struct *work)
{
struct rtl8723bs_priv *priv = container_of(work,
struct rtl8723bs_priv,
fw_work);
const struct firmware *fw;
int ret;
/* 同步加载固件 */
ret = request_firmware(&fw, RTL8723BS_FW_NAME, &priv->func->dev);
if (ret) {
dev_err(&priv->func->dev,
"加载固件 %s 失败:%d\n", RTL8723BS_FW_NAME, ret);
dev_err(&priv->func->dev,
"请安装固件包:apt-get install firmware-realtek\n");
return;
}
dev_info(&priv->func->dev, "固件 %s 加载成功,大小 %zu 字节\n",
RTL8723BS_FW_NAME, fw->size);
/* 将固件写入设备 */
ret = rtl8723bs_write_firmware(priv, fw->data, fw->size);
/* 释放固件内存 */
release_firmware(fw);
if (ret == 0) {
priv->fw_loaded = true;
dev_info(&priv->func->dev, "Wi-Fi 固件加载完成,设备就绪\n");
/* 继续初始化网络设备 */
/* rtl8723bs_init_netdev(priv); */
}
}
/* ── SDIO probe 函数 ──────────────────────────────────────────── */
static int rtl8723bs_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
struct rtl8723bs_priv *priv;
priv = devm_kzalloc(&func->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->func = func;
INIT_WORK(&priv->fw_work, rtl8723bs_fw_work);
sdio_set_drvdata(func, priv);
/* 在工作队列中异步加载固件(避免阻塞 probe)*/
schedule_work(&priv->fw_work);
dev_info(&func->dev, "RTL8723BS 驱动加载,等待固件...\n");
return 0;
}
static void rtl8723bs_remove(struct sdio_func *func)
{
struct rtl8723bs_priv *priv = sdio_get_drvdata(func);
/* 等待固件加载工作完成 */
cancel_work_sync(&priv->fw_work);
dev_info(&func->dev, "RTL8723BS 驱动卸载\n");
}
static const struct sdio_device_id rtl8723bs_ids[] = {
{ SDIO_DEVICE(0x024C, 0xB723) }, /* RTL8723BS */
{}
};
MODULE_DEVICE_TABLE(sdio, rtl8723bs_ids);
static struct sdio_driver rtl8723bs_driver = {
.name = "rtl8723bs",
.id_table = rtl8723bs_ids,
.probe = rtl8723bs_probe,
.remove = rtl8723bs_remove,
};
module_sdio_driver(rtl8723bs_driver);
MODULE_LICENSE("GPL v2");
MODULE_FIRMWARE(RTL8723BS_FW_NAME); /* 声明所需固件 */
MODULE_DESCRIPTION("RTL8723BS Wi-Fi 驱动");
21.3.2 实例二:USB 设备固件上传
某些 USB 设备(如 USB DVB 调谐器)在插入时需要先上传固件才能正常工作:
c
/*
* usb_fw_upload.c ------ USB 设备固件上传驱动
*
* 某些 USB 设备(如 Cypress FX2 系列)插入时以"空设备"状态出现
* 需要驱动上传固件后,设备重新枚举为功能设备
*/
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/firmware.h>
#define MY_USB_VID_EMPTY 0x1234 /* 空设备 VID(无固件)*/
#define MY_USB_PID_EMPTY 0x0001 /* 空设备 PID */
#define MY_USB_VID_LOADED 0x1234 /* 加载固件后的 VID */
#define MY_USB_PID_LOADED 0x0002 /* 加载固件后的 PID */
#define FW_NAME "my_usb_device/firmware.bin"
/* Cypress FX2 固件记录格式 */
struct fx2_ihex_record {
u16 addr; /* 目标地址 */
u8 len; /* 数据长度 */
u8 type; /* 记录类型(0=数据,1=结束)*/
u8 data[0]; /* 数据 */
} __packed;
/* 向 FX2 写入固件数据 */
static int fx2_write_firmware(struct usb_device *udev,
const u8 *fw_data, size_t fw_size)
{
const struct fx2_ihex_record *rec;
size_t offset = 0;
int ret;
/* 步骤1:复位 FX2(进入固件下载模式)*/
u8 reset = 1;
ret = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
0xA0, /* 厂商请求:写 CPUCS */
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0xE600, /* CPUCS 寄存器地址 */
0,
&reset, 1,
1000);
if (ret < 0) {
dev_err(&udev->dev, "复位 FX2 失败\n");
return ret;
}
/* 步骤2:逐条写入固件记录 */
while (offset < fw_size) {
rec = (const struct fx2_ihex_record *)(fw_data + offset);
if (rec->type == 1) /* 结束记录 */
break;
if (rec->type == 0) { /* 数据记录 */
ret = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
0xA0,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
le16_to_cpu(rec->addr),
0,
(void *)rec->data,
rec->len,
1000);
if (ret < 0) {
dev_err(&udev->dev, "写入固件数据失败,地址 0x%04x\n",
le16_to_cpu(rec->addr));
return ret;
}
}
offset += sizeof(*rec) + rec->len;
}
/* 步骤3:释放复位(启动固件)*/
reset = 0;
ret = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
0xA0,
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
0xE600,
0,
&reset, 1,
1000);
if (ret < 0) {
dev_err(&udev->dev, "启动固件失败\n");
return ret;
}
dev_info(&udev->dev, "固件上传完成,设备将重新枚举\n");
return 0;
}
/* USB probe 函数(空设备状态)*/
static int usb_fw_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
const struct firmware *fw;
int ret;
dev_info(&udev->dev, "检测到空 USB 设备,开始上传固件\n");
/* 加载固件 */
ret = request_firmware(&fw, FW_NAME, &udev->dev);
if (ret) {
dev_err(&udev->dev, "加载固件 %s 失败:%d\n", FW_NAME, ret);
return ret;
}
/* 上传固件到设备 */
ret = fx2_write_firmware(udev, fw->data, fw->size);
release_firmware(fw);
if (ret) {
dev_err(&udev->dev, "固件上传失败:%d\n", ret);
return ret;
}
/*
* 固件上传成功后,设备会重新枚举
* 新的 VID:PID 将由另一个驱动处理
* 此驱动的 probe 返回 -ENODEV 表示不接管此设备
*/
return -ENODEV;
}
static const struct usb_device_id usb_fw_ids[] = {
{ USB_DEVICE(MY_USB_VID_EMPTY, MY_USB_PID_EMPTY) },
{}
};
MODULE_DEVICE_TABLE(usb, usb_fw_ids);
static struct usb_driver usb_fw_driver = {
.name = "usb-fw-loader",
.id_table = usb_fw_ids,
.probe = usb_fw_probe,
.disconnect = NULL, /* 不需要 disconnect */
};
module_usb_driver(usb_fw_driver);
MODULE_LICENSE("GPL v2");
MODULE_FIRMWARE(FW_NAME);
MODULE_DESCRIPTION("USB 设备固件上传驱动");
21.3.3 实例三:FPGA 固件(比特流)加载
FPGA 需要加载比特流(Bitstream)才能工作:
c
/*
* fpga_fw.c ------ FPGA 比特流加载驱动
*
* 通过 SPI 接口将 FPGA 比特流加载到 FPGA 芯片
* 以 Xilinx Spartan-6 为例
*/
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/firmware.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#define FPGA_FW_NAME "fpga/spartan6_top.bin"
/* FPGA 配置引脚 */
struct fpga_config_pins {
int prog_b; /* PROG_B:低电平触发配置 */
int done; /* DONE:高电平表示配置完成 */
int init_b; /* INIT_B:低电平表示初始化完成 */
int cclk; /* CCLK:配置时钟(SPI CLK)*/
int din; /* DIN:配置数据(SPI MOSI)*/
};
struct fpga_priv {
struct spi_device *spi;
struct fpga_config_pins pins;
bool configured;
};
/* ── FPGA 配置流程 ──────────────────────────────────────────── */
static int fpga_configure(struct fpga_priv *priv,
const u8 *bitstream, size_t size)
{
struct spi_device *spi = priv->spi;
int ret;
int timeout;
dev_info(&spi->dev, "开始配置 FPGA,比特流大小:%zu 字节\n", size);
/* 步骤1:拉低 PROG_B,触发 FPGA 复位 */
gpio_set_value(priv->pins.prog_b, 0);
udelay(100);
/* 步骤2:等待 INIT_B 变低(FPGA 开始初始化)*/
timeout = 100;
while (gpio_get_value(priv->pins.init_b) && timeout--)
udelay(10);
if (timeout <= 0) {
dev_err(&spi->dev, "等待 INIT_B 低超时\n");
return -ETIMEDOUT;
}
/* 步骤3:释放 PROG_B */
gpio_set_value(priv->pins.prog_b, 1);
/* 步骤4:等待 INIT_B 变高(FPGA 初始化完成,准备接收配置)*/
timeout = 100;
while (!gpio_get_value(priv->pins.init_b) && timeout--)
udelay(10);
if (timeout <= 0) {
dev_err(&spi->dev, "等待 INIT_B 高超时\n");
return -ETIMEDOUT;
}
/* 步骤5:通过 SPI 发送比特流 */
ret = spi_write(spi, bitstream, size);
if (ret) {
dev_err(&spi->dev, "发送比特流失败:%d\n", ret);
return ret;
}
/* 步骤6:发送额外的时钟脉冲(确保配置完成)*/
u8 dummy[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
spi_write(spi, dummy, sizeof(dummy));
/* 步骤7:等待 DONE 变高(配置成功)*/
timeout = 1000;
while (!gpio_get_value(priv->pins.done) && timeout--)
udelay(100);
if (timeout <= 0) {
dev_err(&spi->dev, "FPGA 配置失败(DONE 未变高)\n");
return -EIO;
}
dev_info(&spi->dev, "FPGA 配置成功!\n");
priv->configured = true;
return 0;
}
/* ── SPI probe 函数 ──────────────────────────────────────────── */
static int fpga_probe(struct spi_device *spi)
{
struct fpga_priv *priv;
struct device_node *np = spi->dev.of_node;
const struct firmware *fw;
int ret;
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->spi = spi;
/* 从设备树获取配置引脚 */
priv->pins.prog_b = of_get_named_gpio(np, "prog-b-gpio", 0);
priv->pins.done = of_get_named_gpio(np, "done-gpio", 0);
priv->pins.init_b = of_get_named_gpio(np, "init-b-gpio", 0);
/* 申请 GPIO */
devm_gpio_request_one(&spi->dev, priv->pins.prog_b,
GPIOF_OUT_INIT_HIGH, "fpga-prog-b");
devm_gpio_request_one(&spi->dev, priv->pins.done,
GPIOF_IN, "fpga-done");
devm_gpio_request_one(&spi->dev, priv->pins.init_b,
GPIOF_IN, "fpga-init-b");
/* 配置 SPI 参数(FPGA 配置接口)*/
spi->max_speed_hz = 25000000; /* 25MHz */
spi->mode = SPI_MODE_0;
spi->bits_per_word = 8;
spi_setup(spi);
/* 加载 FPGA 比特流 */
ret = request_firmware(&fw, FPGA_FW_NAME, &spi->dev);
if (ret) {
dev_err(&spi->dev, "加载 FPGA 比特流 %s 失败:%d\n",
FPGA_FW_NAME, ret);
return ret;
}
/* 配置 FPGA */
ret = fpga_configure(priv, fw->data, fw->size);
release_firmware(fw);
if (ret) {
dev_err(&spi->dev, "FPGA 配置失败\n");
return ret;
}
spi_set_drvdata(spi, priv);
dev_info(&spi->dev, "FPGA 驱动加载成功\n");
return 0;
}
static const struct of_device_id fpga_of_match[] = {
{ .compatible = "xilinx,spartan6-spi" },
{}
};
MODULE_DEVICE_TABLE(of, fpga_of_match);
static struct spi_driver fpga_driver = {
.driver = {
.name = "fpga-loader",
.of_match_table = fpga_of_match,
},
.probe = fpga_probe,
};
module_spi_driver(fpga_driver);
MODULE_LICENSE("GPL v2");
MODULE_FIRMWARE(FPGA_FW_NAME);
MODULE_DESCRIPTION("FPGA 比特流加载驱动");
21.3.4 固件文件的管理
bash
# ── 固件文件的安装和管理 ──────────────────────────────────────
# 查看系统中已安装的固件
ls /lib/firmware/
ls /lib/firmware/rtlwifi/
ls /lib/firmware/brcm/
# 安装固件包(Ubuntu/Debian)
sudo apt-get install firmware-realtek # Realtek 固件
sudo apt-get install firmware-brcm80211 # Broadcom Wi-Fi 固件
sudo apt-get install firmware-iwlwifi # Intel Wi-Fi 固件
sudo apt-get install linux-firmware # 通用固件包
# 手动安装固件
sudo mkdir -p /lib/firmware/my_device
sudo cp my_firmware.bin /lib/firmware/my_device/firmware.bin
# 查看驱动需要的固件
modinfo rtl8723bs | grep firmware
# firmware: rtlwifi/rtl8723bs_nic.bin
# 查看固件加载状态
dmesg | grep firmware
# [ 5.123] rtl8723bs: Loading firmware rtlwifi/rtl8723bs_nic.bin
# [ 5.456] rtl8723bs: Firmware loaded successfully
# 固件加载超时设置
cat /sys/class/firmware/timeout
# 60 ← 默认 60 秒超时
# 修改超时时间
echo 120 > /sys/class/firmware/timeout
# ── 调试固件加载问题 ──────────────────────────────────────────
# 查看 udev 固件加载规则
cat /lib/udev/rules.d/50-firmware.rules
# 手动触发固件加载(调试用)
udevadm trigger --action=add /sys/bus/sdio/devices/mmc1:0001:1
# 查看固件加载请求
udevadm monitor --kernel --udev --property | grep firmware
# 检查固件文件是否存在
ls -la /lib/firmware/rtlwifi/rtl8723bs_nic.bin
# 使用 dmesg 查看固件加载详情
dmesg | grep -i firmware | tail -20
21.3.5 固件的编译内置
对于嵌入式系统,可以将固件编译进内核,避免依赖文件系统:
bash
# 内核配置:将固件编译进内核
make menuconfig
# → Device Drivers
# → Generic Driver Options
# → [*] Userspace firmware loading support
# → [*] Include in-kernel firmware blobs in kernel binary
# → (rtlwifi/rtl8723bs_nic.bin) External firmware blobs to build into the kernel binary
# → (/lib/firmware) Firmware blobs root directory
# 对应的 .config 配置
# CONFIG_FW_LOADER=y
# CONFIG_EXTRA_FIRMWARE="rtlwifi/rtl8723bs_nic.bin my_device/firmware.bin"
# CONFIG_EXTRA_FIRMWARE_DIR="/lib/firmware"
# 编译后,固件被编译进 vmlinux
# 驱动调用 request_firmware() 时,内核直接从内存返回固件数据
# 不需要访问文件系统
本章小结
| 章节 | 核心知识点 | 关键 API |
|---|---|---|
| 21.1 固件加载机制 | 固件的概念(设备内部处理器的程序);固件 vs 驱动程序;固件加载必要性;完整加载流程(驱动→内核→udev→/lib/firmware/→设备);固件搜索路径;同步/异步/内置三种模式 | 概念理解 |
| 21.2 固件加载API | request_firmware(同步阻塞);release_firmware(释放);request_firmware_nowait(异步+回调);firmware_request_nowarn(可选固件);固件验证(魔数/版本/校验和);固件缓存机制 |
request_firmware()、release_firmware()、request_firmware_nowait() |
| 21.3 固件加载实例 | RTL8723BS Wi-Fi固件加载 (SDIO接口/工作队列异步加载/分块写入);USB设备固件上传 (FX2芯片/控制传输上传/重新枚举);FPGA比特流加载(SPI接口/PROG_B/DONE/INIT_B时序);固件文件管理(apt安装/手动安装/调试);固件编译内置 | MODULE_FIRMWARE()、sdio_memcpy_toio()、spi_write() |
固件加载开发要点
1. 选择合适的加载模式
probe 函数中需要固件才能初始化 → request_firmware()(同步)
不能阻塞 probe → request_firmware_nowait()(异步)
固件可选 → firmware_request_nowarn()
嵌入式无文件系统 → CONFIG_EXTRA_FIRMWARE(编译内置)
2. 固件验证
验证魔数、版本号、大小、校验和
防止加载损坏或错误的固件
3. 错误处理
固件不存在时给出明确的错误提示
告知用户如何安装固件(apt-get install xxx)
4. 资源释放
同步加载:使用完后必须调用 release_firmware()
异步加载:内核自动释放,回调中不需要调用
5. MODULE_FIRMWARE 声明
使用 MODULE_FIRMWARE() 声明所需固件
modinfo 命令可以显示驱动需要的固件列表
6. 固件文件路径
固件文件放在 /lib/firmware/ 下
使用相对路径(如 "my_device/firmware.bin")
不要使用绝对路径
参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年