Skip to content

Commit 34a83e5

Browse files
authored
Add minimal QNN HTP cDSP test APK (#20599)
Standalone Android app that tests whether a regular installed app (untrusted_app SELinux domain) can initialize the QNN HTP backend and open FastRPC to the Hexagon cDSP. Verified working on Galaxy S23 (SM8550, SELinux enforcing). Supports S23/S24/S25 via skels from Maven com.qualcomm.qti:qnn-runtime. No QNN SDK headers needed — types are inlined, libs loaded via dlopen/dlsym.
1 parent 4e4cb3f commit 34a83e5

13 files changed

Lines changed: 736 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Build artifacts
2+
.gradle/
3+
build/
4+
app/build/
5+
app/.cxx/
6+
7+
# QNN native libraries — download from Maven, don't commit
8+
app/src/main/jniLibs/arm64-v8a/*.so
9+
10+
# IDE
11+
.idea/
12+
*.iml
13+
local.properties
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# QNN HTP cDSP Test APK
2+
3+
Minimal Android app that tests whether a regular installed app (`untrusted_app` SELinux
4+
domain) can initialize the QNN HTP backend and open a FastRPC channel to the Hexagon
5+
cDSP on Qualcomm Snapdragon devices.
6+
7+
## What This Tests
8+
9+
Tap a button, call `backendCreate()` via the HTP backend. This opens FastRPC to the
10+
cDSP. If the device blocks it, you get an error. If it succeeds, unsigned PD access
11+
works from a normal installed app.
12+
13+
**Tested and confirmed working on Galaxy S23 (SM8550, SELinux enforcing, untrusted_app).**
14+
15+
## Setup
16+
17+
### 1. Get the QNN Libraries
18+
19+
Download the QNN runtime AAR from Maven Central and extract the native libs:
20+
21+
```bash
22+
curl -L -o /tmp/qnn-runtime.aar \
23+
"https://repo1.maven.org/maven2/com/qualcomm/qti/qnn-runtime/2.44.0/qnn-runtime-2.44.0.aar"
24+
25+
mkdir -p app/src/main/jniLibs/arm64-v8a
26+
cd app/src/main/jniLibs/arm64-v8a
27+
unzip /tmp/qnn-runtime.aar 'jni/arm64-v8a/*.so'
28+
mv jni/arm64-v8a/*.so .
29+
rm -rf jni
30+
```
31+
32+
This includes skels for V68–V81, covering S23 through S25.
33+
34+
### 2. Build
35+
36+
```bash
37+
./gradlew assembleDebug
38+
```
39+
40+
### 3. Install & Run
41+
42+
```bash
43+
adb install -r app/build/outputs/apk/debug/app-debug.apk
44+
```
45+
46+
Launch the app, tap **"Run QNN HTP Test"**. Results appear on screen.
47+
48+
## Interpreting Results
49+
50+
- **`SUCCESS! backendCreate returned 0`** → cDSP works from `untrusted_app`
51+
- **Error 4000+** → Transport/SELinux block
52+
- **Error 2000** → Check skel version matches device Hexagon arch
53+
54+
## Supported Devices
55+
56+
| Chipset | Hexagon | Devices |
57+
|---------|---------|---------|
58+
| SM8550 | V73 | Galaxy S23 family |
59+
| SM8650 | V75 | Galaxy S24 family |
60+
| SM8750 | V79/V81 | Galaxy S25 family |
61+
62+
## How It Works
63+
64+
QNN libraries are bundled inside the APK via `jniLibs`. On install, Android extracts
65+
them (`extractNativeLibs="true"`). The JNI code:
66+
67+
1. Sets `ADSP_LIBRARY_PATH` to the app's `nativeLibraryDir`
68+
2. `dlopen("libQnnHtp.so")``dlsym("QnnInterface_getProviders")`
69+
3. Calls `logCreate()` and `backendCreate()` via the interface function pointer table
70+
4. Reports success or failure
71+
72+
No QNN SDK headers needed — types are inlined. The manifest entry
73+
`<uses-native-library android:name="libcdsprpc.so"/>` makes the vendor FastRPC client
74+
accessible on Android 12+.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
plugins {
2+
id 'com.android.application'
3+
}
4+
5+
android {
6+
namespace 'com.example.qnntest'
7+
compileSdk 34
8+
ndkVersion '29.0.13599879'
9+
10+
defaultConfig {
11+
applicationId "com.example.qnntest"
12+
minSdk 30
13+
targetSdk 34
14+
versionCode 1
15+
versionName "1.0"
16+
17+
ndk {
18+
abiFilters 'arm64-v8a'
19+
}
20+
21+
externalNativeBuild {
22+
cmake {
23+
cppFlags ""
24+
}
25+
}
26+
}
27+
28+
externalNativeBuild {
29+
cmake {
30+
path "src/main/jni/CMakeLists.txt"
31+
}
32+
}
33+
34+
buildTypes {
35+
release {
36+
minifyEnabled false
37+
}
38+
}
39+
40+
compileOptions {
41+
sourceCompatibility JavaVersion.VERSION_17
42+
targetCompatibility JavaVersion.VERSION_17
43+
}
44+
45+
sourceSets {
46+
main {
47+
jniLibs.srcDirs = ['src/main/jniLibs']
48+
}
49+
}
50+
51+
packaging {
52+
jniLibs {
53+
// Don't strip QNN libs — keep skel intact
54+
keepDebugSymbols.add('**/*.so')
55+
}
56+
}
57+
}
58+
59+
dependencies {
60+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application
5+
android:label="QNN HTP Test"
6+
android:allowBackup="false"
7+
android:extractNativeLibs="true"
8+
android:theme="@android:style/Theme.DeviceDefault">
9+
10+
<!-- Required for cDSP FastRPC access on Android 12+ -->
11+
<uses-native-library
12+
android:name="libcdsprpc.so"
13+
android:required="false"/>
14+
15+
<activity
16+
android:name=".MainActivity"
17+
android:exported="true">
18+
<intent-filter>
19+
<action android:name="android.intent.action.MAIN"/>
20+
<category android:name="android.intent.category.LAUNCHER"/>
21+
</intent-filter>
22+
</activity>
23+
</application>
24+
</manifest>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.example.qnntest;
2+
3+
import android.app.Activity;
4+
import android.os.Bundle;
5+
import android.util.Log;
6+
import android.widget.Button;
7+
import android.widget.ScrollView;
8+
import android.widget.LinearLayout;
9+
import android.widget.TextView;
10+
11+
public class MainActivity extends Activity {
12+
13+
private static final String TAG = "QnnHtpTest";
14+
private TextView logView;
15+
private Button testButton;
16+
17+
static {
18+
System.loadLibrary("qnn_test_jni");
19+
}
20+
21+
private native String runQnnHtpTest(String nativeLibDir);
22+
23+
@Override
24+
protected void onCreate(Bundle savedInstanceState) {
25+
super.onCreate(savedInstanceState);
26+
27+
LinearLayout layout = new LinearLayout(this);
28+
layout.setOrientation(LinearLayout.VERTICAL);
29+
layout.setPadding(32, 32, 32, 32);
30+
31+
testButton = new Button(this);
32+
testButton.setText("Run QNN HTP Test");
33+
testButton.setTextSize(18);
34+
layout.addView(testButton);
35+
36+
ScrollView scrollView = new ScrollView(this);
37+
logView = new TextView(this);
38+
logView.setPadding(0, 24, 0, 0);
39+
logView.setTextSize(13);
40+
logView.setTextIsSelectable(true);
41+
scrollView.addView(logView);
42+
layout.addView(scrollView);
43+
44+
setContentView(layout);
45+
46+
String nativeLibDir = getApplicationInfo().nativeLibraryDir;
47+
48+
testButton.setOnClickListener(v -> {
49+
testButton.setEnabled(false);
50+
testButton.setText("Running...");
51+
logView.setText("");
52+
appendLog("nativeLibraryDir: " + nativeLibDir + "\n");
53+
54+
new Thread(() -> {
55+
String result = runQnnHtpTest(nativeLibDir);
56+
Log.i(TAG, result);
57+
runOnUiThread(() -> {
58+
appendLog(result);
59+
testButton.setEnabled(true);
60+
testButton.setText("Run QNN HTP Test");
61+
});
62+
}).start();
63+
});
64+
}
65+
66+
private void appendLog(String msg) {
67+
Log.i(TAG, msg);
68+
logView.append(msg + "\n");
69+
}
70+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
cmake_minimum_required(VERSION 3.22.1)
2+
project("qnn_test_jni")
3+
4+
add_library(qnn_test_jni SHARED qnn_test.cpp)
5+
6+
find_library(log-lib log)
7+
target_link_libraries(qnn_test_jni ${log-lib} dl)

0 commit comments

Comments
 (0)