《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 21 章 Linux 设备驱动的固件加载

《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年