blob: efb24543234942484efbcfb8d6aa53a3e48bcbe3 [file] [log] [blame]
use android_hardware_uwb::aidl::android::hardware::uwb::{
IUwbChip::IUwbChipAsyncServer, IUwbClientCallback::IUwbClientCallback, UwbEvent::UwbEvent,
UwbStatus::UwbStatus,
};
use android_hardware_uwb::binder;
use async_trait::async_trait;
use binder::{DeathRecipient, IBinder, Result, Strong};
use std::sync::Arc;
use tokio::io::unix::AsyncFd;
use tokio::select;
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::os::unix::fs::OpenOptionsExt;
enum State {
Closed,
Opened {
callbacks: Strong<dyn IUwbClientCallback>,
handle: tokio::task::JoinHandle<()>,
serial: File,
death_recipient: DeathRecipient,
token: CancellationToken,
},
}
pub struct UwbChip {
name: String,
path: String,
state: Arc<Mutex<State>>,
}
impl UwbChip {
pub fn new(name: String, path: String) -> Self {
Self {
name,
path,
state: Arc::new(Mutex::new(State::Closed)),
}
}
}
impl State {
/// Terminate the reader task.
async fn close(&mut self) -> Result<()> {
if let State::Opened { ref mut token, ref callbacks, ref mut death_recipient, ref mut handle, .. } = *self {
log::info!("waiting for task cancellation");
callbacks.as_binder().unlink_to_death(death_recipient)?;
token.cancel();
handle.await.unwrap();
log::info!("task successfully cancelled");
callbacks.onHalEvent(UwbEvent::CLOSE_CPLT, UwbStatus::OK)?;
*self = State::Closed;
}
Ok(())
}
}
pub fn makeraw(file: File) -> io::Result<File> {
// Configure the file descriptor as raw fd.
use nix::sys::termios::*;
let mut attrs = tcgetattr(&file)?;
cfmakeraw(&mut attrs);
tcsetattr(&file, SetArg::TCSANOW, &attrs)?;
Ok(file)
}
/// Wrapper around Read::read to handle EWOULDBLOCK.
/// /!\ will actively wait for more data, make sure to call
/// this method only when data is immediately expected.
fn read_exact(file: &mut File, mut buf: &mut [u8]) -> io::Result<()> {
while buf.len() > 0 {
match file.read(buf) {
Ok(0) => panic!("unexpectedly reached end of file"),
Ok(read_len) => buf = &mut buf[read_len..],
Err(err) if err.kind() == io::ErrorKind::WouldBlock => continue,
Err(err) => return Err(err),
}
}
Ok(())
}
impl binder::Interface for UwbChip {}
#[async_trait]
impl IUwbChipAsyncServer for UwbChip {
async fn getName(&self) -> Result<String> {
Ok(self.name.clone())
}
async fn open(&self, callbacks: &Strong<dyn IUwbClientCallback>) -> Result<()> {
log::debug!("open: {:?}", &self.path);
let mut state = self.state.lock().await;
if matches!(*state, State::Opened { .. }) {
log::error!("the state is already opened");
return Err(binder::ExceptionCode::ILLEGAL_STATE.into());
}
let serial = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.custom_flags(libc::O_NONBLOCK)
.open(&self.path)
.and_then(makeraw)
.map_err(|_| binder::StatusCode::UNKNOWN_ERROR)?;
let state_death_recipient = self.state.clone();
let mut death_recipient = DeathRecipient::new(move || {
let mut state = state_death_recipient.blocking_lock();
log::info!("Uwb service has died");
if let State::Opened { ref mut token, .. } = *state {
token.cancel();
*state = State::Closed;
}
});
callbacks.as_binder().link_to_death(&mut death_recipient)?;
let token = CancellationToken::new();
let cloned_token = token.clone();
let client_callbacks = callbacks.clone();
let reader = serial
.try_clone()
.map_err(|_| binder::StatusCode::UNKNOWN_ERROR)?;
let join_handle = tokio::task::spawn(async move {
log::info!("UCI reader task started");
let mut reader = AsyncFd::new(reader).unwrap();
loop {
const UWB_HEADER_SIZE: usize = 4;
let mut buffer = vec![0; UWB_HEADER_SIZE];
// The only time where the task can be safely
// cancelled is when no packet bytes have been read.
//
// - read_exact() cannot be used here since it is not
// cancellation safe.
// - read() cannot be used because it cannot be cancelled:
// the syscall is executed blocking on the threadpool
// and completes after termination of the task when
// the pipe receives more data.
let read_len = loop {
// On some platforms, the readiness detecting mechanism
// relies on edge-triggered notifications. This means that
// the OS will only notify Tokio when the file descriptor
// transitions from not-ready to ready. For this to work
// you should first try to read or write and only poll for
// readiness if that fails with an error of
// std::io::ErrorKind::WouldBlock.
match reader.get_mut().read(&mut buffer) {
Ok(0) => {
log::error!("file unexpectedly closed");
return;
}
Ok(read_len) => break read_len,
Err(err) if err.kind() == io::ErrorKind::WouldBlock => (),
Err(_) => panic!("unexpected read failure"),
}
let mut guard = select! {
_ = cloned_token.cancelled() => {
log::info!("task is cancelled!");
return;
},
result = reader.readable() => result.unwrap()
};
guard.clear_ready();
};
// Read the remaining header bytes, if truncated.
read_exact(reader.get_mut(), &mut buffer[read_len..]).unwrap();
let length = buffer[3] as usize + UWB_HEADER_SIZE;
buffer.resize(length, 0);
// Read the payload bytes.
read_exact(reader.get_mut(), &mut buffer[UWB_HEADER_SIZE..]).unwrap();
client_callbacks.onUciMessage(&buffer).unwrap();
}
});
callbacks.onHalEvent(UwbEvent::OPEN_CPLT, UwbStatus::OK)?;
*state = State::Opened {
callbacks: callbacks.clone(),
handle: join_handle,
serial,
death_recipient,
token,
};
Ok(())
}
async fn close(&self) -> Result<()> {
log::debug!("close");
let mut state = self.state.lock().await;
if matches!(*state, State::Opened { .. }) {
state.close().await
} else {
Err(binder::ExceptionCode::ILLEGAL_STATE.into())
}
}
async fn coreInit(&self) -> Result<()> {
log::debug!("coreInit");
if let State::Opened { ref callbacks, .. } = *self.state.lock().await {
callbacks.onHalEvent(UwbEvent::POST_INIT_CPLT, UwbStatus::OK)?;
Ok(())
} else {
Err(binder::ExceptionCode::ILLEGAL_STATE.into())
}
}
async fn sessionInit(&self, _id: i32) -> Result<()> {
log::debug!("sessionInit");
Ok(())
}
async fn getSupportedAndroidUciVersion(&self) -> Result<i32> {
Ok(1)
}
async fn sendUciMessage(&self, data: &[u8]) -> Result<i32> {
log::debug!("sendUciMessage");
if let State::Opened { ref mut serial, .. } = &mut *self.state.lock().await {
serial
.write(data)
.map(|written| written as i32)
.map_err(|_| binder::StatusCode::UNKNOWN_ERROR.into())
} else {
Err(binder::ExceptionCode::ILLEGAL_STATE.into())
}
}
}