| # Interrupts |
| |
| Caution: This page may contain information that is specific to the legacy |
| version of the driver framework (DFv1). |
| |
| An interrupt is an asynchronous event, generated by a device when it needs servicing. |
| For example, an interrupt is generated when data is available on a serial port, |
| or an ethernet packet has arrived. |
| Interrupts allow a driver to know about an event as soon as it |
| occurs, but without the driver spending time polling (actively waiting) for it. |
| |
| The general architecture of a driver that uses interrupts is that a background |
| Interrupt Handling Thread (**IHT**) is created during the driver startup / binding |
| operation. |
| This thread waits for an interrupt to happen, and, when it does, performs some |
| kind of servicing action. |
| |
| As an example, consider a serial port driver. |
| It may receive interrupts due to any of the following events happening: |
| |
| * one or more characters have arrived, |
| * room is now available to transmit one or more characters, |
| * a control line (like `DTR`, for example) has changed state. |
| |
| The interrupt wakes up the IHT. |
| The IHT determines the cause of the event, usually by reading some status registers. |
| Then, it runs an appropriate service function to handle the event. |
| Once done, the IHT goes back to sleep, waiting for the next interrupt. |
| |
| For example, if a character arrives, the IHT wakes up, reads a status register that |
| indicates "data is available," and then calls a function that drains all available |
| characters from the serial port FIFO into the driver's buffer. |
| |
| ## No kernel-level code required |
| |
| You may be familiar with other operating systems which use Interrupt |
| Service Routines (**ISR**). |
| These are kernel-level handlers that run in privileged mode and interface with |
| the interrupt controller hardware. |
| |
| In Fuchsia, the kernel deals with the privileged part of the interrupt |
| handling, and provides thread-level functions for driver use. |
| |
| The difference is that the IHT runs at thread level, whereas the ISR runs |
| at kernel level in a very restricted (and sometimes fragile) environment. |
| A principal advantage is that if the IHT crashes, it takes out only the |
| driver, whereas a failing ISR can take out the entire operating system. |
| |
| ## Attaching to an interrupt |
| |
| Currently, the only bus that provides interrupts is the PCI bus. |
| It supports two kinds: legacy and Message Signaled Interrupts (**MSI**). |
| |
| Therefore, in order to use interrupts on PCI: |
| |
| 1. determine which kind your device supports (legacy or MSI), |
| 2. set the interrupt mode to match, |
| 3. get a handle to your device's interrupt vector (usually one, but may be multiple), |
| 4. start IHT background thread, |
| 5. arrange for IHT thread to wait for interrupts (on handle(s) from step 3). |
| |
| Steps `1` and `2` are handled by the `Pci::ConfigureInterruptMode helper function: |
| |
| ```cpp |
| // Configure interrupt mode. |
| uint32_t irq_cnt = 1; |
| fuchsia_hardware_pci::InterruptMode mode; |
| zx_status_t status = pci.ConfigureInterruptMode(irq_cnt, &mode); |
| if (status != ZX_OK) { |
| // handle error |
| } |
| ``` |
| |
| **Pci::ConfigureInterruptMode()** |
| takes two arguments: |
| |
| ```cpp |
| #include <lib/device-protocol/pci.h> |
| |
| zx_status_t Pci::ConfigureInterruptMode(uint32_t requested_irq_count, |
| fpci::InterruptMode* out_mode); |
| ``` |
| |
| The first argument, `requested_irq_count`, is number of interrupts you would like. |
| |
| The second argument is an out parameter for the interrupt mode that the device supports. |
| |
| Having configured interrupt support, you call **Pci::MapInterrupt()** |
| to create a handle to the selected interrupt. Note that |
| **Pci::MapInterrupt()** has the following prototype: |
| |
| ```cpp |
| #include <lib/device-protocol/pci.h> |
| |
| zx_status_t Pci::MapInterrupt(uint32_t which_irq, zx::interrupt* out_interrupt); |
| ``` |
| |
| The first argument, `which_irq` |
| indicates the device-relative interrupt number you'd like, and the second argument |
| is a pointer to the created interrupt object. |
| |
| You now have an interrupt handle. |
| |
| > Note that the vast majority of devices have just one interrupt, so simply passing |
| > `0` for `which_irq` is normal. |
| > If your device does have more than one interrupt, the common practice is to run the |
| > **pci::MapInterrupt()** function in a `for` loop |
| > and bind handles to each interrupt. |
| |
| ## Waiting for the interrupt |
| |
| In your IHT, you call [**zx::interrupt::wait()**](/reference/syscalls/interrupt_wait.md) |
| to wait for the interrupt. |
| The following prototype applies: |
| |
| ```cpp |
| zx_status_t zx::interrupt::wait(zx::time* out_timestamp); |
| ``` |
| |
| The first parameter can be `nullptr` (typical), or it can be a pointer to a time |
| stamp that indicates when the interrupt was triggered (in nanoseconds, |
| relative to the monotonic clock source fetched with |
| [`zx_clock_get_monotonic()`](/reference/syscalls/clock_get_monotonic.md)). |
| |
| Therefore, a typical IHT would have the following shape: |
| |
| ```cpp |
| int MyDevice::IrqThread() { |
| for (;;) { |
| zx_status_t status = dev->irq_.wait(nullptr); |
| // do stuff |
| } |
| } |
| ``` |
| |
| The device class has a member (here `irq_`) that is the object you obtained from |
| **Pci::MapInterrupt()**. |
| |
| ## Edge vs level interrupt mode |
| |
| The interrupt hardware can operate in one of two modes; "edge" or "level". |
| |
| In edge mode, the interrupt is armed on the active-going edge (when the hardware |
| signal goes from inactive to active), and works as a one-shot. |
| That is, the signal must go back to inactive before it can be recognized again. |
| |
| In level mode, the interrupt is active when the hardware signal is in the |
| active state. |
| |
| Typically, edge mode is used when the interrupt is dedicated, and level mode is |
| used when the interrupt is shared by multiple devices (because you want the |
| interrupt to remain active until *all* devices have de-asserted their request line). |
| |
| The Zircon kernel automatically masks and unmasks the interrupt as appropriate. |
| For level-triggered hardware interrupts, |
| [**zx::interrupt::wait()**](/reference/syscalls/interrupt_wait.md) |
| masks the interrupt before returning, and unmasks it when called the next time. |
| For edge-triggered interrupts, the interrupt remains unmasked. |
| |
| > The IHT should not perform any long-running tasks. |
| > For drivers that perform lengthy tasks, use a worker thread. |
| |
| ## Shutting down a driver that uses interrupts |
| |
| In order to cleanly shut down a driver that uses interrupts, you can use |
| [**zx::interrupt::destroy()**](/reference/syscalls/interrupt_destroy.md) |
| to abort the |
| [**zx::interrupt::wait()**](/reference/syscalls/interrupt_wait.md) |
| call. |
| |
| The idea is that when the foreground thread determines that the driver should be |
| shut down, it simply destroys the interrupt handle, causing the IHT to shut down: |
| |
| ```cpp |
| void MyDevice::DdkRelease() { |
| // destroy the handle, this will cause zx_interrupt_wait() to pop |
| irq_.destroy(); |
| irq_thread_.join(); |
| ... |
| } |
| |
| int MyDevice::IrqThread() { |
| ... |
| for(;;) { |
| zx_status_t status = irq_.wait(nullptr); |
| if (status == ZX_ERR_CANCELED) { |
| // we are being shut down, do any cleanups required |
| ... |
| break; |
| } |
| ... |
| } |
| } |
| ``` |
| |
| The main thread, when requested to shut down, destroys the interrupt handle. |
| This causes the IHT's |
| [**zx::interrupt::wait()**](/reference/syscalls/interrupt_wait.md) |
| call to wake up with an error code. |
| The IHT looks at the error code (in this case, `ZX_ERR_CANCELED`) and makes |
| the decision to end. |
| Meanwhile, the main thread is waiting to join the IHT with the call |
| to **thread::join()**. |
| Once the IHT exits, **thread::join()** returns, and the main |
| thread can finish its processing. |
| |
| The advanced reader is invited to look at some of the other interrupt related |
| functions available: |
| |
| * [**zx_interrupt_ack()**](/reference/syscalls/interrupt_ack.md) |
| * [**zx_interrupt_bind()**](/reference/syscalls/interrupt_bind.md) |
| * [**zx_interrupt_create()**](/reference/syscalls/interrupt_create.md) |
| * [**zx_interrupt_trigger()**](/reference/syscalls/interrupt_trigger.md) |