Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I delay in a Linux interrupt handler (I know sleeping usually not possible)

I'm working on an embedded Linux ARM system that needs to react to a power failure signal by turning off some power supplies (via GPIO control) in a specific sequence. This process needs to start as soon as possible, so I've installed an interrupt handler to detect this power failure.

The problem is that we need to introduce a little bit of delay between turning each supply off. I understand that delays are not usually allowed in an interrupt handler, but it's totally okay if this handler never returns (power is failing!).

I'm trying to introduce a delay by using the method described in this post, but I can't for the life of me actually cause a measurable delay (observed on an oscilloscope).

What am I doing wrong, and how can I do it right?

What follows is the relevant code.

/* This function sets en_gpio low, then waits until pg_gpio goes low. */
static inline void powerdown(int en_gpio, int pg_gpio)
{
    /* Bring the enable line low. */
    gpio_set_value(en_gpio, 0);
    /* Loop until power good goes low. */
    while (gpio_get_value(pg_gpio) != 0);
}

/* This is my attempt at a delay function. */
#define DELAY_COUNT 1000000000
static void delay(void)
{
    volatile u_int32_t random;
    volatile u_int32_t accum;
    volatile u_int32_t i;

    get_random_bytes((void*)&random, 4);
    accum = 0;
    for (i = 0; i < DELAY_COUNT; i++)
        accum = accum * random;
}

/* This is the interrupt handler. */
static irqreturn_t power_fail_interrupt(int irq, void *dev_id)
{
    powerdown(VCC0V75_EN, VCC0V75_PG);
    delay();
    powerdown(DVDD15_EN, DVDD15_PG);
    delay();
    powerdown(DVDD18_EN, DVDD18_PG);
    delay();
    powerdown(CVDD1_EN, CVDD1_PG);
    delay();
    powerdown(CVDD_EN, CVDD_PG);
    /* It doesn't matter if we get past this point. Power is failing. */
    /* I'm amazed this printk() sometimes gets the message out before power drops! */
    printk(KERN_ALERT "egon_power_fail driver: Power failure detected!\n");
    return IRQ_HANDLED;
}
like image 298
Steve Avatar asked May 15 '15 14:05

Steve


People also ask

What is interrupt handling in Linux?

Interrupt handling in Linux ¶. In Linux the interrupt handling is done in three phases: critical, immediate and deferred. In the first phase the kernel will run the generic interrupt handler that determines the interrupt number, the interrupt handler for this particular interrupt and the interrupt controller.

How can I delay a loop in an interrupt handler?

You should describe what you want to achieve - trying to delay in an interrupt handler is inadvisable, and can't be done with delay (). For short delays (up to a few 100us only) you can call delayMicroseconds (). Generally though you should try and do the minimum work in the interrupt handler and poll for the heavy-lifting in the loop () routine.

How do I stop the sleep command in Linux?

To stop sleep after it started and before the specified waiting period ends, press Ctrl + C. To see help for the sleep command, type: For version details, type: The following sections contain examples of using the sleep command in the terminal or shell scripts. Note: The sleep command is designed to work in combination with other Linux commands.

When is an interrupt unexpected in Linux?

An interrupt is unexpected if it is not handled by the kernel, that is, either if there is no ISR associated with the IRQ line, or if no ISR associated with the line recognizes the interrupt as raised by its own hardware device.


1 Answers

Using delay functions in hard IRQ handlers is usually bad idea, because interrupts are disabled in hard IRQ handler and system will hang until your hard IRQ function is finished. On the other hand, you can't use sleep functions in hard IRQ handler since hard IRQ is atomic context.

Taking all that into the account, you may want to use threaded IRQ. This way hard IRQ handler is only wakes bottom half IRQ handler (which executed in kernel thread). In this threaded handler you can use regular sleep functions.

To implement threaded IRQ instead of regular IRQ, just replace your request_irq() function with request_threaded_irq() function. E.g. if you have requesting IRQ like this:

ret = request_irq(irq, your_irq_handler, IRQF_SHARED,
                  dev_name(&dev->dev), chip);

You can replace it with something like this:

ret = request_threaded_irq(irq, NULL, your_irq_handler,
                           IRQF_ONESHOT | IRQF_SHARED,
                           dev_name(&dev->dev), chip);

Here NULL means that the standard hard IRQ handler will be used (which is only wakes threaded IRQ handler), and your_irq_handler() function will be executed in kernel thread (where you can call sleep functions). Also IRQF_ONESHOT flag should be used when requesting threaded IRQ.

It also should be mentioned that there is managed version ofrequest_threaded_irq() function, called devm_request_threaded_irq(). Using it (instead of regular request_threaded_irq()) allows you to omit free_irq() function in your driver exit function (and also in error path). I would recommend you use devm_* function (if your kernel version already has it). But don't forget to remove all the free_irq() calls in your driver if you decided to go with devm_*.

TL;DR

Replace your request_irq() with request_threaded_irq() (as it shown above) and you will be able to use sleep in your IRQ handler.

like image 94
Sam Protsenko Avatar answered Oct 14 '22 17:10

Sam Protsenko