SegFault

SIM7000E | Converting Libraries

Dec 5 2021

As I already mentioned in a previous post, the simcom module allows you run your own code right inside the chip. This allows for some pretty interesting applications and reduces external components to a minimum. However the libraries which are used to interface to the kernel running inside the module are only provided as a ARMCC version which makes them quite hard to use with a more standard compiler like GCC. Because of this I developed an open source variant which allows compilation with gcc and can be modified as needed to add more features.

There are two libraries included within the SDK:

  • txm_lib.lib
  • timer_dam_lib.lib

txm_lib.lib

This library provides an Interface to the underlying realtime operating system ThreadX® developed by Express Logic and contains wrapper functions to do syscalls, the entry point for new threads, as well as an callback handler running inside the module.

_txm_module_thread_shell_entry

This function is called when a new thread gets created and should call the entry function with its argument. This extra step allows adding code before and after each threads execution. This can be usefull for various things, like for example initializing and destroying Thread Local Storage. If the current thread is the first thread in the module it also initializes some global variables with information required to do system calls and starts the module's callback thread. After the real thread function exits, the thread suspends itself.

VOID _txm_module_thread_shell_entry(TX_THREAD *thread_ptr, TXM_MODULE_THREAD_ENTRY_INFO *thread_info) {
    if(thread_info->txm_module_thread_entry_info_start_thread) {
        _txm_module_entry_info = thread_info->txm_module_thread_entry_info_module;
        _txm_module_instance_ptr = thread_info;
        _txm_module_kernel_call_dispatcher = thread_info->txm_module_thread_entry_info_kernel_call_dispatcher;
        while(_txm_module_kernel_call_dispatcher == NULL);
        tx_thread_resume(thread_info->txm_module_thread_entry_info_callback_request_thread);
    }
    thread_info->txm_module_thread_entry_info_entry(thread_info->txm_module_thread_entry_info_parameter);
    txm_module_thread_system_suspend(thread_ptr);
}

_txm_module_system_call

While creating the first thread, the kernel provides a function which is used to make system calls. Each call contains 3-12 parameters, as well as an ID to identify which kernel function should be called. If the system call takes 3 or less parameters, they are directly passed to the function. Unused parameters get passed a zero. If there are more than 3 parameters the remaining params get stored to a buffer whose address is then passed to the kernel via the third parameter. Below is an example for a system call with 4 parameters:

ULONG _txm_module_system_call4( ULONG request, ULONG param_1, ULONG param_2, ULONG param_3, ULONG param_4) {
    ULONG args[2];
    args[0] = param_3;
    args[1] = param_4;
    return _txm_module_kernel_call_dispatcher(request, param_1, param_2, (ULONG)&args);
}

_txm_module_callback_request_thread_entry

The last function contained in txm_lib handles callbacks sent to the application by the system. In order to allow for this the kernel provides two queues to the application. The thread runs in a loop, waiting for notifications and executing the contained callback function. After it is done the notification is put back into a second queue in order to single the kernel it is done.

VOID _txm_module_callback_request_thread_entry(ULONG id) {
    // parameter seems to be unused in original code ?
    // r0 is overwritten right after entry
    // but it is in the prototype, so we include it here
    (void)id;

    TXM_MODULE_CALLBACK_NOTIFY notify;
    int ret;
    TX_QUEUE* req_queue = _txm_module_entry_info->txm_module_thread_entry_info_callback_request_queue;
    TX_QUEUE* resp_queue = _txm_module_entry_info->txm_module_thread_entry_info_callback_response_queue;

    do {
        // Receive notify
        ret = tx_queue_receive(req_queue, ¬ify, TX_WAIT_FOREVER);
        if(ret != TX_SUCCESS) break;
        switch(notify.txm_module_callback_notify_type) {
            case 0:
            case 1:
            case 2:
            case 3:
                ((void(*)(ULONG))notify.txm_module_callback_notify_application_function)
                    (notify.txm_module_callback_notify_param_1);
                break;
            case 4:
                ((void(*)(ULONG,ULONG))notify.txm_module_callback_notify_application_function)
                    (notify.txm_module_callback_notify_param_1, notify.txm_module_callback_notify_param_2);
                break;
            default:
                break;
        }
        ret = tx_queue_send(resp_queue, ¬ify, TX_WAIT_FOREVER);
    } while(ret == TX_SUCCESS);
}

txm_lib.lib

The second library is provided by Qualcomm and contains an interface to the timer extensions integrated by Qualcomm. It contains eight functions, however only two are of interest as the remaining are just wrappers around system calls.

qapi_Timer_Def

The first function is qapi_Timer_Def, which creates a Timer. In order to do this it first queries the size of qapi_TIMER_handle from the kernel and then allocates memory in order to hold it. If all of this is successfull it finally creates the real timer inside the newly allocated memory region. The following code is the way it is implemented in Qualcomm's provided library, however I am quite certain, that there is actually a bug in the line with "txm_module_object_deallocate", as it should get the result of "txm_module_object_allocate" and not a pointer to the result. Note that qapi_Timer_Undef uses it the correct way, which strengthens the presumption.

qapi_Status_t qapi_Timer_Def(qapi_TIMER_handle_t* timer_handle, qapi_TIMER_define_attr_t* timer_attr) {
    if(timer_handle == NULL) return QAPI_ERR_INVALID_PARAM;
    static int handle_size = 0;
    if(handle_size == 0) {
        _txm_module_system_call12(TXM_QAPI_TIMER_GET_TIMER_TYPE_SIZE, (ULONG)&handle_size, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
        if(handle_size == 0) return QAPI_ERR_TIMEOUT;
    }
    if(txm_module_object_allocate(timer_handle, handle_size) != 0 || *timer_handle == NULL) return QAPI_ERR_NO_MEMORY;
    qapi_Status_t res = _txm_module_system_call12(TXM_QAPI_TIMER_DEF, (ULONG)timer_handle, (ULONG)timer_attr, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    if(res != QAPI_OK) {
        txm_module_object_deallocate(timer_handle);
    }
    return res;
}

qapi_Timer_Undef

The second function is qapi_Timer_Undef, which destroys the provided timer and deallocates the memory region.

qapi_Status_t qapi_Timer_Undef(qapi_TIMER_handle_t timer_handle) {
    qapi_Status_t res = _txm_module_system_call12(TXM_QAPI_TIMER_UNDEF, (ULONG)timer_handle, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
    if(res == QAPI_OK) {
        txm_module_object_deallocate(timer_handle);
    }
    return res;
}