Difference between revisions of "OCEOS Directives Reference"

From wiki
Jump to navigation Jump to search
Line 452: Line 452:


=== oceos_mutex_wait ===
=== oceos_mutex_wait ===
<syntaxhighlight lang="C" line>
  enum DIRECTIVE_STATUS  oceos_mutex_wait(
  enum DIRECTIVE_STATUS  oceos_mutex_wait(
     unsigned int        // mutex ID, must be in range 0 to 62
     unsigned int        // mutex ID, must be in range 0 to 62
  );
  );
</syntaxhighlight>


<p>This directive, if successful, assigns a mutex to a job and replaces the current system priority ceiling with the ceiling specified when the mutex was created. If the mutex is already held by another task then an error status is returned.</p>
<p>This directive, if successful, assigns a mutex to a job and replaces the current system priority ceiling with the ceiling specified when the mutex was created. If the mutex is already held by another task then an error status is returned.</p>

Revision as of 08:14, 1 October 2021

Introduction

Directives Reference

OCEOS provides a set of directives to the application developer. These directives are detailed below.

Initialisation Manager

oceos_init

1 enum DIRECTIVE_STATUS   oceos_init(
2     const struct application_configuration
3 );

This directive initialises the OCEOS fixed data structures. It is called from application_init() in config.c.

Parameters in: application_configuration structure defined below (oceos.h).

The values if the structure members are overwritten by the application developer #defines for NUMBER_OF_TASKS, NUMBER_OF_MUTEXES, ...etc.

 1  /*
 2   * Application Configuration Structure
 3   *
 4   * The application must create a structure of this type,
 5   * initialise its fields as appropriate for the application
 6   * then pass it to oceos_init().
 7   *
 8   * It is recommended that fields that are not used are set to 0 or to NULL.
 9   *
10   * The structure is not used subsequently by OCEOS,
11   * after oceos_init completes the space used for it can be freed.
12   *
13   */
14  struct application_configuration {
15 
16   /* addresses to be used used for internal OCEOS data */
17   U32_t            log_address;
18   U32_t            fixed_data_address;
19   U32_t            dynamic_data_address;
20 
21   /* used in relation to the system stack */
22   U32_t            stack_start_address;
23   U32_t            stack_lower_bound_address;
24 
25   /* user defined functions to handle invalid system state and log 3/4 full */
26   void (*system_error_function)(void *);
27   void (*log_full_function)(void *);
28 
29   /* basic application component numbers */
30   unsigned int     number_of_tasks                     : 8;
31   unsigned int     number_of_readyQ_entries            : 8;
32   unsigned int     log_number_of_entries               : 10;
33   unsigned int     number_of_mutexes                   : 6;
34   unsigned int     number_of_counting_semaphores       : 6;
35   unsigned int     number_of_data_queues               : 6;
36   unsigned int     timed_actions_timer_index           : 4; // Must be highest priority timer, on timer unit 0
37   unsigned int     action_time_interrupt               : 8; // Interrupt number for action timer(6 bits should be enough)
38   unsigned int     timed_actions_queue_size            : 8;
39   unsigned int     number_of_user_defined_traps        : 8;
40   unsigned int     sys_time_timer_index                : 4; // Timer index within selected timer Unit
41   unsigned int     interrupt_nesting_enabled           : 1; // Change to interrupt nesting enabled
42   unsigned int                                         : 1; // spare
43   unsigned int     watchdog_timer_index                : 2; // Index of watchdog timer
44   unsigned int     watchdog_window                     : 16;
45   unsigned int     watchdog_timeout;
46  };

Status returns:

oceos_init_finish

1 // finalises the fixed data array after all tasks etc. have been created
2  enum DIRECTIVE_STATUS   oceos_init_finish();

This directive finishes intitialisation for the fixed data area. It is called after tasks, metexes and semaphores have been created.

oceos_start

1  // starts OCEOS - usually does not return
2  enum DIRECTIVE_STATUS   oceos_start(
3     const U32_t * const,// pointer to fixed data array
4     const unsigned int, // taskID. If invalid CPU enters sleep mode
5     void * const        // pointer to data to be passed to task
6  );

This directive starts OCEOS scheduling and is called after oceis_init_finish(). Parameters passed are:

  • Pointer to the fixed data area (usually fixed_data declared in config.c)
  • The task ID of the first task to be executed. Usually this task will start other tasks necessary for the application.
  • Pointer to be passed to the task above. A null pointer can be passed if the task does not use the pointer.

oceos_exit

1  // exits OCEOS - terminating functions of all active jobs are called
2  enum DIRECTIVE_STATUS   oceos_exit();

This directive exits OCEOS terminating all jobs.

oceos_CPU_sleep

1  // put CPU in sleep mode after ensuring interrupts are enabled
2  enum DIRECTIVE_STATUS   oceos_CPU_sleep();

This directive puts the CPU in sleep mode which may be required to save power. The watchdog will be disabled and only an interrupt will wake the CPU. If the application is required to wake the processor in the future then a timed action can be used.

oceos_check_area

1  unsigned int oceos_check_area(
2     U32_t * const,      // area to be checked
3     const unsigned int  // area size in 32-bit words);

Directive to check if the area header and end sentinel are o.k. Parameters passed are:

  • Pointer to start of fixed data area.
  • Size of fixed data area

Return values:

  • 0 indicating that area is good.
  • Non-Zero with bits set as follows:
    • Bit 0 - INVALID_VERSION (Invalid OCEOS version)
    • Bit 1 - INVALID_WORDS_USED (area size does not match area size stored)
    • Bit 2 - INVALID_END (invalid sentinel at end of data area)

Task Manager

Task Status

1  /* A task's status is returned as below if queried */
2  enum TASK_STATUS {
3     TASK_DISABLED,      // cannot be scheduled, and no current jobs
4     TASK_ENABLED,       // can be scheduled, but no current jobs
5     TASK_INVALID,       // task does not exist
6   };

Valid task states are listed above.

oceos_task_create

 1  /* create a task, can only be use before OCEOS starts. */
 2  enum DIRECTIVE_STATUS   oceos_task_create(
 3     unsigned int taskID,             // unique, 0 to TASKS_MAX_ID
 4     U32_t        priority,           // TASK_MAX_PRIORITY to TASK_MIN_PRIORITY
 5     U32_t        threshold,          // TASK_MAX_PRIORITY to priority
 6     U32_t        jobs_max,           // 1 to JOBS_MAX
 7     BOOLE_t      FP_used,            // whether uses floating point hardware
 8     BOOLE_t      initially_enabled,  // whether task enabled initially
 9     void (*start_function)(void *),  // task start address
10     void (*end_function)(void *),    // start of task end function
11     U32_t       time_deadline,       // maximum time to complete (ignore if 0)
12     U32_t       time_mininterstart   // minimum time between start requests
13  );

This directive populates the data structures for a task. It should be called after application_init() and before oceos_init_finish()

It must be called for each task otherwise oceos_start(..) will return an error.

Parameters in:

  • integer specifying task ID (0 to 254 and specified a enumerated type in config.h)
  • integer specifying task priority (1 to 254 where 1 is the highest priority and 254 the lowest)
  • integer specifying pre-emption threshold (1 to 254). Only higher priority tasks can pre-empt.
  • integer specifying the maximum number of jobs for this task (1 to 15)
  • boolean specifying if floating point is used (false=not used). Enabling floating point causes the floating point registers to be saved when the task in pre-empted.
  • boolean specifying if the task is initially enabled (false is disabled)
  • pointer to function to be executed on task start
  • pointer to function to be executed when task ends
  • integer specifying the maximum time (in us) that the task can run (0 = no limit)
  • integer specifying the minimum time between task finishing and being restarted (0 = no minimum time)

Status returns:

  • SUCCESSFUL
  • INTERNAL_ERROR (data area not setup)
  • TOO_MANY (NUMBER_OF_TASKS in config.h have already been created)
  • INVALID_ID (invalid task ID)
  • INVALID_NAME (task ID already used)
  • INVALID_PRIORITY (incorrect priority)
  • INVALID_NUMBER (incorrect pre-emption threshold)
  • INVALID_SIZE (incorrect number of jobs)

oceos_task_start

1  /* start a task - create job, pass it data pointer, place on ready queue */
2  enum DIRECTIVE_STATUS   oceos_task_start(
3     const unsigned int, // task ID, must be in range 0 to 254
4     void * const        // pointer to data
5  );

This directive starts a job for the specified task. Starting a task involves three main stages

  • setting up a job
    • finding a free job entry if available
    • setting up this entry including passing it the data void
  • placing the job on readyQ
  • doing a context switch

The context switch must be dealt with differently when a task is started from an interrupt handler.

The function should only be called after oceos_start(..) has been called.

IMPORTANT NOTE: Tasks can be started in interrupt services routines but CANNOT be started in trap handlers.

Parameters in:

  • integer specifying ID of task, must be in range 0 to NUMBER_OF_TASKS - 1
  • pointer: data pointer passed to job when task starts

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (problem with data area, attempt to start disabled task)
  • INVALID_ID (incorrect task ID)
  • RESOURCE_IN_USE (problem placing job on the active queue)
  • TOO_MANY (job queue full)

oceos_task_timed_start

1  /* schedule a task to start at a specific time */
2  enum DIRECTIVE_STATUS   oceos_task_timed_start(
3     const unsigned int, // task ID, must be in range 0 to 254
4     void * const,       // pointer to data
5     const U64_t,        // system time at which job should start
6     const U32_t,        // forward tolerance for time
7     const U32_t         // backward tolerance for time
8     U32_t *             // return job_id, to remove this job if needed
9  );

This directive schedules a task to start execution at a given system time. It should only be called after oceos_start(..)

Parameters in:

  • integer specifying ID of task, must be in range 0 to NUMBER_OF_TASKS - 1
  • pointer passed to job when task starts
  • integer (64-bit) specifying system time at which task should start
  • integer specifying the forward time tolerance in usecs.

    The task can only be started if the current time is within this tolerance before the specified start time.

  • integer specifying the backward time tolerance in usecs.

    The task can only be started if the current time is within this tolerance after the specified start time.

  • Pointer to integer to store the ID of the job to be started.

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised)
  • INVALID_ID (invalid task ID)
  • UNSATISFIED (invalid start time)
  • INTERNAL_ERROR (error adding job to timed actions queue)
  • TOO_MANY (too many jobs already started)

oceos_task_disable

1  /* disable a task and remove any current pending or active jobs */
2  enum DIRECTIVE_STATUS   oceos_task_disable(
3     const unsigned int  // task ID, must be in range 0 to 254
4  );

This directive disables a task and removes any current or pending jobs.

Parameters in:

  • integer specifying task ID

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initilised)
  • INVALID_ID (incorrect task ID)

oceos_task_enable

1  /* enable a task - only enabled tasks are scheduled */
2  enum DIRECTIVE_STATUS   oceos_task_enable(
3     const unsigned int  // task ID, must be in range 0 to 254
4  );

This directive enables a task. It can only be called after oceos_start(..);

Parameters in:

  • integer specifying task ID

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised)
  • INVALID_ID (incorrect task ID)

oceos_task_self

1  /* get the taskID of the current job */
2  unsigned int  oceos_task_self();

This directive returns the task ID of the currently executing job. Parameters in: None Returns integer with the task ID.

oceos_task_get_priority

1  /* get priority of a task */
2  U8_t  oceos_task_get_priority(
3     const unsigned int  // task ID, must be in range 0 to 254
4  );

This directive returns the priority of the specified task ID.

Parameters in:

  • integer specifying task ID

Return values:

  • Integer with the priority if the task ID.
  • TASK_INVALID_PRIORITY (invalid task ID, data area not initialised,)

oceos_task_get_status

1  /* get status of a task */
2  enum TASK_STATUS  oceos_task_get_status(
3     const unsigned int  // task ID, must be in range 0 to 254
4  );

This directive returns the status of the specified task ID as a value of enum TASK_STATUS type below.

1  enum TASK_STATUS {
2     TASK_DISABLED,      // cannot be scheduled, and no current jobs
3     TASK_ENABLED,       // can be scheduled, but no current jobs
4     TASK_INVALID,       // task does not exist
5  };

Parameters in:

  • integer specifying task ID

Return values:

  • Integer with the task status
  • TASK_ENABLED (task is enabled, can be scheduled, but no current jobs)
  • TASK_DISABLED (task is disabled, cannot be scheduled, and no current jobs)
  • TASK_INVALID (task ID passed is incorrect)

oceos_task_kill

1  /*
2   * Directive to call task end function on current task when undesired behavior was detected,
3   * and exit from job to context switch
4   *
5   */
6   enum DIRECTIVE_STATUS        oceos_task_kill();

This directive kills the currently executing job. It can be called from

  1. within the task
  2. the error handler
  3. from an interrupt service routine

OCEOS can only kill the current job due to its single stack design. Application scenarios where this directive could be used include:

  1. Application regular interrupt checking job status

    If such an application interrupt detected an error in the current job it can call oceos_task_kill() as part of its recovery actions

  2. oceos_on_error(..)

    OCEOS calls oceos_on_error(..) in the case of a system_error. This is an application function contained in config.c where application recovery code is located.

    oceos_task_kill is a possible directive to be used as part of error recovery.

Parameters in: None Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised or inconsistent, scheduling not started)
  • INTERNAL_ERROR (dynamic data area not initialised or inconsistent)

oceos_task_get_info

 1  /* Get a struct task_info for task information
 2   * return :
 3   *          INCORRECT_STATE  if initialisation is not done
 4   *          INTERNAL_ERROR   if task information is NULL
 5   *          INVALID_ID       if task id is not valid
 6   *          NOT_DEFINED      if task_info pointer is NULL
 7   */
 8  enum DIRECTIVE_STATUS oceos_task_get_info (
 9     const unsigned int task_id,  // Task ID
10     struct task_info *info   // Pointer to struct task_info
11  );
 1  /*
 2   * task info to be used with oceos_task_get_info
 3   */
 4  struct task_info {
 5 
 6     unsigned int    jobs_current_max  :16;        // maximum number of current jobs for this task, no roll over, can be reset
 7     unsigned int    jobs_total        :16;        // number of times this task was started, no roll-over, can be reset
 8     unsigned int    jobs_current;                 // Number of task instances currently in use by oceos
 9     U32_t           time_max_delay;               // maximum time job is waiting on ready queue before becoming active
10     U32_t           time_max_finish;              // maximum time to finish job execution after starting
11     U32_t           time_max_exec;                // maximum time spent executing
12     U32_t           time_total_exec_low;          // total CPU time used by this task low bits
13     U32_t           time_total_exec_high;         // total CPU time used by this task high bits
14     U32_t           time_min_gap_starts;          // minimum time between job creations, can be reset
15     U32_t           time_min_gap_finish_start;    // minimum time between job ending and new job creation, can be reset
16     U32_t           time_last_start;              // time of most recent job creation
17     U32_t           time_last_finish;             // time of most recent job finish
18     U32_t           preempt_max;                  // maximum times any job was pre-empted, no roll-over, can be reset
19  };

This directive updates the task_info structure for the specified task ID. The elements of the structure are as follows:

  • jobs_current_max (maximum number of concurrent jobs for task since start)
  • jobs_total (number of times the task was started)
  • jobs_current_max (current number of jobs for the specified task)
  • time_max_delay (maximum time on ready queue before becoming active)
  • time_max_finish (maximum time to finish job execution after starting)
  • time_max_exec (maximum time spent executing (see note below))
  • time_total_exec_low (total CPU time used by this task low 32-bits)
  • time_total_exec_high (total CPU time used by this task high 32-bits)
  • time_min_gap_starts (minimum time between job creations)
  • time_min_gap_finish_start (minimum time between job ending and new job creation)
  • time_last_start (time of most recent job creation)
  • time_last_finish (time of most recent job finish)
  • preempt_max (maximum times any job was pre-empted)

Parameters in:

  • integer with task ID
  • pointer to task_info structure

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area inconsistent or not initialised)
  • INTERNAL_ERROR (dynamic data area inconsistent)
  • INVALID_ID (invalid task ID)
  • NOT_DEFINED (pointer to task_info structure is NULL)

Note:

The task execution times above include time spent in interrupt service routines (ISRs) while the task was executing.

Time spent in ISRs is expected to be a small proportion of the overall task execution time.

In the design of OCEOS (ref [SDD]) it was deemed too much of an overhead to accumulate time spent in ISRs.

Mutex Manager

Mutexes can only be created before scheduling begins and are only used after scheduling has started.

In OCEOS each mutex has a priority ceiling, the priority of the highest priority task that uses the mutex. OCEOS uses this to ensure that a task that needs a mutex only starts when the mutex is available. As a result problems such as unbounded priority inversion and deadlocks cannot occur in OCEOS.

A mutex has a unique mutex ID, an unsigned integer usually defined in the application header file as an enum MUTEX_NAME{} thus allowing a user-friendly name be associated with each mutex ID.

Mutex IDs are used as indices into both fixed and dynamic data arrays and must cover the range 0 to ((number of mutexes) -1). This is facilitated by using enum MUTEX_NAME{} to automatically assign names to IDs 0,1,....

The priority ceiling of a mutex is specified when the mutex is created and does not change subsequently. Priority ceilings are stored in the fixed data area as an array U8_t mutexCeilings[] indexed by the mutex ID.

Mutex dynamic data such as its current state and the job holding it are stored in the dynamic data area in an array of U32_t mutexDynamic[] also indexed by the mutex ID.

If a job holds more than one mutex, these should be acquired and released in a nested manner. To help ensure this is the case, a record is kept of the number of mutexes a job already holds when it acquires a mutex, and this is compared with the number of mutexes the job holds after the mutex is released. A warning is given if these are not the same.

oceos_mutex_create

1  enum DIRECTIVE_STATUS   oceos_mutex_create(
2     unsigned int,       // mutex ID, used as index, must be in range 0 to 62
3     U8_t                // mutex priority ceiling
4  );

This directive creates a mutex with a specified ID and priority ceiling. It should be called for each mutex up to NUMBER_OF_MUTEXES after application_init() and before oceos_init_finish() Note that an error will be returned by oceos_init_finish() if not all mutexes have been created.

Parameters in:

  • integer specifying mutex ID (0 to 62)
  • byte specifying priority ceiling (0 to 254)

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised)
  • INVALID_ID (invalid mutex ID)
  • INVALID_NAME (mute ID already created)
  • INVALID_NUMBER (invalid priority ceiling)
  • TOO_MANY (all mutexes already created)

oceos_mutex_wait

1  enum DIRECTIVE_STATUS   oceos_mutex_wait(
2     unsigned int        // mutex ID, must be in range 0 to 62
3  );

This directive, if successful, assigns a mutex to a job and replaces the current system priority ceiling with the ceiling specified when the mutex was created. If the mutex is already held by another task then an error status is returned.

Parameters in:

  • mutex ID (0 to 62)

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised)
  • INTERNAL_ERROR (problem with data area, inconsistent priority ceiling, mutex already held)
  • INVALID_ID (invalid mutex ID)
  • INTERNAL_ERROR (inconsistent tasks state)

oceos_mutex_signal

enum DIRECTIVE_STATUS   oceos_mutex_signal(
   unsigned int        // mutex ID, must be in range 0 to 62
);

This directive releases a mutex from a job, restores the previous system priority ceiling, and causes a reschedule. If the mutex is not currently held by the job then nothing is done and an appropriate status returned.

Parameters in:

  • mutex ID (0 to 62)

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised)
  • INTERNAL_ERROR (problem with data area, inconsistent priority ceiling, inconsistent tasks state, mutex held by a different job)
  • INVALID_ID (invalid mutex ID)

oceos_mutex_get_value

unsigned int            oceos_mutex_get_value(
   unsigned int        // mutex ID, must be in range 0 to 62
);

This directive returns the current value of a mutex. 0 indicates mutex held by a job, 1 indicates that mutex is not held by any job, any other value indicates an error.

Parameters in:

  • mutex ID (0 to 62)

Return values:

  • 0 (mutex held by a job)
  • 1 (mutex not held by a job)
  • INTERNAL_ERROR (data area nt initialised or inconsistent)
  • INVALID_ID (invalid mutex ID)

Semaphore Manager

A counting semaphore has an integer value that is always greater than or equal to zero. This value is set initially when the semaphore is created and modified by atomic wait() and signal() operations.

Each counting semaphore has a pending jobs queue of pending jobs waiting for the semaphore value to become greater than zero.

When a wait operation is performed the semaphore is decremented and the new value is returned, unless the semaphore is already zero in which case the value remains unchanged.

When the semaphore is signalled its value is incremented, and any jobs on its pending jobs queue are transferred to the ready queue and also removed from the timed jobs queue if present.

oceos_sem_create

typedef U8_t  sem_t;
/*
 * sem_t sem_id         Semaphore ID (from 0 to 62)
 * U16_t max_permits    Number of max permits allowed for this semaphore (1 to 4094)
 * U16_t count_permits  Starting number of permits available (1 to 4094)
 * U8_t pending_q_size  MAX Number of jobs on the q for this semaphore (1 to 254)
 * BOOLE_t use_timeout  Set to TRUE if semaphore is using timeout
 */
enum DIRECTIVE_STATUS   oceos_sem_create(sem_t, U16_t, U16_t, U8_t, BOOLE_t);

This directive creates a counting semaphore. It should be called after application_init() and before oceos_init_data_finish()

The number of semaphores created must equal the NUMBER_OF_SEMAPHORES definition in config.h.

Parameters in:

  • integer specifying Semaphore ID (from 0 to 62)
  • integer specifying the maximum number of permits allowed for this semaphore (1 to 4094)
  • integer specifying the starting number of permits available (0 to 4094)
  • integer specifying the maximum number of jobs on the q for this semaphore (1 to 254)
  • boolean specifying if semaphore is using timeout

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised or inconsistent)
  • INVALID_ID (invalid semaphore ID)
  • INVALID_NUMBER (invalid no of permits of max jobs)
  • TOO_MANY (permits exceeds max)
  • INVALID_SIZE (invalid ma no of jobs)

oceos_sem_signal

enum DIRECTIVE_STATUS oceos_sem_signal(sem_t);                // counting semaphore ID
/*
 * Job decrements the number of available permits for semaphore and return SUCCESSFUL
 * If number of available permits is zero, return UNSATISFIED and job should handle unsuccessful result
 */


This directive increments the permits value of the specified semaphore. Any jobs on its pending jobs queue are transferred to the ready queue and also removed from the timed jobs queue if present.

Parameters in:

  • integer specifying the semaphore ID (0 to 62)

Status values returned:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised, system state invalid for this operation)
  • INTERNAL_ERROR (data area inconsistent)
  • INVALID_ID (invalid semaphore ID)
  • TOO_MANY (maximum permits already reached, ready queue full)

oceos_sem_wait_continue

enum DIRECTIVE_STATUS   oceos_sem_wait_continue(sem_t);         // counting semaphore ID

This directive decrements the number of available permits for the specified semaphore. If the number of available permits is zero, then status UNSATISFIED is returned and the job should handle the unsuccessful result.

Parameters in:

  • integer specifying the semaphore ID (0 to 62)

Status values returned:

  • SUCCESSFUL
  • UNSATISFIED (permits value is already zero)
  • INCORRECT_STATE (data area not initialised, system state invalid for this operation)
  • INTERNAL_ERROR (data area inconsistent, job data inconsistent)
  • INVALID_ID (invalid semaphore ID)

oceos_sem_wait_restart

enum DIRECTIVE_STATUS   oceos_sem_wait_restart(sem_t);          // semaphore ID

This directive decrements the number of available permits for the specified semaphore. If the number of available permits is zero the job is terminated and restarted when the number of permits has been incremented.

Jobs restarted will lose any data stored in variables on the stack so it may be necessary for the application to store such data elsewhere so that it is available when the job is restarted.

Parameters in:

  • integer specifying the semaphore ID (0 to 62)

Status values returned:

  • SUCCESSFUL
  • UNSATISFIED (timeout expired before permits were incremented)
  • INCORRECT_STATE (data area not initialised, system state invalid for this operation)
  • INTERNAL_ERROR (data area inconsistent, job data inconsistent)
  • INVALID_ID (invalid semaphore ID)
  • TOO_MANY (job pending queue full)

oceos_sem_wait_restart_timeout

enum DIRECTIVE_STATUS   oceos_sem_wait_restart_timeout(sem_t, U64_t);          // semaphore ID and timeout


This directive decrements the number of available permits for the specified semaphore. If the number of available permits is zero the job is terminated and restarted when the number of permits has been incremented. If a timeout is specified and subsequently expires, the job is restarted and status UNSATISFIED is returned.

Jobs restarted will lose any data stored in variables on the stack so it may be necessary for the application to store such data elsewhere so that it is available when the job is restarted.

Parameters in:

  • integer specifying the semaphore ID (0 to 62)
  • integer (64-bit) specifying the timeout in microseconds

Status values returned:

  • SUCCESSFUL
  • UNSATISFIED (timeout expired before permits were incremented)
  • INCORRECT_STATE (data area not initialised, system state invalid for this operation)
  • INTERNAL_ERROR (data area inconsistent, job data inconsistent)
  • INVALID_ID (invalid semaphore ID)
  • TOO_MANY (job pending queue full)

oceos_sem_get_value

int oceos_sem_get_value(sem_t);             // counting semaphore ID
/*
 * Get Number of Jobs on Pending Q
 * sem_t sem_id
 */

This directive returns the permits value of the specified semaphore.

Parameters in:

  • integer specifying the semaphore ID (0 to 62)

Values returned:

  • integer with the current number of available permits
  • -1 (data area not initialised, OCEOS not started, invalid semaphore ID)

oceos_sem_penq_get_size

int oceos_sem_penq_get_size(sem_t sem_id);
/*
 * Reset Semaphore
 */

This directive returns the number of jobs pending for this semaphore.

Parameters in:

  • integer specifying the semaphore ID (0 to 62)

Values returned:

  • integer with the current number of jobs on the pending queue
  • -1 (data area not initialised, OCEOS not started, invalid semaphore ID)

oceos_sem_reset

enum DIRECTIVE_STATUS   oceos_sem_reset (sem_t sem_id);

This directive returns the queue to its initial state. It removes all jobs from the pending queue and resets the number of permits to the initial value specified when the semaphore was created.

Parameters in:

  • integer specifying the semaphore ID (0 to 62)

Values returned:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised, system state invalid for this operation)
  • INTERNAL_ERROR (data area inconsistent, job data inconsistent)
  • INVALID_ID (invalid semaphore ID)

Data Queue Manager

Data queues are FIFO (first-in-first-out) queues of non-null void pointers ordered by arrival time. Each queue has an associated pending jobs queue, and two associated atomic operations, read() and write(). A size() operation gives the number of pointers on the queue, and the create() operation sets the maximum size of the queue.

oceos_dataq_create

typedef U8_t dataq_t
/*
 * dataq_t dataq_id 
 * U16_t dataq_size 
 * U16_t pen_q_size 
 * BOOLE_t roll_over
 * BOOLE_t use_timeout
 */
enum DIRECTIVE_STATUS   oceos_dataq_create(dataq_t, U16_t, U16_t, BOOLE_t, BOOLE_t);     // data queue ID and maximum queue size


This directive creates the specified data queue with specified queue size and pending jobs queue size. Roll-over on the queue can also be enabled/disabled. Roll-over means that if the queue is full new entries will overwrite the oldest ones on the queue.

Parameters in:

  • integer specifying the data queue to be created (0 to 62)
  • integer specifying the maximum number of entries of the data queue (1 to 255)
  • integer specifying the maximum number of pending jobs (1 to 255)
  • boolean to enable/disable roll-over (0 to disable, 1 to enable roll-over)
  • boolean specifying if dataq is using a timeout

Status returned:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised or inconsistent)
  • INVALID_ID (invalid data queue ID)
  • TOO_MANY (data queue ID too large)
  • INVALID_NAME (data queue already created)
  • INVALID_NUMBER (invalid data queue size)
  • INVALID_SIZE (invalid pending queue size)

oceos_dataq_write

enum DIRECTIVE_STATUS   oceos_dataq_write(dataq_t dataq_id, void* data);    // data queue ID and non-null pointer to data
/*
 * dataq_t dataq_id
 */

Directive to add a pointer to the end of the data queue.

Parameters in:

  • integer specifying data queue ID
  • pointer to data

Status returned:

  • SUCCESSFUL
  • INCORRECT_STATE (data area not initialised or inconsistent)
  • INVALID_ID (invalid data queue ID)
  • INTERNAL_ERROR (invalid job state)
  • TOO_MANY (data queue full, pending queue full)

oceos_dataq_read_continue

void*  oceos_dataq_read_continue(dataq_t dataq_id);

This directive returns the oldest pointer from the data queue. If the queue is empty NULL is returned.

Parameters in:

  • integer specifying data queue (0 to 62)

Status returns:

  • data pointer to oldest entry on the queue
  • NULL (queue is empty, data area not initialised, invalid data queue ID)

oceos_dataq_read_restart

data queue ID

/*
 * dataq_t dataq_id 
 */
void* oceos_dataq_read_restart(dataq_t dataq_id);      // data queue ID

This directive returns the oldest pointer from the data queue. If the queue is empty the task is put on the pending queue to be restarted when new data is available on the queue.

Jobs restarted will lose any data stored in variables on the stack so it may be necessary for the application to store such data elsewhere so that it is available when the job is restarted.

Parameters in:

  • integer specifying data queue ID (0 to 62)

Return values:

  • data pointer to oldest entry on the queue
  • NULL (queue is empty, data area not initialised, invalid data queue ID)

oceos_dataq_read_restart_timeout

data queue ID

/*
 * dataq_t dataq_id 
 * U64_t timeout, if 0 => ignored
 */
void* oceos_dataq_read_restart_timeout(dataq_t dataq_id, U64_t timeout);      // data queue ID

This directive returns the oldest pointer from the data queue. If the queue is empty the task is put on the pending queue to be restarted when new data is available on the queue. A 64-bit timeout in microseconds can be set, after which the task will be removed from the pending queue and NULL will be returned. If a timeout value of zero is passed then the job will remain on the pending queue until a data queue pointer is available.

Jobs restarted will lose any data stored in variables on the stack so it may be necessary for the application to store such data elsewhere so that it is available when the job is restarted.

Parameters in:

  • integer specifying data queue ID (0 to 62)

Return values:

  • data pointer to oldest entry on the queue
  • NULL (queue is empty, data area not initialised, invalid data queue ID)

oceos_dataq_get_size

/*
 * Get Number of elements in Data Q
 * dataq_t dataq_id
 */
int oceos_dataq_get_size(dataq_t);                // data queue ID

This directive returns the current number of pointers on the data queue. A negative return value indicates an error.

Parameters in:

  • integer specifying data queue ID (0 to 62)

Return values:

  • integer whose value is the current number of pointers on the data queue
  • -1 data area not initialised or inconsistent, invalid data queue ID

oceos_dataq_penq_get_size

/*
 * Get Number of Jobs on Pending Q
 * dataq_t dataq_id
 */
int  oceos_dataq_penq_get_size(dataq_t dataq_id);

This directive returns the current number of jobs on the pending queue for the specified data queue ID. A negative value indicates an error.

Parameters in:

  • integer specifying data queue ID (0 to 62)

Return values:

  • integer whose value is the current number of pointers on the data queue
  • -1 data area not initialised or inconsistent, invalid data queue ID

oceos_dataq_clear

/*
 * Reset Data Q
 * Involves reseting data q and freeing up jobs from pending Q
 */
enum DIRECTIVE_STATUS  oceos_dataq_clear(dataq_t dataq_id);

This directive resets the specified data queue deleting all entries and clearing jobs from pending queue.

Parameters in:

  • integer specifying data queue ID (0 to 62)

Status returns:

  • SUCCESSFUL
  • INCORRECT_STATE (memory area not initialised or inconsistent)
  • INVALID_ID (invalid data queue ID)
  • INTERNAL_ERROR (inconsistent pending job data)

Timer Action Manager

This timed action manager provides directives to perform actions at a precise time in the future. The currently supported action types are:

  • Timed output to memory address or register
  • Start job which allows a job to be started at a future time (oceos_task_timed_start)
  • Timeout for data queue pending job (internal)
  • Timeout for semaphore queue pending job (internal)

Action Types

The action types implemented in OCEOS are listed in the enumerated type below. The action types required by the user for oceos_timed_output_add(..) are WRITE_ACTION and RMW_ACTION. Other action types are provided for information but are not available to the user.

enum ACTION_TYPE {
 INVALID_ACION,
 START_JOB_ACTION,         // The job was created with start in future. Put job on ready Q
 DATA_Q_WAIT_ACTION,       // The job is on data_Q pending Q with timeout
 SEM_Q_WAIT_ACTION,        // The job is on semaphore_Q pending Q with timeout
 WRITE_ACTION,             // Write value to address
 RMW_ACTION,               // Read-modify-write Action
};
/*
 * Add job to timed action Q
 * return -1 if fail
 */

oceos_timed_jobs_number

int   oceos_timed_jobs_number();          // get number of jobs on timed actions queue

This directive returns the number of queued jobs setup with oceos_task_timed_start()

Parameters in: None Values returned:

  • integer with the number of jobs in the queue
  • -1 (timed actions not initialised)

oceos_timed_jobs_remove

int  oceos_timed_jobs_remove(       // remove all jobs of this task from timed actions queue
                           U32_t);                         // task ID

This directive removes the specified job from the timed jobs queue. The specified job ID is usually one returned by a successful call of oceos_task_timed_start()

Parameters in:

  • integer specifying timed job ID

Status returns:

  • NOT_CONFIGURED (timed actions not initialised)
  • INVALID_ID (invalid timed job ID, invalid action ID, not found on readyQ)
  • UNSATISFIED (problem stopping timed timer)
  • INCORRECT_STATE (issue removing from timed_q)
  • EMPTY (timed queue is empty)
  • OBJECT_WAS_DELETED (timed job was already removed)

oceos_timed_jobs_reset

int  oceos_timed_jobs_reset();           // clear all timed jobs from timed actions queue

This directive resets the timer jobs queue and removes all timed job actions.

Parameters in: None Status returns:

  • SUCCESSFUL
  • NOT_CONFIGURED (timed jobs system not initialised)
  • UNSATISFIED (failed to stop timer)
  • INCORRECT_STATE (timed q is empty)

oceos_timed_output_add

int  oceos_timed_output_add(             // add output to timed action queue, return action ID
                           const enum ACTION_TYPE,         // Action type
                           const U64_t,                    // system time at which action should be done
                           const U32_t,                    // forward tolerance for time
                           const U32_t,                    // backward tolerance for time
                           const adrs_t,                   // output address
                           const U32_t,                    // output value
                           const U32_t,                    // output mask
);

This directive places a specified action type (write or rmw) on the timed output queue to be executed at the specified time in the future. Forward and backward tolerances are specified, and the action will be carried out within these tolerances.

The target address to be written is supplied along with an output value. The mask is supplied for rmw (read, modify, write) operations on specified bits in the target address. Only the bits in value specified by the mask will be written to the address.

The only output type currently supported is WORD32.>Other types may be supported in the future. The action ID is returned, if successful.

Parameters in:

  • integer action types WRITE_ACTION and RMW_ACTION are supported. WRITE_ACTION causes output value to be written to output address. RMW action causes only the bits in value specified by the mask to be written to the output address.
  • 64-bit integer with the scheduled future time at which the action is to be performed.
  • Integer with forward tolerance in microseconds on the scheduled action time. The action will never be performed in advance of the forward tolerance of the scheduled time.
  • Integer with backward tolerance in microseconds after the scheduled action time. The action will never be performed later than the backward tolerance after the scheduled time.
  • Address (32-bit) of the output location. This is the location to which the value will be written.
  • Integer (32-bit) with the value to be written to the output address.
  • Integer (32-bit) with the output mask used in the read-modify-write operation.

Status returns:

  • Integer with action ID if successful
  • -1 (unsuccessful, timed outputs not initialised, action queue full, failed to stop timer during add operation, failed to execute output action if the time was already within tolerance)

oceos_timed_output_number

int  oceos_timed_output_number();        // get number of timed outputs on timed actions queue

This directive returns the number of timed outputs on the timed actions queue.

Parameters in: None Values returned:

  • integer with number of timed outputs on the action queue
  • -1 (timed actions not initialised)

oceos_timed_output_remove

enum DIRECTIVE_STATUS   oceos_timed_output_remove(          // remove this output from action queue
                           const U32_t                     // Index in action array, returned when action was created
                           );

This directive removes the specified action ID from the timed outputs queue.

Parameters in:

  • integer with action ID

Status returns:

  • SUCCESSFUL
  • NOT_CONFIGURED (timed action system not initialised)
  • INVALID_ID (action ID invalid)
  • OBJECT_WAS_DELETED (action ID was already deleted)
  • UNSATISFIED (failed to stop timer)
  • INCORRECT_STATE (issue removing from timed_q)
  • EMPTY (timed queue is empty)

oceos_timed_output_reset

int oceos_timed_output_reset();         // clear all timed outputs from action queue

This directive clears all timed outputs from the action queue. Parameters in: None Status returns:

  • SUCCESSFUL
  • NOT_CONFIGURED (timed actions system not initialised)
  • UNSATISFIED (failed to stop timer)
  • INCORRECT_STATE (timed q is empty)

Interrupt Manager Directives

Interrupt management is done using BCC calls.

OCEOS includes two directives that wrap the corresponding BCC calls so that OCEOS can ensure the application does not use interrupts used by OCEOS

oceos_interrupt_handle_register (SPARC)

/*
 * OCEOS Wrapper around bcc_isr_register_node(struct bcc_isr_node *isr_node)
 * in order to check if user not interfering with OCEOS internal configurations
 * set the function to be used for the given interrupt
 * @param struct bcc_isr_node
 */
enum DIRECTIVE_STATUS   oceos_interrupt_handle_register(
   struct bcc_isr_node *isr_node
);

Directive to register an interrupt using the standard BCC interrupt functions (see section 5.9 of BCC manual). Extracts from bcc.h and OCEOS example below.

Parameters passed are:

  • Pointer to bcc_isr_node structure with the following elements initialised:
    • Source (interrupt source number)
    • Handler (interrupt handler address)
    • Pointer passed to handler

Return Values are:

  • SUCCESSFUL (Interrupt handler successfully registered)
  • NOT_CONFIGURED (Interrupt Manager not initialised)
  • INVALID_NUMBER (Interrupt source number is used by OCEOS)
  • INTERNAL_ERROR (BCC failed to register isr_node to source number)

struct bcc_isr_node:

/*
 * Interrupt node for registering interrupt handler
 *
 * This structure is used for registering interrupt handlers with
 * bcc_isr_register_node() and bcc_isr_unregister_node(). An application shall
 * not use it if bcc_isr_register() and bcc_isr_unregister() is used.
 */
struct bcc_isr_node {
       /* BCC private data (do not touch) */
       void *__private;
       /* Interrupt source number */
       int source;
       /* User ISR handler */
       void (*handler)(
               void *arg,
               int source
       );
       /* Passed as parameter to handler */
       void *arg;
};

OCEOS example :

/*
 * Register handler
 */
void fun0(void * ptr){
   node.source = 12;
   node.handler = start_task;
   node.arg = ptr;
   ret = oceos_interrupt_handle_register(&node);
   if (SUCCESSFUL != ret) {
       printf("ERROR :: Failed to add ISR handler\n");
   }
   bcc_int_unmask(12);
   exit(1);
} 
/*
 * Handler for interrupt
 */
void start_task(void *arg, int source){
 oceos_task_start(t_harry,arg);
 oceos_task_start(t_dick,arg);

}


bcc_isr_register_node:

/*
 * Register interrupt handler, non-allocating
 *
 * This function is similar to bcc_isr_register() with the difference that the
 * user is responsible for memory management. It will never call malloc().
 * Instead the caller has to provide a pointer to a preallocated and
 * initialized ISR node of type struct bcc_isr node.
 *
 * The memory pointed to by isr_node shall be considered owned exclusively by
 * the run-time between the call to bcc_isr_register_node() and a future
 * bcc_isr_unregister_node(). It means that the memory must be available for
 * this time and must not be modified by the application. The memory pointed to
 * by isr_node must be writable.
 *
 * This function should be used to install interrupt handlers in applications
 * which want full control over memory allocation.
 *
 * isr_node: Pointer to user initialized ISR node. The fields source, handler
 *           and optionally the arg shall be initialized by the caller.
 *
 * return:
 * - BCC_OK: Handler installed successfully.
 * - BCC_FAIL: Failed to install handler.
 */
int bcc_isr_register_node(
       struct bcc_isr_node *isr_node
);

oceos_interrupt_handle_unregister (SPARC)

/*
 * OCEOS Wrapper around bcc_isr_unregister_node(struct bcc_isr_node *isr_node)
 * in order to check if user not interfering with OCEOS internal configurations
 * set the function to be used for the given interrupt
 * @param struct bcc_isr_node
 */
enum DIRECTIVE_STATUS   oceos_interrupt_handle_unregister(
   struct bcc_isr_node *isr_node
);

Directive to unregister an interrupt using the standard BCC interrupt function (see section 5.9 of BCC manual). Extracts from bcc.h and OCEOS example below.

Parameters passed are:

  • Pointer to bcc_isr_node structure with the following elements initialised:
    • Source (interrupt source number)
    • Handler (interrupt handler address)
    • Pointer passed to handler

Return Values are:

  • SUCCESSFUL (Interrupt handler successfully registered)
  • NOT_CONFIGURED (Interrupt Manager not initialised)
  • INVALID_NUMBER (Interrupt source number is used by OCEOS)
  • INTERNAL_ERROR (BCC failed to unregister isr_node to source number)

bcc_isr_unregister_node :

/*
 * Unregister interrupt handler, non-allocating
 *
 * This function is similar to bcc_isr_unregister() with the difference that
 * the user is responsible for memory management.  It is only allowed to
 * unregister an interrupt handler which has previously been registered with
 * bcc_isr_register_node().
 *
 * isr_node: Same as input parameter to bcc_isr_register_node().
 *
 * return:
 * - BCC_OK: Handler successfully unregistered.
 * - BCC_FAIL: Failed to unregister handler.
 */
int bcc_isr_unregister_node(
       const struct bcc_isr_node *isr_node
);

Timing Manager directives

oceos_time_sys_get64

// get the current system time (64-bit)
U64_t  oceos_time_sys_get64();

Directive to return the OCEOS system time as a 64-bit number. The OCEOS system time is the number of microseconds since OCEOS was started.

oceos_time_sys_get32

// get the current system time (low 32 bits of 64 bit value)
U32_t oceos_time_sys_get32();

Directive to return the low 32-bits of the OCEOS system time. The lower 32 bits of OCEOS system time (microseconds) rolls over every 71 minutes.

Logging Manager Directives

oceos_log_add_entry

// add a log entry (32-bit time is included automatically)
 enum DIRECTIVE_STATUS   oceos_log_add_entry(
   enum LOG_ENTRY_TYPE,// 8 bits
   const unsigned int  // information (24 bits)
);

Directive to add an entry to the log and add a 32-bit time tamp. Each log entry consists of a type (8-bit), and information (24-bits).

OCEOS log types are:

  • NOT_VALID_ENTRY (value 0 not allowed)
  • ALL_OK (System OK, information value provides more information)
  • NO_JOB_FREE (TBD)
  • READYQ_FULL (The ready queue is full)
  • MUTEX_WAIT_REPEAT (TBD)
  • MUTEX_SIGNAL_REPEAT (TBD)

Status returns are:

  • SUCCESSFUL (entry successfully added to log)
  • NOT_CONFIGURED (log data area not initialised or corrupt)
  • INVALID_NUMBER (type or information is invalid)
  • INCORRECT_STATE (log read or write pointer is inconsistent)

oceos_log_remove_entry

// read and remove the oldest unread log entry
enum DIRECTIVE_STATUS   oceos_log_remove_entry(
   struct log_entry * const
);

Directive to remove the oldest unread log entry and copy its contents to the pointer passed. If the log is not empty use the entry at the read index to update the value at the output pointer, returning SUCCESSFUL.

If the log is empty, set the value at the output pointer to NOT_VALID_ENTRY and return UNSATISFIED.

Parameters passed are:

  • Pointer to struct log_entry here removed entry will be returned.

Status returns are:

  • SUCCESSFUL (entry successfully removed and returned in pointer)
  • NOT_CONFIGURED (log data area not initialised or corrupt)
  • INCORRECT_STATE (log read or write pointer is inconsistent)
  • UNSATISFIED (log is empty)

oceos_log_get_indexed_entry

// read the log entry at the given index
enum DIRECTIVE_STATUS   oceos_log_get_indexed_entry(
   const unsigned int,
   struct log_entry * const
);

This directive returns the entry at the specified position in the log. The entry is not removed and the log and log indices are not changed.

It is intended to allow the log be examined for example after reset. If the index is out of range, set the value at the output pointer to NOT_VALID_ENTRY and return UNSATISFIED.

Parameters passed are:

  • Integer containing the log entry to be read
  • Pointer (struct log_entry type) where entry address will be copied

Status returns are:

  • SUCCESSFUL (entry successfully retrieved and pointer returned)
  • NOT_CONFIGURED (log data area not initialised or corrupt)
  • INVALID_NUMBER (invalid entry number passed)
  • INVALID_ADDRESS (invalid pointer address passed)

oceos_log_reset

// clear all log entries and reset to empty
enum DIRECTIVE_STATUS oceos_log_reset();

Set all log entries to NOT_VALID_ENTRY and the log to empty, with the read and write indices set to 0 and function called flag 0.

The log is empty if the index of the next write is the same as the index of the next read.

It does not affect the system status variable

Note that Traps should be disabled before calling.

No parameters.

Status returns are:

  • SUCCESSFUL (log successfully reset)
  • NOT_CONFIGURED (log data area not initialised or corrupt)
  • UNSATISFIED (unexpected data corruption)

oceos_log_get_size

// get the number of log entries
unsigned int oceos_log_get_size();

This directive returns the number of entries in the log according to the current values of the read and write indices. The value will be zero if the current values of the read and write indices are the same.

No parameters.

Status returns are:

  • LOG_INVALID_SIZE (log data area corrupt)
  • NOT_CONFIGURED (log data area not initialised)

Protection Manager

The two memory protection units in the GR716 can be used to restrict write access to memory segments by selected system bus masters (MEMPROT0) and by selected DMA bus masters (MEMPROT1).

Each unit allows four memory segments be defined for write protection. In addition MEMPROT0 can restrict access via the system bus AHB to APB bridges by selected system bus masters and the four DMA units to selected APB bus devices.

MEMPROT0 can protect four segments in the range 0x40000000 to 0x4fffffff or the range 0x80000000 to 0x8041ffff, as well as specific APB units.

MEMPROT1 can protect four segments in the range 0x30000000 to 0x31000000 and restrict the access of any master on the DMA bus.

An attempt to write to a protected area causes an AMBA ERROR response which can cause an interrupt from MEMSCRUB or from one of the AHBSTAT units.

The memory protection manager's directives relate to the EDAC and memory protection features of the GR716.

Support for the external memory scrubber (MEMSCRUB) and the two memory protection units (MEMPROT0/1) is available also from the GR716 BSP. Cobham Gaisler documentation on this support can be found in the GR716 board support package documentation in BCC.pdf, Section 7.25 for MEMSCRUB, Section 7.24 for MEMPROT0/1. Support for the internal memory EDAC and scrubbing and for external memory EDAC is not provided by the GR716 BSP.

The directives here access hardware registers and do not use the BSP.

The internal instruction RAM and data RAM of the GR716 each has its own EDAC and scrubber support internal to the GR716. The internal scrubbers use one 32-bit word per burst and an appropriate number of cycles between bursts should be specified for each scrubber. The scrubbers can be individually configured to cause an interrupt when an uncorrectable error occurs and if this is done an appropriate handler for Interrupt Line 63, default Int 19, should be set up in advance.

The GR716 internal RAM can also be protected from external write accesses by the memory protection unit on the DMA bus (MEMPROT1). The external RAM memory controllers (FTMCTRL0/1) provide EDAC support, with scrubbing provided by the MEMSCRUB unit on the main system bus.

As the main external memory (FTMCTRL0) uses an 8-bit data bus the high 20% of this RAM is used for the EDAC check bits and cannot be used for data when EDAC is enabled.

(The GR716-MINI board has 2 MiB of main external RAM accessed via FTMCTRL0 and with EDAC enabled only addresses 0x40000000 to 0x40199997 can be used for data, the check bits are stored at addresses 0x4019999a to 0x401fffff, and addresses 0x40199998 and 0x40199999 cannot be used).

When using MEMSCRUB this restriction must be taken into account in setting the high address of the scrubbing range. (The GR716-MINI external RAM scrubber range is 0x40000000 to 0x40199997.)

On the GR716 the MEMSCRUB unit works in bursts of two 32-bit words, and an appropriate number of cycles between bursts should be specified.

MEMSCRUB can be configured to cause an interrupt due to correctable or uncorrectable error counts exceeding a pre-set threshold, and if this is done an appropriate handler for Interrupt Line 63, default Int 19, should be set up in advance (the same interrupt as for the internal scrubbers).

Two memory protection (MEMPROT) units, one for the main system bus, one for the DMA bus, each allow up to four memory segments be defined and can be configured to prevent writes by a specific bus master to a segment.

MEMPROT0 controls memory writes via the main memory controller FTMCTRL0 by selected bus masters for addresses 0x40000000 to 0x4fffffff.

N.B. If EDAC is in use with FTMCTRL0 the memory area to which access is allowed must include the EDAC check bits as well as the data.

MEMPROT0 can also restrict writes to APB bus addresses in the range 0x80000000 to 0x8041ffff and to specific devices on the APB bus.

MEMPROT1 controls memory writes to the GR716 internal RAM from the DMA bus for addresses in the range 0x30000000 to 0x31ffffff.

Write access permission can be specified on an individual basis for each bus master on the DMA bus.

MEMPROT0/1 do not themselves cause a trap/interrupt when an attempt is made to write to protected memory, but as well as preventing the write return an AMBA ERROR response which can give rise to an interrupt from AHBSTAT1/0 respectively. If this used an appropriate error handler for Interrupt Line 63, default Int 19, should be set up in advance (the same interrupt as for the scrubbers).

N.B. The appropriate clock gating must be set for it to be possible to use FTMCTRL, MEMSCRUB, or the MEMPROT units, and the appropriate GPIO pin selection also configured. This can be done using the GR716 BSP.

The MEMPROT units themselves must be configured to allow access to the registers of the other devices.

A handler for Interrupt Line 63, default Int 19, should be set up in advance if required. The directives here assume this has already been done.

N.B. Where a register contains read-only bits the values set for those bits in the input data used with the directives below must be 0, if not the input will be treated as invalid.

oceos_protect_internal_EDAC_set (SPARC)

/*
 * Internal EDAC control and scrubbing
 */
//  sets the internal EDAC control registers
enum DIRECTIVE_STATUS   oceos_protect_internal_EDAC_set(
   const unsigned int, // internal data memory configuration
   const unsigned int  // internal instruction memory configuration
);

This directive sets the configuration registers for the GR716 internal data RAM and internal instruction RAM. These registers determine various characteristics, including whether EDAC is enabled. See GR716.h for further information. As well as setting the registers this routine reads them back to check that they have been set as requested.

Parameters passed are:

  • data_config (32-bit integer with configuration for internal data memory)
  • inst_config (32-bit integer with configuration for internal instruction memory)

(Note that Input values for read-only bit fields in the registers must be 0)

Status returns are:

  • SUCCESSFUL (registers successfully loaded with configuration values)
  • INVALID_NUMBER (if problem with input parameter)
  • INVALID_ID (problem loading registers)

oceos_protect_internal_scrubber_set (SPARC)

// sets the internal memory scrubber control and interrupt
enum DIRECTIVE_STATUS   oceos_protect_internal_scrubber_set(
   const unsigned int, // internal data scrubber wash data
   const unsigned int, // internal data scrubber control
   const unsigned int, // internal data scrubber configuration
   const unsigned int, // internal instruction scrubber wash data
   const unsigned int, // internal instruction  scrubber control
   const unsigned int  // internal instruction  scrubber configuration
);

This directive sets the configuration registers for the GR716 internal data RAM and internal instruction RAM scrubbers. These registers determine various scrubbing characteristics, including enabling the scrubbers. See GR716.h for further information. As well as setting the registers this routine reads them back to check that they have been set as requested.

Parameters in:

  • Data scrubber wash data (integer)
  • Data scrubber control (integer)
  • Data scrubber configuration (integer)
  • Instruction scrubber wash data (integer)
  • Instruction scrubber control (integer)
  • Instruction scrubber configuration (integer)

Note that input bits for read-only fields in the registers should be 0.

Status returns are:

  • SUCCESSFUL (all O.K.)
  • INVALID_NUMBER (problem with input parameter)
  • INVALID_ID (other problem)

oceos_protect_internal_status_get (SPARC)

// returns the internal EDAC and scrubber status
unsigned int            oceos_protect_internal_status_get(
   const unsigned int  // 0 => data memory, 1 => instruction memory
);

This directive returns the current value of either the data or the instruction configuration register.

Parameters in:

  • Memory type (integer, 0 for data memory, 1 for instruction memory)

Values returned

  • unsigned int (value of configuration register, 0 if a problem)

oceos_protect_external_EDAC_set (SPARC)

/*
 * External RAM EDAC control and scrubbing
 */
// sets the main external RAM EDAC control register.
enum DIRECTIVE_STATUS   oceos_protect_external_EDAC_set(
   const unsigned int  // EDAC control (FTMCTRL.MCFG3)
);

This directive sets the EDAC control register (MCFG3) in the external memory controller. The value input is checked to ensure all read-only bits are 0. The value is not read back for checking as some fields can change. The external EDAC present bit (ME) is checked to make sure it is set.

Parameters in:

  • Value to be set (integer, only low order 12 bits are used)
/*
 *  Return:
 *      DIRECTIVE_STATUS    SUCCESSFUL      if no problem detected
 *                          INVALID_NUMBER  if high order 20 bits not 0
 *                          INCORRECT_STATE if EDAC not present
 */

oceos_protect_external_status_get (SPARC)

// returns the main external RAM EDAC status register (FTMCTRL0.)
unsigned int oceos_protect_external_status_get();

This directive returns the contents of the EDAC control register (MCFG3) in the external memory controller (FTMCTRL).

Parameters in: None

Return: unsigned int with value of FTMCTRL.MCFG3 (0 if clock gated off)

oceos_protect_external_scrubber_set (SPARC)

// set the parameters for the external scrubber unit MEMSCRUB
enum DIRECTIVE_STATUS   oceos_protect_external_scrubber_set(
   const unsigned int, // configuration
   const unsigned int, // error threshold
   const unsigned int, // wash data (used twice)
   const unsigned int, // first range low address
   const unsigned int, // first range high address
   const unsigned int, // second range start address
   const unsigned int  // second range end address
);

This directive sets the parameters for the external memory scrubber unit MEMSCRUB. BCC provides more extensive driver support for MEMSCRUB than is given here. This may be used as an alternative (cf BCC.pdf Section 25).

Notes

  • On the GR716 the burst length used by MEMSCRUB is 2
  • The same wash data is written to MEMSCRUB twice to fill its buffer
  • Values input are checked to ensure all read-only bits are 0
  • The memory controller should have EDAC enabled (this is not checked)

Parameters in:

  • config MEMSCRUB.CONFIG - configuration
  • error_thresh MEMSCRUB.ERROR - interrupt conditions
  • wash_data MEMSCRUB.INIT - wash data (used twice)
  • range1_low MEMSCRUB.RANGEL - range 1 start address
  • range1_high MEMSCRUB.RANGEH - range 2 end address
  • range2_low MEMSCRUB.RANGEL2 - range 2 start address
  • range2_high MEMSCRUB.RANGEH2 - range 2 end address

Status returns:

  • SUCCESSFUL if no problem detected
  • INVALID_NUMBER if any read-only bit is set

oceos_protect_external_scrubber_status_get (SPARC)

// get the status of the external scrubber MEMSCRUB
unsigned int oceos_protect_external_scrubber_status_get();

This directive returns the external memory scrubber status register MEMSCRUB.STAT as required to re-enable interrupts on task completion.

Parameters in: None

Returns: unsigned int with MEMSCRUB.STAT value

oceos_protect_external_scrubber_position_get (SPARC)

// returns the current value of the MEMSCRUB position register
unsigned int oceos_protect_external_scrubber_position_get();

This directive returns the current value of the position register MEMSCRUB.POS.

Parameters in: None

Returns: unsigned int with MEMSCRUB.POS value

oceos_protect_unit_control (SPARC)

// Enable or disable protection by a MEMPROT unit
enum DIRECTIVE_STATUS   oceos_protect_unit_control(
   const unsigned int, // MEMPROT select, 0 => main bus, 1 => DMA bus
   const unsigned int  // 1 => enable protection, 0 => disable
);

This directive allows enable or disable protection by a MEMPROT unit.

Parameters in:

  • integer indicating which MEMPROT unit (0 for main bus, 1 for DMA bus)
  • integer indicating enable or disable (0 is disable, 1 is enable)

Status returned:

  • SUCCESSFUL
  • INCORRECT_STATE (problem enabling clock gating for device)
  • UNSATISFIED (problem updating MEMPROT registers)

oceos_protect_segment_define (SPARC)

// Define the range of addresses for a segment of a MEMPROT unit
enum DIRECTIVE_STATUS   oceos_protect_segment_define(
   const unsigned int, // MEMPROT select, 0 => main bus, 1 => DMA bus
   const unsigned int, // segment number 0 to 3
   const unsigned int, // start address
   const unsigned int  // end address
);

This directive defines the range of addresses for a segment of a MEMPROT unit.

Parameters in:

  • integer to select MEMPROT, 0 => main bus, 1 => DMA bus
  • integer with segment number (0-3)
  • integer with start address
  • integer with end address

Status returns:

  • SUCCESSFUL
  • INVALID_NUMBER (error in input parameters)
  • INCORRECT_STATE (error enabling clock gating)
  • UNSATISFIED (values red back did not match values written)

oceos_protect_memprot0_segment_control (SPARC)

// Control access to MEMPROT0 segments from masters on main bus
enum DIRECTIVE_STATUS   oceos_protect_memprot0_segment_control(
   const unsigned int, // segment number 0 to 3
   const unsigned int, // 1 => allow LEON3 access to segment, 0 => deny
   const unsigned int, // 1 => allow MEMSCRUB access to segment, 0 => deny
   const unsigned int, // 1 => allow DMA bus access to segment, 0 => deny
   const unsigned int  // 1 => enable segment protection, 0 => disable
);

This directive allows control of access to MEMPROT0 segments from masters on main bus.

Parameters in:

  • integer specifying segment (0 to 3)
  • integer specifying leon access (1 allows LEON3 access, 0 denies access
  • integer specifying MEMSCRUB access to segment (1=allow, 0=deny)
  • integer specifying DMA bus access to segment (1=allow, 0=deny)
  • integer specifying segment protection enabled or disabled (1=enable, 0=disable)

Status returns:

  • SUCCESSFUL
  • INVALID_NUMBER (error in input parameters)
  • INCORRECT_STATE (error enabling clock gating for device)
  • UNSATISFIED (did not read back correctly)

oceos_protect_memprot1_segment_control (SPARC)

// Control access to MEMPROT1 segments from masters on DMA bus
enum DIRECTIVE_STATUS   oceos_protect_memprot1_segment_control(
   const unsigned int, // segment number 0 to 3
   const unsigned int, // DMA bus masters (low 16 bits)
   const unsigned int  // 1  => enable segment protection, 0 => disable
);

This directive allows control of access to MEMPROT1 segments from masters on main bus.

Parameters in:

  • integer specifying segment (0 to 3)
  • integer specifying DMA bus masters (low 16 bits)
  • integer specifying segment protection enabled or disabled (1=enable, 0=disable)

Status returns:

  • SUCCESSFUL
  • INVALID_NUMBER (error in input parameters)
  • INCORRECT_STATE (error enabling clock gating for device)
  • UNSATISFIED (did not read back correctly)

oceos_protect_APB_devices (SPARC)

// Control access to APB devices by main bus masters and DMA units
enum DIRECTIVE_STATUS   oceos_protect_APB_devices(
   const unsigned int, // APB bus  (0,1,2,3)
   const unsigned int, // CPU (top 16) and Bridge (low 16) accessible devices
   const unsigned int, // MEMSCRUB (low 16) accessible devices
   const unsigned int, // DMA0 (top 16) and DMA1 (low 16) accessible devices
   const unsigned int  // DMA2 (top 16) and DMA3 (low 16) accessible devices
);

This directive control access to APB devices by main bus masters and DMA units

Parameters in:

  • integer specifying APB (0,1,2,3)
  • integer specifying CPU and Bridge access
  • integer specifying MEMSCRUB access (low 16)
  • integer specifying DMA0 and DMA1 access
  • integer specifying DMA2 and DMA3 access

Status returned:

  • SUCCESSFUL
  • INVALID_NUMBER (error in input parameters)
  • INCORRECT_STATE (error enabling clock gating for device)
  • UNSATISFIED (did not read back correctly)

FDIR Manager directives

oceos_system_state_get

// returns the value of the system state variable
U32_t  oceos_system_state_get();

This directive returns the value of the system state variable.

Parameters in: None Return values:

  • integer with value of system state variable
  • STATUS_INVALID (data area corrupt or not initialised)

oceos_system_state_set

enum DIRECTIVE_STATUS   oceos_system_state_set(
   U32_t               // new value of system state variable
);

This directive sets the system state variable and stores the previous value.

Parameters in:

  • integer with value of written

Status returns are:

  • SUCCESSFUL
  • STATUS_INVALID (data area corrupt or not initialised)

oceos_system_watchdog_init (SPARC)

enum DIRECTIVE_STATUS   oceos_system_watchdog_init(
   BOOLE_t,            // whether enabled initially
   U32_t,              // watchdog timeout
   U8_t                // watchdog window
);

This directive initialises the watchdog. The watchdog window reload value is in the control register. Large window sizes make resets more likely.

Parameters in:

  • Boolean specifying whether watchdog is initially enabled (1 for enabled, 0 for disabled)
  • Integer specifying watchdog timeout value
  • Byte specifying window size

Status returns:

  • SUCCESSFUL
  • INVALID_NUMBER (error in input parameters)

oceos_system_watchdog_enable (SPARC)

// enable the system watchdog
enum DIRECTIVE_STATUS  oceos_system_watchdog_enable();

This directive enables the watchdog which will use timeout and window values setup previously. Parameters in: None Status return: SUCCESSFUL

oceos_system_watchdog_disable (SPARC)

// disable the system watchdog
enum DIRECTIVE_STATUS  oceos_system_watchdog_disable();

This directive disables the system watchdog. Parameters in: None Status return: SUCCESSFUL

oceos_system_watchdog_ticks_remaining (SPARC)

// number of ticks remaining until watchdog timeout
unsigned int  oceos_system_watchdog_ticks_remaining();

This directive returns the number of ticks to watchdog timeout.

oceos_system_watchdog_reset (SPARC)

// reset the watchdog
enum DIRECTIVE_STATUS  oceos_system_watchdog_reset(
   BOOLE_t             // whether to enable or disable after reset
);

This directive resets the watchdog allowing it to be enabled or disabled after reset. Status return: SUCCESSFUL

OCEOS Data Areas

All operating systems require data space to hold various settings and the current states of tasks and other components. Most operating systems also require a separate stack for each task.

OCEOS has been designed to minimise these data memory requirements and to provide clear definitions of data memory use.

OCEOS uses a single system stack shared with the application instead of one stack per task, a major space saving.

The other data space used by OCEOS can be divided into three areas

  1. the fixed data area
  2. the dynamic data area
  3. the log area

The sizes of these areas depend on the number of tasks etc. but are constants for a given application.

OCEOS does not use dynamic memory allocation.

Each area is 32-bit aligned, starts with a constant 32-bit sentinel (OCEOS_VERSION) and ends with a constant 32-bit sentinel (END_SENTINEL). The size of an area, in 32-bit words, is calculated by OCEOS and stored by OCEOS immediately after the initial OCEOS_VERSION constant.

OCEOS does not allow tasks or other components be created after scheduling begins, so much of its configuration data is fixed once all tasks etc. have been created and can be stored in an area that does not change. This fixed data area is initialised by oceos_init(), updated as tasks etc. are created, and finalised with the addition of a checksum by oceos_init_finish(). The general layout of this area is always the same, as described below, its actual size depends on the number of tasks etc.

As OCEOS runs information about tasks and their associated jobs and other components is stored in the dynamic data area. The general layout of this area is always the same, as described below, its actual size depends on the number of tasks, maximum jobs for each task, etc.

As OCEOS runs a system state variable and system log are maintained and updated. These are stored in the log area, which may be placed in external non-volatile memory if this is available. The general layout of this area is always the same, as described below, its actual size depends on the system log size.

The start address of each area is 32-bit aligned and is placed by the application in the configuration structure (.fixed_data_address, .dynamic_data_address, .log_address). OCEOS sets up each area, placing the sentinels at the start and end of each area and the size of the area (in 32-bit words) immediately after the initial sentinel.

In many applications it will be convenient to allocate space for the three areas as arrays of 32-bit words. The start addresses of these arrays are then placed in the configuration structure as above.

To define these arrays their sizes (number of 32-bit words) are needed.

Although OCEOS provides those sizes, as indicated above, it can only do so after all tasks etc. have been created. The array sizes however are required when the arrays are defined, which is before any call to OCEOS is made.

The simplest approach is to initially use overestimates of the array sizes, start using OCEOS, read the second word in each area to get the actual sizes, and restart after re-defining the arrays with these sizes. The sizes can be read and output by application code or using the DMON monitor “oceos” command.

To find the precise sizes of the three OCEOS data areas the application developer should define values for the number of tasks, mutexes, etc. Next, build a skeleton OCEOS application with the number of tasks, mutexes, semaphores, etc specified. Make sure the initial estimates for the three data areas are overestimates e.g. 0x1000 for each. To run the application tasks, semaphores, mutexes,…etc must be created. Below are examples of DMON output, config.h, and application code.

Areas sizes.jpg


Header file image.jpg


Data printing.jpg


This example application to display data area sizes is provided with OCEOS.

DATA AREA LAYOUTS

The design of OCEOS makes the contents of its data areas readily visible to an application. It is straightforward to determine the details of state of the system should it be necessary to do so.

Each data area is made up of a number of fields whose sizes depend on the particular application but whose general layout is invariant.

The layout of each area is described in oceos_areas.h which also provides the offset at which each field in an area is located if the above labels are defined before oceos_areas.h is included.

Each field starts at a 32-bit boundary, so treating an area as an array of 32-bit words any field can be accessed by simply use the offset as an index into the array.

Data areas layout in memory

Memory layout.jpg


Data Areas Layout

Data areas layout.jpg

Error messages

Applications written to use OCEOS are a combination of tasks calling OCEOS directives. All directives return a status value from the enumerated type below (which is based on RTEMS codes). The meaning of the directive status returned depends on context and the called directive.

/* Status codes returned by OCEOS directives (RTEMS based) */
enum DIRECTIVE_STATUS {
SUCCESSFUL, // completed as expected
TASK_EXITTED,
MP_NOT_CONFIGURED,
INVALID_NAME,
INVALID_ID,
TOO_MANY,
TIMEOUT,
OBJECT_WAS_DELETED,
INVALID_SIZE,
INVALID_ADDRESS,
INVALID_NUMBER,
NOT_DEFINED,
RESOURCE_IN_USE,
UNSATISFIED,
INCORRECT_STATE,
ALREADY_SUSPENDED,
ILLEGAL_ON_SELF,
ILLEGAL_ON_REMOTE_OBJECT,
CALLED_FROM_ISR,
INVALID_PRIORITY,
INVALID_CLOCK,
INVALID_NODE,
NOT_CONFIGURED,
NOT_OWNER_OF_RESOURCE,
NOT_IMPLEMENTED,
INTERNAL_ERROR,
NO_MEMORY,
IO_ERROR,
PROXY_BLOCKING,
EMPTY,
FAILED_TO_SET_REGISTER
};

OCEOS Application Notes

This section contains some tips for application developers.

Directives that restart tasks

Some OCEOS directives access a resource which, if not available, cause the task to be restarted when the resource has changed state e.g. oceos_dataq_read_restart, oceos_sem_wait_restart. Tasks restarted will lose any data stored in local variables so it may be necessary for the application to store such data elsewhere so that it is available when the task is restarted.

Mutex priority ceilings

The priority ceiling of a mutex ensures that a task holding a mutex cannot be pre-empted by a higher priority task looking for the same mutex. Note that it is the application developer’s responsibility to ensure the correct priority ceiling is setup when the mutex is created. The priority ceiling should be set to the value of the highest priority task using the mutex.

Task local/global storage

In a RTOS tasks usually start, run for a finite period, then exit. A task involving an endless loop must suspend waiting for a resource at least once around the loop, otherwise it will lock out lower priority tasks. Unless of course it itself is the lowest priority ‘idle’ task, when this lock out doesn’t matter.

As a task runs it stores data in local variables, in most RTOS on its own stack. When a task suspends this locally stored data remains available and can be used by the task when it continues. On exit this local data is lost. Hence if data acquired by a task is to be used by it the next time it starts this data must be stored globally.

A typical example might be to keep track of the total number of things the task has processed.

This would require:

  1. A global integer for the total count, set up and initialised to 0 before the task first starts

    e.g. unsigned int total = 0U;

  2. Each time the task runs it keeps a count and before it exits uses this to update total

    e.g total += count;

    if (total < count) total = UINTMAX_C; // assuming predefined

This is all that is needed if all tasks that update total have the same priority and there is only one CPU. (It is assumed that an RTOS does not do time slicing between tasks with the same priority.)

If tasks with different priorities can update total or there are multiple CPUs then precautions must be taken to avoid a lost update

e.g. disable interrupts on all CPUs remembering previous states

update total

restore all interrupts to their previous states

Another scenario in which a task will need to store data globally is when it has acquired data from a sensor or other source and has to exit before it can complete processing the data. The data needs to be stored globally so that the task can acquire it again when next it starts.

A flag can be used by the task to check if the data has already been acquired, in which case it uses the globally stored data rather than acquiring new data. Once use of the global data is completed the task must reset this flag.

This would require

  1. A global flag for the state of this globally stored data, set to ‘false’ before the task first starts

    e.g. Boole_t my_data_already_obtained = FALSE;

  2. A global structure for the data (this could incorporate the flag) set up before any task starts

    e.g. struct my_data data;

  3. Logic in the task to use the already stored data if any

    e.g. if(!my_data_already_obtained) {

    1. my_data_already_obtained = (data = obtain new data)} // atomic
  4. if (my_data_already_obtained) process the data
  5. Once data processing is complete the task resets the flag

    e.g. my_data_already_obtained = FALSE;

If different tasks can acquire the data, tasks are basically in competition to acquire it and each task should have its own ‘xx_data_already_obtained’ flag and its own structure for holding the data. With only one CPU (and no time slicing between tasks of the same priority) this can be relaxed slightly to one flag and one structure per priority rather than per task.

Another example might be where a task reads data from a queue and has to exit before it can use the data, but the data needs to be used by the task when it restarts rather than being discarded.

Using e.g. an OCEOS data queue (which contains void pointers), no flag is needed as the pointer itself can fulfil that role as well as holding the last item read from the queue by the task:

  1. A global void pointer for use by the task is initialized to ‘NULL’ before any task starts

    e.g. void * my_last_read = NULL;

  2. Logic in the task to use the previously read data (if any) or read fresh data

    e.g. if(NULL == my_last_read) my_last_read = (read the OCEOS data queue)

  3. Data processing

    e.g. if(NULL != my_last_read) { process the data at my_last_read}

  4. Once data processing is complete the task resets my_last_read

    e.g. my_last_read = NULL;

If different tasks can read the data queue, tasks are basically in competition to acquire the data and each task will require its own ‘xx_last_read’. As above this can be relaxed slightly for one CPU to one per priority assuming there is no time slicing between tasks of the same priority.

N.B. If there are multiple CPUs the above will need to be modified.

All of the above apply generally and except for the OCEOS data queue are not specific to OCEOS.

Specific to OCEOS

In the context of the need for global storage OCEOS differs from many other RTOS in two ways

  1. An OCEOS task may be forced to exit rather than suspending when a counting semaphore or data queue item it needs is not available. It then restarts automatically when the item becomes available, rather than continuing from where it was as with other OS.
  2. OCEOS allows the same task be used to process different devices of the same type, rather than needing a different task for each device

OCEOS forced to exit:

In most RTOS, a wait operation on a counting semaphore can cause the task to suspend at that point and wait until the counting semaphore is signalled, it then resumes from where it was suspended

Similarly for an attempt to read a data queue when the data queue is empty.

In most RTOS this behaviour is made possible by each task having its own stack on which the local data remains stored while the task is suspended waiting. Each counting semaphore and data queue also has a pending queue of tasks waiting to continue.

OCEOS has a single system stack shared by all tasks, a significant space saving. This is used by tasks for their local data as they execute, but this space cannot be retained while a task stops and waits for something to become available as a lower priority task can then resume.

As a result In OCEOS tasks cannot be suspended waiting for something to be available. Instead an OCEOS application can choose either to ‘continue’ or to ‘restart’.

If the ‘continue’ option is used an appropriate status code is returned and the application continues taking into account the unavailability of the sought item.

The ‘restart’ option causes the task to exit and be placed on the pending queue of the counting semaphore or data queue. This is similar to what happens when a task waits in other RTOS, but now the task will restart rather than continuing and its previous local data has been lost.

Hence if the ‘restart’ option is chosen and it is desired to reuse some already acquired data when the task restarts, any already acquired data should be globally stored before ‘restart’ is used, as described in the earlier general examples above. (A timeout can be specified with the restart.)

Using the same task with various devices

In many system there are multiple instances of the same unit. Rather than having different tasks to handle each unit, OCEOS allows the same task be used as the processing is similar.

The distinction between the units is made by passing the task a pointer to a specific structure associated with the specific unit.

These structures are global, usually one instance of a particular structure for each unit of a particular type. They are set up by the application, which also may initialise some of their fields. They are usually set up and initialised before OCEOS starts, their contents are not restricted by OCEOS.

Each unit structure will typically give register addresses for the unit and provide space for global storage of information specific to that unit such as the time it last caused an interrupt or the total number of interrupts it has caused.

Typically an OCEOS task is started as a result of an interrupt from a unit. The interrupt handler identifies which task should be started to handle this type of unit, and identifies the specific unit structure associated with the unit that caused the interrupt.

The interrupt handler may store further information in the unit’s structure, such as the time at which the unit last interrupted, the minimum time between interrupts, or other information as determined by the application in setting up the interrupt handler.

The interrupt handler then passes the task ID and a pointer to the specific unit structure to OCEOS with a request to start the task.

If subsequently the task has to exit prematurely due to e.g. a counting semaphore being unavailable, both the task and the unit structure are put on the pending queue, and when the task is restarted by e.g. signalling the counting semaphore it can complete the processing for the unit.

Before e.g. waiting on a counting semaphore the task can if appropriate update the information in the unit structure, perhaps setting a flag in the structure to indicate to the interrupt handler when the next interrupt occurs that processing of the previous interrupt from the unit was incomplete. (The unit structures are in global storage.)

Typically a the task will only update a unit structure with information the task has gathered specific to the unit associated with the structure. More general information the task has gathered, e.g. by reading from an OCEOS data queue, will be stored typically in a global variable or structure associated with the task (or with the task priority) as described in the earlier examples above.

The application developer is free to choose what approaches are taken in order to make the previous information available to the task when next it starts.

Example:

A simple example involves a low priority task putting an item pointer on a data queue and then starting a higher priority task which uses the item.

The high priority task reads the data queue but then waits on a semaphore and as this is 0 has to exit and will restart when the low priority task signals the counting semaphore.

In order to ensure the data from the queue is available when it restarts, the high priority task saves it in global memory before doing the wait on semaphore, and on starting checks to see if data is available in the global storage before reading from the data queue.

(Many other permutations of these activities are possible)

Two tasks, t_tom and t_dick, one counting semaphore s_do_now, one data queue d_items, and in global memory data storage item_store_ptr.

t_tom is the low priority initial task passed to OCEOS when starting:

t_tom loops forever

Output a message

Ensure the global storage item_store_ptr has been cleared

Place an item pointer on the data queue d_items

Start the high priority task t_dick.

Output a message Signal the semaphore s_do_now

t_dick is the higher priority task

output a message

If item_store_ptr is NULL

update with value read from data queue d_items using restart option

output a message

wait on counting semaphore s_do_now using restart option

output a message involving data read from d_items

clear item_store

exit

Expected behaviour: (illustrated by various messages at various stages)

t_tom adds item to data queue d_items

t_tom starts t_dick causing itself to be immediately pre-empted by t_dick

t_dick starts and as d_items is not empty updates item_store_ptr with pointer read from d_items

t_dick waits on counting semaphore s_do_now and exits as this is 0, going on pending queue

t_tom continues and signals the counting semaphore s_do_now

t_dick restarts, processes the data pointed to by item_store_ptr and exits

t_tom goes back to start of loop

Using OCEOS the basic application structure would be

Two tasks:

t_tom (ID=0)

t_dick (ID=1)

(names are an enumerated type, IDs are allocated automatically)

Priorities:

t_tom: 9

t_dick: 8 (highest in this case)

Max jobs: 1 in each case (in general recommended this is >=2)
ReadyQ max entries: 2 ( total of ‘Max jobs’, no point having more than this)
Counting Semaphore:

s_do_now (ID=0)

(names are an enumerated type, IDs are allocated automatically)

Initial values: 0
PendingQ size: 1

(at most one instance of the high priority task, this is only restarted by the

low priority task after the previous start has finished)

Data Queue:

d_items (ID=0)

(names are an enumerated type, IDs are allocated automatically)

Size:

1 (the high priority task removes the item before the low priority task can add a new item

PendingQ size:

1 (only one instance of the high priority task can be waiting for an item)

Global data storage void * item_store_ptr
Application functions

tom(),

dick() These carry out the task actions described above

Header

app_config.h should be updated to correspond to the above

In the above, the minimum sizes consistent with the application have been used.

In general it is recommended that the ‘Max jobs’ for each task be at least two, to cater for a new instance of a task being started before a previous instance has finished. In this simple case that cannot happen, but in general it may happen for some unanticipated reason, particularly when tasks are started by interrupt handlers.

The number of ready queue entries need never be more than the maximum number of task instances that can be in existence at any one time. This can never exceed the total of the ‘Max jobs’, and in most cases will be significantly less than this (in OCEOS it cannot exceed 254). In this case it must cater for two task instances having been started and not being finished at the same time.

The initial value of the counting semaphore is 0, as a result the high priority task does not progress the first time it reads the semaphore but exits and goes on the semaphore pending queue.

The high priority task will also be unable to progress if the data queue is empty when it starts but in this case something is always placed on the queue before the high priority task is started.

DMON debug support for OCEOS

DMON commands list:

  • oceos info
  • oceos log <last | all>
  • oceos state
  • oceos itask <taskID | all>
  • oceos status <rq | dpq | spq | tapq>
  • oceos cpuload <status|enable|disable>
  • traptable [svt | mvt]
  • load <fill> [filename]
  • stack <check><lowerBound><cpuID>

oceos info

Display oceos configuration information. Should be called after oceos_start() directive.

  • checks weather log, fixed and dynamic data areas were not overwritten by application at initialisation phase
  • displays the size of log, fixed and dynamic data section that application used, can be used by user to adjust these data area sizes for building application
  • displays oceos configurations
  • displays pointers to data structures of oceos

See oceos info command example below:

Oceos info printout.jpg

oceos log <last | all>

Display last log message or all log messages. Default is last log message. DMON finds log area start symbol, number of entries, read and write indexes and displays log entries in latest entries first order.

Each entry contains:

  • time the log entry was made
  • entry type
  • entry comment

See oceos log last command example below:

Oceos log last printout.jpg

See oceos log all command example below:

Oceos log all printout.jpg

oceos state

Display system state variables. OCEOS has two system state variables. One variable can be reset by oceos directive, but second is preserved to track any issues occurred while oceos was running. DMON translates system state flags and displays to the user.

See oceos state command example below:

Oceos state printout.jpg

oceos itask <taskID | all>

Display task information for task with ID or for all tasks in oceos. Default is all task information.

Available information:

location in memory pointer to task dynamic and pointer to task fixed blocks
taskId
task priority
task threshold
jobsMax
timeDeadline
timeMinBetweenStarts
jobsCurrent current number of jobs created, <= jobsMax
status Enum TASK_STATUS
jobsCurrentMax maximum number of current jobs for this task, no roll over, can be reset
jobsTotal number of times this task was started, no roll-over, can be reset
timeCurrentExec execution time so far of current job
timeMaxDelay maximum time job is waiting on ready queue before becoming active
timeMaxFinish maximum time to finish job execution after starting
timeMaxExec maximum time spent executing
timeTotalExecLow total CPU time used by this task low bits
timeTotalExecHigh total CPU time used by this task high bits
timeMinGapStarts minimum time between job creations, can be reset
timeMinGapFinishStart minimum time between job ending and new job creation, can be reset
timeLastStart time of most recent job creation
timeLastFinish time of most recent job finish
preemptMax maximum times any job was pre-empted, no roll-over, can be reset

See oceos itask 0 command example below:

Oceos itask 0 printout.jpg

See oceos itask all command example below:

Oceos itask all printout.jpg

oceos status <rq | dpq | spq | tapq>

Display oceos status i.e. number of tasks on ready queue (rq), number of tasks on pending data queue (dpq), number of tasks on pending semaphore queue (spq) and number of tasks on timed action queue (tapq). Default “oceos status” is number of tasks on each queue. And then user can get more details about tasks on particular queue by passing switch to “oceos status” command.

See oceos status rq command example below:

Oceos status rq printout.jpg

See oceos status dpq command example below:

Oceos status dpq printout.jpg

See oceos status spq command example below:

Oceos status spq printout.jpg

See oceos status tapq command example below:

Oceos status tapq printout.jpg

oceos cpuload <status|enable|disable>

Enable/Disable display of CPU Load Graph at interval 1000 ms or check status. Updated while OCEOS is running.

See the CPU load enabled graphs below:

Oceos cpu load graph 1.jpg


Oceos cpu load graph 4.jpg


See the Task Statistics graph for task 0 to 9 below:

Oceos task statistic graph 1.jpg

Oceos task statistic graph 2.jpg

Oceos task statistic graph 3.jpg

Oceos task statistic graph 4.jpg

Oceos task statistic graph 5.jpg

traptable [svt | mvt]

DMON displays content of trap table (interrupt number => trap handler) and translates address to symbols if available.

load <fill> [filename]

It is extension to load command to fill memory from _bss_end to begging of stack with 0xCAFEBABE. When program stops user can use DMON command “stack check” to see how far stack was used.

stack <check><lowerBound><cpuID>

Extension to stack command to check how far stack was grown, can be used when program stops after “load fill” command. User can pass lower bound as parameter. If lower bound parameter is not passed _bss_end symbol is used.

OCEOS Device Support

In OCEOS a device is typically handled by a task, but it may not be necessary to have a different task for each device. If multiple devices are processed in a similar way one task with its maximum number of jobs at least equal to the number of devices may be enough. When the task is started, typically by an interrupt handler, it is passed a data structure specific to one device and the job then created processes that specific device. For example, the six GR716 UARTs might all be handled by one task.

Separate tasks will be needed however (even for devices of the same type) if different priorities are required for processing different devices as all devices processed by one task are processed with the same priority.

In a GR716 application that uses OCEOS, device support will typically have three main parts:

  1. Initialisation routines. These are typically called from the main application to
    1. set up the device’s clock gating control so that it is disabled initially
    2. associate a specific interrupt with the specific device and disable this
    3. associate a specific interrupt handler with this interrupt
    4. usually create a data structure associated with each specific device instance
    5. set the clock gating control so that the device can be configured and function
    6. set the GPIO control to map shared GPIO pins to the device as appropriate
    7. set the parameters of the device itself
    8. use memory protection to protect the device settings
  2. Interrupt handler code to deal with immediate processing requirements. If this starts a task it typically updates the device specific data structure and passes a pointer to this to the task.
  3. Tasks:
    1. A device handler task typically started by the interrupt handler. The job thus created is passed a pointer to the device specific data structure.
    2. An idle task, which clears any pending device interrupt, enables the device interrupt, and then loops indefinitely, perhaps putting the CPU in sleep mode at the end of each loop.

BCC provides support for many of the initialisation routines, with in some cases OCEOS providing wrappers primarily for checking purposes.

It should be noted that the initialisation routines, data structure, interrupt handler and tasks are part of the application software and are not part of OCEOS itself.

General Design Considerations

Typically a device will cause an interrupt, the handler will determine the data structure associated with the device, update this, and if appropriate start a task, passing it a pointer to the data structure. The interrupt handler then exits, the job created will execute at a later time dependent on priority.

In general, the interrupt handler should be kept as short as possible, and any significant processing of data be relegated to a task, or sequence of tasks (in OCEOS tasks can be chained).

In general, an interrupt handler will only start a task in certain conditions, for example when a buffer into which incoming data is placed is becoming full.

When data is being buffered, using a circular buffer, or using double buffering may be advisable.

Careful analysis will be needed to determine the appropriate interrupt priority and the appropriate task priority. These will vary from application to application and are likely to depend on the relative loads on different devices on the systems. A worst-case analysis approach seems advisable.

Example - Using a UART on the GR716

This example uses a UART application to illustrate the main steps involved in GR716 device support.

There are six APBUARTs on the GR716, and APBUART2 is used here.

Application

APBUART2 will receive an incoming stream of characters that are to be buffered with a buffer size of 255 characters and passed to a task for processing. If the buffer becomes full older entries are not to be overwritten, newer entries are to be dropped and an error indicated.

Commands requiring action may be included in the data stream and may consist of one or more characters. Commands should be acted upon as soon as possible after being received. A delay of no more than eight character times is the maximum acceptable, and the ordering of commands and data must be preserved.

Even parity is to be used, with one stop bit and a baud rate of 38,400. Flow control is not used.

Parity errors, a break condition on the RXD line, and a RX FIFO overrun on APBUART2 are to be detected and appropriate error handling and reporting of these conditions is required.

The memory protection features of the GR716 are to be used to protect the APBUART2 configuration.

Design Outline

A circular buffer will be used for the internal buffer to allow flexibility in adding and removing characters.

A data structure associated with APBUART2 will be used to hold this buffer and status information.

Processing will be structured as an interrupt handler I_HANDLER and a task T_PROCESS. An idle task T_IDLE will enable the device interrupt after scheduling begins.

I_HANDLER will add characters to the buffer and start T_PROCESS when this becomes ¾ full, when a command is detected in the input stream, or when there is a parity error, break condition, or RX FIFO overrun. Status flags in the device data structure will indicate the reason for starting T_PROCESS. The number of interrupts is to be minimised while ensuring a timely response to a single character command and to anomalies.

T_PROCESS will remove data from the buffer, reset the status flags, and handle any commands or error conditions. Once started it will loop checking the status and will exit when there is nothing for it to do. Entries may be added to the internal buffer by I_HANDLER while T_PROCESS is running.

One single character command ‘esc’ and two other commands “#a” and “#b” are to be recognised, and all other character sequences treated as data text.

(For this example what T_PROCESS does with the data, or what it does once it recognises a command, are not developed further.)

A low priority idle task T_IDLE will also be created. Initially this will enable the device interrupt and then loop indefinitely, putting the CPU in sleep mode waiting for interrupts at the end of each loop.

Before developing the design further it is necessary to consider the settings needed for APBUART2 and the initialisation required for clock gating, GPIO pin mapping, interrupts, and memory protection.

APBUART Configuration

Each APBUART has two internal 16 byte FIFO buffers for transmit (TX) and receive (RX). These are accessed via the 32-bit data register (DATA) with only the low 8-bits of this register actually used. A read of DATA provides the oldest byte in the RX FIFO as the low byte in the word returned and removes it from the RX FIFO. Writing to DATA adds the low byte in the word to the TX FIFO from which it is removed by the transmitter. For testing purposes a loop back mode can be set so that the TX output is internally connected to the RX input and actual outputs are disabled. A FIFO debug mode can also be set that disables the transmitter and allows the FIFO debug register (FIFO) be used to read from the RX FIFO and write to the TX FIFO.

In each APBUART the baud rate used is the same for receive and transmit and is determined by the value set in its scaler register (SCALER). This value should be the number of system clock cycles that corresponds to 1/8 of the time for one serial bit, or as close as possible to this.

The data format used is always 8 bits per character, with an optional even or odd parity bit, and one stop bit. The transmitter can be set to use two stop bits.

Hardware flow control can be used, with the input CTSN signal controlling transmission from the APBUART, and the output RTSN signal connected to the source of the incoming data.

For this example, the APBUART2 control register (CTRL) and scaler register (SCALER) are as follows:

CTRL 0x80003025

Even parity (PE, PS=0)

Receiver enable (RE)

Receiver interrupt enable (RI)

delayed interrupt enable (DI)

break interrupt enable (BI)

Receiver FIFO interrupt enable (RF)

Other control bits all 0

SCALLER 0xAB (Baud rate 38400 at 50 MHz system clock)

Note that while interrupts are enabled here, APBUART2 interrupts are disabled initially in the interrupt controller IRQAMP and only enabled by T_IDLE after scheduling begins.

These control settings ensure that an interrupt is generated after every character is received, but only after a gap of just over four character times without a character, so that a block of characters received in quick succession will cause only one interrupt. In case such a block succeeds in filling the RX FIFO an interrupt is also generated when the RX FIFO is half full. An interrupt is also caused by a parity error, a break on the RX line, i.e. line held low for longer than 11 bits, or a RX FIFO overrun.

With the above settings each incoming character corresponds to about 15,000 system clock cycles, so if a FIFO half full interrupt occurs it will take at least 120,000 system clock cycles before a further 8 bytes can arrive potentially causing a FIFO overrun and loss of an incoming byte. The interrupt latency and time to remove the first byte from the RX FIFO needs to be guaranteed to be less than this by appropriate choice of the interrupt priority.

These control settings might be assigned in many other ways. It might for example be desirable to cause an interrupt immediately on arrival of a character so as to avoid the four-character latency above.

Before the APBUART2 control bits are set the clock gating, GPIO pin allocations, interrupt controller, and memory protection need to be configured, and these are considered next.

Clock gating

To use a GR716 device, or to modify its registers, the clock gating for the device must be enabled. In the initialisation sequence clock gating can be used to ensure a device is off. To save power when a device is not in use clock gating may also be used at various points in an application.

In the GR716 the APBUART clocks are controlled by the primary clock gating unit. This unit’s unlock register UNLOCK0, its clock enable register CLKEN0 and its core reset register RESTET0 all use Bit 18 to correspond to APBUART2. To modify a bit in CLKEN0 or RESET0 the corresponding bit in UNLOCK0 must first be set, and should be reset to 0 once the desired changes have been made to protect against unintended changes.

(Note: The GR716 documentation uses UNLOCK1 etc. to refer to these registers, but this appears to be a misprint, as they are also referred to as ‘Unlock register 0’ etc. and UNLOCK1 etc. is also used in referring to the secondary clock gating unit, so ‘0’ is used here rather than ‘1’.)

Disable APBUART2

  1. Ensure no software is using the APBUART2 and that it cannot cause an interrupt
  2. Set UNLOCK0 bit 18 to 1
  3. Reset CLKEN0 bit 18 to 0
  4. Reset UNLOCK0 bit 18 to 0

Enable APBUART2

  1. Set UNLOCK0 bit 18 to 1
  2. Set RESET0 bit 18 to 1
  3. Set CLKEN0 bit 18 to 1
  4. Reset CLKEN0 bit 18 to 0
  5. Reset RESET0 bit 18 to 0
  6. Set CLKEN0 bit 18 to 1
  7. Reset UNLOCK0 bit 18 to 0

GPIO Mapping

In the GR716 most input-output pins can be allocated to any one of a number of devices as determined by an I/O switch matrix that must be configured appropriately by the application.

APBUART2 uses GPIO[10] and GPIO[11] for its external TXD and RXD data lines respectively. These GPIO pins can be used for 8 different purposes, and to allocate them for use by APBUART2 the appropriate value must be written to the appropriate system configuration register.

For pins GPIO[10] and GPIO[11] the appropriate configuration register is SYS.CFG.GP1. In SYS.CFG.GP1 bits 8:11 determine the allocation of GPIO[10] and bits 12:15 of GPIO[11], and in both cases the pin is allocated to APBUART2 (TXD and RXD respectively) by setting these 4 bits to 0b0001.

Care is needed in writing to SYS.CFG.GP1 as other bit positions determine the allocation of other pins.

N.B. GPIO mapping consistency should be checked, perhaps using OCE’s DMON or Gaisler’s grmon.

It is worth noting also that when a device is disabled its associated pins are returned to GPIO input.

Interrupts

Each device on the GR716 has a fixed hardware Interrupt Line that is mapped to an Interrupt ID by the Interrupt Controller (IRQAMP). This mapping can be changed by writing to a register on IRQAMP.

The Interrupt ID in conjunction with its associated 0 or 1 interrupt level determines the priority of an interrupt. The highest priority interrupt is passed to the CPU and determines the interrupt handler used. Interrupts can be masked so that a specific interrupt is ignored until the interrupt is unmasked. After reset all interrupts are masked, i.e. disabled (IRQAMP.PIMASK = 0x00000000).

The default interrupt ID assignments for the six APBUARTs are:

APBUART Interrupt Line Interrupt ID
0 24 24
1 25 25
2 42 3
3 44 5
4 45 6
5 46 7

The GR716 Interrupt Controller IRQAMP can be used to change these assignments.

For this example the default assignment is used for APBUART2, i.e. interrupt ID = 3

Initially Interrupt 3 is configured:

Ensure Masked OFF IRQAMP.PIMASK X&= 0xfffffff7
Interrupt CLEAR IRQAMP.ICLEAR |= 0x8
Set level HIGH IRQAMP.ILEVEL |= 0x8
Check no timestamp used IRQAMP.TSISEL != 3 for IRQAMP.ITSTMPC0,1,2,3

After APBUART2 has been configured, Interrupt 3 will be cleared and then enabled

Interrupt CLEAR IRQAMP.ICLEAR |= 0x8
Enable interrupt IRQAMP.PIMASK |= 0x8

The task will periodically disable and enable the interrupt

Disable interrupt IRQAMP.PIMASK &= 0xfffffff7
Enable interrupt IRQAMP.PIMASK |= 0x8

Memory Protection

The GR716 contains two memory protection (MEMPROT) units that can be used to enable/disable writing to certain memory segments and to the configuration registers of various devices.

The MEMPROT units can be gated on or off by the primary clock gating unit, which must enable them in order for them to be configured or used. The MEMPROT units can also protect the clock gating units.

Protecting the APBUART2 configuration involves the MEMPROT registers APB3PROT0, APBPROT1, APBPROT2 and APBPROT3 and setting Bit 13 in all of these and Bit 29 in all except APBPROT1.

CPU access to APBUART2 involves resetting/setting Bit 13 and Bit 29 of APB3PROT0 to enable/disable access from the CPU and intervening bridge. If only CPU access to APBUART2 is required access from other bus masters can be left disabled.

Further Design Details

Device Data Structure:

struct uart_info {
     unsigned int * BASE_ADDRESS // base address of APBUART2 registers
     unsigned int write_index : 8; // index of previous write done
     unsigned int read_index : 8; // index of previous read done
     unsigned int : 6; // spare
     unsigned int COMMAND_0 : 1; // initial part of command detected
     unsigned int COMMAND : 1; // command detected
     unsigned int CHAR_INT : 1;
     unsigned int FIFOHALF_INT : 1;
     unsigned int FIFOFULL_INT : 1;
     unsigned int PARITY_INT : 1;
     unsigned int BREAK_INT : 1;
     unsigned int BUF_FILLING : 1;
     unsigned int BUF_OVERRUN : 1;
     unsigned int RUNNING : 1;
     char buffer[256]; // array size is one more than buffer size
}; // uart_info

Note: The buffer array will be treated as a circular buffer. If full new entries will be dropped rather than overwriting older entries and the BUF_OVERRUN bit be set. An empty buffer is indicated by read and write indices being equal, so the maximum number of entries in the buffer (255) is one less than the array size.

Note: write_index is changed only by the interrupt handler

read_index is changed only by the task

status bits are changed only in the interrupt handler or with interrupts disabled

The bits of the status field will typically be set by the I_HANDLER and reset by T_PROCESS.

Interrupt Handler and Task

I_HANDLER starts T_PROCESS but does not need to do so if T_PROCESS is already active.

T_PROCESS loops until the buffer is empty and there are no anomalies to handle, but must then exit rather than continue looping to allow lower priority tasks become active. I_HANDLER need only start T_PROCESS again if T_PROCESS has exited its main loop.

To facilitate this a status bit RUNNING is set by I_HANDLER when it starts T_PROCESS, and is cleared by T_PROCESS when it determines the buffer is empty and there is no anomaly to be handled.

As T_PROCESS still has to exit after clearing RUNNING, it must be possible for two T_PROCESS jobs to exist simultaneously, the old one finishing up, and the new one just started. OCEOS will not schedule the new job until the old one has completed so no more than two T_PROCESS jobs can be in existence at the same time and the maximum number of jobs for T_PROCESS can be limited to two.

Interrupt Handler

An APBUART2 interrupt is caused by

  1. A character arriving and no further character having arrived after four character times
  2. The RX FIFO becoming half full (eight characters)
  3. The RX FIFO being overrun
  4. A break condition on the RX line i.e. line held low for longer than 11 bits
  5. A parity error on the input

The interrupt handler I_HANDLER updates the status bits in the uart_info structure based on the APBUART2 status register, resets the APBUART2 status register bits, and if space is available in the buffer copies the data from the RX FIFO to the buffer.

As it copies the data to the buffer I_HANDLER recognises the presence of a single character command by setting the COMMAND bit, the start of a multi-character command by setting the COMMAND_0 bit, and the completion of a multi-character command by setting the COMMAND bit and clearing the COMMAND_0 bit.

If buffer space is not available the RX FIFO data is discarded, BUF_OVERRUN is set, and if COMMAND_0 is set it is reset since the continuation characters of the command have now been lost.

A task is not started if RUNNING is set as the current job will recognise the new conditions.

If RUNNING is not set I_HANDLER will set it and start T_PROCESS if any one of the following is true:

  1. an APBUART2 error condition has occurred
  2. BUF_OVERRUN has been set
  3. a command has been recognised in the input stream
  4. the buffer has become ¾ full.

This approach can result in T_PROCESS locking out lower priority tasks for an indefinite period if APBUART2 is heavily loaded with incoming data, and in general this will need to be taken into account in deciding the priority of T_PROCESS. (As the idle task is the only other task in this example this is not relevant here.)

In addition, if the processing involved in T_PROCESS is sometimes but not always heavy, perhaps due to an occasional command, it may be worth using a lower priority task than T_PROCESS to handle this heavy processing and start this task from T_PROCESS so as to ensure the primary buffer and RX FIFO are never overrun.

One the other hand, if a high priority command is recognised it may be desirable to have T_PROCESS start a high priority task to ensure the command is dealt with promptly, without delays due to T_PROCESS being pre-empted by a higher priority task, although this will increase the risk of overruns.

In general, deciding on the buffer size and on the relative priorities of various interrupts and of various tasks is likely to require careful consideration, although not an issue for this simple example.

Task

The task T_PROCESS is started by the interrupt handler I_HANDLER and does most of the processing involved in dealing with the incoming data stream.

As mentioned above, a jobs maximum of two jobs should be specified for T_PROCESS when it is created. It may be worth noting that while these two jobs may exist at the same time, one will complete execution before the other begins, as there is no time slicing in OCEOS and jobs of the same priority are scheduled for execution in the order of their creation.

T_PROCESS will require local storage for data and for status, local_storage and local_status.

As T_PROCESS can be interrupted while processing the data care is needed to ensure that local_status corresponds to the local_storage data being processed. It will be necessary to disable APBUART2 interrupts at some point, in order to guarantee this.

Also while local_storage may not need separate local storage (it might instead be implemented as pointers into the internal buffer) it seems likely that it will be necessary to have explicit local storage in T_PROCESS for local_status.

Processing: T_PROCESS is structured as a loop with the following main steps 1 to 7 below:

  1. While uart_info.read_index != uart_info.write_index
    1. Increment uart_info.read_index (mod 256)
    2. Copy characters from buffer to local_storage
  2. Disable APBUART2 interrupt (need to ensure status and data correspond)
    1. While uart_info.read_index != uart_info.write_index (usually already empty)
      1. Increment uart_info.read_index (mod 256)
      2. Copy character from buffer to local_storage
    2. Copy uart_info status bits to local_status
    3. Reset uart_info status bits to nominal (APBUART2 status bits reset by I_HANDLER)
  3. Enable UART interrupt
  4. If local_status not nominal (buffer overrun, RX_FIFO overrun, parity error, break)
    1. Process anomalous condition
  5. If local_status.COMMAND
    1. Repeat until end of local_storage
      1. Find next command in local_storage
      2. Process command
      3. Reset command search start
  6. Disable APBUART2 interrupt
    1. If (uart_info.read_index == uart_info_write_index) (no new data)

      AND (uart_info.status is nominal) (no new problems)

      1. Reset RUNNING
      2. Enable APBUART2 interrupt
      3. Exit LOOP (task exits)
  7. Enable APBUART2 interrupt (loop continues from 1.)

Application

An application that uses OCEOS

  1. sets up the functions to be used by the interrupt handlers and tasks,
  2. sets up the functions to be called to handle error conditions
  3. determines the memory areas for OCEOS fixed, dynamic and log data
  4. starts executing at main()

main(){ (the initialisation procedures below have been described earlier)

  1. Use clock gating to ensure APBUART2 is disabled
  2. Set interrupt controller IRQAMP for APBUART2, interrupt disabled (masked)
  3. Associate I_HANDLER with APBUART2 interrupt
  4. Create and initialize the device data structure (described earlier)
  5. Set the clock gating control to enable APBUART2
  6. Set the GPIO control to map APBUART2 RXD and TXD to GPIO pins
  7. Set APBUART2 CTRL and SCALER registers
  8. Set the memory protection registers to protect APBUART2
  9. Create and initialise an application configuration structure
  10. Pass the application configuration structure to oceos_init()
  11. Create T_PROCESS task with maximum two jobs, high priority
  12. Create T_IDLE task with maximum one job, low priority
  13. Finish OCEOS initialisation using oceos_init_finish()
  14. Pass fixed data, idle task T_IDLE and null pointer to oceos_start()

OCEOS now starts scheduling and runs T_IDLE initially, pre-empting it whenever T_PROCESS

is started by I_HANDLER. When T_IDLE starts it enables the APBUART2 interrupt, and then

loops indefinitely, perhaps putting the CPU in sleep mode at the end of each loop.

}

Two-phase build to miminise memory used

OCEOS can be build in two phases to reduce the memory requirement. Phase I performs the initialisation of OCEOS data areas, which are saved to an object file. Phase 2 enables the application to be built without the initialisation directives where the OCEOS simply needs to be started. This significantly reduces the memory footprint.

The steps to perform he two-phase build are as follows:

Step 1:

Modify linkcmds.memory located at C:\opt\bcc-2.1.0-llvm\sparc-gaisler-elf\bsp\gr716\leon3

to include a new section for fixed data. The section size must include fixed data, dynamic data and log data.

MEMORY { 
bootprom : ORIGIN = 0x00000000, LENGTH = 4K 
extprom : ORIGIN = 0x01000000, LENGTH = 16M 
spi0 : ORIGIN = 0x02000000, LENGTH = 32M 
spi1 : ORIGIN = 0x04000000, LENGTH = 32M 
dram : ORIGIN = 0x30001000, LENGTH = 60K 
fixed : ORIGIN = 0x30000000, LENGTH = 4K 
iram : ORIGIN = 0x31000000, LENGTH = 128K 
extram : ORIGIN = 0x40000000, LENGTH = 256M 
}

Step 2:

Modify linkcmds located at C:\opt\bcc-2.1.0-llvm\sparc-gaisler-elf\bsp\gr716\leon3

to include new section for fixed data :

INCLUDE linkcmds.memory
REGION_ALIAS("REGION_TEXT", iram);
REGION_ALIAS("REGION_FIXED_DATA", fixed);
REGION_ALIAS("REGION_RODATA", dram);
REGION_ALIAS("REGION_DATA_VMA", dram);
REGION_ALIAS("REGION_DATA_LMA", dram);
REGION_ALIAS("REGION_BSS", dram);
REGION_ALIAS("REGION_EXTDATA_VMA", extram);
REGION_ALIAS("REGION_EXTDATA_LMA", extram);
INCLUDE linkcmds.base

Step 3:

Modify linkcmds.base located at C:\opt\bcc-2.1.0-llvm\sparc-gaisler-elf\bsp\gr716\leon3

to include new section for fixed data :

.fixeddata : { 
__fixeddata_start = .; 
*(.fixeddata)
__fixeddata_end = .; 
} > REGION_FIXED_DATA

Step 4:

Create application and include oceos_init and oceos_finish without starting oceos.

This part should create mutexes, dataqs, semaphores if used.

Fixed data, dynamic data and log data should be declared in the order below.

U32_t __attribute__((section(".fixeddata"))) 
fixed_data[FIXED_DATA_ARRAY_SIZE_U32S] = {0}; 
U32_t __attribute__((section(".fixeddata"))) 
log_data[LOG_DATA_ARRAY_SIZE_U32S] ; 
U32_t __attribute__((section(".fixeddata"))) 
dynamic_data[DYN_DATA_ARRAY_SIZE_U32S];

Pointers to the task functions should be declared in arrays and kept between two stages

(will be used in oceos_start at second stage to replace in fixed data).

Step 5:

Run application on the board.

Use DMON command to capture fixed data area.

save fixed_data_start fixed_data_end fixed_data.bin bin

Step 6:

Use objcopy utility to convert bin file to object and symbols :

_binary_fixed_data_bin_start, _binary_fixed_data_bin_end

and _binary_fixed_data_bin_size , which can be used in application later.

Make sure to replace the default data section to fixeddata section.

$(OBJCOPY) --output-target elf32-sparc --rename-section .data=.fixeddata --input-target binary --binary-architecture sparc fixed_data.bin fixed_data.o

Step 7:

Remove initialization code from application.

Declare:

extern char __attribute__((section(".fixeddata"))) 
_binary_fixed_data_bin_start[];

and start OCEOS as follows:

status = oceos_start((U32_t *)&_binary_fixed_data_bin_start[0], t_dan, NULL);

The oceos_start directive will use fresh pointers to task function from task_start_func_ptr

and task_end_func_ptr and update it in fixed data, initialize dynamic data and start scheduling.

Step 8:

Add fixed_data.o to the linker. Linker should put content of this object file in fixeddata section created earlier.

Now the complete initialization process can be bypassed and OCEOS can be started faster with a smaller memory footprint.