| Brian Duddie | 7f23a87 | 2020-07-01 14:27:31 -0700 | [diff] [blame] | 1 | # Design for Compatibility |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | Compatibility is an important attribute of CHRE, which is accomplished through a |
| 6 | combination of thoughtful API and framework design. When we refer to |
| 7 | compatibility within the scope of CHRE, there are two main categories: |
| 8 | |
| 9 | * **Code compatibility**, which means that a nanoapp can be recompiled to run on |
| 10 | a new platform without needing any code changes. CHRE provides this |
| 11 | cross-device compatibility for all nanoapps which are written in a supported |
| karthik bharadwaj | 9503a1d | 2021-09-24 14:42:17 -0700 | [diff] [blame] | 12 | programming language (C11 or C++17), and reference only the standard CHRE APIs |
| Brian Duddie | 7f23a87 | 2020-07-01 14:27:31 -0700 | [diff] [blame] | 13 | and mandatory standard library elements (or have these standard library |
| 14 | functions statically linked into their binary). |
| 15 | |
| 16 | * **Binary compatibility**, which means that a nanoapp binary which has been |
| 17 | compiled against a particular version of the CHRE API can run on a CHRE |
| 18 | framework implementation which was compiled against a different version of the |
| 19 | API. This is also called *cross-version compatibility*. Note that this does |
| 20 | *not* mean that a nanoapp compiled against one version of the CHRE API can be |
| 21 | compiled against a different version of the CHRE API without compiler errors - |
| 22 | although rare, compile-time breakages are permitted with sufficient |
| 23 | justification, since nanoapp developers can update their code at the time they |
| 24 | migrate to the new API version. |
| 25 | |
| 26 | This section provides an overview of the mechanisms used to ensure |
| 27 | compatibility. |
| 28 | |
| 29 | ## CHRE API |
| 30 | |
| 31 | The CHRE API is a native C API that defines the interface between a nanoapp and |
| 32 | any underlying CHRE implementation to provide cross-platform and cross-version |
| 33 | compatibility. It is designed to be supportable even in very memory-constrained |
| 34 | environments (total system memory in the hundreds of kilobytes range), and is |
| 35 | thoroughly documented to clearly indicate the intended behavior. |
| 36 | |
| 37 | The CHRE API follows [semantic versioning](https://semver.org) principles to |
| 38 | maintain binary compatibility. In short, this means that the minor version is |
| 39 | incremented when new features and changes are introduced in a backwards |
| 40 | compatible way, and the major version is only incremented on a |
| 41 | compatibility-breaking change. One key design goal of the CHRE API is to avoid |
| 42 | major version changes if at all possible, through use of |
| 43 | compatibility-preserving code in the framework and Nanoapp Support Library |
| 44 | (NSL). |
| 45 | |
| 46 | Minor version updates to the CHRE API typically occur alongside each Android |
| 47 | release, but the CHRE version and Android version are not intrinsically related. |
| 48 | Nanoapps should be compiled against the latest version to be able to use any |
| 49 | newly added features, though nanoapp binaries are compatible across minor |
| 50 | version changes. |
| 51 | |
| 52 | ### API Compatibility Design Principles |
| 53 | |
| 54 | API design principles applied within CHRE to ensure compatibility include the |
| 55 | following (not an exhaustive list). These are recommended to be followed for any |
| 56 | vendor-specific API extensions as well. |
| 57 | |
| 58 | * Functionality must not be removed unless it was optional at the time of |
| 59 | introduction, for example as indicated by a capabilities flag (an exception |
| 60 | exists if it has no impact on the regular functionality of a nanoapp, for |
| 61 | example a feature that only aids in debugging) |
| 62 | * Reserved fields must be set to 0 by the sender and ignored by the recipient |
| 63 | * Fields within a structure must not be reordered - new fields may only be |
| 64 | introduced by reclaiming reserved fields (preferred), or adding to the end of |
| 65 | a structure |
| 66 | * When reclaiming a reserved field, the default value of 0 must indicate a |
| 67 | property that is guaranteed to hold for previous API versions, or “unknown” |
| 68 | * Arguments to a function must not be added or removed - introduce a new |
| 69 | function instead |
| 70 | * The meaning of constants (e.g. event types) must never be changed, but may be |
| 71 | deprecated and eventually replaced |
| 72 | |
| 73 | ## Binary Backward Compatibility and the NSL |
| 74 | |
| 75 | This is where we want a nanoapp compiled against e.g. v1.2 to run on a CHRE v1.1 |
| 76 | or older implementation. This is done through a combination of runtime feature |
| 77 | discovery, and compatibility behaviors included in the Nanoapp Support Library |
| 78 | (NSL). |
| 79 | |
| 80 | Runtime feature discovery involves a nanoapp querying for the support of a |
| 81 | feature (e.g. RTT support indicated in `chreWifiGetCapabilities()`, or querying |
| 82 | for a specific sensor in `chreSensorFindDefault()`), which allows it determine |
| 83 | whether the associated functionality is expected to work. The nanoapp may also |
| 84 | query `chreGetApiVersion()` to find out the version of the CHRE API supported by |
| 85 | the platform it is running on. If a nanoapp has a hard requirement on some |
| 86 | missing functionality, it may choose to return false from `nanoappStart()` to |
| 87 | abort initialization. |
| 88 | |
| 89 | However, a CHRE implementation cannot anticipate all future API changes and |
| 90 | automatically provide compatibility. So the NSL serves as a transparent shim |
| 91 | which is compiled into the nanoapp binary to ensure this compatibility. For |
| 92 | example, a nanoapp compiled against v1.2 must be able to reference and call |
| 93 | `chreConfigureHostSleepStateEvents()` when running on a CHRE v1.1 or earlier, |
| 94 | although such a function call would have no effect in that case. Typical dynamic |
| 95 | linking approaches would find an unsatisfied dependency and fail to load the |
| 96 | nanoapp, even if it does not actually call the function, for example by wrapping |
| 97 | it in a condition that first checks the CHRE version. In |
| 98 | `platform/shared/nanoapp/nanoapp_support_lib_dso.cc`, this is supported by |
| 99 | intercepting CHRE API function calls and either calling through to the |
| 100 | underlying platform if it’s supported, or replacing it with stub functionality. |
| 101 | |
| 102 | Along similar lines, if new fields are added to the end of a structure without |
| 103 | repurposing a reserved field in an update to the CHRE API, as was the case with |
| 104 | `bearing_accuracy` in `chreGnssLocationEvent`, the nanoapp must be able to |
| 105 | reference the new field without reading uninitialized memory. This is enabled by |
| 106 | the NSL, which can intercept the event, and copy it into the new, larger |
| 107 | structure, and set the new fields to their default values. |
| 108 | |
| 109 | Since these NSL compatibility behaviors carry some amount of overhead (even if |
| 110 | very slight), they can be disabled if it is known that a nanoapp will never run |
| 111 | on an older CHRE version. This may be the case for a nanoapp developed for a |
| 112 | specific device, for example. The NSL may also limit its compatibility range |
| 113 | based on knowledge of the API version at which support for given hardware was |
| 114 | introduced. For example, if a new hardware family first added support for the |
| 115 | CHRE framework at API v1.1, then NSL support for v1.0 is unnecessary. |
| 116 | |
| 117 | Outside of these cases, the NSL must provide backwards compatibility for at |
| 118 | least 3 previous versions, and is strongly recommended to provide support for |
| 119 | all available versions. This means that if the first API supported by a target |
| 120 | device is v1.0, then a nanoapp compiled against API v1.4 must have NSL support |
| 121 | for v1.1 through v1.4, and should ideally also support v1.0. |
| 122 | |
| 123 | ## Binary Forward Compatibility and Framework Requirements |
| 124 | |
| 125 | Conversely, this is where we want a nanoapp compiled against e.g. v1.1 to run |
| 126 | against CHRE v1.2 or later implementations. The NSL cannot directly provide this |
| 127 | kind of compatibility, so it must be ensured through a combination of careful |
| 128 | CHRE API design, and compatibility behaviors in the CHRE framework. |
| 129 | |
| 130 | Similar to how Android apps have a “target SDK” attribute, nanoapps have a |
| 131 | “target API version” which indicates the version of the CHRE API they were |
| 132 | compiled against. The framework can inspect this value and provide compatibility |
| 133 | behavior as needed. For example, `chreGetSensorInfo()` populates memory provided |
| 134 | by the nanoapp with information about a given sensor. In CHRE API v1.1, this |
| 135 | structure was extended with a new field, `minInterval`. Therefore, the framework |
| 136 | must check if the nanoapp’s target API is v1.1 or later before writing this |
| 137 | field. |
| 138 | |
| 139 | To avoid carrying forward compatibility code indefinitely, it is permitted for a |
| 140 | CHRE implementation to reject compatibility with nanoapps compiled against an |
| 141 | API minor version that is 2 or more generations older. For example, a CHRE v1.4 |
| 142 | implementation may reject attempts to load a nanoapp compiled against CHRE API |
| 143 | v1.2, but it must ensure compatibility with v1.3. However, providing the full |
| 144 | range of compatibility generally does not require significant effort on behalf |
| 145 | of the CHRE implementation, so this is recommended for maximum flexibility. |
| 146 | |
| 147 | ## ABI Stability |
| 148 | |
| 149 | CHRE does not define a standard Application Binary Interface (ABI) - this is |
| 150 | left as a platform responsibility in order to provide maximum flexibility. |
| 151 | However, CHRE implementations must ensure that binary compatibility is |
| 152 | maintained with nanoapps, by choosing a design that provides this property. For |
| 153 | example, if a syscall-like approach is used (with the help of the NSL) to call |
| 154 | from position-independent nanoapp code into fixed-position CHRE API functions |
| 155 | (e.g. in a statically linked monolithic firmware image), syscall IDs and their |
| 156 | calling conventions must remain stable. It is not acceptable to require all |
| 157 | nanoapps to be recompiled to be able to work with an updated CHRE |
| 158 | implementation. |
| 159 | |
| 160 | ## CHRE PALs |
| 161 | |
| 162 | Since the PAL APIs are largely based on the CHRE APIs, they benefit from many of |
| 163 | the compatibility efforts by default. Overall, binary compatibility in the CHRE |
| 164 | PAL APIs are less involved than the CHRE APIs, because we expect CHRE and CHRE |
| 165 | PAL implementations to be built into the vendor image together, and usually run |
| 166 | at the same version except for limited periods during development. However, a |
| 167 | PAL implementation can simultaneously support multiple PAL API versions from a |
| 168 | single codebase by adapting its behavior based on the `requestedApiVersion` |
| 169 | parameter in the \*GetApi method, e.g. `chrePalWifiGetApi()`. |
| 170 | |
| 171 | ## Deprecation Strategy |
| 172 | |
| 173 | In general, nanoapp compilation may be broken in a minor update (given |
| 174 | sufficient justification - this is not a light decision to make, considering the |
| 175 | downstream impact to nanoapp developers), but deprecation of functionality at a |
| 176 | binary level occurs over a minimum of 2 years (minor versions). The general |
| 177 | process for deprecating a function in the CHRE API is as follows: |
| 178 | |
| 179 | * In a new minor version `N` of the CHRE API, the function is marked with |
| 180 | `@deprecated`, with a description of the recommended alternative, and ideally |
| 181 | the justification for the deprecation, so nanoapp developers know why it's |
| 182 | important to update. |
| 183 | |
| 184 | * Depending on the severity of impact, the function may also be tagged with a |
| 185 | compiler attribute to generate a warning (e.g. `CHRE_DEPRECATED`) that may |
| 186 | be ignored. Or, version `N` or later, an attribute or other method may be |
| 187 | used to break compilation of nanoapps using the deprecated function, forcing |
| 188 | them to update. If not considered a high severity issue and compatibility is |
| 189 | easy to maintain, it is recommended to break compilation only in version |
| 190 | `N+2` or later. |
| 191 | |
| 192 | * Binary compatibility at this stage must be maintained. For example the NSL |
| 193 | should map the new functionality to the deprecated function when running on |
| 194 | CHRE `N-1` or older, or a suitable alternative must be devised. Likewise, |
| 195 | CHRE must continue to provide the deprecated function to support nanoapps |
| 196 | built against `N-1`. |
| 197 | |
| 198 | * Impacts to binary compatibility on the CHRE side may occur 2 versions after |
| 199 | the function is made compilation-breaking for nanoapps, since forward |
| 200 | compatibility is guaranteed for 2 minor versions. If done, the nanoapp must be |
| 201 | rejected at load time. |
| 202 | |
| 203 | * Impacts to binary compatibility on the nanoapp side may occur 4 versions after |
| 204 | the function is marked deprecated (at `N+4`), since backward compatibility is |
| 205 | guaranteed for 4 minor versions. If done, the NSL must cause `nanoappStart()` |
| 206 | to return false on version `N` or older. |
| 207 | |
| 208 | For example, if a function is marked deprecated in `N`, and becomes a |
| 209 | compilation-breaking error in `N+2`, then a CHRE implementation at `N+4` may |
| 210 | remove the deprecated functionality only if it rejects a nanoapp built against |
| 211 | `N+1` or older at load time. Likewise, the NSL can remove compatibility code for |
| 212 | the deprecated function at `N+4`. CHRE and NSL implementations must not break |
| 213 | compatibility in a fragmented, unpredictable, or hidden way, for example by |
| 214 | replacing the deprecated function with a stub that does nothing. If it is |
| 215 | possible for CHRE and/or the NSL to detect only nanoapps that use the deprecated |
| 216 | functionality, then it is permissible to block loading of only those nanoapps, |
| 217 | but otherwise this must be a blanket ban of all nanoapps compiled against the |
| 218 | old API version. |