This repository was archived by the owner on Feb 21, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathbmd_rsp.rs
More file actions
365 lines (321 loc) · 12.7 KB
/
bmd_rsp.rs
File metadata and controls
365 lines (321 loc) · 12.7 KB
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// SPDX-License-Identifier: MIT OR Apache-2.0
// SPDX-FileCopyrightText: 2025 1BitSquared <info@1bitsquared.com>
// SPDX-FileContributor: Written by Rachel Mant <git@dragonmux.network>
use std::fs::File;
use std::io::{Read, Write};
use std::mem::MaybeUninit;
use std::path::Path;
use std::sync::{Arc, Mutex};
use color_eyre::eyre::{Result, eyre};
use log::{debug, trace};
use crate::serial::remote::*;
pub struct BmdRspInterface
{
handle: File,
protocol_version: ProtocolVersion,
read_buffer: [u8; RemoteResponse::MAX_MSG_SIZE],
read_buffer_fullness: usize,
read_buffer_offset: usize,
}
impl BmdRspInterface
{
pub fn from_path(serial_port: &Path) -> Result<Self>
{
// Get the serial interface to the probe open
debug!("Opening probe interface at {:?}", serial_port);
let handle = File::options().read(true).write(true).open(serial_port)?;
// Construct an interface object
let mut interface = Self {
handle,
// We start out by not knowing what version of protocol the probe talks
protocol_version: ProtocolVersion::Unknown,
// Initialise an empty read buffer to use for more efficiently reading
// probe responses, being mindful that there's no good way to find out
// how much data is waiting for us from the probe, so it's this or use
// a read call a byte, which is extremely expensive!
read_buffer: [0; RemoteResponse::MAX_MSG_SIZE],
read_buffer_fullness: 0,
read_buffer_offset: 0,
};
// Call the OS-specific handle configuration function to ready
// the interface handle for use with the remote serial protocol
interface.init_handle()?;
// Start remote protocol communications with the probe
Self::start_probe_communication(&mut interface)?;
// Next, ask the probe for its protocol version number.
// For historical reasons this is part of the "high level" protocol set, but is
// actually a general request.
interface.protocol_version = Self::protocol_version(&mut interface)?;
trace!("Probe talks BMD RSP {}", interface.protocol_version);
// Now the object is ready to go, return it to the caller
Ok(interface)
}
fn protocol_version(interface: &mut BmdRspInterface) -> Result<ProtocolVersion>
{
interface.buffer_write(RemoteCommands::HL_CHECK)?;
let buffer = interface.buffer_read()?;
// Check for communication failures, it should respond with at least 1 character
let (first, rest) = buffer
.split_at_checked(1)
.ok_or_else(|| eyre!("Probe failed to respond at all to protocol version request"))?;
match (first.as_bytes()[0], rest) {
// If the request failed by way of a not implemented response, we're on a v0 protocol probe
(RemoteResponse::RESP_NOTSUP, _) => Ok(ProtocolVersion::V0),
(RemoteResponse::RESP_OK, rest) => {
// Parse out the version number from the response
let version = decode_response(&rest, 8);
// Then decode/translate that to a protocol version enum value
match version {
// Protocol version number 0 coresponds to an enchanced v0 probe protocol ("v0+")
0 => Ok(ProtocolVersion::V0Plus),
1 => Ok(ProtocolVersion::V1),
2 => Ok(ProtocolVersion::V2),
3 => Ok(ProtocolVersion::V3),
4 => Ok(ProtocolVersion::V4),
_ => Err(eyre!("Unknown remote protocol version {}", version)),
}
},
// If the probe responded with anything other than OK or not supported, we're done
_ => Err(eyre!("Probe responded improperly to protocol version request with {}", buffer)),
}
}
fn start_probe_communication(interface: &mut BmdRspInterface) -> Result<()>
{
interface.buffer_write(RemoteCommands::START)?;
let buffer = interface.buffer_read()?;
// Check if that failed for any reason
if buffer.is_empty() || buffer.as_bytes()[0] != RemoteResponse::RESP_OK {
let message = if buffer.len() > 1 {
&buffer[1..]
} else {
"unknown"
};
return Err(eyre!("Remote protocol startup failed, error {}", message));
}
// It did not, grand - we now have the firmware version string, so log it
let firmware_version = &buffer[1..];
debug!("Remote is {}", firmware_version);
Ok(())
}
/// Extract the remote protocol object to use to talk with this probe
pub fn remote(self) -> Result<Box<dyn BmdRemoteProtocol>>
{
let interface = Arc::new(Mutex::new(self));
let protocol = interface
.lock()
.map_err(|_| eyre!("Failed to aquire lock on interface to access remote protocol"))?
.protocol_version;
protocol.protocol_impl(interface.clone())
}
pub(crate) fn buffer_write(&mut self, message: &str) -> Result<()>
{
debug!("BMD RSP write: {}", message);
Ok(self.handle.write_all(message.as_bytes())?)
}
pub(crate) fn buffer_read(&mut self) -> Result<String>
{
// First drain the buffer till we see a start-of-response byte
let mut response = 0;
while response != RemoteResponse::RESP {
if self.read_buffer_offset == self.read_buffer_fullness {
self.read_more_data()?;
}
response = self.read_buffer[self.read_buffer_offset];
self.read_buffer_offset += 1;
}
// Now collect the response
let mut buffer = [0u8; RemoteResponse::MAX_MSG_SIZE];
let mut offset = 0;
while offset < buffer.len() {
// Check if we need more data or should use what's in the buffer already
if self.read_buffer_offset == self.read_buffer_fullness {
self.read_more_data()?;
}
// Look for an end of packet marker
let mut response_length = 0;
while self.read_buffer_offset + response_length < self.read_buffer_fullness &&
offset + response_length < buffer.len()
{
if self.read_buffer[self.read_buffer_offset + response_length] == RemoteResponse::EOM {
response_length += 1;
break;
}
response_length += 1;
}
// We now either have a REMOTE_EOM or need all the data from the buffer
let read_buffer_offset = self.read_buffer_offset;
buffer[offset..offset + response_length]
.copy_from_slice(&self.read_buffer[read_buffer_offset..read_buffer_offset + response_length]);
self.read_buffer_offset += response_length;
offset += response_length - 1;
// If this was because of REMOTE_EOM, return
if buffer[offset] == RemoteResponse::EOM {
buffer[offset] = 0;
let result = unsafe { String::from_utf8_unchecked(buffer[..offset].to_vec()) };
debug!("BMD RSP read: {}", result);
return Ok(result);
}
offset += 1;
}
// If we fell out here, we got what we could so return that..
let result = unsafe { String::from_utf8_unchecked(buffer.to_vec()) };
debug!("BMD RSP read: {}", result);
Ok(result)
}
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
impl BmdRspInterface
{
fn init_handle(&self) -> Result<()>
{
use std::os::fd::AsRawFd;
#[cfg(any(target_os = "linux", target_os = "android"))]
use termios::os::linux::CRTSCTS;
#[cfg(target_os = "macos")]
use termios::os::macos::CRTSCTS;
use termios::*;
// Extract the current termios config for the handle
let fd = self.handle.as_raw_fd();
let mut attrs = Termios::from_fd(fd)?;
// Reconfigure the attributes for 8-bit characters, no CTS/RTS hardware control flow,
// w/ no model control signalling
attrs.c_cflag &= !(CSIZE | CSTOPB);
attrs.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS;
// Disable break character handling and turn off XON/XOFF based control flow
attrs.c_iflag &= !(IGNBRK | IXON | IXOFF | IXANY);
// Disable all signaling, echo, remapping and delays
attrs.c_lflag = 0;
attrs.c_oflag = 0;
// Make reads not block, and set 0.5s for read timeout
attrs.c_cc[VMIN] = 0;
attrs.c_cc[VTIME] = 5;
// Reconfigure the handle with the new termios config
tcsetattr(fd, TCSANOW, &attrs)?;
// Let the caller know that we successfully got done
trace!("Configured comms handle to probe remote serial interface");
Ok(())
}
fn read_more_data(&mut self) -> Result<()>
{
use std::os::fd::AsRawFd;
use std::ptr::null_mut;
use color_eyre::eyre::eyre;
use libc::{FD_SET, FD_SETSIZE, FD_ZERO, c_int, fd_set, select, timeval};
// Set up a FD set that describes our handle's FD
let mut select_set = MaybeUninit::<fd_set>::uninit();
unsafe {
FD_ZERO(select_set.as_mut_ptr());
FD_SET(self.handle.as_raw_fd(), select_set.as_mut_ptr());
}
let mut select_set = unsafe { select_set.assume_init() };
// Wait for more data from the probe for up to 2 seconds
let mut timeout = timeval {
tv_sec: 2,
tv_usec: 0,
};
let result = unsafe { select(FD_SETSIZE as c_int, &mut select_set, null_mut(), null_mut(), &mut timeout) };
if result < 0 {
// If the select call failed, bail
Err(eyre!("Failed on select"))
} else if result == 0 {
// If we timed out then bail differently
Err(eyre!("Timeout while waiting for BMD remote protocol response"))
} else {
// Otherwise we now know there's data, so try to fill the read buffer
let bytes_received = self.handle.read(&mut self.read_buffer)?;
trace!("Read {} bytes from probe", bytes_received);
// Now we have more data, so update the read buffer counters
self.read_buffer_fullness = bytes_received;
self.read_buffer_offset = 0;
Ok(())
}
}
}
#[cfg(target_os = "windows")]
impl BmdRspInterface
{
const DCB_CHECK_PARITY: u32 = 1 << 1;
const DCB_DSR_SENSITIVE: u32 = 1 << 6;
const DCB_DTR_CONTROL_ENABLE: u32 = 1 << 4;
const DCB_DTR_CONTROL_MASK: u32 = 3 << 4;
const DCB_RTS_CONTROL_DISABLE: u32 = 0 << 12;
const DCB_RTS_CONTROL_MASK: u32 = 3 << 12;
const DCB_USE_CTS: u32 = 1 << 2;
const DCB_USE_DSR: u32 = 1 << 3;
const DCB_USE_XOFF: u32 = 1 << 9;
const DCB_USE_XON: u32 = 1 << 8;
fn init_handle(&self) -> Result<()>
{
use std::os::windows::io::AsRawHandle;
use windows::Win32::Devices::Communication::{
COMMTIMEOUTS, DCB, GetCommState, NOPARITY, PURGE_RXCLEAR, PurgeComm, SetCommState, SetCommTimeouts,
};
use windows::Win32::Foundation::HANDLE;
// Extract the current CommState for the handle
let handle = HANDLE(self.handle.as_raw_handle());
let mut serial_params = MaybeUninit::<DCB>::uninit();
let mut serial_params = unsafe {
GetCommState(handle, serial_params.as_mut_ptr())?;
serial_params.assume_init()
};
// Reconfigure and adjust device state to disable hardware flow control and
// get it into the right mode for communications to work properly
serial_params.ByteSize = 8;
serial_params.Parity = NOPARITY;
// The windows-rs crate exposes the bitfield parameters to us as a nebulous thing..
// we hold local definitions for each of the values so we can turn them on and off
// appropriately here. See <https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb>
// for where these values come from. When reading this particular bitfield, assume LSb to MSb
// as one traverses down the structure.
serial_params._bitfield &= !(Self::DCB_CHECK_PARITY |
Self::DCB_USE_CTS |
Self::DCB_USE_DSR |
Self::DCB_DTR_CONTROL_MASK |
Self::DCB_DSR_SENSITIVE |
Self::DCB_USE_XOFF |
Self::DCB_USE_XON |
Self::DCB_RTS_CONTROL_MASK);
serial_params._bitfield |= Self::DCB_DTR_CONTROL_ENABLE | Self::DCB_RTS_CONTROL_DISABLE;
// Reconfigure the handle with the new communications state
unsafe { SetCommState(handle, &serial_params)? };
let timeouts = COMMTIMEOUTS {
// Turn off read timeouts so that ReadFile() underlying File's read calls instantly returns
// even if there's o data waiting (we implement our own mechanism below for that case as we
// only want to wait if we get no data)
ReadIntervalTimeout: u32::MAX,
ReadTotalTimeoutMultiplier: 0,
ReadTotalTimeoutConstant: 0,
// Configure an exactly 100ms write timeout - we want this triggering to be fatal as something
// has gone very wrong if we ever hit this.
WriteTotalTimeoutMultiplier: 0,
WriteTotalTimeoutConstant: 100,
};
unsafe {
SetCommTimeouts(handle, &timeouts)?;
// Having adjusted the line state, discard anything sat in the receive buffer
PurgeComm(handle, PURGE_RXCLEAR)?;
}
// Let the caller know that we successfully got done
trace!("Configured comms handle to probe remote serial interface");
Ok(())
}
fn read_more_data(&mut self) -> Result<()>
{
use std::os::windows::io::AsRawHandle;
use windows::Win32::Foundation::{HANDLE, WAIT_OBJECT_0};
use windows::Win32::System::Threading::WaitForSingleObject;
use windows_result::Error;
// Try to wait for up to 100ms for data to become available
let handle = HANDLE(self.handle.as_raw_handle());
if unsafe { WaitForSingleObject(handle, 100) } != WAIT_OBJECT_0 {
return Err(eyre!("Timeout while waiting for BMD RSP response: {}", Error::from_win32()));
}
// Now we know there's data, so try to fill the read buffer
let bytes_received = self.handle.read(&mut self.read_buffer)?;
trace!("Read {} bytes from probe", bytes_received);
// Now we have more data, so update the read buffer counters
self.read_buffer_fullness = bytes_received;
self.read_buffer_offset = 0;
Ok(())
}
}