flashthing/lib.rs
1//! # flashthing
2//!
3//! A Rust library for flashing custom firmware to the Spotify Car Thing device.
4//!
5//! This library provides a comprehensive toolset for interacting with the Amlogic
6//! SoC on the Spotify Car Thing, allowing users to flash custom firmware, read/write
7//! partitions, and execute other low-level operations on the device.
8//!
9//! ## Main Features
10//!
11//! - Device detection and mode switching
12//! - Memory reading and writing
13//! - Partition management and restoration
14//! - Custom firmware flashing via JSON configuration
15//! - Progress reporting and event callbacks
16//! - Error handling and recovery (including unbricking)
17//!
18//! ## Usage Example
19//!
20//! ```no_run
21//! use flashthing::{AmlogicSoC, Flasher, Event};
22//! use std::{path::PathBuf, sync::Arc};
23//!
24//! // Set up USB access for the device (on Linux, but no-op for other OSes so fine to call)
25//! AmlogicSoC::host_setup().unwrap();
26//!
27//! // Create a callback to handle events
28//! let callback = Arc::new(|event: Event| {
29//! match event {
30//! Event::FlashProgress(progress) => {
31//! println!("Progress: {:.1}%, ETA: {:.1}s", progress.percent, progress.eta / 1000.0);
32//! },
33//! Event::Step(step_index, step) => {
34//! println!("Step {}: {:?}", step_index, step);
35//! },
36//! Event::DeviceMode(mode) => {
37//! println!("Device mode: {:?}", mode);
38//! },
39//! _ => {}
40//! }
41//! });
42//!
43//! // Flash firmware from a directory
44//! let mut flasher = Flasher::from_directory(
45//! PathBuf::from("/path/to/firmware"),
46//! Some(callback.clone())
47//! ).unwrap();
48//!
49//! // Start the flashing process
50//! flasher.flash().unwrap();
51//! ```
52//!
53//! ## Device Connection
54//!
55//! To use this library, the Spotify Car Thing must be connected via USB and placed
56//! in USB Mode by holding buttons 1 & 4 during power-on.
57//!
58//! ## Configuration Format
59//!
60//! The flashing process is guided by a `meta.json` file that specifies a sequence
61//! of operations to perform. See the schema documentation for details on the format.
62
63mod aml;
64mod flash;
65mod partitions;
66mod setup;
67
68/// Configuration types for the flashing process
69pub mod config;
70
71use std::sync::Arc;
72
73pub use aml::*;
74pub use flash::{FlashProgress, Flasher};
75
76use config::FlashStep;
77
78/// Callback type for receiving flash events
79///
80/// This is used to handle events during the flashing process, such as
81/// progress updates, device connection status, and step transitions.
82pub type Callback = Arc<dyn Fn(Event) + Send + Sync>;
83
84/// Events emitted during the flashing process
85///
86/// These events are sent to the callback function to notify about
87/// the progress and status of the flashing procedure.
88#[derive(Debug)]
89pub enum Event {
90 /// Indicates the tool is searching for a connected device
91 FindingDevice,
92 /// Indicates the device was found and reports its current mode
93 DeviceMode(DeviceMode),
94 /// Indicates the tool is attempting to connect to the device
95 Connecting,
96 /// Indicates a successful connection to the device
97 Connected,
98 /// Indicates the BL2 boot process has started
99 Bl2Boot,
100 /// Indicates the device is being reset
101 Resetting,
102 /// Indicates movement to a new flashing step
103 ///
104 /// Parameters: (step_index, step_details)
105 Step(usize, FlashStep),
106 /// Provides progress information for the current flashing step
107 FlashProgress(FlashProgress),
108}
109
110/// Result type used throughout the crate
111pub type Result<T> = std::result::Result<T, Error>;
112
113/// Error types that can occur during the flashing process
114#[derive(thiserror::Error, Debug)]
115pub enum Error {
116 /// Error from the USB subsystem
117 #[error("USB error: {0}")]
118 UsbError(#[from] rusb::Error),
119
120 /// I/O related error
121 #[error("IO error: {0}")]
122 IoError(#[from] std::io::Error),
123
124 /// Error converting slices
125 #[error("slice conversion error: {0}")]
126 Bytes(#[from] std::array::TryFromSliceError),
127
128 /// Error when an operation is invalid in the current context
129 #[error("Invalid operation: {0}")]
130 InvalidOperation(String),
131
132 /// UTF-8 conversion error
133 #[error("UTF8 conversion error: {0}")]
134 Utf8Error(#[from] std::string::FromUtf8Error),
135
136 /// Error when the device is not found
137 #[error("device not found!")]
138 NotFound,
139
140 /// Error when the device is in an incompatible mode
141 #[error("device in wrong mode!")]
142 WrongMode,
143
144 /// Error when a bulk command fails
145 #[error("bulkcmd failed: {0}")]
146 BulkCmdFailed(String),
147
148 /// Error when the meta.json version is not supported
149 #[error("unsupported `meta.json` version: {0}")]
150 UnsupportedVersion(usize),
151
152 /// Error when a feature in meta.json is not supported
153 #[error("unsupported `meta.json` feature: {:?}", 0)]
154 UnsupportedFeature(config::FlashStep),
155
156 /// JSON deserialization error
157 #[error("failed to deserialize json: {0}")]
158 Json(#[from] serde_json::Error),
159
160 /// Error when a path expected to be a directory is not
161 #[error("{0} is not a directory")]
162 NotDir(std::path::PathBuf),
163
164 /// Error when the required meta.json file is not found
165 #[error("could not find required `meta.json` at {0}")]
166 NoMeta(std::path::PathBuf),
167
168 /// Error when a required file is missing
169 #[error("required file does not exist at {0}")]
170 FileMissing(std::path::PathBuf),
171
172 /// Zip archive error
173 #[error("zip error: {0}")]
174 Zip(#[from] zip::result::ZipError),
175}
176
177const SUPPORTED_META_VERSION: usize = 1;
178
179const BL2_BIN: &[u8] = include_bytes!("../resources/superbird.bl2.encrypted.bin");
180const BOOTLOADER_BIN: &[u8] = include_bytes!("../resources/superbird.bootloader.img");
181const UNBRICK_BIN_ZIP: &[u8] = include_bytes!("../resources/unbrick.bin.zip");
182const STOCK_META: &[u8] = include_bytes!("../resources/stock-meta.json");
183
184const VENDOR_ID: u16 = 0x1b8e;
185const PRODUCT_ID: u16 = 0xc003;
186
187#[allow(dead_code)]
188const VENDOR_ID_BOOTED: u16 = 0x1d6b;
189#[allow(dead_code)]
190const PRODUCT_ID_BOOTED: u16 = 0x1014;
191
192const ADDR_BL2: u32 = 0xfffa0000;
193const TRANSFER_SIZE_THRESHOLD: usize = 8 * 1024 * 1024;
194const ADDR_TMP: u32 = 0x1080000;
195
196// all requests
197const REQ_WRITE_MEM: u8 = 0x01;
198const REQ_READ_MEM: u8 = 0x02;
199#[allow(dead_code)]
200const REQ_FILL_MEM: u8 = 0x03;
201#[allow(dead_code)]
202const REQ_MODIFY_MEM: u8 = 0x04;
203const REQ_RUN_IN_ADDR: u8 = 0x05;
204#[allow(dead_code)]
205const REQ_WRITE_AUX: u8 = 0x06;
206#[allow(dead_code)]
207const REQ_READ_AUX: u8 = 0x07;
208
209const REQ_WR_LARGE_MEM: u8 = 0x11;
210#[allow(dead_code)]
211const REQ_RD_LARGE_MEM: u8 = 0x12;
212const REQ_IDENTIFY_HOST: u8 = 0x20;
213
214#[allow(dead_code)]
215const REQ_TPL_CMD: u8 = 0x30;
216#[allow(dead_code)]
217const REQ_TPL_STAT: u8 = 0x31;
218
219#[allow(dead_code)]
220const REQ_WRITE_MEDIA: u8 = 0x32;
221#[allow(dead_code)]
222const REQ_READ_MEDIA: u8 = 0x33;
223
224const REQ_BULKCMD: u8 = 0x34;
225
226#[allow(dead_code)]
227const REQ_PASSWORD: u8 = 0x35;
228#[allow(dead_code)]
229const REQ_NOP: u8 = 0x36;
230
231const REQ_GET_AMLC: u8 = 0x50;
232const REQ_WRITE_AMLC: u8 = 0x60;
233
234const FLAG_KEEP_POWER_ON: u32 = 0x10;
235
236const AMLC_AMLS_BLOCK_LENGTH: usize = 0x200;
237const AMLC_MAX_BLOCK_LENGTH: usize = 0x4000;
238const AMLC_MAX_TRANSFER_LENGTH: usize = 65536;
239
240#[allow(dead_code)]
241const WRITE_MEDIA_CHEKSUM_ALG_NONE: u16 = 0x00ee;
242#[allow(dead_code)]
243const WRITE_MEDIA_CHEKSUM_ALG_ADDSUM: u16 = 0x00ef;
244#[allow(dead_code)]
245const WRITE_MEDIA_CHEKSUM_ALG_CRC32: u16 = 0x00f0;
246
247// Constants for partition operations
248const PART_SECTOR_SIZE: usize = 512; // bytes, size of sectors used in partition table
249const TRANSFER_BLOCK_SIZE: usize = 8 * PART_SECTOR_SIZE; // 4KB data transferred into memory one block at a time