如何在内核驱动程序固件更新期间防止关机或挂起?

How do I prevent shutdown or suspend during kernel driver firmware update?

我有一个 Linux 实现固件更新的设备驱动程序 API。驱动程序通过使用 I2C 命令更新附加的 EEPROM 来更新我们设备上的固件。我像这样使用固件更新 API:

request_firmware_nowait(
    THIS_MODULE, true, "DEVICE.img", &client->dev,
    GFP_KERNEL, pdata,
    &DEVICE_firmware_callback);

我遇到的问题是,如果系统在此固件更新过程中断电,那么附加的 EEPROM 将处于损坏状态,设备控制器将无法在下次启动时正确响应,甚至不足以启动另一个固件更新来解决这个问题。

我认为一个干净的解决方案是防止系统在此过程中关闭或挂起,但我不知道如何实现。有没有办法让我们的设备驱动程序在固件更新过程中阻止系统关闭?

您可以使用重启通知程序以及完成此操作。每当您需要执行固件更新时:

  1. 通过register_reboot_notifier()注册重启通知程序。
  2. 使用init_completion()初始化完成。
  3. 开始固件更新。您可以通过专用的 kthread 来完成此操作。
  4. 更新完成后,用 complete() 表示完成。
  5. 通过unregister_reboot_notifier()取消注册重启通知程序。

您的重启通知程序将检测到重启(停止、重启、关机),并有可能等待通过 wait_for_completion()(或其变体之一)完成工作。

这是一个示例模块,它使用仅休眠 5 秒的虚拟 kthread 执行此操作:

// SPDX-License-Identifier: GPL-3.0
#include <linux/init.h>       // module_{init,exit}()
#include <linux/module.h>     // THIS_MODULE, MODULE_VERSION, ...
#include <linux/kernel.h>     // printk(), pr_*()
#include <linux/reboot.h>     // register_reboot_notifier()
#include <linux/kthread.h>    // kthread_{create,stop,...}()
#include <linux/delay.h>      // msleep()
#include <linux/completion.h> // struct completion, complete(), ...

#ifdef pr_fmt
#undef pr_fmt
#endif
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

static DECLARE_COMPLETION(done_wasting_time);

int my_notifier(struct notifier_block *nb, unsigned long action, void *data) {
    if (!completion_done(&done_wasting_time)) {
        pr_info("Wait! I have some critical job to finish...\n");
        wait_for_completion(&done_wasting_time);
        pr_info("Done!\n");
    }

    return NOTIFY_OK;
}

static struct notifier_block notifier = {
    .notifier_call = my_notifier,
    .next = NULL,
    .priority = 0
};

int waste_time(void *data) {
    struct completion *cmp = data;
    msleep(5000);
    complete(cmp);
    return 0;
}

static int __init modinit(void)
{
    register_reboot_notifier(&notifier);
    kthread_run(waste_time, &done_wasting_time, "waste_time");
    return 0;
}

static void __exit modexit(void)
{
    unregister_reboot_notifier(&notifier);
}

module_init(modinit);
module_exit(modexit);
MODULE_VERSION("0.1");
MODULE_DESCRIPTION("Test module: wait for a critical job to finish before"
           " rebooting or powering down.");
MODULE_AUTHOR("Marco Bonelli");
MODULE_LICENSE("GPL");

我的测试机器上的输出:

# insmod reboot_notifier_test.ko
# reboot
[    6.031410] reboot_notifier_test: Wait! I have some critical job to finish...
[    9.998207] reboot_notifier_test: Done!
[   10.003917] reboot: Power down