Extending CHRE with Vendor-specific Functionality

The CHRE framework is easily extensible with no modifications to the core framework. Depending on the goals of the new API, one or more of the following steps must be performed. At a high-level, to add a new vendor-specific API to CHRE, one must:

  1. Define new APIs in a header that can be referenced by both platform CHRE framework code and vendor-specific nanoapps.

  2. Expose the new APIs from the framework to nanoapps, and connect them to a new module to provide the desired functionality

  3. Integrate the new module with existing CHRE framework features, e.g. the event subsystem, to provide complete functionality that fits within the existing CHRE conventions

It's best to refer to existing standard CHRE API feature areas, such as chre/wifi.h and WifiRequestManager, and follow a similar design where possible.

Defining the API

To prevent collision with future common CHRE API definitions, vendor extensions must not use the plain ‘chre’ prefix followed by a capitalized letter. Instead, it’s recommended to prefix the APIs with the vendor’s name as lowercase. For example, if your company name is XYZ Semiconductor and you’re defining a new ‘widget’ API, it’s recommended to use a naming scheme like chrexyzWidget<FunctionName>(), and included indirectly via #include <chre_xyz.h> or directly via <chre_xyz/widget.h>. The equivalent C++ namespace would be ::chre::xyz.

There are reserved ranges for vendor/implementation-specific event types (starting from CHRE_EVENT_INTERNAL_EXTENDED_FIRST_EVENT), and other cases where vendors may wish or need to define a custom value in an existing field. To prevent collision with future versions of the CHRE API, vendor extensions must only use values within vendor-reserved ranges. If you would like to add a new value to an existing field for a vendor extension and a vendor-reserved range does not already exist, please reach out to the CHRE team for guidance - solutions may involve creating a new reserved range in the common CHRE API, or providing advice on a different method of defining the API.

Vendors can only add on to the CHRE API - existing APIs must not be changed. Do not modify core CHRE definitions, for example by adding on fields to common structures, re-using event types, repurposing fields that are reserved for future use, etc.

It’s recommended that any vendor extensions consider compatibility when designing it - see the Compatibility section for API design guidelines.

If this API is intended to be open-sourced, it should be added to platform/<platform_name>/extensions/include. Otherwise, it’s suggested that the API be placed outside of the CHRE tree, in a separate Git project under vendor/ in the Android tree, to avoid potential conflicts when upgrading to a new version of CHRE.

Build Customization

As part of the CHRE framework build system, the CHRE_VARIANT_MK_INCLUDES environment variable can be used to inject an external .mk file into the top-level build without any source code changes in the system/chre project. Alternatively, if open sourcing, the platform.mk file should contain the additions needed to support the new vendor API. Refer to the CHRE framework build documentation for further details.

To expose the new functionality to nanoapps, it’s recommended to create a single .mk file that adds the necessary COMMON_CFLAGS entries (and potentially other build configuration). For example, create a chrexyz.mk file which nanoapps should include in their Makefile prior to including $(CHRE_PREFIX)/build/nanoapp/app.mk.

Threading Model

Interactions with a nanoapp always happen from within the CHRE thread that runs the EventLoop, so vendor extension code does not need to worry about race conditions due to multiple nanoapps calling into APIs, and likewise nanoapps do not need to worry about race conditions in its callbacks/handlers. However, it is common for a platform module to receive data in a callback on another thread. In that case, it is recommended to use EventLoopManager::deferCallback() to pass the incoming data to the CHRE thread for processing, as opposed to using mutexes or other synchronization primitives, to avoid multithreading-related issues that can arise in rare conditions. Further, note that most of the core CHRE functionality is only safe to call from within the CHRE thread (other than posting an event, or methods that are explicitly marked as thread-safe).

Initialization

Since the new API will not be part of the core framework, it won’t be attached to EventLoopManager or initialized as part of chre::init() or EventLoopManagerSingleton::get()->lateInit(), since vendor-extension APIs are by definition not part of the common code. Instead, a separate singleton object should be created, for example chre::xyz::VendorExtensionManager, and platform-specific initialization code should invoke any necessary initialization after chre::init is called, but before loading any static nanoapps or invoking EventLoop::run() to ensure that nanoapps don’t begin interacting with the API before its state is ready.

Handling Nanoapp API Calls

Calls from a nanoapp into the CHRE framework first arrive in platform-specific code (refer to the Framework Overview documentation for details). The first step once an API call reaches the framework is usually to call EventLoopManager::validateChreApiCall(__func__). This fetches a pointer to the Nanoapp object associated with the nanoapp that invoked the API, which will fail if the API is called outside of the EventLoop thread context (see the Threading Model above). From this point, the vendor extension singleton should be used to invoke the appropriate functionality.

Sending Events to Nanoapps

Vendor extension APIs that need to pass data to a nanoapp asynchronously should use the event susbsystem, using the vendor-reserved event type range (starting at CHRE_EVENT_INTERNAL_EXTENDED_FIRST_EVENT and extending to CHRE_EVENT_INTERNAL_LAST_EVENT). Event types for a given vendor extension should be globally unique and stable over time.

Synchronous API calls that can potentially block for periods greater than a few milliseconds are discouraged, as these can prevent other nanoapps from executing, and/or cause the pending event queue to grow excessively during periods of high activity. Refer to the GNSS and WWAN APIs for design patterns related to passing data to a nanoapp asynchronously, using custom event payloads and/or chreAsyncResult.

Events can either be unicast to a nanoapp identified by its instance ID (Nanoapp::getInstanceId()), or broadcast to all nanoapps registered for the given event type - see Nanoapp::registerForBroadcastEvent() and Nanoapp::unregisterForBroadcastEvent().

Use EventLoop::postEventOrDie() or EventLoop::postLowPriorityEventOrFree() (via EventLoopManagerSingleton::get()->getEventLoop()) to pass events to nanoapps, depending on what error handling is desired in the case that the event cannot be posted to the queue. Any memory referenced by eventData must not be modified until freeCallback is invoked.