Search code examples
clinux-kernellinux-device-driverfirmware

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


I have a Linux device driver that implements the firmware update API. The driver updates the firmware on our device by using I2C commands to update an attached EEPROM. I use the firmware update API like so:

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

The issue I have is that if the system is powered down during this firmware update process then the attached EEPROM is left in a corrupt state and the device controller will not respond properly on the next boot, not even enough to start another firmware update to correct the issue.

I think a clean solution would be to prevent the system from powering down or suspending during this process, but I do not know how to implement that. Is there a way to have our device driver prevent the system from shutting down while the firmware update is in process?


Solution

  • You can use a reboot notifier along with a completion for this. Whenever you need to perform the firmware update:

    1. Register the reboot notifier through register_reboot_notifier().
    2. Initialize a completion using init_completion().
    3. Start the firmware update. You could do this through a dedicated kthread.
    4. When the update is finished, signal completion with complete().
    5. Unregister the reboot notifier through unregister_reboot_notifier().

    Your reboot notifier will detect a reboot (halt, reboot, power off), and will have the possibility to wait for the work to be done through wait_for_completion() (or one of its variants).

    Here's an example module that does exactly this with a dummy kthread that just sleeps for 5 seconds:

    // 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");
    

    Output on my test machine:

    # 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