Repository navigation
Add experimental on-printer HID erase-fix installer (review of #19) #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
58eb6c9
c6c5932
9652053
faf9343
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| name: On-printer HID tool | ||
| on: | ||
| push: | ||
| pull_request: | ||
| workflow_call: | ||
| permissions: | ||
| contents: read | ||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 20 | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| ref: ${{ github.sha }} | ||
| persist-credentials: false | ||
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | ||
| with: | ||
| python-version: '3.12' | ||
| - name: Check embedded canonical payload and shell contracts | ||
| run: | | ||
| python on-printer/generate_payload.py --check | ||
| python -m unittest discover -s tests -p test_on_printer.py -v | ||
| sh -n on-printer/payload/installer.sh | ||
| - name: Install native build tools | ||
| run: | | ||
| rustup toolchain install 1.85.1 --profile minimal --component clippy --target armv7-unknown-linux-musleabihf | ||
| sudo apt-get update | ||
| sudo apt-get install -y gcc-arm-linux-gnueabihf qemu-user | ||
| - name: Test native protocol and compile static ARMv7 binary | ||
| env: | ||
| CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER: arm-linux-gnueabihf-gcc | ||
| CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_RUNNER: qemu-arm | ||
| run: | | ||
| cargo +1.85.1 test --locked --manifest-path on-printer/Cargo.toml | ||
| cargo +1.85.1 clippy --locked --manifest-path on-printer/Cargo.toml --all-targets -- -D warnings | ||
| cargo +1.85.1 test --locked --manifest-path on-printer/Cargo.toml --target armv7-unknown-linux-musleabihf | ||
| cargo +1.85.1 build --locked --manifest-path on-printer/Cargo.toml --release --target armv7-unknown-linux-musleabihf | ||
| - name: Check static linking and smoke-test executable | ||
| run: | | ||
| mkdir -p dist | ||
| cp on-printer/target/armv7-unknown-linux-musleabihf/release/cc2camera-hid dist/cc2camera-hid-armv7-linux | ||
| file dist/cc2camera-hid-armv7-linux | ||
| if readelf -l dist/cc2camera-hid-armv7-linux | grep -q INTERP; then exit 1; fi | ||
| if readelf -d dist/cc2camera-hid-armv7-linux | grep -q NEEDED; then exit 1; fi | ||
| qemu-arm dist/cc2camera-hid-armv7-linux --help | ||
| if qemu-arm dist/cc2camera-hid-armv7-linux install; then exit 1; fi | ||
| cd dist | ||
| sha256sum cc2camera-hid-armv7-linux > cc2camera-hid-armv7-linux.sha256 | ||
| wc -c cc2camera-hid-armv7-linux | ||
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: cc2camera-hid-armv7-linux | ||
| path: | | ||
| dist/cc2camera-hid-armv7-linux | ||
| dist/cc2camera-hid-armv7-linux.sha256 | ||
| if-no-files-found: error |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,3 +23,6 @@ Thumbs.db | |
| *.swp | ||
| *~ | ||
| *.spec | ||
|
|
||
| # Native on-printer builds | ||
| on-printer/target/ | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| [package] | ||
| name = "cc2camera-hid" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| publish = false | ||
|
|
||
| [profile.release] | ||
| opt-level = "s" | ||
| lto = true | ||
| codegen-units = 1 | ||
| panic = "abort" | ||
| strip = true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| # Install the camera fix from a CC2 shell | ||
|
|
||
| This tool installs the startup fix on a working, affected camera without | ||
| disconnecting it from the printer. You need root shell access to an idle CC2 | ||
| and a USB drive to transfer the executable. Obtaining shell access is outside | ||
| this guide. | ||
|
|
||
| First [identify your camera](../README.md#first-identify-your-camera). This | ||
| route supports the affected 8 MiB 30B family, not the early 16 MiB 30B or the | ||
| 30D. A camera that no longer boots needs [hardware recovery](../README.md#failed-camera-hardware-recovery). | ||
|
|
||
| **Experimental:** installation and verification have succeeded on a previously | ||
| modified 30B. First installation on an unmodified, supported camera still needs | ||
| volunteers. If you try it, please [open an issue](https://github.com/OpenCentauri/cc2_camera_fix/issues/new) | ||
| to share results and get help with problems or tool adjustments. | ||
|
|
||
| ## Before installing | ||
|
|
||
| The installer writes two startup scripts to the camera. It does **not** make a | ||
| backup or check how much safely writable configuration space remains. If that | ||
| space is nearly exhausted, installing the scripts can leave the camera unable | ||
| to boot, requiring an external SPI programmer for recovery. Power loss during | ||
| installation can also cause failure. | ||
|
|
||
| Leaving an affected camera unchanged also consumes configuration space on each | ||
| boot. A camera close enough to exhaustion for these writes to cause failure | ||
| may have only a few normal boots left. Moving it to a computer for the | ||
| [USB procedure](../README.md#working-camera-usb-prevention) adds a boot that can | ||
| itself trigger failure. That procedure offers a backup and space check once | ||
| connected; this route avoids the extra boot and cable work, but is less tested. | ||
| The number of remaining boots cannot be determined by this tool. | ||
|
|
||
| **Keep the camera powered for its first installation. Do not reboot as a | ||
| preparation step.** If you have already attempted `install` or `verify` during | ||
| this camera boot, see [If a command fails](#if-a-command-fails) before doing more. | ||
|
|
||
| ## Download and inspect | ||
|
|
||
| Download `cc2camera-hid-armv7-linux` and `cc2camera-hid-armv7-linux.sha256` from | ||
| [Releases](https://github.com/OpenCentauri/cc2_camera_fix/releases) and put both | ||
| files at the root of a USB drive. Insert it into the printer. | ||
|
|
||
| In the printer's root shell, with the drive mounted at `/mnt/exUDISK`: | ||
|
|
||
| ```sh | ||
| cd /mnt/exUDISK | ||
| sha256sum -c cc2camera-hid-armv7-linux.sha256 | ||
| ``` | ||
|
|
||
| Continue only if the checksum reports `OK`. If `sha256sum` is unavailable, | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sha256sum is available. |
||
| verify the checksum on your computer first. Adjust the drive path if needed. | ||
|
|
||
| ```sh | ||
| cp cc2camera-hid-armv7-linux /tmp/cc2camera-hid | ||
| chmod 755 /tmp/cc2camera-hid | ||
| /tmp/cc2camera-hid inspect | ||
| ``` | ||
|
|
||
| `inspect` reads the camera identification and queries its version. It does not | ||
| upload anything or change camera files. **You can proceed directly to install | ||
| without rebooting.** A message saying the version is unavailable is expected | ||
| on cameras without a temporary version file; installation performs the firmware | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...some cameas don't have a version file, in that case it might say "vesion unavailable". This does not affect if the fix is possible. |
||
| checks. Stop if inspection reports an error or an unsupported camera. | ||
|
|
||
| ## Install | ||
|
|
||
| If you accept the installation risk above, run: | ||
|
|
||
| ```sh | ||
| /tmp/cc2camera-hid install --accept-no-backup-space-risk --verbose > /tmp/cc2camera-install.log 2>&1 | ||
| cat /tmp/cc2camera-install.log | ||
| ``` | ||
|
|
||
| Keep power on until the command finishes. Success begins with: | ||
|
|
||
| ```text | ||
| Camera reports exact installed hook contents and permissions verified. | ||
| ``` | ||
|
|
||
| This means the startup scripts were written and checked. They take effect on | ||
| the next camera boot. Any error instead requires the steps below, not a retry. | ||
|
|
||
| If you know your camera already has custom startup scripts, adding | ||
| `--overwrite-managed-scripts` permits replacing `/etc/conf.d/system.sh` and | ||
| `/etc/conf.d/enabled/10-erase-fix.sh`. Custom behavior in those two files will be | ||
| replaced; other enabled scripts remain. Without the flag, different contents | ||
| are refused. A stock camera does not need this flag. If you encounter that | ||
| refusal unexpectedly, keep the log and ask for help before another attempt. | ||
|
|
||
| ## Verify after installation | ||
|
|
||
| After the success message, wait at least two minutes, then turn the idle printer | ||
| fully off and back on. A shell `reboot` may leave the camera powered and will | ||
| not reliably activate the fix. Copy the executable back from the USB drive: | ||
|
|
||
| ```sh | ||
| cp /mnt/exUDISK/cc2camera-hid-armv7-linux /tmp/cc2camera-hid | ||
| chmod 755 /tmp/cc2camera-hid | ||
| /tmp/cc2camera-hid verify --verbose > /tmp/cc2camera-verify.log 2>&1 | ||
| cat /tmp/cc2camera-verify.log | ||
| ``` | ||
|
|
||
| Verification runs a temporary probe on the camera, without changing its | ||
| persistent files or applying the fix itself. Success is: | ||
|
|
||
| ```text | ||
| Camera reports canonical hooks and the live erase correction verified for this boot. | ||
| ``` | ||
|
|
||
| Check that the camera feed works too, and include that result in your issue. | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the reader, "in your issue" comes without any context here. ...please consider opening a github issue and letting us know it worked. |
||
|
|
||
| ## If a command fails | ||
|
|
||
| Keep the camera powered and save the log to the USB drive before it is lost | ||
| on a printer restart, for example: | ||
|
|
||
| ```sh | ||
| cp /tmp/cc2camera-install.log /mnt/exUDISK/ | ||
| sync | ||
| ``` | ||
|
|
||
| Use `cc2camera-verify.log` instead for a verification failure. Open an issue with | ||
| the log and command you ran. `--verbose` captures the HID communication; it can | ||
| also be added to `inspect` when diagnosing a connection problem. | ||
|
|
||
| Do not repeat an upload or restart simply because a command failed. A preflight | ||
| refusal means installation writes did not start; a partial-installation error | ||
| means they did. A timeout or USB error leaves the outcome unknown, and a camera | ||
| worker may still be running. The uploader also cannot accept another attempt | ||
| after launching a worker during the same camera boot. Resolve the failure with | ||
| help before deciding whether a power cycle is appropriate. | ||
|
|
||
| For protocol details, build instructions and validation evidence, see the | ||
| [technical report](report.md). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| 01 chmod | ||
| 02 config-changing | ||
| 03 config-mount | ||
| 04 config-path | ||
| 05 config-read | ||
| 06 copy | ||
| 07 destination-appeared | ||
| 08 enabled-path | ||
| 09 final-missing | ||
| 10 fix-inactive | ||
| 11 hid-fingerprint | ||
| 12 identity-file | ||
| 13 incomplete-install | ||
| 14 instructions | ||
| 15 kernel-fingerprint | ||
| 16 managed-link | ||
| 17 missing-hooks | ||
| 18 mkdir | ||
| 19 mode | ||
| 20 mount-topology | ||
| 21 partition-map | ||
| 22 pointer | ||
| 23 pointer-changed | ||
| 24 pointer-format | ||
| 25 pointer-range | ||
| 26 rename | ||
| 27 root | ||
| 28 staged-fix | ||
| 29 staged-runner | ||
| 30 symbols | ||
| 31 sync | ||
| 32 temporary-readback | ||
| 33 unknown-managed-file | ||
| 34 unrelated-hook-type | ||
| 35 starter-type | ||
| 36 starter-stat | ||
| 37 starter-mode | ||
| 38 starter-content | ||
| 39 starter-compare | ||
| 40 erase-hook-type | ||
| 41 erase-hook-stat | ||
| 42 erase-hook-mode | ||
| 43 erase-hook-content | ||
| 44 erase-hook-compare |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| #!/usr/bin/env python3 | ||
| """Build the native tool's embedded installer from canonical hook bytes. | ||
|
|
||
| Python is required only on the development/build machine, never on the printer. | ||
| """ | ||
| import hashlib | ||
| import re | ||
| from pathlib import Path | ||
| import sys | ||
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | ||
| from cc2camera.startup_payloads import RUNNER, erase_hook, KERNEL_MD5 | ||
| from cc2camera.adb_backup import EXPECTED_PARTITIONS | ||
| from cc2camera.restore_prepare import INSTRUCTIONS, SYMBOLS | ||
|
|
||
| ROOT = Path(__file__).resolve().parent | ||
|
|
||
| def generate(): | ||
| fix = erase_hook() | ||
| files = {'fix': fix, 'runner': RUNNER} | ||
| payloads = '\n'.join( | ||
| ' cat > "$STAGE/' + name + '" <<\'CC2_PAYLOAD_END\' || fail payload\n' | ||
| + data.decode('ascii') + 'CC2_PAYLOAD_END' | ||
| for name, data in files.items() | ||
| ) | ||
| checks = '\n'.join( | ||
| f'[ "$(busybox devmem {a:#x} 32)" = "0x{v:08X}" ] || fail instructions' | ||
| for a, v in INSTRUCTIONS.items() | ||
| ) + '\n' + '\n'.join( | ||
| f'[ "$(busybox awk \'$3=="{n}" {{print $1}}\' /proc/kallsyms)" = "{a:08x}" ] || fail symbols' | ||
| for n, a in SYMBOLS.items() | ||
| ) | ||
| reasons = [line.split() for line in (ROOT/'failure-reasons.txt').read_text().splitlines()] | ||
| replacements = { | ||
| '@FAIL_CASES@': '\n'.join(f' {name}) code={code};;' for code,name in reasons), | ||
| '@KERNEL_MD5@': KERNEL_MD5, | ||
| '@HID_MD5@': '8091751fdd4d0d50ea31901663797a86', | ||
| '@MTD@': '\n'.join(f'mtd{i}: {size:08x} 00004000 "{name}"' for i, (name,size) in enumerate(EXPECTED_PARTITIONS)), | ||
| '@PAYLOADS@': payloads, | ||
| '@FIX_MD5@': hashlib.md5(fix).hexdigest(), | ||
| '@RUNNER_MD5@': hashlib.md5(RUNNER).hexdigest(), | ||
| '@LIVE_CHECKS@': checks, | ||
| } | ||
| script = (ROOT/'installer.sh.in').read_text() | ||
| for key,value in replacements.items(): script=script.replace(key,value) | ||
| # Check only this worker and generated live checks, not the embedded hooks. | ||
| worker = (ROOT/'installer.sh.in').read_text() + checks | ||
| used = set(re.findall(r'\bfail ([a-z][a-z-]*)', worker)) | ||
| if used != {name for _,name in reasons}: | ||
| raise ValueError('Worker failure-reasons.txt does not match fail sites') | ||
| if len({code for code,_ in reasons}) != len(reasons) or any(not re.fullmatch(r'[0-9]{2}', code) or code == '00' for code,_ in reasons): | ||
| raise ValueError('Failure codes must be unique two-digit values other than 00') | ||
| return script | ||
|
|
||
| if __name__ == '__main__': | ||
| path = ROOT/'payload/installer.sh' | ||
| data = generate() | ||
| if sys.argv[1:] == ['--check']: | ||
| if path.read_text() != data: raise SystemExit('Embedded installer is stale; run on-printer/generate_payload.py') | ||
| elif not sys.argv[1:]: | ||
| path.write_text(data) | ||
| else: raise SystemExit('Usage: generate_payload.py [--check]') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] Put the power-cycle warning before this identification link. In the linked section, the fallback for an unavailable or unknown stream tells readers to switch the printer off and remove the camera for visual inspection. To use this installer afterward, they must power the camera on again. That is the extra boot which this guide warns can exhaust a nearly full configuration partition and brick the camera. Explain that risk before sending a user with an already-running camera to the physical-inspection fallback, and make the safe stop/decision point explicit.