Transform workspace into OpenKE Stock USB Installer generator (v1.0.0)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Hardware Partition Layout & Dual-Slot A/B Boot Model
|
||||
|
||||
This document details the eMMC physical partition layout, the hardware A/B slot mechanics, and the persistent storage architecture used by Creality X2000 devices (Ender-3 V3 KE and Nebula Pad).
|
||||
|
||||
---
|
||||
|
||||
## 1. Physical Partition Table
|
||||
|
||||
The onboard eMMC is partitioned with a standard GPT partition table:
|
||||
|
||||
| Partition | Label | Fixed Capacity | Mount / Role | Active Slot 1 (Stock) | Active Slot 2 (OpenKE) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| `/dev/mmcblk0p1` | `ota` | **1 MiB** | Boot marker storage | Stores `ota:kernel` or `ota:kernel2` | Shared |
|
||||
| `/dev/mmcblk0p2` | `sn_mac` | **1 MiB** | Factory serial & MAC addresses | Read-only identity | Shared |
|
||||
| `/dev/mmcblk0p3` | `rtos` | **4 MiB** | Aux RTOS firmware (Slot 1) | Active | Inactive |
|
||||
| `/dev/mmcblk0p4` | `rtos2` | **4 MiB** | Aux RTOS firmware (Slot 2) | Inactive | Active |
|
||||
| `/dev/mmcblk0p5` | `kernel` | **8 MiB** | Linux uImage (Slot 1) | Active | Inactive |
|
||||
| `/dev/mmcblk0p6` | `kernel2` | **8 MiB** | Linux uImage (Slot 2) | Inactive | Active |
|
||||
| `/dev/mmcblk0p7` | `rootfs` | **500 MiB** | SquashFS rootfs (Slot 1) | Active | Inactive |
|
||||
| `/dev/mmcblk0p8` | `rootfs2` | **500 MiB** | SquashFS rootfs (Slot 2) | Inactive | Active |
|
||||
| `/dev/mmcblk0p9` | `rootfs_data` | **300 MiB** | Ext4 writable overlay | `/overlay` | `/overlay` |
|
||||
| `/dev/mmcblk0p10` | `userdata` | **~6.1 GiB** | Ext4 persistent storage | Mounted at `/usr/data` | Mounted at `/usr/data` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Boot Selection via the OTA Marker
|
||||
|
||||
The bootloader (U-Boot) reads the 1 MiB `ota` partition (`/dev/mmcblk0p1`) on power-up to decide which kernel and rootfs to mount. The partition contains a simple ASCII string:
|
||||
|
||||
* `ota:kernel` $\rightarrow$ Boots **Slot 1** (Stock CrealityOS: kernel `p5`, rootfs `p7`).
|
||||
* `ota:kernel2` $\rightarrow$ Boots **Slot 2** (Custom / OpenKE: kernel `p6`, rootfs `p8`).
|
||||
|
||||
### How Stock OTA Updater Flashes to Slot 2
|
||||
|
||||
When you run a USB update from the stock Creality touchscreen:
|
||||
1. Stock firmware queries which slot is currently running (`local_get_kernel_dev_path`).
|
||||
2. Since the printer is running on Slot 1 (`ota:kernel`), the updater targets the **inactive** slot:
|
||||
- Flashes `xImage` to `kernel2` (`mmcblk0p6`).
|
||||
- Flashes `rootfs.squashfs` to `rootfs2` (`mmcblk0p8`).
|
||||
- Flashes `zero.bin` to `rtos2` (`mmcblk0p4`).
|
||||
3. Calls `local_set_next_boot_device`, which writes `ota:kernel2` into `mmcblk0p1`.
|
||||
4. Reboots into OpenKE.
|
||||
|
||||
> [!NOTE]
|
||||
> Stock firmware in Slot 1 (`mmcblk0p5` and `mmcblk0p7`) is **never overwritten** by this process.
|
||||
|
||||
---
|
||||
|
||||
## 3. Persistent Data Isolation (`/usr/data`)
|
||||
|
||||
`/dev/mmcblk0p10` is the single shared ~6.1 GiB persistent partition mounted at `/usr/data`. Both Slot 1 and Slot 2 share this partition, but they use strict directory separation:
|
||||
|
||||
```
|
||||
/usr/data/
|
||||
├── creality/ # Stock CrealityOS files (gcode, logs, userdata, factory data)
|
||||
│ ├── userdata/
|
||||
│ └── printer_data/
|
||||
└── openke/ # OpenKE isolated runtime
|
||||
├── apps/ # Mutable checkouts (klipper, moonraker, mainsail)
|
||||
├── envs/ # Python virtual environments
|
||||
├── system/ # Boot logs, activation state, machine configs
|
||||
└── printer_data/ # Klipper config (printer.cfg), macros, Moonraker DB
|
||||
```
|
||||
|
||||
Because OpenKE namespaces all its data under `/usr/data/openke/`, flashing OpenKE does not erase or corrupt stock print history, WiFi credentials, or factory calibration files.
|
||||
|
||||
---
|
||||
|
||||
## 4. Reverting to Stock Firmware
|
||||
|
||||
If you ever wish to return to stock CrealityOS, you do not need to reflash. You simply toggle the boot marker back to Slot 1:
|
||||
|
||||
### From OpenKE Shell (SSH)
|
||||
```bash
|
||||
echo -n "ota:kernel" > /dev/mmcblk0p1
|
||||
sync
|
||||
reboot
|
||||
```
|
||||
|
||||
### From Stock CrealityOS (if toggling forward again)
|
||||
```bash
|
||||
. /etc/ota_bin/ota_utils.sh
|
||||
. /etc/ota_bin/ota_local_method.sh
|
||||
local_set_next_boot_device
|
||||
reboot
|
||||
```
|
||||
@@ -0,0 +1,168 @@
|
||||
# CrealityOS OTA Packaging & Update Subsystem Specification
|
||||
|
||||
This document provides a technical specification of Creality's Over-The-Air (OTA) firmware update container format, encryption model, payload chunking, and the on-device update execution engine used across the **Ender-3 V3 KE** (board target `F005`) and the **Nebula Smart Kit / Nebula Pad** (board target `NEBULA`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview & Security Model
|
||||
|
||||
Creality's local (USB) and network OTA update mechanism on Ingenic X2000 devices is **integrity-checked but unauthenticated**:
|
||||
|
||||
* **Archive Envelope**: A password-protected 7z archive with encrypted headers (`-mhe=on`).
|
||||
* **Encryption Key**: Derived deterministically from the hardware board name using standard Unix MD5 crypt.
|
||||
* **Payload Verification**: Each sub-payload (`xImage`, `rootfs.squashfs`, `zero.bin`) is checked using MD5 checksums.
|
||||
* **No Cryptographic Signatures**: The device does **not** enforce RSA digital signatures or public key verification during OTA updates. Unsigned custom kernels and root filesystems flash and boot cleanly through stock firmware.
|
||||
|
||||
---
|
||||
|
||||
## 2. Key Derivation Formula
|
||||
|
||||
The 7z archive password is generated per board type using standard Unix MD5 password encryption (`$1$` format) with a hardcoded salt:
|
||||
|
||||
$$\text{Password} = \text{MD5-Crypt}(\text{Key} = \text{BOARD\_NAME} + \text{"C3\_7e\_bz"}, \text{Salt} = \text{"cxswfile"})$$
|
||||
|
||||
In shell (using `openssl`):
|
||||
```bash
|
||||
openssl passwd -1 -salt cxswfile "${BOARD_NAME}C3_7e_bz"
|
||||
```
|
||||
|
||||
### Known Board Encryption Keys
|
||||
|
||||
| Target Device | Board Identifier | Input String | Resulting 7z Password |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Ender-3 V3 KE** | `F005` | `F005C3_7e_bz` | `$1$cxswfile$ZFd0RWFYkJQugbtKVGL9y0` |
|
||||
| **Nebula Pad** | `NEBULA` | `NEBULAC3_7e_bz` | `$1$cxswfile$XG7ANbYX3bq2H2xmmwrka.` |
|
||||
| **Creality K1** | `CR4CU220812S12` | `CR4CU220812S12C3_7e_bz` | Board-derived key |
|
||||
|
||||
> [!TIP]
|
||||
> `tools/pack_openke.py` includes a pure-Python implementation of Unix MD5-crypt, making password derivation completely cross-platform without external tools.
|
||||
|
||||
---
|
||||
|
||||
## 3. Archive Structure & Layout
|
||||
|
||||
Creality's update extractor (`/etc/ota_bin/local_ota_update.sh`) has strict structural expectations.
|
||||
|
||||
### Directory Hierarchy
|
||||
|
||||
For an archive file named `<ARCHIVE_STEM>.img` with package version `<VERSION>` (e.g. `NEBULA_ota_img_V1.1.0.34.img` with version `1.1.0.34`):
|
||||
|
||||
```
|
||||
<ARCHIVE_STEM>/ # Top-level directory inside 7z
|
||||
├── ota_config.in # Root version pointer
|
||||
└── ota_v<VERSION>/ # Versioned payload folder
|
||||
├── ota_v<VERSION>.ok # Readiness marker (touch file)
|
||||
├── ota_update.in # Primary payload manifest
|
||||
├── ota_md5_xImage.<FULL_MD5> # Chunk MD5 sidecar for kernel
|
||||
├── ota_md5_rootfs.squashfs.<FULL_MD5> # Chunk MD5 sidecar for rootfs
|
||||
├── ota_md5_zero.bin.<FULL_MD5> # Chunk MD5 sidecar for RTOS
|
||||
├── xImage.0000.<FULL_MD5> # 1 MiB chunk 0
|
||||
├── xImage.0001.<CHUNK0_MD5> # 1 MiB chunk 1
|
||||
├── ...
|
||||
├── rootfs.squashfs.0000.<FULL_MD5>
|
||||
├── ...
|
||||
└── zero.bin.0000.<FULL_MD5>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Manifest Formats
|
||||
|
||||
### 1. `ota_config.in`
|
||||
Located at the root of the extracted folder:
|
||||
```ini
|
||||
current_version=1.1.0.34
|
||||
```
|
||||
|
||||
### 2. `ota_v<VERSION>.ok`
|
||||
An empty 0-byte or 1-byte newline file placed in `ota_v<VERSION>/`. If missing, the updater aborts.
|
||||
|
||||
### 3. `ota_update.in`
|
||||
Lists each payload with its partition type, internal filename, byte size, and full-file MD5:
|
||||
```ini
|
||||
ota_version=1.1.0.34
|
||||
|
||||
img_type=kernel
|
||||
img_name=xImage
|
||||
img_size=5541952
|
||||
img_md5=f3bf93ad2fd1e770dacb3062ba9cf05c
|
||||
|
||||
img_type=rootfs
|
||||
img_name=rootfs.squashfs
|
||||
img_size=346923008
|
||||
img_md5=7d0d3742dedd7a236632e4f7439f52dd
|
||||
|
||||
img_type=rtos
|
||||
img_name=zero.bin
|
||||
img_size=424264
|
||||
img_md5=8e852ae9b8f5b78f3818e4236c3e8f1d
|
||||
```
|
||||
|
||||
### 4. Sidecar Checksum Files (`ota_md5_<NAME>.<FULL_MD5>`)
|
||||
Text files containing the MD5 checksum of each 1 MiB chunk in order, separated by newlines:
|
||||
```
|
||||
e10adc3949ba59abbe56e057f20f883e
|
||||
c33367701511b4f6020ec61ded352059
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Payload Slicing & Chunking Algorithm
|
||||
|
||||
Creality splits large image files into fixed **1 MiB** (1,048,576 bytes) chunks to manage RAM constraints on the 208MB-RAM device during download and flashing:
|
||||
|
||||
1. **Chunk Naming Convention**:
|
||||
$$\text{chunk\_filename} = \text{base\_name} + \text{"."} + \text{index:04d} + \text{"."} + \text{prev\_md5}$$
|
||||
2. **First Chunk (`index = 0000`)**:
|
||||
`prev_md5` is set to the **FULL MD5** of the unchunked source file.
|
||||
3. **Subsequent Chunks (`index > 0000`)**:
|
||||
`prev_md5` is the MD5 checksum of the **immediately preceding chunk** (`index - 1`).
|
||||
4. **Final Chunk**:
|
||||
Carries the remaining bytes ($\le 1,048,576$ bytes).
|
||||
|
||||
---
|
||||
|
||||
## 6. On-Device Update Execution Flow
|
||||
|
||||
Understanding the device-side execution path in `/etc/ota_bin/local_ota_update.sh` explains the strict naming rules:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor User as User via Touchscreen
|
||||
participant MS as master-server (UI)
|
||||
participant LU as local_ota_update.sh
|
||||
participant OF as ota_file (7z Decryptor)
|
||||
participant BL as Block Devices (mmcblk0)
|
||||
|
||||
User->>MS: Selects USB Update
|
||||
MS->>MS: Scans for 'ota_img_V' on USB
|
||||
MS->>MS: Compares versions (regex [0-9]{1,3})
|
||||
MS->>LU: Calls local_ota_update.sh /media/sda1/<NAME>.img
|
||||
LU->>LU: Computes OTA_UNZIP_FILE_NAME=${OTA_FILE_NAME%.img*}
|
||||
LU->>OF: Calls ota_file e <NAME>.img <TEMP_DIR>
|
||||
OF->>OF: Derives key & extracts 7z archive
|
||||
LU->>LU: Checks <TEMP_DIR>/<OTA_UNZIP_FILE_NAME>/ota_config.in
|
||||
LU->>LU: Validates partition sizes against device nodes
|
||||
LU->>BL: Streams xImage chunks -> mmcblk0p6 (kernel2)
|
||||
LU->>BL: Streams rootfs chunks -> mmcblk0p8 (rootfs2)
|
||||
LU->>BL: Streams zero.bin chunks -> mmcblk0p4 (rtos2)
|
||||
LU->>BL: Writes "ota:kernel2" to /dev/mmcblk0p1
|
||||
LU->>MS: Returns success
|
||||
MS->>User: Reboots printer into OpenKE
|
||||
```
|
||||
|
||||
### The Critical Folder-Name Matching Rule
|
||||
|
||||
Lines 312–320 of `local_ota_update.sh`:
|
||||
```bash
|
||||
OTA_FILE_NAME=$(basename ${OTA_FILE})
|
||||
OTA_UNZIP_FILE_NAME=${OTA_FILE_NAME%.img*}
|
||||
ota_site=${OTA_FILE_PATH}/${OTA_UNZIP_FILE_NAME}
|
||||
/usr/bin/ota_file e ${OTA_FILE_PATH}/${OTA_FILE_NAME} ${OTA_FILE_PATH}
|
||||
ota_site_config=$ota_site/ota_config.in
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If the archive file is named `NEBULA_ota_img_V1.1.0.34.img`, `OTA_UNZIP_FILE_NAME` evaluates to `NEBULA_ota_img_V1.1.0.34`. The root directory inside the 7z archive **must match that exact string**. If the archive has a different folder name (e.g. `OpenKE_...`), `cp -f $ota_site_config` fails and the update aborts immediately.
|
||||
@@ -0,0 +1,71 @@
|
||||
# Troubleshooting & USB Flashing Diagnostics
|
||||
|
||||
This document covers common issues when flashing OpenKE via USB OTA, root causes, and diagnostic steps.
|
||||
|
||||
---
|
||||
|
||||
## 1. "No firmware found" / "No local update available"
|
||||
|
||||
When you insert the USB drive and navigate to **Settings** $\rightarrow$ **Firmware Update** $\rightarrow$ **Local Update**, the screen reports no file found.
|
||||
|
||||
### Root Causes & Fixes:
|
||||
1. **USB Drive File System**:
|
||||
* The Creality Linux kernel does not reliably mount exFAT or NTFS drives on early boot.
|
||||
* **Fix**: Format the USB flash drive as **FAT32** with an **MBR** (Master Boot Record) partition table. Avoid GPT partition tables on the flash drive.
|
||||
2. **File Location**:
|
||||
* The `.img` file must be placed in the **root directory** of the USB drive (e.g. `E:\NEBULA_ota_img_V1.1.0.34.img`), not inside a folder.
|
||||
3. **Filename Format**:
|
||||
* Creality's `master-server` scans for files matching `ota_img_V`.
|
||||
* Files must be named:
|
||||
- For Nebula Pad: `NEBULA_ota_img_V<version>.img`
|
||||
- For Ender-3 V3 KE: `Ender-3_V3_KE_F005_ota_img_V<version>.img`
|
||||
|
||||
---
|
||||
|
||||
## 2. "Update failed" Immediately After Confirming
|
||||
|
||||
The screen detects the update, you click "Update Now", the progress bar appears for a split second, and it immediately reports "Update Failed".
|
||||
|
||||
### Root Cause:
|
||||
* Creality's updater extracts the 7z archive to a temporary directory and looks for `ota_config.in` in `<TEMP_DIR>/<UNZIP_FOLDER>/ota_config.in`.
|
||||
* If the root directory inside the 7z archive does not match the `.img` filename (without `.img`), `cp -f $ota_site_config` fails.
|
||||
* **Resolution**: The updated `build_openke_image.sh` / `tools/pack_openke.py` guarantees this alignment automatically. Ensure you use images built with the latest packager.
|
||||
|
||||
---
|
||||
|
||||
## 3. "Current version is newest" / Won't Allow Flashing
|
||||
|
||||
The UI says the firmware is already up-to-date or newer.
|
||||
|
||||
### Root Cause:
|
||||
* In `local_ota_update.sh`:
|
||||
```bash
|
||||
if [ $current_version -ge $ota_current_version ]; then
|
||||
echo "ota not update: current version is newest"
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
* If your device is running `1.1.0.30` and the package version is `1.1.0.30` or lower, it refuses to flash.
|
||||
* **Fix**: Pass a higher version number when running the build script:
|
||||
```bash
|
||||
./build_openke_image.sh --target nebula --version 1.1.0.35
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. How to Inspect Live Update Logs on the Printer
|
||||
|
||||
If you have SSH access to stock firmware and want to see what `local_ota_update.sh` is doing during a flash:
|
||||
|
||||
1. Connect to the printer via SSH:
|
||||
```bash
|
||||
ssh root@<printer-ip>
|
||||
```
|
||||
2. Trigger the local update manually from the command line to see stdout/stderr in real time:
|
||||
```bash
|
||||
sh -x /etc/ota_bin/local_ota_update.sh /media/sda1/NEBULA_ota_img_V1.1.0.34.img
|
||||
```
|
||||
3. Check temporary updater logs:
|
||||
```bash
|
||||
ls -la /usr/data/creality/ota_updater*
|
||||
```
|
||||
Reference in New Issue
Block a user