Skip to content

Spin42/ex_kobo_eink

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KoboEink

Elixir library that initializes the e-ink display on Kobo e-readers running Nerves, so that fbink_nif can drive the screen.

Kobo devices (tested on the Clara Colour, MT8113 SoC) ship with proprietary userspace daemons that must be running before the kernel's HWTCON framebuffer is fully operational. Because these binaries live on the stock Kobo root partition and cannot be redistributed, this library extracts them at first boot (via BlobCopy) and starts the required services under OTP supervision.

What it does

  1. Extracts proprietary binaries from the stock Kobo root partition (/dev/mmcblk0p10) on first boot:

    Binary Role
    mdpd MediaTek Display Processing Daemon
    nvram_daemon NVRAM calibration/config service
    libnvram.so, libnvram_custom.so NVRAM access libraries

    A marker file prevents re-extraction on subsequent boots.

  2. Starts display services in order:

    • Mounts the init_bin partition (/dev/mmcblk0p8 -> /data/init_bin) which contains the waveform LUT and CFA LUT files needed by the hwtcon kernel driver
    • Creates REGAL waveform device nodes in /dev
    • Starts mdpd (supervised via MuonTrap.Daemon)
    • Starts nvram_daemon (supervised via MuonTrap.Daemon)
  3. Broadcasts events so your application knows exactly when /dev/fb0 is ready for FBInk.

Installation

Add kobo_eink to your dependencies in mix.exs:

def deps do
  [
    {:kobo_eink, github: "Spin42/ex_kobo_eink"}
  ]
end

kobo_eink pulls in blob_copy and muontrap automatically.

Usage

The application starts automatically. Subscribe to get notified when the display is ready:

defmodule MyApp.Display do
  use GenServer

  def start_link(_), do: GenServer.start_link(__MODULE__, nil, name: __MODULE__)

  @impl true
  def init(nil) do
    KoboEink.subscribe()
    {:ok, :waiting}
  end

  @impl true
  def handle_info({:kobo_eink, :ready}, _state) do
    {:ok, fd} = FBInk.open()
    FBInk.init(fd, %FBInk.Config{})
    FBInk.print(fd, "Hello from Nerves!", %FBInk.Config{})
    {:noreply, {:ready, fd}}
  end

  def handle_info({:kobo_eink, {:error, reason}}, _state) do
    require Logger
    Logger.error("E-ink init failed: #{inspect(reason)}")
    {:noreply, {:error, reason}}
  end

  def handle_info({:kobo_eink, phase}, state) do
    require Logger
    Logger.info("E-ink init phase: #{phase}")
    {:noreply, state}
  end
end

Events

Subscribers receive {:kobo_eink, event} messages:

Event Meaning
:copying_firmware Extracting binaries from stock partition
:firmware_copied Extraction complete
:starting_services Starting mdpd, nvram_daemon
:services_started All services running
:ready /dev/fb0 is ready for fbink_nif
{:error, reason} Initialization failed

If you subscribe after initialization has already completed (or failed), you immediately receive the current terminal state.

One-off status check

KoboEink.status()
# :ready | :initializing | :copying_firmware | :starting_services | {:error, reason}

Stopping services

KoboEink.stop_services()

Configuration

All values have sensible defaults for the Kobo Clara Colour. Override in your config.exs if your device differs:

config :kobo_eink,
  # Partition containing waveform/CFA LUT files for the hwtcon driver
  init_bin_partition: "/dev/mmcblk0p8",
  init_bin_mount: "/data/init_bin",

  # BlobCopy configuration — first-boot extraction of proprietary binaries
  blob_copy: [
    partition: "/dev/mmcblk0p10",
    mount_point: "/tmp/kobo-eink-mount",
    marker_file: "/var/lib/kobo-eink-copied",
    log_prefix: "KoboEink",
    partition_wait_timeout: 30_000,
    mount_retries: 10,
    mount_retry_delay: 1_000,
    manifest: [
      %{source: "usr/bin/mdpd", dest: "/usr/bin/mdpd", permissions: 0o755, critical: true},
      %{source: "sbin/nvram_daemon", dest: "/sbin/nvram_daemon", permissions: 0o755},
      %{source: "lib/libnvram.so", dest: "/lib/libnvram.so", permissions: 0o755},
      %{source: "lib/libnvram_custom.so", dest: "/lib/libnvram_custom.so", permissions: 0o755}
    ]
  ]

See the BlobCopy.Config module for a full description of each option.

How it works

This library is the Elixir/Nerves equivalent of the S20kobo-eink init script from buildroot-kobo. The Kobo Clara Colour uses a MediaTek MT8113 SoC with a kernel-level HWTCON driver that provides /dev/fb0. However, the framebuffer isn't usable until several proprietary userspace daemons are running.

Because the device uses Secure Boot, the proprietary daemons can't be freely redistributed. Instead, they're extracted at first boot from the stock Kobo root filesystem that still lives on the device's eMMC. This extraction is handled by the BlobCopy library.

Daemons are started as MuonTrap.Daemon processes under KoboEink.DaemonSupervisor (a DynamicSupervisor), giving proper OTP supervision, automatic restart on crash, and clean shutdown without manual PID tracking.

The initialization sequence:

Kernel (HWTCON + PMIC drivers built-in)
  |
  +-- /dev/fb0 exists (waveform loaded lazily on first update)
  |
KoboEink.Init GenServer starts
  |
  +-- [first boot] BlobCopy: mount /dev/mmcblk0p10, extract binaries
  |
  +-- Mount /dev/mmcblk0p8 -> /data/init_bin  (waveform + CFA LUTs)
  +-- REGAL waveform device node setup
  +-- mdpd -f                  (MuonTrap.Daemon)
  +-- nvram_daemon              (MuonTrap.Daemon)
  |
  +-- :ready -- /dev/fb0 is now usable by FBInk/fbink_nif

License

MIT License. See LICENSE for details.

About

Kobo eink initialization for Nerves

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages