12.4. The Onyx/Boox platform layer

What turns a Qualcomm lagoon reference build into a Boox e-reader is mostly implemented above the hardware bridge, in the Android platform itself. This page catalogues that customisation as far as it is statically visible: the system/vendor version split, the native Onyx SDK, the configuration and policy glue, and the preinstalled Onyx apps. Coverage of application logic here is shallow — the bulk of the Boox behaviour is compiled Java in odex/vdex’d framework and app archives, which are not decompiled in this documentation set — but the shape of the layer, and the one genuinely load-bearing structural fact (the API-level split), are clear from the partition metadata.

Everything here is from the system/system_ext/product/vendor partition listings and the extracted build properties and init scripts. No code was executed.

12.4.1. A GSI-style Android-15 system on an Android-11 vendor

“The Android version” is not a single number on this device — three layers carry independent version stamps, and only the system stamp is the platform version the user sees:

Layer

Version (authoritative property)

Notes

kernel

Linux 4.19.157

the SM6350 lagoon BSP kernel line; decoupled from the Android userspace version (GKI) — a 4.19 kernel under Android 15 is normal

vendor / boot

Android 11 (ro.vendor.build.version.release=11, sdk=30)

the Onyx/NoteAir5C lagoon BSP + HALs; vendor sepolicy version 30.0; vendor security patch 2025-10-01

system / system_ext / product

Android 15 (ro.build.version.release=15, sdk=35)

the ONYX/TabBoox unified Onyx system; sepolicy 202404 [1]; system security patch 2026-06-01

So the vendor and boot images are the Android-11 lagoon BSP built for this specific device (NoteAir5C), while the system / system_ext / product images are a generic Android-15 Onyx system named TabBoox — inferred, from the generic naming and Treble/GSI-style layering, to be a single unified Boox tablet system shipped across many models on top of a per-device Android-11 vendor; no cross-device comparison was done to confirm this. This is the structural key to several observations elsewhere: it is why keystore2 (an Android-12+ daemon) runs over Android-11 keymaster@4.1 (Security and DRM userspace), and why the framework is four releases newer than the lagoon HALs it sits on.

Note

The ro.build.fingerprint string reads ONYX/TabBoox/TabBoox:13/TKQ1.230615.001/… — a stale :13 release token and an old Tiramisu build-ID left in the hand-built fingerprint. It is not the version: the authoritative ro.build.version.release / …sdk are 15 / 35, corroborated by the Android-15 SELinux policy version (202404) and the 2026-06 system patch. Read the version properties, not the fingerprint token.

12.4.2. The native Onyx SDK

The Onyx-specific native libraries in /system/lib64 (a few also 32-bit):

Library

Role (where determinable)

libonyx_epd_listener.so

the EPD update service — the userspace end of /dev/ebc (The e-ink software stack (Onyx EPDC))

libonyx_neo_dither.so / libonyx_cfa.so

e-ink dithering and colour-filter-array / RGBW colour (The e-ink software stack (Onyx EPDC))

libonyx_pen_touch_reader.so

low-latency pen/touch input reader

libonyx_stardict.so / libonyx_mdict.so

dictionary engines (StarDict / MDict formats — the reader’s lookup)

libonyx_lame.so

LAME MP3 encoder (audio note / recording export)

libonyx_algorithm.so / libonyx_babylon.so / libonyx_dsl.so

Onyx algorithm helpers (image/text processing; dsl is another dictionary format) — internals not decompiled

libmmkv-onyx.so

Onyx’s build of Tencent MMKV, the key-value store backing /onyxconfig (below)

libonyx_memory_test.so

a memory self-test helper

The display and input libraries are documented in The e-ink software stack (Onyx EPDC); the rest are reader/app-support libraries. Note there is no separate Onyx framework .jar — the framework-level customisation is compiled into the standard framework.jar / services.jar of the TabBoox system image (odex’d, not examined here).

12.4.3. Inside the native Onyx SDK: static RE

The libraries above were catalogued by name; disassembling the e-ink/pen-related ones (nm -D / objdump -T symbols, strings, and capstone on the exported functions) shows what each actually does and what it does not. All are ordinary AArch64 ELF shared objects that retain their dynamic symbols; the C++ is demangled below. One structural correction falls out immediately: none of the ``libonyx_*`` libraries open ``/dev/ebc``. The EPD listener talks to a FIFO, the pen reader talks to evdev, and the two “colour” libraries are pure bitmap helpers. The actual /dev/ebc update client lives in the patched AOSP graphics stack (The userspace to kernel EPD update path).

12.4.3.1. The EPD event FIFO listener

libonyx_epd_listener.so (JNI class android/onyx/optimization/EpdEventListener, class EpdListener, log tag lib_epd_listener) is an event forwarder, not an update driver. Its dynamic-symbol imports are __open_2, eventfd, poll, read, write, close — there is no ``ioctl`` and no ``mmap``. Disassembly of EpdListener::setup shows it __open_2(path, O_RDWR) a FIFO (the failure log reads Could not open fifo: %s %s), creates an eventfd, and fills a two-entry pollfd array (FIFO + eventfd). readEpdEventLoopImpl then loops poll(fds, 2, -1); on POLLIN it reads up to 1024 bytes, wraps them as a std::string and hands them to a Java onEpdEvent(String) callback. The eventfd is the shutdown wakeup — closeDevice writes an 8-byte token to it (“notify event fd”). The FIFO path is passed in from Java as a jstring (nativeStart calls GetStringUTFChars), so it is not a literal in the .so; it is the /dev/onyx/listener FIFO that init.onyx.rc creates (The e-ink software stack (Onyx EPDC)). So this library is a text-event channel from the framework/SurfaceFlinger to the app, not the pixel path.

12.4.3.2. The raw pen/stylus reader

libonyx_pen_touch_reader.so (JNI class com/onyx/android/sdk/pen/RawInputReader, log tags lib_touch_reader / onyx_emp) is the low-latency handwriting input path — it reads the pen digitiser straight from evdev, bypassing the Android InputReader. It carries a full C++ engine: TouchReader (device + read loop), PenManager (pressure, clip regions, erase state) and a per-mode reader family DrawReader / BtnReader / EraseReader / SideEraseReader. TouchReader::findDevice iterates /dev/input/event0event15 and selects the node whose EVIOCGNAME string contains ``Wacom``, ``hanvon`` or ``onyx_emp`` (the immediates are visible in the disassembly). openDevice then queries it with the standard evdev ioctls, recovered as request numbers:

ioctl

request

use

EVIOCGVERSION

0x80044501

driver version

EVIOCGID

0x80084502

bus/vendor/product id

EVIOCGNAME(79)

0x804f4506

device name (matched above)

EVIOCGPHYS(79) / EVIOCGUNIQ(79)

0x804f4507 / 0x804f4508

physical location / unique id

EVIOCGABS(ABS_PRESSURE)

0x80184558

input_absinfo.maximum → the pen’s max pressure, stored for PenManager::normalizePressure

The read loop polls (same FIFO+eventfd wakeup pattern as the EPD listener) and reads 24-byte ``struct input_event`` records (type at +0x10, code at +0x12, value at +0x14), timestamps them, and feeds TouchConsumer::processTouchEvent → the Java onTouchPointReceived callback. This is the reader that fills the ION handwriting buffer (CONFIG_ONYX_EPDC_HANDWRITE_BUF_MALLOC_FROM_ION, The e-ink software stack (Onyx EPDC)).

12.4.3.3. Bitmap pre-processors: dither and RGBW

libonyx_neo_dither.so and libonyx_cfa.so are pure JNI bitmap helpers over libjnigraphics — neither opens /dev or calls ioctl; they transform pixels before the framework submits the buffer, and are not the “userspace end of /dev/ebc” the name might suggest.

  • libonyx_neo_dither.so (log tag neo_color_filter) exports DitherUtils.dither / DitherUtils.ditherColor and the classes imgfilter::ImageFilter / ImageColorFilter / ImageFilterRGB565, each a doFilterInPlace(AndroidBitmapFormat, ptr, w, h) that dithers the locked bitmap in place (RGBA_8888 and RGB_565).

  • libonyx_cfa.so (log tag onyx_cfa) exports QRCodeUtil.toRgbwBitmap and a ColorUtils with red/green/blue/gray/white + toRed/toGreen/toBlue/toWhite — it packs an RGBA_8888 bitmap into the panel’s RGBW sub-pixel layout (the colour filter array for the Kaleido-class panel, The e-ink software stack (Onyx EPDC)).

The two smallest e-ink-adjacent libraries are libonyx_algorithm.so (a single scribble.utils.Algorithm.distance geometry helper) and libonyx_dsl.so (an ABBYY-Lingvo DSL dictionary provider — not e-ink at all). Several of these libraries also embed a shared DeviceUtils::isValid helper that calls back into Java android/hardware/DeviceController.systemIntegrityCheck()Z — a soft anti-tamper gate; it is orthogonal to the display path.

12.4.3.4. Deeper static RE: the small helper libs, the pen pressure curve, and libneo_pen.so

The subsections above catalogue libonyx_cfa.so / libonyx_neo_dither.so / libonyx_pen_touch_reader.so by exported JNI class and general shape. This section goes one level deeper — actual disassembly of the exported functions’ bodies, not just their names — for those three plus the two libraries that were previously only name/size-catalogued: the 4 KB libonyx_algorithm.so and the 3.8 MB handwriting engine libneo_pen.so. Full instruction transcripts, file offsets and the literal constant bytes for every claim below are in artifacts/re_static/item_onyx_native_deep.txt; only the findings are summarised here. Confirmed for all five: none of them issues any ``ioctl`` at all (artifacts/re_static/ioctlscan.py reports 0 callsites in four of the five, and exactly the nine evdev EVIOCG* calls already known in libonyx_pen_touch_reader.so — never /dev/ebc or /dev/onyx/*), so the “outside the /dev/ebc path” judgement holds for every one of these libraries as a file. One of them has a subtler qualification, noted below.

libonyx_algorithm.so (4104 bytes total) has exactly one export, Java_com_onyx_android_sdk_scribble_utils_Algorithm_distance, and its entire 128-byte body is a textbook point-to-line-segment distance: project the third point onto the line through the first two, clamp the projection parameter to [0, 1], and return the Euclidean distance to the clamped point. That is the whole library — a single geometry primitive for the scribble/stroke code (most likely stroke simplification or hit-testing, though unconfirmed which one) — fully covered, no device access of any kind.

libonyx_cfa.so’s ColorUtils channel extractors confirm the pixel format is plain 0xAARRGGBB (red() = >>0x10, green() = >>8, blue() = identity, alpha() = >>0x18), and ColorUtils::gray() disassembles to a literal 0.587*G + 0.299*R + 0.114*B — the exact ITU-R BT.601 luma weights, read directly out of the .so as IEEE-754 doubles at file offsets 0x22a0/0x22a8/0x22b0. ColorUtils::white(r,g,b) is the same formula’s fixed-point twin: (299r + 587g + 114b) / 1000 via a reciprocal-multiplication divide (magic constant 0x10624DD3). The sole JNI export, QRCodeUtil.toRgbwBitmap, uses exactly this: for every source ARGB pixel it writes a 2x2 block of four destination pixels — one red-replicated (argb(a,r,r,r)), one green-replicated, one blue-replicated, and one carrying the BT.601 luma replicated across all three channels (the “white”/K tap) — i.e. despite the QR-code-sounding class name, this is the generic RGB→RGBW colour-filter-array packer for the Kaleido-class colour panel, with the per-tap formula and luma weights now pinned by bytes rather than inferred from the function name.

libonyx_neo_dither.so’s grayscale ImageFilter::doFilterInPlace is a genuine error-diffusion dither, fully disassembled: it computes a per-pixel weighted grey level (11*R + 16*G + 5*B) / 32 — a different, independently-authored weighting from the CFA library’s BT.601 constants — quantizes it to a configurable quant_bits depth (visible in its own log string, "quant_bits: %d, quant_step: %d"), and forward-diffuses half the quantization residual into a per-column error-line buffer for the next row. This is a simplified 2-tap forward/down variant of the Floyd–Steinberg family, not the textbook 7/16-3/16-5/16-1/16 four-neighbour kernel — no such coefficients appear anywhere in the disassembly. ImageColorFilter (the ditherColor export) runs the identical quantize/diffuse math but allocates three independent error-line buffers, one per R/G/B channel, confirming colour dithering is per-channel rather than luma-derived.

libonyx_pen_touch_reader.so’s PenManager::normalizePressure() turns out to implement a real, hand-tuned piecewise pressure-response curve, not a raw pass-through: it low-pass-filters consecutive raw samples (smoothed = (0.5*prev + raw) / 1.5), normalises against the device’s EVIOCGABS(ABS_PRESSURE).maximum, and then applies a 4x linear gain for the lightest 15% of the range (boosting faint initial pen contact) that hands off — continuously, verified by evaluating both branches at the 0.15 threshold and getting the same 0.60 — to a compressive power curve pow(frac-0.135, 0.2)+0.17 for the rest, with all three constants (-0.135, 0.2, 0.17) read as literal doubles out of the binary. (There is also an unconditioned fallback branch, gated on an unrelated per-instance field, that returns the raw sample unmodified — its trigger condition was not traced further.) Button handling maps the evdev codes precisely: BTN_TOOL_RUBBER (physical eraser tip), BTN_TOUCH and BTN_STYLUS (side button) combine in isShortcutErasing() — named field by field in that function’s own log string — to support a chorded “hold-side-button-to-erase” shortcut. Region clipping (inLimitRegion/inValidRegion/inExcludeRegion) expands each sample point into a tiny stroke-width-sized box and tests it against app-supplied allow/deny rectangle arrays (setLimitRegion/ setExcludeRegion) — this is what the “coordinate transform” question resolves to in this library: there is no calibration/rotation matrix anywhere in it, only region clipping. Likewise moveTo()/quadTo() turn out to be two-line wrappers that just tag an event-type flag for report() — no Bézier/quadratic math happens in this library despite the Android-Path-style naming; if curve smoothing happens anywhere, it is either in the Java SDK layer or in libneo_pen.so (next).

libneo_pen.so (3.8 MB) exports only seven of its own symbols — the JNI entry points nativeCreatePen/nativeDestroyPen/nativeOnPenDown/ nativeOnPenMove/nativeOnPenUp/nativeSetBitmapColor/ nativeSetLogLevel of class com.onyx.android.sdk.pen.NeoPenNative — plus roughly 971 more symbols (978 total defined dynamic symbols) that turn out to be the public API of statically-linked spdlog and fmt (783 and 45 symbols respectively) and the same small JNIUtils/JNIBitmap/ColorUtils/DeviceUtils glue shared with the other onyx libs. The actual handwriting engine is compiled with hidden visibility, so its own classes carry no dynamic symbols — but the compiler’s C++ RTTI typeinfo-name strings for those classes are still present as literal ASCII in the binary (strings -a finds them even though nm -D cannot), and they lay out the real architecture precisely: neo::pen::NeoStrokeRenderer, a templated StrokeProcessor<A,B> chaining TouchEvent StrokePointBlob StrokeSpline, a SplineProducer/SplineInterpolator family specialised into SplineDistanceInterpolator (arc-length parameterised) and SplineCurvatureInterpolator (curvature-adaptive recursive subdivision — its mangled lambda names literally include subdivideRecursive/subdivideRecursiveXY/getTForCubic, the same family of technique as AGG-style curve flattening), and a SmoothFilter (paired with a smoothLevel string). Together with the JNI method names and log strings drawStroke/"invalid empty stroke renderer!"/"alloc render points/result data failed!", this confirms libneo_pen.so is the low-latency ink stroke-rendering engine: it ingests raw pen samples, curve-fits/smooths them via curvature-adaptive spline subdivision, and rasterises the result directly into a locked Android Bitmap (AndroidBitmap_lockPixels is imported; no ioctl/open/mmap is). Disassembly of the fully-transcribed entry points bears this out structurally — nativeOnPenDown/nativeOnPenUp marshal a Java touch-point object through a JNI vtable call into a native record carrying position, at least two extra float channels, and a 64-bit timestamp, then dispatch to internal handlers; nativeSetBitmapColor is a NEON-vectorised pass that recolours every opaque-white pixel of a bitmap to translucent red (0x80FF0000, literal constant) and clears every other pixel to transparent — a mark/highlight-region helper, not ink rendering itself.

This library is also where the “outside the boot path” scope judgement needs a real qualification rather than a blanket reconfirmation. SurfaceFlinger’s own strings (already recorded above: neopen_createPen, createPen, "invalid empty stroke renderer!") are byte-for-byte identical to strings found inside libneo_pen.so — but surfaceflinger’s NEEDED list (56 entries, checked in full) does not include libneo_pen.so. The two binaries therefore do not share this code at the dynamic-linking level; the more likely explanation (unconfirmed — no build system or source tree was available to check directly) is that the same neo::pen C++ source is compiled into both independently — once as the standalone, JNI-callable, device-free libneo_pen.so documented here, and once statically embedded directly inside SurfaceFlinger as its neopen_createPen/createPen direct compositor-side fast-draw path, which does go on to call ioctl(/dev/ebc, SET_EBC_SEND_UPDATE, ...) for the actual panel refresh. So: the libneo_pen.so file never touches a device — confirmed — but the stroke-rendering algorithm it implements has a sibling copy that sits squarely in the low-latency ink display path. That is a more precise finding than either “it’s part of the EPD path” or “it’s completely unrelated to the EPD path”.

12.4.3.5. The userspace to kernel EPD update path

The panel-update ioctls on /dev/ebc are not issued by any libonyx_* library. Grepping the images, /dev/ebc appears in vendor only in init.onyx.rc (chmod 0666) and the sepolicy file_contexts (label ebc_device), and in system only in bin/charger (the minui charge screen) and bin/surfaceflinger. The real client is the Onyx-patched SurfaceFlinger, and the app-facing API is compiled into a patched ``libgui.so``: Onyx extended the core AOSP graphics classes with an e-ink update list that rides the normal buffer-queue/Parcel path. The exported symbols (defined in libgui.so) are:

symbol (demangled)

role

android::EpdcWrapper (addEpdc(int,int,int,int,int), addEpdcList, mergeByMode, getEpdcList, setBatch, flatten / unflatten / flattenEpdcList)

a wrapper around std::vector<hwc_epdc_llist>; each element is 5×int32 = 20 bytes (an update rectangle plus a waveform_mode — hence mergeByMode)

android::Surface::transferEpdc(vector<hwc_epdc_llist>&) / Surface::clearEpdcList

the app attaches its e-ink update regions to a Surface

android::SurfaceComposerClient::Transaction::transferEpdc(...)

the same, through a Transaction

android::BufferData::writeEpdc(Parcel*) / readEpdc(Parcel*)

the list is flattened into the buffer’s Parcel and unflattened in SurfaceFlinger

So the flow is: an app calls Surface::transferEpdc with a list of hwc_epdc_llist regions → libgui flattens it into the frame’s Parcel (writeEpdc) → SurfaceFlinger unflattens (readEpdc), merges same-mode regions (EpdcWrapper::mergeByMode) and calls its onyx_epdc_screenRefresh, which issues ioctl(/dev/ebc, SET_EBC_SEND_UPDATE, …). That request number was pinned by cross-referencing SurfaceFlinger’s own error string (onyx_epdc_screenRefresh(): error! SET_EBC_SEND_UPDATE retval = 0x%x): the ioctl immediately preceding it loads w1 = 0x700c, so:

SET_EBC_SEND_UPDATE  =  ioctl request  0x700c

The other EBC ioctls SurfaceFlinger issues sit in the same 0x70xx family (e.g. 0x701d), i.e. the /dev/ebc ABI uses plain 0x7000-based magic numbers (the rockchip-style “ebc” numbering the Onyx software EPDC reuses); their individual names are the GET_EBC_* / SET_EBC_* set recovered kernel-side (The /dev/ebc interface: ioctls, update modes, and the waveform format). SurfaceFlinger also carries a direct pen “stroke renderer” fast-draw path (neopen_createPen / createPen, ### disable refresh, /sys/onyx_misc/cytp_lo_filter) for low-latency ink.

12.4.4. Configuration, policy and the /onyxconfig store

  • Init glueinit.onyx.sh, init.onyx.misc.sh, init.onyxconfig.sh (system) and init.onyx.rc (vendor, The e-ink software stack (Onyx EPDC)). These set up device knobs, gate download_mode off on user builds unless a production-test tag file exists, and start the config store.

  • The ``/onyxconfig`` partition — a dedicated ext4 partition (Partition map and checksums, raw image lun0/onyxconfig.bin) holding an mmkv directory: Onyx’s persistent settings/config store, separate from /data so it survives factory reset. On this unit it holds exactly four top-level entries: the panel VCOM value (com.android.vcom = [REDACTED unique per physical unit], E-ink driver internals (register-level, from the kernel)) and two 4-byte factory-done markers, com.onyx.android.production.test and com.onyx.android.screenmanager, both literally the ASCII string DONE — plus test_result_info below.

  • ``/onyxconfig/test_result_info`` — a 1 KB JSON record written by the factory production line itself, carried through to the shipped device rather than wiped: model NoteAir5C, the unit’s WiFi MAC ([REDACTED unique per physical unit]), and three timestamped test-station passes — SMT ([REDACTED narrows production timeframe], board-level: charge, WiFi, BT, dual-speaker, dual-mic record, OTG, keys, SENSOR_TEST_TYPE_ACCELEROMETER/_GYROSCOPE, fingerprint, “note” i.e. pen), ASSEMBLE ([REDACTED narrows production timeframe], an AGING_TEST burn-in pass), and QA ([REDACTED narrows production timeframe], final line: adds SCREEN_FRONTLIGHT_TEST, SCRIBBLE_TEST, VCOM_SETTING, NATURAL_LIGHT_TEST, ANALOG_EARPHONE_TEST/DIGITAL_EARPHONE_TEST, SDCARD_TEST) — all results 1 (pass). This independently corroborates, from the factory’s own checklist rather than static code RE, the full sensor/peripheral inventory already recovered in Wacom EMR pen digitizer driver and Misc sensors (fingerprint, hall, keyboard cover) (Wacom EMR pen, fingerprint, IMU) and the front-light/VCOM path in E-ink driver internals (register-level, from the kernel). The fingerprint field in this JSON is a build fingerprint string (ONYX/TabBoox/TabBoox:13/TKQ1.230615.001/…), not a biometric fingerprint — most plausibly a stale/default value baked into the factory-test tool itself (unconfirmed; an Android-13 base string on a shipping Android-15 build, see the version split noted at the top of this page); either way, it is not a reliable version record.

  • Permissions / policyprivapp-permissions-onyx.xml (priv-app grants), onyx-default-permissions.xml (runtime-permission pre-grants), sysconfig/onyx_whitelist.xml (power/hibernation whitelist), and onyx_unavailable_features.xml (the “no NFC/GPS/camera/telephony” declaration, The e-ink software stack (Onyx EPDC), Absent and vestigial hardware (reference-base leftovers)).

  • Onyx propertiesvendor.onyx.tablet=true, vendor.onyx.htcon=true, sys.onyx.idledelay=2500, ro.build.version.onyxincremental=225 / ro.vendor.build.onyxid=… (the Onyx build-tracking IDs).

12.4.5. Preinstalled Onyx apps

The Boox application suite itself — the note app, reader, app store, cloud sync, and the rest — is catalogued in Preinstalled apps (Boox, GMS, AOSP); an onyx-only search for them misses their naming convention, but does surface OnyxLatinIME (keyboard) and OnyxOtaService (the OTA service, with libota_jni.so, driving the A/B updater in Userspace boot — init, SELinux, A/B updates). This page covers the platform scaffolding those apps run on (the SDK libraries, the EPD/pen native path, the config store, the permissions); the apps’ own compiled Java is not decompiled.

12.4.6. Scope boundary

The compiled Java of the TabBoox framework, the two Onyx system services, and the Boox app suite itself (odex/vdex, all preinstalled per Preinstalled apps (Boox, GMS, AOSP)) is not decompiled here; nor is the product partition’s Google/AOSP payload, covered separately in Preinstalled apps (Boox, GMS, AOSP). This page is the map of the Onyx platform scaffolding, not a reverse of the Boox applications.

12.4.7. Provenance

Source:

artifacts/notes/props/*.build.prop (the split fingerprints, Onyx properties), system_a.img (lib64/libonyx_*.so, lib64/libmmkv-onyx.so, bin/init.onyx*.sh, etc/permissions/sysconfig Onyx XML, app/OnyxLatinIME, priv-app/OnyxOtaService, framework/*.jar), partition listings for the app census. The “Inside the native Onyx SDK” section additionally uses the extracted .so binaries themselves — lib64/libonyx_epd_listener.so, libonyx_pen_touch_reader.so, libonyx_cfa.so, libonyx_neo_dither.so, libgui.so and bin/surfaceflinger — with the full symbol dumps, ioctl/dev/property strings and disassembly recorded in artifacts/re_static/item4_onyx_native.txt. The “Deeper static RE” subsection adds a second pass over libonyx_algorithm.so, libonyx_cfa.so, libonyx_neo_dither.so, libonyx_pen_touch_reader.so and libneo_pen.so — full instruction transcripts, extracted floating-point/fixed-point constants, and the ioctlscan.py per-library ioctl-callsite counts are in artifacts/re_static/item_onyx_native_deep.txt.

Method:

ext4 images listed/extracted read-only with 7z; properties and init scripts read as text. Dynamic symbols read with objdump -T / nm -D and demangled with c++filt; exported functions disassembled with capstone (helper scripts artifacts/re_static/sodis.py / ioctlscan.py); ioctl request numbers decoded from the movz/movk immediates and pinned to names by error-string cross-reference. Compiled Java not decompiled. No code executed.

Cross-refs:

The e-ink software stack (Onyx EPDC) (EPD/pen native libraries), E-ink driver internals (register-level, from the kernel) (kernel-side /dev/ebc ioctl set), Security and DRM userspace (keystore2-on-keymaster4.1, explained by the 13-on-11 split), Userspace boot — init, SELinux, A/B updates (A/B + OTA), Preinstalled apps (Boox, GMS, AOSP) (GMS/AOSP apps), Partition map and checksums (the onyxconfig partition), Absent and vestigial hardware (reference-base leftovers) (unavailable features).