Interrupts

NAME

interrupts - Usermode I/O interrupt delivery

SYNOPSIS

Interrupt objects allow userspace to create, signal, and wait on hardware interrupts.

DESCRIPTION

Interrupt objects are objects typically used by drivers to receive notifications of pending interrupt requests (IRQs), typically generated by hardware units. Interrupts come in two main flavors, physical and virtual, and are slightly different from other Zircon kernel objects as they signal pending IRQs via a special set of kernel syscalls instead of relying on the standard signaling mechanisms.

Physical Interrupts

Interrupt objects are typically created by a highly privileged driver processes (frequently the platform bus driver, or the PCIe bus driver) during device enumeration, and then made available to the appropriate drivers via delegation. Objects representing physical edge and level triggered interrupts are created using zx_interrupt_create(), and require that various platform specific configuration options be passed (such as whether the interrupt is edge vs. level triggered, or active high vs. active low) in order properly configure the system's interrupt controller.

Message signaled interrupts (MSIs, used by PCIe devices) are slightly different, and must be allocated and constructed using calls to zx_msi_allocate() and zx_msi_create().

Please refer to the reference documentation for these syscalls for more details.

Virtual Interrupts

Virtual interrupts are also created using zx_interrupt_create() and passing the ZX_INTERRUPT_VIRTUAL option. Virtual interrupt object are pure software constructs, and not signalled by hardware based interrupt requests. Instead, the zx_interrupt_trigger() syscall is used to trigger a virtual IRQ for the object.

Virtual interrupts are typically used in one of two scenarios.

The first is testing. Virtual interrupts use the same special set of syscalls for waiting and acknowledging that physical interrupt use, however they place triggering of IRQs under software control. This means that test code can create a virtual interrupt and pass it off to a driver under test, and the driver can simply use the interrupt object the way it always does with no code changes. The difference is that now the test framework has control of the object's interrupt request state, allowing it to mock various hardware behaviors during testing.

The second is demultiplexing, usually for GPIO (General Purpose Input Output) interrupts. Many chips allow general purpose pins to be configured as interrupt sources to be attached to external devices who need to deliver IRQs to the system. Typically, configuration of these pins (whether used for interrupts or not) in addition to control of pins configured as interrupts (masking, acknowledging, and so on) is done using shared registers which represent banks of pins. Frequently sets of 16 or 32 pins share one set of registers, as well as a single physical interrupt tied to the system's interrupt controller.

In systems such as this, there may be one driver responsible for configuring GPIO hardware and servicing the common GPIO physical interrupt, while other drivers are responsible for external hardware which may be connected to the main system using distinct pins which share a common GPIO bank. For example, consider a system which has an external chip to handle Bluetooth, and another chip which is an accelerometer. Each driver is separate from the GPIO driver, as well as from each other, and should be process isolated. However, the BT and accelerometer drivers each need a separate interrupt object to wait on, even though they are actually sharing a single physical interrupt under the control of the GPIO driver.

The interrupts in this situation need to be "de-multiplexed" by the GPIO driver, and individually delegated to the appropriate drivers by using virtual interrupts. For each pin configured to be an interrupt, the GPIO driver can create a virtual interrupt which can be passed on to the appropriate task specific driver. When the common physical GPIO interrupt fires, the GPIO driver can identify the virtual interrupt(s) which represent any newly asserted IRQs and trigger them using zx_interrupt_trigger(). After the driver-user of the virtual interrupt has serviced its hardware and acknowledged the interrupt, the GPIO driver can then re-enable the interrupt in the proper GPIO register bank, and wait for a new IRQ via the shared physical interrupt.

Waiting for and acknowledging interrupts

Users may wait for an interrupt to be requested via an interrupt object either synchronously, by blocking a thread on the interrupt object, or asynchronously, by binding the interrupt to a Zircon port object where IRQs will be delivered using a port packet which can be read along with other packets sent to the port.

Only one method may be used. An interrupt object cannot be both bound to a port and have a thread blocked on it at the same time. Additionally, when used in a synchronous fashion, only one thread may be blocked on an interrupt object at a time. Attempting to block a second thread will result in an error. Likewise, when used in an asynchronous fashion, the interrupt object may only be bound to one port at a time, no more.

Synchronous waiting and acknowledgement

After creating an interrupt object, users may block a thread and wait for an IRQ by calling zx_interrupt_wait(). If the interrupt object had already been triggered (either by a HW IRQ for a physical interrupt, or by a call to zx_interrupt_trigger() on a virtual interrupt), the call will return immediately. Otherwise, the thread will remain blocked until an IRQ is asserted, or the interrupt object is destroyed using a call to zx_interrupt_destroy().

When an interrupt object is triggered, it will release either the current thread waiting on it, or the next thread to wait on it. Even after that thread unblocks, however, the interrupt is still logically triggered, and will not be re-armed and re-enabled until it has been acknowledged. Synchronous users of interrupt objects acknowledge their IRQs by waiting on the interrupt object again. The interrupt object will be re-armed at the interrupt controller level, and the thread will be unblocked when the next IRQ is asserted.

Asynchronous waiting and acknowledgement

Users may also wait for IRQs asynchronously by binding their interrupts to a Zircon port object. To do so, users call zx_interrupt_bind(), passing a handle to the interrupt object as well as a handle to a port which has been created using the ZX_PORT_BIND_TO_INTERRUPT option. Once an interrupt has been bound to a port:

1) IRQs will be signaled by the queueing of an interrupt port packet to the bound port. 2) Unlike standard Zircon signalling, after an interrupt has been bound to a port, no call needs to be made to zx_object_wait_async(). A port packet will be automatically delivered to the bound port any time an IRQ is asserted. 2) Subsequent calls to zx_interrupt_bind() will fail. Interrupt objects may only be bound to a single port at a time. 3) Calls to zx_interrupt_wait() will fail. Interrupt objects can be used either in a synchronous fashion, or an asynchronous fashion, but not both at once. 4) The interrupt object may be disconnected from its bound port by using the zx_interrupt_unbind(), after which it may be re-bound to a different port, or used synchronously via zx_interrupt_wait().

After a bound interrupt is triggered, and a port packet is delivered, no new packet will be delivered until the interrupt has been acknowledged. Unlike the synchronous pattern where interrupts are ack'ed with the next call to zx_interrupt_wait(), asynchronous users of interrupts objects must explicitly acknowledge interrupts using a call to zx_interrupt_ack(). Once the interrupt has been acknowledged, a new port packet may be delivered either the next time the IRQ becomes asserted, or immediately if there is already another IRQ pending.

User Signals

While interrupt objects do not use standard Zircon signals to notify users of IRQs, they are still Zircon kernel objects. As such, they still possess the standard set of eight "user signals" which can be set and cleared using calls to zx_object_signal(), and waited on using zx_object_wait_one(), zx_object_wait_many(), and zx_object_wait_async(), provided that the caller has a handle with sufficient rights.

Virtual interrupts and ZX_VIRTUAL_INTERRUPT_UNTRIGGERED

In addition to user signals, virtual interrupt objects define one other standard zircon signal called ZX_VIRTUAL_INTERRUPT_UNTRIGGERED.

Unlike physical interrupts whose requests are automatically triggered by hardware when certain conditions are met, virtual interrupt objects need to be explicitly triggered by software. After a virtual interrupt is triggered, the software responsible for the triggering needs to know when the IRQ receiver has processed and acknowledged the IRQ so that it can be re-asserted at the proper point in time in the future.

In order to provide this knowledge, we introduce the ZX_VIRTUAL_INTERRUPT_UNTRIGGERED signal.

When a virtual interrupt is used for testing, this signal can be used to advance a test to the next phase. When used for demultiplexing interrupts, this signal can be used by the owner of the set of multiplexed interrupts to unmask and re-arm interrupts which share a physical interrupt, and then go back to waiting on the shared physical interrupt.

The rules pertaining to the ZX_VIRTUAL_INTERRUPT_UNTRIGGERED signal are:

  • Immediately after being created, a virtual interrupt object is in the "untriggered" state, and the ZX_VIRTUAL_INTERRUPT_UNTRIGGERED signal will be asserted.
  • A call to zx_interrupt_trigger() will cause an interrupt object to enter the triggered state, and de-assert the ZX_VIRTUAL_INTERRUPT_UNTRIGGERED signal.
  • The signal will remain de-asserted until it has become ack'ed by the consumer, either via the next call to zx_interrupt_wait() (synchronous usage), or via a call to zx_interrupt_ack() (asynchronous usage).
  • Observers of the signal use the standard zx_object_wait_one(), zx_object_wait_many(), and zx_object_wait_async() syscalls.

Note that when an interrupt object is used with a port, it is possible for a virtual interrupt be in a triggered state, with another interrupt already pending. An initial call to zx_interrupt_trigger() will cause a port packet to be sent to the bound port immediately, while a subsequent call will pend another interrupt, but will not immediately trigger a port packet.

In a situation such as this, when a user acknowledges the first port packet using a call to zx_interrupt_ack(), the object effectively moves from the triggered state to the untriggered state, but is then immediately triggered once again, delivering the second port packet, and transitioning from untriggered back to triggered.

In the process, the ZX_VIRTUAL_INTERRUPT_UNTRIGGERED signal will essentially be "strobed". Any currently pending wait operations will be satisfied (threads blocked on the signal will be unblocked, posted async wait operations will deliver a port packet) however the object signal itself will be immediately de-asserted as the call to zx_interrupt_ack() unwinds.

Physical interrupt objects do not implement ZX_VIRTUAL_INTERRUPT_UNTRIGGERED, and will never assert the signal.

NOTES

Interrupt Objects are private to the DDK and not generally available to userspace processes.

SYSCALLS