blob: 208d65fca5ef184e11eb2efa3bf7e7c7e534996d [file] [log] [blame] [view]
# 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)