Message ID | df53dd77-9341-4a72-a4ce-33654b2e877d@suse.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | x86: detect PIT aliasing on ports other than 0x4[0-3] | expand |
On 2024-05-22 08:59, Jan Beulich wrote: > ... in order to also deny Dom0 access through the alias ports (commonly > observed on Intel chipsets). Without this it is only giving the > impression of denying access to PIT. Unlike for CMOS/RTC, do detection > pretty early, to avoid disturbing normal operation later on (even if > typically we won't use much of the PIT). > > Like for CMOS/RTC a fundamental assumption of the probing is that reads > from the probed alias port won't have side effects (beyond such that PIT > reads have anyway) in case it does not alias the PIT's. > > As to the port 0x61 accesses: Unlike other accesses we do, this masks > off the top four bits (in addition to the bottom two ones), following > Intel chipset documentation saying that these (read-only) bits should > only be written with zero. > > Signed-off-by: Jan Beulich <jbeulich@suse.com> > --- a/xen/arch/x86/time.c > +++ b/xen/arch/x86/time.c > @@ -427,6 +427,74 @@ static struct platform_timesource __init > .resume = resume_pit, > }; > > +unsigned int __initdata pit_alias_mask; > + > +static void __init probe_pit_alias(void) > +{ > + unsigned int mask = 0x1c; > + uint8_t val = 0; > + > + if ( !opt_probe_port_aliases ) > + return; > + > + /* > + * Use channel 2 in mode 0 for probing. In this mode even a non-initial > + * count is loaded independent of counting being / becoming enabled. Thus > + * we have a 16-bit value fully under our control, to write and then check > + * whether we can also read it back unaltered. > + */ > + > + /* Turn off speaker output and disable channel 2 counting. */ > + outb(inb(0x61) & 0x0c, 0x61); > + > + outb(PIT_LTCH_CH(2) | PIT_RW_LSB_MSB | PIT_MODE_EOC | PIT_BINARY, > + PIT_MODE); > + > + do { > + uint8_t val2; > + unsigned int offs; > + > + outb(val, PIT_CH2); > + outb(val ^ 0xff, PIT_CH2); > + > + /* Wait for the Null Count bit to clear. */ > + do { > + /* Latch status. */ > + outb(PIT_RDB | PIT_RDB_NO_COUNT | PIT_RDB_CH2, PIT_MODE); > + > + /* Try to make sure we're actually having a PIT here. */ > + val2 = inb(PIT_CH2); > + if ( (val2 & ~(PIT_STATUS_OUT_PIN | PIT_STATUS_NULL_COUNT)) != > + (PIT_RW_LSB_MSB | PIT_MODE_EOC | PIT_BINARY) ) > + return; > + } while ( val2 & (1 << 6) ); You can use PIT_STATUS_NULL_COUNT here. With that: Reviewed-by: Jason Andryuk <jason.andryuk@amd.com> Thanks, Jason
On 22.05.2024 15:57, Jason Andryuk wrote: > On 2024-05-22 08:59, Jan Beulich wrote: >> --- a/xen/arch/x86/time.c >> +++ b/xen/arch/x86/time.c >> @@ -427,6 +427,74 @@ static struct platform_timesource __init >> .resume = resume_pit, >> }; >> >> +unsigned int __initdata pit_alias_mask; >> + >> +static void __init probe_pit_alias(void) >> +{ >> + unsigned int mask = 0x1c; >> + uint8_t val = 0; >> + >> + if ( !opt_probe_port_aliases ) >> + return; >> + >> + /* >> + * Use channel 2 in mode 0 for probing. In this mode even a non-initial >> + * count is loaded independent of counting being / becoming enabled. Thus >> + * we have a 16-bit value fully under our control, to write and then check >> + * whether we can also read it back unaltered. >> + */ >> + >> + /* Turn off speaker output and disable channel 2 counting. */ >> + outb(inb(0x61) & 0x0c, 0x61); >> + >> + outb(PIT_LTCH_CH(2) | PIT_RW_LSB_MSB | PIT_MODE_EOC | PIT_BINARY, >> + PIT_MODE); >> + >> + do { >> + uint8_t val2; >> + unsigned int offs; >> + >> + outb(val, PIT_CH2); >> + outb(val ^ 0xff, PIT_CH2); >> + >> + /* Wait for the Null Count bit to clear. */ >> + do { >> + /* Latch status. */ >> + outb(PIT_RDB | PIT_RDB_NO_COUNT | PIT_RDB_CH2, PIT_MODE); >> + >> + /* Try to make sure we're actually having a PIT here. */ >> + val2 = inb(PIT_CH2); >> + if ( (val2 & ~(PIT_STATUS_OUT_PIN | PIT_STATUS_NULL_COUNT)) != >> + (PIT_RW_LSB_MSB | PIT_MODE_EOC | PIT_BINARY) ) >> + return; >> + } while ( val2 & (1 << 6) ); > > You can use PIT_STATUS_NULL_COUNT here. Indeed, and I meant to but then forgot. Thanks for noticing. > With that: > Reviewed-by: Jason Andryuk <jason.andryuk@amd.com> Thanks. Jan
--- a/xen/arch/x86/dom0_build.c +++ b/xen/arch/x86/dom0_build.c @@ -20,6 +20,7 @@ #include <asm/p2m.h> #include <asm/setup.h> #include <asm/spec_ctrl.h> +#include <io_ports.h> struct memsize { long nr_pages; @@ -495,7 +496,11 @@ int __init dom0_setup_permissions(struct rc |= ioports_deny_access(d, 0x4D0, 0x4D1); /* Interval Timer (PIT). */ - rc |= ioports_deny_access(d, 0x40, 0x43); + for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4; + offs <= pit_alias_mask; offs += i ) + if ( !(offs & ~pit_alias_mask) ) + rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs); + /* PIT Channel 2 / PC Speaker Control. */ rc |= ioports_deny_access(d, 0x61, 0x61); --- a/xen/arch/x86/include/asm/setup.h +++ b/xen/arch/x86/include/asm/setup.h @@ -49,6 +49,7 @@ extern unsigned long highmem_start; #endif extern unsigned int i8259A_alias_mask; +extern unsigned int pit_alias_mask; extern int8_t opt_smt; extern int8_t opt_probe_port_aliases; --- a/xen/arch/x86/time.c +++ b/xen/arch/x86/time.c @@ -427,6 +427,74 @@ static struct platform_timesource __init .resume = resume_pit, }; +unsigned int __initdata pit_alias_mask; + +static void __init probe_pit_alias(void) +{ + unsigned int mask = 0x1c; + uint8_t val = 0; + + if ( !opt_probe_port_aliases ) + return; + + /* + * Use channel 2 in mode 0 for probing. In this mode even a non-initial + * count is loaded independent of counting being / becoming enabled. Thus + * we have a 16-bit value fully under our control, to write and then check + * whether we can also read it back unaltered. + */ + + /* Turn off speaker output and disable channel 2 counting. */ + outb(inb(0x61) & 0x0c, 0x61); + + outb(PIT_LTCH_CH(2) | PIT_RW_LSB_MSB | PIT_MODE_EOC | PIT_BINARY, + PIT_MODE); + + do { + uint8_t val2; + unsigned int offs; + + outb(val, PIT_CH2); + outb(val ^ 0xff, PIT_CH2); + + /* Wait for the Null Count bit to clear. */ + do { + /* Latch status. */ + outb(PIT_RDB | PIT_RDB_NO_COUNT | PIT_RDB_CH2, PIT_MODE); + + /* Try to make sure we're actually having a PIT here. */ + val2 = inb(PIT_CH2); + if ( (val2 & ~(PIT_STATUS_OUT_PIN | PIT_STATUS_NULL_COUNT)) != + (PIT_RW_LSB_MSB | PIT_MODE_EOC | PIT_BINARY) ) + return; + } while ( val2 & (1 << 6) ); + + /* + * Try to further make sure we're actually having a PIT here. + * + * NB: Deliberately |, not ||, as we always want both reads. + */ + val2 = inb(PIT_CH2); + if ( (val2 ^ val) | (inb(PIT_CH2) ^ val ^ 0xff) ) + return; + + for ( offs = ISOLATE_LSB(mask); offs <= mask; offs <<= 1 ) + { + if ( !(mask & offs) ) + continue; + val2 = inb(PIT_CH2 + offs); + if ( (val2 ^ val) | (inb(PIT_CH2 + offs) ^ val ^ 0xff) ) + mask &= ~offs; + } + } while ( mask && (val += 0x0b) ); /* Arbitrary uneven number. */ + + if ( mask ) + { + dprintk(XENLOG_INFO, "PIT aliasing mask: %02x\n", mask); + pit_alias_mask = mask; + } +} + /************************************************************ * PLATFORM TIMER 2: HIGH PRECISION EVENT TIMER (HPET) */ @@ -2416,6 +2484,8 @@ void __init early_time_init(void) } preinit_pit(); + probe_pit_alias(); + tmp = init_platform_timer(); plt_tsc.frequency = tmp;
... in order to also deny Dom0 access through the alias ports (commonly observed on Intel chipsets). Without this it is only giving the impression of denying access to PIT. Unlike for CMOS/RTC, do detection pretty early, to avoid disturbing normal operation later on (even if typically we won't use much of the PIT). Like for CMOS/RTC a fundamental assumption of the probing is that reads from the probed alias port won't have side effects (beyond such that PIT reads have anyway) in case it does not alias the PIT's. As to the port 0x61 accesses: Unlike other accesses we do, this masks off the top four bits (in addition to the bottom two ones), following Intel chipset documentation saying that these (read-only) bits should only be written with zero. Signed-off-by: Jan Beulich <jbeulich@suse.com> --- If Xen was running on top of another instance of itself (in HVM mode, not PVH, i.e. not as a shim), prior to 14f42af3f52d ('x86/vPIT: account for "counter stopped" time') I'm afraid our vPIT logic would not have allowed the "Try to further make sure ..." check to pass in the Xen running on top: We don't respect the gate bit being clear when handling counter reads. (There are more unhandled [and unmentioned as being so] aspects of PIT behavior though, yet it's unclear in how far addressing at least some of them would be useful.) --- v3: Use PIT_* in dom0_setup_permissions(). Use #define-s introduced by new earlier patch. v2: Use new command line option. Re-base over changes to earlier patches. Use ISOLATE_LSB().