view doc/Firmware_Architecture @ 48:4bb56b6c8645

LICENSE: new terms
author Mychaela Falconia <falcon@freecalypso.org>
date Tue, 14 Apr 2020 21:38:35 +0000
parents b56928f8c001
children
line wrap: on
line source

Our FreeCalypso GSM firmware follows the same architecture as TI's TCS211;
this document is an attempt to describe this architecture.

Nucleus environment
===================

Like all classic TI firmwares, ours is based on the Nucleus PLUS RTOS.  Just
like TI's original code on which we are based, we use only a small subset of
the functionality provided by Nucleus - but because the latter is a library,
the pieces we don't use simply don't get pulled into the link.  The main
function we get out of Nucleus is the scheduling of threads, or tasks as
Nucleus calls them.

Our entry point code as we receive control from the Calypso boot ROM or from
other bootloaders on crippled targets or from loadagent in the case of fc-xram
loadable builds does some absolutely minimal initialization (set up sensible
memory access timings, copy iram.text to IRAM and .data to XRAM if we are
booting from flash, zero out our two bss segments (int.bss and ext.bss)) and
jumps to Nucleus' assembly init entry point.  Prior to jumping to Nucleus, we
don't even have a stack (all init code prior to that point is pure assembly and
uses only ARM registers); Nucleus then sets up the stack pointer for everything
running under its control.

Aside from just a few exceptions (ARM exception handlers come to mind, never
mind the pun), every piece of code in the firmware executes in one of the
following contexts:

* Application_Initialize(): this function and everything called from it execute
  just before Nucleus' thread scheduler starts; at this point interrupts are
  disabled at the ARM7 core level (in the CPSR) and must not be enabled; the
  stack is Nucleus' "system stack" which is also used by the scheduler and LISRs
  as explained below.

* Regular threads or tasks: once Application_Initialize() finishes, all code
  with the exception of interrupt handlers (LISRs and HISRs as explained below)
  runs in the context of some Nucleus task.  Whenever you are trying to debug
  or simply understand some piece of code in the firmware, the first question
  you should ask is "which task does this code execute in?".  Most functional
  components run in their own tasks, i.e., a given piece of code is only
  intended to run within the Nucleus task that belongs to the component in
  question.  On the other hand, some components are implemented as APIs,
  functions to be called from other components: these don't have their own task
  associated with them, and instead they run in the context of whatever task
  they were called from.  Some only get called from one task: for example, the
  "uartfax" driver API calls only get called from the protocol stack's UART
  entity, which is its own task.  Other component API functions like FFS and
  trace can get called from just about any task in the system.  Many components
  have both their own task and some API functions to be called from other tasks,
  and the API functions oftentimes post messages to the task to be worked on by
  the latter; the just-mentioned FFS and trace functions work in this manner.

  In our current GSM firmware (just like in TCS211) every Nucleus task is
  created either through Riviera or through GPF, and not in any other way - see
  the description of Riviera and GPF below.

* LISRs (Low level Interrupt Service Routines): these are the interrupt handlers
  that run immediately when an ARM IRQ or FIQ comes in.  The code at the IRQ and
  FIQ vector entry points calls Nucleus' magic stack switching function
  (switches the CPU from IRQ/FIQ into SVC mode, saves the interrupted thread's
  registers on that thread's stack, and switches to the "system" stack) and
  then calls TI's IRQ dispatcher implemented in C.  The latter figures out
  which Calypso interrupt needs to be handled and calls the handler configured
  in the compiled-in table.  Nucleus' LISR registration framework is not used
  by the GSM fw, but these interrupt handlers should be viewed as LISRs
  nonetheless.

  There is one additional difference between canonical Nucleus and TI's version
  (we've replicated the latter): canonical Nucleus was designed to support
  nested LISRs, i.e., IRQs re-enabled in the magic stack switching function,
  but in TI's version which we follow this IRQ re-enabling is removed: each LISR
  runs with interrupts disabled and cannot be interrupted.  (The corner case of
  an FIQ interruping an IRQ remains to be looked at more closely as bugs may be
  hiding there, but Calypso doesn't really use FIQ interrupts.)  There is really
  no need for LISR nesting in our GSM fw, as each LISR is very short: most LISRs
  do nothing more than trigger the corresponding HISR.

* HISRs (High level Interrupt Service Routines): these hold an intermediate
  place between LISRs and tasks, similar to softirqs in the Linux kernel.  A
  HISR can be activated by a LISR calling NU_Activate_HISR(), and when the LISR
  returns, the HISR will run before the interrupted task (or some higher
  priority task, see below) can resume.  HISRs run with CPU interrupts enabled,
  thus more interrupts can occur, with their LISRs executing and possibly
  triggering other HISRs.  All triggered HISRs must complete and thereby go
  "quiescent" before task scheduling resumes, i.e., all HISRs as a group have a
  higher scheduling priority than tasks.

Nucleus implements priority scheduling for tasks.  Tasks have their priority set
when they are created (through Riviera or GPF, see below), and a higher priority
task will run until it gets blocked waiting for something, at which time lower
priority tasks will run.  If a lower priority task sends a message to a higher
priority task, unblocking the latter which was waiting for incoming messages,
the lower priority task will effectively suspend itself immediately while the
higher priority task runs to process the message it was sent.

HISRs oftentimes post messages to their associated tasks as well; if one of
these messages unblocks a higher priority task, that unblocked task will run
upon the completion of the HISR instead of the original lower priority task
that was interrupted by the LISR that triggered the HISR.  Nucleus' scheduler
is fun!

Major functional blocks
=======================

At the highest level, all code in TI's classic firmwares and in our FreeCalypso
fw can be divided into 3 broad groupings:

* GSM Layer 1: this code was developed by TI, is highly specific to TI's
  baseband chipset family in general and to specific individual chips in
  particular (the code is liberally sprinkled with conditional compilation
  based on DBB type, ABB type, DSP ROM version and so on), and is absolutely
  necessary in order to operate a Calypso device as a GSM MS (mobile station)
  and not merely as a general purpose microprocessor platform.  This code can
  be considered to be the most important part of the entire firmware.

  L1 interties with Nucleus and with the G23M stack (with which it needs to
  communicate) in a very peculiar way described later in this article.

* G23M protocol stack: at the beginning of TI's involvement in the GSM baseband
  chipset business, they only developed and maintained their own L1 code, while
  the rest of the protocol stack (which is hardware-independent) was licensed
  from another company called Condat.  Later Condat as a company was fully
  acquired by TI, and the once-customer of this code became its owner.  The
  name of TI/Condat's implementation of GSM layers 2&3 for the MS side is G23M,
  and it forms its own major division of the overall fw architecture.

  Underlying the G23M stack is a special layer called GPF, which was originally
  Condat's Generic Protocol stack Framework.  Apparently Condat was in the
  business of developing and maintaining a whole bunch of protocol stacks: GSM
  MS side, GSM network side, TETRA and who knows what else.  GPF was their
  common underpinning for all of their protocol stack projects, which ran on top
  of many different OS environments: Nucleus, pSOS, VxWorks, Unix/Linux, Win32
  and who knows what else.

  In the case of FreeCalypso GSM fw, both the protocol stack and the underlying
  OS environment are fixed: GSM and Nucleus, respectively.  But GPF is still a
  critically important layer in the firmware architecture: in addition to
  serving as the glue between the G23M stack and Nucleus, it provides some
  important support infrastructure for the protocol stack.

* Miscellaneous peripheral accessories: under this category I (Space Falcon)
  place everything implemented through TI's Riviera framework.  Historical
  evidence indicates that TI's earliest firmwares did not have this part, i.e.,
  Riviera and everything built on top of it is a "non-essential" later
  addition.  It appears that TI originally invented Riviera in order to support
  the development of fancy "feature phone" UI/application layers, complete with
  Java, MMS, WAP, games and whatnot - things upon which our FreeCalypso project
  looks with disdain - but in the TCS211 firmware from 2007 which I used as the
  reference for FreeCalypso this Riviera framework serves as the foundation for
  some small but essential pieces of functionality: the FFS implementation, the
  SPI-based ABB access driver, the RTC driver and the debug trace facility.

  While it is certain that TI had some non-Riviera implementation of the just-
  listed essential pieces in their earliest pre-Riviera days, trying to find
  surviving sources from those days would be a "mission impossible" task.  OTOH,
  reusing the Riviera code from TCS211 was quite easy, as the copy of TCS211 we
  got has it in full source form with nothing omitted.  Therefore, I took the
  sensible easy road and kept Riviera in FreeCalypso.

The above division of the firmware into 3 broad functional groupings also
corresponds quite neatly with where each piece of our source code originally
came from.  Our versions of L1 and G23M came in their entirety from TI's TCS3.2
program targeting their later LoCosto chipset (specifically from the
TCS3.2_N5.24_M18_V1.11_M23BTH_PSL1_src.zip release from Peek/FGW), whereas
everything in the 3rd division (Riviera and everything built on top of it) came
from our TCS211/Leonardo source from Sotovik.

The just-listed divisions of the firmware are really separate software
environments which are linked together into one final image, but which have
very little in the way of interties.  Each of the 3 realms has its own very
different coding style, its own set of header files and its own defined types.
It is very rare for a module from one realm to include any header files or call
any functions from another realm, and while they all ultimately run on top of
Nucleus, they interface with Nucleus in different ways: G23M goes through GPF,
everything in Riviera land goes through Riviera, and L1 uses its own bizarre
mechanism which in our fw ends up going through GPF but hasn't always been this
way - to be explained lated in this article.

Also note that there is no mention of any handset UI code (or MMI in the GSM
industry's sexist speak) in the above breakdown of code divisions.  This
document describes the architecture of TI's modem firmware in which the highest
layer is the AT command interface (part of the G23M suite, or its uppermost
layer to be precise), and which does not include any UI code.  Our TI reference
sources do include their "MMI" code, but I haven't studied it closely enough
yet to comment on it properly, and the version of TCS211 which serves as our
primary reference is set up for the modem configuration without this "MMI" part.
Making sense of TI's "MMI" code is a task to be tackled later in the project
when we have a working modem and are ready to start building a usable handset
with UI.

Riviera and GPF
===============

Riviera and GPF are two parallel/independent/competing wrappers around or
layers above Nucleus.  The way in which they are treated in our FreeCalypso fw
architecture is somewhat inverted: originally GPF was the essential framework
underlying the G23M stack (and to which L1 was also attached in a hacky way)
while Riviera was added to support non-essential frills, but in our current FC
fw Riviera is always included just like Nucleus, whereas GPF only needs to be
included in the build when building with feature gsm (full GSM MS functionality)
or feature l1stand (L1 standalone) - but is not needed if one wishes to build
an "in vivo" FFS editing agent, for example.

This peculiar arrangement happened because of the source code availability
situation we found ourselves in.  TCS211 uses real Riviera that is fully
independent of GPF (see below), and our copy thereof came with this part in
full source form.  On the other hand, we never got the complete original source
for GPF in one piece, thus our FC version of GPF had to be reconstructed from
bits and pieces.  For this reason I made the decision early on to include
Riviera and some RV-based components in the "mandatory core" part of our FC fw
architecture, while leaving GPF to be worked on later.  And when I did get to
reintegrating GPF, at that point it was natural to make it into an "optional"
component that is included only when needed.

At some point in their post-Calypso TCS3.x program TI decided to eliminate
Riviera as an independent framework and to reimplement Riviera APIs (used by
peripheral but necessary code such as FFS, ETM, various drivers etc) over GPF.
This arrangement is used in the TCS3.2 LoCosto code from which we lifted our
versions of L1 and G23M.  However, I (Space Falcon) chose not to adopt this
approach for FreeCalypso, and mimic the TCS211 way (Riviera entirely
independent of GPF) instead.  The reasons were twofold: (1) there was no full
source for GPF and a painstaking reconstruction effort was required before we
could have our own working version of GPF in our gcc-built fw, and (2) I felt
more comfortable and familiar with following TCS211.

Start-up process
================

I mentioned earlier that every Nucleus task in our firmware gets created and
started either through Riviera or through GPF.  All GPF tasks are created and
placed into the runable state in the Application_Initialize() context: the work
is done by GPF init code in gsm-fw/gpf/frame/frame.c, and the top level GPF
init function called from Application_Initialize() is StartFrame().  Thus when
Application_Initialize() finishes and the Nucleus thread scheduler starts
running for the first time, all GPF tasks are there to be scheduled.

There is a compiled-in table of all protocol stack entities and the tasks in
which they need to run which (in our fw) lives under gsm-fw/gpf/conf and which
logically belongs to GPF.  Canonically each protocol stack entities runs in its
own task, but sometimes two or more are combined to run in the same task: for
example, in the minimal GSM "voice only" configuration (no CSD, fax or GPRS)
CC, SMS and SS entities share the same task named CM.  Unlike Riviera, GPF does
not support dynamic starting and stopping of tasks.

As each GPF task starts running (immediately upon entry into Nucleus' scheduling
loop as Application_Initialize() finishes), pf_TaskEntry() function in
gsm-fw/gpf/frame/frame.c is the first code it runs.  This function creates the
queue for messages to be sent to all entities running within the task in
question, calls each entity's pei_init() function (repeatedly until it succeeds:
it will fail until the other entities to which this entity needs to send
messages have created their message queues), and then falls into the main body
of the task: for all "regular" entities/tasks except L1, this main body consists
of waiting for messages (or signals or timeouts) to arrive on the queue and
dispatching each received message to the appropriate handler in the right
entity.

Riviera tasks get started in a different way.  The same Application_Initialize()
function that calls StartFrame() to create and start all GPF tasks also calls
create_tasks() (found in gsm-fw/riviera/init/create_RVtasks.c), the appinit-time
function for starting the Riviera environment.  But this function does not
create and start every configured Riviera task like StartFrame() does for GPF.
Instead it creates a special helper task which will do this work once scheduled.
Thus at the completion of Application_Initialize() and the beginning of
scheduling the set of runable Nucleus tasks consists of all GPF ones plus the
special RV starter task.  Once the RV starter task gets scheduled, it will call
rvm_start_swe() to launch every configured Riviera SWE (SoftWare Entity), which
in turns entails creating the tasks in which these SWEs are to run.

Dynamic memory allocation
=========================

All dynamic memory allocation (i.e., all RAM usage beyond statically allocated
variables and buffers) is once again done either through Riviera or through GPF,
and in no other way.  Ultimately all areas of the physical RAM that will ever
be used by the fw in any way are allocated when the fw is compiled and linked:
the areas from which Riviera and GPF serve their dynamic memory allocations are
statically allocated as char arrays in the respective C modules and placed in
the int.ram or ext.ram section as appropriate; Riviera and GPF then provide
API functions that allocate memory dynamically from these statically allocated
large pools.

Riviera and GPF have entirely separate memory pools from which they serve their
respective clients, hence there is no possibility of one affecting the other.
Riviera's memory allocation scheme is very much like the classic malloc&free:
there is one large unstructured pool from which all allocations are made, one
can allocate a chunk of any size, free chunks are merged when physically
adjacent, and fragmentation is an issue: a memory allocation request may fail
even when there is enough memory available in total if it is too fragmented.

GPF's dynamic memory allocation facility is considerably more robust: while it
does maintain one or two (depending on configuration) memory pools of the
traditional "dynamic" kind (like malloc&free, susceptible to fragmentation),
most GPF memory allocation works on "partition" memory instead.  Here GPF
maintains 3 separate groups of pools: PRIM, TEST and DMEM; each allocation
request must specify the appropriate pool group and cannot affect the others.
Within each pool there is a fixed number of partitions of a fixed size: for
example, in TI's TCS211 GSM+GPRS configuration the PRIM pool group consists of
190 partitions of 60 bytes, 110 partitions of 128 bytes, 50 partitions of 632
bytes and 7 partitions of 1600 bytes.  An allocation request from a given pool
group (e.g., PRIM) can request any arbitrary size in bytes, but it gets rounded
up to the nearest partition size and allocated out of the respective pool.  If
no free partitions are available, the requesting task is suspended until another
task frees on.  Because these partitions are used primarily for intertask
communication, if none are free, it can only mean (assuming that the firmware
functions correcly) that all partitions have been allocated and sent to some
queue for some task to work on, hence eventually they will get freed.

This scheme implemented in GPF is extremely robust in the opinion of this
author, and the other purely "dynamic" scheme is used (in the case of GPF) only
for init-time allocations which are never freed, such as task stacks - hence
the GPF-based part of the firmware is not suspectible at all to the problem of
memory fragmentation.  But Riviera does suffer from this problem, and the
concern is more than just theoretical: one major user of Riviera-based dynamic
memory allocation is the trace facility (described in its own section below),
and my observation of the trace output from Pirelli's proprietary fw (which
appears to use the same architecture with separate Riviera and GPF) suggests
that after the fw has been running for a while, Riviera memory gets fragmented
to a point where many traces are being dropped.  Replacing Riviera's poor
dynamic memory allocation scheme with a GPF-like partition-based one is a to-do
item for our project.

Message-based intertask communication
=====================================

Even though all entities of the G23M protocol stack are linked together into
one monolithic fw image and there is nothing to stop them from calling each
other's functions and accessing each other's variables, they don't work that
way.  Instead all communication between entities is done through messages, just
as if they ran in separate address spaces or even on separate processors.
Buffers for this message exchange are allocated from a GPF partition pool: an
entity that needs to send a message to another entity allocates a buffer of the
needed size, fills it with the message to be sent, and posts it on the recipient
entity's message queue, all through GPF services.  The other entity simply
processes the stream of messages that arrives on its message queue, freeing each
message (returning the buffer to the partition pool in came from) as it is
processed.

Riviera-based tasks use a similar mechanism: unlike G23M protocol stack
entities, most Riviera-based functional modules provide APIs that are called as
functions from other tasks, but these API functions typically allocate a memory
buffer (through Riviera), fill it with the call parameters, and post it to the
associated task's message queue (also in the Riviera land) to be worked on.
Once the worker task gets the job done, it will either call a callback function
or post a response message back to the requestor - the latter option is only
possible if the requesting entity is also Riviera-based.

A closer look at GPF
====================

There are certain sublayers within GPF which need to be pointed out.  The 3
major subdivisions within GPF are:

* The meaty core of GPF: this part is the code under gsm-fw/gpf/frame in our
  source tree.  It appears that this part was originally intended to be both
  project-independent (same for GSM, TETRA etc) and OS-independent (same for
  Nucleus, pSOS, VxWorks etc).  This is the part of GPF that matters for the
  G23M stack: all APIs called by PS entities are implemented here, and so are
  all other PS-facing functions such as startup.  (PS = protocol stack)

* OS adaptation layer (OSL): this is the part of GPF that adapts it to a given
  underlying OS, in our case Nucleus.

* Test interface: see the code under gsm-fw/gpf/tst_drv and gsm-fw/gpf/tst_pei.
  This part handles the trace output from all entities that run under GPF and
  the mechanism for sending external debug commands to the GPF+PS subsystem.

GPF was a difficult step in our GSM firmware reintegration process because no
complete source for it could be found anywhere: apparently GPF was so stable
and so independent of firmware particulars (Calypso or LoCosto, GSM only or
GSM+GPRS, modem or complete phone with UI etc) that it appears to have been
used and distributed as prebuilt binary libraries even inside TI.  All TI fw
(semi-)sources we have use GPF in prebuilt library form and are not set up to
recompile any part of it from source.  (They had to include all GPF header
files though, as most of them are included by G23M C modules, and it would be
too much hassle to figure out which ones are or aren't needed, hence all were
included.)

Fortunately though, we were able to find the sources for most parts of GPF:

* The LoCosto source in TCS3.2_N5.24_M18_V1.11_M23BTH_PSL1_src.zip features the
  source for the "core" part of GPF under gpf/FRAME - these sources aren't
  actually used by that fw's build system (it only uses the prebuilt binary
  libs for GPF), but they are there.

* Our TCS211 semi-src doesn't have any sources for the core part of GPF, but
  instead it features the source for the test interface and some "misc" parts:
  under gpf/MISC and gpf/tst in that source tree - these sources are not present
  in the LoCosto version from Peek.

But one critical piece was still missing: the OS adaptation layer.  It appears
that the GPF core (vsi_??? modules) and OSL (os_??? modules) were maintained
and built together, ending up together in frame_<blah>.lib files in the binary
form used to build firmwares, but the source for the "frame" part in the Peek
find contained only vsi_*.c and others, but not any of os_*.c.

Thus we had to reconstruct GPF from the shattered bits and pieces we had.  I
took the frame sources from Peek and the misc and tst sources from Sotovik, and
saw that they compiled w/o problems in our gcc environment.  Attempting to link
any firmware that uses GPF would have been futile at this point, as it would
have failed with undefined references to os_*() functions.  Then I had to do
the hard work: disassemble the missing os_??? modules from the binary libs in
the TCS211 version (hey, at least this one was known to work reliably) and write
new C code replicating the exact logic found in the disassembly of the known
working and fitting binary.  This work is now mostly done (some non-essential
functions have been stubbed out to be revisited later), and the version of GPF
used by FreeCalypso is a significant work of reconstruction, not merely lifted
from a readily available source and plopped in.

A closer look at L1
===================

The L1 code is remarkable in how little intertie it has with the rest of the
firmware it is linked into.  It is almost entirely self-contained, expecting
only 4 functions to be provided by the underlying OS environment:

os_alloc_sig	-- allocate message buffer
os_free_sig	-- free message buffer
os_send_sig	-- send message to upper layers
os_receive_sig	-- receive message from upper layers

It helps to remember that at the beginning of TI's involvement in the GSM
baseband chipset business, L1 was the only thing they "owned", while Condat,
the maintainers of the higher level protocol stack, was a separate company.
TI's "turnkey" solution must have consisted of their own L1 code plus G23M code
(including GPF etc) licensed from Condat, but I'm guessing that TI probably
wanted to retain the ability to sell their chips with their L1 without being
entangled by Condat: let the customer use their own GSM L23 stack, or perhaps
work out their own independent licensing arrangements with Condat.  I'm
guessing that L1 was maintained as its own highly independent and at least
conceptually portable entity for these reasons.

The way in which L1 is intertied into our FreeCalypso GSM fw is the same as how
it is done in TI's production firmwares, including both our TCS211 reference
and the TCS3.2 version from which we got our L1 source.  There is a module
called OSX, which is an extremely thin adaptation layer that implements the
APIs expected by L1 in terms of GPF.  Furthermore, this OSX layer provides
header file isolation: the only "outside" (non-L1) header included by L1 is
cust_os.h, and it defines the necessary interface to OSX *without* including
any other headers (no GPF headers in particular), using only the C language's
native types.  Apart from this cust_os.h header, the entire OSX layer is
implemented in one C module (osx.c, which we had to reconstruct from osx.obj as
the source was missing - but it's very simple) which does include some GPF
headers and implements the OSX API in terms of GPF services.  Thus in TI's
production firmwares and in our FC GSM fw L1 does sit on top of GPF, but very
indirectly.

More specifically, the "production" version of OSX implements its API in terms
of *high-level* GPF functions, i.e., VSI.  However, they also had an interesting
OP_L1_STANDALONE configuration which omitted not only all of G23M, but also the
core of GPF and possibly the Riviera environment as well.  We don't have a way
to recreate this configuration exactly as it existed inside TI because we don't
have the source bits specific to this configuration (our own standalone L1
configuration is implemented differently, see below), but we do have a little
bit of insight into how it worked.

It appears that TI's OP_L1_STANDALONE build used a special "gutted" version of
GPF in which the "meaty core" (VSI etc) was removed.  The OS layer (os_???
modules implementing os_*() functions) that interfaces to Nucleus was kept, and
so was OSX used by L1 - but this time the OSX API functions were implemented in
terms of os_*() ones (low-level wrappers around Nucleus) instead of the higher-
level VSI APIs provided by the "meaty core" of GPF.  It is purely a guess on my
part, but perhaps this hack was also done in the days before TI's acquisition
of Condat, and by omitting the "meaty core" of GPF, TI could claim that their
OP_L1_STANDALONE configuration did not contain any of Condat's "intellectual
property".

In FreeCalypso we do have a way to build a firmware image that includes L1 but
not G23M: it is our own L1 standalone configuration, enabled with a
feature l1stand line in build.conf.  However, because IP considerations don't
apply to us (we operate under the doctrine of eminent domain), we are not
replicating TI's gutting of GPF: *our* L1 standalone configuration includes the
full GPF (with OSX for L1 implemented in terms of VSI), but with a greatly
reduced set of tasks when G23M is omitted.

Run-time structure of L1
========================

L1 consists of two major parts: L1S and L1A.  L1S is the synchronous part where
the most time-critical functions are performed; it runs as a Nucleus HISR.  The
hardware in the Calypso generates an interrupt on every TDMA frame (4.615 ms),
and the LISR handler for this interrupt triggers the L1S HISR.  L1S communicates
with L1A through a shared memory data structure, and also sometimes allocates
message buffers and posts them to L1A's incoming message queue (both via OSX
API functions, i.e., via GPF in disguise).

L1A runs as a regular task under Nucleus, and includes a blocking call (to GPF
via OSX) to wait for incoming messages on its queue.  It is one big loop that
waits for incoming messages, then processes each received message and commands
L1S to do most of the work.  The entry point to L1A in the L1 code proper is
l1a_task(), although the responsibility for running it as a task falls on some
"glue" code outside of L1 proper.  TI's production firmwares with G23M included
have an L1 protocol stack entity within G23M whose only job (aside from some
initialization) is to run l1a_task() in the Nucleus task created by GPF for
that protocol stack entity; we do the same in our firmware.

Communication between L1 and G23M
=================================

It is remarkable that L1 and G23M don't have any header files in common: L1
uses its own (almost fully self-contained), whereas the G23M+GPF realm is its
own world with its own header files.  One has to ask then: how do they
communicate?  OK, we know they communicate through primitives (messages in
buffers allocated from GPF's PRIM partition memory pool) passes via message
queues, but what about the data structures in these messages?  Where are those
defined if there are no header files in common between L1 and G23M?

The answer is that there are separate definitions of the L1<->G23M interface on
each side, and TI must have kept them in sync manually.  Not exactly a
recommended programming or software maintenance practice for sure, but TI took
care of it, and the existing proprietary products based on TI's firmware are
rock solid, so it is not really our place to complain.

TI's firmwares from the era we are working with (the TCS3.2/LoCosto source from
20090327 from which we took our L1 and G23M and the binary libs version of
TCS211 from 20070608 which serves as our reference) also include a component
called ALR.  It resides in the G23M code realm: G23M coding style, uses Condat
header files, runs as its own protocol stack entity under GPF.  This component
appears to serve as a glue layer between the rest of the G23M stack (which is
supposed to be truly hardware-independent) and TI's L1.

Speaking of ALR, it is worth mentioning that there is a little naming
inconsistency here.  ALR is known to the connect-by-name logic in GPF as "PL"
(physical layer, apparently), while the ACI entity (Application Control
Interface, the top level entity) is known to the same logic as "MMI".  No big
deal really, but hopefully knowing this quirk will save someone some confusion.

Debug trace facility
====================

See the RVTMUX document in the same directory as this one for general background
information about the debug and development interface provided by TI-based
firmwares.  Our FreeCalypso GSM firmware implements an RVTMUX interface as well,
and the most immediate use to which it is put is debug trace output.  In this
section I'm going to describe how this debug trace output is generated inside
the fw.

The firmware component that "owns" the physical UART channel assigned to RVTMUX
is RVT, implemented in gsm-fw/riviera/rvt.  It is a Riviera-based component,
and it has a Nucleus task that is created and started through Riviera.  All
calls to the actual driver for the UART are made from RVT.  In the case of
output from the Calypso GSM device to an external host, all such output is
performed in the context of RVT's Nucleus task; this task drains RVT's message
queue and emits the content of allocated buffers posted to it, freeing them
afterward.  (The dynamic memory allocation system in this case is Riviera's,
which is susceptible to fragmentation - see discussion earlier in this article.)
Therefore, every trace or other output packet emitted from a GSM device running
our fw (or any of the proprietary firmwares based on the same architecture)
appears as a result of a message in a dynamically allocated buffer having been
posted to RVT's queue.

RVT exports several API functions that are intended to be called from other
tasks, it is by way of these functions that most output is submitted to RVT.
One can call rvt_send_trace_cpy() with a fully prepared output message, and
that function will allocate a buffer from Riviera's dynamic memory allocator
properly accounted to RVT, fill it and post it to the RVT task's queue.
Alternatively, one can can rvt_mem_alloc() to allocate the buffer, fill it in
and then pass it to rvt_send_trace_no_cpy().

At higher levels, there are a total of 3 kinds of debug traces that can be
emitted:

* Riviera traces: these are generated by various components implemented in
  Riviera land, although in reality any component can generate a trace of this
  form by calling rvf_send_trace() - this function can be called from any task.

* L1 traces: L1 has its own trace facility implemented in
  gsm-fw/L1/cfile/l1_trace.c; it generates its traces as ASCII messages and
  sends them out via rvt_send_trace_cpy().

* GPF traces: code that runs in GPF/G23M land and uses those header files and
  coding conventions etc can emit traces through GPF.  GPF's trace functions
  (implemented in gsm-fw/gpf/frame/vsi_trc.c) allocate a memory partition from
  GPF's TEST pool, format the trace into it, and send the trace primitive to
  GPF's special test interface task.  That task receives trace and other GPF
  test interface primitives on its queue, performs some manipulations on them,
  and ultimately generates RVT trace output, i.e., a new dynamic memory buffer
  is allocated in the Riviera land, the trace is copied there, and the Riviera
  buffer goes to the RVT task for the actual output.

Trace masking
=============

The RV trace facility invoked via rvf_send_trace() has a crude masking ability,
but by default all traces are enabled.  In TI's standard firmwares most of the
trace output comes from L1: L1's trace output is very voluminous, and appears
to be fully enabled by default.  I have yet to look more closely if there is
any trace masking functionality in L1 and what the default trace verbosity
level should be.

On the other hand, GPF and therefore G23M traces are mostly disabled by default.
One can turn the trace verbosity level from any GPF-based entity up or down by
sending a "system primitive" command to the running fw, and another such command
can be used to save these masks in FFS, so that they will be restored on the
next boot cycle and be effective at the earliest possible time.  Enabling *all*
GPF trace output for all entities is generally not useful though, as it is so
verbose that a developer trying to make sense of it will likely drown in it.

GPF compressed trace hack
=========================

TI's Windows-based GSM firmware build systems include a hack called str2ind.
Seeking to reduce the fw image size by eliminating trace ASCII strings from it,
and seeking to reduce the load on the RVTMUX serial interface by eliminating
the transmission time of these strings, they passed their sources through an
ad hoc preprocessor that replaces these ASCII strings with numeric indices.
The compilation process with this str2ind hack becomes very messy: each source
file is first passed through the C preprocessor, then the intermediate form is
passed through str2ind, and finally the de-string-ified form is compiled, with
the compiler being told not to run the C preprocessor again.

TI's str2ind tool maintains a table of correspondence between the original trace
ASCII strings and the indices they've been turned into, and a copy of this table
becomes essential for making sense of GPF trace output: the firmware now emits
only numeric indices which are useless without this str2ind.tab mapping table.

Our FreeCalypso firmware does not currently implement this str2ind aka
compressed trace hack, i.e., all GPF trace output from our fw is in full ASCII
string form.  I have not bothered to implement compressed traces because:

* We have not yet encountered a case of the full ASCII strings causing a problem
  either with fw images not fitting into the available memory or excessive load
  on the RVTMUX interface;

* Implementing the hack in question would require extra work: the str2ind tool
  would have to be reimplemented anew, as of the original we have no source,
  only a Windows binary, and requiring our free fw build process to run a
  Windows binary under Wine is a no-no;

* I don't feel like doing all that extra work for what appears to be no real
  gain;

* Having to run gcc with separate cpp and actual compilation steps with str2ind
  sandwiched in between would be ugly and gross;

* Having to keep track of which str2ind.tab goes with which fw image and supply
  the right table to our rvinterf tools would likely be a pita.

So we shall stick with full ASCII string traces until and unless we run into an
actual (as opposed to hypothetical) problem with either fw image size or serial
interface load.

RVTMUX command input
====================

RVTMUX is not just debug trace output: it is also possible for an external host
to send commands to the running fw via RVTMUX.

Inside the fw RVTMUX input is handled by the RVT entity by way of a Nucleus
HISR.  This HISR gets triggered when Rx bytes arrive at the designated UART,
and it calls the UART driver to collect the input.  RVT code running in this
HISR parses the message structure and figures out which fw component the
incoming message is addressed to.  Any fw component can register to receive
RVTMUX packets, and provides a callback function with this registration; this
callback function is called in the context of the HISR.

In our current FC GSM fw there are two components that register to receive
external host commands via RVTMUX: ETM and GPF.  ETM is described in my earlier
RVTMUX write-up.  ETM is implemented as a Riviera SWE and has its own Nucleus
task; the callback function that gets called from the RVT HISR posts received
messages onto ETM's own queue drained by its task.  The ETM task gets scheduled,
picks up the command posted to its queue, executes it, and sends a response
message back to the external host through RVT.

Because all ETM commands funnel through ETM's queue and task, and that task
won't start looking at a new command until it finished handling the previous
one, all ETM commands and responses are in strict lock-step: it is not possible
to send two commands and have their responses come in out of order, and it makes
no sense to send another ETM command prior to receiving the response to the
previous one.  (But there can still be debug traces or other traffic intermixed
on RVTMUX in between an ETM command and the corresponding response!)

The other component that can receive external commands is GPF.  GPF's test
interface can receive so-called "system primitives", which are ASCII string
commands parsed and acted upon by GPF, and also binary protocol stack
primitives.  Remember how all entities in the G23M stack communicate by sending
messages to each other?  Well, GPF's test interface allows such messages to be
injected externally as well, directed to any entity in the running fw.  System
primitive commands can also be used to cause entities to send their outgoing
primitives to the test interface, either instead of or in addition to the
originally intended recipient.

Firmware subsetting
===================

We have built our firmware up incrementally, piece by piece, starting from a
very small skeleton.  As we added pieces working toward full GSM MS
functionality, the ability to build less functional fw images corresponding to
our earlier stages of development has been retained.  Each piece we added is
"optional" from the viewpoint of our build system, even if it is absolutely
required for normal usage, and is enabled by the appropriate feature line in
build.conf.

Our minimal baseline with absolutely no "features" enabled consists of:

* Nucleus
* Riviera
* TI's basic drivers for GPIO, ABB etc
* RVTMUX on the UART port chosen by the user (RVTMUX_UART_port Bourne shell
  variable in build.conf) and the UART driver for it
* FFS code operating on a fake FFS image in RAM

If one runs this minimal "firmware" on a Calypso device, one will see some
startup messages in RV trace format followed by a System Time trace every 20 s.
This "firmware" can't do anything more, there is not even a way to command it
to power off or reboot.

Working toward full GSM MS functionality, pieces can be added to this skeleton
in this order:

* GPF
* L1
* G23M

feature gsm enables all of the above for normal usage; feature l1stand can be
used alternatively to build an L1 standalone image without G23M - we expect
that we may end up using a ramImage form of the latter for RF calibration on
our own Calypso hardware.

ETM and various FFS configurations are orthogonal features to the choice of
core functionality level.

Further reading
===============

Believe it or not, some of the documentation that was written by the original
vendors of the software in question and which we've been able to locate turns
out to be fairly relevant and helpful, such that I recommend reading it.

Documentation for Nucleus PLUS RTOS:

	ftp://ftp.freecalypso.org/pub/embedded/Nucleus/nucleus_manuals.tar.bz2

	Quite informative, and fits our version of Nucleus just fine.

Riviera environment:

	ftp://ftp.freecalypso.org/pub/GSM/Calypso/riviera_preso.pdf

	It's in slide presentation form, not a detailed technical document, but
	it covers a lot of points, and all that Riviera stuff described in the
	preso *is* present in our fw for real, hence it should be considered
	relevant.

GPF documentation:

	https://www.freecalypso.org/LoCosto-docs/SW%20doc/frame_users_guide.pdf
	https://www.freecalypso.org/LoCosto-docs/SW%20doc/vsipei_api.pdf

	Very good reading, helped me understand GPF when I first reached this
	part of firmware reintegration.

TCS3.x/LoCosto fw architecture:

	https://www.freecalypso.org/LoCosto-docs/SW%20doc/TCS2_1_to_3_2_Migration_v0_8.pdf
	ftp://ftp.freecalypso.org/pub/GSM/LoCosto/LoCosto_Software_Architecture_Specification_Document.pdf

	These TI docs focus mostly on how they changed the fw architecture from
	their TCS2.x program (Calypso) to their newer TCS3.x (LoCosto), but one
	can still get a little insight into the "old" TCS211 architecture they
	were moving away from, which is the architecture I've adopted for
	FreeCalypso.