code/machine/interrupt.cc
// interrupt.cc
// Routines to simulate hardware interrupts.
//
// The hardware provides a routine (SetLevel) to enable or disable
// interrupts.
//
// In order to emulate the hardware, we need to keep track of all
// interrupts the hardware devices would cause, and when they
// are supposed to occur.
//
// This module also keeps track of simulated time. Time advances
// only when the following occur:
// interrupts are re-enabled
// a user instruction is executed
// there is nothing in the ready queue
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "interrupt.h"
#include "system.h"
// String definitions for debugging messages
static char *intLevelNames[] = { "off", "on"};
static char *intTypeNames[] = { "timer", "disk", "console write",
"console read", "network send", "network recv"};
//----------------------------------------------------------------------
// PendingInterrupt::PendingInterrupt
// Initialize a hardware device interrupt that is to be scheduled
// to occur in the near future.
//
// "func" is the procedure to call when the interrupt occurs
// "param" is the argument to pass to the procedure
// "time" is when (in simulated time) the interrupt is to occur
// "kind" is the hardware device that generated the interrupt
//----------------------------------------------------------------------
PendingInterrupt::PendingInterrupt(VoidFunctionPtr func, int param, int time,
IntType kind)
{
handler = func;
arg = param;
when = time;
type = kind;
}
//----------------------------------------------------------------------
// Interrupt::Interrupt
// Initialize the simulation of hardware device interrupts.
//
// Interrupts start disabled, with no interrupts pending, etc.
//----------------------------------------------------------------------
Interrupt::Interrupt()
{
level = IntOff;
pending = new List();
inHandler = FALSE;
yieldOnReturn = FALSE;
status = SystemMode;
}
//----------------------------------------------------------------------
// Interrupt::~Interrupt
// De-allocate the data structures needed by the interrupt simulation.
//----------------------------------------------------------------------
Interrupt::~Interrupt()
{
while (!pending->IsEmpty())
delete pending->Remove();
delete pending;
}
//----------------------------------------------------------------------
// Interrupt::ChangeLevel
// Change interrupts to be enabled or disabled, without advancing
// the simulated time (normally, enabling interrupts advances the time).
//----------------------------------------------------------------------
// Interrupt::ChangeLevel
// Change interrupts to be enabled or disabled, without advancing
// the simulated time (normally, enabling interrupts advances the time).
//
// Used internally.
//
// "old" -- the old interrupt status
// "now" -- the new interrupt status
//----------------------------------------------------------------------
void
Interrupt::ChangeLevel(IntStatus old, IntStatus now)
{
level = now;
DEBUG('i',"\tinterrupts: %s -> %s\n",intLevelNames[old],intLevelNames[now]);
}
//----------------------------------------------------------------------
// Interrupt::SetLevel
// Change interrupts to be enabled or disabled, and if interrupts
// are being enabled, advance simulated time by calling OneTick().
//
// Returns:
// The old interrupt status.
// Parameters:
// "now" -- the new interrupt status
//----------------------------------------------------------------------
IntStatus
Interrupt::SetLevel(IntStatus now)
{
IntStatus old = level;
ASSERT((now == IntOff) || (inHandler == FALSE));// interrupt handlers are
// prohibited from enabling
// interrupts
ChangeLevel(old, now); // change to new state
if ((now == IntOn) && (old == IntOff))
OneTick(); // advance simulated time
return old;
}
//----------------------------------------------------------------------
// Interrupt::Enable
// Turn interrupts on. Who cares what they used to be?
// Used in ThreadRoot, to turn interrupts on when first starting up
// a thread.
//----------------------------------------------------------------------
void
Interrupt::Enable()
{
(void) SetLevel(IntOn);
}
//----------------------------------------------------------------------
// Interrupt::OneTick
// Advance simulated time and check if there are any pending
// interrupts to be called.
//
// Two things can cause OneTick to be called:
// interrupts are re-enabled
// a user instruction is executed
//----------------------------------------------------------------------
void
Interrupt::OneTick()
{
MachineStatus old = status;
// advance simulated time
if (status == SystemMode) {
stats->totalTicks += SystemTick;
stats->systemTicks += SystemTick;
} else { // USER_PROGRAM
stats->totalTicks += UserTick;
stats->userTicks += UserTick;
}
DEBUG('i', "\n== Tick %d ==\n", stats->totalTicks);
// check any pending interrupts are now ready to fire
ChangeLevel(IntOn, IntOff); // first, turn off interrupts
// (interrupt handlers run with
// interrupts disabled)
while (CheckIfDue(FALSE)) // check for pending interrupts
;
ChangeLevel(IntOff, IntOn); // re-enable interrupts
if (yieldOnReturn) { // if the timer device handler asked
// for a context switch, ok to do it now
yieldOnReturn = FALSE;
status = SystemMode; // yield is a kernel routine
currentThread->Yield();
status = old;
}
}
//----------------------------------------------------------------------
// Interrupt::YieldOnReturn
// Called from within an interrupt handler, to cause a context switch
// (for example, on a time slice) in the interrupted thread,
// when the handler returns.
//
// We can't do the context switch here, because that would switch
// out the interrupt handler, and we want to switch out the
// interrupted thread.
//----------------------------------------------------------------------
void
Interrupt::YieldOnReturn()
{
ASSERT(inHandler == TRUE);
yieldOnReturn = TRUE;
}
//----------------------------------------------------------------------
// Interrupt::Idle
// Routine called when there is nothing in the ready queue.
//
// Since something has to be running in order to put a thread
// on the ready queue, the only thing to do is to advance
// simulated time until the next scheduled hardware interrupt.
//
// If there are no pending interrupts, stop. There's nothing
// more for us to do.
//----------------------------------------------------------------------
void
Interrupt::Idle()
{
DEBUG('i', "Machine idling; checking for interrupts.\n");
status = IdleMode;
if (CheckIfDue(TRUE)) { // check for any pending interrupts
while (CheckIfDue(FALSE)) // check for any other pending
; // interrupts
yieldOnReturn = FALSE; // since there's nothing in the
// ready queue, the yield is automatic
status = SystemMode;
return; // return in case there's now
// a runnable thread
}
// if there are no pending interrupts, and nothing is on the ready
// queue, it is time to stop. If the console or the network is
// operating, there are *always* pending interrupts, so this code
// is not reached. Instead, the halt must be invoked by the user program.
DEBUG('i', "Machine idle. No interrupts to do.\n");
printf("No threads ready or runnable, and no pending interrupts.\n");
printf("Assuming the program completed.\n");
Halt();
}
//----------------------------------------------------------------------
// Interrupt::Halt
// Shut down Nachos cleanly, printing out performance statistics.
//----------------------------------------------------------------------
void
Interrupt::Halt()
{
printf("Machine halting!\n\n");
stats->Print();
Cleanup(); // Never returns.
}
//----------------------------------------------------------------------
// Interrupt::Schedule
// Arrange for the CPU to be interrupted when simulated time
// reaches "now + when".
//
// Implementation: just put it on a sorted list.
//
// NOTE: the Nachos kernel should not call this routine directly.
// Instead, it is only called by the hardware device simulators.
//
// "handler" is the procedure to call when the interrupt occurs
// "arg" is the argument to pass to the procedure
// "fromNow" is how far in the future (in simulated time) the
// interrupt is to occur
// "type" is the hardware device that generated the interrupt
//----------------------------------------------------------------------
void
Interrupt::Schedule(VoidFunctionPtr handler, int arg, int fromNow, IntType type)
{
int when = stats->totalTicks + fromNow;
PendingInterrupt *toOccur = new PendingInterrupt(handler, arg, when, type);
DEBUG('i', "Scheduling interrupt handler the %s at time = %d\n",
intTypeNames[type], when);
ASSERT(fromNow > 0);
pending->SortedInsert(toOccur, when);
}
//----------------------------------------------------------------------
// Interrupt::CheckIfDue
// Check if an interrupt is scheduled to occur, and if so, fire it off.
//
// Returns:
// TRUE, if we fired off any interrupt handlers
// Params:
// "advanceClock" -- if TRUE, there is nothing in the ready queue,
// so we should simply advance the clock to when the next
// pending interrupt would occur (if any). If the pending
// interrupt is just the time-slice daemon, however, then
// we're done!
//----------------------------------------------------------------------
bool
Interrupt::CheckIfDue(bool advanceClock)
{
MachineStatus old = status;
int when;
ASSERT(level == IntOff); // interrupts need to be disabled,
// to invoke an interrupt handler
if (DebugIsEnabled('i'))
DumpState();
PendingInterrupt *toOccur =
(PendingInterrupt *)pending->SortedRemove(&when);
if (toOccur == NULL) // no pending interrupts
return FALSE;
if (advanceClock && when > stats->totalTicks) { // advance the clock
stats->idleTicks += (when - stats->totalTicks);
stats->totalTicks = when;
} else if (when > stats->totalTicks) { // not time yet, put it back
pending->SortedInsert(toOccur, when);
return FALSE;
}
// Check if there is nothing more to do, and if so, quit
if ((status == IdleMode) && (toOccur->type == TimerInt)
&& pending->IsEmpty()) {
pending->SortedInsert(toOccur, when);
return FALSE;
}
DEBUG('i', "Invoking interrupt handler for the %s at time %d\n",
intTypeNames[toOccur->type], toOccur->when);
#ifdef USER_PROGRAM
if (machine != NULL)
machine->DelayedLoad(0, 0);
#endif
inHandler = TRUE;
status = SystemMode; // whatever we were doing,
// we are now going to be
// running in the kernel
(*(toOccur->handler))(toOccur->arg); // call the interrupt handler
status = old; // restore the machine status
inHandler = FALSE;
delete toOccur;
return TRUE;
}
//----------------------------------------------------------------------
// PrintPending
// Print information about an interrupt that is scheduled to occur.
// When, where, why, etc.
//----------------------------------------------------------------------
static void
PrintPending(int arg)
{
PendingInterrupt *pend = (PendingInterrupt *)arg;
printf("Interrupt handler %s, scheduled at %d\n",
intTypeNames[pend->type], pend->when);
}
//----------------------------------------------------------------------
// DumpState
// Print the complete interrupt state - the status, and all interrupts
// that are scheduled to occur in the future.
//----------------------------------------------------------------------
void
Interrupt::DumpState()
{
printf("Time: %d, interrupts %s\n", stats->totalTicks,
intLevelNames[level]);
printf("Pending interrupts:\n");
fflush(stdout);
pending->Mapcar(PrintPending);
printf("End of pending interrupts\n");
fflush(stdout);
}