11

Creating a Custom Binderized HAL

In Chapter 10, we introduced Project Treble, or the new binderized HAL architecture rolled out with Android 8.0. The new binderized HAL, or HIDL, is required for all devices running Android 8.0 and newer. Now it is time to apply the HIDL concepts on a custom platform, making it clear how a new HIDL-based HAL can be created and utilized.

This chapter demonstrates replacing the legacy HAL for Acme’s custom proximity device, making it HIDL based. We’ll see how the system can be customized to pick up new, custom hardware for unique devices like those used in the IoT space.

Acme Custom HIDL

As mentioned in Chapter 10, vendors are free to define their own HIDLs as well as vendor-specific customizations of AOSP/Google-defined HIDLs. Unlike traditional HAL shared libraries, these components will live within the vendor area of the platform: the /vendor or /odm filesystems. This clean separation allows the core Android system to be updated without requiring the OEM’s involvement; this is one of the primary goals of the HIDL architecture.

To illustrate this, let’s define a new HIDL for the Acme One, building upon our previous work in Chapter 8 with a custom proximity HAL. Because this proximity support is custom and is not part of the standard AOSP sensors API, our custom HIDL is needed and is called aproximity. To keep things simple, the HIDL implementation will not require modifications to the kernel—it will leverage the same shim library used in Chapter 8 to access the underlying device. The HIDL will illustrate both simple as well as more complex return data.

Note

This chapter utilizes a tool from the AOSP build results, hidl-gen. This tool is built at the same time as a given target. If you have not previously built a platform, this tool will not be found in your build tree. Please see Chapter 2 for building the platform. Alternatively, if the build system is set up and lunch has been run, the tool may also be built by executing the command m hidl-gen.

HIDL Definition

The aproximity HIDL definition is similar to the traditional HAL API covered in Chapter 8. In addition to the poll API, you can use a couple of new APIs to retrieve the proximity details for the underlying sensor as well as some details about the HIDL’s usage. Listing 11.1 and Listing 11.2 define the IAproximity HIDL (IAproximity.hal) and its corresponding types (types.hal), respectively.

Listing 11.1 IAproximity HIDL Definition

package [email protected];

/**
 * The Acme specialized proximity support, providing simple APIs to
 * illustrate vendor custom HIDL.
 */
interface IAproximity {
    /**
     * Retrieve the latest proximity value for the specified precision
     * value.
     *
     * @param precision contains the precision requested by the caller.
     *    Valid values may be retrieved using the get_details method.
     * @return the proximity value returned by the sensor
     */
    poll(int32_t precision) generates (int32_t proximity);

    /**
     * Get the details about the underlying sensor.
     *
     * @return the details for the underlying sensor, containing the
     *    supported precision values and the range of proximity values.
     */
    get_details() generates (ProximityDetails details);

    /**
     * Retrieve usage summary information about the backing HIDL service.
     *
     * @return a summary of usage information for the HIDL service.
     */
    summarize() generates (ProximitySummary summary);
};

Listing 11.2 aproximity HIDL types.hal Definition

package [email protected];

struct ValueRange {
    int32_t  min;
    int32_t  max;
};

struct ProximityDetails {
    ValueRange  precision;
    ValueRange  proximity;
};

struct ProximitySummary {
    uint64_t  pollCallCount;
    int64_t   lastPollCalledMs;
};

Create these files in the location vendor/acme/one/interfaces/aproximity/1.0 within the AOSP source tree. The AOSP build contains a tool to help generate build files and boilerplate code to get started with a service implementation. After the preceding files have been created in the tree, execute the following command in the AOSP build shell to generate an Android.bp file for building the HIDL (see Listing 11.3).

Listing 11.3 Create the HIDL Android.bp File

hidl-gen -L androidbp 
    -r vendor.acme.one:vendor/acme/one/interfaces 
    [email protected]

The new Android.bp file will be located alongside the .hal files. This file instructs the build system to generate interface headers and boilerplate code needed for the implementation and client(s). Listing 11.4 shows the generated content.

Listing 11.4 Android.bp for the aproximity HIDL

// This file is autogenerated by hidl-gen -Landroidbp.

hidl_interface {
    name: "[email protected]",
    root: "vendor.acme.one",
    product_specific: true,
    srcs: [
        "types.hal",
        "IAproximity.hal",
    ],
    interfaces: [
        "[email protected]",
    ],
    gen_java: true,
}

Before the AOSP build system will recognize the new HIDL, the top-level interfaces directory within the vendor tree for the device needs an Android.bp file. This informs the build system that the directory is the root location for HIDL packages. Create a new file, vendor/acme/one/interfaces/Android.bp, with the content of Listing 11.5.

Listing 11.5 Android.bp for the Acme One HIDL Interfaces

hidl_package_root {
    name: "vendor.acme.one",
}
HIDL Service Implementation

Now that the aproximity HIDL is defined, the platform needs the actual implementation. As previously mentioned, this is typically handled by a separate service process, particularly for HIDLs that are “drivers” for hardware. Because aproximity is an example of such a HIDL implementation, a native (for example, C++) implementation is needed. Fortunately, hidl-gen
comes to the rescue here and can generate boilerplate code as a starting point. Listing 11.6 shows the commands to execute at the top level of the build tree to generate the boilerplate code for a C++ implementation of the HIDL.

Listing 11.6 Create aproximity Service Boilerplate

mkdir -p device/acme/one/hidl/aproximity
hidl-gen -L c++-impl -o device/acme/one/hidl/aproximity 
  -r vendor.acme.one:vendor/acme/one/interfaces [email protected]

You can find the resultant files, Aproximity.cpp and Aproximity.h, in device/acme/one/hidl/aproximity. Similar to the way the javah or javac -h tools are used to help create JNI boilerplate code, hidl-gen is invaluable for creating a starting point for the service implementation. These two files will be built into a library that is then used by the service process to host the HIDL. First things first: The files need to be modified to remove unnecessary boilerplate code and provide the implementation for Acme One. The details of the methods and the special Return type will be covered shortly while working through the implementation. Listing 11.7 shows the updated header file.

Listing 11.7 Aproximity.h Implementation

#pragma once
#include <vendor/acme/one/aproximity/1.0/IAproximity.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>

namespace vendor {
namespace acme {
namespace one {
namespace aproximity {
namespace V1_0 {
namespace implementation {

using ::android::hardware::hidl_array;
using ::android::hardware::hidl_handle;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;

struct Aproximity : public IAproximity {
    Aproximity();
    ~Aproximity();

    // Methods from ::vendor::acme::one::aproximity::V1_0::IAproximity follow.
    Return<int32_t> poll(int32_t precision) override;
    Return<void> get_details(get_details_cb _hidl_cb) override;
    Return<void> summarize(summarize_cb _hidl_cb) override;

    // Methods from ::android::hidl::base::V1_0::IBase follow.
    Return<void> debug(const hidl_handle &handle,
                       const hidl_vec<hidl_string> &options) override;

private:
    uint64_t            pollCallCount;
    int64_t             lastPollCalledMs;
    int                 fd;
    proximity_params_t  params;
};

}  // namespace implementation
}  // namespace V1_0
}  // namespace aproximity
}  // namespace one
}  // namespace acme
}  // namespace vendor

Now for the actual implementation of the IAproximity methods for the HIDL. Remember that for a hardware-based HIDL (for example, driver HIDL), this is where access to the kernel via a /sys or /dev interface would be performed (or connected via a secondary library that performs the actual kernel I/O). For our trivial HIDL implementation, we will leverage the shim library that was created as part of the traditional HAL in Chapter 8, as shown in Listing 11.8.

Listing 11.8 Aproximity.cpp Implementation

#include <chrono>
#include "Aproximity.h"

using namespace std::chrono;

namespace vendor {
namespace acme {
namespace one {
namespace aproximity {
namespace V1_0 {
namespace implementation {

static int64_t now() {
    time_point now = system_clock().now();
    milliseconds nowMs =
        duration_cast<milliseconds>(now.time_since_epoch());
    return static_cast<int64_t>(nowMs.count());
}

Aproximity::Aproximity() {
    this->fd = open_sensor(this->params);
    if (this->fd < 0) {
        this->params.precision.min = -1;
        this->params.precision.range = -1;
        this->params.proximity.min = -1;
        this->params.proximity.range = -1;
    }
}

Aproximity::~Aproximity() {
    if (this->fd >= 0) {
        close_sensor(this->fd);
        this->fd = -1;
    }
    this->pollCallCount = 0;
    this->lastPollCalledMs = 0;
}

// Methods from ::vendor::acme::one::aproximity::V1_0::IAproximity follow.
Return<int32_t> Aproximity::poll(int32_t precision) {
    this->pollCallCount++;
    this->lastPollCalledMs = now(); 

    if (this->fd < 0) {
        return -1;
    }

    int shimPrecision = static_cast<int>(precision);
    int32_t result =
        static_cast<int32_t>(poll_sensor(this->fd, shimPrecision));

    return result;
}

Return<void> Aproximity::get_details(get_details_cb _hidl_cb) {
    ProximityDetails  result;

    result.precision.min = static_cast<int32_t>(this->params.precision.min);
    result.precision.max = static_cast<int32_t>(this->params.precision.range);
    result.proximity.min = static_cast<int32_t>(this->params.proximity.min);
    result.proximity.max = static_cast<int32_t>(this->params.proximity.range);

    _hidl_cb(result);
    return Void();
}

Return<void> Aproximity::summarize(summarize_cb _hidl_cb) {
    ProximitySummary  result;

    result.pollCallCount = this->pollCallCount;
    result.lastPollCalledMs = this->lastPollCalledMs;
    _hidl_cb(result);
    return Void();
}

Return<void> Aproximity::debug(const hidl_handle &handle,
                               const hidl_vec<hidl_string> & /*options*/) {
    if (handle == nullptr || handle->numFds < 1 || handle->data[0] < 0) {
        return Void();
    }
    int fd = handle->data[0];
    dprintf(fd, "HIDL:
");
    dprintf(fd, "  Poll call count: %lu
", this->pollCallCount);
    dprintf(fd, "  Last poll call:  %ld
", this->lastPollCalledMs);
    fsync(fd);
    return Void();
}

}  // namespace implementation
}  // namespace V1_0
}  // namespace aproximity
}  // namespace one
}  // namespace acme
}  // namespace vendor

Notice how the methods get_details and summarize each use a HIDL callback function provided by the caller to return data. This must be done before the method returns: it is how the service “generates” the non-primitive data returned to the client, which is waiting synchronously. What makes this particularly confusing is both methods do return something: an instance of a special class, Void! If you look carefully at the generated C++ code, each of the HIDL methods returns a special Return class instance. The HIDL framework uses the Return class along with the help of the backing Binder framework to determine that the HIDL call succeeded or not. The aproximity clients, discussed in Chapter 12, will demonstrate how this can be used.

The remaining method defined for the IAproximity interface is the poll method, which generates an int32_t with the latest proximity value. However, unlike the other methods that return more complex data types, this return value is provided as part of the Return object rather than utilizing a synchronous callback method.

Most HIDL service implementations will build the backing HIDL calls into a static library that is then linked with the service executable. The aproximity service is constructed in the same manner, requiring a separate file containing the main service (daemon) entry point. The file device/acme/one/hidl/aproximity/service.cpp provides the entry point for the service. This is where the Aproximity class instance is created and registered as the HIDL service. It then joins the RPC thread pool used by the HIDL subsystem. The RPC thread pool is utilized by the HIDL framework (really, the backing Binder subsystem) to process incoming requests. By joining the thread pool, the main thread of this service is added to the pool and will not return—it will process incoming requests forever. The code for this is straightforward as shown in Listing 11.9.

Listing 11.9 Aproximity Service Entry Point

#include <hidl/HidlSupport.h>
#include <hidl/HidlTransportSupport.h>
#include <utils/Errors.h>
#include <utils/StrongPointer.h>

#include "Aproximity.h"

using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using vendor::acme::one::aproximity::V1_0::implementation::Aproximity;
using namespace android;

int main() {
    configureRpcThreadpool(1, true);

    sp<Aproximity>  aproximity = new Aproximity();
    status_t status = aproximity->registerAsService("default");

    if (status != OK) {
        return status;
    }

    joinRpcThreadpool();
}

After the HIDL service is built and present on the platform, it needs to be started by the system so the service is available for clients. This is done using an init run command (or rc) file for the service. Unlike other daemons in the system that require changes to a core platform script (such as init.hikey960.rc), the rc file for a HIDL service is kept alongside the service code and is pulled into the image based on build rules. Create the file device/acme/one/hidl/aproximity/[email protected] with the content shown in Listing 11.10.

Listing 11.10 The Aproximity Service rc File

service vendor.aproximity-1-0 /vendor/bin/hw/[email protected]
    class hal
    user system
    group system

The rc file tells the (vendor) init process to start a service named vendor.aproximity-1-0 using the executable found at /vendor/bin/hw as part of the class of daemons labeled hal, and it is to be executed as the user system and in the group system.

The service code is ready! Time to pull it into the platform build. Create the file device/acme/one/hidl/aproximity/Android.bp with the content in Listing 11.11.

Listing 11.11 Aproximity Service Android.bp

cc_defaults {
    name: "[email protected]",
    defaults: ["hidl_defaults"],
    relative_install_path: "hw",
    shared_libs: [
        "libhidlbase",
        "libhidltransport",
        "libhwbinder",
        "libutils",
        "[email protected]",
    ],
    vendor: true,
    proprietary: true,
}

cc_library {
    name: "[email protected]",
    defaults: ["[email protected]"],
    srcs: [
        "Aproximity.cpp",
    ],
    header_libs: [
        "libacmeproximityshim_headers",
    ],
    export_include_dirs: ["."],
}

cc_binary {
    name: "[email protected]",
    defaults: ["[email protected]"],
    init_rc: ["[email protected]"],
    srcs: ["service.cpp"],
    header_libs: [
        "libacmeproximityshim_headers",
    ],
    static_libs: [
        "libacmeproximityshim",
        "[email protected]",
    ],
    proprietary: true,
    vendor: true,
}

This blueprint file for soong lays out three different pieces: a set of default build options for C++ code, a C++ library containing the Aproximity.cpp implementation, and a C++ binary containing the service entry point and linked with the service library implementation.

The cc_defaults section builds on top of an existing hidl_defaults definition elsewhere in the platform and is named [email protected]. This type of section in a blueprint file sets up common things that can be applied to other blocks, such as building a library or executable binary. The defaults section in this file specifies the relative install path for any rule using these defaults as well as a set of shared libraries to be used. The HIDL library and service binary sections both apply these defaults. The net result is the service binary will be located at /vendor/bin/hw on the target.

The service helper library is static, so it is not installed on the running target. It is linked directly into the service binary along with the contents of the libacmeproximityshim library that provides access to the underlying device.

The final section pulls together the static libraries and the service entry point code to create an executable, [email protected]. This is what is executed by init via the rc file in Listing 11.10. Remember: It is a native Linux binary, not a runtime (for example, Java/Kotlin) Android application.

SE Linux for Android Changes

Before the new HIDL can be used, you must put some security-related settings into place. As discussed in Chapter 5, Android’s use of SE Linux requires that binaries have the necessary access controls enabled. In this case, the aproximity service executable will need to have SE policies defined and applied to it before it will function.

Even though Acme One is built upon the HiKey960 device support, the SE policy changes are made in the overlay found in device/acme/one/acme_one/sepolicy. The changes are broken down into three different files: the policy file for the HIDL and two context definition files that utilize the context labels defined in the policy. The longest and most complex is the policy type enforcement file, because it defines several attributes and context labels needed for the HIDL service. As discussed in Chapter 5, crafting an SE policy file is non-trivial and also sparsely documented within AOSP. Often, the best documentation is examining existing SE policy files beneath system/sepolicy in the AOSP source tree. For example, the files system/sepolicy/public/hal_sensors.te and system/policy/vendor/hal_sensors_default.te were used as guideposts when creating the policy file for aproximity. Listing 11.12 shows the policy file, device/acme/one/acme_one/sepolicy/hal_aproximity.te.

Listing 11.12 hal_aproximity.te Type Enforcement File

hal_attribute(aproximity)

type hal_aproximity_hwservice, hwservice_manager_type;

###
# The HIDL aproximity attribute defines the _server and _client
#
binder_call(hal_aproximity_client, hal_aproximity_server)
binder_call(hal_aproximity_server, hal_aproximity_client)

hal_attribute_hwservice(hal_aproximity, hal_aproximity_hwservice)

####
# Create an execution domain for the service hosting the server side
#
type hal_aproximity_default, domain;
hal_server_domain(hal_aproximity_default, hal_aproximity)

type hal_aproximity_default_exec, exec_type, vendor_file_type, file_type;
init_daemon_domain(hal_aproximity_default)

allow hal_aproximity_default hal_aproximity_hwservice:hwservice_manager find;

# Allow the HIDL access to /dev/tty*, which would cover USB
# serial devices at /dev/ttyUSB*
allow hal_aproximity_default serial_device:chr_file rw_file_perms;

The file leverages a number of macros defined by the AOSP policy files, which makes the policy file shorter and consistent with other HIDL definitions. But those macros also obfuscate some very important details! Let’s walk through the file in chunks to make it easier to digest.

The first chunk to look at is up through the hal_attribute_hw_service line. The hal_attribute macro at the start of the file does a number of things. First, it sets up new SE attributes hal_aproximity, hal_aproximity_client, and hal_aproximity_service. From there, it also declares some neverallow policies restricting processes that have these attributes from forking other processes. This lays the groundwork for definitions later in the file. Similarly, the hal_aproximity_hwservice type is defined to be an hwservice_manager_type. This ultimately allows the hwservicemanager to manage service interfaces labeled with this type. The new hal_aproximity attribute and hal_aproximity_hwservice are tied together via the macro hal_attribute_hwservice. This macro sets up allow rules for hal_aproximity_client to find interfaces labeled with hal_aproximity_hwservice via hwservicemanager. The client label will be used later in Chapter 12 when we create HIDL client apps. The hal_attribute_hwservice macro also sets up components running as hal_aproximity_server so they can add themselves and perform find operations with the hwservicemanager and use the base HIDL functionality. Finally, the binder_call macros are used to link the hal_aproximity_client and hal_aproximity_service processes via Binder, allowing the client to call the server as well as transfer references and files between the two. That is a ton of setup for just five lines!

The next chunk defines an execution domain for the HIDL service. Recall from Chapter 5 that Android SE policies require daemons to run in a well-defined execution domain. Further, the init process handling of vendor services will not even try to start up the service defined in the rc file shown earlier in this chapter, if the SE domain is not set up correctly! The new domain, hal_aproximity_default, is first defined then passed to the macro hal_server_domain with the previously defined attribute hal_aproximity as a type attribute of the domain. Additionally, the macro associates type attributes halserverdomain and hal_aproximity_server to the new domain. This effectively marks anything in the new domain as being an aproximity server and also within the scope of the existing halserverdomain. From here, a new type, hal_aproximity_default_exec, is used for describing files needed for the HIDL service execution. This new execution domain will be used to label the HIDL service binary shortly, in another SE file.

Now that the server side has an execution domain defined, the init process must be allowed to transition to it. This is a subtle, but critical nuance. After init has forked to create the new process for the service, it needs to be allowed to transition domains to the one defined for the HIDL service. This is accomplished via the init_daemon_domain macro. This macro sets up several allow policies for init to automatically transition to hal_aproximity_default when it forks and starts a process labeled as hal_aproximity_default_exec. Rounding out this chunk is an allow policy for processes within the hal_aproximity_default domain to find the hal_aproximity_hwservice.

The final allow rule specifies that any binary running in the hal_aproximity_default domain is able to access serial devices exposed by the kernel. Because this HIDL leverages the same shim library as the traditional HAL to access the underlying device interface(s), it needs the same SE access to the backing USB serial interface that connects the custom proximity device to the system on a chip (SoC). This is the same type of allow rule that is applied to the domain executing the daemon(s) leveraging the legacy HAL, discussed in Chapter 9.

All the various types defined and allow rules established in hal_aproximity.te are great, but by themselves they do not accomplish anything. Remember, with SE Linux, each process, file, socket, and so on is given a context for the kernel to understand what access it has to features within the system. In the case of our custom HIDL, the two “top” level points of concern are the HIDL service and the HIDL interface. The service is an executable binary that is stored on the file system. To apply the SE policies to the specific file, add the lines in Listing 11.13 to the end of device/acme/one/acme_one/sepolicy/file_contexts.

Listing 11.13 Aproximity Additions to file_contexts

# Acme One Specific Changes
/vendor/bin/hw/[email protected]   
➥ u:object_r:hal_aproximity_default_exec:s0

The [email protected] executable is declared to be the hal_aproximity_default_exec type. This also marks it as a vendor-executable file and also establishes that it is a HIDL service for hal_aproximity_hwservice labeled interfaces.

This leaves the HIDL interface (the binder interface) to be defined. Create the file device/acme/one/acme_one/sepolicy/hwservice_contexts with the content shown in Listing 11.14.

Listing 11.14 Aproximity-Specific hwservice_contexts

vendor.acme.one.aproximity::IAproximity     u:object_r:hal_aproximity_hwservice:s0

This final piece brings all the SE policy definitions added for the HIDL together. The one interface the custom HIDL exposed, vendor.acme.one.aproximity::IAproximity, is declared to be of type hal_aproximity_hwservice.

Device Manifest

Now that the HIDL is defined and SE policy has been associated with it, the system needs to know that this HIDL exists and some of the details about it. The HIDL infrastructure requires devices to declare via a manifest which HIDL components are present and the details about them. Without the details in this file, hwservicemanager does not know how the HIDL can be registered and used (for example, the transport). Using this information, hwservicemanager can verify/impose correct SE policy and support for the transport. The possible values for transports are hwbinder and passthrough. Any other value, including a missing transport, is considered an error. Remember that any new devices running Android 8 or newer are allowed a very limited set of passthrough type HIDLs. Create the file devices/acme/one/acme_one/manifest.xml with the content shown in Listing 11.15.

Listing 11.15 Aproximity Additions to Device manifest.xml

<manifest version=”1.0” type=”device”>
    <hal format="hidl">
        <name>vendor.acme.one.aproximity</name>
        <transport>hwbinder</transport>
        <version>1.0</version>
        <interface>
            <name>IAproximity</name>
            <instance>default</instance>
        </interface>
    </hal>
</manifest>

Because Acme One is derived from the hikey960 device, the device manifest changes need to be picked up by the HiKey960 board configuration makefile. Edit the file device/linaro/hikey/hikey960/BoardConfig.mk, adding the content shown in Listing 11.16 immediately after the BoardConfigCommon.mk file is included.

Listing 11.16 Aproximity Additions to HiKey960 BoardConfig.mk

# Extend the device manifest file (for Acme One HIDL)
ifeq (acme_one, $(TARGET_PRODUCT))
$(warning Including Acme One HIDL manifest)
DEVICE_MANIFEST_FILE += device/acme/one/manifest.xml
endif
Build the HIDL into Acme

Just like adding a custom app or other component, you must add the HIDL service to the Acme build. Edit the file device/acme/one/acme_one/acme_one.mk and add the line shown in Listing 11.17.

Listing 11.17 Acme One HIDL Updates to acme_one.mk

PRODUCT_PACKAGES += [email protected]

Re-running the build for the target will result in updates to system.img and vendor.img. After these are flashed on to the HiKey960 board, the aproximity service will start at system startup, shown in bold in Listing 11.18.

Listing 11.18 Running aproximity Service

$ adb logcat -v time | grep -C 3 -i aproximity

01-01 00:07:12.519 I/ServiceManagement( 2453): Removing namespace from process name
[email protected] to [email protected].
01-01 00:07:12.523 I/ServiceManagement( 2449): Registered
[email protected]::IHealth/backup (start delay of 70ms)
01-01 00:07:12.523 I/[email protected]/( 2449): [email protected]/backup: Hal init done
01-01 00:07:12.525 I/ServiceManagement( 2460): Registered
[email protected]::IAproximity/default (start delay of 56ms)
01-01 00:07:12.525 I/ServiceManagement( 2460): Removing namespace from process name
[email protected] to [email protected].
00:07:12.532 I/netdClient( 2443): Skipping libnetd_client init since *we* are netd
01-01 00:07:12.533 I/ServiceManagement( 2458): Registered
[email protected]::IMemtrack/default (start delay of 65ms)
01-01 00:07:12.534 I/ServiceManagement( 2458): Removing namespace from process name
[email protected] to [email protected].

<CTRL-C>

$ adb shell
hikey960:/$ ps -A | grep -i aproximity
system   2460   1   37068   4652 0      0 S [email protected]
Locking Down the API

Just like a publicly released SDK or library, defining a new, custom HIDL is a non-trivial effort. It requires careful consideration of the API details. Creating an API just to turn around and change or remove it would be obnoxious as well as a nightmare to support. These public interfaces are not the same as internal codebase APIs, which may be constantly refactored. After they are “published,” HIDL-defined interfaces have to be considered frozen: “stable” and always available. This ensures that clients using the HIDL will work on any target that provides the interface, and that future devices that expose the HIDL are also compatible. The AOSP framework does not enforce this requirement at runtime—it really can’t because it has no way of knowing what prior revisions of an API looked like. All is not lost, though. The AOSP build system has a mechanism that ensures that an interface at a specific API does not change. Recall that each HIDL defined by the AOSP framework, as well as the custom HIDL in this section, has a version number associated with it. This combination makes the HIDL interface unique. After an interface has been defined and is ready to be “locked down,” a special hash is created for it. This hash is used both at build time and also when executing the Vendor Test Suite (VTS) to verify the vendor’s device build is compliant. You can find more details about interface hashing at:

https://source.android.com/devices/architecture/hidl/hashing

All HIDL interfaces, including the system-defined interfaces, must be locked down to pass VTS. For example, Listing 11.19 shows the first several lines of the AOSP HIDL interfaces for Android 10. It has been abbreviated for space considerations. You can find the actual file at hardware/interfaces/current.txt.

Listing 11.19 AOSP HIDL Hashes

# Do not change this file except to add new interfaces. Changing
# pre-existing interfaces will fail VTS and break framework-only OTAs

# HALs released in Android O

f219c3b5b8c...9ad090417a2 [email protected]::IDevice
4d579cae1cd...9de5c1c7362 [email protected]::IDevicesFactory
203e23f1801...624c2b0848a [email protected]::IPrimaryDevice
aaf93123dee...ebe1ee86748 [email protected]::IStream
0468c5723b0...27b04b766df [email protected]::IStreamIn
7296f7064fd...178d063680a [email protected]::IStreamOut
19d241d71c3...25e9a98b6c2 [email protected]::IStreamOutCallback
c84da9f5860...774f8ea2648 [email protected]::types
1305786c06e...54752b8566b [email protected]::types
...

The Acme One aproximity HIDL is now “locked down” and ready for use. Because this is a vendor-specific HIDL, the hash details for it go into the vendor-specific interfaces directory. Thankfully, there is no need to know the exact algorithm used to generate the hash, the exact hash type (SHA-256), or to manually calculate it. The hidl-gen tool provides support for generating interface hashes. Listing 11.20 shows how to generate the file vendor/acme/one/interfaces/current.txt, which will contain the locked-down hashes for the aproximity HIDL.

Listing 11.20 Create Custom HIDL Hashes

hidl-gen -L hash -r vendor.acme.one:vendor/acme/one/interfaces 
  -r android.hidl:system/libhidl/transport [email protected] 
  >> vendor/acme/one/interfaces/current.txt

The content of the file will look similar to the AOSP framework’s current.txt, shown in Listing 11.19. However, only the custom HIDL interface and types are present in the file, an abbreviated version of which is shown in Listing 11.21.

Listing 11.21 Acme One HIDL Hashes

3b78d426c04...8b4b19ef250 [email protected]::types
dff6991e375...976a938449f [email protected]::IAproximity

To demonstrate how changes to the HIDL are caught at build time, let’s change a small aspect of the HIDL definition. Edit the file IAproximity.hal and add a new API, reset, which can be used to reset the underlying hardware (see Listing 11.22).

Listing 11.22 Add the reset Method to IAProximity

package [email protected];

interface IAproximity {
...
    /**
     * Reset the underlying hardware
     */
    reset();
};

There is no need to provide an implementation of the new interface. Simply start a build and notice how it fails. The hash for the interface does not match the hash for the same version of the interface. An abbreviated output is shown in Listing 11.23, with the error set in bold.

Listing 11.23 Failed Build After HIDL API Change

$ m -j 1

...

[ 19% 7/36] HIDL c++-sources: vendor/acme/one/interfaces/aproximity/1.0/types.hal
vendor/acme/one/interfaces/aproximity/1
FAILED:
out/soong/.intermediates/vendor/acme/one/interfaces/aproximity/1.0/vendor.acme.
[email protected]_genc++/gen/vendor/acme/one/aproximity/1.0/AproximityAll.cpp
out/soong/.intermediates/vendor/acme/one/interfaces/aproximity/1.0/vendor.acme.
[email protected]_genc++/gen/vendor/acme/one/aproximity/1.0/types.cpp
rm -rf
out/soong/.intermediates/vendor/acme/one/interfaces/aproximity/1.0/vendor.acme.
[email protected]_genc++/gen && out/soong/host/linux-x86/bin/hidl-gen -R -p . -d
out/soong/.intermediates/vendor/acme/one/interfaces/aproximity/1.0/vendor.acme.
[email protected]_genc++/gen/vendor/acme/one/aproximity/1.0/AproximityAll.cpp.d -o
out/soong/.intermediates/vendor/acme/one/interfaces/aproximity/1.0/vendor.acme.
[email protected]_genc++/gen -L c++-sources -
rvendor.acme.one:vendor/acme/one/interfaces -
randroid.hidl:system/libhidl/transport [email protected]
ERROR: [email protected]::IAproximity has hash
c270b98c7a304a5026ffd1c6e6cfb03bf01756efdcd45950758a87e604b108d7 which does not match
hash on record. This interface has been frozen. Do not change it!
ERROR: Could not parse [email protected]::types. Aborting.
09:55:15 ninja failed with: exit status 1

#### failed to build some targets (46 seconds) ####

Because we had locked down the HIDL interface (hashes were created), the build system will not allow a new image to be created. There are three choices in this situation: do not modify the API, bump the version number and provide the new API implementation, or provide the new API implementation and re-generate the hash (safe because the HIDL API was not fully released). Bumping the version number would require a new HIDL implementation that derives from this 1.0 version and is beyond the scope of this book. See the AOSP sensors HIDL API for an example of a multi-versioned HIDL API. To keep things simple, we will just add a no-op implementation for the new API, regenerate the API hashes for our HIDL, and verify we can build, as shown in Listing 11.24.

Listing 11.24 Regenerate Custom HIDL Hashes

$ hidl-gen -L hash -r vendor.acme.one:vendor/acme/one/interfaces 
  -r android.hidl:system/libhidl/transport [email protected] 
  > vendor/acme/one/interfaces/current.txt
$ m -j 12

...

[100% 15/15] Target vendor fs image: out/target/product/hikey960/vendor.img

#### build completed successfully (10 seconds) ####

Summary

This chapter defined a new HIDL-based HAL for the Acme platform: aproximity. This HAL replaces the traditional HAL implementation defined in Chapter 8. The implementation leverages the shim library that was built as part of the traditional HAL, but exposes the functionality via Binder-based APIs. The binders are registered with the system by a simple service executable and processed by a service-side implementation of the IAproximity interface. Chapter 12 will round out the HIDL example by showing how to create client applications that can access the aproximity HAL.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.81.221.121