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
- Using the IDE, perform following steps
- Import project
- Building
- Simulator testing
- Modifying the example
- Project structure
- Customizing the calculator example
Please note that in this document we are discussing procedures for Android targets only. For LE targets refer OS support CPU.
The calculator example consumes an array of integers and returns both the sum and the maximum value of that array.
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 if you are stuck at any step in this section and examine the output of script and/or script itself for better understanding.
Without the walkthrough script, you will need to build both the Android and Hexagon modules. This is accomplished by running the following make commands (assuming your desired Hexagon architecture version is v65):
make android BUILD=Debug
make hexagon BUILD=Debug DSP_ARCH=v65
For more information on the build syntax, please refer to the building reference instructions.
Alternatively, you can build the same Android and Hexagon modules with CMake.
build_cmake hexagon BUILD=Debug
build_cmake android BUILD=Debug
To target a different DSP architecture than the default one (V65), simply use the DSP_ARCH
option when using the hexagon
target. 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 v65 instruction set:
make hexagon BUILD=Debug DSP_ARCH=v65
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_toolv87_v65\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. -mv65 . |
--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 -mv65 --simulated_returnval --usefs hexagon_Debug_toolv87_v65 --pmu_statsfile hexagon_Debug_toolv87_v65/pmu_stats.txt hexagon_Debug_toolv87_v65/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 (v65a_512)
- 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
adb root adb wait-for-device adb remount
-
Push the HLOS side calculator test executable and supporting calculator stub library onto the device's file system
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/
-
Push the Hexagon Shared Object to the device's file system
adb shell mkdir -p /vendor/lib/rfsa/dsp/sdk adb push hexagon_Debug_toolv87_v65/ship/libcalculator_skel.so /vendor/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.
adb shell echo "0x1f > /vendor/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 two environment variables are to be passed along with example binary and its arguments
- 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 example:
# run example with 1000 array size on CPU adb shell "export LD_LIBRARY_PATH=/vendor/lib64/ DSP_LIBRARY_PATH=/vendor/lib/rfsa/dsp/sdk; /vendor/bin//calculator -r 1 -n 1000" 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
# run example with 1000 array size on aDSP 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 sDSP adb shell "export LD_LIBRARY_PATH=/vendor/lib64/ DSP_LIBRARY_PATH=/vendor/lib/rfsa/dsp/sdk; /vendor/bin//calculator -r 0 -d 2 -n 1000 -U 0" Starting calculator test Attempting to run on signed PD on domain 2 Allocate 4000 bytes from ION heap Creating sequence of numbers from 0 to 999 Compute sum on domain 2 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 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 0" Starting calculator test Attempting to run on signed 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 last command, the commands above will run successfully only on a signed device. The last 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.# run example with 1000 array size on cDSP Unsigned PD 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
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.
adb shell "export LD_LIBRARY_PATH=/vendor/lib64/ DSP_LIBRARY_PATH=/vendor//lib/rfsa/dsp/sdk; /vendor/bin//calculator -d 3 -U 0 -q 1"
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
Using the IDE
The previous sections described how to build and run the code on simulator or on target using the command line. This section explains how to perform the same steps using the IDE. Before following these instructions, please ensure that you have already created a workspace for your IDE.
Importing project
Once in the IDE, import calculator into your IDE workspace by right-clicking in Project Explorer and selecting Import -> Hexagon C/C++ -> Import Hexagon Project > Next
.
In the dialog box, specify the following project properties:
- Project type –
Makefile Project
- Project name –
calculator
- Project location –
%HEXAGON_SDK_ROOT%\examples\calculator
Click on the Finish
button to finish importing the project
Building
To build the imported project, right-click on the project , select properties
and then C/C++ Build
. Under Builder Settings
, enter the build command as make hexagon BUILD=Debug DSP_ARCH=v65 VERBOSE=1
(or enter your target-appropriate build flavor).
While debugging, Debug build flavors are recommended when possible over ReleaseG. When ReleaseG is selected, the compiler will apply optimizations that will result in the disassembly interleaving various C instructions, which makes the code harder to understand while stepping through it.
Click on Apply and Close
to apply setting and close the window.
Build the project by right-clicking on the project and select Build Project
.
Once building is done, you should see 0 errors, 0 warnings in the console window.
Simulator testing
To run the project on simulator, right-click on the project in Project Explorer and choose Run As -> Run Configuration
.
This command displays the Run Configurations
dialog box, which enables you to configure the simulator, program arguments, and runtime environment.
In order to debug on the simulator, the user must select the target-specific QuRT image runelf.pbn
file for the desired architecture version by specifying the QuRT image path in the C/C++ Application
field. This approach is illustrated below and will result in the debugger loading the user ELF, calculator_q, and calling main()
.
The dialog box displays tabs for configuring the simulator, program arguments, and runtime environment.
(Note that the left-side pane in the dialog box includes a newly-created runtime configuration named calculator Default
, which appears under the item Hexagon C/C++ Application
.)
To specify the simulator arguments, click on the Simulator
tab in the dialog box. For this example please give following options in the miscellaneous flags section. The brief explanations of these flags is given above.
-mv65 --simulated_returnval --usefs hexagon_Debug_toolv87_v65 --pmu_statsfile hexagon_Debug_toolv87_v65/pmu_stats.txt hexagon_Debug_toolv87_v65/calculator_q --
To execute the program, click on the Run
button at the bottom of the dialog box.
The dialog box closes and the following output is displayed in the console of the main IDE window.
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 (Android, Embedded Linux etc...)calculator_skel.c
: Skel source that needs to be built for the Hexagon DSP
-
src/calculator_main.c
Source for the Android 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 android 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)