aboutsummaryrefslogtreecommitdiff
path: root/src/arch/x86/interface/pcbios/rtc_entropy.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/arch/x86/interface/pcbios/rtc_entropy.c')
-rw-r--r--src/arch/x86/interface/pcbios/rtc_entropy.c76
1 files changed, 72 insertions, 4 deletions
diff --git a/src/arch/x86/interface/pcbios/rtc_entropy.c b/src/arch/x86/interface/pcbios/rtc_entropy.c
index c400d8a..c9e5e46 100644
--- a/src/arch/x86/interface/pcbios/rtc_entropy.c
+++ b/src/arch/x86/interface/pcbios/rtc_entropy.c
@@ -55,6 +55,10 @@ static struct segoff rtc_old_handler;
extern volatile uint8_t __text16 ( rtc_flag );
#define rtc_flag __use_text16 ( rtc_flag )
+/** RTC interrupt requires rearming each time */
+extern volatile uint8_t __text16 ( rtc_rearm );
+#define rtc_rearm __use_text16 ( rtc_rearm )
+
/**
* Hook RTC interrupt handler
*
@@ -73,19 +77,49 @@ static void rtc_hook_isr ( void ) {
*/
"movb %2, %%al\n\t"
"outb %%al, %0\n\t"
- "inb %1\n\t"
+ "inb %1, %%al\n\t"
+
+ /* Rearm RTC interrupt, if required */
+ "testb $0xff, %%cs:rtc_rearm\n\t"
+ "jz rtc_isr_done\n\t"
+ /* Preserve registers */
+ "pushw %%bx\n\t"
+ /* Read current contents of register B */
+ "movb %3, %%al\n\t"
+ "outb %%al, %0\n\t"
+ "inb %1, %%al\n\t"
+ "movb %%al, %%bl\n\t"
+ /* Toggle periodic interrupt enable in register B */
+ "movb %3, %%al\n\t"
+ "outb %%al, %0\n\t"
+ "movb %%bl, %%al\n\t"
+ "xorb %4, %%al\n\t"
+ "outb %%al, %1\n\t"
+ /* Restore periodic interrupt enable in register B */
+ "movb %3, %%al\n\t"
+ "outb %%al, %0\n\t"
+ "movb %%bl, %%al\n\t"
+ "outb %%al, %1\n\t"
+ /* Restore registers */
+ "popw %%bx\n\t"
+
/* Send EOI */
+ "\nrtc_isr_done:\n\t"
"movb $0x20, %%al\n\t"
"outb %%al, $0xa0\n\t"
"outb %%al, $0x20\n\t"
/* Restore registers and return */
"popw %%ax\n\t"
"iret\n\t"
+
"\nrtc_flag:\n\t"
+ ".byte 0\n\t"
+
+ "\nrtc_rearm:\n\t"
".byte 0\n\t" )
:
- : "i" ( CMOS_ADDRESS ), "i" ( CMOS_DATA ),
- "i" ( RTC_STATUS_C ) );
+ : "i" ( CMOS_ADDRESS ), "i" ( CMOS_DATA ), "i" ( RTC_STATUS_C ),
+ "i" ( RTC_STATUS_B ), "i" ( RTC_STATUS_B_PIE ) );
hook_bios_interrupt ( RTC_INT, ( intptr_t ) rtc_isr, &rtc_old_handler );
}
@@ -178,6 +212,38 @@ static int rtc_entropy_check ( void ) {
}
/**
+ * Apply workaround for broken RTC interrupts
+ *
+ * @ret rc Return status code
+ *
+ * Some versions of Hyper-V (observed with Windows Server 2022) fail
+ * to properly emulate the RTC periodic interrupt. The typical
+ * symptom is that only a single interrupt will be generated:
+ * subsequent interrupts will appear to be asserted by the virtual RTC
+ * but will be ignored by the virtual PIC.
+ *
+ * Experiments show that this apparent hypervisor bug can be worked
+ * around by disabling and re-enabling the periodic interrupt within
+ * the interrupt handler.
+ */
+static int rtc_entropy_workaround ( void ) {
+ int rc;
+
+ /* Apply workaround */
+ DBGC ( &rtc_flag, "RTC applying workaround for broken interrupts\n" );
+ rtc_rearm = 1;
+
+ /* Force one interrupt, to trigger the rearming code path */
+ __asm__ __volatile__ ( "int %0" : : "i" ( RTC_INT ) );
+
+ /* Check that RTC interrupts are now working */
+ if ( ( rc = rtc_entropy_check() ) != 0 )
+ return rc;
+
+ return 0;
+}
+
+/**
* Enable entropy gathering
*
* @ret rc Return status code
@@ -200,8 +266,10 @@ static int rtc_entropy_enable ( void ) {
rtc_enable_int();
/* Check that RTC interrupts are working */
- if ( ( rc = rtc_entropy_check() ) != 0 )
+ if ( ( ( rc = rtc_entropy_check() ) != 0 ) &&
+ ( ( rc = rtc_entropy_workaround() ) != 0 ) ) {
goto err_check;
+ }
return 0;