Wednesday, 7 June 2023

Practical example of AIDLs for HIDLs

Hi Guys!!!
Hope you are doing well !!!. Today I am going to discuss a Practical example of AIDLs for HIDLs.
Previous post Explain AIDLs for HIDLs

Here is the plan :

Third party App take String input from User and pass it to Android Kernel module to get the reverse  of String. 

Kernel Module 

We need to create a kernel module named as "invstr" and add our logic to reverse the string that coming from User App. I am assuming that you guys know how to add a new module in kernel and build it.

If you guys are not familiar to add new module in kernel and build. I will add a separate post on it.

This guide assumes that invstr module is loaded as below:

device/generic/goldfish/init.ranchu.rc
+ on boot
+ insmod /system/lib/modules/invstr.ko
+ chown system system /dev/invstr
+ chmod 0600 /dev/invstr

Change in Android Build

AOSP
  • build

    • make
      • target
        • product
          • base_vendor.mk

            Include new packages
            + PRODUCT_PACKAGES += \
            +     android.hardware.invstr \
            +     android.hardware.invstr-service \
            +     Invstr
AIDL for HIDL Changes 
Below  I am putting folder structure details
  • hardware

    • interfaces

      • invstr

        • aidl

          • Android.bp
          • android

            • hardware
              • invstr
                • IInvstr.aidl

                  @VintfStability
                  interface IInvstr {
                      String getChars();
                      void putChars(in String msg);
                  }
                  
          • default

            • Android.bp
            • Invstr.h
            • Invstr.cpp
            • service.cpp
      • compatible_matrices

        • compatible_matrix.current.xml

          + <hal format="aidl" optional="true">
          +     <name>android.hardware.invstr</name>
          +     <version>1</version>
          +     <interface>
          +         <name>IInvstr</name>
          +         <instance>default</instance>
          +     </interface>
          + </hal>

Define HAL Interface#

Create a new AIDL file in the folder hardware/interfaces/invstr/aidl:

hardware/interfaces/invstr/aidl/android/hardware/invstr/IInvstr.aidl
package android.hardware.invstr;

@VintfStability
interface IInvstr
    String getChars();
    void putChars(in String msg);
}
Configure Android.bp for building
hardware/interfaces/invstr/aidl/Android.bp
  • Select the backend: We will use NDK (as recommended), so declare the CPP backend as false
  • Set vendor: true and remove vendor_available because this is a custom vendor HAL
  • Remove vndk section, so this HAL is located in /vendor only
aidl_interface {
    name: "android.hardware.invstr",n                            
    vendor: true,                                                
    srcs: ["android/hardware/invstr/*.aidl"],                    
    stability: "vintf",                                          
    owner: "vqtrong",                                            
    backend: {                                                   
        cpp: {                                                   
            enabled: false,                                      
        },                                                       
        java: {                                                  
            sdk_version: "module_current",                       
        },                                                       
    },                                                           
}                                                                

At this time, if try to build the module with:
mmm hardware/interfaces/invstr/you will get error about the API missing.
We need to freeze the API by running:
m android.hardware.invstr-update-ap

Ok, build it again:

mmm hardware/interfaces/invstr/

Then include the module to the system:

build/make/target/product/base_vendor.mk
PRODUCT_PACKAGES += \
    android.hardware.invstr \

Implement HAL#

We will use the ndk_platfrom library, therefore, let check the generated code for ndk_platform.

cd out/soong/.intermediates/hardware/interfaces/invstr/aidl/android.hardware.invstr-V1-ndk_platform-source
find .
.
./gen
./gen/timestamp
./gen/include
./gen/include/aidl
./gen/include/aidl/android
./gen/include/aidl/android/hardware
./gen/include/aidl/android/hardware/invstr
./gen/include/aidl/android/hardware/invstrBpInvstr.h
./gen/include/aidl/android/hardware/invstr/IInvstr.h
./gen/include/aidl/android/hardware/invstr/BnInvstr.h
./gen/android
./gen/android/hardware
./gen/android/hardware/invstr
./gen/android/hardware/invstr/IInvstr.cpp.d
./gen/android/hardware/invstr/IInvstr.cpp

Our interface APIs are converted to APIs as below:

IInvstr.h
virtual ::ndk::ScopedAStatus getChars(std::string* _aidl_return) = 0;
virtual ::ndk::ScopedAStatus putChars(const std::string& in_msg) = 0;

They are virtual functions and then need to be defined.

Header file

hardware/interfaces/invstr/aidl/default/Invstr.h
#pragma once

#include <aidl/android/hardware/invstr/BnInvstr.h>

namespace aidl {
namespace android {
namespace hardware {
namespace invstr {

class Invstr : public BnInvstr {
    public:
        //String getChars();
        ndk::ScopedAStatus getChars(std::string* _aidl_return);
        //void putChars(in String msg);
        ndk::ScopedAStatus putChars(const std::string& in_msg);
};

}  // namespace invstr
}  // namespace hardware
}  // namespace android
}  // namespace aidl

Implementation

hardware/interfaces/invstr/aidl/default/Invstr.cpp

#define LOG_TAG "Invstr"

#include <utils/Log.h>
#include <iostream>
#include <fstream>
#include "Invstr.h"

namespace aidl {
namespace android {
namespace hardware {
namespace invstr {

//String getChars();
ndk::ScopedAStatus Invstr::getChars(std::string* _aidl_return) {
    std::ifstream invstr_dev;
    invstr_dev.open("/dev/invstr");
    if(invstr_dev.good()) {
        std::string line;
        invstr_dev >> line;
        ALOGD("Invstr service: getChars: %s", line.c_str());
        *_aidl_return =  line;
    } else {
        ALOGE("getChars: can not open /dev/invstr");
        return ndk::ScopedAStatus::fromServiceSpecificError(-1);
    }
    return ndk::ScopedAStatus::ok();
}

//void putChars(in String msg);
ndk::ScopedAStatus Invstr::putChars(const std::string& in_msg) {
    std::ofstream invstr_dev;
    invstr_dev.open ("/dev/invstr");
    if(invstr_dev.good()) {
        invstr_dev << in_msg;
        ALOGD("Invstr service: putChars: %s", in_msg.c_str());
    } else {
        ALOGE("putChars: can not open /dev/invstr");
        return ndk::ScopedAStatus::fromServiceSpecificError(-1);
    }
    return ndk::ScopedAStatus::ok();
}

}  // namespace invstr
}  // namespace hardware
}  // namespace android
}  // namespace aidl

Implement HAL Service
Service implementation
hardware/interfaces/invstr/aidl/default/service.cpp
#define LOG_TAG "Invstr"

#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include "Invstr.h"

using aidl::android::hardware::invstr::Invstr;
using std::string_literals::operator""s;

void logd(std::string msg) {
    std::cout << msg << std::endl;
    ALOGD("%s", msg.c_str());
}

void loge(std::string msg) {
    std::cout << msg << std::endl;
    ALOGE("%s", msg.c_str());
}

int main() {
    // Enable vndbinder to allow vendor-to-venfor binder call
    android::ProcessState::initWithDriver("/dev/vndbinder");

    ABinderProcess_setThreadPoolMaxThreadCount(0);
    ABinderProcess_startThreadPool();

    std::shared_ptr<Invstr> invstr = ndk::SharedRefBase::make<Invstr>();
    const std::string name = Invstr::descriptor + "/default"s;

    if (invstr!= nullptr) {
        if(AServiceManager_addService(invstr->asBinder().get(), name.c_str()) != STATUS_OK) {
            loge("Failed to register IInvstr service");
            return -1;
        }
    } else {
        loge("Failed to get IInvstr instance");
        return -1;
    }

    logd("IInvstr service starts to join service pool");
    ABinderProcess_joinThreadPool();

    return EXIT_FAILURE;  // should not reached
}


Build Service

Similar to the HIDL module, we will create a cc_binary module in theAndroid.bp.

AIDL has three different backends: Java, NDK, CPP. To use Stable AIDL, you must always use the system copy of libbinder at system/lib*/libbinder.so and talk on /dev/binder. For code on the vendor image, this means that libbinder (from the VNDK) cannot be used: this library has an unstable C++ API and unstable internals. Instead, native vendor code must use the NDK backend of AIDL, link against libbinder_ndk (which is backed by system libbinder.so), and link against the -ndk_platform libraries created by aidl_interface entries.

hardware/interfaces/invstr/aidl/default/Android.bp
cc_binary {
    name: "android.hardware.invstr-service",
    vendor: true,
    relative_install_path: "hw",
    init_rc: ["android.hardware.invstr-service.rc"],
    vintf_fragments: ["android.hardware.invstr-service.xml"],

    srcs: [
        "Invstr.cpp",
        "service.cpp",
    ],

    cflags: [
        "-Wall",
        "-Werror",
    ],

    shared_libs: [
        "libbase",
        "liblog",
        "libhardware",
        "libbinder_ndk",
        "libbinder",
        "libutils",
        "android.hardware.invstr-V1-ndk_platform",
    ],
}

Then include the service to the system:

build/make/target/product/base_vendor.mk
PRODUCT_PACKAGES += \
    android.hardware.invstr \
    android.hardware.invstr-service \

Run Service

We need to define the service with the init process, so it can start whenever the hal class is started. To do this, we will create a new android.hardware.invstr-service.rc:

hardware/interfaces/invstr/aidl/default/android.hardware.invstr-service.rc
service android.hardware.invstr-service /vendor/bin/hw/android.hardware.invstr-service
        interface aidl android.hardware.invstr.IInvstr/default
        class hal
        user system
        group system
Expose AIDL Interface
A new VINTF AIDL object should be declared as below:
hardware/interfaces/invstr/aidl/default/android.hardware.invstr-service.xml
<manifest version="1.0" type="device">
    <hal format="aidl">
        <name>android.hardware.invstr</name>
        <version>1</version>
        <fqname>IInvstr/default</fqname>
    </hal>
</manifest>

If this is a new package, add it to the latest framework compatibility matrix. If no interface should be added to the framework compatibility matrix (e.g. types-only package), add it to the exempt list in libvintf_fcm_exclude.

hardware/interfaces/compatibility_matrices/compatibility_matrix.6.xml
hardware/interfaces/compatibility_matrices/compatibility_matrix.current.xml
<hal format="aidl" optional="true">
    <name>android.hardware.invstr</name>
    <version>1</version>
    <interface>
        <name>IInvstr</name>
        <instance>default</instance>
    </interface>
</hal>

Define SELinux Policy for HAL service#

To make the service run at boot, HAL service needs to be registered to system under a security policy.


Declare new type

system/sepolicy/prebuilts/api/32.0/public/hwservice.te
system/sepolicy/public/hwservice.te
type hal_invstr_hwservice, hwservice_manager_type;

Set compatibility

Ignore in API 31, which also ignore in lower API:

system/sepolicy/prebuilts/api/32.0/private/compat/31.0/31.0.ignore.cil
system/sepolicy/private/compat/31.0/31.0.ignore.cil
(type new_objects)
(typeattribute new_objects)
(typeattributeset new_objects
    ( new_objects
        hal_invstr_hwservice
    )
)


Add service path

Add a new label in the:

system/sepolicy/vendor/file_contexts
/(vendor|system/vendor)/bin/hw/android\.hardware\.invstr-service u:object_r:hal_invstr_service_exec:s0

Set service context interface:

system/sepolicy/prebuilts/api/32.0/private/hwservice_contexts
system/sepolicy/private/hwservice_contexts
android.hardware.invstr::IInvstr                             u:object_r:hal_invstr_hwservice:s0

Declare attribute:

system/sepolicy/prebuilts/api/32.0/public/attributes
system/sepolicy/public/attributes
hal_attribute(invstr);

this is macro for adding below attributes:

attribute hal_invstr;
attribute hal_invstr_client;
attribute hal_invstr_server;mon

Define default domain:

system/sepolicy/vendor/hal_invstr_service.te
type hal_invstr_service, domain;
hal_server_domain(hal_invstr_service, hal_invstr)
type hal_invstr_service_exec, exec_type, vendor_file_type, vendor_file_type, file_type;
init_daemon_domain(hal_invstr_service)

Set binder policy:

system/sepolicy/prebuilts/api/32.0/public/hal_invstr.te
system/sepolicy/public/hal_invstr.te
binder_call(hal_invstr_client, hal_invstr_server)
binder_call(hal_invstr_server, hal_invstr_client)
hal_attribute_hwservice(hal_invstr, hal_invstr_hwservice)

Declare system_server as client of HAL service:

system/sepolicy/prebuilts/api/29.0/private/system_server.te
system/sepolicy/private/system_server.te
hal_client_domain(system_server, hal_invstr)

Deliver HAL module#

Include HAL service and the test app to the PRODUCT_PACKAGES:

build/target/product/base_vendor.mk
+ PRODUCT_PACKAGES += \
+     android.hardware.invstr-service \

This will include below files to system:

/vendor/lib/hw/android.hardware.invstr-service

User App

The User App will be very simple to test the hardware. It contains an EditText to get user input, a Button to execute commands, and a TextView to display the result.

I will Add user App details as separate blog . Current post itself become too long. 

Thanks you guys for bearing with me to read this long post. Hope it help you guys as one stop solution to Use AIDL as HIDL.

Thanks
Saurabh
Happy Coding!!!!

Build a Custom Kernel Module for Android

Hi Guys!!!Hope you are doing well !!!. Today I will describe how you can write a custom kernel module(Hello world) for Android and load it a...