-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathusb.rs
189 lines (171 loc) · 5.96 KB
/
usb.rs
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! Teensy 4 USB, taken from the original Teensy 4 C libraries.
//!
//! The USB stack provides a [`log`] implementation for logging over USB
//!
//! This is `Serial.println()` in Rust. Use the macros of the
//! [`log`] crate to write data over USB. Messages can be read
//! back using `screen` or `PuTTY`.
//!
//! [`log`]: https://crates.io/crates/log
use crate::interrupt; // bring in interrupt variants for #[interrupt] macro
use core::fmt;
use teensy4_usb_sys as usbsys;
/// Logging configuration
///
/// Allows a user to specify certain configurations of the logging
/// system. By default, the max log level is the log level set at
/// compile time. See the [compile time filters](https://docs.rs/log/0.4.8/log/#compile-time-filters)
/// section for more information. We also enable logging for all targets.
/// Set the `filters` collection to specify log targets of interest.
///
/// If the default configuration is good for you, use `Default::default()`
/// as the argument to `init()`.
pub struct LoggingConfig {
/// The max log level
///
/// By default, we select the static max level. Users may
/// override this if they'd like to bypass the statically-assigned
/// max level
pub max_level: ::log::LevelFilter,
/// A list of filtered targets to log.
///
/// If set to an empty slice (default), the logger performs no
/// filtering. Otherwise, we filter the specified targets by
/// the accompanying log level. If there is no level, we default
pub filters: &'static [(&'static str, Option<::log::LevelFilter>)],
}
impl Default for LoggingConfig {
fn default() -> LoggingConfig {
LoggingConfig {
max_level: ::log::STATIC_MAX_LEVEL,
filters: &[],
}
}
}
/// A handle that enables logging
///
/// Calling `init()` will initialize the USB stack and enable the USB interrupt.
/// Once initialized, messages will be written over USB.
pub struct USB(&'static mut Logger);
impl USB {
/// Initializes the USB stack. This prepares the logging back-end. Returns a `Reader`
/// that can read USB serial messages.
///
/// To select the default logger behavior, specify `Default::default()` as the
/// argument for `config`.
///
/// This may only be called once. If this is not called, we do not initialize the logger,
/// and log messages will not be written to the USB host.
pub fn init(self, config: LoggingConfig) -> Reader {
self.0.enabled = true;
self.0.filters = config.filters;
::log::set_logger(self.0)
.map(|_| ::log::set_max_level(config.max_level))
.unwrap();
unsafe {
usbsys::usb_pll_start();
usbsys::usb_init();
cortex_m::peripheral::NVIC::unmask(crate::interrupt::USB_OTG1);
}
Reader(core::marker::PhantomData)
}
/// # Safety
///
/// This is only called once, when we're setting up peripherals.
/// If `init()` is called, we will set the members of the struct
/// into their state. There can only be one Logging struct, so
/// there's only one reference to the logger singleton.
pub(super) fn new() -> Self {
unsafe { USB(&mut LOGGER) }
}
}
#[crate::rt::interrupt]
fn USB_OTG1() {
unsafe {
usbsys::isr();
}
}
struct Logger {
/// Tracks if we are (not) enabled
enabled: bool,
/// A collection of targets that we are expected
/// to filter. If this is empty, we allow everything
filters: &'static [(&'static str, Option<::log::LevelFilter>)],
}
impl Logger {
/// Returns true if the target is in the filter, else false if the target is
/// not in the list of kept targets. If the filter collection is empty, return
/// true.
fn filtered(&self, metadata: &::log::Metadata) -> bool {
if self.filters.is_empty() {
true
} else if let Some(idx) = self
.filters
.iter()
.position(|&(target, _)| target == metadata.target())
{
let (_, lvl) = self.filters[idx];
lvl.is_none() || lvl.filter(|lvl| metadata.level() <= *lvl).is_some()
} else {
false
}
}
}
static mut LOGGER: Logger = Logger {
enabled: false,
filters: &[],
};
impl ::log::Log for Logger {
fn enabled(&self, metadata: &::log::Metadata) -> bool {
self.enabled // We're enabled
&& metadata.level() <= ::log::max_level() // The log level is appropriate
&& self.filtered(metadata) // The target is in the filter list
}
fn log(&self, record: &::log::Record) {
if self.enabled(record.metadata()) {
use core::fmt::Write;
writeln!(
Writer,
"[{} {}]: {}",
record.level(),
record.target(),
record.args()
)
.expect("infallible");
}
}
fn flush(&self) {
unsafe { usbsys::usb_serial_flush_output() }
}
}
// TODO: make this public?
struct Writer;
impl fmt::Write for Writer {
fn write_str(&mut self, string: &str) -> fmt::Result {
let mut at_linefeed = false;
for line in string.split('\n') {
if at_linefeed {
usbsys::serial_write("\r\n");
}
let bytes = line.as_bytes();
if !bytes.is_empty() {
usbsys::serial_write(bytes);
}
at_linefeed = true;
}
Ok(())
}
}
/// A type that can read USB serial messages from a host
// Uses a raw `*const ()` to ensure that Reader is not Send or Sync
pub struct Reader(core::marker::PhantomData<*const ()>);
/// OK to transfer across 'thread' boundaries, but not safe for
/// multi-threaded access (Sync).
unsafe impl Send for Reader {}
impl Reader {
/// Read from the USB serial endpoint into buffer. Returns the number
/// of bytes read, or zero if there is no data.
pub fn read(&mut self, buffer: &mut [u8]) -> usize {
usbsys::serial_read(buffer)
}
}