Runtime Loadable Procedures.

Namespace: GHIElectronics.NETMF.Native
Assembly: GHIElectronics.NETMF.Native (in GHIElectronics.NETMF.Native.dll) Version: 4.1.8.0 (4.1.8.0)

Syntax

C#
public static class RLP

Remarks

RLP allows developers to load/execute their own compiled C/Assembly procedures at runtime. RLP supports loading ELF executables and, for advanced users, loading binary images using GHIElectronics.NETMF.Hardware.LowLevel.AddressSpace class. The user can load multiple images in the reserved RLP memory region.
Compiled C/Assembly procedures are executed natively and can perform much faster than managed application. This is very useful for calculation intensive applications.
Also, RLP has extensions that can be used directly from native code. You can use dynamic memory allocation, perform native tasks at a specific time, install interrupts and much more.

Steps to execute RLP:
  • Create a C or Assembly program. Make sure the program uses the available address space for the RLP. This is set by the linker using the linker scripts.
  • Make sure that the exposed C procedures (called from C#) are in the required form.
  • Debug the program as needed.
  • Include the program binary image or ELF file (recommended) in the managed application. For example, it can be a Resource, a file on an SD card, a file obtained over a network...etc.
  • In the managed application, load the native program into the reserved region.
  • You can now invoke (access) the native functions from the managed application.

Platform Support and RLP Memory Mapping
The user must make sure that the native programs use the assigned memory for program code and for variables. The following platforms support RLP:

ChipworkX: by default, RLP is enabled and unlocked.
RLP Address: 0xA0000000. RLP Size: 0x001FB3FC.
For advanced users, this region is a cached mapping of non-cached SDRAM region starting at 0x23E00000.

EMX: by default, RLP is enabled but locked.
Unlocking information is provided online under your GHI account. Unlocking must be done before using RLP each time the application starts.
RLP Address: 0xA0F00000. RLP Size: 0x000FFFFC.

USBizi: by default, RLP is not enabled and locked.
Enabling RLP will reserve about 10KB of the user RAM memory for RLP. To enable RLP call RLP.Enable(). This call is only needed once. It will reserve about 10 KB for RLP and then reboot the device automatically. To restore the original RAM configurations without RLP, the firmware must be erased using GHI's boot loader.
Unlocking information is provided online under your GHI account. Unlocking must be done before using RLP each time the application starts.
RLP Address: 0x40000440. RLP Size: 0x000027FC.

USBizi has a small RAM memory and ELF files might be large. It is recommended to load the ELF files at the start up of the application before creating other objects. Once done, the ELF files can be disposed. For example:
CopyC#
public static void Main()
{
    // Program start up on USBizi

    // Unlock RLP if needed

    // Load ELF file first because it might be large
    byte[] elf_file = Resources.GetBytes(Resources.BinaryResources.RLP_test);
    RLP.LoadELF(elf_file);
    // now load all RLP.Procedure(s)
    // ......

    // dispose of the elf file to reclaim memory
    elf_file = null;
    Debug.GC(true);

    // start the application
    // ...

Loading Native Images
Using an ELF executable is easier than using a raw binary image because it has information about the load regions and the available functions.
For example:
CopyC#
RLP.LoadELF(elfImage); // Load file
RLP.Procedure Foo = RLP.GetProcedure(elfImage, "Foo"); // Get some procedure
Foo.Invoke(...); // Access native code
Note that the global variables are not initialized to zero automatically. To do this, you need to provide the BSS region name to InitializeBSSRegion.
If you are using the examples and linker script provided with the SDK, do the following:
CopyC#
RLP.LoadELF(elfImage); // Load file
RLP.InitializeBSSRegion();
...
It is also possible to load a binary image directly.
For example:
CopyC#
AddressSpace.Write(loadAddress, binaryImage, 0, binaryImage.Length); // Load binary image
RLP.Procedure Foo = new RLP.Procedure(procedureAddress); // Create a procedure with a known address
Foo.Invoke(...); // Access native code
Calling Procedures from Managed Code (C#)
After loading the image as mentioned above, you call the native code directly and pass arguments. You need to get the Procedure(s) as mentioned above and then call Procedure.Invoke(...).
Procedures can be invoked in many ways:
Invoke() is a call without arguments.
Invoke(params object[] argList) is a call with a variable argument list. You can send up to 8 variable arguments, RLP.MAX_ARGS.
Examples:
CopyC#
byte[] byteArray = new byte[10];
Foo1.Invoke(); // No arguments are sent
Foo2.Invoke(5); // send the number 5
Foo3.Invoke("Some String"); // Send a string
Foo4.Invoke(5, byteArray, "Some String", 53.14f); // send several arguments including a float variable
The following are supported for the variable argument list:
C# TypeCorresponding C Type
byte[]unsigned char[]
stringchar[] null-terminated
byteunsigned char
sbytechar
charchar
shortshort
ushortunsigned short
intint
uintunsigned int
boolunsigned char
floatfloat
Important note: Variable arguments (including strings) are copied before they are passed to native procedures. Changing the arguments in the native side will not affect the arguments in the managed application. This does not apply to C# byte[] arrays where changes in the native side will change the array data in the managed side.
Note that the variable argument list has some overhead when invoking the native code. The least overhead is achieved when sending byte[] from C# as opposed to sending a string or int.

Also, procedures can be called as follows: InvokeEx(generalArgument, params object[] argList) which includes the following:
argList is the same as Invoke above and has the same rules. It is optional.
generalArgument is an array that can be one of several types byte [], int [], float [] ...etc
This array is passed as a pointer to the native code. Changing array values in the native code will affect the array in the managed code. Also, this argument has minimum overhead when invoking compared to using argList.

Examples continued:
CopyC#
byte[] byteArray = new byte[10];
byte[] byteArray2 = new byte[10];
int[] intArray = new int[10];
float[] floatArray = new float[10];
Foo5.InvokeEx(byteArray); // uses the general array // no variable arguments
Foo6.InvokeEx(floatArray, "Some String");     // uses a general array   // one variable argument
Foo7.InvokeEx(intArray, byteArray);     // uses a generalArray (intArray) // one variable argument a byte[]
Foo8.InvokeEx(byteArray, byteArray2);     // uses a generalArray (byteArray)  // one variable argument (byteArray2)
Native Procedures Implementation (C Language)
The managed side is flexible and the number/types of arguments can be different. However, all of them is mapped to a single type of function in the native code.
The native procedures (C/Assembly) that will be invoked from managed code (C#) must be as follows:
int FunctionName(Type *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
generalArray is the same as managed C# array. Changing values in the native side will also change them in the managed application.
The Type should be replaced appropriately. For example:
C# is passing byte[] array. The corresponding C declaration is: Type *generalArray replaced by unsigned char *generalArray.
C# is passing float[] array. The corresponding C declaration is: Type *generalArray replaced by float *generalArray.
and so on...
You can also replace it in the native side with void *generalArray. This will work in all situations, but you have to cast the void pointer when used.
args are all the variable list arguments passed as apointers array. See examples next.
argsCount is the variable arguments count. Zero means no arguments are passed except for generalArray.
argSize contains the size of each one of the variable arguments in bytes. For example, passing an int will result in 4 bytes and passing an array of 10 bytes will result in 10.

When some of these arguments are not used, for example the generalArray or the variable argument list, they are not necessarily NULL and should not be accessed.

The following example refers to the same Foo1, Foo2, Foo3... functions above but in the native side:
CopyC
// C#: Foo1.Invoke(); // No arguments are sent
int Foo1(unsigned int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // argsCount == 0
    // No arguments are passed

    return 0;    // Some return value
}

// C#: Foo2.Invoke(5); // send the number 5
int Foo2(unsigned int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // passed one argument, an int.
    // argsCount == 1
    // argSize[0] == 4
    int number = *(int*)args[0];
    // number == 5

    return 0;    // Some return value
}

// C#: Foo3.Invoke("Some String"); // Send a string
int Foo3(unsigned int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // passed one argument, a string.
    // argsCount == 1
    // argSize[0] == 12 (includes the null at the end)
    char *string = (char*)args[0];
    // string == "Some String"

    return 0;    // Some return value
}

// C#: Foo4.Invoke(5, byteArray, "Some String", 53.14f); // send several arguments including a float variable
int Foo4(unsigned int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // passed four arguments.
    // argsCount == 4
    // argSize[0] == 4
    // argSize[1] == 10
    // argSize[2] == 12
    // argSize[3] == 4
    int number = *(int*)args[0];        // number == 5
    unsigned char *byteArray = (unsigned char*)args[1];    // same byteArray in C#
    char *string = (char*)args[2];             // string == "Some String"
    float fNumber = *(float*)args[3];        // fNumber == 53.14f

    return 0;    // Some return value
}

// C#: Foo5.InvokeEx(byteArray); // uses the general array // no variable arguments
int Foo5(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // argsCount == 0
    // generalArray is the same as byteArray in C#

    return 0;    // Some return value
}

// C#: Foo6.InvokeEx(floatArray, "Hello");     // uses a general array   // one variable argument
int Foo6(float *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // argsCount == 1
    // argSize[0] == 6 (includes the null at the end)
    char *string = (char*)args[0]; // string == "Hello"
    // generalArray is the same as floatArray in C#

    return 0;    // Some return value
}

// C#: Foo7.InvokeEx(intArray, byteArray);     // uses a generalArray (intArray) // one variable argument (byteArray)
int Foo7(int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // argsCount == 1
    // argSize[0] == 10
    unsigned char *byteArray = (unsigned char*)args[0];    // same byteArray in C#
    // generalArray is the same as intArray in C#

    return 0;    // Some return value
}

// C#: Foo8.InvokeEx(byteArray, byteArray2);     // uses a generalArray (byteArray)  // one variable argument (byteArray2)
int Foo8(unsigned char *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    // argsCount == 1
    // argSize[0] == 10
    unsigned char *byteArray2 = (unsigned char*)args[0];    // same byteArray2 in C#
    // generalArray is the same as byteArray in C#

    return 0;    // Some return value
}

Native Procedures Important Notes
  • Native function calls suspend all other C# threads and return only when the native procedure finishes.
  • Hardware interrupts are always active unless they are explicitly stopped.
  • You cannot pin arrays. When you receive pointers from managed code and use them in C/Assembly code, do not store them. After the native procedure returns, these pointers are invalid. When the garbage collector start working, it might change the arrays/pointers locations. The following is invalid and might crash the system:
    CopyC#
    byte[] array = new byte [100];
    Initialize.Invoke(array);       // This calls a native procedure that stores the array's pointer internally.
    ProcessArray.Invoke();          // This calls a native procedure that processes the previous array data.
    In the example above, after the first native call, the pointer is no longer valid and cannot be used for the second procedure call.
    The solution is to copy the data internally in the native code. It can be copied to the RLP memory region or to a dynamically allocated memory using RLPext->malloc. Also, note that the custom-heap memory does not change locations between managed calls like the normal managed memory.
Native RLP Extensions
RLP provides native code ability. The extensions provide additional functionalities and procedures that can be called from your native code. For example, installing an interrupt service routine, allocating memory...etc
One of the extensions is native tasks support. A task must be initialized first and then it can be scheduled to run together with the other threads when appropriate or it can be scheduled to run after a specific amount of time. When the native task runs, it blocks all other managed/native threads/tasks. Once the task finishes, the managed threads will continue to run. See RLPext->Task and the RLP extensions examples for more information.
The extensions are available from RLP.h which is included in the Examples section below.
Note that the correct RLP region address and size must be updated in the header file (RLP.h) before using the extensions. These are defined as RLP_ADDRESS and RLP_SIZE. See Platform Support and RLP Memory Mapping section above for the RLP memory location.

Extensions are accessed through the RLPext structure. Here is an example:
CopyC
#include <RLP.h>

#define USED_FIRMWARE_VERSION 0x01020304
void MyFunction(void)
{
    ...

    // This is not necessary but an extra caution check
    if(RLPext->magic != RLP_EXT_MAGIC)
        MyError();   // Some user function

    // This is not necessary but an extra caution check. See if the firmware is correct...
    if(RLPext->firmwareVersion != USED_FIRMWARE_VERSION)
        MyError();    // Some user function  

    // Actual call. Let's allocate some memory and then free it.
    char *buffer = (char*)RLPext->malloc(500);
    RLPext->free(buffer);

    ...
}

Some of the extensions control an I/O (also called GPIO) pin. They require the pin number which depends on the used platform.
EMX: The pin number for EMX.Pin.IOxx is xx.
USBizi: The pin number for USBizi.Pin.IOxx is xx. If you are using a FEZ assembly (FEZ Domino, FEZ Panda...etc), look up the internal pin numbers for the FEZ_Pin.Digital.Dixx. For example, Di15 is not necessarily I/O 15.
ChipworkX: The pin number for ChipworkX.Pin.PAxx is RLP_GPIO_CWX_PORTA(xx). Similar macros exist for other ports names. Please see RLP.h Definitions below.

RLP.h Definitions
DefinitionDescription
RLP_TRUETrue boolean value.
RLP_FALSEFalse boolean value.
RLP_GPIO_NONENo GPIO is used.
RLP_GPIO_CWX_PORTA(x)For ChipworkX, GPIO number on Port A. For example, pin PA10 is RLP_GPIO_PORTA(10).
RLP_GPIO_CWX_PORTB(x)For ChipworkX, GPIO number on Port B. For example, pin PB10 is RLP_GPIO_PORTB(10).
RLP_GPIO_CWX_PORTC(x)For ChipworkX, GPIO number on Port C. For example, pin PC10 is RLP_GPIO_PORTC(10).
RLP_GPIO_INT_NONEInterrupt type is none.
RLP_GPIO_INT_EDGE_LOWInterrupt type is on low edge.
RLP_GPIO_INT_EDGE_HIGHInterrupt type is on high edge.
RLP_GPIO_INT_EDGE_BOTHInterrupt type is on both edges.
RLP_GPIO_RESISTOR_DISABLEDGPIO resistor state is disabled.
RLP_GPIO_RESISTOR_PULLDOWNGPIO resistor state is pulled down.
RLP_GPIO_RESISTOR_PULLUPGPIO resistor state is pulled up.
RLP_CALLBACK_FPNCallback function type. User function should be as follows: void UserFunction(void* arg);
arg: is an optional user argument.
RLP_GPIO_INTERRUPT_SERVICE_ROUTINEGPIO callback function type. User function should be as follows: void UserFunction(unsigned int Pin, unsigned int PinState, void* Param);
Pin: GPIO pin number.
PinState: True or false.
Param: Optional user argument.
RLP_InterruptInputPinArgs Pin arguments structure.
RLP_InterruptInputPinArgs.GlitchFilterEnable: True to enable the glitch filter.
RLP_InterruptInputPinArgs.IntEdge: interrupt edge state.
RLP_InterruptInputPinArgs.ResistorState: resistor state.
RLP_TaskRepresents a native task.
RLPext Structure
RLPext MemberDeclarationDescription
magicunsigned int magicThis is fixed to RLP_EXT_MAGIC. It can be used for checking purposes.
firmwareVersionunsigned int firmwareVersionPlatform's firmware version number in BCD. 0x01020304 denotes firmware version 01.02.03.04
magicSizeunsigned int magicSizeThis is the size of RLPext in GHI firmware. It can be used for checking purposes.
Interrupt.Installunsigned int Install(unsigned int Irq_Index, RLP_CALLBACK_FPN ISR, void* ISR_Param)Installs an ISR.
Irq_Index: (Peripheral ID). Refer to the processor datasheet for details.
RLP_CALLBACK_FPN: Your ISR must use the same type. See Definitions section.
ISR_Param: Optional user argument. This is passed to the user's ISR when an interrupt occurs.
Return: True or false for success state.
Interrupt.Uninstallunsigned int Uninstall(unsigned int Irq_Index)Uninstalls an ISR.
Irq_Index: (Peripheral ID). Refer to processor datasheet for details.
Return: True or false for success state.
Interrupt.Disableunsigned int Disable(unsigned int Irq_Index)Disables an ISR.
Irq_Index: (Peripheral ID). Refer to the processor datasheet for details.
Return: True or false for success state.
Interrupt.Enableunsigned int Enable(unsigned int Irq_Index)Enables an ISR.
Irq_Index: (Peripheral ID). Refer to the processor datasheet for details.
Return: True or false for success state.
Interrupt.IsEnabledunsigned int IsEnabled(unsigned int Irq_Index)Checks if ISR is enabled.
Irq_Index: (Peripheral ID). Refer to the processor datasheet for details.
Return: True or false for enabled state.
Interrupt.IsPendingunsigned int IsPending(unsigned int Irq_Index)Checks if ISR is pending.
Irq_Index: (Peripheral ID). Refer to the processor datasheet for details.
Return: True or false for pending state.
Interrupt.GlobalInterruptDisableunsigned int GlobalInterruptDisable()Disables interrupts.
Return: True or false for the previous interrupt state.
Interrupt.GlobalInterruptEnableunsigned int GlobalInterruptEnable()Enables interrupts.
Return: True or false for the previous interrupt state.
Interrupt.IsGlobalInterruptEnabledunsigned int IsGlobalInterruptEnabled()Checks the interrupt state.
Return: True or false for the interrupt state.
GPIO.EnableOutputModevoid EnableOutputMode(unsigned int Pin, unsigned int InitialState)Enables an output pin.
Pin: GPIO number.
InitialState: True or false for initial state.
GPIO.EnableInputModeunsigned int EnableInputMode(unsigned int Pin, unsigned int ResistorState)Enables an input pin.
Pin: GPIO number.
ResistorState: See Definitions section.
Return: True or false for success state.
GPIO.EnableInterruptInputModeunsigned int EnableInterruptInputMode(unsigned int Pin, RLP_InterruptInputPinArgs *args, RLP_GPIO_INTERRUPT_SERVICE_ROUTINE ISR, void* ISR_Param)Enables an interrupt pin.
Pin: GPIO number.
args: GPIO args, see Definitions section.
ISR: Your ISR must use the same type. See Definitions section.
ISR_Param: Optional user argument. This is passed back to the user when an interrupt occurs.
Return: True or false for success state.
GPIO.ReadPinunsigned int ReadPin(unsigned int Pin)Reads the pin state.
Pin: GPIO pin number.
Return: True or false for the pin state.
GPIO.WritePinvoid WritePin(unsigned int Pin, unsigned int PinState)Writes the pin state.
Pin: GPIO pin number.
PinState: True or false for the pin state.
GPIO.ReservePinunsigned int ReservePin(unsigned int Pin, unsigned int reserve)Reserves the pin. When a pin is reserved, accessing the pin from C# application will cause an exception.
Pin: GPIO pin number.
reserve: True or false for reserve state.
Return: True or false for success state.
GPIO.IsReservedunsigned int IsReserved(unsigned int Pin)Checks if the pin is reserved. When a pin is reserved, accessing the pin from C# application will cause an exception.
Pin: GPIO pin number.
Return: True or false for reserve state.
mallocvoid* malloc(unsigned int len)Allocates dynamic memory. NOTE that this memory is shared with Micro Framework. Make sure to free the buffers when done. The location of the allocated memory does not change during the lifetime of the application.
len: Memory allocation length.
Return: Pointer to memory location.
freevoid free(void *ptr)Frees previously allocated memory.
ptr: Pointer to memory location.
malloc_CustomHeapvoid* malloc_CustomHeap(unsigned int len)Allocates dynamic memory on the custom heap in NETMF.
len: Memory allocation length.
Return: Pointer to memory location.
free_CustomHeapvoid _CustomHeap(void *ptr)Frees previously allocated memory in custom heap.
ptr: Pointer to memory location.
Delayvoid Delay(unsigned int microSeconds)Provides a delay.
microSeconds: Delay in microseconds.
Task.Initializevoid Initialize(RLP_Task *task, RLP_CALLBACK_FPN taskCallback, void* arg, unsigned int isKernelMode)Initializes a task structure. The task will run when Schedule/ScheduleTimeOffset is called. When a task is executing, it blocks all other managed/native threads/tasks. The user must return from the task in order for the managed threads to run again.
task: User's task.
taskCallback: Callback when the task runs.
arg: This is passed to the callback function.
isKernelMode: True or False value. Always try to use False (non kernel mode). In this mode, the task executes when appropriate among other managed threads without interrupting them. True (kernel mode) executes the task at a specific time. It interrupts everything else and stops the hardware interrupts while executing.
Task.Schedulevoid Schedule(RLP_Task *task)Schedules a task to run when appropriate. It is only scheduled to run once. It can be scheduled again in the task's callback if needed.
task: User's task.
Task.ScheduleTimeOffsetvoid ScheduleTimeOffset(RLP_Task *task, unsigned int timeOffset_us)Schedules a task to run when appropriate after a specified time period. It is only scheduled to run once. It can be scheduled again in the task's callback if needed. If isKernelMode is True, the task will execute at a precise time.
task: User's task.
timeOffset_us: Time offset from now specified in microseconds.
Task.Abortvoid Abort(RLP_Task *task)Aborts the task if scheduled.
task: User's task.
Task.IsScheduledunsigned int IsScheduled(RLP_Task *task)Checks if the task is scheduled to run.
task: User's task.
Return: True if the task is currently scheduled to run.
PostManagedEventvoid PostManagedEvent(unsigned int data)Queues a managed event to be sent to RLPEvent (not sent immediately).
data: User supplied data.

Examples

The following contains examples and the RLP extensions header file (RLP.h): Download.

Inheritance Hierarchy

System..::..Object
  GHIElectronics.NETMF.Native..::..RLP

See Also