|  | # 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()**](/docs/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()`](/docs/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()**](/docs/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()**](/docs/reference/syscalls/interrupt_destroy.md) | 
|  | to abort the | 
|  | [**zx::interrupt::wait()**](/docs/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()**](/docs/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()**](/docs/reference/syscalls/interrupt_ack.md) | 
|  | *   [**zx_interrupt_bind()**](/docs/reference/syscalls/interrupt_bind.md) | 
|  | *   [**zx_interrupt_create()**](/docs/reference/syscalls/interrupt_create.md) | 
|  | *   [**zx_interrupt_trigger()**](/docs/reference/syscalls/interrupt_trigger.md) |