|
|
*** aside
|
|
|
See also:
|
|
|
[Porting Guide](/doc/porting_guide.md) |
|
|
|
[Build System](/doc/framework_build.md) |
|
|
|
[Debugging](/doc/framework_debugging.md) |
|
|
|
[Testing](/doc/framework_testing.md) |
|
|
|
[Vendor Extensions](/doc/vendor_extensions.md)
|
|
|
***
|
|
|
|
|
|
# CHRE Framework Overview
|
|
|
|
|
|
[TOC]
|
|
|
|
|
|
The CHRE reference implementation (hereafter referred to just as "CHRE" or "the
|
|
|
CHRE framework") is developed primarily in C++11 using a modular object-oriented
|
|
|
approach that separates common code from platform-specific code. CHRE is an
|
|
|
event-based system, so CHRE is built around an event loop which executes nanoapp
|
|
|
code as well as CHRE system callbacks. Per the CHRE API, nanoapps can’t execute
|
|
|
in more than one thread at a time, so CHRE is structured around a single thread
|
|
|
that executes the event loop, although there may be other threads in the system
|
|
|
that support CHRE. The EventLoopManager is a Singleton object which owns the
|
|
|
main state of the CHRE framework, including EventLoop and \*Manager classes for
|
|
|
the various subsystems supported by CHRE.
|
|
|
|
|
|
To get a better understanding of code structure and how it weaves between common
|
|
|
and platform-specific components, it is helpful to trace the flow through a few
|
|
|
example scenarios. Note that this is not meant to be an exhaustive list of
|
|
|
everything that happens in each case (for that, refer to the code itself), but
|
|
|
rather an overview of key points to serve as an introduction.
|
|
|
|
|
|
## Loading a nanoapp via the HAL
|
|
|
|
|
|
There are multiple ways by which a nanoapp can be loaded (see the relevant
|
|
|
section below for details), but this example traces the flow for dynamically
|
|
|
loading a nanoapp that has been passed in via the Context Hub HAL's
|
|
|
`loadNanoapp()` method.
|
|
|
|
|
|
1. The nanoapp binary reaches the HAL implementation, and it is loaded into the
|
|
|
processor where CHRE is running using a platform-specific method. While the
|
|
|
path this takes can vary, one common approach is to transmit the binary into
|
|
|
CHRE via the platform-specific HostLink implementation, then verify its
|
|
|
digital signature, and parse the binary file format (e.g. ELF) to load and
|
|
|
link the code.
|
|
|
|
|
|
2. Once the nanoapp code is loaded, the platform code calls
|
|
|
`EventLoopManager::deferCallback()` to switch context to the main CHRE thread
|
|
|
(if needed), so it can complete loading and starting the nanoapp.
|
|
|
`deferCallback()` effectively posts an event to the main event loop which
|
|
|
does not get delivered to any nanoapps. Instead, the purpose is to invoke the
|
|
|
supplied callback from the CHRE thread once the event is popped off the
|
|
|
queue.
|
|
|
|
|
|
3. The (platform-specific) callback finalizes the newly constructed `Nanoapp`
|
|
|
object as needed, and passes it to `EventLoop::startNanoapp()` - this marks a
|
|
|
transition from platform-specific to common code.
|
|
|
|
|
|
4. `EventLoop` takes ownership of the `Nanoapp` object (which is a composite of
|
|
|
common and platform-specific data and functions, as described in the Platform
|
|
|
Abstractions section), includes it in the collection of loaded nanoapps to
|
|
|
execute in the main event loop, updates `mCurrentNanoapp` to reference the
|
|
|
nanoapp it's about to execute, and calls into `PlatformNanoapp::start()`.
|
|
|
|
|
|
5. Since the mechanism of supporting dynamic linkage and position independent
|
|
|
code can vary by platform, transferring control from the framework to a
|
|
|
nanoapp is considered part of the platform layer. So
|
|
|
`PlatformNanoapp::start()` performs any necessary tasks for this, and calls
|
|
|
into the `nanoappStart()` function defined in the nanoapp binary.
|
|
|
|
|
|
## Invoking a CHRE API from a nanoapp
|
|
|
|
|
|
Let's assume the nanoapp we've loaded in the previous section calls the
|
|
|
`chreSensorConfigure()` CHRE API function within `nanoappStart()`:
|
|
|
|
|
|
1. The nanoapp invokes `chreSensorConfigure()` with parameters to enable the
|
|
|
accelerometer.
|
|
|
|
|
|
2. The Nanoapp Support Library (NSL) and/or the platform's dynamic linking
|
|
|
module are responsible for handling the transition of control from the
|
|
|
nanoapp binary to the CHRE framework. This can vary by platform, but we'll
|
|
|
assume that control arrives in the `chreSensorConfigure()` implementation in
|
|
|
`platform/shared/chre_api_sensor.cc`.
|
|
|
|
|
|
3. `EventLoopManager::validateChreApiCall()` is invoked to confirm that this
|
|
|
function is being called from the context of a nanoapp being executed within
|
|
|
the event loop (since associating the API call with a specific nanoapp is a
|
|
|
requirement of this API and many others, and the majority of the CHRE
|
|
|
framework code is only safe to execute from within the main CHRE thread), and
|
|
|
fetch a pointer to the current `Nanoapp` (i.e. it retrieves `mCurrentNanoapp`
|
|
|
set previosly by `EventLoop`).
|
|
|
|
|
|
4. `SensorManager::setSensorRequest()` (via
|
|
|
`EventLoopManager::getSensorRequestManager()`) is called to process the
|
|
|
nanoapp’s request - we transition to common code here.
|
|
|
|
|
|
5. The request is validated and combined with other nanoapp requests for the
|
|
|
same sensor to determine the effective sensor configuration that should be
|
|
|
requested from the platform, and the nanoapp is registered to receive
|
|
|
broadcast accelerometer sensor events.
|
|
|
|
|
|
6. `SensorRequestManager` calls into `PlatformSensorManager::configureSensor()`,
|
|
|
which performs the necessary operations to actually configure the
|
|
|
accelerometer to collect data.
|
|
|
|
|
|
7. Assuming success, the return value propagates back up to the nanoapp, and it
|
|
|
continues executing.
|
|
|
|
|
|
## Passing an event to a nanoapp
|
|
|
|
|
|
Following the example from above, let's follow the case where an accelerometer
|
|
|
sample has been generated and is delivered to the nanoapp for processing.
|
|
|
|
|
|
1. Starting in platform-specific code, likely in a different thread, the
|
|
|
accelerometer sample is received from the underlying sensor framework - this
|
|
|
typically happens in a different thread than the main CHRE thread, and within
|
|
|
the fully platform-specific `PlatformSensorManagerBase` class.
|
|
|
|
|
|
2. As needed, memory is allocated to store the sample while it is being
|
|
|
processed, and the data is converted into the CHRE format: `struct
|
|
|
chreSensorThreeAxisData`.
|
|
|
|
|
|
3. `SensorRequestManager::handleSensorDataEvent()` is invoked (common code) to
|
|
|
distribute the data to nanoapps.
|
|
|
|
|
|
4. `SensorRequestManager` calls into `EventLoop` to post an event containing the
|
|
|
sensor data to all nanoapps registered for the broadcast event type
|
|
|
associated with accelerometer data, and sets `sensorDataEventFree()` as the
|
|
|
callback invoked after the system is done processing the event.
|
|
|
|
|
|
5. `EventLoop` adds this event to its event queue and signals the CHRE thread.
|
|
|
|
|
|
6. Now, within the context of the CHRE thread, once the event loop pops this
|
|
|
event off of its queue in `EventLoop::run()`, the `nanoappHandleEvent()`
|
|
|
function is invoked (via `PlatformNanoapp`, as with `nanoappStart`) for each
|
|
|
nanoapp that should receive the event.
|
|
|
|
|
|
7. Once the event has been processed by each nanoapp, the free callback
|
|
|
(`sensorDataEventFree()`), is called to release any memory or do other
|
|
|
necessary cleanup actions now that the event is complete.
|
|
|
|
|
|
## Platform Abstractions
|
|
|
|
|
|
CHRE follows the 'compile time polymorphism' paradigm, to allow for the benefits
|
|
|
of `virtual` functions, while minimizing code size impact on systems with tight
|
|
|
memory constraints.
|
|
|
|
|
|
Each framework module as described in the previous section is represented by a
|
|
|
C++ class in `core/`, which serves as the top-level reference to the module and
|
|
|
defines and implements the common functionality. This common object is then
|
|
|
composed with platform-specific functionality at compile-time. Using the
|
|
|
`SensorRequestManager` class as an example, its role is to manage common
|
|
|
functionality, such as multiplexing sensor requests from all clients into a
|
|
|
single request made to the platform through the `PlatformSensorManager` class,
|
|
|
which in turn is responsible for forwarding that request to the underlying
|
|
|
sensor system.
|
|
|
|
|
|
While `SensorRequestManager` is fully common code, `PlatformSensorManager` is
|
|
|
defined in a common header file (under `platform/include/chre/platform`), but
|
|
|
implemented in a platform-specific source file. In other words, it defines the
|
|
|
interface between common code and platform-specific code.
|
|
|
|
|
|
`PlatformSensorManager` inherits from `PlatformSensorManagerBase`, which is
|
|
|
defined in a platform-specific header file, which allows for extending
|
|
|
`PlatformSensorManager` with platform-specific functions and data. This pattern
|
|
|
applies for all `Platform<Module>` classes, which must be implemented for all
|
|
|
platforms that support the given module.
|
|
|
|
|
|
Selection of which `PlatformSensorManager` and `PlatformSensorManagerBase`
|
|
|
implementation is instantiated is controlled by the build system, by setting the
|
|
|
appropriate include path and source files. This includes the path used to
|
|
|
resolve include directives appearing in common code but referencing
|
|
|
platform-specific headers, like `#include
|
|
|
"chre/target_platform/platform_sensor_manager_base.h"`.
|
|
|
|
|
|
To ensure compatibility across all platforms, common code is restricted in how
|
|
|
it interacts with platform-specific code - it must always go through a common
|
|
|
interface with platform-specific implementation, as described above. However,
|
|
|
platform-specific code is less restricted, and can refer to common code, as well
|
|
|
as other platform code directly.
|
|
|
|
|
|
## Coding conventions
|
|
|
|
|
|
This project follows the [Google-wide style guide for C++
|
|
|
code](https://google.github.io/styleguide/cppguide.html), with the exception of
|
|
|
Android naming conventions for methods and variables. This means 2 space
|
|
|
indents, camelCase method names, an mPrefix on class members and so on. Style
|
|
|
rules that are not specified in the Android style guide are inherited from
|
|
|
Google. Additionally, this project uses clang-format for automatic code
|
|
|
formatting.
|
|
|
|
|
|
This project uses C++11, but with two main caveats:
|
|
|
|
|
|
1. General considerations for using C++ in an embedded environment apply. This
|
|
|
means avoiding language features that can impose runtime overhead, due to the
|
|
|
relative scarcity of memory and CPU resources, and power considerations.
|
|
|
Examples include RTTI, exceptions, overuse of dynamic memory allocation, etc.
|
|
|
Refer to existing literature on this topic including this [Technical Report
|
|
|
on C++ Performance](http://www.open-std.org/jtc1/sc22/wg21/docs/TR18015.pdf)
|
|
|
and so on.
|
|
|
|
|
|
2. Full support of the C++ standard library is generally not expected to be
|
|
|
extensive or widespread in the embedded environments where this code will
|
|
|
run. This means things like <thread> and <mutex> should not be used, in
|
|
|
favor of simple platform abstractions that can be implemented directly with
|
|
|
less effort (potentially using those libraries if they are known to be
|
|
|
available).
|