Not only is PCI bus design a large area of design for peripherals, but the specification involves some pitfalls for newer specs, such as the Advanced Configuration and Power Interface (ACPI).
The PCI Power Management specification introduced a signal called PME#, along with configuration space registers to express a PCI device's power management capabilities and a status and enable register for PME#. Arming a PCI device for wakeup sets the PME enable bit. When the device asserts PME# to wake itself, PME enable is activated.
Many PCI device designers have rerouted their internal interrupt logic to create PME#. But the design has a problem when waking up.
The machine goes through the following steps when moving from the D3 to the D0 state:
- The device gets an external request (via a ringing phone, network packet or the like) that it needs to fulfill.
- The device asserts PME#.
- If the system was in a sleeping state, the system moves to the S0 state.
- The ACPI driver sends a message to the PCI driver telling it that PME# has been asserted.
- The PCI driver scans the PCI bus looking for the device that is asserting PME#, thus completing IRP_MN_WAIT_WAKE.
- The functional driver sends IRP_MN_SET_POWER to its own device node to move the device into the D0 state.
- That IRP reaches the function driver, which cannot respond until the IRP has traveled down to the bus driver. However, a function driver cannot access its hardware unless the device is in D0-and the device is not yet in D0.
- The IRP reaches the bus driver, which is at the bottom of the device node. The bus driver, in this case PCI, moves the device from D3 to D0. At that point, the PME# becomes an interrupt, asserted on INTA#.
- The operating system receives the interrupt and calls the functional driver's interrupt service routine.
- The functional driver cannot access its hardware yet, since it has not received notification that the device has transitioned to D0. That would happen when the IRP percolates back up to the functional driver, which cannot happen until the interrupt has been handled. So the functional driver returns without handling the interrupt.
- The operating system dismisses the interrupt at the interrupt controller. But because this is a level-triggered interrupt, the process goes back to step 9 and repeats endlessly.
Many have suggested that the operating system just mask the interrupt as a solution to the problem. But PCI interrupts, by definition, can be shared. So the INTA# signal may actually be combined with the INTA# from another PCI device, which may be in the D0 state.
To make such a design work, the PCI device designer would need to add logic that dismisses the interrupt event, in hardware, when the device is moved from D3 to D0.
Another, simpler, popular design for PCI device wakeup logic always uses the interrupt logic to trigger INTA#. But it also triggers PME# when the PME enable bit is set. The approach has two problems-one when moving from D0 to D3 and one when moving from D3 to D0.
This is the sequence of events when moving from D0 to D3:
- The functional driver sends IRP_MN_WAIT_WAKE to its own device node. The bus driver (PCI) receives it and sets the PME enable bit.
- Because the device is still in the D0 state, it will probably generate an interrupt as part of normal operation. If it does, the device will assert both INTA# and PME#. The ACPI driver will then send a message to the PCI driver, telling it that some device has triggered PME#.
- The PCI driver will then complete IRP_MN_WAIT_WAKE, sending it back to the functional driver and thus sending the process back to step 1.
There is another side-effect here. If the WAIT_WAKE IRP has been sent because the device has to be able to wake the machine from a sleep state, then the machine is probably in the process of transitioning from S0 to S3. Because that sequence of events triggers a wake event, the machine will instantly transition back to S0 when it does get to S3, causing the user to wonder why the machine woke up again.
Consider the following steps for moving the device from D3 to D0:
- The device asserts PME# to signal that it should be moved from D3 to D0.
- That causes the device to assert INTA#, which causes the operating system to receive an interrupt.
- The operating system calls the functional driver's interrupt service routine, which can't handle it yet, since the device is in D3.
The operating system dismisses the interrupt, which causes it to be reasserted. The process goes back to Step 2-and the machine hangs.
There's no easy way to fix this design.