Open jjhugues opened 3 years ago
Here is an example from A.9 (3)
subprogram Send_Output
features
OutputPorts: in parameter <implementation-dependent port list>;
-- List of ports whose output is transferred
SendException: out event data; -- exception if send fails to complete
end Send_Output;
subprogram Put_Value
features
Portvariable: requires data access; -- reference to port variable
DataValue: in parameter; -- value to be stored
DataSize: in parameter; - size in bytes (optional)
end Put_Value;
The critical path here is to define a portable way to refer to port variables and to data values.
Ocarina proposes the following approach, implemented as of today
Note that this approach has been extended to the Application RTS: Send_Output, Put_Value, Get_Value, Get_Count, Next_Value, and Updated. Receive_Input has not been made visible at this stage
This approach allows for
activity.h
, just a legacy conventionvoid waterlevelmonitoring_body(__po_hi_task_id self) {
/* AADL request type, this type is generated form the AADL model, does not need to be documented in the standard .
This provides an abstract DataValue parameter for languages without polymorphism */
__po_hi_request_t request;
/* First we need a container to store the data, this is achieved using the PORT_VARIABLE field of the request. */
request.PORT_VARIABLE (waterlevelmonitoring_thread, /* thread classifier */
wateralarm /* port name */) = waterlvl;
/* Then, we send the request through the thread *local* port (i.e. the port on the emitter side).
Note: this is a legacy decision, this part can be renamed easily */
__po_hi_gqueue_store_out /* put_value */
(self,
LOCAL_PORT (waterlevelmonitoring_thread, wateralarm),
&request);
}
Note: since we cannot have function overloading in C, we decided to have one set of functions, and tweak the request type to be polymorphic and be the C union of all relevant thread interfaces. Having one set of function per interface does not respect the initial intent of the standard.
_Another approach like in ARINC653 APEX would have considered a more radical approach based on void */sizet parameter tuple. This would have come at the expense of the analyzability of the underlying middleware, especially the respect of interface types.
A variant might consider self
to be a more complex structure that has access to port variables, e.g.
__po_hi_gqueue_store_out /* put_value */
(self->wateralarm,
&request);
However, self->wateralarm
is evaluated dynamically, whereas the previous approach can be aggressively optimized by a C compiler thanks to static arguments.
A variant might also consider an additional RTS that provides an allocator for the request, e.g.
void waterlevelmonitoring_body(__po_hi_task_id self) {
/* AADL request type, allocated (from a pool etc) attached to self */
__po_hi_request_t *request = new_request (self);
However, one has to predefine how many instances of request should be pre allocated per task, adding more configuration space. With the solutions above, the request lifecycle is managed by the user.
@Etienne13 can you please provide some details on how this is addressed in Ramses?
First of, in RAMSES the signatures of runtime services are gathered in a header file named aadl_runtime_services.h
. This is an important starting point as it tells C developers what is the header to include in order to use these services.
The services that are implemented are:
error_code_t Put_Value(port_reference_t * port, runtime_addr_t value);
error_code_t Send_Output(port_reference_t * port);
error_code_t Next_Value(port_reference_t * port);
error_code_t Get_Value(port_reference_t * port, runtime_addr_t dst);
error_code_t Updated(port_reference_t * port, uint8_t * fresh_flag);
error_code_t Get_Count(port_reference_t * port, uint16_t * count_res);
error_code_t Receive_Input(port_reference_t * port);
error_code_t Receive_Input_Thread(thread_config_t * config);
error_code_t Send_Output_Thread(thread_config_t * config);
error_code_t Await_Mode(thread_config_t * config);
error_code_t Await_Dispatch(thread_config_t * config);
error_code_t Error_Handler(thread_config_t * config, error_code_t error)
void Raise_Error (thread_config_t * config, error_code_t error)
You see that it returns an error_cote_t
, which is currently defined as:
typedef enum error_code_t
{
RUNTIME_OK,
RUNTIME_EMPTY_QUEUE,
RUNTIME_FULL_QUEUE,
RUNTIME_LOCK_ERROR,
RUNTIME_OUT_OF_BOUND,
RUNTIME_INVALID_PARAMETER,
RUNTIME_INVALID_SERVICE_CALL,
RUNTIME_OUTDATED_INPUT, // not fresh for an input port of an immediate connection
RUNTIME_SYSCALL_ERROR,
RUNTIME_START_THREAD_ERROR,
RUNTIME_WAIT_DISPATCH_ERROR,
RUNTIME_DEADLINE_MISS
} error_code_t;
Note: these services are also described in an aadl_runtime.aadl
file in RAMSES. In this model, types and services are "opaque" (only the name of types and associated files are described).
When used in C user code, with the code generation convention named AADL, it looks like this:
#include "aadl_runtime_services.h"
#include "gtypes.h"
void send(__sender_spg_context* ctx)
{
uint8_t msg;
Put_Value(ctx->spg_event_port, &msg);
...
}
Explanation: __sender_spg_context
is a type generated in gtypes.h from a subprogram called sender_spg in the aadl model; __sender_spg_context
is a struct containing a field spg_event_port
which is of type port_reference_t. It has been generated because as an image of the aadl subprogram port called spg_event_port
. It should also contain a field called "config" which points to the thread_config_t
in which the subprogram is called.
error_code_t the_name_you_want(runtime_addr_t context);
it is supposed to return RUNTIME_OK if it recovered the error; a different error code otherwise. The type of the context is opaque, and can be casted to the generated type for a thread context (__t1_context_t for a thread subcomponent t1 having the_name_you_want as a recovery entrypoint source text).
Note: Error_Handler is called when an error occur in a runtime service. If defined, it calls the recovery entrypoint. If error is recovered, it return RUNTIME_OK. Otherwise, if defined, it sends the error code through the error port of the thread.
Some considerations after the posts above:
[ ] Both approaches do not make visible Receive_Input
, should it be part of Executive Services, and not of Application Services?
[ ] Both approaches change Send_Output
to take only one port (and not a list).
[ ] There is a notion of context for a thread and for a subprogram. Should the subprogram have access only to a subset of its enclosing thread (e.g. ports), or all. (this is a core issue since it could be part on the discussion on port freezing and associated visibility rules at the time of dispatch)
aadl.h
; then this file may make visible other definitions through inclusion of other files, eventually generated
The code generation annex does not provide a precise definition of RTS. At the time of completion of the previous edition, it has been decided to defer until more implementations are created. We have now reached this state with many tools: Ocarina, RAMSES, ISOCELES and HAMR.