如何在内核驱动程序固件更新期间防止关机或挂起?
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 将处于损坏状态,设备控制器将无法在下次启动时正确响应,甚至不足以启动另一个固件更新来解决这个问题。
我认为一个干净的解决方案是防止系统在此过程中关闭或挂起,但我不知道如何实现。有没有办法让我们的设备驱动程序在固件更新过程中阻止系统关闭?
您可以使用重启通知程序以及完成此操作。每当您需要执行固件更新时:
- 通过
register_reboot_notifier()
注册重启通知程序。
- 使用
init_completion()
初始化完成。
- 开始固件更新。您可以通过专用的 kthread 来完成此操作。
- 更新完成后,用
complete()
表示完成。
- 通过
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(¬ifier);
kthread_run(waste_time, &done_wasting_time, "waste_time");
return 0;
}
static void __exit modexit(void)
{
unregister_reboot_notifier(¬ifier);
}
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
我有一个 Linux 实现固件更新的设备驱动程序 API。驱动程序通过使用 I2C 命令更新附加的 EEPROM 来更新我们设备上的固件。我像这样使用固件更新 API:
request_firmware_nowait(
THIS_MODULE, true, "DEVICE.img", &client->dev,
GFP_KERNEL, pdata,
&DEVICE_firmware_callback);
我遇到的问题是,如果系统在此固件更新过程中断电,那么附加的 EEPROM 将处于损坏状态,设备控制器将无法在下次启动时正确响应,甚至不足以启动另一个固件更新来解决这个问题。
我认为一个干净的解决方案是防止系统在此过程中关闭或挂起,但我不知道如何实现。有没有办法让我们的设备驱动程序在固件更新过程中阻止系统关闭?
您可以使用重启通知程序以及完成此操作。每当您需要执行固件更新时:
- 通过
register_reboot_notifier()
注册重启通知程序。 - 使用
init_completion()
初始化完成。 - 开始固件更新。您可以通过专用的 kthread 来完成此操作。
- 更新完成后,用
complete()
表示完成。 - 通过
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(¬ifier);
kthread_run(waste_time, &done_wasting_time, "waste_time");
return 0;
}
static void __exit modexit(void)
{
unregister_reboot_notifier(¬ifier);
}
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