460 Notes on Timer

          460 NOTES on TIMER and TIME-SHARING

0. BACKGROUND:  NOTES on Interrupts

1. PC's timer:
   The timer has a base frequency of 1193182 Hz. It can be programmed to 
   generate a timer interrupt once every 1/60 of a second (commonly known 
   as one clock tick).  It interrupts at IRQ0 (vector 8).

2. The code shown below initializes the timer.

============================================================================
/* Clock parameters. */

#define LATCH_COUNT     0x00	/* cc00xxxx, c = channel, x = any */
#define SQUARE_WAVE     0x36	/* ccaammmb, a = access, m = mode, b = BCD */
				/* 11x11, 11 = LSB then MSB, x11 = sq wave */
#define TIMER_FREQ   1193182L	/* clock frequency for timer in PC and AT */
#define TIMER_COUNT ((unsigned) (TIMER_FREQ/60)) /*initial value of counter*/

#define TIMER0       0x40
#define TIMER_MODE   0x43
#define CLOCK_IRQ       0

#define INT_CNTL     0x20       /* 8259 interrupt control register */       
#define INT_MASK     0x21       /* bit i = 0 means enable interrupt i */ 

/*==========================================================================*
 *				enable_irq				    *
 *==========================================================================*/
int enable_irq(irq_nr) ushort irq_nr;
{
  /* Assume IRQ 0-7 ONLY. Clear the corresponding 8259 interrupt mask bit. */
  out_byte(INT_MASK, in_byte(INT_MASK) & ~(1 << irq_nr));
}

int tick,sec,min,hr;                    /* Wall clock */

/*===========================================================================*
 *				clock_init				     *
 *===========================================================================*/
int clock_init()
{
 /* Initialize channel 0 of the 8253A timer to 60 Hz. */

  tick = sec = min = hr = 0;            /* initialize wall clock to 0 */
  out_byte(TIMER_MODE, SQUARE_WAVE);	/* set timer to run continuously */
  out_byte(TIMER0, TIMER_COUNT)         /* load timer low byte */
  out_byte(TIMER0, TIMER_COUNT >> 8);	/* load timer high byte */
  enable_irq(CLOCK_IRQ);                /* enable timer interrupts */
}
=============================================================================


3. clock_init() initializes and starts the timer. Once started, it will 
   interrupt (at IRQ0 or vector 8) once every 1/60 seconds. To handle such 
   interrupts, a timer interrupt handler MUST be installed BEFORE starting
   the timer.

4. Stack for Interrupt Processing:

   Fiirst, our MTX depends on BIOS for I/O (e.g. getc/putc/diskio). So, when a
   timer interrupt occurs, (the running task of) our MTX may be executing in 
   ANY one of 3 possible places:
              Kmode : in this case, CS=DS=SS=0x1000
              Umode : in this case, CS=DS=SS=running task's Umode segment
              BIOS  : CS=0xF000, (DS,SS)=(DS,SS) of Kmode OR Umode
   Note that BIOS does NOT have its own stack segment. Therefore, while a task
   executes in BIOS, the stack is still the task's stack, which is either its 
   kstack or ustack, depending on which mode it is executing in.

   Therefore, when an interrupt occurs, the interrupted stack MUST BE either
   kstack (if the task is running in Kmode) or ustack (if it is running in 
   Umode).

   (1). Timer interrupt may occur anywhere, but interrupt processing must be 
        performed in Kernel. When a timer interrupt occurs, the CPU saves 
        [flag, CS, PC] of the interrupted point onto CURRENT stack.

   (2). Then it follows the vector #8 contents to continue execution from
                [_tinth, 0x1000]
        in the MTX kernel. At this moment, all other CPU registers are still
        those of the interrupted point.

        Similar to _int80h, we may let _tinth save all CPU registers into the
        CURRENT stack.

        Then a decision must be made in order to set up the correct execution
        environment:

        (a). If the task was in Umode, we MUST change DS,SS,ES to kernel's
             segment (0x1000), save the interrupted Umode SS,SP, and establish
             a kstack, then call a C handler function (as we did for INT 80) to
             continue processing the interrupt.

             In this case, the situation is exactly the same as that when the 
             task enters Kmode via INT 80. So, we may save the interrupted
             (SS, SP) into (proc.uss, proc.usp), and use the task's kstack
             from scratch (i.e. let sp -> High end of task's kstack[]).

        (b). If the task was in Kmode, then there is no need to change DS,SS,ES
             since they are already at 0x1000, and (more importantly) we must 
             continue to use the task's kstack AS IS since it already contains 
             the (saved) interrupted point.

        How do we know which mode the CPU was in before the interrupt?
        One way is to compare the interrupted segment value with 0x1000. 
        Here is another way:

        (d). Define a global variable  int in_kmode;  to keep track of the 
             number of times of entering Kmode. Initialize in_kmode to 1 
             (since MTX starts from Kmode). Whenever a task enters Kmode 
             (via INT 80 or due to an interrupt), it increments in_kmode by 1.
            Whenever a task exits Kmode (via goUmode() or return from interrupt
            handler), it decrements in_kmode by 1. Then we may test the value 
            of in_kmode to determine which mode the CPU was in when interrupt 
            occurred.

   While processing an interrupt, execution is in Kmode so that CS=DS=SS=ES=
   0x1000 and stack is the running task's kstack. 
   
   (4). call thandler() in C, which IMPLEMENTS the actual timer interrupt
        processing, i.e. it may update time count, display wall clock, start 
        timer-dependent actions, etc, including
       
           switch task!!!! but before switching to another task,
           it must re-enable the 8259 by writing 0x20 to port 0x20.
             
           If a task switch occurs, the running task's resume point is
           saved in its kstack, as it should.

   (5). When thandler() finishes, _tinth returns to the interrupted point by
        _treturn: 
              cli                          ! mask out interrupts

              if (was in Umode){ 
                 restore interrupted (SS,SP);  // from proc.iss, proc.isp
              }

              pop registers in reverse order
              iret 

   NOTE: For nested interrupts, the first interrupt may occur in Umode, but
   any subsequent interrupts MUST occur in Kmode (i.e. in the middle of 
   handling an interrupt). In the latter cases, we continue to use the task's
   kstack to save interrupted point and then return to the interrupted point.
   Therefore, our scheme can handle nested interrupts (of upto 15 levels).
   Of course, each task's kstack[] must be big enough to contain upto 15
   layers of saved interrupted points AND their stack frames (i.e. calling 
   chain and local variables, etc).
   

5. Implement Timer in MTX:

   Simliar to INT80 interrupts, the steps are:

   (1). Mask out ALL interrupts (by CLI or lock(), which issues CLI).

   (2). Initialize MTX kernel as before. Let running-> mainProc,  myfork()
        task1 with a Umode image. In myfork(), set the SAVED flag to 0x0200 
        (so that the task will run with interrupts enabled).

   (4). Set interrupt vectors (int80 for syscall and int8 for timer).
        Specifically: vector80 ==> (_int80h, 0x1000)
                      vector8  ==> (_tinth,  0x1000)
 
   (5). Initialize and ENABLE the timer by clock_init(); ==> Timer interrupts 
        will occur immediately but they are MASKed out for now.

   (6). tswitch(), which will load CPU's flag register with Ibit=1 ==>
        CPU begins to accept interrupts.
     ==================================================================
                         HELP files
       Download and READ the files ts.s and clock.c in samples/TIMER/
     ==================================================================

$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
            LAB ASSIGNMENT #7 : TIMER FOR MTX
            DUE: Friday, Nov 2, 2007 in class
		 
Implement timer for the MTX kernel such that:

1. At each second : display time HH:MM:SS at the lower right corner.

! The PC's screen has 25 rows (0-24), and 80 columns (0-79)
! The origin (row,col) = (0,0) is at the UPPER-LEFT corner.
! 
! When calling BIOS to diaplay a char, (refer to getc()), the char is 
! displayed at the current CURSOR position. The following parameters 
! manipulate the CURSOR position: 
!
!         ah = 2:  set curosr position to (row, col) in (dh,dl)
!         ah = 3; read cursor position in (dh,dl)
! 

    _setCursor:
        movb    dh, _row
        movb    dl, _col
        mov     ax, #0x0200
        xor     bx, bx
        int     0x10
        ret

    _getCursor:
        mov     ax, #0x0300
        xor     bx, bx
        int     0x10
        movb    _row, dh
        movb    _col, dl
        ret


2. When a task is scheduled to run, set its PROC.time to an alloted run time,
   e.g. 5 seconds. Decrement running's run time in Umode only!
   When a task's run time expires in Umode, switch task.

   NOTE:   do NOT switch task while it's in Kmode!!! 
   REASON: our MTX kernel is NOT a multi-processor kernel. But switch task
           in Umode is perferctly OK. 

3. Every 5 seconds: turn off FD if it is on;   (write 0x0C to 0x3F2)
                    turn on  FD if it is off;  (write 0x1C to 0x3F2)

   SAMPLE DISK: samples/timer.bin.gz 
                download,  gunzip, dd to a disk, boot and run. 

   HELP files:
   In samples/TIMER: ts.s is the assembly code file, use it as is
                     clock.c file : complete the thandler() C function

                     
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

5. TIMER SERVICE:
   
   With a timer in Kernel, tasks may request timer services as follows: 
   From Umode, implement a command:

       Umode : set_timer t

   which enters Kernel to set a timer request of t seconds. In genral, a task
   may continue after setting a timer request. It will be NOTIFIED when the
   time interval expires. Since our MTX does not have "notify" mechanism yet,
   we shall assume that the task BLOCKs itself (sleep) until the time interval
   expires.

   LISTEN TO THE LECTURE for more details. 

$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

                  Sample Timer Service Programming

           You may implement timer service in the mtx system.

           SAMPLE DISK: samples/timer2.bin.gz
                        download, gunzip, then dd to a disk
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$


6. Additional Experiments: 

   The (Master) 8259 interrupt controller has an
             INT_CNTL    register at 0x20
             INT_MASK    register at 0x21 

   (1). To enable IRQn (n = 0 to 7) interrupts, 
            bit n of INT_MASK register must be 0;
           (bit n == 1 means IRQn is disabled)
          
   (2). After each interrupt (of ANY kind), the interrupt handler 
        MUST explicitly re-enable the 8259 again by writing

             0x20  to the INT_CNTL register.

        Otherwise, no further interrupts can occur. 
   ====================================================================

                  Put all these into action:

   (0). Usually, BIOS initializes the 8259 in such a way that all IRQn are
        enabled, so you don't have to enable them again. To be safe, you 
        should enable any specific IRQn that you plan to use. But you MUST 
        re-enable the 8259 after each interrupt in your interrupt handler.

   (1). Try this: In your mtx's initialize() function, do
                  out_byte(0x21, 0xFF);
        This will disable ALL interrupts, including KBD. ==> nothing
        will work anymore, not even BIOS.
   
   (2). In your clock.c function, try
                  out_byte(0x21, 0x01);
        This will disable IRQ0 so that YOUR clock will no longer work.       
        
   (3). In your clock interrupt handler fucntion, do NOT
                   out_byte(INT_CNTL, 0x20);
        at the end. Then try to type as hard as you can on the KND 
        to see what happens.  WHY?
   ======================================================================