(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:
ROM
COREAPI
VIVA
app
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)
{
mutexLock();
m_msg.remote_func = (remote_call_ptr)func;
m_msg.userdata =
userdata;
m_msg.signal =
m_signal;
sendMsg(VM_MSG_ARDUINO_CALL, &m_msg);
vm_signal_wait(m_signal);
mutexUnlock();
}
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