📄 hardclock.cxx
字号:
/* * Copyright (C) 1998, 1999, Jonathan S. Shapiro. * * This file is part of the EROS Operating System. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */#include <kerninc/kernel.hxx>#include <kerninc/IRQ.hxx>#include <kerninc/Machine.hxx>#include <kerninc/SysTimer.hxx>#include <kerninc/Thread.hxx>#include <eros/i486/io.h>#include "IDT.hxx"/* The timer chip on the PC has three channels. There is a fourth on * the PS/2, but for the moment I am ignoring that platform. * * These channels are hard-wired to the following purposes on PC * compatible motherboards: * * Channel 0 Generates IRQ0, aka system clock * Channel 1 Memory refresh * Channel 3 Routed to internal speaker * * Needless to say, dorking with channel 1 is, um, unhealthy, and I do * not plan to mess with channel 3 until I understand the speaker better. * Fortunately, the BIOS can be more or less trusted to establish * reasonable initial defaults for each of these channels. * * The timer chip is driven by a clock rate of 1.193182 MHz; the * motherboard hardware is responsible to see that this happens. * Ordinarily, channel 0 is programmed to further divide this by * 65536, giving the PC timer interrupt speed of 18.2 cycles per * second. * * For EROS, the desired timer granularity is milliseconds. Unfortunately * this is very much too fast for the x86 hardware. *//* The hardware clock is tricky - we configure a hardware clock, but * give SysTimer::Wakeup() as the handler function. We're going to * build a fast path interrupt filter such that the only interrupts * that actually make it to the interrupt dispatch mechanism are the * ones where a wake up is really required. *//* Speed of the actual base tick rate, in ticks/sec */#ifdef OPTION_FAST_CLOCK#define BASETICK 2000 /* 4000 possible on pentium only */#else#define BASETICK 200#endif#define HARD_TICK_RATE 1193182L/* SOFT_TICK_DIVIDER is the divider to use to get the soft tick rate down * as close as possible to the value specified by BASETICK. The soft tick * rate MUST be >= BASETICK. * * Given a value for SOFT_TICK_DIVIDER, the number of ticks per second * is given by HARD_TICK_RATE/SOFT_TICK_DIVIDER, and the number of ticks * per millisecond is (HARD_TICK_RATE/SOFT_TICK_DIVIDER) * (1/1000). * Here's where it all starts to become interesting. * * We need to convert milliseconds to ticks using integer arithmetic. * One year is longer than we are going to go without a reboot (random * alpha hits, if nothing else), so we set that as an upper bound on the * sleep call. One year is just less than 2^35 milliseconds, and 64 bit * integer arithmetic is the largest convenient kind on this machine. * What we're going to do is take the milliseconds number handed us, * multiply that by 2^20 * (ticks/ms), and then divide the whole mess * by 2^20 giving ticks. The end result will be to correct for tick * drift. * * TICK_MULTIPLIER, then, is the result of computing (by hand): * 2^20 * (HARD_TICK_RATE/(SOFT_TICK_DIVIDER*1000)) * * Note: if you use UNIX bc, compute these with scale=8 * * The other conversion we need to be able to do is to go from ticks * since last reboot to milliseconds. This is basically the same * calculation done in the opposite direction. The goal is not to be * off by more than 1ms per 1024*1024 seconds. * * TICK_TO_MS_MULTIPLIER, then, is the result of computing (by hand): * 2^20 * ((1000 * 2^20) / (SOFT_TICK_RATE * 2^20)) * where * SOFT_TICK_RATE = HARD_TICK_RATE / SOFT_TICK_DIVIDER */#if (BASETICK == 4000)# define SOFT_TICK_DIVIDER 298# define TICK_MULTIPLIER 4198463ll# define TICK_TO_MS_MULTIPLIER 261884ll#elif (BASETICK == 2000)# define SOFT_TICK_DIVIDER 596# define TICK_MULTIPLIER 2099231ll# define TICK_TO_MS_MULTIPLIER 523768ll#elif (BASETICK == 1000)# define SOFT_TICK_DIVIDER 1193# define TICK_MULTIPLIER 1048736ll# define TICK_TO_MS_MULTIPLIER 1048416ll#elif (BASETICK == 500)# define SOFT_TICK_DIVIDER 2386# define TICK_MULTIPLIER 524368ll# define TICK_TO_MS_MULTIPLIER 2096832ll#elif (BASETICK == 200)# define SOFT_TICK_DIVIDER 5965# define TICK_MULTIPLIER 209747ll# define TICK_TO_MS_MULTIPLIER 5242080ll#elif (BASETICK == 100)# define SOFT_TICK_DIVIDER 11931# define TICK_MULTIPLIER 104865ll# define TICK_TO_MS_MULTIPLIER 10485039ll#elif (BASETICK == 60)# define SOFT_TICK_DIVIDER 19886# define TICK_MULTIPLIER 62912ll# define TICK_TO_MS_MULTIPLIER 17475944ll#elif (BASETICK == 50)# define SOFT_TICK_DIVIDER 23863# define TICK_MULTIPLIER 52430ll# define TICK_TO_MS_MULTIPLIER 20991457ll#else# error "BASETICK not properly defined"#endif#define TIMER_PORT_0 0x40#define TIMER_MODE 0x43#define SQUARE_WAVE0 0x34static uint32_t usec_calibration_count = 0;uint64_tMachine::MillisecondsToTicks(uint64_t ms){ if ((ms >> 35) > 0) ms = 1ll << 35; /* don't sleep more than a year */ uint64_t ticks = ms; ticks *= TICK_MULTIPLIER; ticks >>= 20; /* divide by (1024*1024) */ /* It's okay to sleep for zero ticks, since we will sleep for at * least one tick interval before being woken up. */ return (uint32_t) ticks;}/* Pragmatically, this only works if the machine has been up for less * than 136 years since the last reboot. */uint64_tMachine::TicksToMilliseconds(uint64_t ticks){ uint64_t ms = ticks; ms *= TICK_TO_MS_MULTIPLIER; ms >>= 20; /* divide by (1024*1024) */ return (uint32_t) ms;}#ifdef GNU_INLINE_ASMvoidMachine::InitHardClock(){ /* Set up the hardware clock: */ old_outb(TIMER_MODE, SQUARE_WAVE0); old_outb(TIMER_PORT_0, SOFT_TICK_DIVIDER & 0xff); old_outb(TIMER_PORT_0, (SOFT_TICK_DIVIDER >> 8) & 0xff); IRQ::SetHandler(IRQ386::HardClock, SysTimer::Wakeup); IRQ::Enable(IRQ386::HardClock); /* Above should have taken an interrupt instantaneously, since the * clock has by this point been disabled a while. Calibrate the * microsecond spin multiplier. */ printf("Calibrating SpinWait... "); uint64_t ticks = MillisecondsToTicks(11); uint64_t calibrateDone = SysTimer::Now() + ticks; usec_calibration_count = 0; while (SysTimer::Now() < calibrateDone) { usec_calibration_count ++; for (uint32_t i = 0; i < 200; i++) { GNU_INLINE_ASM ("nop"); } } printf("done\n");}#endif /* GNU_INLINE_ASM */#ifdef GNU_INLINE_ASMvoidMachine::SpinWaitUs(uint32_t w){ w *= usec_calibration_count; w /= 10; /* calibrated for 11 milliseconds, but */ /* divide by something less than that! */ if (w < 2) w = 2; /* MIN sleep of 2 usec on 200Mhz */ for (uint32_t count = 0; count < w; count++) { for (uint32_t i = 0; i < 200; i++) { GNU_INLINE_ASM ("nop"); } }}#endif /* GNU_INLINE_ASM */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -