I am porting a device driver from QNX to Linux. In QNX, the old driver used a pthread with an infinite loop to monitor for the occurrence of an interrupt, rather than registering a true interrupt handler. To try to demonstrate the efficacy of using register_irq() instead of a dedicated polling thread, I wrote two drivers in Linux. The relevant code for each is shown below and the question is at the bottom.
IRQ
Write the handler
irqreturn_t timing_interrupt_handler(int irq, void *dev_id) {
u32 test;
/* read device interrupt command/status register */
test = ioread32(timing_card[3].base);
/* sanity check that the device reported the interrupt */
if ( test & (1 << 2) ) {
/* clear interrupt status */
iowrite32( 0x0d, timing_card[3].base);
/* toggle digital output line */
test = ioread32(timing_card[2].base);
if ( test & 0x01 )
iowrite32(test & ~0x1, timing_card[2].base);
else
iowrite32(test | 0x1, timing_card[2].base);
}
return IRQ_HANDLED;
}
Register the handler
rc = request_irq(irq_line, timing_interrupt_handler,
IRQF_SHARED, "timing", timing_card);
if ( rc ) {
printk(KERN_ALERT "Failed to register irq %d\n", irq_line);
return rc;
}
POLLING THREAD
Write the thread function
int poll_irq(void *data) {
u32 test;
/* until module unload */
while ( !kthread_should_stop() ) {
/* read device interrupt command/status register */
test = ioread32(timing_card[3].base);
/* sanity check that the device reported the interrupt */
if ( test & (1 << 2) ) {
/* clear interrupt status */
iowrite32( 0x0d, timing_card[3].base);
/* toggle digital output line */
test = ioread32(timing_card[2].base);
if ( test & 0x01 )
iowrite32(test & ~0x1, timing_card[2].base);
else
iowrite32(test | 0x1, timing_card[2].base);
}
else
usleep_range(9, 11);
}
return 0;
}
Begin the thread
kthread = kthread_create(poll_irq, 0x0, "poll_IRQ_test");
wake_up_process(kthread);
THE QUESTION
When I put two traces on an oscilloscope - one monitoring the card's digital input (which would trigger the interrupt) and one monitoring the card's digital output (which would react to the interrupt) I can measure the reaction time to an event.
The first "proper" method, registering an IRQ, takes approximately 80 microseconds.
The second method, running an infinite thread, takes approximately 15-30 microseconds.
What gives? The benefit of the first is that it doesn't waste as much processing power, but why does response time suffer so dramatically? How bad is it, really, to have this polling thread? How could one go about investigating and eventually demonstrating the extra toll the polling thread puts on the CPU?
Thanks for your time!
Best
Scott
The interrupt response time is affected by the time your system (whatever it is) needs to deliver the interrupt, and by the time your CPU (whatever it is) needs to wake up from some power-saving sleeping mode.
The polling thread eats both CPU time and power.
To measure them, use something like top or powertop, or measure the power consumption directly on the hardware.
Related
Recently I have a project to use a Linux host to communicate with an ADC device(SPI communication). I use my knowledge to write a device driver for it.
The goal of this driver is to read ADC data and transfer them to userspace. My idea is when the Linux host gets the DRDY signal(data ready signal, the signal means the data of ADC can be read) from ADC, an interrupt will be triggered, and the SPI read API of the driver will read data from the SPI bus. the data will fill into a buffer, when the buffer is full, the driver sends a SIGNAL to the userspace program, and the data in the buffer will be read by the userspace.
Although this idea may not be a perfect plan to realize my goal, I finish the code above. Unfortunately, I face a question that makes my goal failed.
The SPI transfer API of the Linux host should be put into the bottom half of the interrupt(due to the sleep mechanic of SPI API), that is to say, if the sample rate of AD is too fast, the bottom half of the interrupt may read a delayed data of ADC, when I use 4kHz sample rate, there are 7997 interrupts, but only 7907 data has been read. When I use the 250Hz sample rate, my idea is OK. But, I must use at least 4ksps.
I do not know whether you have some experience with this kind of problem, or maybe my idea is not suited for the high-speed ADC, I hope you can give me some suggestions, thanks a lot.
Here is some core code of my idea. The SPI transfer function:
int get_ad_data(struct spi_device *ad_spi_dev)
{
int ret = -1;
gpio_set_value(ADS1299_CS_PIN, 0);
if( ad_spi_dev )
{
struct spi_transfer tr =
{
.tx_buf = &send_data,
.rx_buf = &get_data,
.len = 27,
};
ret = spi_sync_transfer(ad_spi_dev, &tr, 1);
}
printk("%02x, %02x, %02x\r\n",get_data[6],get_data[7],get_data[8]);
gpio_set_value(ADS1299_CS_PIN, 1);
return ret;
}
The interrupt handler:
static irqreturn_t drdy_handler(int irq, void *dev_id)
{
struct ads1299_dev *dev = dev_id;
schedule_work(&dev->drdy_irq.work_drdy);
return IRQ_HANDLED;
}
static void drdy_work(struct work_struct *work)
{
int ret;
ret = get_ad_data(ads1299_spi_dev);
}
I'm trying to implement this logic in STM8S103F3:
1) Controller wait for external interurpts on GPIOC (high by default) after initialization.
2.1) external interrupt triggered: if PIN5 of GPIOC is low, turn test led on, and start timer for 5s.
2.2) external interrupt triggered: if PIN5 of GPIOC is high, turn test led off and stop timer.
3) timer interrupt triggered: turn test led off.
My code:
#include "stm8s.h"
#include "stm8s_gpio.h"
#include "stm8s_exti.h"
#include "stm8s_tim1.h"
void tim1_update_handler() __interrupt(11)
{
GPIO_WriteHigh(GPIOB, GPIO_PIN_5);
TIM1_ClearITPendingBit(TIM1_IT_UPDATE);
}
void portc_ext_int_handler() __interrupt(5)
{
uint8_t state = GPIO_ReadInputData(GPIOC);
if (state & GPIO_PIN_5) // PORTC pin 5 high, default state, button not pressed.
{
TIM1_SetCounter(0);
TIM1_Cmd(ENABLE);
}
else // Button pressed.
{
GPIO_WriteLow(GPIOB, GPIO_PIN_5);
TIM1_Cmd(DISABLE);
}
}
int main(void)
{
disableInterrupts();
GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_HIGH_FAST);
GPIO_Init(GPIOC, GPIO_PIN_ALL, GPIO_MODE_IN_PU_IT);
EXTI_DeInit();
EXTI_SetExtIntSensitivity(EXTI_PORT_GPIOC, EXTI_SENSITIVITY_RISE_FALL);
TIM1_TimeBaseInit(2000, TIM1_COUNTERMODE_UP, 5000, 0);
TIM1_SelectOnePulseMode(TIM1_OPMODE_SINGLE);
TIM1_ITConfig(TIM1_IT_UPDATE, ENABLE);
enableInterrupts();
while (1)
{
wfi();
}
}
External interrupt is triggered fine, but timer interrupt does not triggers.
What i'm doing wrong with timer, and how i can gix it?
Bit late with this,but possibly it is because of the interrupt priority, which by default is set to NOT interrupt an ISR already in progress. Changing the ITC_xxx registers allows setting these priorities so one IRQ can interrupt another, which is what you need with the timer.
I have a GPIO peripheral, defined in Device Tree as that:
gpio0: gpio#2300000
{
compatible = "fsl,qoriq-gpio";
reg = <0x0 0x2300000 0x0 0x10000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
I want to write an interrupt handler for this (as a kernel module). But this IRQ number (66) is a hardware one and I need a virtual, Linux IRQ number to pass it to request_irq.
How can I get this number? There is only one interrupt controller (GIC).
Is there a way to do this without writing a platform device driver (as there is probably already one working in the system and I think I cannot register another one).
As per you comment you want to register a GPIO as an interrupt.
The node of device tree that you have posted is the interrupt controller node, which wont concern us for the task we have at hand.
To register a gpio as an interrupt, you first need to find a GPIO which can be configured as an interrupt (in most modern processors all GPIOs support it) and then you have to make sure it is not used by some other device by multiplexing it (if it is used by some one like SPI or UART etc , you can disable them from device tree if you are not using that entity).
now that you have a GPIO pin that you can use. Find the GPIO number on kernel that pin corresponds to (it depends on the architecture of your processor and its carrier board).
When you have that you can just write a simple module that will export your GPIO and use it as interrupt.
Below is a snippet from http://derekmolloy.ie
gpio_request(gpioButton, "sysfs"); // Set up the gpioButton
gpio_direction_input(gpioButton); // Set the button GPIO to be an input
gpio_set_debounce(gpioButton, 200); // Debounce the button with a delay of 200ms
gpio_export(gpioButton, false); // Causes gpio115 to appear in /sys/class/gpio
// the bool argument prevents the direction from being changed
// Perform a quick test to see that the button is working as expected on LKM load
printk(KERN_INFO "GPIO_TEST: The button state is currently: %d\n", gpio_get_value(gpioButton));
// GPIO numbers and IRQ numbers are not the same! This function performs the mapping for us
irqNumber = gpio_to_irq(gpioButton);
printk(KERN_INFO "GPIO_TEST: The button is mapped to IRQ: %d\n", irqNumber);
// This next call requests an interrupt line
result = request_irq(irqNumber, // The interrupt number requested
(irq_handler_t) ebbgpio_irq_handler, // The pointer to the handler function below
IRQF_TRIGGER_RISING, // Interrupt on rising edge (button press, not release)
"ebb_gpio_handler", // Used in /proc/interrupts to identify the owner
NULL); // The *dev_id for shared interrupt lines, NULL is okay
Link to the complete code.
For whom is not trying to create a GPIO driver but still need to get Linux virtual IRQ from HW IRQ, there is a specific API for platform drivers. You can register a platform driver and then, during the probing, call
/**
* platform_get_irq - get an IRQ for a device
* #dev: platform device
* #num: IRQ number index
*
* Gets an IRQ for a platform device and prints an error message if finding the
* IRQ fails. Device drivers should check the return value for errors so as to
* not pass a negative integer value to the request_irq() APIs.
*
* Return: non-zero IRQ number on success, negative error number on failure.
*/
int platform_get_irq(struct platform_device *dev, unsigned int num);
Detailed explanation
To reach your goal, you have a lot of different options:
GPIO Driver Interface (GPIO drivers providing IRQs)
Low-level OF API for Device Trees (of_irq_get)
Device drivers' infrastructure API (platform_get_irq)
GPIO Driver Interface
If your platform has a programmable GPIO, you can use the GPIO Driver Interface. See #yashC reply. In your specific case, given that your device is part of GPIO, you should go for this approach.
Low-level Device Trees APIs
If you want to interact directly with the device tree, you can use this solution. Imho, you should follow this approach only if you are writing a specific (and non-generic) driver and you need a "dirty and clean" way to go.
static const struct of_device_id qoriq_gpio_match_table[] =
{
{ .compatible = "fsl,qoriq-gpio" },
{ }
};
np = of_find_matching_node(NULL, qoriq_gpio_match_table);
if (!np)
{
pr_err("No device tree node for qoriq-gpio\n");
return -ENODEV;
}
// decode a node's IRQ and return it as a Linux IRQ number
irq_num = of_irq_get(np, 0);
// request_irq(...)
Device drivers' infrastructure API
Basically, you have to register a platform driver.
static const struct of_device_id qoriq_gpio_match_table[] =
{
{ .compatible = "fsl,qoriq-gpio" },
{ }
};
static struct platform_driver qoriq_gpi_driver = {
.driver = {
.name = "qoriq-gpio",
.of_match_table = qoriq_gpio_match_table
},
.probe = qoriq_gpio_probe
};
static int qoriq_gpio_probe(struct platform_device *pdev)
{
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// request_irq(...)
}
Until Kernel v5.19 you were able to use also: platform_get_resource(pdev, IORESOURCE_IRQ, 0); API that, currently, is no more available.
You should use this approach if your device is a bit more generic (eg. you are working with several boards with different DTs).
In a linux kernel driver, I would like to repeat indefinitely the following sequence:
at time T, a hardware IRQ is enabled
between time T and T + "around" 15ms, the IRQ callback can be reached if the IRQ is triggered. I say around because I'm not using an RT kernel and if it's 14 or 16ms, it's fine. In the IRQ callback, I need to write down get cpu_clock(0) and call wake_up_interruptible. The timeout needs to be killed. The whole process needs to be started again within 5ms.
if by T + "around" 15ms, the IRQ has not been triggered, I need to execute some other code. The IRQ should be disabled then. The whole process needs to be started again within 5ms.
Therefore, by T + "around" 20ms, in worst case, the whole process needs to be started again.
Note that if the IRQ is physically triggered at 18ms, too bad, "I missed the train". I will catch another hardware trigger at the next sequence.
While testing, I was doing something along the following pseudo-code:
INIT_DELAYED_WORK(&priv->work, driver_work);
INIT_DELAYED_WORK(&priv->timeout, driver_timeout);
request_irq(priv->irq, driver_interrupt, IRQF_TRIGGER_RISING, "my_irq", priv);
then:
queue_delayed_work(priv->workq, &priv->work, 0ms);
static void driver_work(struct work_struct *work) {
queue_delayed_work(priv->workq, &priv->timeout, 15ms);
priv->interruptCalled = 0;
enable_irq(priv->irq);
}
Then:
static irqreturn_t driver_interrupt(int irq, void *_priv) {
disable_irq_nosync(priv->irq);
priv->interruptCalled = 1;
cancel_delayed_work(&priv->timeout);
priv->stamp = cpu_clock(0);
wake_up_interruptible(&driver_wait);
queue_delayed_work(priv->workq, &priv->work, 5ms);
return IRQ_HANDLED;
}
And:
static void driver_timeout(struct work_struct *work) {
if (priv->interruptCalled == 0) {
disable_irq_nosync(priv->irq);
//Do other small cleanup
queue_delayed_work(priv->workq, &priv->work, 5ms);
}
}
I'm trying to write a robust but simple driver. Is this a proper implementation? How can I improve this implementation?
Answering my own question: the problem is that queue_delayed_work is based on jiffies. Or 5ms is not possible as HZ=100 (1 jiffy = 10ms). HR timer brought a good solution.
I've blocked, and then waited for a signal via the following code:
sigset_t set;
sigfillset(&set); // all signals
sigprocmask(SIG_SETMASK, &set, NULL); // block all signals
siginfo_t info;
int signum = sigwaitinfo(&set, &info); // wait for next signal
struct sigaction act;
sigaction(signum, NULL, &act); // get the current handler for the signal
act.sa_handler(signum); // invoke it
The last line generates a segmentation fault, as the handler is set to SIG_DFL (defined as 0). How can I manually invoke the default handler if it's set to SIG_DFL or SIG_IGN? Also note that SIG_IGN is defined as 1.
As you discovered you cannot invoke SIG_DFL and SIG_IGN per se. However, you can more-or-less mimic their behavior.
Briefly, imitating normal signal disposition would be:
quite easy for user-defined sa_handlers
easy enough for SIG_IGN, with the caveat that you'd need to waitpid() in the case of CHLD
straightforward but unpleasant for SIG_DFL, re-raising to let the kernel do its magic.
Does this do what you want?
#include <signal.h>
#include <stdlib.h>
/* Manually dispose of a signal, mimicking the behavior of current
* signal dispositions as best we can. We won't cause EINTR, for
* instance.
*
* FIXME: save and restore errno around the SIG_DFL logic and
* SIG_IGN/CHLD logic.
*/
void dispatch_signal(const int signo) {
int stop = 0;
sigset_t oset;
struct sigaction curact;
sigaction(signo, NULL, &curact);
/* SIG_IGN => noop or soak up child term/stop signals (for CHLD) */
if (SIG_IGN == curact.sa_handler) {
if (SIGCHLD == signo) {
int status;
while (waitpid(-1, &status, WNOHANG|WUNTRACED) > 0) {;}
}
return;
}
/* user defined => invoke it */
if (SIG_DFL != curact.sa_handler) {
curact.sa_handler(signo);
return;
}
/* SIG_DFL => let kernel handle it (mostly).
*
* We handle noop signals ourselves -- "Ign" and "Cont", which we
* can never intercept while stopped.
*/
if (SIGURG == signo || SIGWINCH == signo || SIGCONT == signo) return;
/* Unblock CONT if this is a "Stop" signal, so that we may later be
* woken up.
*/
stop = (SIGTSTP == signo || SIGTTIN == signo || SIGTTOU == signo);
if (stop) {
sigset_t sig_cont;
sigemptyset(&sig_cont);
sigaddset(&sig_cont, SIGCONT);
sigprocmask(SIG_UNBLOCK, &sig_cont, &oset);
}
/* Re-raise, letting the kernel do the work:
* - Set exit codes and corefiles for "Term" and "Core"
* - Halt us and signal WUNTRACED'ing parents for "Stop"
* - Do the right thing if we forgot to handle any special
* signals or signals yet to be introduced
*/
kill(getpid(), signo);
/* Re-block CONT, if needed */
if (stop) sigprocmask(SIG_SETMASK, &oset, NULL);
}
UPDATE
(in response to OP's excellent questions)
1: does this slot in after the sigwaitinfo?
Yes. Something like:
... block signals ...
signo = sigwaitinfo(&set, &info);
dispatch_signal(signo);
2: Why not raise those signals handled by SIG_IGN, they'll be ignored anyway
It's slightly more efficient to noop in userspace than by three syscalls (re-raise, unmask, re-mask). Moreover, CHLD has special semantics when SIG_IGNored.
3: Why treat SIGCHLD specially?
Originally (check answer edits) I didn't -- re-raised it in the SIG_IGN case,
because IGNored CHLD signals tell the kernel to automatically reap children.
However, I changed it because "natural" CHLD signals carry information about
the terminated process (at least PID, status, and real UID).
User-generated CHLD signals don't carry the same semantics, and, in my testing,
IGNoring them doesn't cause 2.6 to autoreap queued zombies whose SIGCHLD
was "missed." So, I do it myself.
4: Why are "stop" related signals unblocking CONT. Will not invoking the default handler for CONT unstop the process?
If we're stopped (not executing) and CONT is blocked, we will never receive the
signal to wake us up!
5: Why not call raise instead of the kill line you've given?
Personal preference; raise() would work, too.
I see 2 mistakes in your code :
1) You should reverse the last two lines like this :
act.sa_handler(signum);
sigaction(signum, NULL, &act);
2) You must pass a function handler to the fiedl sa_handler instead of a int. The prototype of the function shoudl look like this :
/**
*some where in you code
*/
void handler (int signal){ /*your code*/}
/**
*
*/
act.sa_handler = handler;
If you want the default handler to be invoked, you should set the field sa_handler to SIG_DFL and it should work.
I'm not aware of the way to do it.
Only suggestion I have is to look into the man 7 signal and perform manually the action according the table you see there. Ign is nothing. Core is call to abort(). Term is _exit().
Of course you can also set signal handler back to SIG_DFL and then kill(getpid(),THE_SIG) (or its equivalent raise(THE_SIG)). (I personally do not like raise because on some systems it might produce some messages on stderr.)