blob: c578234c7128012e479b020ac34c47962ddb9907 [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNU ADA RUN-TIME LIBRARY (GNARL) COMPONENTS --
-- --
-- S Y S T E M . O S _ I N T E R F A C E --
-- --
-- B o d y --
-- --
-- $Revision: 1.15 $
-- --
-- Copyright (C) 1997-2001 Free Software Foundation --
-- --
-- GNARL is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- ware Foundation; either version 2, or (at your option) any later ver- --
-- sion. GNARL is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
-- for more details. You should have received a copy of the GNU General --
-- Public License distributed with GNARL; see file COPYING. If not, write --
-- to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, --
-- MA 02111-1307, USA. --
-- --
-- As a special exception, if other files instantiate generics from this --
-- unit, or you link this unit with other files to produce an executable, --
-- this unit does not by itself cause the resulting executable to be --
-- covered by the GNU General Public License. This exception does not --
-- however invalidate any other reasons why the executable file might be --
-- covered by the GNU Public License. --
-- --
-- GNARL was developed by the GNARL team at Florida State University. It is --
-- now maintained by Ada Core Technologies Inc. in cooperation with Florida --
-- State University (http://www.gnat.com). --
-- --
------------------------------------------------------------------------------
-- This is the VxWorks version.
-- This package encapsulates all direct interfaces to OS services
-- that are needed by children of System.
pragma Polling (Off);
-- Turn off polling, we do not want ATC polling to take place during
-- tasking operations. It causes infinite loops and other problems.
with Interfaces.C; use Interfaces.C;
with System.VxWorks;
-- used for Wind_TCB_Ptr
with Unchecked_Conversion;
package body System.OS_Interface is
use System.VxWorks;
-- Option flags for taskSpawn
VX_UNBREAKABLE : constant := 16#0002#;
VX_FP_TASK : constant := 16#0008#;
VX_FP_PRIVATE_ENV : constant := 16#0080#;
VX_NO_STACK_FILL : constant := 16#0100#;
function taskSpawn
(name : System.Address; -- Pointer to task name
priority : int;
options : int;
stacksize : size_t;
start_routine : Thread_Body;
arg1 : System.Address;
arg2 : int := 0;
arg3 : int := 0;
arg4 : int := 0;
arg5 : int := 0;
arg6 : int := 0;
arg7 : int := 0;
arg8 : int := 0;
arg9 : int := 0;
arg10 : int := 0) return pthread_t;
pragma Import (C, taskSpawn, "taskSpawn");
procedure taskDelete (tid : pthread_t);
pragma Import (C, taskDelete, "taskDelete");
-- These are the POSIX scheduling priorities. These are enabled
-- when the global variable posixPriorityNumbering is 1.
POSIX_SCHED_FIFO_LOW_PRI : constant := 0;
POSIX_SCHED_FIFO_HIGH_PRI : constant := 255;
POSIX_SCHED_RR_LOW_PRI : constant := 0;
POSIX_SCHED_RR_HIGH_PRI : constant := 255;
-- These are the VxWorks native (default) scheduling priorities.
-- These are used when the global variable posixPriorityNumbering
-- is 0.
SCHED_FIFO_LOW_PRI : constant := 255;
SCHED_FIFO_HIGH_PRI : constant := 0;
SCHED_RR_LOW_PRI : constant := 255;
SCHED_RR_HIGH_PRI : constant := 0;
-- Global variable to enable POSIX priority numbering.
-- By default, it is 0 and VxWorks native priority numbering
-- is used.
posixPriorityNumbering : int;
pragma Import (C, posixPriorityNumbering, "posixPriorityNumbering");
-- VxWorks will let you set round-robin scheduling globally
-- for all tasks, but not for individual tasks. Attempting
-- to set the scheduling policy for a specific task (using
-- sched_setscheduler) to something other than what the system
-- is currently using will fail. If you wish to change the
-- scheduling policy, then use the following function to set
-- it globally for all tasks. When ticks is 0, time slicing
-- (round-robin scheduling) is disabled.
function kernelTimeSlice (ticks : int) return int;
pragma Import (C, kernelTimeSlice, "kernelTimeSlice");
function taskPriorityGet
(tid : pthread_t;
pPriority : access int)
return int;
pragma Import (C, taskPriorityGet, "taskPriorityGet");
function taskPrioritySet
(tid : pthread_t;
newPriority : int)
return int;
pragma Import (C, taskPrioritySet, "taskPrioritySet");
function To_Wind_TCB_Ptr is
new Unchecked_Conversion (pthread_t, Wind_TCB_Ptr);
-- Error codes (errno). The lower level 16 bits are the
-- error code, with the upper 16 bits representing the
-- module number in which the error occurred. By convention,
-- the module number is 0 for UNIX errors. VxWorks reserves
-- module numbers 1-500, with the remaining module numbers
-- being available for user applications.
M_objLib : constant := 61 * 2**16;
-- semTake() failure with ticks = NO_WAIT
S_objLib_OBJ_UNAVAILABLE : constant := M_objLib + 2;
-- semTake() timeout with ticks > NO_WAIT
S_objLib_OBJ_TIMEOUT : constant := M_objLib + 4;
-- We use two different kinds of VxWorks semaphores: mutex
-- and binary semaphores. A null (0) ID is returned when
-- a semaphore cannot be created. Binary semaphores and common
-- operations are declared in the spec of this package,
-- as they are used to implement hardware interrupt handling
function semMCreate
(options : int) return SEM_ID;
pragma Import (C, semMCreate, "semMCreate");
function taskLock return int;
pragma Import (C, taskLock, "taskLock");
function taskUnlock return int;
pragma Import (C, taskUnlock, "taskUnlock");
-------------------------------------------------------
-- Convenience routines to convert between VxWorks --
-- priority and POSIX priority. --
-------------------------------------------------------
function To_Vxworks_Priority (Priority : in int) return int;
pragma Inline (To_Vxworks_Priority);
function To_Posix_Priority (Priority : in int) return int;
pragma Inline (To_Posix_Priority);
function To_Vxworks_Priority (Priority : in int) return int is
begin
return SCHED_FIFO_LOW_PRI - Priority;
end To_Vxworks_Priority;
function To_Posix_Priority (Priority : in int) return int is
begin
return SCHED_FIFO_LOW_PRI - Priority;
end To_Posix_Priority;
----------------------------------------
-- Implementation of POSIX routines --
----------------------------------------
-----------------------------------------
-- Nonstandard Thread Initialization --
-----------------------------------------
procedure pthread_init is
begin
Keys_Created := 0;
Time_Slice := -1;
end pthread_init;
---------------------------
-- POSIX.1c Section 3 --
---------------------------
function sigwait
(set : access sigset_t;
sig : access Signal) return int
is
Result : Interfaces.C.int;
function sigwaitinfo
(set : access sigset_t; sigvalue : System.Address) return int;
pragma Import (C, sigwaitinfo, "sigwaitinfo");
begin
Result := sigwaitinfo (set, System.Null_Address);
if Result /= -1 then
sig.all := Signal (Result);
return 0;
else
sig.all := 0;
return errno;
end if;
end sigwait;
----------------------------
-- POSIX.1c Section 11 --
----------------------------
function pthread_mutexattr_init
(attr : access pthread_mutexattr_t) return int is
begin
-- Let's take advantage of VxWorks priority inversion
-- protection.
--
-- ??? - Do we want to also specify SEM_DELETE_SAFE???
attr.Flags := int (SEM_Q_PRIORITY + SEM_INVERSION_SAFE);
-- Initialize the ceiling priority to the maximim priority.
-- We will use POSIX priorities since these routines are
-- emulating POSIX routines.
attr.Prio_Ceiling := POSIX_SCHED_FIFO_HIGH_PRI;
attr.Protocol := PTHREAD_PRIO_INHERIT;
return 0;
end pthread_mutexattr_init;
function pthread_mutexattr_destroy
(attr : access pthread_mutexattr_t) return int is
begin
attr.Flags := 0;
attr.Prio_Ceiling := POSIX_SCHED_FIFO_HIGH_PRI;
attr.Protocol := PTHREAD_PRIO_INHERIT;
return 0;
end pthread_mutexattr_destroy;
function pthread_mutex_init
(mutex : access pthread_mutex_t;
attr : access pthread_mutexattr_t) return int
is
Result : int := 0;
begin
-- A mutex should initially be created full and the task
-- protected from deletion while holding the semaphore.
mutex.Mutex := semMCreate (attr.Flags);
mutex.Prio_Ceiling := attr.Prio_Ceiling;
mutex.Protocol := attr.Protocol;
if mutex.Mutex = 0 then
Result := errno;
end if;
return Result;
end pthread_mutex_init;
function pthread_mutex_destroy
(mutex : access pthread_mutex_t) return int
is
Result : STATUS;
begin
Result := semDelete (mutex.Mutex);
if Result /= 0 then
Result := errno;
end if;
mutex.Mutex := 0; -- Ensure the mutex is properly cleaned.
mutex.Prio_Ceiling := POSIX_SCHED_FIFO_HIGH_PRI;
mutex.Protocol := PTHREAD_PRIO_INHERIT;
return Result;
end pthread_mutex_destroy;
function pthread_mutex_lock
(mutex : access pthread_mutex_t) return int
is
Result : int;
WTCB_Ptr : Wind_TCB_Ptr;
begin
WTCB_Ptr := To_Wind_TCB_Ptr (taskIdSelf);
if WTCB_Ptr = null then
return errno;
end if;
-- Check the current inherited priority in the WIND_TCB
-- against the mutex ceiling priority and return EINVAL
-- upon a ceiling violation.
--
-- We always convert the VxWorks priority to POSIX priority
-- in case the current priority ordering has changed (see
-- posixPriorityNumbering). The mutex ceiling priority is
-- maintained as POSIX compatible.
if mutex.Protocol = PTHREAD_PRIO_PROTECT and then
To_Posix_Priority (WTCB_Ptr.Priority) > mutex.Prio_Ceiling
then
return EINVAL;
end if;
Result := semTake (mutex.Mutex, WAIT_FOREVER);
if Result /= 0 then
Result := errno;
end if;
return Result;
end pthread_mutex_lock;
function pthread_mutex_unlock
(mutex : access pthread_mutex_t) return int
is
Result : int;
begin
Result := semGive (mutex.Mutex);
if Result /= 0 then
Result := errno;
end if;
return Result;
end pthread_mutex_unlock;
function pthread_condattr_init
(attr : access pthread_condattr_t) return int is
begin
attr.Flags := SEM_Q_PRIORITY;
return 0;
end pthread_condattr_init;
function pthread_condattr_destroy
(attr : access pthread_condattr_t) return int is
begin
attr.Flags := 0;
return 0;
end pthread_condattr_destroy;
function pthread_cond_init
(cond : access pthread_cond_t;
attr : access pthread_condattr_t) return int
is
Result : int := 0;
begin
-- Condition variables should be initially created
-- empty.
cond.Sem := semBCreate (attr.Flags, SEM_EMPTY);
cond.Waiting := 0;
if cond.Sem = 0 then
Result := errno;
end if;
return Result;
end pthread_cond_init;
function pthread_cond_destroy (cond : access pthread_cond_t) return int is
Result : int;
begin
Result := semDelete (cond.Sem);
if Result /= 0 then
Result := errno;
end if;
return Result;
end pthread_cond_destroy;
function pthread_cond_signal
(cond : access pthread_cond_t) return int
is
Result : int := 0;
Status : int;
begin
-- Disable task scheduling.
Status := taskLock;
-- Iff someone is currently waiting on the condition variable
-- then release the semaphore; we don't want to leave the
-- semaphore in the full state because the next guy to do
-- a condition wait operation would not block.
if cond.Waiting > 0 then
Result := semGive (cond.Sem);
-- One less thread waiting on the CV.
cond.Waiting := cond.Waiting - 1;
if Result /= 0 then
Result := errno;
end if;
end if;
-- Reenable task scheduling.
Status := taskUnlock;
return Result;
end pthread_cond_signal;
function pthread_cond_wait
(cond : access pthread_cond_t;
mutex : access pthread_mutex_t) return int
is
Result : int;
Status : int;
begin
-- Disable task scheduling.
Status := taskLock;
-- Release the mutex as required by POSIX.
Result := semGive (mutex.Mutex);
-- Indicate that there is another thread waiting on the CV.
cond.Waiting := cond.Waiting + 1;
-- Perform a blocking operation to take the CV semaphore.
-- Note that a blocking operation in VxWorks will reenable
-- task scheduling. When we are no longer blocked and control
-- is returned, task scheduling will again be disabled.
Result := semTake (cond.Sem, WAIT_FOREVER);
if Result /= 0 then
cond.Waiting := cond.Waiting - 1;
Result := EINVAL;
end if;
-- Take the mutex as required by POSIX.
Status := semTake (mutex.Mutex, WAIT_FOREVER);
if Status /= 0 then
Result := EINVAL;
end if;
-- Reenable task scheduling.
Status := taskUnlock;
return Result;
end pthread_cond_wait;
function pthread_cond_timedwait
(cond : access pthread_cond_t;
mutex : access pthread_mutex_t;
abstime : access timespec) return int
is
Result : int;
Status : int;
Ticks : int;
TS : aliased timespec;
begin
Status := clock_gettime (CLOCK_REALTIME, TS'Unchecked_Access);
-- Calculate the number of clock ticks for the timeout.
Ticks := To_Clock_Ticks (To_Duration (abstime.all) - To_Duration (TS));
if Ticks <= 0 then
-- It is not worth the time to try to perform a semTake,
-- because we know it will always fail. A semTake with
-- ticks = 0 (NO_WAIT) will not block and therefore not
-- allow another task to give the semaphore. And if we've
-- designed pthread_cond_signal correctly, the semaphore
-- should never be left in a full state.
--
-- Make sure we give up the CPU.
Status := taskDelay (0);
return ETIMEDOUT;
end if;
-- Disable task scheduling.
Status := taskLock;
-- Release the mutex as required by POSIX.
Result := semGive (mutex.Mutex);
-- Indicate that there is another thread waiting on the CV.
cond.Waiting := cond.Waiting + 1;
-- Perform a blocking operation to take the CV semaphore.
-- Note that a blocking operation in VxWorks will reenable
-- task scheduling. When we are no longer blocked and control
-- is returned, task scheduling will again be disabled.
Result := semTake (cond.Sem, Ticks);
if Result /= 0 then
if errno = S_objLib_OBJ_TIMEOUT then
Result := ETIMEDOUT;
else
Result := EINVAL;
end if;
cond.Waiting := cond.Waiting - 1;
end if;
-- Take the mutex as required by POSIX.
Status := semTake (mutex.Mutex, WAIT_FOREVER);
if Status /= 0 then
Result := EINVAL;
end if;
-- Reenable task scheduling.
Status := taskUnlock;
return Result;
end pthread_cond_timedwait;
----------------------------
-- POSIX.1c Section 13 --
----------------------------
function pthread_mutexattr_setprotocol
(attr : access pthread_mutexattr_t;
protocol : int) return int is
begin
if protocol < PTHREAD_PRIO_NONE
or protocol > PTHREAD_PRIO_PROTECT
then
return EINVAL;
end if;
attr.Protocol := protocol;
return 0;
end pthread_mutexattr_setprotocol;
function pthread_mutexattr_setprioceiling
(attr : access pthread_mutexattr_t;
prioceiling : int) return int is
begin
-- Our interface to the rest of the world is meant
-- to be POSIX compliant; keep the priority in POSIX
-- format.
attr.Prio_Ceiling := prioceiling;
return 0;
end pthread_mutexattr_setprioceiling;
function pthread_setschedparam
(thread : pthread_t;
policy : int;
param : access struct_sched_param) return int
is
Result : int;
begin
-- Convert the POSIX priority to VxWorks native
-- priority.
Result := taskPrioritySet (thread,
To_Vxworks_Priority (param.sched_priority));
return 0;
end pthread_setschedparam;
function sched_yield return int is
begin
return taskDelay (0);
end sched_yield;
function pthread_sched_rr_set_interval (usecs : int) return int is
Result : int := 0;
D_Slice : Duration;
begin
-- Check to see if round-robin scheduling (time slicing)
-- is enabled. If the time slice is the default value (-1)
-- or any negative number, we will leave the kernel time
-- slice unchanged. If the time slice is 0, we disable
-- kernel time slicing by setting it to 0. Otherwise, we
-- set the kernel time slice to the specified value converted
-- to clock ticks.
Time_Slice := usecs;
if Time_Slice > 0 then
D_Slice := Duration (Time_Slice) / Duration (1_000_000.0);
Result := kernelTimeSlice (To_Clock_Ticks (D_Slice));
else
if Time_Slice = 0 then
Result := kernelTimeSlice (0);
end if;
end if;
return Result;
end pthread_sched_rr_set_interval;
function pthread_attr_init (attr : access pthread_attr_t) return int is
begin
attr.Stacksize := 100000; -- What else can I do?
attr.Detachstate := PTHREAD_CREATE_DETACHED;
attr.Priority := POSIX_SCHED_FIFO_LOW_PRI;
attr.Taskname := System.Null_Address;
return 0;
end pthread_attr_init;
function pthread_attr_destroy (attr : access pthread_attr_t) return int is
begin
attr.Stacksize := 0;
attr.Detachstate := 0;
attr.Priority := POSIX_SCHED_FIFO_LOW_PRI;
attr.Taskname := System.Null_Address;
return 0;
end pthread_attr_destroy;
function pthread_attr_setdetachstate
(attr : access pthread_attr_t;
detachstate : int) return int is
begin
attr.Detachstate := detachstate;
return 0;
end pthread_attr_setdetachstate;
function pthread_attr_setstacksize
(attr : access pthread_attr_t;
stacksize : size_t) return int is
begin
attr.Stacksize := stacksize;
return 0;
end pthread_attr_setstacksize;
-- In VxWorks tasks, we can set the task name. This
-- makes it really convenient for debugging.
function pthread_attr_setname_np
(attr : access pthread_attr_t;
name : System.Address) return int is
begin
attr.Taskname := name;
return 0;
end pthread_attr_setname_np;
function pthread_create
(thread : access pthread_t;
attr : access pthread_attr_t;
start_routine : Thread_Body;
arg : System.Address) return int is
begin
thread.all := taskSpawn (attr.Taskname,
To_Vxworks_Priority (attr.Priority), VX_FP_TASK, attr.Stacksize,
start_routine, arg);
if thread.all = -1 then
return -1;
else
return 0;
end if;
end pthread_create;
function pthread_detach (thread : pthread_t) return int is
begin
return 0;
end pthread_detach;
procedure pthread_exit (status : System.Address) is
begin
taskDelete (0);
end pthread_exit;
function pthread_self return pthread_t is
begin
return taskIdSelf;
end pthread_self;
function pthread_equal (t1 : pthread_t; t2 : pthread_t) return int is
begin
if t1 = t2 then
return 1;
else
return 0;
end if;
end pthread_equal;
function pthread_setspecific
(key : pthread_key_t;
value : System.Address) return int
is
Result : int;
begin
if Integer (key) not in Key_Storage'Range then
return EINVAL;
end if;
Key_Storage (Integer (key)) := value;
Result := taskVarAdd (taskIdSelf, Key_Storage (Integer (key))'Access);
-- We should be able to directly set the key with the following:
-- Key_Storage (key) := value;
-- but we'll be safe and use taskVarSet.
-- ??? Come back and revisit this.
Result := taskVarSet (taskIdSelf,
Key_Storage (Integer (key))'Access, value);
return Result;
end pthread_setspecific;
function pthread_getspecific (key : pthread_key_t) return System.Address is
begin
return Key_Storage (Integer (key));
end pthread_getspecific;
function pthread_key_create
(key : access pthread_key_t;
destructor : destructor_pointer) return int is
begin
Keys_Created := Keys_Created + 1;
if Keys_Created not in Key_Storage'Range then
return ENOMEM;
end if;
key.all := pthread_key_t (Keys_Created);
return 0;
end pthread_key_create;
-----------------
-- To_Duration --
-----------------
function To_Duration (TS : timespec) return Duration is
begin
return Duration (TS.ts_sec) + Duration (TS.ts_nsec) / 10#1#E9;
end To_Duration;
-----------------
-- To_Timespec --
-----------------
function To_Timespec (D : Duration) return timespec is
S : time_t;
F : Duration;
begin
S := time_t (Long_Long_Integer (D));
F := D - Duration (S);
-- If F has negative value due to a round-up, adjust for positive F
-- value.
if F < 0.0 then
S := S - 1;
F := F + 1.0;
end if;
return timespec' (ts_sec => S,
ts_nsec => long (Long_Long_Integer (F * 10#1#E9)));
end To_Timespec;
--------------------
-- To_Clock_Ticks --
--------------------
-- ??? - For now, we'll always get the system clock rate
-- since it is allowed to be changed during run-time in
-- VxWorks. A better method would be to provide an operation
-- to set it that so we can always know its value.
--
-- Another thing we should probably allow for is a resultant
-- tick count greater than int'Last. This should probably
-- be a procedure with two output parameters, one in the
-- range 0 .. int'Last, and another representing the overflow
-- count.
function To_Clock_Ticks (D : Duration) return int is
Ticks : Long_Long_Integer;
Rate_Duration : Duration;
Ticks_Duration : Duration;
begin
-- Ensure that the duration can be converted to ticks
-- at the current clock tick rate without overflowing.
Rate_Duration := Duration (sysClkRateGet);
if D > (Duration'Last / Rate_Duration) then
Ticks := Long_Long_Integer (int'Last);
else
-- We always want to round up to the nearest clock tick.
Ticks_Duration := D * Rate_Duration;
Ticks := Long_Long_Integer (Ticks_Duration);
if Ticks_Duration > Duration (Ticks) then
Ticks := Ticks + 1;
end if;
if Ticks > Long_Long_Integer (int'Last) then
Ticks := Long_Long_Integer (int'Last);
end if;
end if;
return int (Ticks);
end To_Clock_Ticks;
end System.OS_Interface;