|
|
# Design for Compatibility
|
|
|
|
|
|
[TOC]
|
|
|
|
|
|
Compatibility is an important attribute of CHRE, which is accomplished through a
|
|
|
combination of thoughtful API and framework design. When we refer to
|
|
|
compatibility within the scope of CHRE, there are two main categories:
|
|
|
|
|
|
* **Code compatibility**, which means that a nanoapp can be recompiled to run on
|
|
|
a new platform without needing any code changes. CHRE provides this
|
|
|
cross-device compatibility for all nanoapps which are written in a supported
|
|
|
programming language (C99 or C++11), and reference only the standard CHRE APIs
|
|
|
and mandatory standard library elements (or have these standard library
|
|
|
functions statically linked into their binary).
|
|
|
|
|
|
* **Binary compatibility**, which means that a nanoapp binary which has been
|
|
|
compiled against a particular version of the CHRE API can run on a CHRE
|
|
|
framework implementation which was compiled against a different version of the
|
|
|
API. This is also called *cross-version compatibility*. Note that this does
|
|
|
*not* mean that a nanoapp compiled against one version of the CHRE API can be
|
|
|
compiled against a different version of the CHRE API without compiler errors -
|
|
|
although rare, compile-time breakages are permitted with sufficient
|
|
|
justification, since nanoapp developers can update their code at the time they
|
|
|
migrate to the new API version.
|
|
|
|
|
|
This section provides an overview of the mechanisms used to ensure
|
|
|
compatibility.
|
|
|
|
|
|
## CHRE API
|
|
|
|
|
|
The CHRE API is a native C API that defines the interface between a nanoapp and
|
|
|
any underlying CHRE implementation to provide cross-platform and cross-version
|
|
|
compatibility. It is designed to be supportable even in very memory-constrained
|
|
|
environments (total system memory in the hundreds of kilobytes range), and is
|
|
|
thoroughly documented to clearly indicate the intended behavior.
|
|
|
|
|
|
The CHRE API follows [semantic versioning](https://semver.org) principles to
|
|
|
maintain binary compatibility. In short, this means that the minor version is
|
|
|
incremented when new features and changes are introduced in a backwards
|
|
|
compatible way, and the major version is only incremented on a
|
|
|
compatibility-breaking change. One key design goal of the CHRE API is to avoid
|
|
|
major version changes if at all possible, through use of
|
|
|
compatibility-preserving code in the framework and Nanoapp Support Library
|
|
|
(NSL).
|
|
|
|
|
|
Minor version updates to the CHRE API typically occur alongside each Android
|
|
|
release, but the CHRE version and Android version are not intrinsically related.
|
|
|
Nanoapps should be compiled against the latest version to be able to use any
|
|
|
newly added features, though nanoapp binaries are compatible across minor
|
|
|
version changes.
|
|
|
|
|
|
### API Compatibility Design Principles
|
|
|
|
|
|
API design principles applied within CHRE to ensure compatibility include the
|
|
|
following (not an exhaustive list). These are recommended to be followed for any
|
|
|
vendor-specific API extensions as well.
|
|
|
|
|
|
* Functionality must not be removed unless it was optional at the time of
|
|
|
introduction, for example as indicated by a capabilities flag (an exception
|
|
|
exists if it has no impact on the regular functionality of a nanoapp, for
|
|
|
example a feature that only aids in debugging)
|
|
|
* Reserved fields must be set to 0 by the sender and ignored by the recipient
|
|
|
* Fields within a structure must not be reordered - new fields may only be
|
|
|
introduced by reclaiming reserved fields (preferred), or adding to the end of
|
|
|
a structure
|
|
|
* When reclaiming a reserved field, the default value of 0 must indicate a
|
|
|
property that is guaranteed to hold for previous API versions, or “unknown”
|
|
|
* Arguments to a function must not be added or removed - introduce a new
|
|
|
function instead
|
|
|
* The meaning of constants (e.g. event types) must never be changed, but may be
|
|
|
deprecated and eventually replaced
|
|
|
|
|
|
## Binary Backward Compatibility and the NSL
|
|
|
|
|
|
This is where we want a nanoapp compiled against e.g. v1.2 to run on a CHRE v1.1
|
|
|
or older implementation. This is done through a combination of runtime feature
|
|
|
discovery, and compatibility behaviors included in the Nanoapp Support Library
|
|
|
(NSL).
|
|
|
|
|
|
Runtime feature discovery involves a nanoapp querying for the support of a
|
|
|
feature (e.g. RTT support indicated in `chreWifiGetCapabilities()`, or querying
|
|
|
for a specific sensor in `chreSensorFindDefault()`), which allows it determine
|
|
|
whether the associated functionality is expected to work. The nanoapp may also
|
|
|
query `chreGetApiVersion()` to find out the version of the CHRE API supported by
|
|
|
the platform it is running on. If a nanoapp has a hard requirement on some
|
|
|
missing functionality, it may choose to return false from `nanoappStart()` to
|
|
|
abort initialization.
|
|
|
|
|
|
However, a CHRE implementation cannot anticipate all future API changes and
|
|
|
automatically provide compatibility. So the NSL serves as a transparent shim
|
|
|
which is compiled into the nanoapp binary to ensure this compatibility. For
|
|
|
example, a nanoapp compiled against v1.2 must be able to reference and call
|
|
|
`chreConfigureHostSleepStateEvents()` when running on a CHRE v1.1 or earlier,
|
|
|
although such a function call would have no effect in that case. Typical dynamic
|
|
|
linking approaches would find an unsatisfied dependency and fail to load the
|
|
|
nanoapp, even if it does not actually call the function, for example by wrapping
|
|
|
it in a condition that first checks the CHRE version. In
|
|
|
`platform/shared/nanoapp/nanoapp_support_lib_dso.cc`, this is supported by
|
|
|
intercepting CHRE API function calls and either calling through to the
|
|
|
underlying platform if it’s supported, or replacing it with stub functionality.
|
|
|
|
|
|
Along similar lines, if new fields are added to the end of a structure without
|
|
|
repurposing a reserved field in an update to the CHRE API, as was the case with
|
|
|
`bearing_accuracy` in `chreGnssLocationEvent`, the nanoapp must be able to
|
|
|
reference the new field without reading uninitialized memory. This is enabled by
|
|
|
the NSL, which can intercept the event, and copy it into the new, larger
|
|
|
structure, and set the new fields to their default values.
|
|
|
|
|
|
Since these NSL compatibility behaviors carry some amount of overhead (even if
|
|
|
very slight), they can be disabled if it is known that a nanoapp will never run
|
|
|
on an older CHRE version. This may be the case for a nanoapp developed for a
|
|
|
specific device, for example. The NSL may also limit its compatibility range
|
|
|
based on knowledge of the API version at which support for given hardware was
|
|
|
introduced. For example, if a new hardware family first added support for the
|
|
|
CHRE framework at API v1.1, then NSL support for v1.0 is unnecessary.
|
|
|
|
|
|
Outside of these cases, the NSL must provide backwards compatibility for at
|
|
|
least 3 previous versions, and is strongly recommended to provide support for
|
|
|
all available versions. This means that if the first API supported by a target
|
|
|
device is v1.0, then a nanoapp compiled against API v1.4 must have NSL support
|
|
|
for v1.1 through v1.4, and should ideally also support v1.0.
|
|
|
|
|
|
## Binary Forward Compatibility and Framework Requirements
|
|
|
|
|
|
Conversely, this is where we want a nanoapp compiled against e.g. v1.1 to run
|
|
|
against CHRE v1.2 or later implementations. The NSL cannot directly provide this
|
|
|
kind of compatibility, so it must be ensured through a combination of careful
|
|
|
CHRE API design, and compatibility behaviors in the CHRE framework.
|
|
|
|
|
|
Similar to how Android apps have a “target SDK” attribute, nanoapps have a
|
|
|
“target API version” which indicates the version of the CHRE API they were
|
|
|
compiled against. The framework can inspect this value and provide compatibility
|
|
|
behavior as needed. For example, `chreGetSensorInfo()` populates memory provided
|
|
|
by the nanoapp with information about a given sensor. In CHRE API v1.1, this
|
|
|
structure was extended with a new field, `minInterval`. Therefore, the framework
|
|
|
must check if the nanoapp’s target API is v1.1 or later before writing this
|
|
|
field.
|
|
|
|
|
|
To avoid carrying forward compatibility code indefinitely, it is permitted for a
|
|
|
CHRE implementation to reject compatibility with nanoapps compiled against an
|
|
|
API minor version that is 2 or more generations older. For example, a CHRE v1.4
|
|
|
implementation may reject attempts to load a nanoapp compiled against CHRE API
|
|
|
v1.2, but it must ensure compatibility with v1.3. However, providing the full
|
|
|
range of compatibility generally does not require significant effort on behalf
|
|
|
of the CHRE implementation, so this is recommended for maximum flexibility.
|
|
|
|
|
|
## ABI Stability
|
|
|
|
|
|
CHRE does not define a standard Application Binary Interface (ABI) - this is
|
|
|
left as a platform responsibility in order to provide maximum flexibility.
|
|
|
However, CHRE implementations must ensure that binary compatibility is
|
|
|
maintained with nanoapps, by choosing a design that provides this property. For
|
|
|
example, if a syscall-like approach is used (with the help of the NSL) to call
|
|
|
from position-independent nanoapp code into fixed-position CHRE API functions
|
|
|
(e.g. in a statically linked monolithic firmware image), syscall IDs and their
|
|
|
calling conventions must remain stable. It is not acceptable to require all
|
|
|
nanoapps to be recompiled to be able to work with an updated CHRE
|
|
|
implementation.
|
|
|
|
|
|
## CHRE PALs
|
|
|
|
|
|
Since the PAL APIs are largely based on the CHRE APIs, they benefit from many of
|
|
|
the compatibility efforts by default. Overall, binary compatibility in the CHRE
|
|
|
PAL APIs are less involved than the CHRE APIs, because we expect CHRE and CHRE
|
|
|
PAL implementations to be built into the vendor image together, and usually run
|
|
|
at the same version except for limited periods during development. However, a
|
|
|
PAL implementation can simultaneously support multiple PAL API versions from a
|
|
|
single codebase by adapting its behavior based on the `requestedApiVersion`
|
|
|
parameter in the \*GetApi method, e.g. `chrePalWifiGetApi()`.
|
|
|
|
|
|
## Deprecation Strategy
|
|
|
|
|
|
In general, nanoapp compilation may be broken in a minor update (given
|
|
|
sufficient justification - this is not a light decision to make, considering the
|
|
|
downstream impact to nanoapp developers), but deprecation of functionality at a
|
|
|
binary level occurs over a minimum of 2 years (minor versions). The general
|
|
|
process for deprecating a function in the CHRE API is as follows:
|
|
|
|
|
|
* In a new minor version `N` of the CHRE API, the function is marked with
|
|
|
`@deprecated`, with a description of the recommended alternative, and ideally
|
|
|
the justification for the deprecation, so nanoapp developers know why it's
|
|
|
important to update.
|
|
|
|
|
|
* Depending on the severity of impact, the function may also be tagged with a
|
|
|
compiler attribute to generate a warning (e.g. `CHRE_DEPRECATED`) that may
|
|
|
be ignored. Or, version `N` or later, an attribute or other method may be
|
|
|
used to break compilation of nanoapps using the deprecated function, forcing
|
|
|
them to update. If not considered a high severity issue and compatibility is
|
|
|
easy to maintain, it is recommended to break compilation only in version
|
|
|
`N+2` or later.
|
|
|
|
|
|
* Binary compatibility at this stage must be maintained. For example the NSL
|
|
|
should map the new functionality to the deprecated function when running on
|
|
|
CHRE `N-1` or older, or a suitable alternative must be devised. Likewise,
|
|
|
CHRE must continue to provide the deprecated function to support nanoapps
|
|
|
built against `N-1`.
|
|
|
|
|
|
* Impacts to binary compatibility on the CHRE side may occur 2 versions after
|
|
|
the function is made compilation-breaking for nanoapps, since forward
|
|
|
compatibility is guaranteed for 2 minor versions. If done, the nanoapp must be
|
|
|
rejected at load time.
|
|
|
|
|
|
* Impacts to binary compatibility on the nanoapp side may occur 4 versions after
|
|
|
the function is marked deprecated (at `N+4`), since backward compatibility is
|
|
|
guaranteed for 4 minor versions. If done, the NSL must cause `nanoappStart()`
|
|
|
to return false on version `N` or older.
|
|
|
|
|
|
For example, if a function is marked deprecated in `N`, and becomes a
|
|
|
compilation-breaking error in `N+2`, then a CHRE implementation at `N+4` may
|
|
|
remove the deprecated functionality only if it rejects a nanoapp built against
|
|
|
`N+1` or older at load time. Likewise, the NSL can remove compatibility code for
|
|
|
the deprecated function at `N+4`. CHRE and NSL implementations must not break
|
|
|
compatibility in a fragmented, unpredictable, or hidden way, for example by
|
|
|
replacing the deprecated function with a stub that does nothing. If it is
|
|
|
possible for CHRE and/or the NSL to detect only nanoapps that use the deprecated
|
|
|
functionality, then it is permissible to block loading of only those nanoapps,
|
|
|
but otherwise this must be a blanket ban of all nanoapps compiled against the
|
|
|
old API version.
|