Listing 1 (clockint.c)

/*0*********************************************************
*   Richard Carver                            July 1990
*
*   This program demonstrates interprocess communication
*   between several tasks using the iRMX II Operating
*   System.
*
*   Two tasks are used for resource control. The
*   clock_task() controls use of the hardware clock and the
*   crt_task() controls use of the display screen.
*
*   The count_task() competes with the clock_task() for
*   use of the crt_task() services. The timer_task() uses
*   the services of the clock_task() for a software timer.
*
*   The initial task, main(), starts every thing up and,
*   when the timer_task() finishes, shuts everything down.
***********************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rmxc.h>       /* iRMX OS calls & structures  */
#include <rmxerr.h>     /* iRMX status codes (defines) */
#include <delay.h>      /* Hardcoded delay loop macros */

#define ERROR   (0xFFFF)   /* Error Condition */

#define FALSE   (0)
#define TRUE    (!FALSE)

#define MAX_CLOCK_RETRIES  (4)

/* The NULL token (similar to NULL pointer) */

#define NUL_TKN   ((selector)0)

/* Options for rqgettasktokens() */

#define THIS_TASK (0x00)
#define THIS_JOB  (0x01)
#define PARAM_OBJ (0x02)
#define ROOT_JOB  (0x03)

/* Options for rqcreatemailbox() */

#define FIFO_OBJ_MBX       (0x0000)
#define PRIORITY_OBJ_HBX   (0x0001)
#define FIFO_DATA_MBX      (0x0020)
#define PRIORITY_DATA_MBX  (0x0021)

/* Options for rqcreatesemaphore() */

#define FIFO_SEM      (0x0000)
#define PRIORITY_SEM  (0x0001)

#define SEM_INIT_0    (0)
#define SEM_MAX_1     (1)

/* Common wait times for iRMX System Calls */

#define NO_WAIT       (0x0000)
#define WAIT_FOREVER  (0xFFFF)

/* Options for rqcreatetask() */

#define NO_FLOAT   (0x0000)
#define DEFAULT_STACK_SIZE     ((unsigned int)2048)

/* Task Priorities  */

#define CLOCK_TASK_PRIORITY    ((unsigned char)48)
#define TIMER_TASK_PRIORITY        ((unsigned char)150)
#define CRT_TASK_PRIORITY      ((unsigned char)200)
#define COUNT_TASK_PRIORITY    ((unsigned char)200)

/* Defines For Hardware Clock */
/* Clock Interrupt Level */

#define CLOCK_INT_LEVEL (0x26)

/* Clock Ports */

#define ADR_PORT  ((unsigned short)0xA060)
#define DAT_PORT  ((unsigned short)0xA062)
#define CNT_PORT  ((unsigned short)0xA064)
#define PIO_PORT  ((unsigned short)0xA066)

/* Clock Port I/O Operations */

#define PIO_READ  ((unsigned char)0x82)
#define PIO_WRITE ((unsigned char)0x80)

/* Clock Registers */

#define SEC01_REG (0x00)
#define SEC10_REG (0x01)
#define MIN01_REG (0x02)
#define MIN10_REG (0x03)
#define HRS01_REG (0x04)
#define HRS10_REG (0x05)
#define DAT01_REG (0x07)
#define DAT10_REG (0x08)
#define MON01_REG (0x09)
#define MON10_REG (0x0A)
#define YRS01_REG (0x0B)
#define YRS10_REG (0x0C)

/* Clock Control Lines */

#define CNT_RST     ((unsigned char)0x00)
#define INT_ENB     ((unsigned char)0x20)
#define HOLD_HIGH   ((unsigned char)0x10)
#define HOLD_READ   ((unsigned char)0x30)
#define HOLD_WRITE  ((unsigned char)0x50)

/* Defines for months */

#define JANUARY     (1)
#define FEBRUARY    (2)
#define MARCH       (3)
#define DECEMBER    (12)

/* Time/Date and Timers Data Structure  */

#define MAX_TIMERS    (10)

struct SYS_TIME_STR
  {
  unsigned char change_flg;
  unsigned char second;
  unsigned char minute;
  unsigned char hour;
  unsigned char date;
  unsigned char month;
  unsigned char year;
  
  struct TIMER_STR
    {
    unsigned char in_use;
    unsigned int count;
    unsigned int timeout;
    selector    sem_tkn;
    }timer[MAX_TIMERS];
  };

/* Request Message format for the Display Task */

struct CRT_REQ_STR
  {
   unsigned char *msg_ptr;
   unsigned int  row;
   unsigned int  col;
   };
 
 /* Object Catalog Names */
 
 const unsigned char
   clock_sem_name[] = "\011CLOCK_SEM",
   init_sem_name[] = "\010INIT_SEM";
 
 const unsigned char
   time_seg_name[] = "\010TIME_SEG";
 
 const unsigned char
   crt_req_mbx_name[] = "\007CRT_MBX";
 
 const unsigned char
   crt_shtdwn_sem_name[] = "\012CRT_SHTDWN",
   clock_shtdwn_sem_name[] = "\014CLOCK_SHTDWN",
   count_shtdwn_sem_name[] = "\014COUNT_SHTDWN";
 
 /* Days Per Month */
 
 const unsigned char
   days_in_month[12] =
   {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/* Global System Time/Date Structure */

selector  sys_time_seg;
struct SYS_TIME_STR *sys_time;

/* Task declarations  */

void clock_task(void);
void crt_task(void);
void timer_task(void);
void count_task(void);

/* Task objects (global for debugging purposes) */

selector  clock_tsk;
selector  crt_tsk;
selector  timer_tsk;
selector  count_tsk;

/*1*********************************************************
* Task:     main()  (the initial task)
*
* Summary:  Starts it all up and shuts it all down.
*
* Caveats:  None
***********************************************************/

void main(void)
  {
  selector  current_job;       /* current job            */
  selector  init_sem;          /* initilaization sem     */
  selector  clock_sem;         /* time/date shared memory*/
  selector  clock_shtdwn_sem;  /* clock_task() shutdown  */
  selector  count_shtdwn_sem;  /* count_task() shutdown  */
  selector  crt_shtdwn_sem;    /* crt_task() shutdown    */
  selector  sys_time_seg;      /* time/date data segment */
  
  unsigned  int   status;      /* iRMX condition code    */
  
  struct EXCEPTIONSTRUCT exceptioninfo;

/*  Disable current task's exception handler  */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);
/*  Get Token For Current Job */

  current_job = rqgettasktokens(THIS_JOB, &status);

/*  Create and catalog the semaphore used for */
/*  accessing the shared clock data.          */

  clock_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, PRIORITY_SEM, &status);
  rqcatalogobject(current_job,
    clock_sem, &clock_sem_name, &status);

/*  Create and catalog the semaphore used for */
/*  synchronizing tasks during initialization */
/*  and shutdown.                             */

  init_sem = rqcreatesemaphore(SEM_INIT_0,
    99, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    init_sem, &init_sem_name, &status);

/*  Create and catalog the segment of memory  */
/*  used to hold the shared time/date data.   */

  sys_time_seg = rqcreatesegment(
    sizeof(struct SYS_TIME_STR), &status);
  rqcatalogobject(current_job,
    sys_time_seg, &time_seg_name, &status);

/*  Create and catalog the semaphores used to */
/*  signal task shutdown.                     */

  clock_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    clock_shtdwn_sem, &clock_shtdwn_sem_name, &status);

  crt_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    crt_shtdwn_sem, &crt_shtdwn_sem_name, &status);

  count_shtdwn_sem = rqcreatesemaphore(SEM_INIT_0,
    SEM_MAX_1, FIFO_SEM, &status);
  rqcatalogobject(current_job,
    count_shtdwn_sem, &count_shtdwn_sem_name, &status);

/*  Create the crt_task(). Wait for the task to  */
/*  indicate it has completed initialization.    */

  crt_tsk = rqcreatetask(CRT_TASK_PRIORITY, &crt_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Create the clock_task(). Wait for the task    */
/*  to indicate it has completed initialization.  */

  clock_tsk = rqcreatetask(CLOCK_TASK_PRIORITY, &clock_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Create the count_task(). Wait for the task    */
/*  to indicate it has completed initialization.  */

  count_tsk = rqcreatetask(COUNT_TASK_PRIORITY, &count_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Create the timer_task(). Wait for the task    */
/*  to indicate it has completed initialization.  */
/*  This also signals the shutdown process.       */
  timer_tsk = rqcreatetask(TIMER_TASK_PRIORITY, &timer_task,
    NUL_TKN, NULL, DEFAULT_STACK_SIZE, NO_FLOAT, &status);
  
  rqreceiveunits(init_sem, 1, WAIT_FOREVER, &status);

/*  Begin shutdown by first disabling any further  */
/*  interrupts from the hardware clock. This task  */
/*  suspends itself for 2/100th of a second to     */
/*  ensure interrupt is disabled.                  */

  rqdisable(CLOCK_INT_LEVEL, &status);
  rqsleep(2, &status);

/*  Send shutdown signals to all tasks and wait    */
/*  for all tasks to complete shutdown.            */

  rqsendunits(count_shtdwn_sem, 1, &status);
  rqsendunits(crt_shtdwn_sem, 1, &status);
  rqsendunits(clock_shtdwn_sem, 1, &status);
  
  rqreceiveunits(init_sem, 3, WAIT_FOREVER, &status);

/*  Cleanup before terminating. This includes      */
/*  deleting tasks and uncataloging/deleteing      */
/*  all objects created.                           */

  rqdeletetask(crt_tsk, &status);
  rqdeletetask(clock_tsk, &status);
  rqdeletetask(count_tsk, &status);
  rqdeletetask(timer_tsk, &status);
  
  rquncatalogobject(current_job,
    &clock_sem_name, &status);
  rquncatalogobject(current_job,
    &init_sem_name, &status);
  rquncatalogobject(current_job,
    &time_seg_name, &status);
  rquncatalogobject(current_job,
    &crt_req_mbx_name, &status);
  
  rquncatalogobject(current_job,
    &clock_shtdwn_sem_name, &status);
  rquncatalogobject(current_job,
    &count_shtdwn_sem_name, &status);
  rquncatalogobject(current_job,
    &crt_shtdwn_sem_name, &status);
  
  rqdeletesemaphore(clock_sem, &status);
  rqdeletesemaphore(init_sem, &status);
  
  rqdeletesemaphore(clock_shtdwn_sem, &status);
  rqdeletesemaphore(count_shtdwn_sem, &status);
  rqdeletesemaphore(crt_shtdwn_sem, &status);
  
  rqdeletesegment(sys_time_seg, &status);
  
  cursor_on();
  printf("\nAll Done!\n");
  
  exit(0);
  }
  
/*1*********************************************************
* Function: start_timer
*
* Summary:  Create and start a timer.
*
* Invocation: timer_sem = start_timer(timeout);
*
* Inputs:   timeout (unsigned int) - timer interval
*              in seconds
*
* Outputs:  timer_sem (selector) - the semaphore that will
*              receive a unit at each timer interval
*
* Caveats:  Accesses the time/date data segment that is
*           shared with the clock_task().
***********************************************************/

selector start_timer(unsigned int timeout)
  {
  unsigned int    status;
  unsigned int    indx;
  unsigned int    done;
  unsigned int    no_more_timers;
  
  selector    timer_sem;
  selector    current_job;
  selector    clock_sem;
  selector    sys_time_seg;
  
  struct SYS_TIME_STR   *sys_time;
  
  indx = 0;
  done = FALSE;
  no_more_timers = TRUE;
  timer_sem = NUL_TKN;

/*  Get objects for access semaphore and */
/*  time/date data segment               */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  clock_sem = rqlookupobject(current_job,
    &clock_sem_name, NO_WAIT, &status);
  sys_time_seg = rqlookupobject(current_job,
    &time_seg_name, NO_WAIT, &status);
  
  sys_time = buildptr(sys_time_seg, 0);

/* Access time/date memory  */

  rqreceiveunits(clock_sem, 1, WAIT_FOREVER, &status);

/* Find an empty timer slot  */

  while (!done)
    {
    if (!sys_time->timer[indx].in_use)
      {
      done = TRUE;
      no_more_timers = FALSE;
      }
    else
      {
      indx++;
      
      if (indx == MAX_TIMERS)
        {
        done = TRUE;
        }
      }
    }

/* Establish a timer  */

  if (no_more_timers == FALSE)
    {
    if (timeout ! = 0)
      {
      timer_sem = rqcreatesemaphore(SEM_INIT_0,
        999, FIFO_SEM, &status);
      sys_time->timer[indx].in_use = TRUE;
      sys_time->timer[indx].count = 0;
      sys_time->timer[indx].timeout = timeout;
      sys_time->timer[indx].sem_tkn = timer_sem;
      }
   }
/*  Release time/date memory */

  rqsendunits(clock_sem, 1, &status);

/*  Return timer semaphore to caller */

  return(timer_sem);
  }

/*1*********************************************************
* Function:  stop_timer
*
* Summary:   Delete a timer.
*
* Invocation:  timer_sem = start_timer(timeout);
*
* Inputs:    timeout (unsigned int) - timer interval
*              in seconds
*
* Outputs:   timer_sem (selector) - the semaphore that will
*              receive a unit at each timer interval
*
* Caveats:   Accesses the time/date data segment that is
*            shared with the clock_task().
***********************************************************/

unsigned int stop_timer(selector timer_sem)
  {
  unsigned int    status;
  unsigned int    indx;
  unsigned int    done;
  unsigned int    invalid_timer;
  
  selector    current_job;
  selector    clock_sem;
  selector    sys_time_seg;
  
  struct SYS_TIME_STR   *sys_time;
  
  indx = 0;
  done = FALSE;
  invalid_timer = TRUE;
  
/*  Get objects for access semaphore and */
/*  time/date data segment               */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  clock_sem = rqlookupobject(current_job,
    &clock_sem_name, NO_WAIT, &status);
  sys_time_seg = rqlookupobject(current_job,
    &time_seg_name, NO_WAIT, &status);
  
  sys_time = buildptr(sys_time_seg, 0);

/*  Access time/date memory.  */

  rqreceiveunits(clock_sem, 1, WAIT_FOREVER, &status);

/* Locate and remove this timer  */

  while (!done)
    {
    if (sys_time->timer[indx].in_use &&
      (sys_time->timer[indx].sem_tkn == timer_sem))
      {
      rqdeletesemaphore(timer_sem, &status);
      sys_time->timer[indx].in_use = FALSE;
      sys_time->timer[indx].sem_tkn = NUL_TKN;
      sys_time->timer[indx].count = 0;
      sys_time->timer[indx].timeout = 0;
      done = TRUE;
      invalid_timer = FALSE;
      }
    else
      {
      indx++;
      
      if (indx == MAX_TIMERS)
        {
        done = TRUE;
        }
      }
    }

/*  Release time/date memory  */
  
  rqsendunits(clock_sem, 1, &status);
  
/*  Return with result of call  */
  
  return (invalid_timer ? ERROR : EOK);
  }

/*1*********************************************************
* Task:     count_task
*
* Summary:  Continuously counts and displays a value from
*           0-65535.
*
* Caveats:  Communicates with the crt_task() for displaying
*           counter values.
***********************************************************/

void count_task(void)
  {
  unsigned char counter_buf[6];
  
  selector  current_job;
  selector  rsp_mbx;
  selector  crt_req_mbx;
  selector  init_sem;
  selector  count_shtdwn_sem;
  selector  req_seg;
  
  unsigned int  counter;
  unsigned int    status;
  
  struct CRT_REQ_STR    *req_msg;
  
  struct EXCEPTIONSTRUCT    exceptioninfo;
  
/*  Disable this task's exception handler  */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/*  Lookup/Create needed objects  */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, NO_WAIT, &status);
  
  count_shtdwn_sem = rqlookupobject(current_job,
    &count_shtdwn_sem_name, NO_WAIT, &status);
  
  rsp_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);
  
  crt_req_mbx = rqlookupobject(current_job,
    &crt_req_mbx_name, NO_WAIT, &status);

/*  Initialize request message  */

  req_seg = rqcreatesegment(
    sizeof(struct CRT_REQ_STR), &status);
  
  req_msg = buildptr(req_seg, 0);
  
  req_msg->msg_ptr = counter_buf;
  req_msg->row = 12;
  req_msg->col = 38;

/*  Singal that initialization is completed.  */

  rqsendunits(init_sem, 1, &status);

/*  Continuously count 0-65535 and display the  */
/*  value until signalled to stop.               */

  counter = 0;
  
  rqreceiveunits(count_shtdwn_sem, 1, NO_WAIT, &status);
  
  while (status == ETIME) /* while no signal received */
    {
    sprintf(counter_buf, "%d", counter);
    rqsendmessage(crt_req_mbx,
      req_seg, rsp_mbx, &status);
    req_seg = rqreceivemessage(rsp_mbx,
      WAIT_FOREVER, NUL_TKN, &status);
    counter++;
    rqreceiveunits(count_shtdwn_sem, 1, NO_WAIT, &status);
    }

/*  Cleanup objects created by this task. */

  rqdeletemailbox(rsp_mbx, &status);
  rqdeletesegment(req_seg, &status);

/*  Signal shutdown complete and suspend execution. */

  rqsendunits(init_sem, 1, &status);
  
  rqsuspendtask(NUL_TKN, &status);  /* suspend this task  */
  }

/*1************************************************************
* Task:     timer_task
*
* Summary:
*
* Caveats:
**************************************************************/

void timer_task(void)
  {
  selector    current_job;
  selector    init_sem;
  selector    clock_sem;
  selector    timer_sem;
  
  unsigned  int   status;
  
  struct EXCEPTIONSTRUCT  exceptioninfo;

/*  Disable this task's exception handler  */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/* Lookup needed objects  */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, NO_WAIT, &status);

/*  Start a one-second timer. If the timer was      */
/*  created, wait for 20 seconds (units) then stop  */
/*  stop the timer.                                 */
  timer_sem = start_timer(1);
  
  if (timer_sem != NUL_TKN)
    {
    rqreceiveunits(timer_sem, 20, WAIT_FOREVER, &status);
    stop_timer(timer_sem);
    }

/*  Signal task finished and suspend execution.  */

  rqsendunits(init_sem, 1, &status);
  
  rqsuspendtask(NUL_TKN, &status); /* suspend this task */
  }

/*1**********************************************************
* Functions:   clrscrn, cursor_on, cursor_off, print_at
*
* Summary:   Functions for basic display control.
*
* Invocation:    clrscrn( );
*                curson_on( );
*                cursoff_on( );
*
* Invocation:    print_at(row, col, msg_ptr)
*
* Inputs:  row (unsigned int) - row position
*          col (unsigned int) - column position
*          msg_ptr (unsigned char *) - message string pointer
*
* Caveats: Escape sequences are for a WYSE60 terminal.
**************************************************************/

void clrscrn(void)
  {
  fprintf(stdout, "\x1B+");
  fflush(stdout);
  return;
  }

void cursor_on(void)
  {
  fprintf(stdout, "\x1B`1");
  fflush(stdout);
  return;
  }

void cursor_off(void)
  {
  fprintf(stdout, "\x1B'0");
  fflush(stdout);
  return;
  }

void print_at(unsigned int row,
  unsigned int col, unsigned char *msg_ptr)
  {
  fprintf(stdout, "\x1B=%c%c%s",
    (row + 31), (col + 31), msg_ptr);
  fflush(stdout);
  return;
  }

/*1*********************************************************
* Task:     crt_task
*
* Summary:
*
* Caveats:
***********************************************************/

void crt_task(void)
  {
  selector    current_job;
  selector    req_mbx;
  selector    rsp_mbx;
  selector    rec_seg;
  selector    init_sem;
  selector    crt_shtdwn_sem;
  
  unsigned int    status;
  
  struct CRT_REQ_STR    *req_msg;
  
  struct EXCEPTIONSTRUCT    exceptioninfo;
  
/*  Disable this task's exception handler */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/*  Lookup/Create needed objects  */

  current_job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, WAIT_FOREVER, &status);
  
  crt_shtdwn_sem = rqlookupobject(current_job,
    &crt_shtdwn_sem_name, WAIT_FOREVER, &status);
  
  req_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);
  rqcatalogobject(current_job,
    req_mbx, &crt_req_mbx_name, &status);

/*  Initialize display  */

  cursor_off( );
  clrscrn( );

/*  Signal initialization complete  */

  rqsendunits(init_sem, 1, &status);
  
  
  rqreceiveunits(crt_shtdwn_sem, 1, NO_WAIT, &status);
  
  while (status == ETIME)
    {
    req_seg = rqreceivemessage(req_mbx,
      WAIT_FOREVER, &rsp_mbx, &status);
    
    req_msg = buildptr(req_seg, 0);
    
    print_at(req_msg->row,
      req_msg->col, req_msg->msg_ptr);
    
    if (rsp_mbx != NUL_TKN)
      {
      rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);
      }
    else
      {
      rqdeletesegment(req_seg, &status);
      }
      
    rqreceiveunits(crt_shtdwn_sem, 1, NO_WAIT, &status);
    }
    
  rqsendunits(init_sem, 1, &status);
  
  for(;;)
    {
    req_seg = rqreceivemessage(req_mbx,
      WAIT_FOREVER, &rsp_mbx, &status);
      
    if (rsp_mbx != NUL_TKN)
      {
      rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);
      }
    else
      {
      rqdeletesegment(req_seg, &status);
      }
    }
  }

/*1*********************************************************
* Function:  read_time
*
* Summary:   Read a specified time/date register of the
*            hardware clock.
*
* Invocation:  in_byte = read_time(reg);
*
* Inputs:    reg (unsigned char) - the hardware time/date
*              register to be read
*
* Outputs:   in_byte (unsigned char) - the value of the
*              register read
*
* Caveats:   None
***********************************************************/

unsigned char read_time(unsigned char reg)
  {
  unsigned char in_byte;
  
  outbyte(CNT_PORT, HOLD_HIGH);
  delay(3);
  outbyte(CNT_PORT, HOLD_READ);
  delay(1);
  outbyte(ADR_PORT, reg);
  delay(1);
  in_byte = inbyte(DAT_PORT);
  outbyte(CNT_PORT, CNT_RST);
  delay(1);
  
  return (in_byte);
  }

/*1*********************************************************
* Function:  write_time
*
* Summary:   Write a byte to the specified time/date register
*            of the hardware clock.
*
* Invocation:  write_time(reg, out_byte);
*
* Inputs:    reg (unsigned char) - the hardware time/date
*              register
*
*           out_byte (unsigned char) - the value to be
*             written to the hardware clock register
*
* Caveats:  None
***********************************************************/

void write_time(unsigned char reg, unsigned char out_byte)
  {
  outbyte(CNT_PORT, HOLD_HIGH);
  delay(3);
  outbyte(ADR_PORT, reg);
  delay(1);
  outbyte(DAT_PORT, out_byte);
  delay(1);
  outbyte(CNT_PORT, HOLD_WRITE);
  delay(1);
  outbyte(CNT_PORT, CNT_RST);
  delay(1);
  
  return;
  }

/*1*********************************************************
* Function:  read_clock
*
* Summary:   Read the time/date from the hardware clock and
*            stores it in the global time/date structure.
*
* Invocation:  read_clock();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
***********************************************************/

void read_clock(void)
  {
  unsigned char time_buf[13];
  unsigned int i;

/*  Set mode */

  outbyte(PIO_PORT, PIO_READ);
  delay(1);

/*  Set control lines to disable interrupts */

  outbyte(CNT_PORT, CNT_RST);
  delay(1);

/*  Read time/date */

  for (i = 0 ; (i <= 12); i++)
    {
    time_buf[i] = read_time(i);
    }
  
  sys_time->second =
    (10 * (time_buf[SEC10_REG] & 0x07)) +
    (time_buf[SEC01_REG] & 0x0F);
  sys_time->minute =
    (10 * (time_buf[MIN10_REG] & 0x07)) +
    (time_buf[MIN01_REG] & 0x0F);
  sys_time->hour =
    (10 * (time_buf[HRS10_REG] & 0x03)) +
    (time_buf[HRS01_REG] & 0x0F);
  sys_time->date =
    (10 * (time_buf[DAT10_REG] & 0x03)) +
    (time_buf[DAT01_REG] & 0x0F);
  sys_time->month =
    (10 * (time_buf[MON10_REG] & 0x01)) +
    (time_buf[MON01_REG] & 0x0F);
  sys_time->year =
    (10 * (time_buf[YRS10_REG] & 0x0F)) +
    (time_buf[YRS01_REG] & 0x0F);

/*  Reset control lines to enable interrupts */

  outbyte(CNT_PORT, INT_ENB);
  delay(1);
  
  outbyte(ADR_PORT, 0x0F);
  delay(1);
  
  return;
  }

/*1*********************************************************
* Function:  write_clock
*
* Summary:   Uses the global time/date structure to write a
*            new time/date to the hardware clock.
*
* Invocation:  write_clock();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
************************************************************/

void write_clock(void)
  {
  unsigned char temp_byte;

/*  Set mode */

  outbyte(PIO_PORT, PIO_WRITE);
  delay(1);

/*  Set control lines to disable interrupts */

  outbyte(CNT_PORT, CNT_RST);
  delay(1);

/*  Write time/date. Seconds always set to 0 */

  sys_time->second = 0;
  write_time(SEC01_REG, 0);
  write_time(SEC10_REG, 0);
  
  temp_byte =
    sys_time->minute - (10 * (sys_time->minute / 10));
  write_time(MIN01_REG, temp_byte);
  temp_byte = sys_time->minute / 10;
  write_time(MIN10_REG, temp_byte);
  
  temp_byte =
    sys_time->hour - (10 * (sys_time->hour / 10));
  write_time(HRS01_REG, temp_byte);
  temp_byte =
    (sys_time->hour / 10) | 0x08;     /* 24hr format */
  write_time(HRS10_REG, temp_byte);
  
  temp_byte =
    sys_time->date - (10 * (sys_time->date / 10));
  write_time(DAT01_REG, temp_byte);
  temp_byte = sys_time->date / 10;
  write_time(DAT10_REG, temp_byte);
  
  temp_byte =
    sys_time->month - (10 * (sys_time->month / 10));
  write_time(MON01_REG, temp_byte);
  temp_byte = sys_time->month / 10;
  write_time(MON10_REG, temp_byte);
  
  temp_byte =
    sys_time->year - (10 *(sys_time->year / 10));
  write_time(YRS01_REG, temp_byte);
  temp_byte = sys_time->year / 10;
  write_time(YRS10_REG, temp_byte);

/*  Reset mode*/

  outbyte(PIO_PORT, PIO_READ);
  delay(1);

/*  Reset control lines to enable interrupts */

  outbyte(CNT_PORT, INT_ENB);
  delay(1);
  
  outbyte(ADR_PORT, 0x0F);
  delay(1);
  
  return;
  }

/*1*********************************************************
* Function:  reset_timers
*
* Summary:   Initializes (zero out) all timer slots of the
*            shared time/date data segment.
*
* Invocation:  reset_timers();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
************************************************************/

void reset_timers(void)
  {
  unsigned int    i;
  
  for (i = 0 ; i < MAX_TIMERS; i++)
    {
    sys_time->timer[i].in_use = FALSE;
    sys_time->timer[i].sem_tkn = NUL_TKN;
    sys_time->timer[i].count = 0;
    sys_time->timer[i].timeout = 0;
    }
  
  return;
  }

/*1*********************************************************
* Function:  advance_timers
*
* Summary:   Increments all active timers in the shared
*            time/date data segment by one second.
*
* Invocation:  advance_timers();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
***********************************************************/

void advance_timers(void)
  {
  unsigned int    i;
  unsigned int    status;
  
  
  for (i = 0 ; i < MAX_TIMERS; i++)
    {
    if (sys_time->timer[i].in_use)
      {
      sys_time->timer[i].count++;
      if (sys_time->timer[i].count >=
        sys_time->timer[i].timeout)
        {
        rqsendunits(
          sys_time->timer[i].sem_tkn, 1, &status);
        sys_time->timer[i].count = 0;
        }
      }
    }
  
  return;
  }


/*1**********************************************************
* Function:  advance_time_date
*
* Summary:   Increments the time/date (hh:mm:ss mm/dd/yy) in
*            the shared time/date data segment by one second.
*
* Invocation:  advance_time_date();
*
* Caveats:   Requires the caller to obtain access the shared
*            time/date data segment.
************************************************************/


void advance_time_date(void)
  {
  if (sys_time->second < 59)
    {
    sys_time->second++;
    
    }
  else
    {
    sys_time->second = 0;
    
    if (sys_time->minute < 59)
      {
      sys_time->minute++;
      }
    else
      {
      sys_time->minute = 0;
      
      if (sys_time->hour < 23)
        {
        sys_time->hour++;
        }
      else
        {
        sys_time->hour = 0;
        
        if (sys_time->date <
          days_in_month[sys_time->month - 1])
          {
          sys_time->date++;
          }
        else
          {
          if (sys_time->month == FEBRUARY)
            {
/*  Check for leap year (only good through 2100)  */

            if (((sys_time->year % 4) == 0) &&
              (sys_time->date == 28))
              {
              sys_time->date = 29;
              }
            else
              {
              sys_time->month = MARCH;
              sys_time->date = 1;
              }
            }
          else
            {
            sys_time->date = 1;
            
            if (sys_time->month < DECEMBER)
              {
              sys_time->month++;
              }
            else
              {
              sys_time->month = JANUARY;
              
              if (sys_time->year < 99)
                {
                sys_time->year++;
                }
              else
                {
                sys_time->year = 0;
                }
              }
            }
          }
        }
      }
    }
  
  return;
  }


/*1*********************************************************
* Function:  start_timer
*
* Summary:   Create and start a timer.
*
* Invocation:  timer_sem = start_timer(timeout);
*
* Inputs:    timeout (unsigned int) - timer interval
*             in seconds
*
* Outputs:   timer_sem (selector) - the semaphore that will
*             receive a unit at each timer interval
*
* Caveats:   The interrupt handler will signal the
*            interrupt task to finish servicing the
*            interrupt.
************************************************************/

/* Tell the compiler that this function is an interrupt    */
/* routine so it can generate proper cede for entering     */
/* and exiting the interrupt routine.                      */

#pragma interrupt("clockint_handler")

void clockint_handler(void)
  {
  unsigned int status;

/*  Signal the interrupt task */

  rqsignal interrupt(CLOCK_INT_LEVEL, &status);
  
  return;
  }

/*1*********************************************************
* Task:     clock_task
*
* Summary:  Services interrupts from the hardware clock.
*           Also maintains/updates the global time/date
*           data and services software timers.
*
* Caveats:  Communicates with the crt_task() to display the
*           current time/date.
***********************************************************/

void clock_task(void)
  {
  char time_string[19];
  
  unsigned int status;
  unsigned int retry;
  
  selector current_job;
  selector init_sem;
  selector clock_sem;
  selector clock_shtdwn_sem;
  
  selector rsp_seg;
  selector req_seg;
  selector crt_req_mbx;
  selector rsp_mbx;
  
  struct CRT_REQ_STR       *req_msg;
  
  struct EXCEPTIONSTRUCT      exceptioninfo;

/*  Disable this task's exception handler */

  rqgetexceptionhandler(&exceptioninfo, &status);
  exceptioninfo.exceptionmode = 0;
  rqsetexceptionhandler(&exceptioninfo, &status);

/*  Disable any current interrupt task */

  rqresetinterrupt(CLOCK_INT_LEVEL, &status);

/*  Lookup/Create needed objects */

  current job = rqgettasktokens(THIS_JOB, &status);
  
  init_sem = rqlookupobject(current_job,
    &init_sem_name, NO_WAIT, &status);
  
  clock_sem = rqlookupobject(current_job,
    &clock_sem_name, NO_WAIT, &status);
  
  sys_time_seg = rqlookupobject(current_job,
    &time_seg_name, NO_WAIT, &status);
  
  clock_shtdwn_sem = rqlookupobject(current_job,
    &clock_shtdwn_sem_name, NO_WAIT, &status);
  
  crt_req_mbx = rqlookupobject(current_job,
    &crt_req_mbx_name, NO_WAIT, &status);
  
  sys_time = buildptr(sys_time_seg, 0);
  
  rsp_mbx = rqcreatemailbox(FIFO_OBJ_MBX, &status);

/*  Initialize time string buffer  */

  memset(&time_string, ' ' ,sizeof(time_string));

/*  Initialize crt_task() request message and  */
/*  send it to the response mailbox to setup   */
/*  for the processing loop.                   */

  req_seg = rqcreatesegment(
    sizeof(struct CRT_REQ_STR), &status);
  req_msg = buildptr(req_seg, 0);
  
  req_msg->msg_ptr = &time_string;
  req_msg->row = 1;
  req_msg->col = 60;
  
  rqsendmessage(rsp_mbx, req_seg, NUL_TKN, &status);

/*  Initialize retry counter and timers */

  retry = 0;
  sys_time->change_flg = FALSE;
  reset_timers();

/*  Initialize and read hardware clock */

  outbyte(PIO_PORT, PIO_READ);
  delay(1);
  
  outbyte(CNT_PORT, INT_ENB);
  delay(1);
  
  outbyte(ADR_PORT, 0x0F);
  delay(1);
  
  read_clock();

/*  Allow access to time/date memory */

  rqsendunits(clock_sem, 1, &status);

/*  Set interrupt vector  */

  rqsetinterrupt(CLOCK_INT_LEVEL, 1,
    &clockint_handler, NUL_TKN, &status);

/*  Signal initialization complete */

  rqsendunits(init_sem, 1, &status);

/*  Process interrupt signals until shutdown */

  rqreceiveunits(clock_shtdwn_sem, 1, NO_WAIT, &status);
  
  while (status == ETIME)  /* while no signal received */
    {
/*  Wait no longer than 2 seconds for interrupt signal */
   
   rqetimedinterrupt(CLOCK_INT_LEVEL, 200, &status);

/*  If an interrupt is NOT received, attempt to reset  */
/*  the hardware clock.                                */

   if (status == ETIME)      /* if no interrupt  */
     {
     retry++;

/*  Once the maximum number of retries is reached,    */
/*  give up. Retrieve outstanding display request     */
/*  and wait for shutdown. When shutdown signal is    */
/*  received, disable this interrupt task, signal     */
/*  shutdown complete and suspend execution.          */

     if (retry == MAX_CLOCK_RETRIES)
       {
       req_seg = rqreceivemessage(rsp_mbx,
         WAIT_FOREVER, NUL_TKN, &status);
       rqreceiveunits(clock_shtdwn_sem, 1,
         WAIT_FOREVER, &status);
       rqresetinterrupt(CLOCK_INT_LEVEL, &status);
       rqsendunits(init_sem, 1, &status);
       rqsuspendtask(NUL_TKN, &status);
       }

/*  Reinitialize hardware clock */

       outbyte(PIO_PORT, PIO_READ);
       delay(1);
       
       outbyte(CNT_PORT, INT_ENB);
       delay(1);
       
       outbyte(ADR_PORT, 0x0F);
       delay(1);

/*  Re-read hardware crock */

       rqreceiveunits(clock_sem, 1,
         WAIT_FOREVER, &status);
       
       read_clock();
       
       rqsendunits(clock_sem, 1, &status);
       }
     else
       {
/*  Interrupt signal received, reset retry count */

       retry = 0;

/*  Access time/date memory */

       rqreceiveunits(clock_sem, 1,
         WAIT_FOREVER, &status);

/*  If needed, update hardware clock with new  */
/*  time/date data. Otherwise, advance the     */
/*  time/data data by one second.              */

       if (sys_time->change_flg)
         {
         write_clock();
         sys_time->change_flg = 0;
         }
       else
         {
         advance_time_date();
         }

/*  Advance any timers by one second  */

       advance_timers();

/*  Prepare time/date data for display  */

     sprintf(time_string, "%02d:%02d:%02d %02d/%02d/%O2d",
       sys_time->hour, sys_time->minute, sys_time->second,
       sys_time->month, sys_time->date, sys_time->year);

/*  Release time/date memory */

     rqsendunits(clock_sem, 1, &status);

/*  If previous display request has finished(segment    */
/*  returned), then request new time/date be displayed  */

     rsp_seg = rqreceivemessage(rsp_mbx,
       NO_WAIT, NUL_TKN, &status);
     
     if (status != ETIME)
       {
       rqsendmessage(crt_req_mbx,
         req_seg, rsp_mbx, &status);
       }
     }
/*  Check for shutdown  */

   rqreceiveunits(clock_shtdwn_sem, 1, NO_WAIT, &status);
   }

/*  Retrieve outstanding display request, disable   */
/*  this interrupt task, signal shutdown complete   */
/*  and suspend execution.                          */

  req_seg = rqreceivemessage(rsp_mbx,
    WAIT_FOREVER, NUL_TKN, &status);
  
  rqsendunits(init_sem, 1, &status);
  
  rqresetinterrupt(CLOCK_INT_LEVEL, &status);
  
  rqsuspendtask(NUL_TKN, &status);  /* suspend this task  */
  }