Hello,
Following patch makes seabios friendly to 386/486 CPUs, providing a way how to emulate the CPUID instruction and using TSC only if CPU actually supports the TSC. Otherwise it uses TSC emulation mode which is similar to what is used in Xenomai except we use timer0 here.
With this patch it is able to run on 486SX class system aka bifferboard together with coreboot.
Signed-off-by: Rudolf Marek r.marek@assembler.cz
Please let me know if you there is something you don't like in that patch.
Thanks Rudolf
On Sun, Jan 22, 2012 at 10:04:33PM +0100, Rudolf Marek wrote:
Hello,
Following patch makes seabios friendly to 386/486 CPUs, providing a way how to emulate the CPUID instruction and using TSC only if CPU actually supports the TSC. Otherwise it uses TSC emulation mode which is similar to what is used in Xenomai except we use timer0 here.
Thanks - please see my comments below.
With this patch it is able to run on 486SX class system aka bifferboard together with coreboot.
It would be preferable if this was in two patches - one to check for cpuid and one to check for the tsc.
[...]
--- a/src/biosvar.h +++ b/src/biosvar.h @@ -238,6 +238,10 @@ struct extended_bios_data_area_s {
u16 boot_sequence;
- /* TSC emulation timekeepers */
- u64 tsc_8254;
- int last_tsc_8254;
Can't the existing timer_counter in the BDA be used instead?
[...]
--- a/src/clock.c +++ b/src/clock.c @@ -57,6 +57,24 @@ #define CALIBRATE_COUNT 0x800 // Approx 1.7ms
u32 cpu_khz VAR16VISIBLE; +u8 no_tsc VAR16VISIBLE;
+u64 emulate_tsc(void) {
- int cnt, d;
- u16 ebda_seg = get_ebda_seg();
- u64 ret;
- /* read timer 0 current count */
- ret = GET_EBDA2(ebda_seg, tsc_8254);
- /* readback mode has slightly shifted registers, works on all 8254, readback PIT0 latch */
- outb(PM_SEL_READBACK | 0x10 | 0x2, PORT_PIT_MODE);
BTW, what's the impact of writing to the PORT_PIT_MODE? Will it corrupt the old value? (Not an issue for the patch, as obviously something is better than nothing for your board.)
[...]
- SET_GLOBAL(no_tsc, 0);
FYI - SET_GLOBAL(x,y) is the same as writing "x=y". The macro is only available in 32bit mode, so it's not really any different.
[...]
--- a/src/post.c +++ b/src/post.c @@ -360,6 +360,9 @@ handle_post(void) // Enable CPU caching setcr0(getcr0() & ~(CR0_CD|CR0_NW));
- // Emulate the CPUID if 386/486
- detect_cpuid();
This is too early (can't assign global variables here on QEmu). I suggest you put in maininit() after the malloc_fixup() call.
[...]
--- a/src/util.h +++ b/src/util.h @@ -7,6 +7,10 @@ #define __UTIL_H
#include "types.h" // u32 +#include "biosvar.h" // GET_GLOBAL
+#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0)
These already exist in types.h.
-Kevin
Hi,
Thanks for the quick reply,
It would be preferable if this was in two patches - one to check for cpuid and one to check for the tsc.
OK I can do that.
Can't the existing timer_counter in the BDA be used instead?
I got some earlier iteration which used the *_timer value but it did not work for some reason (most likely I forgot all the magic for real mode)
I think using extra variables will remove the complexity with day rollover and custom changes to the timer variable either from option ROM or from BIOS API.
BTW, what's the impact of writing to the PORT_PIT_MODE? Will it corrupt the old value? (Not an issue for the patch, as obviously something is better than nothing for your board.)
I think none, it is a readback command of the 8254 chip which allows to read back the latch and configuration. The mode of timer or timer itself should not be affected. I think this may need to be protected if some interrupt jump in. I had in previous patch the IRQ_save/restore stuff but you told me that irqs are enabled only during yields. Maybe it would be safer to have the irq_save / restore here too?
[...]
- SET_GLOBAL(no_tsc, 0);
FYI - SET_GLOBAL(x,y) is the same as writing "x=y". The macro is only available in 32bit mode, so it's not really any different.
Ok but lets keep it orthogonal.
- // Emulate the CPUID if 386/486
- detect_cpuid();
This is too early (can't assign global variables here on QEmu). I suggest you put in maininit() after the malloc_fixup() call.
It is too late there. There is some CPUID instruction executed from xen_probe(). Yes I xen is enabled for coreboot for some reason... The whole point of the cpuid emulation is to have it globally available and not making it dependent on particular configuration. Not sure how to solve it here.
These already exist in types.h.
OK I remove them,
Thanks for the review.
Rudolf
On Sun, Jan 22, 2012 at 11:12:00PM +0100, Rudolf Marek wrote:
Can't the existing timer_counter in the BDA be used instead?
I got some earlier iteration which used the *_timer value but it did not work for some reason (most likely I forgot all the magic for real mode)
I think using extra variables will remove the complexity with day rollover and custom changes to the timer variable either from option ROM or from BIOS API.
I'm not sure there would be an issue with third party code (optionroms / old code changing the pit). The seabios timer users are fairly straight forward - get the current value, and then wait for the expected value in a loop with yield(). So, there'd only be a problem if some third-party irq handler messed with the pit - if they did that from a hardware irq handler, I'd guess it would break lots of things. (Things are less clear with CONFIG_THREAD_OPTIONROMS, but I'd say it would be safe to leave that off for your use case.)
BTW, what's the impact of writing to the PORT_PIT_MODE? Will it corrupt the old value? (Not an issue for the patch, as obviously something is better than nothing for your board.)
I think none, it is a readback command of the 8254 chip which allows to read back the latch and configuration. The mode of timer or timer itself should not be affected. I think this may need to be protected if some interrupt jump in. I had in previous patch the IRQ_save/restore stuff but you told me that irqs are enabled only during yields. Maybe it would be safer to have the irq_save / restore here too?
All SeaBIOS C code runs with irqs disabled. There's no reason to have irq_save/restore in the C code.
However, are there any seabios timer users reachable from irq handlers? Should userspace start programming the pit without irq prevention, could it get interrupted mid-way through and then corrupted by a seabios timer user in an irq handler?
- // Emulate the CPUID if 386/486
- detect_cpuid();
This is too early (can't assign global variables here on QEmu). I suggest you put in maininit() after the malloc_fixup() call.
It is too late there. There is some CPUID instruction executed from xen_probe(). Yes I xen is enabled for coreboot for some reason... The whole point of the cpuid emulation is to have it globally available and not making it dependent on particular configuration. Not sure how to solve it here.
Hrmm - the xen thing is a special case - I think we could just make CONFIG_XEN dependent on !CONFIG_COREBOOT in Kconfig and ignore it's early cpuid usage.
-Kevin
Hi again,
I'm not sure there would be an issue with third party code (optionroms / old code changing the pit). The seabios timer users are fairly straight forward - get the current value, and then wait for the expected value in a loop with yield(). So, there'd only be a problem if some third-party irq handler messed with the pit - if they did that from a hardware irq handler, I'd guess it would break lots of things. (Things are less clear with CONFIG_THREAD_OPTIONROMS, but I'd say it would be safe to leave that off for your use case.)
Well I had in mind the changes to timer_counter itself. There is an API to change that (1a02) and also there is a rollover. It seems more complex to me to handle all the cases than just use two variables more.
However, are there any seabios timer users reachable from irq handlers? Should userspace start programming the pit without irq prevention, could it get interrupted mid-way through and then corrupted by a seabios timer user in an irq handler?
I did not study what happens to unread values halfway through. I would suggest to mimick the TSC as close as possible making the readout also atomic (guarded with irq_safe, irq_restore which in most cases will do nothing but it might fix the race conditions if someones manages to use counting services in irqs/strange parts of the yield code.
This reminds me that the GET_ set stuff should treat the variables as volatile, because otherwise if it can be changed from outside compiler might not know and will happily perform a test on register version of the variable. Or even better, it should contain memory clobber asm volatile ("" ::: "memory") which will act as a barrier for compiler to reload all variables used and not rely on cached values in registers.
Hrmm - the xen thing is a special case - I think we could just make CONFIG_XEN dependent on !CONFIG_COREBOOT in Kconfig and ignore it's early cpuid usage.
OK.
Thanks Rudolf
This reminds me that the GET_ set stuff should treat the variables as volatile, because otherwise if it can be changed from outside compiler might not know and will happily perform a test on register version of the variable. Or even better, it should contain memory clobber asm volatile ("" ::: "memory") which will act as a barrier for compiler to reload all variables used and not rely on cached values in registers.
Just to make it more clear, this remark is just for the situation if you may have irq handler interrupting a function. Compiler can read from memory just once and use cached value in register.
All in all everything depends what can/could be called while interrupts are enabled in seabios. If you want to have the GET/SET stuff at least atomic, and reloadable then use same thing as in Linux kernel "atomic" variables. On x86 mov is guaranteed to be atomic (at most 32bit only) and because it is internally implemented as structure with volatile property it works fine in all cases.
In my case I use here 64bit data then it means I need to use irq_save restore in any case. It is even doing a barrier for compiler. Therefore I think it is the best solution here.
Thanks Rudolf
On Tue, Jan 24, 2012 at 12:47:39PM +0100, Rudolf Marek wrote:
This reminds me that the GET_ set stuff should treat the variables as volatile, because otherwise if it can be changed from outside compiler might not know and will happily perform a test on register version of the variable. Or even better, it should contain memory clobber asm volatile ("" ::: "memory") which will act as a barrier for compiler to reload all variables used and not rely on cached values in registers.
Just to make it more clear, this remark is just for the situation if you may have irq handler interrupting a function. Compiler can read from memory just once and use cached value in register.
SeaBIOS C code always runs with irqs disabled.
I've played with the macros in farptr.h quite a bit, and I think they're okay. If anything, the compiler seems to unnecessarily pessimize the resulting assembler (I've never seen it cache the result of a read even when it's valid to do that).
All in all everything depends what can/could be called while interrupts are enabled in seabios. If you want to have the GET/SET stuff at least atomic, and reloadable then use same thing as in Linux kernel "atomic" variables. On x86 mov is guaranteed to be atomic (at most 32bit only) and because it is internally implemented as structure with volatile property it works fine in all cases.
Since the code runs with irqs disabled, it can't be interrupted and is therefore atomic. The yield() call does invoke a barrier() so it can't be run out of order.
In my case I use here 64bit data then it means I need to use irq_save restore in any case. It is even doing a barrier for compiler. Therefore I think it is the best solution here.
Please don't do that. It's just going to confuse the code. There should be no reason for a barrier() or messing with the irqs.
-Kevin
On Tue, Jan 24, 2012 at 12:01:10PM +0100, Rudolf Marek wrote:
Hi again,
I'm not sure there would be an issue with third party code (optionroms / old code changing the pit). The seabios timer users are fairly straight forward - get the current value, and then wait for the expected value in a loop with yield(). So, there'd only be a problem if some third-party irq handler messed with the pit - if they did that from a hardware irq handler, I'd guess it would break lots of things. (Things are less clear with CONFIG_THREAD_OPTIONROMS, but I'd say it would be safe to leave that off for your use case.)
Well I had in mind the changes to timer_counter itself. There is an API to change that (1a02) and also there is a rollover. It seems more complex to me to handle all the cases than just use two variables more.
Thinking about this more, I agree with you - it's probably simpler to just keep separate variables.
-Kevin
Hi all
As requested here is the first patch.
Thanks Rudolf
On Wed, Jan 25, 2012 at 06:45:28PM +0100, Rudolf Marek wrote:
Hi all
As requested here is the first patch.
How about this patch instead?
-Kevin
commit c2228f401c13e0b300c6b1fcc43cc9097f47bb43 Author: Kevin O'Connor kevin@koconnor.net Date: Sun Jan 29 13:30:56 2012 -0500
Detect CPUID instruction before using it.
Enable SeaBIOS to work on 386/486 machines that don't have CPUID instruction.
Based on patch by Rudolf Marek.
Signed-off-by: Rudolf Marek r.marek@assembler.cz Signed-off-by: Kevin O'Connor kevin@koconnor.net
diff --git a/src/bregs.h b/src/bregs.h index f026fa8..577effc 100644 --- a/src/bregs.h +++ b/src/bregs.h @@ -11,6 +11,7 @@ #define F_CF (1<<0) #define F_ZF (1<<6) #define F_IF (1<<9) +#define F_ID (1<<21)
// CR0 flags #define CR0_PG (1<<31) // Paging diff --git a/src/mptable.c b/src/mptable.c index 103f462..3422212 100644 --- a/src/mptable.c +++ b/src/mptable.c @@ -35,7 +35,7 @@ mptable_init(void)
// Detect cpu info u32 cpuid_signature, ebx, ecx, cpuid_features; - cpuid(1, &cpuid_signature, &ebx, &ecx, &cpuid_features); + get_cpuid(1, &cpuid_signature, &ebx, &ecx, &cpuid_features); if (! cpuid_signature) { // Use default values. cpuid_signature = 0x600; diff --git a/src/mtrr.c b/src/mtrr.c index 0548043..b6a8427 100644 --- a/src/mtrr.c +++ b/src/mtrr.c @@ -37,7 +37,7 @@ void mtrr_setup(void) return;
u32 eax, ebx, ecx, edx, cpuid_features; - cpuid(1, &eax, &ebx, &ecx, &cpuid_features); + get_cpuid(1, &eax, &ebx, &ecx, &cpuid_features); if (!(cpuid_features & CPUID_MTRR)) return; if (!(cpuid_features & CPUID_MSR)) @@ -82,11 +82,11 @@ void mtrr_setup(void)
// Set variable MTRRs int phys_bits = 36; - cpuid(0x80000000u, &eax, &ebx, &ecx, &edx); + get_cpuid(0x80000000u, &eax, &ebx, &ecx, &edx); if (eax >= 0x80000008) { - /* Get physical bits from leaf 0x80000008 (if available) */ - cpuid(0x80000008u, &eax, &ebx, &ecx, &edx); - phys_bits = eax & 0xff; + /* Get physical bits from leaf 0x80000008 (if available) */ + get_cpuid(0x80000008u, &eax, &ebx, &ecx, &edx); + phys_bits = eax & 0xff; } u64 phys_mask = ((1ull << phys_bits) - 1); for (i=0; i<vcnt; i++) { diff --git a/src/paravirt.h b/src/paravirt.h index 4a370a0..4014943 100644 --- a/src/paravirt.h +++ b/src/paravirt.h @@ -11,10 +11,12 @@
static inline int kvm_para_available(void) { + if (CONFIG_COREBOOT) + return 0; unsigned int eax, ebx, ecx, edx; char signature[13];
- cpuid(KVM_CPUID_SIGNATURE, &eax, &ebx, &ecx, &edx); + get_cpuid(KVM_CPUID_SIGNATURE, &eax, &ebx, &ecx, &edx); memcpy(signature + 0, &ebx, 4); memcpy(signature + 4, &ecx, 4); memcpy(signature + 8, &edx, 4); diff --git a/src/smbios.c b/src/smbios.c index fe1e183..9c8df98 100644 --- a/src/smbios.c +++ b/src/smbios.c @@ -255,7 +255,7 @@ smbios_init_type_4(void *start, unsigned int cpu_number) if (!qemu_cfg_smbios_load_field(4, offsetof(struct smbios_type_4, processor_id), p->processor_id)) { u32 cpuid_signature, ebx, ecx, cpuid_features; - cpuid(1, &cpuid_signature, &ebx, &ecx, &cpuid_features); + get_cpuid(1, &cpuid_signature, &ebx, &ecx, &cpuid_features); p->processor_id[0] = cpuid_signature; p->processor_id[1] = cpuid_features; } diff --git a/src/smp.c b/src/smp.c index 8c077a1..e36f960 100644 --- a/src/smp.c +++ b/src/smp.c @@ -73,7 +73,7 @@ smp_probe(void) { ASSERT32FLAT(); u32 eax, ebx, ecx, cpuid_features; - cpuid(1, &eax, &ebx, &ecx, &cpuid_features); + get_cpuid(1, &eax, &ebx, &ecx, &cpuid_features); if (eax < 1 || !(cpuid_features & CPUID_APIC)) { // No apic - only the main cpu is present. dprintf(1, "No apic - only the main cpu is present.\n"); diff --git a/src/util.c b/src/util.c index ed73d63..7141f5d 100644 --- a/src/util.c +++ b/src/util.c @@ -8,6 +8,22 @@ #include "bregs.h" // struct bregs #include "config.h" // BUILD_STACK_ADDR
+void +get_cpuid(u32 index, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) +{ + // Check for cpu id + u32 origflags = save_flags(); + restore_flags(origflags ^ F_ID); + u32 newflags = save_flags(); + restore_flags(origflags); + + if (((origflags ^ newflags) & F_ID) != F_ID) + // no cpuid + *eax = *ebx = *ecx = *edx = 0; + else + cpuid(index, eax, ebx, ecx, edx); +} +
/**************************************************************** * 16bit calls diff --git a/src/util.h b/src/util.h index eecba8b..a69949d 100644 --- a/src/util.h +++ b/src/util.h @@ -18,15 +18,14 @@ static inline void irq_enable(void) asm volatile("sti": : :"memory"); }
-static inline unsigned long irq_save(void) +static inline u32 save_flags(void) { - unsigned long flags; - asm volatile("pushfl ; popl %0" : "=g" (flags): :"memory"); - irq_disable(); + u32 flags; + asm("pushfl ; popl %0" : "=rm" (flags)); return flags; }
-static inline void irq_restore(unsigned long flags) +static inline void restore_flags(u32 flags) { asm volatile("pushl %0 ; popfl" : : "g" (flags) : "memory", "cc"); } @@ -196,6 +195,7 @@ struct descloc_s { } PACKED;
// util.c +void get_cpuid(u32 index, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx); struct bregs; inline void call16(struct bregs *callregs); inline void call16big(struct bregs *callregs); diff --git a/src/xen.c b/src/xen.c index 4072793..5f4abdb 100644 --- a/src/xen.c +++ b/src/xen.c @@ -55,7 +55,7 @@ void xen_probe(void) return;
for (base = 0x40000000; base < 0x40010000; base += 0x100) { - cpuid(base, &eax, &ebx, &ecx, &edx); + get_cpuid(base, &eax, &ebx, &ecx, &edx); memcpy(signature + 0, &ebx, 4); memcpy(signature + 4, &ecx, 4); memcpy(signature + 8, &edx, 4); @@ -88,7 +88,7 @@ void xen_init_hypercalls(void) if (!usingXen()) return;
- cpuid(xen_cpuid_base + 2, &eax, &ebx, &ecx, &edx); + get_cpuid(xen_cpuid_base + 2, &eax, &ebx, &ecx, &edx);
xen_hypercall_page = (unsigned long)memalign_high(PAGE_SIZE, eax*PAGE_SIZE); if (!xen_hypercall_page) @@ -99,7 +99,7 @@ void xen_init_hypercalls(void) wrmsr(ebx, xen_hypercall_page + (i << 12) + i);
/* Print version information. */ - cpuid(xen_cpuid_base + 1, &eax, &ebx, &ecx, &edx); + get_cpuid(xen_cpuid_base + 1, &eax, &ebx, &ecx, &edx); hypercall_xen_version(XENVER_extraversion, extraversion); dprintf(1, "Detected Xen v%u.%u%s\n", eax >> 16, eax & 0xffff, extraversion); }
Hi,
Sure why not. The idea behind the variable use was not to perform the test everytime as you suggested on the IRC channel. But I like this variant too, lets give it a go.
Thanks Rudolf
On Sun, Jan 29, 2012 at 07:56:05PM +0100, Rudolf Marek wrote:
Hi,
Sure why not. The idea behind the variable use was not to perform the test everytime as you suggested on the IRC channel. But I like this variant too, lets give it a go.
Yeah - sorry about that - the fact that cpuid is needed so early makes it painful to cache the results. Also, cpuid isn't really called frequently, so I don't think there's much harm in doing explicit detection. Now that I think about it, though, it's probably better to keep the name cpuid() and rename the low-level cpuid to __cpuid().
-Kevin
On Sun, Jan 29, 2012 at 02:02:38PM -0500, Kevin O'Connor wrote:
On Sun, Jan 29, 2012 at 07:56:05PM +0100, Rudolf Marek wrote:
Hi,
Sure why not. The idea behind the variable use was not to perform the test everytime as you suggested on the IRC channel. But I like this variant too, lets give it a go.
Yeah - sorry about that - the fact that cpuid is needed so early makes it painful to cache the results. Also, cpuid isn't really called frequently, so I don't think there's much harm in doing explicit detection. Now that I think about it, though, it's probably better to keep the name cpuid() and rename the low-level cpuid to __cpuid().
New patch below.
-Kevin
commit dabc0039ece59cabc37bf1164e76be7c7af0eea5 Author: Kevin O'Connor kevin@koconnor.net Date: Sun Jan 29 13:30:56 2012 -0500
Detect CPUID instruction before using it.
Enable SeaBIOS to work on 386/486 machines that don't have CPUID instruction.
Based on patch by Rudolf Marek.
Signed-off-by: Rudolf Marek r.marek@assembler.cz Signed-off-by: Kevin O'Connor kevin@koconnor.net
diff --git a/src/bregs.h b/src/bregs.h index f026fa8..577effc 100644 --- a/src/bregs.h +++ b/src/bregs.h @@ -11,6 +11,7 @@ #define F_CF (1<<0) #define F_ZF (1<<6) #define F_IF (1<<9) +#define F_ID (1<<21)
// CR0 flags #define CR0_PG (1<<31) // Paging diff --git a/src/mtrr.c b/src/mtrr.c index 0548043..ec3be4f 100644 --- a/src/mtrr.c +++ b/src/mtrr.c @@ -84,9 +84,9 @@ void mtrr_setup(void) int phys_bits = 36; cpuid(0x80000000u, &eax, &ebx, &ecx, &edx); if (eax >= 0x80000008) { - /* Get physical bits from leaf 0x80000008 (if available) */ - cpuid(0x80000008u, &eax, &ebx, &ecx, &edx); - phys_bits = eax & 0xff; + /* Get physical bits from leaf 0x80000008 (if available) */ + cpuid(0x80000008u, &eax, &ebx, &ecx, &edx); + phys_bits = eax & 0xff; } u64 phys_mask = ((1ull << phys_bits) - 1); for (i=0; i<vcnt; i++) { diff --git a/src/paravirt.h b/src/paravirt.h index 4a370a0..9674089 100644 --- a/src/paravirt.h +++ b/src/paravirt.h @@ -11,6 +11,8 @@
static inline int kvm_para_available(void) { + if (CONFIG_COREBOOT) + return 0; unsigned int eax, ebx, ecx, edx; char signature[13];
diff --git a/src/util.c b/src/util.c index ed73d63..53ef84d 100644 --- a/src/util.c +++ b/src/util.c @@ -8,6 +8,22 @@ #include "bregs.h" // struct bregs #include "config.h" // BUILD_STACK_ADDR
+void +cpuid(u32 index, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) +{ + // Check for cpu id + u32 origflags = save_flags(); + restore_flags(origflags ^ F_ID); + u32 newflags = save_flags(); + restore_flags(origflags); + + if (((origflags ^ newflags) & F_ID) != F_ID) + // no cpuid + *eax = *ebx = *ecx = *edx = 0; + else + __cpuid(index, eax, ebx, ecx, edx); +} +
/**************************************************************** * 16bit calls diff --git a/src/util.h b/src/util.h index eecba8b..2c5f7eb 100644 --- a/src/util.h +++ b/src/util.h @@ -18,15 +18,14 @@ static inline void irq_enable(void) asm volatile("sti": : :"memory"); }
-static inline unsigned long irq_save(void) +static inline u32 save_flags(void) { - unsigned long flags; - asm volatile("pushfl ; popl %0" : "=g" (flags): :"memory"); - irq_disable(); + u32 flags; + asm volatile("pushfl ; popl %0" : "=rm" (flags)); return flags; }
-static inline void irq_restore(unsigned long flags) +static inline void restore_flags(u32 flags) { asm volatile("pushl %0 ; popfl" : : "g" (flags) : "memory", "cc"); } @@ -54,7 +53,7 @@ static inline void wbinvd(void) #define CPUID_MSR (1 << 5) #define CPUID_APIC (1 << 9) #define CPUID_MTRR (1 << 12) -static inline void cpuid(u32 index, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) +static inline void __cpuid(u32 index, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx) { asm("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) @@ -196,6 +195,7 @@ struct descloc_s { } PACKED;
// util.c +void cpuid(u32 index, u32 *eax, u32 *ebx, u32 *ecx, u32 *edx); struct bregs; inline void call16(struct bregs *callregs); inline void call16big(struct bregs *callregs);
Hi
OK thanks for looking into that. I checked your second TSC patch and it looks good to me. I can test it on Monday evening.
Acked-By: Rudolf Marek r.marek@assembler.cz
Thanks Rudolf
And here is the other part.
Thanks Rudolf
On Wed, Jan 25, 2012 at 06:46:03PM +0100, Rudolf Marek wrote:
And here is the other part.
How about this patch instead?
-Kevin
commit 1f35810efe39e156fbd8df1b84888f6e092d5337 Author: Kevin O'Connor kevin@koconnor.net Date: Sun Jan 29 14:15:14 2012 -0500
Add TSC emulation layer for 386/486 CPUs.
Original patch from Rudolf Marek.
Signed-off-by: Rudolf Marek r.marek@assembler.cz Signed-off-by: Kevin O'Connor kevin@koconnor.net
diff --git a/src/biosvar.h b/src/biosvar.h index ad791ab..b6f7174 100644 --- a/src/biosvar.h +++ b/src/biosvar.h @@ -239,6 +239,10 @@ struct extended_bios_data_area_s {
u16 boot_sequence;
+ /* TSC emulation timekeepers */ + u64 tsc_8254; + int last_tsc_8254; + // Stack space available for code that needs it. u8 extra_stack[512] __aligned(8); } PACKED; diff --git a/src/clock.c b/src/clock.c index fcdc698..e8a48a1 100644 --- a/src/clock.c +++ b/src/clock.c @@ -48,6 +48,12 @@ #define PM_MODE5 (5<<1) #define PM_CNT_BINARY (0<<0) #define PM_CNT_BCD (1<<0) +#define PM_READ_COUNTER0 (1<<1) +#define PM_READ_COUNTER1 (1<<2) +#define PM_READ_COUNTER2 (1<<3) +#define PM_READ_STATUSVALUE (0<<4) +#define PM_READ_VALUE (1<<4) +#define PM_READ_STATUS (2<<4)
/**************************************************************** @@ -57,10 +63,23 @@ #define CALIBRATE_COUNT 0x800 // Approx 1.7ms
u32 cpu_khz VAR16VISIBLE; +u8 no_tsc VAR16VISIBLE;
static void calibrate_tsc(void) { + u32 eax, ebx, ecx, edx, cpuid_features = 0; + cpuid(0, &eax, &ebx, &ecx, &edx); + if (eax > 0) + cpuid(1, &eax, &ebx, &ecx, &cpuid_features); + + if (!(cpuid_features & CPUID_TSC)) { + SET_GLOBAL(no_tsc, 1); + SET_GLOBAL(cpu_khz, PIT_TICK_RATE / 1000); + dprintf(3, "386/486 class CPU. Using TSC emulation\n"); + return; + } + // Setup "timer2" u8 orig = inb(PORT_PS2_CTRLB); outb((orig & ~PPCB_SPKR) | PPCB_T2GATE, PORT_PS2_CTRLB); @@ -89,10 +108,43 @@ calibrate_tsc(void) dprintf(1, "CPU Mhz=%u\n", hz / 1000000); }
+static u64 +emulate_tsc(void) +{ + int cnt, d; + u16 ebda_seg = get_ebda_seg(); + u64 ret; + /* read timer 0 current count */ + ret = GET_EBDA2(ebda_seg, tsc_8254); + /* readback mode has slightly shifted registers, works on all 8254, readback PIT0 latch */ + outb(PM_SEL_READBACK | PM_READ_VALUE | PM_READ_COUNTER0, PORT_PIT_MODE); + cnt = (inb(PORT_PIT_COUNTER0) | (inb(PORT_PIT_COUNTER0) << 8)); + d = GET_EBDA2(ebda_seg, last_tsc_8254) - cnt; + /* Determine the ticks count from last invocation of this function */ + ret += (d > 0) ? d : (PIT_TICK_INTERVAL + d); + SET_EBDA2(ebda_seg, last_tsc_8254, cnt); + SET_EBDA2(ebda_seg, tsc_8254, ret); + return ret; +} + +static u64 +get_tsc(void) +{ + if (unlikely(GET_GLOBAL(no_tsc))) + return emulate_tsc(); + return rdtscll(); +} + +int +check_tsc(u64 end) +{ + return (s64)(get_tsc() - end) > 0; +} + static void tscdelay(u64 diff) { - u64 start = rdtscll(); + u64 start = get_tsc(); u64 end = start + diff; while (!check_tsc(end)) cpu_relax(); @@ -101,7 +153,7 @@ tscdelay(u64 diff) static void tscsleep(u64 diff) { - u64 start = rdtscll(); + u64 start = get_tsc(); u64 end = start + diff; while (!check_tsc(end)) yield(); @@ -132,13 +184,13 @@ u64 calc_future_tsc(u32 msecs) { u32 khz = GET_GLOBAL(cpu_khz); - return rdtscll() + ((u64)khz * msecs); + return get_tsc() + ((u64)khz * msecs); } u64 calc_future_tsc_usec(u32 usecs) { u32 khz = GET_GLOBAL(cpu_khz); - return rdtscll() + ((u64)(khz/1000) * usecs); + return get_tsc() + ((u64)(khz/1000) * usecs); }
diff --git a/src/util.h b/src/util.h index 2c5f7eb..70d3c4c 100644 --- a/src/util.h +++ b/src/util.h @@ -50,6 +50,7 @@ static inline void wbinvd(void) asm volatile("wbinvd": : :"memory"); }
+#define CPUID_TSC (1 << 4) #define CPUID_MSR (1 << 5) #define CPUID_APIC (1 << 9) #define CPUID_MTRR (1 << 12) @@ -326,9 +327,7 @@ void lpt_setup(void); // clock.c #define PIT_TICK_RATE 1193180 // Underlying HZ of PIT #define PIT_TICK_INTERVAL 65536 // Default interval for 18.2Hz timer -static inline int check_tsc(u64 end) { - return (s64)(rdtscll() - end) > 0; -} +int check_tsc(u64 end); void timer_setup(void); void ndelay(u32 count); void udelay(u32 count);