⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 delay.c

📁 一个极小型的操作系统
💻 C
字号:
/* * delay.c:  Delay calibration, the Linux way * * By Ramon van Handel (19/12/98) * Based on the method used in linux 2.0.30 init/main.c * * Resembles a lot the way I thought up in my original email, just a tiny * bit more accurate.  The principle is the same, implementation is * different.  I didn't make this as detailed as linux does, but I * documented it pretty well.  If you really want to trim this to the * microsecond, have a look at the linux sources.  The main calibration * code (init/main.c) is similar, but have a look in include/linux/delay.h, * include/asm-i386/delay.h, and arch/i386/lib/delay.S. *                                                        -- Ramon *//*GazOS Operating SystemCopyright (C) 1999  Gareth Owen <gaz@athene.co.uk>This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public Licenseas published by the Free Software Foundation; either version 2of the License, 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 ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program; if not, write to the Free SoftwareFoundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.*/#include "pit.h"                         /* GazOS stuff                 */#include "8259.h"#include "gdt.h"#include "idt.h"extern void pit_delay_ISR(void);         /* Timer ISR for calibration   *//**************************************************************************//* * Allright, let's review how we're going to do this. * * Goal: * The goal is to create a reasonably accurate version of the delay() * function, which creates a delay in multiples of one millisecond. * * Restrictions: * We don't want to use the PIT for the delay.  In your average * bare-hardware system (ie OS) you'll be using the PIT for quite a few * other things already (in my OS code, channel 0 and 1 are used by the * scheduler, and channel 2 would probably be needed to control the * speaker.) * * Method: * A simple loop would delay the computer wonderfully.  Ie, take something * like: * *         for(i=0;i<BIGNUMBER;i++);  // Delay loop * * That kills time wonderfully.  All we need to do is to find out what * BIGNUMBER we need to use to delay one millisecond.  Finding the correct * BIGNUMBER for your machine is called *delay loop calibration*. * * We can calibrate the delay loop using the PIT.  In the initialisation * phase of the OS, when the PIT is not yet hooked to the scheduler, it * can be freely used to do this.  When the delay loop has been calibrated * against the PIT, the PIT is free to be used for other purposes. * * There are many ways to calibrate a delay loop with the PIT.  This is * one way (used in Linux.)  More documentation in the code. *//**************************************************************************//* * The delay function: * Have a look at this function before you look at the calibration code. * Basically, delay_count is the number you have to count to in order to * kill 1ms.  delay() takes the amount of milliseconds to delay as * parameter. * * The goal of the rest of this code is to determine what delay_count *is* * for your machine. * * Keep delay_count initialised to 1.  We'll need that later. */static unsigned long delay_count = 1;/* * When I was testing the delay code, I notice that the calibration we use * is very delicate.  Originally, I had inlined the delay for() loops into * the delay() and calibrateDelayLoop() functions, but this doesn't work. * Due to different register allocation or something similar the compiler * might have generated different code in the two situations (I didn't * check this.)  Or alignment differences might have caused the problems. * Anyway, to solve the problem, we always use the __delay() function for * the delay for() loop, because it always is the same code with the same * alignment. __delay() is called from delay() as well as from * calibrateDelayLoop().  By using __delay() we can fine-tune our * calibration without it losing its finesse afterwards. */void __delay(loops){    unsigned long c;    for(c=0;c<loops;c++);}void delay(unsigned long milliseconds){    __delay(milliseconds*delay_count);     /* Delay milliseconds ms */}/**************************************************************************//* * The timer ISR: * In order to calibrate the delay loop, we program the PIT channel 0 with * a fixed timer rate of MILLISEC milliseconds.  The only thing the ISR * does is increment a timer, ticks, on every interrupt (that is, every * 10ms in this case.)  The main program will use this counter in order to * calibrate the delay loop. */#define MILLISEC 10           /* Take a 10ms fixed timer rate   */#define FREQ (1000/MILLISEC)  /* Timer freqency for MILLISEC    */static volatile unsigned long ticks = 0;void delayCalibInt(void)      /* The timer ISR                  */{    ticks++;                  /* Increment the ticks counter    */}/**************************************************************************//* * Calibrate the delay loop: * This is the most important part of the whole business.  I'll explain * the general strategy here, which is supplemented by comments in the * code. * * Stage 1: * First, we are going to make a rough approximation of delay_count. We * are going to try to determine between which two powers of 2 delay_count * needs to be.  In order to do that, we first wait until the next timer * tick has started (we know it has when the ticks variable has just been * incremented.)  Then we start a delay with delay_count of 1.  When the * delay has finished, we look whether a timer tick has passed during the * delay (nah !).  If not, we shift delay_count to the right once and try * again.  We continue until after the delay, one timer tick has passed. * We then know that our value for delay_count is too big, so the 'real' * delay_count is between (delay_count) and (delay_count>>1). * * Stage 2: * Afterwards, we start refining our value.  We shift our delay_count to * the right once to obtain out bottom-value for delay_count (ie we then * know that the 'real' delay_count is bigger than the actual * delay_count.)  delay_count is a power of 2, so only one bit is set. * What we do is, we start setting bits in delay_count and see whether * delay_count is bigger than a tick afterwards. An example helps * clarifying matters: * * Imagine we found in stage 1 that delay_count is between 00100000b and * 01000000b.  (In reality it would be much bigger, but for the example * this will do.)  Before we start our fine-calibration, we set * delay_count to 00100000b. * Now we turn on the bit after the set bit, ie:  00110000b * We do the delay like we did previously, and see whether ticks has * changed during it.  If it has, we know that the value is too big, and * turn the bit back off.  If it hasn't, we leave it on.  In either case, * we proceed with the next bit: * *     00100000b   <--- start value *     00110000b   <--- tick hasn't passed *     00111000b   <--- tick has passed, turn it off: 00110000b *     00110100b   <--- tick has passed, turn it off: 00110000b *     00110010b   <--- tick hasn't passed *     00110011b   <--- tick hasn't passed * * And there is our delay_count value. * * In order to save time (calibrating can be a time-consuming matter, and * tuning it extremely finely is often useless because at a certain level * you get additional delay by calling the delay() function, and returning * from it, etc.) we only calculate delay_count up to a certain precision * (ie, we only calculate PRECISION extra bits after stage 1.)  A * PRECISION value of 8 gives good results (according to the linux * sources, it gives an imprecision of approximately 1%.) */#define PRECISION 8                 /* Calibration precision          */unsigned long calibrateDelayLoop(void){    unsigned int prevtick;          /* Temporary variable             */    unsigned int i;                 /* Counter variable               */    unsigned int calib_bit;         /* Bit to calibrate (see below)   */    /* Initialise timer interrupt with MILLISECOND ms interval        */    init_pit(FREQ, 0);    set_vector(pit_delay_ISR, M_VEC, (D_INT + D_PRESENT + D_DPL3));    enable_irq(0);    /* Stage 1:  Coarse calibration                                   */    do {        delay_count <<= 1;          /* Next delay count to try        */        prevtick=ticks;             /* Wait for the start of the next */        while(prevtick==ticks);     /* timer tick                     */        prevtick=ticks;             /* Start measurement now          */        __delay(delay_count);       /* Do the delay                   */    } while(prevtick == ticks);     /* Until delay is just too big    */    delay_count >>= 1;              /* Get bottom value for delay     */    /* Stage 2:  Fine calibration                                     */    calib_bit = delay_count;        /* Which bit are we going to test */    for(i=0;i<PRECISION;i++) {        calib_bit >>= 1;            /* Next bit to calibrate          */        if(!calib_bit) break;       /* If we have done all bits, stop */        delay_count |= calib_bit;   /* Set the bit in delay_count     */        prevtick=ticks;             /* Wait for the start of the next */        while(prevtick==ticks);     /* timer tick                     */        prevtick=ticks;             /* Start measurement now          */        __delay(delay_count);       /* Do the delay                   */        if(prevtick != ticks)       /* If a tick has passed, turn the */            delay_count &= ~calib_bit;     /* calibrated bit back off */    }    /* We're finished:  Do the finishing touches                      */    disable_irq(0);                /* Our wonderful PIT can stop now  */    delay_count /= MILLISEC;       /* Calculate delay_count for 1ms   */    return delay_count;}/**************************************************************************//* * The hardware part of the timer ISR: * This code is invoked when the PIT finishes its period. */asm (   ".globl pit_delay_ISR   \n"   "pit_delay_ISR:         \n"   "   pusha               \n"   /* Save all registers             */   "   pushw %ds           \n"   /* Save user code data segment    */   "   pushw %ss           \n"   /* Get kernel data segment        */   "   popw %ds            \n"   /* %ss always valid (ss0 in TSS)  */   "                       \n"   "   call delayCalibInt  \n"   /* Call the actual ISR code       */   "                       \n"   "   movb $0x20,%al      \n"   /* Send EOI to the 8259           */   "   outb %al,$0x20      \n"   "   popw %ds            \n"   /* Restore user code data segment */   "   popa                \n"   /* Restore registers              */   "   iret                \n"   /* Return from interrupt          */);/**************************************************************************//* The end */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -