Saturday 3 December 2016

Interrupt Handling Driver Development


       Registering an interrupt handler
       Defined in include/linux/interrupt.h
       int request_irq(                                                Returns 0 if successful
unsigned int irq,                               Requested irq channel
irq_handler_t handler,                  Interrupt handler
unsigned long irq_flags,                Optional flags
const char * devname,                  Registered name
void *dev_id);                                   Pointer to some handler data, Cannot be NULL and must be unique for shared irqs!

       void free_irq(unsigned int irq, void *dev_id);
       dev_id cannot be NULL and must be unique for shared irqs. Otherwise, on a shared interrupt line, free_irq wouldn't know which handler to free
       Main irq_flags bit values (can be combined, none is fine too)
       IRQF_SHARED
       The interrupt channel can be shared by several devices. Requires a hardware status register telling whether an IRQ was raised or not.
       IRQF_SAMPLE_RANDOM
       Use the IRQ arrival time to feed the kernel random number generator.

Interrupt handler constraints
       No guarantee in which address space the system will be in when the interrupt occurs: can't transfer data to and from user space
       Interrupt handler execution is managed by the CPU, not by the scheduler. Handlers can't run actions that may sleep, because there is nothing to resume their execution. In particular, need to allocate memory with GFP_ATOMIC.
       Interrupt handlers are run with all interrupts disabled (since 2.6.36). Therefore, they have to complete their job quickly enough, to avoiding blocking interrupts for too long.

Information of installed handlers
/proc/interrupts                                                               PANDA Board (OMAP4 ARM)
                CPU0     CPU1
39:          4              0             GIC TWL6030-PIH
41:          0              0              GIC l3-dbg-irq
42:          0              0              GIC l3-app-irq
43:          0              0              GIC prcm
44:          20294 0                 GIC DMA
52:          0              0              GIC gpmc
53:          47590 0                 GIC SGX ISR
57:          6              0              GIC OMAP DISPC
69:          14           0              GIC gp timer
85:          0              0              GIC omapdss_dsi1
88:          300         0              GIC omap_i2c
89:          0              0              GIC omap_i2c
91:          6909 0 G               IC mmc1
93:          0              0              GIC omap_i2c
94:          0              0              GIC omap_i2c
102: 0    0              GIC serial idle

Interrupt handler prototype
       irqreturn_t foo_interrupt (int irq, void *dev_id)
       Arguments
      irq, the IRQ number
      dev_id, the opaque pointer passed at request_irq()
       Return value
      IRQ_HANDLED: recognized and handled interrupt
      IRQ_NONE: not on a device managed by the module. Useful to share interrupt channels and/or report spurious interrupts to  the kernel.

Interrupt handler’s job
       Acknowledge the interrupt to the device  (otherwise no more interrupts will be generated, or the interrupt will keep firing over and over again)
       Read/write data from/to the device
       Wake up any waiting process waiting for the completion of an operation, typically using wait queues
       wake_up_interruptible(&module_queue);

Threaded interrupts
       In 2.6.30, support for threaded interrupts has been added to the
       Linux kernel
       The interrupt handler is executed inside a thread.
       Allows to block during the interrupt handler, which is often needed for I2C/SPI devices as the interrupt handler needs to communicate with them.
       Allows to set a priority for the interrupt handler execution, which is useful for real-time usage of Linux
       int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev);
      handler, “hard IRQ” handler
      thread_fn, executed in a thread

Top half and bottom half processing
       Splitting the execution of interrupt handlers in 2 parts
       Top half
      This is the real interrupt handler, which should complete as quickly as possible since all interrupts are disabled. If possible, take the data out of the device and schedule a bottom half to handle it.
       Bottom half
       Is the general Linux name for various mechanisms which allow to postpone the handling of interrupt-related work.
       Implemented in Linux as softirqs, tasklets or workqueues.



SoftIRQs
       Softirqs are a form of bottom half processing
       The softirqs handlers are executed with all interrupts enabled, and a given softirq handler can run simultaneously on multiple CPUs
       They are executed once all interrupt handlers have completed, before the kernel resumes scheduling processes, so sleeping is
       not allowed.
       The number of softirqs is fixed in the system, so softirqs are not directly used by drivers, but by complete kernel subsystems (network, etc.)
       The list of softirqs is defined in include/linux/interrupt.h:
       HI, TIMER, NET_TX, NET_RX, BLOCK, BLOCK_IOPOLL, TASKLET, SCHED, HRTIMER, RCU
       The HI and TASKLET softirqs are used to execute tasklets

Tasklets
       Tasklets are executed within the HI and TASKLET softirqs.
       They are executed with all interrupts enabled, but a given tasklet is guaranteed to execute on a single CPU at a time.
       A tasklet can be declared statically with the
      DECLARE_TASKLET() macro or dynamically with the
      tasklet_init() function.
       A tasklet is simply implemented as a function.
       Tasklets can easily be used by individual device drivers, as opposed to softirqs.
       The interrupt handler can schedule the execution of a tasklet With
      tasklet_schedule() to get it executed in the TASKLET softirq
      tasklet_hi_schedule() to get it executed in the HI softirq (higher priority)

Work queues
       Workqueues are a general mechanism for deferring work. It is not limited in usage to handling interrupts.
       The function registered as workqueue is executed in a thread, which means :
      All interrupts are enabled
      Sleeping is allowed
       A workqueue is registered with INIT_WORK and typically triggered with queue_work()
       The complete API, in include/linux/workqueue.h provides many other possibilities (creating its own workqueue threads, etc.)

Examples
       Softirq
       Tasklet
       Work queue

Summery
       Device driver
      When the device file is first opened, register an interrupt handler for the device's interrupt channel.
       Interrupt handler
      Called when an interrupt is raised.
       Acknowledge the interrupt
       If needed, schedule a tasklet taking care of handling data. Otherwise, wake up processes  waiting for the data.
       Tasklet
      Process the data
      Wake up processes waiting for the data
      Device driver    
      When the device is no longer opened by any process, unregister the interrupt handler.


No comments:

Post a Comment