10.5. 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 The e-ink software stack (Onyx EPDC) (userspace down to the driver interface) and E-ink driver internals (register-level, from the kernel) (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 The e-ink software stack (Onyx EPDC) (userspace glue, init.onyx.rc, the Onyx libraries) and E-ink driver internals (register-level, from the kernel) (the lfe5u/ onyx_tcon/onyx_epdc register-level internals, the LUT pipeline, the SDE-atomic-commit splice).

10.5.1. 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.

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.

10.5.2. Update modes and schemes

The named, special-cased paths (these correspond to the lfe5u_i2c DT capability flags):

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

10.5.3. 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.

10.5.3.1. 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.

10.5.3.2. 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:

Header

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 / 67 modes / 1314 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 (0x3a80x5fef9 < 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:

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 E-ink driver internals (register-level, from the kernel) 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.

10.5.4. 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:

The e-ink software stack (Onyx EPDC) (the userspace client of this interface — SurfaceFlinger/libgui, init.onyx.rc, the Onyx libraries), E-ink driver internals (register-level, from the kernel) (the register-level FPGA/TCON and EPDC internals that implement this interface), Display pipeline — DSI transport to the color e-ink TCON (DSI transport, panel geometry), Firmware-blob inventory (the payloads) (the built-in TCON/waveform/NVCM firmware inventory).