Signed-off-by: Kevin O'Connor kevin@koconnor.net --- Makefile | 2 +- src/clock.c | 240 +-------------------------------------------------------- src/csm.c | 1 + src/pit.h | 33 ++++++++ src/post.c | 1 + src/timer.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/usb-ehci.c | 1 + src/usb-ohci.c | 1 + src/usb-uhci.c | 1 + src/util.h | 13 ++-- 10 files changed, 273 insertions(+), 245 deletions(-) create mode 100644 src/pit.h create mode 100644 src/timer.c
diff --git a/Makefile b/Makefile index 759bbbb..00ef346 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ OUT=out/
# Source files SRCBOTH=misc.c stacks.c output.c util.c block.c floppy.c ata.c mouse.c \ - kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \ + kbd.c pci.c serial.c timer.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \ pnpbios.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \ usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \ virtio-ring.c virtio-pci.c virtio-blk.c virtio-scsi.c apm.c ahci.c \ diff --git a/src/clock.c b/src/clock.c index 2eedab8..b9a708b 100644 --- a/src/clock.c +++ b/src/clock.c @@ -10,6 +10,7 @@ #include "disk.h" // floppy_tick #include "cmos.h" // inb_cmos #include "pic.h" // pic_eoi1 +#include "pit.h" // PM_SEL_TIMER0 #include "bregs.h" // struct bregs #include "biosvar.h" // GET_GLOBAL #include "usb-hid.h" // usb_check_event @@ -26,213 +27,6 @@ #define RTC_B_DSE 0x01
-// Bits for PORT_PS2_CTRLB -#define PPCB_T2GATE (1<<0) -#define PPCB_SPKR (1<<1) -#define PPCB_T2OUT (1<<5) - -// Bits for PORT_PIT_MODE -#define PM_SEL_TIMER0 (0<<6) -#define PM_SEL_TIMER1 (1<<6) -#define PM_SEL_TIMER2 (2<<6) -#define PM_SEL_READBACK (3<<6) -#define PM_ACCESS_LATCH (0<<4) -#define PM_ACCESS_LOBYTE (1<<4) -#define PM_ACCESS_HIBYTE (2<<4) -#define PM_ACCESS_WORD (3<<4) -#define PM_MODE0 (0<<1) -#define PM_MODE1 (1<<1) -#define PM_MODE2 (2<<1) -#define PM_MODE3 (3<<1) -#define PM_MODE4 (4<<1) -#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) - - -/**************************************************************** - * TSC timer - ****************************************************************/ - -#define CALIBRATE_COUNT 0x800 // Approx 1.7ms - -u32 cpu_khz VARFSEG; -u8 no_tsc VARFSEG; - -u16 pmtimer_ioport VARFSEG; -u32 pmtimer_wraps VARLOW; -u32 pmtimer_last VARLOW; - -static void -calibrate_tsc(void) -{ - u32 eax, ebx, ecx, edx, cpuid_features = 0; - - if (CONFIG_PMTIMER && GET_GLOBAL(pmtimer_ioport)) { - dprintf(3, "pmtimer already configured; will not calibrate TSC\n"); - return; - } - - 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); - /* binary, mode 0, LSB/MSB, Ch 2 */ - outb(PM_SEL_TIMER2|PM_ACCESS_WORD|PM_MODE0|PM_CNT_BINARY, PORT_PIT_MODE); - /* LSB of ticks */ - outb(CALIBRATE_COUNT & 0xFF, PORT_PIT_COUNTER2); - /* MSB of ticks */ - outb(CALIBRATE_COUNT >> 8, PORT_PIT_COUNTER2); - - u64 start = rdtscll(); - while ((inb(PORT_PS2_CTRLB) & PPCB_T2OUT) == 0) - ; - u64 end = rdtscll(); - - // Restore PORT_PS2_CTRLB - outb(orig, PORT_PS2_CTRLB); - - // Store calibrated cpu khz. - u64 diff = end - start; - dprintf(6, "tsc calibrate start=%u end=%u diff=%u\n" - , (u32)start, (u32)end, (u32)diff); - u32 hz = diff * PIT_TICK_RATE / CALIBRATE_COUNT; - SET_GLOBAL(cpu_khz, hz / 1000); - - dprintf(1, "CPU Mhz=%u\n", hz / 1000000); -} - -/* TSC emulation timekeepers */ -u64 TSC_8254 VARLOW; -int Last_TSC_8254 VARLOW; - -static u64 -emulate_tsc(void) -{ - /* read timer 0 current count */ - u64 ret = GET_LOW(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); - int cnt = (inb(PORT_PIT_COUNTER0) | (inb(PORT_PIT_COUNTER0) << 8)); - int d = GET_LOW(Last_TSC_8254) - cnt; - /* Determine the ticks count from last invocation of this function */ - ret += (d > 0) ? d : (PIT_TICK_INTERVAL + d); - SET_LOW(Last_TSC_8254, cnt); - SET_LOW(TSC_8254, ret); - return ret; -} - -void pmtimer_setup(u16 ioport, u32 khz) -{ - if (!CONFIG_PMTIMER) - return; - dprintf(1, "Using pmtimer, ioport 0x%x, freq %d kHz\n", ioport, khz); - SET_GLOBAL(pmtimer_ioport, ioport); - SET_GLOBAL(cpu_khz, khz); -} - -static u64 pmtimer_get(void) -{ - u16 ioport = GET_GLOBAL(pmtimer_ioport); - u32 wraps = GET_LOW(pmtimer_wraps); - u32 pmtimer = inl(ioport) & 0xffffff; - - if (pmtimer < GET_LOW(pmtimer_last)) { - wraps++; - SET_LOW(pmtimer_wraps, wraps); - } - SET_LOW(pmtimer_last, pmtimer); - - dprintf(9, "pmtimer: %u:%u\n", wraps, pmtimer); - return (u64)wraps << 24 | pmtimer; -} - -static u64 -get_tsc(void) -{ - if (unlikely(GET_GLOBAL(no_tsc))) - return emulate_tsc(); - if (CONFIG_PMTIMER && GET_GLOBAL(pmtimer_ioport)) - return pmtimer_get(); - return rdtscll(); -} - -int -check_tsc(u64 end) -{ - return (s64)(get_tsc() - end) > 0; -} - -static void -tscdelay(u64 diff) -{ - u64 start = get_tsc(); - u64 end = start + diff; - while (!check_tsc(end)) - cpu_relax(); -} - -static void -tscsleep(u64 diff) -{ - u64 start = get_tsc(); - u64 end = start + diff; - while (!check_tsc(end)) - yield(); -} - -void ndelay(u32 count) { - tscdelay(count * GET_GLOBAL(cpu_khz) / 1000000); -} -void udelay(u32 count) { - tscdelay(count * GET_GLOBAL(cpu_khz) / 1000); -} -void mdelay(u32 count) { - tscdelay(count * GET_GLOBAL(cpu_khz)); -} - -void nsleep(u32 count) { - tscsleep(count * GET_GLOBAL(cpu_khz) / 1000000); -} -void usleep(u32 count) { - tscsleep(count * GET_GLOBAL(cpu_khz) / 1000); -} -void msleep(u32 count) { - tscsleep(count * GET_GLOBAL(cpu_khz)); -} - -// Return the TSC value that is 'msecs' time in the future. -u64 -calc_future_tsc(u32 msecs) -{ - u32 khz = GET_GLOBAL(cpu_khz); - return get_tsc() + ((u64)khz * msecs); -} -u64 -calc_future_tsc_usec(u32 usecs) -{ - u32 khz = GET_GLOBAL(cpu_khz); - return get_tsc() + ((u64)(khz/1000) * usecs); -} - - /**************************************************************** * Init ****************************************************************/ @@ -290,10 +84,9 @@ bcd2bin(u8 val) u8 Century VARLOW;
void -timer_setup(void) +clock_setup(void) { dprintf(3, "init timer\n"); - calibrate_tsc(); pit_setup();
rtc_setup(); @@ -326,35 +119,6 @@ timer_setup(void) * Standard clock functions ****************************************************************/
-#define TICKS_PER_DAY (u32)((u64)60*60*24*PIT_TICK_RATE / PIT_TICK_INTERVAL) - -// Calculate the timer value at 'count' number of full timer ticks in -// the future. -u32 -calc_future_timer_ticks(u32 count) -{ - return (GET_BDA(timer_counter) + count + 1) % TICKS_PER_DAY; -} - -// Return the timer value that is 'msecs' time in the future. -u32 -calc_future_timer(u32 msecs) -{ - if (!msecs) - return GET_BDA(timer_counter); - u32 kticks = DIV_ROUND_UP((u64)msecs * PIT_TICK_RATE, PIT_TICK_INTERVAL); - u32 ticks = DIV_ROUND_UP(kticks, 1000); - return calc_future_timer_ticks(ticks); -} - -// Check if the given timer value has passed. -int -check_timer(u32 end) -{ - return (((GET_BDA(timer_counter) + TICKS_PER_DAY - end) % TICKS_PER_DAY) - < (TICKS_PER_DAY/2)); -} - // get current clock count static void handle_1a00(struct bregs *regs) diff --git a/src/csm.c b/src/csm.c index 2886bba..e855d2c 100644 --- a/src/csm.c +++ b/src/csm.c @@ -170,6 +170,7 @@ handle_csm_0002(struct bregs *regs) bda->hdcount = 0;
timer_setup(); + clock_setup(); device_hardware_setup(); wait_threads(); interactive_bootmenu(); diff --git a/src/pit.h b/src/pit.h new file mode 100644 index 0000000..6d58895 --- /dev/null +++ b/src/pit.h @@ -0,0 +1,33 @@ +// Definitions for the Intel 8253 Programmable Interrupt Timer (PIT). +#ifndef __PIT_H +#define __PIT_H + +#define PIT_TICK_RATE 1193180 // Underlying HZ of PIT +#define PIT_TICK_INTERVAL 65536 // Default interval for 18.2Hz timer +#define TICKS_PER_DAY (u32)((u64)60*60*24*PIT_TICK_RATE / PIT_TICK_INTERVAL) + +// Bits for PORT_PIT_MODE +#define PM_SEL_TIMER0 (0<<6) +#define PM_SEL_TIMER1 (1<<6) +#define PM_SEL_TIMER2 (2<<6) +#define PM_SEL_READBACK (3<<6) +#define PM_ACCESS_LATCH (0<<4) +#define PM_ACCESS_LOBYTE (1<<4) +#define PM_ACCESS_HIBYTE (2<<4) +#define PM_ACCESS_WORD (3<<4) +#define PM_MODE0 (0<<1) +#define PM_MODE1 (1<<1) +#define PM_MODE2 (2<<1) +#define PM_MODE3 (3<<1) +#define PM_MODE4 (4<<1) +#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) + +#endif // pit.h diff --git a/src/post.c b/src/post.c index ff201fa..d706d4f 100644 --- a/src/post.c +++ b/src/post.c @@ -158,6 +158,7 @@ platform_hardware_setup(void) pic_setup(); mathcp_setup(); timer_setup(); + clock_setup();
// Platform specific setup qemu_platform_setup(); diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000..a491ef6 --- /dev/null +++ b/src/timer.c @@ -0,0 +1,225 @@ +// Internal timer support. +// +// Copyright (C) 2008-2013 Kevin O'Connor kevin@koconnor.net +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "pit.h" // PM_SEL_TIMER0 +#include "ioport.h" // PORT_PIT_MODE +#include "config.h" // CONFIG_* +#include "biosvar.h" // GET_LOW + +// Bits for PORT_PS2_CTRLB +#define PPCB_T2GATE (1<<0) +#define PPCB_SPKR (1<<1) +#define PPCB_T2OUT (1<<5) + + +/**************************************************************** + * TSC timer + ****************************************************************/ + +#define CALIBRATE_COUNT 0x800 // Approx 1.7ms + +u32 cpu_khz VARFSEG; +u8 no_tsc VARFSEG; + +u16 pmtimer_ioport VARFSEG; +u32 pmtimer_wraps VARLOW; +u32 pmtimer_last VARLOW; + +void +timer_setup(void) +{ + u32 eax, ebx, ecx, edx, cpuid_features = 0; + + if (CONFIG_PMTIMER && GET_GLOBAL(pmtimer_ioport)) { + dprintf(3, "pmtimer already configured; will not calibrate TSC\n"); + return; + } + + 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); + /* binary, mode 0, LSB/MSB, Ch 2 */ + outb(PM_SEL_TIMER2|PM_ACCESS_WORD|PM_MODE0|PM_CNT_BINARY, PORT_PIT_MODE); + /* LSB of ticks */ + outb(CALIBRATE_COUNT & 0xFF, PORT_PIT_COUNTER2); + /* MSB of ticks */ + outb(CALIBRATE_COUNT >> 8, PORT_PIT_COUNTER2); + + u64 start = rdtscll(); + while ((inb(PORT_PS2_CTRLB) & PPCB_T2OUT) == 0) + ; + u64 end = rdtscll(); + + // Restore PORT_PS2_CTRLB + outb(orig, PORT_PS2_CTRLB); + + // Store calibrated cpu khz. + u64 diff = end - start; + dprintf(6, "tsc calibrate start=%u end=%u diff=%u\n" + , (u32)start, (u32)end, (u32)diff); + u32 hz = diff * PIT_TICK_RATE / CALIBRATE_COUNT; + SET_GLOBAL(cpu_khz, hz / 1000); + + dprintf(1, "CPU Mhz=%u\n", hz / 1000000); +} + +/* TSC emulation timekeepers */ +u64 TSC_8254 VARLOW; +int Last_TSC_8254 VARLOW; + +static u64 +emulate_tsc(void) +{ + /* read timer 0 current count */ + u64 ret = GET_LOW(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); + int cnt = (inb(PORT_PIT_COUNTER0) | (inb(PORT_PIT_COUNTER0) << 8)); + int d = GET_LOW(Last_TSC_8254) - cnt; + /* Determine the ticks count from last invocation of this function */ + ret += (d > 0) ? d : (PIT_TICK_INTERVAL + d); + SET_LOW(Last_TSC_8254, cnt); + SET_LOW(TSC_8254, ret); + return ret; +} + +void pmtimer_setup(u16 ioport, u32 khz) +{ + if (!CONFIG_PMTIMER) + return; + dprintf(1, "Using pmtimer, ioport 0x%x, freq %d kHz\n", ioport, khz); + SET_GLOBAL(pmtimer_ioport, ioport); + SET_GLOBAL(cpu_khz, khz); +} + +static u64 pmtimer_get(void) +{ + u16 ioport = GET_GLOBAL(pmtimer_ioport); + u32 wraps = GET_LOW(pmtimer_wraps); + u32 pmtimer = inl(ioport) & 0xffffff; + + if (pmtimer < GET_LOW(pmtimer_last)) { + wraps++; + SET_LOW(pmtimer_wraps, wraps); + } + SET_LOW(pmtimer_last, pmtimer); + + dprintf(9, "pmtimer: %u:%u\n", wraps, pmtimer); + return (u64)wraps << 24 | pmtimer; +} + +static u64 +get_tsc(void) +{ + if (unlikely(GET_GLOBAL(no_tsc))) + return emulate_tsc(); + if (CONFIG_PMTIMER && GET_GLOBAL(pmtimer_ioport)) + return pmtimer_get(); + return rdtscll(); +} + +int +check_tsc(u64 end) +{ + return (s64)(get_tsc() - end) > 0; +} + +static void +tscdelay(u64 diff) +{ + u64 start = get_tsc(); + u64 end = start + diff; + while (!check_tsc(end)) + cpu_relax(); +} + +static void +tscsleep(u64 diff) +{ + u64 start = get_tsc(); + u64 end = start + diff; + while (!check_tsc(end)) + yield(); +} + +void ndelay(u32 count) { + tscdelay(count * GET_GLOBAL(cpu_khz) / 1000000); +} +void udelay(u32 count) { + tscdelay(count * GET_GLOBAL(cpu_khz) / 1000); +} +void mdelay(u32 count) { + tscdelay(count * GET_GLOBAL(cpu_khz)); +} + +void nsleep(u32 count) { + tscsleep(count * GET_GLOBAL(cpu_khz) / 1000000); +} +void usleep(u32 count) { + tscsleep(count * GET_GLOBAL(cpu_khz) / 1000); +} +void msleep(u32 count) { + tscsleep(count * GET_GLOBAL(cpu_khz)); +} + +// Return the TSC value that is 'msecs' time in the future. +u64 +calc_future_tsc(u32 msecs) +{ + u32 khz = GET_GLOBAL(cpu_khz); + return get_tsc() + ((u64)khz * msecs); +} +u64 +calc_future_tsc_usec(u32 usecs) +{ + u32 khz = GET_GLOBAL(cpu_khz); + return get_tsc() + ((u64)(khz/1000) * usecs); +} + + +/**************************************************************** + * IRQ based timer + ****************************************************************/ + +// Calculate the timer value at 'count' number of full timer ticks in +// the future. +u32 +calc_future_timer_ticks(u32 count) +{ + return (GET_BDA(timer_counter) + count + 1) % TICKS_PER_DAY; +} + +// Return the timer value that is 'msecs' time in the future. +u32 +calc_future_timer(u32 msecs) +{ + if (!msecs) + return GET_BDA(timer_counter); + u32 kticks = DIV_ROUND_UP((u64)msecs * PIT_TICK_RATE, PIT_TICK_INTERVAL); + u32 ticks = DIV_ROUND_UP(kticks, 1000); + return calc_future_timer_ticks(ticks); +} + +// Check if the given timer value has passed. +int +check_timer(u32 end) +{ + return (((GET_BDA(timer_counter) + TICKS_PER_DAY - end) % TICKS_PER_DAY) + < (TICKS_PER_DAY/2)); +} diff --git a/src/usb-ehci.c b/src/usb-ehci.c index 144dec4..e1e659b 100644 --- a/src/usb-ehci.c +++ b/src/usb-ehci.c @@ -15,6 +15,7 @@ #include "biosvar.h" // GET_LOWFLAT #include "usb-uhci.h" // uhci_setup #include "usb-ohci.h" // ohci_setup +#include "pit.h" // PIT_TICK_RATE
struct usb_ehci_s { struct usb_s usb; diff --git a/src/usb-ohci.c b/src/usb-ohci.c index ef6a52c..f3a3b3b 100644 --- a/src/usb-ohci.c +++ b/src/usb-ohci.c @@ -11,6 +11,7 @@ #include "pci_regs.h" // PCI_BASE_ADDRESS_0 #include "usb.h" // struct usb_s #include "biosvar.h" // GET_LOWFLAT +#include "pit.h" // PIT_TICK_RATE
#define FIT (1 << 31)
diff --git a/src/usb-uhci.c b/src/usb-uhci.c index 4098bf2..e77f5c1 100644 --- a/src/usb-uhci.c +++ b/src/usb-uhci.c @@ -12,6 +12,7 @@ #include "pci_regs.h" // PCI_BASE_ADDRESS_4 #include "usb.h" // struct usb_s #include "biosvar.h" // GET_LOWFLAT +#include "pit.h" // PIT_TICK_RATE
struct usb_uhci_s { struct usb_s usb; diff --git a/src/util.h b/src/util.h index 8bde0b2..19733cd 100644 --- a/src/util.h +++ b/src/util.h @@ -275,8 +275,13 @@ void serial_setup(void); 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 +void clock_setup(void); +void handle_1583(struct bregs *regs); +void handle_1586(struct bregs *regs); +void useRTC(void); +void releaseRTC(void); + +// timer.c void pmtimer_setup(u16 ioport, u32 khz); int check_tsc(u64 end); void timer_setup(void); @@ -291,10 +296,6 @@ u64 calc_future_tsc_usec(u32 usecs); u32 calc_future_timer_ticks(u32 count); u32 calc_future_timer(u32 msecs); int check_timer(u32 end); -void handle_1583(struct bregs *regs); -void handle_1586(struct bregs *regs); -void useRTC(void); -void releaseRTC(void);
// apm.c void apm_shutdown(void);