Skip to main content

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::*;
74use config::FlashStep;
75pub use flash::{FlashProgress, Flasher};
76
77/// Callback type for receiving flash events
78///
79/// This is used to handle events during the flashing process, such as
80/// progress updates, device connection status, and step transitions.
81pub type Callback = Arc<dyn Fn(Event) + Send + Sync>;
82
83/// Events emitted during the flashing process
84///
85/// These events are sent to the callback function to notify about
86/// the progress and status of the flashing procedure.
87#[derive(Debug)]
88pub enum Event {
89  /// Indicates the tool is searching for a connected device
90  FindingDevice,
91  /// Indicates the device was found and reports its current mode
92  DeviceMode(DeviceMode),
93  /// Indicates the tool is attempting to connect to the device
94  Connecting,
95  /// Indicates a successful connection to the device
96  Connected,
97  /// Indicates the BL2 boot process has started
98  Bl2Boot,
99  /// Indicates the device is being reset
100  Resetting,
101  /// Indicates movement to a new flashing step
102  ///
103  /// Parameters: (step_index, step_details)
104  Step(usize, FlashStep),
105  /// Provides progress information for the current flashing step
106  FlashProgress(FlashProgress),
107}
108
109/// Result type used throughout the crate
110pub type Result<T> = std::result::Result<T, Error>;
111
112/// Error types that can occur during the flashing process
113#[derive(thiserror::Error, Debug)]
114pub enum Error {
115  /// Error from the USB subsystem
116  #[error("USB error: {0}")]
117  UsbError(#[from] rusb::Error),
118
119  /// I/O related error
120  #[error("IO error: {0}")]
121  IoError(#[from] std::io::Error),
122
123  /// Error converting slices
124  #[error("slice conversion error: {0}")]
125  Bytes(#[from] std::array::TryFromSliceError),
126
127  /// Error when an operation is invalid in the current context
128  #[error("Invalid operation: {0}")]
129  InvalidOperation(String),
130
131  /// UTF-8 conversion error
132  #[error("UTF8 conversion error: {0}")]
133  Utf8Error(#[from] std::string::FromUtf8Error),
134
135  /// Error when the device is not found
136  #[error("device not found!")]
137  NotFound,
138
139  /// Error when the device is in an incompatible mode
140  #[error("device in wrong mode!")]
141  WrongMode,
142
143  /// Error when a bulk command fails
144  #[error("bulkcmd failed: {0}")]
145  BulkCmdFailed(String),
146
147  /// Error when the meta.json version is not supported
148  #[error("unsupported `meta.json` version: {0}")]
149  UnsupportedVersion(usize),
150
151  /// Error when a feature in meta.json is not supported
152  #[error("unsupported `meta.json` feature: {:?}", 0)]
153  UnsupportedFeature(config::FlashStep),
154
155  /// JSON deserialization error
156  #[error("failed to deserialize json: {0}")]
157  Json(#[from] serde_json::Error),
158
159  /// Error when a path expected to be a directory is not
160  #[error("{0} is not a directory")]
161  NotDir(std::path::PathBuf),
162
163  /// Error when the required meta.json file is not found
164  #[error("could not find required `meta.json` at {0}")]
165  NoMeta(std::path::PathBuf),
166
167  /// Error when a required file is missing
168  #[error("required file does not exist at {0}")]
169  FileMissing(std::path::PathBuf),
170
171  /// Zip archive error
172  #[error("zip error: {0}")]
173  Zip(#[from] zip::result::ZipError),
174
175  #[cfg(target_os = "linux")]
176  /// whoami error
177  #[error("whoami error: {0}")]
178  Whoami(#[from] whoami::Error),
179}
180
181const SUPPORTED_META_VERSION_MIN: usize = 1;
182const SUPPORTED_META_VERSION_MAX: usize = 2;
183
184const BL2_BIN: &[u8] = include_bytes!("../resources/superbird.bl2.encrypted.bin");
185const BOOTLOADER_BIN: &[u8] = include_bytes!("../resources/superbird.bootloader.img");
186const UNBRICK_BIN_ZIP: &[u8] = include_bytes!("../resources/unbrick.bin.zip");
187const STOCK_META: &[u8] = include_bytes!("../resources/stock-meta.json");
188
189const VENDOR_ID: u16 = 0x1b8e;
190const PRODUCT_ID: u16 = 0xc003;
191
192#[allow(dead_code)]
193const VENDOR_ID_BOOTED: u16 = 0x1d6b;
194#[allow(dead_code)]
195const PRODUCT_ID_BOOTED: u16 = 0x1014;
196
197const ADDR_BL2: u32 = 0xfffa0000;
198const TRANSFER_SIZE_THRESHOLD: usize = 8 * 1024 * 1024;
199const ADDR_TMP: u32 = 0x1080000;
200
201// all requests
202const REQ_WRITE_MEM: u8 = 0x01;
203const REQ_READ_MEM: u8 = 0x02;
204#[allow(dead_code)]
205const REQ_FILL_MEM: u8 = 0x03;
206#[allow(dead_code)]
207const REQ_MODIFY_MEM: u8 = 0x04;
208const REQ_RUN_IN_ADDR: u8 = 0x05;
209#[allow(dead_code)]
210const REQ_WRITE_AUX: u8 = 0x06;
211#[allow(dead_code)]
212const REQ_READ_AUX: u8 = 0x07;
213
214const REQ_WR_LARGE_MEM: u8 = 0x11;
215#[allow(dead_code)]
216const REQ_RD_LARGE_MEM: u8 = 0x12;
217const REQ_IDENTIFY_HOST: u8 = 0x20;
218
219#[allow(dead_code)]
220const REQ_TPL_CMD: u8 = 0x30;
221#[allow(dead_code)]
222const REQ_TPL_STAT: u8 = 0x31;
223
224#[allow(dead_code)]
225const REQ_WRITE_MEDIA: u8 = 0x32;
226#[allow(dead_code)]
227const REQ_READ_MEDIA: u8 = 0x33;
228
229const REQ_BULKCMD: u8 = 0x34;
230
231#[allow(dead_code)]
232const REQ_PASSWORD: u8 = 0x35;
233#[allow(dead_code)]
234const REQ_NOP: u8 = 0x36;
235
236const REQ_GET_AMLC: u8 = 0x50;
237const REQ_WRITE_AMLC: u8 = 0x60;
238
239const FLAG_KEEP_POWER_ON: u32 = 0x10;
240
241const AMLC_AMLS_BLOCK_LENGTH: usize = 0x200;
242const AMLC_MAX_BLOCK_LENGTH: usize = 0x4000;
243const AMLC_MAX_TRANSFER_LENGTH: usize = 65536;
244
245#[allow(dead_code)]
246const WRITE_MEDIA_CHEKSUM_ALG_NONE: u16 = 0x00ee;
247#[allow(dead_code)]
248const WRITE_MEDIA_CHEKSUM_ALG_ADDSUM: u16 = 0x00ef;
249#[allow(dead_code)]
250const WRITE_MEDIA_CHEKSUM_ALG_CRC32: u16 = 0x00f0;
251
252// Constants for partition operations
253const PART_SECTOR_SIZE: usize = 512; // bytes, size of sectors used in partition table
254const TRANSFER_BLOCK_SIZE: usize = 8 * PART_SECTOR_SIZE; // 4KB data transferred into memory one block at a time