diff options
author | Michael Brown <mcb30@ipxe.org> | 2018-02-20 10:56:31 +0000 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2018-02-20 10:56:31 +0000 |
commit | c89a446cf09f30a121ae21d91f4a1aa071044084 (patch) | |
tree | d9b8a253fc68ac4e97221ad5dfae8707c5bab873 /src/interface/efi/efi_timer.c | |
parent | 8dbb73a779e5b11ee2b65f9d2af6dd9bd8998608 (diff) | |
download | ipxe-c89a446cf09f30a121ae21d91f4a1aa071044084.tar.gz |
[efi] Run at TPL_CALLBACK to protect against UEFI timers
As noted in the comments, UEFI manages to combines the all of the
worst aspects of both a polling design (inefficiency and inability to
sleep until something interesting happens) and of an interrupt-driven
design (the complexity of code that could be preempted at any time,
thanks to UEFI timers).
This causes problems in particular for UEFI USB keyboards: the
keyboard driver calls UsbAsyncInterruptTransfer() to set up a periodic
timer which is used to poll the USB bus. This poll may interrupt a
critical section within iPXE, typically resulting in list corruption
and either a hang or reboot.
Work around this problem by mirroring the BIOS design, in which we run
with interrupts disabled almost all of the time.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/interface/efi/efi_timer.c')
-rw-r--r-- | src/interface/efi/efi_timer.c | 41 |
1 files changed, 35 insertions, 6 deletions
diff --git a/src/interface/efi/efi_timer.c b/src/interface/efi/efi_timer.c index 0ffe2a1a0..8fe307ceb 100644 --- a/src/interface/efi/efi_timer.c +++ b/src/interface/efi/efi_timer.c @@ -76,11 +76,36 @@ static void efi_udelay ( unsigned long usecs ) { * @ret ticks Current time, in ticks */ static unsigned long efi_currticks ( void ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - /* EFI provides no clean way for device drivers to shut down - * in preparation for handover to a booted operating system. - * The platform firmware simply doesn't bother to call the - * drivers' Stop() methods. Instead, drivers must register an + /* UEFI manages to ingeniously combine the worst aspects of + * both polling and interrupt-driven designs. There is no way + * to support proper interrupt-driven operation, since there + * is no way to hook in an interrupt service routine. A + * mockery of interrupts is provided by UEFI timers, which + * trigger at a preset rate and can fire at any time. + * + * We therefore have all of the downsides of a polling design + * (inefficiency and inability to sleep until something + * interesting happens) combined with all of the downsides of + * an interrupt-driven design (the complexity of code that + * could be preempted at any time). + * + * The UEFI specification expects us to litter the entire + * codebase with calls to RaiseTPL() as needed for sections of + * code that are not reentrant. Since this doesn't actually + * gain us any substantive benefits (since even with such + * calls we would still be suffering from the limitations of a + * polling design), we instead choose to run at TPL_CALLBACK + * almost all of the time, dropping to TPL_APPLICATION to + * allow timer ticks to occur. + * + * + * For added excitement, UEFI provides no clean way for device + * drivers to shut down in preparation for handover to a + * booted operating system. The platform firmware simply + * doesn't bother to call the drivers' Stop() methods. + * Instead, all non-trivial drivers must register an * EVT_SIGNAL_EXIT_BOOT_SERVICES event to be signalled when * ExitBootServices() is called, and clean up without any * reference to the EFI driver model. @@ -97,10 +122,14 @@ static unsigned long efi_currticks ( void ) { * the API lazily assumes that the host system continues to * travel through time in the usual direction. Work around * EFI's violation of this assumption by falling back to a - * simple free-running monotonic counter. + * simple free-running monotonic counter during shutdown. */ - if ( efi_shutdown_in_progress ) + if ( efi_shutdown_in_progress ) { efi_jiffies++; + } else { + bs->RestoreTPL ( TPL_APPLICATION ); + bs->RaiseTPL ( TPL_CALLBACK ); + } return ( efi_jiffies * ( TICKS_PER_SEC / EFI_JIFFIES_PER_SEC ) ); } |