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 (MSI
s, 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 theZX_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 tozx_interrupt_ack()
(asynchronous usage). - Observers of the signal use the standard
zx_object_wait_one()
,zx_object_wait_many()
, andzx_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
zx_interrupt_create()
- Create an interrupt object.zx_interrupt_destroy()
- Destroy an interrupt object.zx_interrupt_bind()
- Bind an interrupt object to a port object.zx_interrupt_unbind()
- Unbind a bound interrupt object from its port.zx_interrupt_wait()
- Wait for an IRQ to be asserted on an interrupt object.zx_interrupt_trigger()
- Trigger a virtual interrupt object.zx_interrupt_ack()
- Acknowledge a port-bound interrupt object and re-arm it.zx_msi_allocate()
- Allocate a range of MSI interrupt.zx_msi_create()
- Create an MSI interrupt object in an allocated range.