8

Getting to Android: The HAL

The Android’s Hardware Abstraction Layer (HAL) is just an interface. It is a layer of abstraction that separates components by disconnecting the definition of the component’s behavior from its implementation.

As is always the case, introducing an abstraction layer comes with a cost. So why use the HAL? There are several reasons.

Why a HAL?

The HAL is Android’s interface to hardware. On most Linux systems, the interface to the hardware is a device driver. Device drivers, however, are usually device specific and sometimes proprietary.

Consider: The Android system will be installed on diverse hardware with, for instance, a wide variety of Wi-Fi chips. Although each Wi-Fi chip has its own kernel driver, at some point, they all provide a Wi-Fi service used by Android code. It would be very convenient if above some point in the stack, the Android code for all the different Wi-Fi hardware/driver combinations were the same.

The HAL is the lingua franca for a class of devices. A single set of C header files describes the functionality that a HAL provides to the Android system. HAL code for a particular device is the implementation of the API defined by those header files. The HAL code shims the hardware device driver behavior so that it looks to Android like a generic device of some particular type: Wi-Fi, Bluetooth, and so on. Adapting Android to use, for instance, an entirely new Wi-Fi device consists of writing the native code that implements the Android Wi-Fi HAL (as defined by the .h files) for the new device. The HAL layer means that no code above the HAL—most of it written in Java—needs to be changed to port Android to use the new device.

Another reason for the HAL is that one of the original goals for Android (a goal that it has clearly achieved) was to make putting it on a new device as frictionless as possible from both engineering and legal points of view. From the legal point of view, that meant navigating a thin line between the GPL (GNU Public License) and proprietary code.

The Android system is based on the Linux kernel, which is licensed under the notoriously viral GPL. The Android platform, on the other hand, is intended to support hardware manufacturers with devices that are strictly proprietary, right down to their APIs. The HAL provides an elegant way of keeping proprietary code away from the GPL.

Hardware developers who are concerned about getting their proprietary code anywhere near an open source, or, worse yet, GPLed codebase, can keep all of their proprietary code in the HAL. To do this, they first create a trivial device driver. It can be open source or even GPLed if there is any reason to do so. The driver does essentially nothing: It simply passes data to and from the hardware. The developer next creates a HAL. The HAL code runs in user space (as a library linked into the application) and may be completely proprietary. It does not run as part of the kernel and is not a device driver under the normal definition of that term. It is, however, the actual driver for the device. Because it is linked into the application as a library, there is no requirement of any kind that this code be public. It need never be available except in a binary form.

A third application for the HAL has arisen more recently. An additional problem that Google encountered as Android came of age was that it could not get vendors to update devices with the latest versions of Android.

In early Android, hardware vendors built the system images that they deployed to their devices. They got the base system source from Google, modified it as necessary, adding their proprietary software and extensions, built an image, and deployed it to their devices. When a new version of Android came along, they did it again—sometimes. From a vendor’s point of view, a new version of Android is something to be sold: a reason for end users to buy a new device. It is not at all surprising that they tended to drag their feet releasing updates.

Google, on the other hand, is constantly increasing the number of pies into which it has a finger. It does not want to have to wait for a recalcitrant vendor or an entirely new generation of hardware to reap the benefits of some new feature. Google needed a way to completely remove the device update mechanism from the clutches of the vendors.

To do this, Google created Project Treble. Treble specifies and requires that HAL modules use Binder, Android’s interprocess communication (IPC) system, to communicate with the Android stack. HAL modules are no longer binary libraries that are linked into the Android runtime. Instead, they are separate binaries that are stored on completely separate hardware volumes from the ones that hold the Android core and user applications. The new HALs are not called directly from Android but are, instead, invoked using highly optimized IPC calls.

Since Project Treble went live, an over-the-air (OTA) update can completely rewrite the file system containing the Android OS without disturbing the vendor’s HAL. There is no longer any need for Google to wait for a vendor to build a new, hardware-specific version of its Android system. Google can update the Android OS on any device at pretty much any time, according to its own schedule. As long as they don’t violate the HAL IPC contract, the HAL that worked with the old version of the OS will work with the new version.

Is a HAL Necessary?

A HAL is necessary only for products that need the Android label or want automatic updates. Developers creating a custom Android for an OEM device may not need a complicated HAL. As described in the preceding section, the HAL has exactly three purposes:

  • It is the native code layer that shims Android onto specific hardware. At the very least, this means bridging code written in Java to native code, usually written in C or C++.

  • It is an abstraction for the functionality provided by a class of devices, for example, Bluetooth, Wi-Fi, graphics, and so on. It makes it possible for Android to abstract away the differences between, for example, Broadcom and Qualcomm Wi-Fi chipsets.

  • It is an abstraction that permits OTA updates that do not require linking a new version of Android to vendor-specific binaries.

Although the first of these purposes is obviously necessary, the second two may not be. A developer building a custom Android targeting a specific hardware device might just dive right in and modify core Android code, as necessary, to adapt it to the device. One might even make the argument that the Java code is easier to modify than the native code in the HAL. So why not?

Actually, there are some fairly strong arguments for keeping platform customizations in the HAL. One good reason, of course, is updates. Even if your business plan does not depend on updates, not having some kind of firmware update plan for an embedded device would be irresponsible.

No matter what version of Android you choose, either security issues are already known or security issues will be known. One need only look back to the massive DDOS attack on Dyn, Inc. in 2016. The attack was launched predominantly from IoT devices, many of which were manufactured by a single company, XiongMai Technologies. It is a cautionary tale that should convince anyone that releasing a programmable device with software that cannot be updated is careless and dangerous. No one wants to be the trampoline for the next cyberattack and, even worse, be unable to fix a security breach when it occurs.

One of the easiest ways to patch bugs, of course, is to take updates from the official AOSP codebase. If a customized fork of AOSP code has been modified to adapt it to a specific device, the fork of AOSP will be much more difficult to merge with the official sources.

Allowing your device to be updated automatically by Google might even be worth considering. Doing so might take the entire issue of security and liability off your plate. To do that, though, your custom Android must at a minimum comply with the Project Treble standards.

Another reason for using the HAL is that an implementation for a new device may already exist. If the hardware that your project uses is something that other Android developers have used—say, a motion or a temperature sensor—it is entirely possible that a complete HAL may already exist. If it does not, it is possible that a HAL exists that supports a similar device and that you can adapt that HAL to the new device with minimal effort.

Designing the HAL

The initial discussion of the Acme project introduced a simple hardware proximity sensor. Preceding chapters sketched the process of building a device driver for it. The next step up the Android layer cake is creating code that uses the driver to interact with the sensor.

By design, the Acme proximity sensor HAL is not only simple but includes a stub for the device driver. The focus here is on defining the HAL API and publishing it into Java; not the vagaries of the Linux USB system. Even though part of the actual code is a stub, the functionality that it represents is intended to be entirely realistic.

Recall that a HAL is an abstraction for a group of devices. A device driver is also an abstraction for the device. On the hardware side, device drivers are typically very specific: a particular driver probably works only with very specific hardware. (A USB driver is a bit of an exception; an intermediate layer for devices that support the USB interface.) From the application side, a device driver is very general. They abstract devices into one of only two groups: character and block.

HALs are more general on the bottom and more specific on the top. On top, there are quite a few HAL abstractions, each of which is the interface for a narrower category of devices: cameras, Wi-Fi, Bluetooth, and so on. On the bottom a HAL provides access to multiple hardware implementations, all of which have a specific, common purpose. The possibility even exists that a single HAL abstraction could integrate multiple hardware devices (and their drivers), exposing the combination of several hardware components as a single service.

When designing a HAL, understanding how the services provided by the hardware will be exposed to an application is absolutely essential. Although this book has generally approached the Android system as a series of layers, each built on the previous layer, designing a HAL requires looking far ahead and trying to predict the future. Even when you build a simple, proprietary device that doesn’t need a real HAL, this boundary is a good place to pause for a moment of reflection. Because the HAL is Android’s abstraction layer, it is a great place to hedge your bets.

First, consider the constraints from below (nearer the hardware). In a project in which the creation of a hardware device (or its driver) is part of a project, the kernel device driver and the API (its .h file) that it publishes are also part of the project. Project developers can modify the driver API as necessary to sensibly divide functionality across code modules.

That is not the typical case, however. The likelihood is higher that a hardware device manufacturer that provides a device will also provide its driver. When a third party—the device manufacturer—provides the device driver, its API is not under project control. That is a constraint.

To be concrete, if the Acme team created an entirely new type of proximity sensor, wrote a device driver for it, and that device was the secret sauce for a new product, then the Acme team would choose the API for the sensor device driver and adjust it to suit their needs. If, on the other hand (the more typical case), the Acme team’s plan was a clever new use of the ability to sense proximity, and they sourced a sensor during a trip to Shenzhen, then the device driver for the sensor would most likely be provided by the third-party hardware engineers: possibly as source, possibly not.

In either case, the API of the device driver is the constraint for the bottom of the HAL. In the former case, that API is more malleable than it is in the latter.

Next, consider the requirements from above (nearer application code). The HAL is the first step toward building a service that will expose the hardware to an application. Now would be a great time for an app developer (preferably not the same person as the one who will write the HAL!) to write one of those applications. What feels like a natural interface in the application? Are the edge cases—initialization, configuration, application failure, and access control—all handled? When a new sensor with new capabilities comes along, will the API accommodate the changes with backward compatibility?

After application requirements are more or less clear, converting the application API into a set of operations required of the hardware should be a relatively straightforward task. Remember, of course, that most of the heavy lifting will be done by a Java service that actually exposes the hardware to the application. The HAL layer doesn’t have to provide the service API that the application uses. It merely has to provide a clean, minimal set of hardware operations on which the service can be built.

The HAL for the Acme proximity sensor is sleek to avoid obscuring the process of building a HAL. Its design is simple. As is often the case, power is an important concern. A sensor may use power when it is running and pointlessly load the battery unless it can be turned off. The design of the HAL for the Acme proximity sensor assumes that it is exactly such a power hog: there are calls to turn it on and off.

The call that turns the sensor on takes one argument—an out parameter struct—that the driver will populate with the min and max values acceptable in the precision argument passed to the HAL when polling it. Similarly, it contains the max and min values that the device will return for the proximity data it will return when it is polled.

After the sensor is turned on, it can be polled for proximity values. The polling call takes a single argument: the desired precision for the proximity value it will return. The precision argument restricts the resources (perhaps time or battery) that the sensor will expend in obtaining the returned value. The poll returns a current value from the sensor.

When all client applications are done using the sensor, it can be turned off to save battery.

Building a HAL

This chapter and the next show how to create the code that connects native code into the Android framework in four steps:

  1. Define the HAL: create the .h files that specify the API for the proximity service

  2. Implement the HAL for the Acme proximity service: create layers of code that shim the device driver API for the proximity service to its HAL API

  3. Create a native (C language) daemon that uses the HAL to access the proximity device

  4. Create the JNI interface that publishes the new HAL into the Java language

Note

In pre-SE Linux versions of Android, it was possible to create and run a standalone Java daemon, a translation of the C-language daemon created in step 3. Although it may still be possible, we abandoned the effort to create one after many hours of trying. Caveat developer.

Code Structure

The code in these next two chapters will implement both the HAL for the proximity device and a simple daemon that uses the device through the HAL code. This simple daemon could replace the one introduced as a startup service in Chapter 6.

Figure 8.1 illustrates the components and their relationships.

Images

Figure 8.1 HAL Layer Structure

Note that all the code discussed in these chapters runs as part of an application (user space) and not as part of the kernel (kernel space). This is all just normal application code.

The code consists of four functional components, as shown in Figure 8.1:

  1. HAL code (dotted boxes): This is the abstraction that separates the capabilities of hardware from its specific implementations. The .h file defines the HAL interface. The implementation (.cpp file) specializes the Android HAL API for the target hardware.

  2. Shim code (dashed boxes): This is the glue code that connects the HAL to a specific device hardware/driver. This code adapts the Android HAL API to the device driver for the hardware.

  3. Daemon (blue/solid gray): This is the stand-alone application that interacts with the hardware through the HAL.

  4. Java System Service (white): This is the System Service that Android applications will use to access the custom hardware.

To build these components into the Acme One device, you must place their code in the Acme One device folder, as introduced in previous chapters. The new directories are organized as shown in Figure 8.2.

Images

Figure 8.2 HAL Directory Structure

All the code implementing the HAL for the proximity sensor goes into a new subdirectory, proximity. If the Acme device had several HALs for several different devices, they might be further organized into an intermediate directory, perhaps hal, that contained separate subdirectories for each of the different device HALs. The Acme project contains only a single HAL so it is located directly in the project root directory.

Note that to be useful as a real HAL, the abstract definition of the interface—specifically the definition of the proximity HAL, proximity/include/proximity_hal.h—would have to be promoted from its current location inside the directory specifically for the One device, up into the Android source tree to a location that would make it visible to other code that needed to use it. If shared only by generations of Acme devices, it might be put into a subdirectory of the Acme device directory. If visible across devices from multiple vendors, it might even be promoted into the device directory itself.

Figure 8.2 also shows the locations in the source tree of the two daemons to be implemented in the next chapter. Although this organization is appropriate during the build process, it is entirely likely that applications—native daemons and system services—will be developed independently from the libraries on which they depend (the HAL); perhaps even by different developers. Facilitate this by separating the code bases into distinct git repositories and using the manifest to place them in the build tree in their required locations, as shown in Listing 8.1.

Listing 8.1 Manifest Additions for the Acme HAL

<!-- Acme Specific Projects -->
  <project path="device/acme/one-kernel" name="one-kernel" remote="acme" />
  <project path="device/acme/one/proximity"
           name="platform_device_acme_one_proximity"
           remote="acme" />
  <project path="device/acme/one/app/native_daemon"
           name="platform_device_acme_one_app_native_daemon"
           remote="acme" />
  <project path="device/acme/one/app/java_daemon"
           name="platform_device_acme_one_app_java_daemon"
           remote="acme" />

Implementing the HAL

The HAL is a line between two endpoints. The first of those two endpoints is the device driver. As noted previously, it is quite likely that the device driver interface for a specific piece of hardware is a given: that its API is not under project control. The device driver and its API are defined by a third-party hardware provider and whatever driver they supply with it.

Given the ubiquity of the universal serial bus (USB), it is also very likely that any new device will communicate with the processor via USB. Even a newly created device may not have its own driver. It may simply appear in the USB device tree and be accessed with generic USB commands. If it does have a distinct driver, that driver is likely to be a specialization of the USB interface.

Note

Contrary to the implications evoked by its name, USB is not really a bus. Instead, it is a tree containing a master device that polls one or more slaves. Masters and slaves behave quite differently and require very different implementations.

Linux USB core supports both modes, referring to the drivers for the master end as USB device drivers and those for the slave end as USB gadget drivers.

Although not discussed here, it is entirely possible that a small Android device will not be the bus master but, instead, a slave that is occasionally connected to a master. One can imagine, for instance, a tablet acting as master for the sensors plugged into it for data collection in the field. At night, though, when plugged into some kind of data aggregator, it would act as a gadget.

In its full generality, a USB device can be a very complex thing. Communicating with a single physical device, for instance, may require interacting with multiple virtual devices. A USB loudspeaker, for example, presents as both a keyboard—its controls—and a separate interface for the bulk transfer of sound data.

Entire books are available on the construction of USB drivers and the code that interacts with them. Those topics are well outside the scope of this book. Instead of accessing an actual USB interface (boxes with slanted lines in Figure 8.1) the shim code in this example (boxes with dashed lines in Figure 8.1) will be a stub. The “bottom” end of this example HAL does not actually connect to a device driver. In an actual HAL, the shim code would include the .h files for one or more device drivers. It would make read, write, and ioctl calls on the drivers they defined. Even though this example uses a stub, recall that Chapter 6 described the SE additions necessary to allow executables (applications) access to USB serial devices labeled as proximityd_exec. The SE additions in Chapter 6 illustrated creating the device-specific SE policy that is necessary to grant applications access to kernel-exposed interfaces.

Several resources can be quite helpful in building the HAL for a USB device. Most obviously, the Linux kernel contains an entire subsystem, USB Core, that does most of the heavy lifting.

Another important resource is libusb (https://libusb.info). Libusb is a portable, user-mode, and USB-version agnostic library for using USB devices. It supports Android. Even if the library is excessive and overly general for use in some specific applications, the code provides several excellent examples of how to use USB devices from application code.

HAL Declaration

The second of the two HAL endpoints is the definition of the HAL API. Visiting it next is another deviation from the strict bottom-to-top order in which we’ve been visiting components of the stack so far. It is, however, entirely realistic and appropriate. After the two endpoints are defined, writing the HAL is the straightforward (if not simple) task of drawing the line between them.

As noted earlier, the HAL definition should be more than just the simple reiteration of a hardware interface. A good HAL API will be flexible enough to support multiple related devices and friendly to the applications that use it. It is entirely prudent to define the interface as part of system and even application design, and then to do whatever is necessary to implement it.

As described previously, the HAL .h file is, in most senses, the HAL. It is the interface through which client code will interact with the device it represents.

The HAL for the proximity device is proximity/include/dev/proximity_hal.h. It is shown in Listing 8.2.

Listing 8.2 Proximity HAL

#ifndef PROXIMITY_HAL_H
#define PROXIMITY_HAL_H

#include <hardware/hardware.h>

#define ACME_PROXIMITY_SENSOR_MODULE "libproximityhal"

typedef struct proximity_sensor_device proximity_sensor_device_t;

struct value_range {
    int min;
    int range;
};

typedef struct proximity_params {
    struct value_range precision;
    struct value_range proximity;
} proximity_params_t;

struct proximity_sensor_device {
    hw_device_t common;

    int fd;

    proximity_params_t params;

    int (*poll_sensor)(proximity_sensor_device_t *dev, int precision);
};

#endif // PROXIMITY_HAL_H

Listing 8.2 first defines the constant ACME_PROXIMITY_SENSOR_MODULE. This is a unique string and the name that client code will use to find the proximity sensor’s HAL.

Next, the code declares a struct that is best understood in the terms of object-oriented (O-O) design. Think of the proximity_sensor_device struct as the definition of a new O-O class. The first member of the struct, common, in O-O terms is its super class: it contains data and behaviors that are common to all HALs for all types of devices. This use of the term “super class” and the function of the common struct member will become clearer in the examination of the HAL implementation.

Note

The definitions of the device in hardware.h, hw_module_methods_t, really is a kind of raw version of inheritance. The HAL structs that represent device instances are hw_module_methods_ts. That means that the open method, for instance, is at the same offset relative to the pointer to the HAL struct for every HAL for any device: hw_module_methods_t.open. Individual HALs, however, “subclass” the hw_module_methods_t struct by defining a new struct whose first element is an hw_module_methods_t but which allocate extra space at the end of that struct, containing pointers to methods with device-specific functionality. The resulting struct can be cast as the “super class” struct, an hw_module_methods_t, because the pointer to the subclass struct is also a pointer to the super class struct. It can also be cast as the device-specific struct by code that needs the device-specific functionality.

Next in Listing 8.2 are more of the struct’s members. To continue the object-orientation analogy, these are the class’s data members (fields). The first is the file descriptor for the open device driver that the HAL will hold. Next are the four values that all proximity sensor devices will provide: min and max for precision and proximity. Each individual hardware device and its driver will populate these fields with information about the behavior of that particular device.

The last member of the struct is a pointer to a function, poll, which takes the struct itself as its first argument. This is the O-O idiom for a method call.

Note

Object-oriented languages bundle data (fields) with the operations that may be performed on that data (methods). The standard way of implementing this is that an operation on a particular data type takes, as an implicit first argument, a reference to an instance of the data on which it will operate. Each call to the operation mutates only the specific instance passed in the call.

Note that nothing in the declaration of the HAL refers in any way to the actual hardware or driver to which the HAL provides access. In particular, the HAL definition file does not refer to the driver or even the shim code’s .h file (see Listing 8.9). The isolation is complete.

This HAL, now completely described, defines what it is to be a proximity device. This is the bottleneck through which all information must pass, moving up or down the stack between any proximity device driver and Android Java code. With the definition of the top and bottom endpoints, the implementation should be simply a matter of programming.

HAL Definition

At last, we arrive at the definition of the HAL: its implementation. The implementation of the proximity HAL for the Acme proximity device is in proximity/hal/proximity_hal.cpp.

The most important resource for implementing a HAL is the AOSP source file hardware/libhardware/include/hardware/hardware.h. It defines the types needed to implement a HAL and, in its comments, describes how a HAL is implemented. It largely determines the structure of the code in proximity_hal.cpp.

The documentation in hardware.h outlines a three-step process for creating a HAL. The first step in the process consists of defining a HAL module. The HAL module contains metadata about a device and is also the factory for instances of the HAL. Listing 8.3 shows the module definition for the Acme One Proximity Sensor.

Listing 8.3 Proximity HAL Module Definition

// ...
hw_module_t HAL_MODULE_INFO_SYM = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = HARDWARE_HAL_API_VERSION,
        .hal_api_version = 0,
        .id = ACME_PROXIMITY_SENSOR_MODULE,
        .name = "Acme Proximity Sensor",
        .author = "Acme Team",
        .methods = &proximity_sensor_methods
};

Several of these fields, tag, module_api_version and hal_api_version, are required and must be bound to the values specified for them in hardware.h. After the required fields are several fields that identify a specific HAL. The value of the field .id, for instance, is the constant defined back in this HAL’s .h file.

The most interesting of the fields is .methods. The .methods field must hold a reference to an hw_module_methods_t. Listing 8.4 quotes—again from hardware.h—the declaration of hw_module_methods_t.

Listing 8.4 HAL Methods Definition

typedef struct hw_module_methods_t {
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module,
                const char* id,
                struct hw_device_t** device);
} hw_module_methods_t;

In other words, the hw_module_methods_t is a struct that contains a pointer to a function that all HAL implementations have but that each will implement differently. In object-oriented terms, it is an abstract method in the HAL class, the super class for all HALs (including the proximity sensor HAL).

What method will all HAL implementations have but that each HAL will implement differently? Well, in O-O terms, it is the class’s constructor, of course! In the realm of the HAL, the constructor is named .open.

The .open method takes a reference to the HAL module struct itself as its first parameter. As noted earlier, this is the O-O design idiom for a method call.

As its second argument, it takes the device ID. This allows the code for a single open method to specialize its behavior for several similar devices: a single implementation might have slightly different behaviors for each of several devices with different IDs. This is the mechanism that allows a single HAL to support multiple implementations.

Finally, down to business: It is the job of each HAL’s implementation of the abstract .open method to allocate and initialize the hw_device_t object returned in the third parameter of the call.

Listing 8.5 shows the hw_module_method_t implementation for the proximity sensor.

Listing 8.5 Methods Definition for the Proximity HAL

// ...

static hw_module_methods_t proximity_sensor_methods = {
        .open = &open_proximity_sensor_module
};

// ...

The hw_module_t struct for the proximity sensor is now complete. Its .methods.open field contains a reference to a method that opens the underlying device for use: open_proximity_sensor_module. That method will be defined in a moment.

Although there is now a way (incompletely implemented) to create an instance of the proximity sensor HAL, as yet, there is no way use it. There is no way to poll it or to close it when it is no longer needed. This is the purpose of the struct created, initialized, and returned by the .open method, the hw_device_t. In O-O terms, it is roughly the equivalent of an instance of the HAL. The Acme proximity sensor device needs an extension of hw_device_t that declares the two needed methods, poll and close.

Note

Be careful not to confuse hw_module_t and hw_device_t. The hw_module_t is the HAL’s description and factory. It has an open method that returns instances of extensions of hw_device_t that define the behavior of a class of devices.

Finally, Listing 8.6 is the complete definition of the HAL (located at device/acme/one/proximity/hal/proximity_hal.cpp). As one would expect in an O-O architecture, the subclass instance contains references to subclass methods.

Listing 8.6 Proximity HAL Implementation

#include <errno.h>
#include <string.h>
#include <malloc.h>
#include <log/log_system.h>

#include "proximity_hal.h"
#include "dev/proximity_sensor.h"

#define LOG_TAG "PROX"

static int poll_proximity_sensor(proximity_sensor_device_t *dev, int precision) {
    SLOGV("Polling proximity sensor");

    if (!dev)
        return -1;

    return poll_sensor(dev->fd, precision);
}

static int close_proximity_sensor(proximity_sensor_device_t *dev) {
    SLOGV("Closing proximity sensor");

    if (!dev)
        return 0;

    close_sensor(dev->fd);
    free(dev);
    return 0;
}

static int open_proximity_sensor_module(
        const struct hw_module_t *module,
        char const *name,
        struct hw_device_t **device) {
    SLOGV("Opening proximity sensor");

    auto *dev = static_cast<proximity_sensor_device_t*>
      (malloc(sizeof(proximity_sensor_device_t)));
    if (!dev)
        return -ENOMEM;

    memset(dev, 0, sizeof(*dev));

    int fd = open_sensor(dev->params);
    if (fd < 0) {
        SLOGE("Failed to open proximity sensor: %s", strerror(errno));
        free(dev);
        return -1;
    }
    dev->fd = fd;

    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = (struct hw_module_t *) module;
    dev->common.close = (int (*)(struct hw_device_t *)) close_proximity_sensor;

    dev->poll_sensor = poll_proximity_sensor;

    *device = reinterpret_cast<hw_device_t *>(dev);

    return 0;
}

static hw_module_methods_t proximity_sensor_methods = {
        .open = open_proximity_sensor_module
};

hw_module_t HAL_MODULE_INFO_SYM = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = HARDWARE_HAL_API_VERSION,
        .hal_api_version = 0,
        .id = ACME_PROXIMITY_SENSOR_MODULE,
        .name = "Acme Proximity Sensor",
        .author = "Acme Team",
        .methods = &proximity_sensor_methods
};

Note that, so far, this HAL implementation is completely device agnostic. This same code could be used for nearly any proximity sensor device, depending on the definitions of three methods: open_sensor, close_sensor, and poll_sensor.

There are a few ways of specializing this generic implementation for a specific device. A HAL implementation might be specialized at compile/bind time by statically assigning the reference to a method implementation appropriate for the specific device to each of those three symbols. In fact, that is how this example will work: That is what we do here. There will be a single shim that will define those three functions.

The choice could also be runtime, though. Multiple definitions of hw_module_t might all use a HAL implementation very similar to this one. An extension of the implementation might use the value of hw_module_t.id to choose among several device-specific function implementations to be assigned to the proximity_sensor_device_t’s .common.close and poll_sensor methods.

The HAL must be added to the build system. The build file is shown in Listing 8.7 and is located at device/acme/one/proximity/Android.bp. The HAL is a shared library named “libacmehal” and is built from the source “proximity_hal.cpp.” That source file, of course, includes the file “proximity_hal.h,” which defines the HAL and which was shown in Listing 8.2.

Listing 8.7 Acme HAL Build Blueprint File

cc_defaults {
    name: "vendor.acme.one.proximity.defaults",
    relative_install_path: "hw",
    cflags: [
        "-g",
        "-O0",
        "-Wall",
    ],
    vendor: true,
}

cc_library_shared {
    name: "proximityhal.default",
    defaults: [
        "vendor.acme.one.proximity.defaults",
    ],
    srcs: [
        "hal/proximity_hal.cpp"
    ],
    header_libs: [
        "liblog_headers",
        "libhardware_headers",
    ],
    local_include_dirs: [
        "include"
    ],
    shared_libs: [
        "liblog",
        "libhardware",
    ],
    static_libs: [
        "libacmeproximityshim",
    ]
}
Understanding the Shim

The final step in connecting the proximity driver to its HAL is the shim that actually implements the three device methods, open_sensor, close_sensor, and poll_sensor, for the Acme Proximity Sensor device. The definition of the shim interface is in the file proximity/include/dev/proximity_sensor.h. This is the “bottom” of the HAL. It is the API for the proxy to which the HAL code will delegate calls for one, specific type of proximity sensor device to which the proximity HAL provides access.

Because this example is quite simple and the HAL is responsible for only one actual hardware device, these multiple layers of abstraction may seem excessive. Indeed, in this specific context, they may be. Consider, though, that in this specific pedagogical exercise, the whole notion of a HAL may be excessive.

The shim API is not part of the HAL. It is not included by proximity_hal.h, the definition of a proximity sensor’s HAL. The implementations of the device HAL (there may be more than one) is probably the only code in the entire system that uses it. It completely encapsulates the details of a specific device and should certainly never be needed by code that uses the HAL. Listing 8.8 shows the shim API.

Listing 8.8 Proximity Sensor Shim API

#ifndef ACME_PROXIMITY_SENSOR_H
#define ACME_PROXIMITY_SENSOR_H

#include "proximity_hal.h"

int open_sensor(proximity_params_t &params);

int poll_sensor(int fd, int precision);

int close_sensor(int fd);

#endif //ACME_PROXIMITY_SENSOR_H

No surprises here. These are exactly the services described earlier and used by the HAL: The sensor can be turned on and off to optimize battery use; while it is on, it can be polled. A poll takes as arguments the device file descriptor, a requested precision (a number between precision.min and precision.max), and returns a proximity value that is between proximity.min and proximity.max. These bounds are populated in the params struct passed to the shim when turning the sensor on.

It is worth restating that although the device driver for a given device may run at least partially as part of the kernel, none of the HAL code does. All the code in this chapter runs in user space. Although its use may be restricted to privileged applications, HAL code runs exclusively as part of some application.

Implementing the Shim

Finally, Listing 8.9 shows the actual implementation of the shim that connects the HAL to the device driver. It is in the file proximity/dev/proximity_sensor.cpp. In this example, it is just a stub. It just mocks the code that would actually talk to a device driver. Instead of the hardwired values returned here, a real proxy would interface with the hardware device, probably through its USB driver, to perform the actions required by the HAL.

Listing 8.9 Proximity Sensor Glue Code Stub

#include "dev/proximity_sensor.h"

// This is stub, mocking actual glue code.
// If this were a real thing, it would talk to a device driver,
// presumably for a USB device

int open_sensor(proximity_params_t &params) {
    params.precision_min = 0;
    params.precision_range = 100;
    params.proximity_min = 0;
    params.proximity_range = 100;

    return 0; // a completely fake fd
}

int poll_sensor(int fd, int precision) {
    if (precision < 0) {
        return -1;
    } else if (precision < 70) {
        return 60;
    } else if (precision < 100) {
        return 63;
    } else {
        return -1;
    }
}
int close_sensor(int fd) {
    return 0;
}

Now that the shim is defined, it must also be added to the build. Because it is used by the HAL as well as the simple daemon and the binderized HAL covered in Chapter 12, it is built as a library. The extension of the Android.bp shown in Listing 8.10 shows the additions necessary to build the library as well as expose its headers to other components of the system.

Listing 8.10 Proximity Sensor Shim Additions to Android.bp

cc_library {
    name: "libacmeproximityshim",
    defaults: [
        "vendor.acme.one.proximity.defaults",
    ],
    srcs: [
        "dev/proximity_sensor.cpp",
    ],
    header_libs: [
        "libhardware_headers",
    ],
    local_include_dirs: [
        "include",
    ],
}

cc_library_headers {
    name: "libacmeproximityshim_headers",
    defaults: [
        "vendor.acme.one.proximity.defaults",
    ],
    header_libs: [
        "libhardware_headers",
    ],
    export_header_lib_headers: [
        "libhardware_headers",
    ],
    export_include_dirs: ["include"],
}

Summary

This chapter introduced the Android HAL. A HAL is the interface between Android and a class of similar hardware devices: cameras, audio, sensors, and so on. The primary purpose of a HAL is to provide a single 152API for all devices that provide a similar service. A HAL abstracts device specifics so that Android code that uses the device need not change to accommodate a specific device.

Because they are both abstractions and APIs, designing HALs requires careful thought and a pretty good crystal ball. Especially when a team does not have a lot of experience with either Android or a new device, designing a HAL may bog down development and not prove future-proof anyway. Good reasons exist for creating a HAL. Good reasons also exist for planning to throw one away.

It is even possible to make an argument for C glue code that is extremely simple and hoisting any complexity up into Android Java. If the project scope already includes modification of the Android code, a Java device shim may be the most effective plan. On the other hand, for devices that will get OS updates—especially if those updates will come from an external source or that will need to adapt to multiple hardware implementations—a HAL is just the thing.

The implementation of the Acme Proximity sensor HAL in this chapter is a representative basic HAL: It is realistic, complete, and it compiled and ran at the time of this writing. It is, however, a stub. It does not actually use a USB driver to communicate with a physical device as it almost certainly would were it more than pedagogical.

The next chapter demonstrates using this HAL in a daemon written in C. Chapter 11 demonstrates converting this legacy HAL to a Binderized (Treble) HAL.

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

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