#![allow(unsafe_code)]
use std::io;
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::path::Path;
use std::ptr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, PoisonError};
use std::thread::{self, ThreadId};
use std::time::{Duration, Instant};
use serial2::os::windows::CommTimeouts;
use serial2::{SerialPort, Settings};
use windows_sys::Win32::Devices::Communication::{
COMSTAT, ClearCommError, EV_RXCHAR, SetCommMask, WaitCommEvent,
};
use windows_sys::Win32::Foundation::{
ERROR_IO_PENDING, ERROR_OPERATION_ABORTED, WAIT_OBJECT_0, WAIT_TIMEOUT,
};
use windows_sys::Win32::System::IO::{CancelIoEx, GetOverlappedResult, OVERLAPPED};
use windows_sys::Win32::System::Threading::{CreateEventW, WaitForSingleObject};
use super::{Port, READ_SLICE};
use crate::backend::WRITE_DEADLINE;
const ABORT_ON_ERROR: u32 = 1 << 14;
const END_GRACE: Duration = Duration::from_millis(20);
const SPURIOUS_WAKES: u32 = 3;
const SPURIOUS_PAUSE: Duration = Duration::from_millis(1);
pub(super) fn open(path: &Path, baud: u32) -> io::Result<Box<dyn Port>> {
let port = SerialPort::open(path, |mut settings: Settings| {
settings.set_raw();
settings.set_baud_rate(baud)?;
settings.as_raw_dbc_mut()._bitfield &= !ABORT_ON_ERROR;
Ok(settings)
})?;
port.set_windows_timeouts(&CommTimeouts {
read_interval_timeout: u32::MAX,
read_total_timeout_multiplier: 0,
read_total_timeout_constant: 0,
write_total_timeout_multiplier: 0,
write_total_timeout_constant: WRITE_DEADLINE.as_millis() as u32,
})?;
if unsafe { SetCommMask(port.as_raw_handle(), EV_RXCHAR) } == 0 {
return Err(io::Error::last_os_error());
}
let sleeper = Sleeper::new()?;
let _ = port.discard_input_buffer();
Ok(Box::new(WindowsPort {
port,
sleeper: Mutex::new(sleeper),
woken: AtomicBool::new(false),
}))
}
struct WindowsPort {
port: SerialPort,
sleeper: Mutex<Sleeper>,
woken: AtomicBool,
}
impl Port for WindowsPort {
fn read(&self, buffer: &mut [u8]) -> io::Result<usize> {
let mut sleeper = self.sleeper.lock().unwrap_or_else(PoisonError::into_inner);
let until = Instant::now() + READ_SLICE;
let mut empty_wakes = 0;
loop {
match self.port.read(buffer) {
Err(error) if error.kind() == io::ErrorKind::TimedOut => {}
other => return other,
}
if Instant::now() >= until || self.woken.swap(false, Ordering::AcqRel) {
return Err(io::ErrorKind::TimedOut.into());
}
if empty_wakes >= SPURIOUS_WAKES {
thread::sleep(SPURIOUS_PAUSE);
}
if !sleeper.until_data(&self.port, until)? {
return Err(io::ErrorKind::TimedOut.into());
}
empty_wakes += 1;
}
}
fn write_all(&self, bytes: &[u8]) -> io::Result<()> {
self.port.write_all(bytes)
}
fn wake(&self) {
self.woken.store(true, Ordering::Release);
nudge(&self.port);
}
fn retire(&self) {
let _ = self
.sleeper
.lock()
.unwrap_or_else(PoisonError::into_inner)
.end(&self.port);
}
}
impl Drop for WindowsPort {
fn drop(&mut self) {
let sleeper = self
.sleeper
.get_mut()
.unwrap_or_else(PoisonError::into_inner);
let _ = sleeper.end(&self.port);
}
}
fn nudge(port: &SerialPort) {
unsafe { SetCommMask(port.as_raw_handle(), EV_RXCHAR) };
}
fn queued(port: &SerialPort) -> io::Result<u32> {
let mut errors = 0;
let mut status = COMSTAT::default();
if unsafe { ClearCommError(port.as_raw_handle(), &mut errors, &mut status) } == 0 {
return Err(io::Error::last_os_error());
}
Ok(status.cbInQue)
}
#[repr(C)]
#[derive(Default)]
struct Wait {
overlapped: OVERLAPPED,
mask: u32,
}
struct Sleeper {
event: OwnedHandle,
wait: *mut Wait,
in_flight: Option<ThreadId>,
}
unsafe impl Send for Sleeper {}
impl Sleeper {
fn new() -> io::Result<Self> {
let raw = unsafe { CreateEventW(ptr::null(), 1, 0, ptr::null()) };
if raw.is_null() {
return Err(io::Error::last_os_error());
}
Ok(Self {
event: unsafe { OwnedHandle::from_raw_handle(raw) },
wait: Box::into_raw(Box::default()),
in_flight: None,
})
}
fn until_data(&mut self, port: &SerialPort, until: Instant) -> io::Result<bool> {
let me = thread::current().id();
if self.in_flight.is_some_and(|thread| thread != me) {
self.end(port)?;
}
if self.in_flight.is_none() {
let fired = unsafe {
self.wait.write(Wait::default());
(*self.wait).overlapped.hEvent = self.event.as_raw_handle();
WaitCommEvent(
port.as_raw_handle(),
&raw mut (*self.wait).mask,
&raw mut (*self.wait).overlapped,
)
};
if fired != 0 {
return Ok(true);
}
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(ERROR_IO_PENDING as i32) {
return Err(error);
}
self.in_flight = Some(me);
}
if queued(port)? > 0 {
return Ok(true);
}
let left = until.saturating_duration_since(Instant::now());
if !self.signalled(left)? {
return Ok(false);
}
self.in_flight = None;
let mut transferred = 0;
let ok = unsafe {
GetOverlappedResult(
port.as_raw_handle(),
&raw const (*self.wait).overlapped,
&mut transferred,
0,
)
};
if ok == 0 {
let error = io::Error::last_os_error();
if error.raw_os_error() != Some(ERROR_OPERATION_ABORTED as i32) {
return Err(error);
}
}
Ok(true)
}
fn signalled(&self, timeout: Duration) -> io::Result<bool> {
let millis = u32::try_from(timeout.as_millis()).unwrap_or(u32::MAX - 1);
match unsafe { WaitForSingleObject(self.event.as_raw_handle(), millis) } {
WAIT_OBJECT_0 => Ok(true),
WAIT_TIMEOUT => Ok(false),
_ => Err(io::Error::last_os_error()),
}
}
fn end(&mut self, port: &SerialPort) -> io::Result<()> {
if self.in_flight.is_none() {
return Ok(());
}
nudge(port);
if !self.signalled(END_GRACE)? {
unsafe { CancelIoEx(port.as_raw_handle(), &raw const (*self.wait).overlapped) };
if !self.signalled(END_GRACE)? {
return Err(io::Error::new(
io::ErrorKind::TimedOut,
"the serial driver wouldn't finish a wait on the port, if this keeps happening unplug the box and plug it back in",
));
}
}
self.in_flight = None;
Ok(())
}
}
impl Drop for Sleeper {
fn drop(&mut self) {
if self.in_flight.is_none() {
drop(unsafe { Box::from_raw(self.wait) });
}
}
}
pub(crate) fn qpc() -> Option<i64> {
let mut count = 0i64;
let ok =
unsafe { windows_sys::Win32::System::Performance::QueryPerformanceCounter(&mut count) };
(ok != 0).then_some(count)
}