===================================================================== The /dev/ebc interface: ioctls, update modes, and the waveform format ===================================================================== This page is the single copy of the ``/dev/ebc`` control-surface contract that :doc:`stack` (userspace down to the driver interface) and :doc:`tcon` (register-level driver internals) both need: the ioctl command set, the update-mode/scheme taxonomy, and the ``.wbf`` waveform file format every mode indexes into. Everything below is a fact about the *interface*, not about who calls it or how the FPGA executes it — for that, see :doc:`stack` (userspace glue, ``init.onyx.rc``, the Onyx libraries) and :doc:`tcon` (the ``lfe5u``/ ``onyx_tcon``/``onyx_epdc`` register-level internals, the LUT pipeline, the SDE-atomic-commit splice). The ``/dev/ebc`` ioctl surface ============================== The handler is ``epdc_ioctl`` (kernel file offset ``@0x57a988``; a ~15 KB switch) with a legacy ``ebc_ioctl`` alongside. Dispatch is on the ioctl **sequence number** via a jump table (not a flat compare chain), so the numeric ``_IOC`` encodings are not literal constants in the switch; one number is pinned from the userspace side — the Onyx-patched SurfaceFlinger issues ``ioctl(/dev/ebc, SET_EBC_SEND_UPDATE = 0x700c, …)``, in the same ``0x70xx`` rockchip-style "ebc" range the Onyx software EPDC reuses. .. list-table:: :header-rows: 1 :widths: 42 58 * - ioctl - Purpose * - ``GET_EBC_BUFFER_INFO`` / ``GET_EBC_BUFFER`` - query framebuffer geometry / obtain a drawing buffer * - ``SET_EBC_SEND_BUFFER`` - submit a filled frame (with byte offset) * - ``SET_EBC_SEND_UPDATE`` (``0x700c``) - update a rectangle, tagged ``update_marker`` * - ``SET_EBC_WAIT_ALL_UPDATE_COMPLETE`` / ``SET_EBC_CLEAR_ALL_UPDATE`` - fence on / cancel outstanding updates * - ``SET_EBC_UPDATE_SCHEME`` / ``SET_EBC_UPD_LIST_SIZE`` - queue scheme; queue depth + power-down delay * - ``SET_EBC_FORCE_WAVEFORM`` / ``SET_EBC_LUT_ENABLE`` / ``SET_EBC_GAMMA_TAB`` - waveform override, LUT enable, gamma table * - ``SET_EBC_EXTBUF_SYNC_FB_ENABLE`` - external-buffer → framebuffer sync * - ``SET_EBC_CAPTURE_ALL_START`` / ``…STOP`` / ``GET_EBC_CAPTURE_ALL_BUFFER`` / ``GET_EBC_CAPTURE_ALL_NAME`` - screen-capture path * - ``GET_EBC_DRIVER_SN`` - driver serial / version The update-request struct carries ``{waveform_mode, update_mode, update_marker, flags, temp}`` over a rectangle ``[l, t, w, h]``. The other EBC ioctls SurfaceFlinger issues sit in the same ``0x70xx`` family (e.g. ``0x701d``) — plain ``0x7000``-based magic numbers, not a documented public ABI. Update modes and schemes ========================= The named, special-cased paths (these correspond to the ``lfe5u_i2c`` DT capability flags): .. list-table:: :header-rows: 1 :widths: 24 76 * - Mode / scheme - Evidence * - **A2** (fast) - ``a2_enhance_default_7t`` vs ``…_4t`` (two frame-count variants), ``a2_clean_mode`` / ``du_a2_clean_enable``; ``onyx_a2_last_submit_work_func``; DT ``epdc-a2-enhance-enable``; FPGA ``CMD_TCON_A2_*`` * - **GU / REGAL** - ``gu_regal`` / ``regal_nm_enable`` (night-mode REGAL) / ``onyx_waveform_mode_swap_regal_and_regal_plus``; DT ``epdc-gu-regal-enable`` / ``epdc-regal_nm-enable`` * - **CFA (colour)** - ``cfa_mode`` (DT ``cfa_mode=1``) — the colour-filter-array path * - **handwrite** - ``EPDC_FLAG_HANDWRITE`` / ``UPDATE_SCHEME_HANDWRITE`` — the pen fast path into the FPGA transport's dedicated handwriting buffer * - **dither** - ``dither_pix_level`` / ``dither_debounce_delta`` — error-diffusion dithering, also exposed in userspace as ``libonyx_neo_dither.so`` * - **merge / snapshot** - ``merge_mode`` (coalesce queued updates), ``update_snapshot`` (capture-consistent update); ``vcom_workaround`` toggles a panel VCOM fix The waveform file format (``.wbf``) ==================================== The ``.wbf`` blob is ``eink_waveform.wbf`` — an E-Ink Corp ``.wbf`` waveform blob, **393,123 bytes**, present as a file at ``/system/waveform/ eink_waveform.wbf`` *and* compiled into the kernel image through ``CONFIG_EXTRA_FIRMWARE``. It carries an embedded FPL / panel identifier string, ``470_VSF006_HM7501_EC103KH1C1_SC1452-KCC_TC`` (the 10.3″ colour E-Ink FPL for this device), loaded from ``/vendor/waveform`` or ``/system/waveform`` or the built-in firmware. Header fields ------------- Parsed on the way to panel init (``onyx_waveform_parse_and_init_panel``); the header field-copy code (kernel file offset ``@0x593038``) reads fields from the raw header into its internal descriptor and logs them in two groups: - ``file_size``, ``fpl_material_version``, **``waveform_mode_version`` at header offset ``0x10``**, **``WFM_REV`` at offset ``0x16``**, ``waveform_version``; - ``frame_rate`` (Hz), ``waveform_lookup_table_fmt``, a 24-bit ``waveform_mode_table_address``, and the three table dimensions **``mode_num`` × ``temp_num`` × ``gray_num``** — one LUT per (mode, temperature-band, gray-level). ``onyx_waveform_mode_transform_init`` maps framework mode names to the blob's numeric indices; the ``.wbf`` itself carries only the numbers. Decoded structure ------------------ Parsing the actual ``eink_waveform.wbf`` (393,123 bytes) against that header map — it is the standard E-Ink/i.MX waveform binary — yields: .. list-table:: Header :header-rows: 1 :widths: 34 66 * - Field - Value * - ``checksum`` / ``filesize`` / ``serial`` - ``0xbccd23d8`` / ``393123`` / ``9696`` * - ``mode_version`` (0x10) / ``wfm_version`` (0x11) / ``wfm_type`` (0x13) - ``22`` / ``117`` / ``0x90`` * - ``mfg_code`` (0x15) / ``wfm_rev`` (0x16) - ``34`` / ``0`` * - ``xwia`` (extra-WF-info addr, 0x1c) / ``wmta`` (mode-table addr, 0x20) - ``0x40`` / ``0x6c`` * - ``luts`` (0x24) / ``mc`` (0x25) / ``trc`` (0x26) - ``4`` / ``6`` → **7 modes** / ``13`` → **14 temperature ranges** **Temperature bands** — the table at file offset ``0x30`` gives the band boundaries in °C: ``0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 38, 43, 48, 71`` — 14 ranges spanning **0–71 °C** (finer near room temperature). Userspace ``temp`` selects the band. **Mode / temperature / LUT tree** — the blob is a three-level index. ``wmta`` (``0x6c``) holds **7** mode entries, each **4 bytes** = a 24-bit offset + a 1-byte checksum (the checksum is the **byte sum** ``(b0+b1+b2) & 0xff``, not an XOR — verified for every entry). The 7 mode pointers point to per-mode temperature tables at ``0x88``, ``0xf8``, ``0x168``, ``0x1d8``, ``0x248``, ``0x2b8``, ``0x328`` (stride ``0x70``); each of those is **14** entries of the same ``[u24 addr][u8 sum]`` form, pointing to the actual waveform LUT for that (mode, temperature band). All 98 (7×14) cells resolve inside the file (``0x3a8``–``0x5fef9`` < ``0x5ffa3``); **92 are distinct** — six cells reuse a neighbour's pointer, i.e. two adjacent bands share one waveform. **Temperature compensation is visible directly in the LUT sizes.** Taking the raw byte-span between consecutive LUT pointers (no decoding needed), within every mode the span shrinks **monotonically as the panel warms** — cold e-ink needs the longer, more-frame waveforms: .. code-block:: text mode\band 0° 3° 6° 9° 12° 15° 18° 21° 24° 27° 30° 33° 38° 43° m0 212 184 164 144 128 216 192 168 152 136 136 128 120 112 m1 722 638 562 484 407 347 302 258 228 188 186 180 169 128 m2 12781 10820 9669 8446 7532 6250 5441 4804 4350 4156 4263 4191 4207 3925 m3 13012 11021 9824 8579 7653 6360 5527 4874 4400 4213 4322 4257 4276 3994 m4 15445 12889 11289 9694 8639 7571 6389 5050 4610 4448 4260 3964 4097 3804 m5 14880 12390 10881 9321 8329 7311 6174 4901 4481 4319 4126 3866 3999 3720 m6 206 190 182 158 134 110 102 86 · (bands 8–13 degenerate) Modes 2–5 are the multi-kilobyte grayscale / REGAL waveforms (~13 KB cold → ~4 KB hot); mode 0 and mode 6 are the small fast modes (~100–220 bytes). Mode 6 is defined only for the **8 cold bands** (0–21 °C); its bands 8–13 all collapse to a single trailing offset (``0x5fef9``, a degenerate LUT) — that fast mode is simply not used above ~24 °C. **LUT payload encoding** — the container above is exact; the inner payload is the FPL-specific part and is only partially decoded. The small modes (0, 6) carry no ``0xfc`` byte and read as literal packed transition bytes with ``0xff`` separators (e.g. mode 0/temp 0 = ``55 ff 55 ff … aa ff 00 ff`` + a short trailer; ``0x55`` = ``01 01 01 01``, four 2-bit transition codes per byte). The large modes (2–5) are RLE-compressed with **``0xfc`` (the header ``sb`` byte) as the run escape**: a toggle-style decode (``0xfc`` flips run-mode; a value byte is then followed by a repeat count) consumes every LUT buffer exactly to its end pointer, confirming ``0xfc`` is the control byte — but the expanded lengths do **not** factor into a single fixed frame size (per-mode GCD = 1), so no ``frames × framesize`` grid is asserted. The exact expansion and the 2-bit-code → drive voltage mapping are completed at runtime by ``onyx_get_waveform_one_frame_segment_16bit`` (``@0x59e0b0``) and ``_onyx_epdc_produce_wf_sg`` (gray handling is 4-bit / 16-level). The 7 modes are the numeric ``waveform_mode`` indices userspace passes in the ``ebc`` update struct, mapped through ``onyx_waveform_mode_transform_init`` (above). Full parse in ``artifacts/re_static/wbf_format.txt`` (and ``artifacts/re_eink/wbf_parse.txt``). The TCON firmware and panel configs the FPGA needs are *also* built into the kernel via ``CONFIG_EXTRA_FIRMWARE`` (they are not files in any partition): ``lfcpnx/lfcpnx100_tcon_fw_{9f,9e,99,a2,a5,a7}.bin`` (six LF-CPNX FPGA TCON firmware variants, selected by panel hardware revision) and ``mxo/mxo1300_nvcm_{81,82,83,84,86,87}.ied`` + ``mxo/mxo4300_nvcm_{81,82}.ied`` (panel NVCM init-data variants; see :doc:`/display/tcon` for the exact bundled set). Because the waveform + TCON firmware live inside the boot image's kernel, a replacement bootloader/kernel would have to carry (or re-request) them; they are the one part of the e-ink path that is effectively *below* userspace even though the driver that consumes them is an ordinary built-in kernel driver. Provenance ========== :Source: ``artifacts/kernel`` (built-in ``onyx_epdc`` driver strings: the ``epdc_ioctl`` switch, update-parm struct, waveform-header parse, ``a2_enhance`` / ``gu_regal`` / ``cfa_mode`` / dither / handwrite/merge tokens), ``artifacts/boot_a/kernel_config.txt`` (``CONFIG_ONYX_EPDC_*`` / ``CONFIG_EXTRA_FIRMWARE``), ``system_a.img``'s ``waveform/eink_waveform.wbf`` (parsed against the recovered header map — full walk in ``artifacts/re_eink/wbf_parse.txt``). :Method: strings + targeted static disassembly (base-free ADRP/ADD cross-referencer + capstone; function starts located via ``__func__`` log-string cross-reference). No code executed; nothing run on a device. :Cross-refs: :doc:`stack` (the userspace client of this interface — SurfaceFlinger/``libgui``, ``init.onyx.rc``, the Onyx libraries), :doc:`tcon` (the register-level FPGA/TCON and EPDC internals that implement this interface), :doc:`/display/panel` (DSI transport, panel geometry), :doc:`/userspace/firmware-blobs` (the built-in TCON/waveform/NVCM firmware inventory).