206 lines
10 KiB
Markdown
206 lines
10 KiB
Markdown
*** 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++17 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++17, 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).
|