Calculator example
Overview
The calculator example illustrates the following features:
- Using the command line, perform the following steps
- Building
- Simulator testing
- On-target testing
- Multi-session testing
- Modifying the example
- Project structure
- Customizing the calculator example
The calculator example consumes an array of integers and returns both the sum and the maximum value of that array. Refer to the Feature Matrix for example support and to know the DSP architecture on the target.
Using command line
Building
The example comes with a walkthrough script called calculator_walkthrough.py
. Please review the generic setup and walkthrough_scripts instructions to learn more about setting up your device and using walkthrough scripts. Walkthrough script automates building, running and signing the device steps mentioned in this section. You can run walkthrough script with dry run (-DR) option to get the list of these commands for any specific target.
Without the walkthrough script, you will need to build both the HLOS and Hexagon modules manually. The HLOS build may be for Android (LA) or for ARM Ubuntu (LE), depending upon the device you want to test on. To build for a different DSP architecture than the default one (V68), simply use the DSP_ARCH
option when using the hexagon
target.
The following commands show how to build the LA, LE and Hexagon modules (assuming the desired Hexagon architecture version is v68):
For Hexagon:
make hexagon BUILD=Debug
For LA target:
make android BUILD=Debug
For LE target:
make ubuntuARM BUILD=Debug
For more information on the build syntax, please refer to the building reference instructions.
Alternatively, you can build the same HLOS (LA/LE) and Hexagon modules with CMake.
For Hexagon:
build_cmake hexagon BUILD=Debug NO_QURT_INC=1
For LA target:
build_cmake android BUILD=Debug
For LE target:
build_cmake ubuntuARM BUILD=Debug
For more information on CMake usage, see the CMake documentation.
Simulator testing
Now that we have built the code, we will discuss how to run the code on the simulator.
The Hexagon Simulator, hexagon-sim
, is found in %DEFAULT_HEXAGON_TOOLS_ROOT% \Tools\bin\
. For reference documentation on the Hexagon simulator, please refer to hexagon simulator document. For general instructions on the process of running code on the simulator and on device, please refer to the run instructions.
The following command will build the Debug Hexagon variant of the calculator example targeting the v68 instruction set:
make hexagon BUILD=Debug DSP_ARCH=v68
This command will result in creating a binary ELF calculator_q
. The simulator command generated by the build system to run calculator_q
can be found in the the last section Command line used to invoke simulator
of the generated file hexagon_Debug_toolv88_v68\pmu_stats.txt
. You can reuse this command directly and/or modify it as desired for running additional simulations.
The main simulator command line options are as follows:
Simulator option | Description |
---|---|
--mv* |
Simulate for a particular architecture version of Hexagon. E.g. -mv68 . |
--simulated_returnval |
Cause the simulator to return a value to its caller indicating the final execution status of the target application. |
--usefs <path> |
Cause the simulator to search for required files in the directory with the specified path. |
--pmu_statsfile <filename> |
Generate a PMU statistics file with the specified name. See the SysMon Profiler for more information about PMU events. |
--help |
Print available simulator options. |
As an example, here is how the calculator_q
ELF file might be run on the simulator:
$DEFAULT_HEXAGON_TOOLS_ROOT/Tools/bin/hexagon-sim -mv68 --simulated_returnval --usefs hexagon_Debug_toolv88_v68 --pmu_statsfile hexagon_Debug_toolv88_v68/pmu_stats.txt hexagon_Debug_toolv88_v68/calculator_q --
This command should generate the following output, which highlights that two tests were executed and completed successfully.
hexagon-sim INFO: The rev_id used in the simulation is 0x00004065 (v68n_1024)
- allocate 1024 bytes from ION heap
- creating sequence of numbers from 0 to 255
- compute sum on domain 0
- call calculator_sum on the DSP
- sum = 32640
- call calculator_max on the DSP
=============== DSP: maximum result 255 ==============: HIGH:0x0:55:calculator_imp.c
- max value = 255
- allocate 1024 bytes from ION heap
- creating sequence of numbers from 0 to 255
- compute sum locally
=============== DSP: local sum result 32640 ===============
- find max locally
=============== DSP: local max result 255 ===============
############################################################
Summary Report
############################################################
Pass: 2
Undetermined: 0
Fail: 0
Did not run: 0
On-target testing
Now that we have run the code on the simulator, let's discuss the process of running the code on target.
If you want to run your code on target without using the walkthrough script, please use the following steps:
-
Use ADB as root and remount system read/write
For LA target:
adb root adb wait-for-device adb remount
For LE target:
adb root adb wait-for-device adb shell mount -o remount,rw,exec /
-
Push the HLOS side calculator test executable and supporting calculator stub library onto the device's file system
For LA target:
adb shell mkdir -p /vendor/bin/ adb push android_Debug_aarch64/ship/calculator /vendor/bin/ adb shell chmod 777 /vendor/bin/calculator adb push android_Debug_aarch64/ship/libcalculator.so /vendor/lib64/
For LE target:
adb shell mkdir -p /usr/bin/ adb push UbuntuARM_Debug_aarch64/ship/calculator /usr/bin/ adb shell chmod 777 /usr/bin/calculator adb shell mkdir -p /usr/lib/ adb push UbuntuARM_Debug_aarch64/ship/libcalculator.so /usr/lib/
-
Push the Hexagon Shared Object to the device's file system
For LA target:
adb shell mkdir -p /vendor/lib/rfsa/dsp/sdk adb push hexagon_Debug_toolv88_v68/ship/libcalculator_skel.so /vendor/lib/rfsa/dsp/sdk/
For LE target:
adb shell mkdir -p /usr/lib/rfsa/dsp/sdk adb push hexagon_Debug_toolv88_v68/ship/libcalculator_skel.so /usr/lib/rfsa/dsp/sdk
-
Generate and push a device-specific test signature based on the device's serial number.
Follow the steps listed in the Use signer.py section of the signing documentation.
Note: This step only needs to be done once as the same test signature will enable loading any module.
-
Redirect DSP FARF messages to ADB logcat by creating a farf file. For more information on logcat please refer to the logcat section of messaging.
For LA target:
adb shell echo "0x1f > /vendor/lib/rfsa/dsp/sdk/calculator.farf"
For LE target:
adb shell echo "0x1f > /usr/lib/rfsa/dsp/sdk/calculator.farf"
-
Launch a new CLI shell to view the DSP's diagnostic messages via logcat
Open a new shell or command window and type:
adb logcat -s adsprpc
NOTE: This adsprpc filter captures messages from any of the DSPs (e.g aDSP, cDSP, or sDSP).
-
Execute the example
Please note that following environment variables are to be passed along with example binary and its arguments
- EXE_PATH : Location of HLOS-side executable on device.
- LD_LIBRARY_PATH : Location of HLOS-side stub library on device.
- ADSP_LIBRARY_PATH (or DSP_LIBRARY_PATH for SM8250 and later devices): Location of DSP libraries and TestSig on device.
For LA target:
adb shell "export EXE_PATH=/vendor/bin/ LD_LIBRARY_PATH=/vendor/lib64/ DSP_LIBRARY_PATH=/vendor/lib/rfsa/dsp/sdk; /vendor/bin/calculator -r 1 -n 1000"
For LE target:
adb shell "export EXE_PATH=/usr/bin/ LD_LIBRARY_PATH=/usr/lib/ DSP_LIBRARY_PATH=/usr/lib/rfsa/dsp/sdk; /usr/bin/calculator -r 1 -n 1000"
Output of the above run command is as follows:
Allocate 4000 bytes from ION heap Creating sequence of numbers from 0 to 999 Compute sum locally =============== DSP: local sum result 499500 =============== Find max locally =============== DSP: local max result 999 =============== Success
For example:
# run example with 1000 array size on aDSP signed PD on LA target adb shell "export LD_LIBRARY_PATH=/vendor/lib64/ DSP_LIBRARY_PATH=/vendor/lib/rfsa/dsp/sdk; /vendor/bin/calculator -r 0 -d 0 -n 1000 -U 0" Starting calculator test Attempting to run on signed PD on domain 0 Allocate 4000 bytes from ION heap Creating sequence of numbers from 0 to 999 Compute sum on domain 0 Call calculator_sum on the DSP Sum = 499500 Call calculator_max on the DSP Max value = 999 Success
# run example with 1000 array size on cDSP Unsigned PD on LA target adb shell "export LD_LIBRARY_PATH=/vendor/lib64/ DSP_LIBRARY_PATH=/vendor/lib/rfsa/dsp/sdk; /vendor/bin/calculator -r 0 -d 3 -n 1000 -U 1" Starting calculator test Attempting to run on unsigned PD on domain 3 Allocate 4000 bytes from ION heap Creating sequence of numbers from 0 to 999 Compute sum on domain 3 Call calculator_sum on the DSP Sum = 499500 Call calculator_max on the DSP Max value = 999 Success
# run example with 1000 array size on aDSP signed PD on LE target adb shell "export EXE_PATH=/usr/bin/ LD_LIBRARY_PATH=/usr/lib/ DSP_LIBRARY_PATH=/usr/lib/rfsa/dsp/sdk; /usr/bin/calculator -r 0 -d 0 -n 1000 -U 0" Starting calculator test Attempting to run on signed PD on domain 0 Allocate 4000 bytes from ION heap Creating sequence of numbers from 0 to 999 Compute sum on domain 0 Call calculator_sum on the DSP Sum = 499500 Call calculator_max on the DSP Max value = 999 Success
If you want to run the example on any other DSP and PD (Signed/Unsigned), execute the walkthrough with dry run (-DR) option with the appropriate DSP and PD to see which commands you should run manually.# run example with 1000 array size on cDSP Unsigned PD on LE target adb shell "export EXE_PATH=/usr/bin/ LD_LIBRARY_PATH=/usr/lib/ DSP_LIBRARY_PATH=/usr/lib/rfsa/dsp/sdk; /usr/bin/calculator -r 0 -d 3 -n 1000 -U 1" Starting calculator test Attempting to run on unsigned PD on domain 3 Allocate 4000 bytes from ION heap Creating sequence of numbers from 0 to 999 Compute sum on domain 3 Call calculator_sum on the DSP Sum = 499500 Call calculator_max on the DSP Max value = 999 Success
NOTE: With the exception of Unsigned PD command, the other commands will run successfully only on a signed device. The Unsigned PD command executes the example using an unsigned PD, and as such, will work properly even if the TestSig is removed from the DSP search path.
Multi-Session testing
The calculator example demonstrates the use of Multi-Sessions. To run the Multi-sessions tests, please pass the -q 1
flag to the calculator example. The Multi-sessions tests are currently supported only on Lanai.
In the Multi-sessions tests, the calculator example, attempts to reserve a session using the remote_sesion_control
call with option FASTRPC_RESERVE_NEW_SESSION
:
#include "remote.h"
struct remote_rpc_reserve_new_session reserve_session;
...
remote_session_control(FASTRPC_RESERVE_NEW_SESSION, (void*)&reserve_session, sizeof(reserve_session));
Once the session is reserved, we can obtain the Effective Domain and the URI as follows:
struct remote_rpc_effective_domain_id data_effective_dom_id;
struct remote_rpc_get_uri session_uri;
...
data_effective_dom_id.session_id = reserve_session.session_id;
session_uri.session_id = reserve_session.session_id;
session_uri.module_uri_len = sizeof(calculator_URI);
snprintf(session_uri.module_uri, session_uri.module_uri_len, "%s", calculator_URI);
remote_session_control(FASTRPC_GET_EFFECTIVE_DOMAIN_ID, (void*)&data_effective_dom_id, sizeof(data_effective_dom_id));
remote_session_control(FASTRPC_GET_URI, (void *)&session_uri, sizeof(session_uri))
Finally, the handle can be opened on the reserved session using the URI as follows:
calculator_open(session_uri.uri, &handle);
For the Multi-sessions tests, predetermined arrays are used to distinguish between the multiple sessions that are open on the DSP. The Multi-Sessions tests can be run on both Signed PD and Unsigned PD and is supported on all domains.
For LA target:
adb shell "export EXE_PATH=/vendor/bin/ LD_LIBRARY_PATH=/vendor/lib64/ DSP_LIBRARY_PATH=/vendor/lib/rfsa/dsp/sdk; /vendor/bin/calculator -d 3 -U 0 -q 1"
For LE target:
adb shell "export EXE_PATH=/usr/bin/ LD_LIBRARY_PATH=/usr/lib/ DSP_LIBRARY_PATH=/usr/lib/rfsa/dsp/sdk; /usr/bin/calculator -d 3 -U 0 -q 1"
Output of the multi-session is as follows:
Starting calculator test
Attempting to run on signed PD on domain 3
Running Multisession Tests:
Creating sequence of numbers from 0 to 10
Call calculator_sum on the handle_0
Sum for array 0 = 45
Creating sequence of numbers from 0 to 20
Call calculator_sum on the handle_1
Sum for array 1 = 190
Success
Modifying the example
Project structure
Now that you are able to build and run the code, let's understand how the code is organized.
The Hexagon SDK's calculator example demonstrates the ability to offload computation on the DSP by calling a function on the HLOS and have it executed on the DSP. This is done via FastRPC in which the complexity of the remote procedure call is made transparent to the caller by only requiring the client to call a library function on the HLOS. That library on the HLOS is referred to as a stub and its corresponding implementation on the DSP is referred to as a skel.
-
Makefile
Root makefile that invokes variant-specific
.min
files to either build the application processor source code or the Hexagon DSP source code. -
hexagon.min
,android.min
,UbuntuARM.min
Contains the make.d directives used to build the application processor and Hexagon DSP source code.
-
inc/calculator.idl
IDL interface that defines the calculator API.
This IDL is compiled by the QAIC IDL compiler into the following files:
calculator.h
: C/C++ header filecalculator_stub.c
: Stub source that needs to be built for the HLOS (LA, LE etc...)calculator_skel.c
: Skel source that needs to be built for the Hexagon DSP
-
src/calculator_main.c
Source for the LA/LE executable that calls the calculator stub on the HLOS side to offload the compute task onto the DSP.
-
src/calculator_test.c
Source for the HLOS-side test function.
-
src/calculator_imp.c
Source for the Hexagon-side implementation of the calculator interface and is compiled into a shared object.
-
src/calculator_test_main.c
Source for simulator executable which calls DSP side compute task on simulator.
Customizing the calculator example
The following steps will show you how you can declare and implement a new method on the DSP and invoke it from the application processor:
-
Add a new method to the calculator interface in inc/calculator.idl
long diff(in sequence<long> vec, rout long long res);
-
Add a new implementation of that function in
src/calculator_imp.c
int calculator_diff(remote_handle64 h, const int* vec, int vecLen, int64* res) { int ii = 0; *res = vec[0]; for(ii = 1; ii < vecLen; ++ii) { *res = *res - vec[ii]; } FARF(HIGH, "=============== DSP: diff result %lld ===============", *res); return 0; }
-
Call the new function from the executable (
src/calculator_main.c
).assert(0 == calculator_diff(h, test, num, &result));
-
Both LA/LE and hexagon binaries are to be rebuilt once modification is done.
Versioning the example
This section illustrates how to generate a shared object with version embedded in it. Versioning is only supported on targets having v73 arch and beyond.
Security vulnerabilities have been discovered across the libraries loaded on the DSP. The objective of versioning is to enable blacklisting all versions lower than the version specified in oemconfig.so, and thus prevent some older libraries from running on targets.
This example should be used as a reference to create shared objects with version.
Versioning scheme
- Valid versions are of the form
a[[.b].etc]
with no restriction on the number of version levels. - Versioning mechanism only supports “Integers”
- Clients need to make sure they use one scheme of versioning and communicate the version information to OEMs to avoid loading issues
- Version information is embedded inside the libraries using a NOTE segment
Blacklisting rules
Library versions blacklisted in oemconfig |
Version present in library | Loading library version > Blacklisted version | Library loaded successfully? |
---|---|---|---|
NO | NO | N.A. | YES |
NO | YES | N.A. | YES |
YES | NO | N.A. | NO |
YES | YES | NO if version blacklisted, YES otherwise | NO if version blacklisted, YES otherwise |
Shared object generation with version in NOTE segment
The GNU/LLVM linker uses attribute __attribute__ ((section (".note.lib.ver")))
to add a new library version note section in the shared object.
In this example src/version.c
is compiled as part of the shared object to add lib.ver.1.0.0. in the new section. Here 1.0.0 is the library format type, which expects library and version pair in following format.
"lib.ver.1.0.0.libcalculator_skel.so:4.5.0"
FastRPC uses this note section lib.ver.1.0.0. to decide whether to load the shared object onto the DSP or not.
Library and version pairs should be defined in the format shown below with the prefix "lib.ver.1.0.0." to indicate a version note segment.
In incs/version_note.h:-
typedef struct {
int size_name; // Size of the NOTE section
int size_desc; // Size of the descriptor(unused)
int type; // Type of section(unused)
char name[100]; // Name of NOTE section(version of shared object)
int desc[3]; // used for labeling note segment version (lib.ver.V1.V2.V3)
} lib_ver_note_t;
In examples/version.c:-
// Library version
const lib_ver_note_t so_ver __attribute__ ((section (".note.lib.ver")))
__attribute__ ((visibility ("default"))) __attribute__((aligned(0x1000)))= {
100,
0,
0,
"lib.ver.1.0.0.libcalculator_skel.so:4.5.0"
};
Verifying the version is included in library
Shared object with a version must have a separate note section containing text lib.ver.<library format type>.<library name>:<library version number>
.
You can confirm whether a shared object contains a version or not by using the command below.
hexagon-llvm-readelf --notes <shared_object>
If your output is similar to the excerpt below, then the specified shared object has version NOTE
section.
Notes at offset 0x00001000 with length 0x00000070:
Owner Data size Description
lib.ver.1.0.0.libcalculator_skel.so:4.3.0 0x00000000 Unknown note type: (0x00000000)