You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

206 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

*** 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 cant 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
nanoapps 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).