opentitanlib/transport/hyperdebug/
dfu.rs1use std::any::Any;
8use std::cell::RefCell;
9use std::sync::LazyLock;
10
11use anyhow::{Result, anyhow, bail};
12use regex::Regex;
13use serde_annotate::Annotate;
14
15use crate::transport::{
16 Capabilities, Capability, ProgressIndicator, Transport, TransportError, UpdateFirmware,
17};
18use crate::util::usb::UsbBackend;
19
20const VID_ST_MICROELECTRONICS: u16 = 0x0483;
21const PID_DFU_BOOTLOADER: u16 = 0xdf11;
22
23pub struct HyperdebugDfu {
27 usb_backend: RefCell<UsbBackend>,
29 current_firmware_version: Option<String>,
30 usb_vid: u16,
32 usb_pid: u16,
33}
34
35impl HyperdebugDfu {
36 pub fn open(
39 usb_vid: Option<u16>,
40 usb_pid: Option<u16>,
41 usb_serial: Option<&str>,
42 ) -> Result<Self> {
43 if let Ok(usb_backend) =
50 UsbBackend::new(VID_ST_MICROELECTRONICS, PID_DFU_BOOTLOADER, usb_serial)
51 {
52 return Ok(Self {
57 usb_backend: RefCell::new(usb_backend),
58 current_firmware_version: None,
59 usb_vid: usb_vid.unwrap_or(super::VID_GOOGLE),
60 usb_pid: usb_pid.unwrap_or(super::PID_HYPERDEBUG),
61 });
62 }
63
64 let usb_backend = UsbBackend::new(
65 usb_vid.unwrap_or(super::VID_GOOGLE),
66 usb_pid.unwrap_or(super::PID_HYPERDEBUG),
67 usb_serial,
68 )?;
69 let config_desc = usb_backend.active_config_descriptor()?;
72 let current_firmware_version = if let Some(idx) = config_desc.description_string_index() {
73 usb_backend.read_string_descriptor_ascii(idx).ok()
74 } else {
75 None
76 };
77 Ok(Self {
78 usb_backend: RefCell::new(usb_backend),
79 current_firmware_version,
80 usb_vid: usb_vid.unwrap_or(super::VID_GOOGLE),
81 usb_pid: usb_pid.unwrap_or(super::PID_HYPERDEBUG),
82 })
83 }
84}
85
86impl Transport for HyperdebugDfu {
88 fn capabilities(&self) -> Result<Capabilities> {
89 Ok(Capabilities::new(Capability::NONE))
90 }
91
92 fn dispatch(&self, action: &dyn Any) -> Result<Option<Box<dyn Annotate>>> {
93 if let Some(update_firmware_action) = action.downcast_ref::<UpdateFirmware>() {
94 update_firmware(
95 &mut self.usb_backend.borrow_mut(),
96 self.current_firmware_version.as_deref(),
97 &update_firmware_action.firmware,
98 update_firmware_action.progress.as_ref(),
99 update_firmware_action.force,
100 self.usb_vid,
101 self.usb_pid,
102 )
103 } else {
104 bail!(TransportError::UnsupportedOperation)
105 }
106 }
107}
108
109const USB_CLASS_APP: u8 = 0xFE;
110const USB_SUBCLASS_DFU: u8 = 0x01;
111
112const DFUSE_ERASE_PAGE: u8 = 0x41;
113const DFUSE_PROGRAM_PAGE: u8 = 0x21;
114
115const DFU_STATUS_OK: u8 = 0x00;
116
117const DFU_STATE_APP_IDLE: u8 = 0x00;
118const DFU_STATE_DFU_IDLE: u8 = 0x02;
119const DFU_STATE_DOWNLOAD_BUSY: u8 = 0x04;
120const DFU_STATE_DOWNLOAD_IDLE: u8 = 0x05;
121
122const USB_DFU_DETACH: u8 = 0;
123const USB_DFU_DNLOAD: u8 = 1;
124const USB_DFU_GETSTATUS: u8 = 3;
125
126#[cfg(not(feature = "include_hyperdebug_firmware"))]
127const OFFICIAL_FIRMWARE: Option<&'static [u8]> = None;
128#[cfg(feature = "include_hyperdebug_firmware")]
129const OFFICIAL_FIRMWARE: Option<&'static [u8]> = Some(include_bytes!(env!("hyperdebug_firmware")));
130
131pub fn official_firmware_version() -> Result<Option<&'static str>> {
132 if let Some(fw) = OFFICIAL_FIRMWARE {
133 Ok(Some(get_hyperdebug_firmware_version(fw)?))
134 } else {
135 Ok(None)
136 }
137}
138
139fn validate_firmware_image(firmware: &[u8]) -> Result<()> {
141 get_hyperdebug_firmware_version(firmware)?;
142 Ok(())
143}
144
145const EC_COOKIE: [u8; 4] = [0x99, 0x88, 0x77, 0xce];
146const EC_FIRMWARE_NAME_LEN: usize = 32;
147
148fn get_hyperdebug_firmware_version(firmware: &[u8]) -> Result<&str> {
149 let Some(pos) = firmware[0..1024]
150 .chunks(4)
151 .position(|c| c[0..4] == EC_COOKIE)
152 else {
153 bail!(TransportError::FirmwareProgramFailed(
154 "File is not a HyperDebug firmware image".to_string()
155 ));
156 };
157 let firmware_name_field = &firmware[(pos + 1) * 4..(pos + 1) * 4 + EC_FIRMWARE_NAME_LEN];
158 let end = firmware_name_field
159 .iter()
160 .rev()
161 .position(|b| *b != 0x00)
162 .map(|j| EC_FIRMWARE_NAME_LEN - j)
163 .unwrap_or(0);
164 Ok(std::str::from_utf8(&firmware_name_field[0..end])?)
165}
166
167pub fn update_firmware(
170 usb_device: &mut UsbBackend,
171 current_firmware_version: Option<&str>,
172 firmware: &Option<Vec<u8>>,
173 progress: &dyn ProgressIndicator,
174 force: bool,
175 usb_vid: u16,
176 usb_pid: u16,
177) -> Result<Option<Box<dyn Annotate>>> {
178 let firmware: &[u8] = if let Some(vec) = firmware.as_ref() {
179 validate_firmware_image(vec)?;
180 vec
181 } else {
182 OFFICIAL_FIRMWARE.ok_or_else(|| anyhow!("No build-in firmware, use --filename"))?
183 };
184
185 if !force && let Some(current_version) = current_firmware_version {
186 let new_version = get_hyperdebug_firmware_version(firmware)?;
187 if new_version == current_version {
188 log::warn!(
189 "HyperDebug already running firmware version {}. Consider --force.",
190 new_version,
191 );
192 return Ok(None);
193 }
194 }
195
196 let dfu_desc = scan_usb_descriptor(usb_device)?;
197
198 usb_device.claim_interface(dfu_desc.dfu_interface)?;
200
201 if wait_for_idle(usb_device, dfu_desc.dfu_interface)? != DFU_STATE_APP_IDLE {
202 do_update_firmware(usb_device, dfu_desc, firmware, progress)?;
204 log::info!("Connecting to newly flashed firmware...");
205 if restablish_connection(usb_vid, usb_pid, usb_device.get_serial_number()).is_none() {
206 bail!(TransportError::FirmwareProgramFailed(
207 "Unable to establish connection after flashing. Possibly bad image.".to_string()
208 ));
209 }
210 return Ok(None);
211 }
212
213 log::info!("Requesting switch to DFU mode...");
217 let _ = usb_device
218 .write_control(
219 rusb::request_type(
220 rusb::Direction::Out,
221 rusb::RequestType::Class,
222 rusb::Recipient::Interface,
223 ),
224 USB_DFU_DETACH,
225 1000,
226 dfu_desc.dfu_interface as u16,
227 &[],
228 )
229 .and_then(|_| wait_for_idle(usb_device, dfu_desc.dfu_interface));
230
231 std::thread::sleep(std::time::Duration::from_millis(1000));
236 log::info!("Connecting to DFU bootloader...");
237 let Some(mut dfu_device) = restablish_connection(
238 VID_ST_MICROELECTRONICS,
239 PID_DFU_BOOTLOADER,
240 usb_device.get_serial_number(),
241 ) else {
242 bail!(TransportError::FirmwareProgramFailed(
243 "Unable to establish connection with DFU bootloader.".to_string()
244 ));
245 };
246 log::info!("Connected to DFU bootloader");
247
248 let dfu_desc = scan_usb_descriptor(&dfu_device)?;
249 dfu_device.claim_interface(dfu_desc.dfu_interface)?;
250 do_update_firmware(&dfu_device, dfu_desc, firmware, progress)?;
251 log::info!("Connecting to newly flashed firmware...");
255 if restablish_connection(usb_vid, usb_pid, usb_device.get_serial_number()).is_none() {
256 bail!(TransportError::FirmwareProgramFailed(
257 "Unable to establish connection after flashing. Possibly bad image.".to_string()
258 ));
259 }
260 Ok(None)
261}
262
263fn restablish_connection(usb_vid: u16, usb_pid: u16, serial_number: &str) -> Option<UsbBackend> {
264 for _ in 0..10 {
265 std::thread::sleep(std::time::Duration::from_millis(500));
266 if let Ok(usb_backend) = UsbBackend::new(usb_vid, usb_pid, Some(serial_number)) {
267 return Some(usb_backend);
268 }
269 }
270 None
271}
272
273fn do_update_firmware(
274 usb_device: &UsbBackend,
275 dfu_desc: DfuDescriptor,
276 firmware: &[u8],
277 progress: &dyn ProgressIndicator,
278) -> Result<()> {
279 let DfuDescriptor {
280 dfu_interface,
281 xfer_size,
282 page_size,
283 flash_size,
284 base_address,
285 } = dfu_desc;
286
287 if page_size == 0 || flash_size != 0x80000 || xfer_size == 0 {
288 bail!(TransportError::UsbOpenError(
289 "Unrecognized DFU layout (not a Nucleo-L552ZE?)".to_string()
290 ));
291 }
292
293 log::info!("Erasing flash storage...");
294 let firmware_len = firmware.len() as u32;
295 progress.new_stage("Erasing", firmware_len as usize);
296 let mut bytes_erased: u32 = 0;
297 while bytes_erased < firmware_len {
298 let mut request = [0u8; 5];
299 request[0] = DFUSE_ERASE_PAGE;
300 request[1..5].copy_from_slice(&(base_address + bytes_erased).to_le_bytes());
301 usb_device.write_control(
302 rusb::request_type(
303 rusb::Direction::Out,
304 rusb::RequestType::Class,
305 rusb::Recipient::Interface,
306 ),
307 USB_DFU_DNLOAD,
308 0,
309 dfu_interface as u16,
310 &request,
311 )?;
312 wait_for_idle(usb_device, dfu_interface)?;
313 bytes_erased += page_size;
314 progress.progress(bytes_erased as usize);
315 }
316
317 log::info!("Programming flash storage...");
318 progress.new_stage("Writing", firmware_len as usize);
319 let mut bytes_sent: u32 = 0;
320 while bytes_sent < firmware_len {
321 let chunk_size = std::cmp::min(firmware_len - bytes_sent, xfer_size);
322
323 let mut request = [0u8; 5];
324 request[0] = DFUSE_PROGRAM_PAGE;
325 request[1..5].copy_from_slice(&(base_address + bytes_sent).to_le_bytes());
326 usb_device.write_control(
327 rusb::request_type(
328 rusb::Direction::Out,
329 rusb::RequestType::Class,
330 rusb::Recipient::Interface,
331 ),
332 USB_DFU_DNLOAD,
333 0,
334 dfu_interface as u16,
335 &request,
336 )?;
337 wait_for_idle(usb_device, dfu_interface)?;
338
339 usb_device.write_control(
340 rusb::request_type(
341 rusb::Direction::Out,
342 rusb::RequestType::Class,
343 rusb::Recipient::Interface,
344 ),
345 USB_DFU_DNLOAD,
346 2,
347 dfu_interface as u16,
348 &firmware[bytes_sent as usize..(bytes_sent + chunk_size) as usize],
349 )?;
350 wait_for_idle(usb_device, dfu_interface)?;
351 bytes_sent += chunk_size;
352 progress.progress(bytes_sent as usize);
353 }
354
355 usb_device.write_control(
357 rusb::request_type(
358 rusb::Direction::Out,
359 rusb::RequestType::Class,
360 rusb::Recipient::Interface,
361 ),
362 USB_DFU_DNLOAD,
363 0,
364 dfu_interface as u16,
365 &[],
366 )?;
367 let _ = wait_for_idle(usb_device, dfu_interface);
370 Ok(())
371}
372
373struct DfuDescriptor {
374 dfu_interface: u8,
375 xfer_size: u32,
376 page_size: u32,
377 flash_size: u32,
378 base_address: u32,
379}
380
381fn scan_usb_descriptor(usb_device: &UsbBackend) -> Result<DfuDescriptor> {
383 let mut dfu_interface = 0;
384 let mut xfer_size = 0;
385 let mut page_size = 0;
386 let mut flash_size = 0;
387 let mut base_address = 0;
388
389 let config_desc = usb_device.active_config_descriptor()?;
390 for interface in config_desc.interfaces() {
391 for interface_desc in interface.descriptors() {
392 let idx = match interface_desc.description_string_index() {
393 Some(idx) => idx,
394 None => continue,
395 };
396 let interface_name = match usb_device.read_string_descriptor_ascii(idx) {
397 Ok(interface_name) => interface_name,
398 _ => continue,
399 };
400 if interface_desc.class_code() != USB_CLASS_APP
401 || interface_desc.sub_class_code() != USB_SUBCLASS_DFU
402 || (interface_desc.protocol_code() != 0x01
403 && interface_desc.protocol_code() != 0x02)
404 {
405 continue;
406 }
407 dfu_interface = interface.number();
408 let extra_bytes = interface_desc.extra();
409 if extra_bytes.len() >= 9 {
411 xfer_size = extra_bytes[5] as u32 | (extra_bytes[6] as u32) << 8;
412 }
413 static DFU_SECTION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
414 Regex::new("^@([^/]*)/0x([0-9a-fA-F]+)/([0-9]+)\\*([0-9]+)(..)").unwrap()
415 });
416 let Some(captures) = DFU_SECTION_REGEX.captures(&interface_name) else {
417 continue;
418 };
419 let section_name = captures.get(1).unwrap().as_str().trim();
420 if section_name != "Internal Flash" {
421 continue;
422 }
423 base_address = u32::from_str_radix(captures.get(2).unwrap().as_str(), 16).unwrap();
426 let num_pages = captures.get(3).unwrap().as_str().parse::<u32>().unwrap();
427 page_size = captures.get(4).unwrap().as_str().parse::<u32>().unwrap();
428 let suffix = captures.get(5).unwrap().as_str();
429 if suffix.starts_with('K') {
430 page_size *= 1024;
431 }
432 flash_size = num_pages * page_size;
433 }
434 }
435 Ok(DfuDescriptor {
436 dfu_interface,
437 xfer_size,
438 page_size,
439 flash_size,
440 base_address,
441 })
442}
443
444fn wait_for_idle(dfu_device: &UsbBackend, dfu_interface: u8) -> Result<u8> {
446 loop {
447 let mut response = [0u8; 6];
448 let rc = dfu_device.read_control(
449 rusb::request_type(
450 rusb::Direction::In,
451 rusb::RequestType::Class,
452 rusb::Recipient::Interface,
453 ),
454 USB_DFU_GETSTATUS,
455 0,
456 dfu_interface as u16,
457 &mut response,
458 )?;
459 if rc != response.len() {
460 bail!(TransportError::FirmwareProgramFailed("".to_string()));
461 }
462 let command_status = response[0];
463 let minimum_delay_ms =
464 u64::from_le_bytes([response[1], response[2], response[3], 0, 0, 0, 0, 0]);
465 let device_state = response[4];
466 if command_status != DFU_STATUS_OK {
467 bail!(TransportError::FirmwareProgramFailed(format!(
468 "Unexpected DFU status {}",
469 response[0]
470 )));
471 }
472 if device_state == DFU_STATE_APP_IDLE
473 || device_state == DFU_STATE_DFU_IDLE
474 || device_state == DFU_STATE_DOWNLOAD_IDLE
475 {
476 return Ok(device_state);
477 } else if device_state == DFU_STATE_DOWNLOAD_BUSY {
478 std::thread::sleep(std::time::Duration::from_millis(minimum_delay_ms));
479 } else {
480 bail!(TransportError::FirmwareProgramFailed(format!(
481 "Unexpected DFU state {}",
482 response[4]
483 )));
484 }
485 }
486}