1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#![no_std]
#![deny(missing_docs)]
#![doc = include_str!("../README.md")]
#![doc(issue_tracker_base_url = "https://github.com/Finomnis/teensy4-selfrebootor/issues")]
#![cfg_attr(docsrs, feature(doc_cfg))]

use teensy4_bsp::hal::usbd::BusAdapter;
use usb_device::{class_prelude::*, prelude::*};
use usbd_hid::{descriptor::SerializedDescriptor, hid_class::HIDClass};

mod hid_descriptor;
mod reboot;

pub use reboot::reboot_to_bootloader;

/// The rebootor USB driver.
///
/// Once it receives a reboot request (`teensy_loader_cli -r`), it reboots
/// the device into the HalfKay bootloader for flashing.
///
/// This allows reflashing without having to press the `boot` hardware button.
pub struct Rebootor<'a> {
    class: HIDClass<'a, BusAdapter>,
    device: UsbDevice<'a, BusAdapter>,
    configured: bool,
}

impl<'a> Rebootor<'a> {
    /// Creates a rebootor usb device.
    ///
    /// In order for the device to function, its `poll` function has to be called
    /// periodically.
    ///
    /// For more information, see the crate's examples.
    pub fn new(bus_alloc: &'a UsbBusAllocator<BusAdapter>) -> Self {
        let class = HIDClass::new(bus_alloc, crate::hid_descriptor::Rebootor::desc(), 10);
        let device = UsbDeviceBuilder::new(bus_alloc, UsbVidPid(0x16C0, 0x0477))
            .product("Self-Rebootor")
            .manufacturer("PJRC")
            .self_powered(true)
            .max_packet_size_0(64)
            .build();

        device.bus().set_interrupts(true);

        Self {
            class,
            device,
            configured: false,
        }
    }

    /// Needs to be called every couple of milliseconds for the USB device to work
    /// properly.
    ///
    /// See the crate's examples for more information.
    pub fn poll(&mut self) {
        self.device.poll(&mut [&mut self.class]);

        if self.device.state() == UsbDeviceState::Configured {
            if !self.configured {
                self.device.bus().configure();
            }
            self.configured = true;
        } else {
            self.configured = false;
        }

        if self.configured {
            let mut buf = [0u8; 6];

            let result = self.class.pull_raw_output(&mut buf);
            match result {
                Ok(info) => {
                    let buf = &buf[..info];
                    if buf == b"reboot" {
                        log::info!("Rebooting to HalfKay ...");
                        reboot::reboot_to_bootloader();
                    }
                }
                Err(usb_device::UsbError::WouldBlock) => (),
                Err(_) => {}
            }
        }
    }
}