Sunday, April 5, 2015

Interfacing user code with MTK MAUI OS – Simcom EmbeddedAT vs Mediatek Labs LinkIt ONE approach

[This post is dedicated to Fernvale project]

(Intro skipped for now)

EmbeddedAT defines API in terms of function pointers. Locations of these function pointers are provided in symbol file (.sym) supplied to ARM RealView linker during user app linking. On OS firmware side, these function pointers are packed together in a section which then appear towards beginning of Flash, and whose address are thus relatively stable even if firmware is updated. Flash map is specifically:


COREAPI is the API function pointer table (thunk table, vtable). “app” is user application.

LinkIt ONE uses more elaborated scheme which emulates protected-OS syscall approach.

First of all, these “syscalls” happen via message passing between user and OS tasks (aka threads).
Here's typical call from user-facing API function:

LTask.remoteCall(linkit_sms_delete_handler, (void*)_msgId);

remoteCall is implemented as (some code skipped):

void _LTaskClass::remoteCall(remote_call_ptr func, void* userdata)
m_msg.remote_func = (remote_call_ptr)func;
m_msg.userdata = userdata;
m_msg.signal = m_signal;
sendMsg(VM_MSG_ARDUINO_CALL, &m_msg);

So, it packs reference to remote function and its args into a message and sends it to a remote execution task, then wait it to signal completion. This all happens synchronously, protected by a mutex. Note that remote function is a real function identified by address, not a syscall number. It is defined as:

boolean linkit_sms_delete_handler(void* userdata)
VMUINT16 msg_id = (VMUINT16)((VMUINT32)userdata);
vm_sms_delete_msg(msg_id, linkit_sms_delete_callback, userdata);
return false;

I.e. it calls similarly named vm_* function. We saw another vm_* function already - vm_signal_wait. Let's see how that is implemented. The source code is actually not provided, that function is precompled in libmtk.a:vmthread.o. But looking at that object file, there's very little magic: it defines bunch of vm_* functions, and bunch of corresponding _vm_* variables, and has one outside function call: vm_get_sym_entry(), it also has strings matching vm_* function names. One can speculate that each of vm_* functions defined there, take a corresponding string, pass to vm_get_sym_entry() to look up address of that symbol, then call that address, caching it in _vm vars for next time.

Grepping source, vm_get_sym_entry turns out to be not a function proper, but a function pointer. And it gets initialized from a value passed to function:

void gcc_entry(unsigned int entry, unsigned int init_array_start, unsigned int count)
vm_get_sym_entry = (vm_get_sym_entry_t)entry;

And gcc_entry is marked as an ELF entry point. So, the overall structure is: user app is compiled to an ELF object (note that actual storage format may be different). When initialized, its entry point gets called with reference to vm_get_sym_entry() API function. Using that function, all other API functions can be looked up. Wrapped in few more layers, we arrive to Arduino API which sketches call.

Actual application binary storage format appears to be “vxp”, which is actually ELF with some stuff (“tag”) appended at the end by:

tools/mtk/PackTag.exe sketch_apr05a.cpp.elf