#
# SPDX-FileCopyrightText: 2026 The LineageOS Project
# SPDX-License-Identifier: Apache-2.0
#
LineageOS 18.1 for Biscuit (MT8163, 512 MB RAM, no display), built on the
maintained amazon-oss MT8163 common tree and
the arm64 Linux 4.9 kernel. This lineage-18.1 branch is the 4.9 port:
it boots Android 11 with Wi-Fi, Bluetooth, the speaker and the microphone.
lineage-18.1-3.18 is the same device tree on the 32-bit Fire OS 6 3.18
kernel.
Biscuit needs the amonet v2 unlock (native boot_a/b, system_a/b GPT and
TWRP in recovery). To keep a 3.18 build on slot A and this one on slot B,
use amonet v2.0.1 or newer: its kaeru boots the active slot, while v2.0.0's
boots boot_a whatever slot is active. On v2.0.0, install
amonet-biscuit-v2.0.1.zip from TWRP; it also wipes misc, which holds
the slot selection, so check adb shell bcbtool dump in the TWRP it
reboots into. tools/bootchain/ covers the
slot switch and flashing only a boot.img.
Add to .repo/local_manifests/biscuit.xml on top of a LineageOS 18.1 checkout:
<?xml version="1.0" encoding="UTF-8"?>
<manifest>
<remote name="amazon-oss" fetch="https://github.com/amazon-oss" revision="refs/heads/lineage-18.1" />
<remote name="biscuit" fetch="https://github.com/meiser79" revision="refs/heads/lineage-18.1" />
<remote name="mt8163" fetch="https://github.com/mt8163" />
<project path="device/amazon/biscuit" name="android_device_amazon_biscuit" remote="biscuit" />
<project path="kernel/amazon/biscuit" name="android_kernel_amazon_biscuit" remote="biscuit" />
<project path="vendor/amazon/biscuit" name="android_vendor_amazon_biscuit" remote="biscuit" />
<project path="device/amazon/mt8163-common" name="android_device_amazon_mt8163-common" remote="amazon-oss" />
<project path="vendor/amazon/mt8163-common" name="android_vendor_amazon_mt8163-common" remote="amazon-oss" />
<project path="hardware/amazon" name="android_hardware_amazon" remote="amazon-oss" />
<project path="hardware/mediatek/mt66xx" name="android_hardware_mediatek_mt66xx" remote="amazon-oss" />
<project path="patches" name="patches" remote="amazon-oss" />
</manifest>Then sync and apply the amazon-oss source patches, then Biscuit's own
(patches/<project path>/, applied with git am, skipping patches that are
already in):
repo sync -c --no-clone-bundle --no-tags
./patches/apply.sh
./device/amazon/biscuit/patches/apply.sh| Biscuit patch | Why |
|---|---|
external/openssh 0001-sshd-Tolerate-a-NULL-privsep-pw_passwd |
bionic returns the privsep user (shell) with a NULL pw_passwd; sshd crashed in strlen() on it at every start as root |
external/openssh 0002-start-ssh-Use-the-sshd_config-the-build-installs |
start-ssh ran sshd with /vendor/etc/ssh/sshd_config, but the config installs to /system/etc/ssh only |
external/openssh 0003-config.h-Enable-SHA-384-and-SHA-512 |
The prebuilt config.h lacks HAVE_EVP_SHA384/HAVE_EVP_SHA512, so every SHA-512 digest failed and sshd could not load its host keys ("Unable to shield host key") |
frameworks/av 0001-TimeCheck-Report-a-timeout-once-instead-of-wedging-a |
amazon-oss keeps audioserver alive on a TimeCheck timeout, but the expired check then fired every second with its lock held, so one binder call over 5 s left audioserver hung for good (no recording, a HAL tombstone every second); each timeout is now logged once and dropped |
frameworks/base 0001-PhoneWindowManager-Hand-claimed-volume-keys-to-Biscu |
Kiosk Satellite claims the volume buttons through BiscuitService to steer a Music Assistant group; while sys.biscuit.volume_claimed is 1, each press goes to BiscuitService instead of changing the volume |
frameworks/base 0002-PhoneWindowManager-Skip-the-torch-callback-without-a |
Lineage's power button torch registered a torch callback at boot, and without cameraserver CameraManager retried the camera service every second for it; the callback is now registered only when a camera flash is declared |
frameworks/opt/net/wifi 0001-ClientModeImpl-Keep-power-save-off-after-DHCP-while- |
After every DHCP exchange, lease renewals included, Android turned power save back on without asking WifiLockManager, so a WIFI_MODE_FULL_HIGH_PERF lock held for good (Kiosk Satellite's) lost its effect at the first renewal (measured: lock held, iw showed power save on) |
system/bt 0001-hci-Tolerate-a-rejected-extended-features-page |
The MT8163 controller rejects a classic feature page it reports (status 0x30); Android 11 aborts on it, Android 7 ignored it |
kernel/amazon/mt8163-4.9 0001-init-Drop-the-skip_initramfs-parameter |
For the other MT8163 devices, which build mt8163-common's kernel (Biscuit's kernel already has it): none boots with skip_initramfs, but the string makes Magisk take the device for legacy system-as-root and hex-patch the kernel image, which kept Biscuit's repacked 3.18 kernel from starting |
device/amazon/mt8163-common 0001-mt8163-releasetools-Let-Magisk-s-addon.d-patch-the-f |
For the other MT8163 devices (Biscuit's own releasetools already does it): the generic updater restores addon.d before it writes boot.img, so Magisk's background re-patch raced the boot write and could still be writing after the install reported success; boot.img now goes first and the updater waits for addon.d's job |
hardware/amazon 0001-audio-hal-Capture-mic-sources-on-Amazon-s-ASR-path |
For the other MT8163 devices (Biscuit's own HAL wrapper already does it): amazon-oss moved mic and camcorder captures to libasp's voice pipeline, which runs AEC, NR and AGC for calls but no beamformer; they now use the ASR path, which Fire OS uses for Alexa (AEC and beamformer over all calibrated mics, no NR or AGC). Checked on the Show 8 (PipelineAsr), the Spot and the Show 5. persist.vendor.amazon.mic_source picks another source (7 for the voice pipeline, -1 for the app's) |
device/amazon/mt8163-common 0002-mt8163-audio-Add-the-WebRTC-AGC-to-mic-captures |
For the other MT8163 devices (Biscuit's own audio_effects.xml already has it): the ASR path has no AGC, so AOSP's WebRTC AGC from libaudiopreprocessing now runs on mic and voice_recognition |
kernel/amazon/mt66xx-common 0001-wmt-Request-firmware-by-its-base-name |
Fire OS 6's wmt_launcher passes the connsys ROM patch as an absolute path, which request_firmware() cannot resolve below firmware_class.path; the driver retried forever, so connsys never came up (no wlan0, the Bluetooth HAL blocked on /dev/stpbt) |
system/netd 0001-MDnsSdListener-Report-start-service-OK-when-mdnsd-al |
libmdnssd starts mdnsd for native clients (Kiosk Satellite's sendspin), so NsdService's start-service found it running and logged a NativeDaemonFailureException ("402 Service already running") although discovery works |
Common blobs come from the karnak Fire OS 7 image listed in
device/amazon/mt8163-common/proprietary-files.txt. Biscuit's own blobs
(connsys loader/launcher and firmware) come from Biscuit's stock Fire OS
6.5.7.4 system image:
cd device/amazon/biscuit
./extract-files.sh --only-common <karnak system dump>
./extract-files.sh --only-target <biscuit 6.5.7.4 system dump>. build/envsetup.sh
breakfast lineage_biscuit-userdebug
mka baconOnly through amonet v2 TWRP sideload. The OTA writes nothing except TWRP's
/dev/block/current-system and /dev/block/current-boot (see
releasetools/releasetools.py, which also refuses incremental and two-step
packages and checks the active slot against amonet's BCB). Inspect the
updater script before every install, and install twice to fill both slots,
or once into slot B next to a 3.18 build on slot A
(tools/bootchain/README.md covers the
amonet update, the slot switch and boot-only flashing).
adb shell 'getprop ro.twrp.version; getprop ro.boot.slot_suffix'
adb shell twrp sideload
adb sideload lineage-18.1-*-biscuit.zipThe OTA writes boot.img before it restores addon.d scripts, so Magisk's
addon.d re-patches the new boot image, and then waits (at most 120 s) for
addon.d jobs that continue in the background, as Magisk's does: once the
install reports success, the boot image is final.
TWRP runs an OpenRecoveryScript, so a build that is already installed can
be updated from Android, over Wi-Fi or USB: tools/biscuit-ota.sh pushes
the zip over adb, checks its SHA-256 on the device, writes
install /sdcard/update.zip to /cache/recovery/openrecoveryscript and
reboots into recovery. TWRP installs it to the current slot, deletes the
zip (cmd rm) and reboots; the other slot keeps the previous build.
tools/biscuit-ota.sh 192.168.1.50 lineage-18.1-*-biscuit.zip # Wi-Fi
tools/biscuit-ota.sh usb lineage-18.1-*-biscuit.zip # USBWith several USB devices attached, SERIAL=<serial> picks one. If the Dot
is already in TWRP (USB only: TWRP has no Wi-Fi), the script installs the
zip right away with twrp install and reboots into Android; if TWRP
reports an error, the Dot stays in TWRP. -f (or FORCE=1) skips the
checks for a Biscuit and for amonet v2's TWRP, so the script also updates
other Echo devices whose TWRP runs an OpenRecoveryScript; the zip's updater
still decides whether it installs.
-r (or RESET=1) factory resets the Dot after the install, with TWRP's
wipe data: it wipes data and cache but keeps /sdcard. A failed install
stops TWRP's script, so nothing is wiped then. The reset also forgets the
Wi-Fi network, so afterwards connect USB and set it up again with
adb shell biscuit_service wifi connect '<ssid>' '<psk>'.
-s <a|b> (or SLOT=) installs into that slot instead, over Wi-Fi or
USB. The script prints the current slot and asks before reinstalling into
it. TWRP links /dev/block/current-boot and current-system to the slot
it booted on, so it has to boot on the wanted slot first:
- Over USB, the script sets the slot active with
bcbtoolin TWRP, reboots into recovery and checks the links before installing. If the install fails after a switch, it prints thebcbtool set_activecommand that returns to the old slot. - Over Wi-Fi, it queues two OpenRecoveryScripts: the first sets the slot
active and reboots into recovery, the second checks that TWRP runs on
that slot, installs and reboots. If the Dot stays in TWRP (cyan LED),
connect USB, read
/tmp/recovery.log, andadb shell bcbtool set_active <old slot>goes back.
- Kernel (
kernel/amazon/biscuit, branchlineage-18.1): the amazon-oss MT8163 4.9 kernel with Biscuit's device tree and the Fire OS drivers it needs (amz_priv, the TSL2540/TSL2584 light sensors, the IS31FL3236 LED ring), built by mt8163-common's Linaro GCC into an arm64Image.gz-dtb(mediatek/biscuit_evt). Stock LK forces 32-bit kernels; amonet v2's kaeru switches it to 64-bit for mt8163-common'sbootopt=64S3,32N2,64N2. The layout is mt8163-common's (kernel0x40080000, second0x40f00000, tags0x48000000) with the ramdisk at the stock0x44000000, inside 512 MB. Fire OS 6's LK reports 504 MiB, and the DT also reserves the top 8 MiB (0x5f800000), ATF/TEE secure memory the kernel hung on right afterpaging_init. Biscuit takes the codec MCLK from the camera MCLK and builds withoutCONFIG_SND_I2S_MCLK, so its audio node keeps mt8163.dtsi's clock list, and DL1 starts the AFE itself. The userspace stays 32-bit. The kernel forces its command line (the Fire OS LK passesroot=,dm=and a stale slot suffix), keeps only the LK serial number and boot reason, and exports amonet v2's active slot (misc+0x360) asandroidboot.slot_suffix, so first-stage init mounts the rightsystem_a/b. It writes its console to pstore (0x44410000, console zone at0x444bf000). - Radio: MT8163 connsys comes from the
mt66xxmodules (init.insmod.cfg); Biscuit's stockwmt_loader/wmt_launcherbring it up (the WMT driver requests the ROM patch by its base name, see the patch table), thenlibbt-vendor,libwifi-hal-mt66xxandlib_driver_cmd_mt66xxfromhardware/mediatek/mt66xxuse/dev/stpbtand/dev/wmtWifi. - USB: mt8163-common's configfs gadget, unchanged.
- Display: none.
hwcomposer.biscuitis a headless HWC 1.1 with one 1x1 primary display and software vsync at 1 Hz (debug.biscuit.hwc.size=WxHanddebug.biscuit.hwc.fps=1..60override them for debugging), wrapped by the composer 2.1 passthrough service. It claims every layer as an overlay and drops it, so SurfaceFlinger never composes and never needs a framebuffer target buffer (there is no fbdev to allocate it from). mt8163-common's LiveDisplay HAL is left out: without the MediaTek display driver it never registers, and its VINTF declaration madesystem_serverwait for it until Watchdog restarted it. The stock HWC is 1.0, whichHWC2On1Adapterrejects. Mali stays off on Biscuit, so GLES is SwiftShader on AOSP'sgralloc.default(ro.hardware.egl,ro.hardware.gralloc), as on CM14.gralloc.defaulthas no BLOB buffers, so Codec2 takes its linear buffers from ION (debug.stagefright.c2-poolmaskinvendor.prop, over mt8163-common's mask). - Recovery:
recoveryholds amonet's TWRP.init.target.rcbind-mounts/system/bin/trueoverinstall-recovery.sh, so Android never replaces it.
- Audio:
audio.primary.biscuitruns the Fire OS 6 (Android 7.1)audio.primary_amazon.mt8163.sounder the Android 11 HAL ABI and switches the TLV320DAC3203 speaker route and amplifier around playback. Missing legacy symbols come fromlibbiscuit_audio_shimand the amazon-oss shims (blob_fixupinextract-files.sh). The HAL also drives GPIO87; its 3.18 sysfs path is rewritten to/dev/biscuit_mt_gpio, which links to the 4.9 pinctrl node. Its Biscuit mixer setup comes from Fire OS's/system/etc/audio_device.xml(the four TI ADCs behind the seven microphones, mic PGA 20 dB, SPI framing, playback ramp); without it the microphones stay muted. Amazon's signal processing (libasp: beamforming, AEC, AGC) runs with its models from/system/vendor/etc/audio-algorithms. Its LIPC client (Amazon's IPC over D-Bus, which Fire OS runs and LineageOS does not) talks to a source-builtliblipcstub (lipc/): every call succeeds and no events arrive, as with the Echo Show's Fire OS 7libasp, which has no LIPC at all. Its playback path also applies Amazon's device volume (music_volTab), which Fire OS sets over LIPC; the HAL wrapper sets it to the 0 dB top (persist.vendor.biscuit.asp_volume, default 30, -1 to leave it alone), so Android's volume alone controls loudness. Microphone capture is opened in the vendor HAL asVOICE_RECOGNITION, libasp's ASR path as Fire OS uses it for Alexa: the seven calibrated microphones (/proc/idme/miccal.*) go through Amazon's AEC and beamformer, without NR or AGC (persist.vendor.biscuit.mic_source; 7 selects theVOICE_COMMUNICATIONcall path with AEC, NR and AGC but no beamformer). Android adds the AOSP/WebRTC AGC on top formicandvoice_recognition(configs/audio/audio_effects.xml), as CM14 did, so one AGC runs. - Bluetooth speaker: A2DP sink and AVRCP controller only; BiscuitService
answers pairing requests (PIN
0000or just-works) and connects A2DP sink after bonding. - Microphone privacy: as on Fire OS, the kernel owns it.
amz_privmutes GPIO87 (global GPIO 444) when the microphone button is released and unmutes it on the next press, even if Android is hung. Software can enter privacy (privacy_trigger) but never leave it: the HAL wrapper refuses an unmute whileprivacy_stateis 1, sobiscuit_service mic unmuteonly works after the button.biscuit-buttondfollowsprivacy_stateinto Android's microphone mute flag. Both live under/dev/biscuit_privacy(the keypad'samz_privacysysfs group). - Action button:
biscuit-buttondalso readsKEY_HELPfrom the keypad and sends every press and release, also while asleep, ascom.amazon.biscuit.service.ACTION_BUTTON(down,eventTimein uptime milliseconds) througham broadcast.ro.biscuit.action_broadcast=1advertises it, so Kiosk Satellite needs no accessibility service for the button. The key is not grabbed; Android still sees it. - Volume buttons: Android handles them as usual until an app claims them
through BiscuitService's
claimVolumeButtons(), the Kiosk Satellite Edition interface (ro.biscuit.volume_claim=1advertises it). While the app's binder token lives,sys.biscuit.volume_claimedis 1, theframeworks/basepatch sends each press to BiscuitService, and BiscuitService broadcasts it to that app ascom.amazon.biscuit.service.VOLUME_BUTTON(directionupordown) without changing the volume. Kiosk Satellite uses it to steer a Music Assistant group.biscuit_service volume up|downalways changes the Dot's own volume. - LED ring:
biscuit-ledddrives the IS31FL3236 ring, and BiscuitService mirrors the music volume on it. - Ambient light:
sensors.biscuit(sensors HAL 1.3) reports the TSL2584TSV through IIOilluminance0_inputor, on boards with a TSL2540, its i2c0 sysfsals_lux(powered throughals_power_state). - Memory: KSM merges identical pages (CM14's settings), and the boot
animation is off (
debug.sf.nobootanimation=1). - Debugging:
/dev/i2c-*(fori2c-poke),/sys/class/gpio, andtinymix/tinyplay/tinycap/tinypcminfoon userdebug builds. - CLI:
/system/bin/biscuit_service(volume,mute/mic,countdown,bt pair|off,wifi on|off|connect), unchanged from CM14. - Wi-Fi runs as STA only; the connsys WMT/PSM/BTIF threads run SCHED_FIFO.
- Apps:
BiscuitEmptyLauncheris a black HOME activity (Android needs one to finish booting). Itsoverridesdrop the launchers, Settings, LineageOS's settings screens (LineageParts),SimpleDeviceConfig, the setup wizard, updater, user apps, keyboard, wallpapers, themes and printing from the image, and the services a speaker without a secure element or a screen does not use: SecureElement, the ClearKey and CAS HALs (Widevine stays), Perfetto'straced, and the storage, VPN, proxy, captive portal, certificate and companion device apps. - Disabled services:
securetime, as on rook: the TEE rejects its calls every 5 seconds, and Widevine L3 does not need it.cameraserver: Biscuit has no camera, so the image also leaves out mt8163-common's camera HAL (camerahalserverand its 57 files, 14 MB), the libraries built for it and the camera feature files. - Features:
biscuit_unavailable_features.xmltakes back what a headless speaker lacks (touchscreen, motion sensors, location, Ethernet, USB host, WebView, SIP, NFC), so system_server and the lineage-sdk skip the printing, MIDI, backup, app widget, companion device, autofill, Ethernet, LiveDisplay, profile, power menu and trust services.
Verified on the host:
- Host tests pass for the bootargs allowlist, amonet slot parsing, the OTA
write-target checks, the headless HWC (Android 11 headers),
biscuit-buttond(privacy state and Action button events) andsensors.biscuit(both light sensor sources), with ASan/UBSan. - The audio wrapper compiles for arm32 against Android 11 headers with its struct-layout asserts; the shim exports exactly the symbols the blobs import; BiscuitService compiles against the Android 11 API signatures it uses.
extract-files.sh --only-targetextracts all 48 blobs and applies every fixup.
Verified on the device (2026-10-20, slot B, 4.9.337): Android 11 boots;
Wi-Fi and Bluetooth come up, and a phone streams to the Dot as an A2DP sink;
playback reaches the speaker at 48 kHz; the microphone array captures (PCM 22,
9 channels at 16 kHz) and a voice satellite's wake word triggers; the light
sensor reads plausible values (about 2500 lx at a sunny window); the LED ring
plays its animations and shows the volume; zram swaps with lz4 (about 2.7:1)
and KSM merges pages; with all four cores loaded at 1.3 GHz for 2.5 minutes
the CPU settles at about 57 °C, well below the first trip point at 84 °C;
Widevine runs at L3 (system ID 4445), provisions and gets a license from
Google's test server. The kernel's console survives a reboot in pstore
(/sys/fs/pstore/console-ramoops).
Open work is tracked in TODO.md; the history and decision record is the port study.
The first 4.9 images seemed not to get past LK. The kernel did start (kaeru
switches LK to 64-bit; the kernel enters at EL2 with a valid DTB at
0x48000000); pstore breadcrumbs then showed where it stopped:
| Problem | Fix (android_kernel_amazon_biscuit, lineage-18.1) |
|---|---|
Hang right after paging_init |
Reserve the top 8 MiB below the 504 MiB Fire OS 6's LK reports (0x5f800000, ATF/TEE secure memory) |
USB oops on a VBUS ERR_PTR |
Build usb20 with OTG_WITHOUT_VBUS_CHECK for Biscuit |
| SMI larb 2 NULL dereference | Keep imgsys enabled; it is larb 2's clock controller |
| Sound card did not register | Use the TI DAC links (as on rook) |
| Audio routed to the headset | Read the jack GPIO as on rook |
| Playback stalled, every write timed out | Keep mt8163.dtsi's audio clocks (Biscuit builds without CONFIG_SND_I2S_MCLK, so rook's list left every clock unprepared), and start the AFE with DL1 |
In this tree: the mt66xx-common patch that requests the connsys ROM patch by
its base name (Wi-Fi and Bluetooth) and the frameworks/av TimeCheck patch.
If this port is useful to you, you can support the work on Ko-fi.