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

问题描述

我有一个实现固件更新API的Linux设备驱动程序。驱动程序通过使用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