Skip to content

Commit

Permalink
Merge pull request #12 from alexmoon/try-controller
Browse files Browse the repository at this point in the history
Add try* variants of traits for controllers that support non-blocking…
  • Loading branch information
lulf authored May 13, 2024
2 parents 1425c12 + cb2b130 commit 32eba64
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 30 deletions.
11 changes: 6 additions & 5 deletions src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
use core::future::Future;

use crate::controller::{Controller, ControllerCmdAsync, ControllerCmdSync};
use crate::param::{self, param};
use crate::{FixedSizeValue, FromHciBytes, HostToControllerPacket, PacketKind, WriteHci};
use embedded_io::ErrorType;

use crate::controller::{ControllerCmdAsync, ControllerCmdSync};
use crate::{param, FixedSizeValue, FromHciBytes, HostToControllerPacket, PacketKind, WriteHci};

pub mod controller_baseband;
pub mod info;
Expand Down Expand Up @@ -112,7 +113,7 @@ pub trait AsyncCmd: Cmd {
fn exec<C: ControllerCmdAsync<Self>>(
&self,
controller: &C,
) -> impl Future<Output = Result<(), Error<<C as Controller>::Error>>> {
) -> impl Future<Output = Result<(), Error<<C as ErrorType>::Error>>> {
controller.exec(self)
}
}
Expand Down Expand Up @@ -155,7 +156,7 @@ pub trait SyncCmd: Cmd {
fn exec<C: ControllerCmdSync<Self>>(
&self,
controller: &C,
) -> impl Future<Output = Result<Self::Return, Error<<C as Controller>::Error>>> {
) -> impl Future<Output = Result<Self::Return, Error<<C as ErrorType>::Error>>> {
controller.exec(self)
}
}
Expand Down
131 changes: 110 additions & 21 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ use cmd::controller_baseband::Reset;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::signal::Signal;
use embassy_sync::waitqueue::AtomicWaker;
use embedded_io::ErrorType;
use futures_intrusive::sync::LocalSemaphore;

use crate::cmd::{Cmd, CmdReturnBuf};
use crate::event::{CommandComplete, Event};
use crate::param::{RemainingBytes, Status};
use crate::transport::Transport;
use crate::{cmd, data, ControllerToHostPacket, FixedSizeValue, FromHciBytes, HostToControllerPacket};
use crate::{cmd, data, ControllerToHostPacket, FixedSizeValue, FromHciBytes};

pub trait Controller {
type Error: embedded_io::Error;
pub mod blocking;

pub trait Controller: ErrorType {
fn write_acl_data(&self, packet: &data::AclPacket) -> impl Future<Output = Result<(), Self::Error>>;
fn write_sync_data(&self, packet: &data::SyncPacket) -> impl Future<Output = Result<(), Self::Error>>;
fn write_iso_data(&self, packet: &data::IsoPacket) -> impl Future<Output = Result<(), Self::Error>>;
Expand All @@ -37,54 +38,50 @@ pub trait ControllerCmdAsync<C: cmd::AsyncCmd + ?Sized>: Controller {
fn exec(&self, cmd: &C) -> impl Future<Output = Result<(), cmd::Error<Self::Error>>>;
}

/// An external [`Controller`] with communication via [`Transport`] type `T`.
/// An external Bluetooth controller with communication via [`Transport`] type `T`.
///
/// The controller state holds a number of command slots that can be used
/// to issue commands and await responses from an underlying controller.
///
/// The contract is that before sending a command, a slot is reserved, which
/// returns a signal handle that can be used to await a response.
pub struct ExternalController<T, const SLOTS: usize>
where
T: Transport,
{
pub struct ExternalController<T, const SLOTS: usize> {
transport: T,
slots: ControllerState<SLOTS>,
}

impl<T, const SLOTS: usize> ExternalController<T, SLOTS>
where
T: Transport,
{
impl<T, const SLOTS: usize> ExternalController<T, SLOTS> {
pub fn new(transport: T) -> Self {
Self {
slots: ControllerState::new(),
transport,
}
}
}

async fn write<W: HostToControllerPacket>(&self, data: &W) -> Result<(), T::Error> {
self.transport.write(data).await
}
impl<T, const SLOTS: usize> ErrorType for ExternalController<T, SLOTS>
where
T: ErrorType,
{
type Error = T::Error;
}

impl<T, const SLOTS: usize> Controller for ExternalController<T, SLOTS>
where
T: Transport,
{
type Error = T::Error;
async fn write_acl_data(&self, packet: &data::AclPacket<'_>) -> Result<(), Self::Error> {
self.write(packet).await?;
self.transport.write(packet).await?;
Ok(())
}

async fn write_sync_data(&self, packet: &data::SyncPacket<'_>) -> Result<(), Self::Error> {
self.write(packet).await?;
self.transport.write(packet).await?;
Ok(())
}

async fn write_iso_data(&self, packet: &data::IsoPacket<'_>) -> Result<(), Self::Error> {
self.write(packet).await?;
self.transport.write(packet).await?;
Ok(())
}

Expand Down Expand Up @@ -119,6 +116,98 @@ where
}
}

impl<T, const SLOTS: usize> blocking::Controller for ExternalController<T, SLOTS>
where
T: crate::transport::blocking::Transport,
{
fn write_acl_data(&self, packet: &data::AclPacket<'_>) -> Result<(), Self::Error> {
loop {
match self.try_write_acl_data(packet) {
Err(blocking::TryError::Busy) => {}
Err(blocking::TryError::Error(e)) => return Err(e),
Ok(r) => return Ok(r),
}
}
}

fn write_sync_data(&self, packet: &data::SyncPacket<'_>) -> Result<(), Self::Error> {
loop {
match self.try_write_sync_data(packet) {
Err(blocking::TryError::Busy) => {}
Err(blocking::TryError::Error(e)) => return Err(e),
Ok(r) => return Ok(r),
}
}
}

fn write_iso_data(&self, packet: &data::IsoPacket<'_>) -> Result<(), Self::Error> {
loop {
match self.try_write_iso_data(packet) {
Err(blocking::TryError::Busy) => {}
Err(blocking::TryError::Error(e)) => return Err(e),
Ok(r) => return Ok(r),
}
}
}

fn read<'a>(&self, buf: &'a mut [u8]) -> Result<ControllerToHostPacket<'a>, Self::Error> {
loop {
// Safety: we will not hold references across loop iterations.
let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) };
match self.try_read(buf) {
Err(blocking::TryError::Busy) => {}
Err(blocking::TryError::Error(e)) => return Err(e),
Ok(r) => return Ok(r),
}
}
}

fn try_write_acl_data(&self, packet: &data::AclPacket<'_>) -> Result<(), blocking::TryError<Self::Error>> {
self.transport.write(packet)?;
Ok(())
}

fn try_write_sync_data(&self, packet: &data::SyncPacket<'_>) -> Result<(), blocking::TryError<Self::Error>> {
self.transport.write(packet)?;
Ok(())
}

fn try_write_iso_data(&self, packet: &data::IsoPacket<'_>) -> Result<(), blocking::TryError<Self::Error>> {
self.transport.write(packet)?;
Ok(())
}

fn try_read<'a>(&self, buf: &'a mut [u8]) -> Result<ControllerToHostPacket<'a>, blocking::TryError<Self::Error>> {
loop {
{
// Safety: we will not hold references across loop iterations.
let buf = unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr(), buf.len()) };
let value = self.transport.read(&mut buf[..])?;
match value {
ControllerToHostPacket::Event(ref event) => match &event {
Event::CommandComplete(e) => {
self.slots.complete(
e.cmd_opcode,
e.status,
e.num_hci_cmd_pkts as usize,
e.return_param_bytes.as_ref(),
);
continue;
}
Event::CommandStatus(e) => {
self.slots
.complete(e.cmd_opcode, e.status, e.num_hci_cmd_pkts as usize, &[]);
continue;
}
_ => return Ok(value),
},
_ => return Ok(value),
}
}
}
}
}

impl<T, C, const SLOTS: usize> ControllerCmdSync<C> for ExternalController<T, SLOTS>
where
T: Transport,
Expand All @@ -134,7 +223,7 @@ where
self.slots.release_slot(idx);
});

self.write(cmd).await.map_err(cmd::Error::Io)?;
self.transport.write(cmd).await.map_err(cmd::Error::Io)?;

let result = slot.wait().await;
let return_param_bytes = RemainingBytes::from_hci_bytes_complete(&retval.as_ref()[..result.len]).unwrap();
Expand All @@ -161,7 +250,7 @@ where
self.slots.release_slot(idx);
});

self.write(cmd).await.map_err(cmd::Error::Io)?;
self.transport.write(cmd).await.map_err(cmd::Error::Io)?;

let result = slot.wait().await;
result.status.to_result()?;
Expand Down
22 changes: 22 additions & 0 deletions src/controller/blocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::controller::ErrorType;
use crate::{data, ControllerToHostPacket};

pub trait Controller: ErrorType {
fn write_acl_data(&self, packet: &data::AclPacket) -> Result<(), Self::Error>;
fn write_sync_data(&self, packet: &data::SyncPacket) -> Result<(), Self::Error>;
fn write_iso_data(&self, packet: &data::IsoPacket) -> Result<(), Self::Error>;

fn try_write_acl_data(&self, packet: &data::AclPacket) -> Result<(), TryError<Self::Error>>;
fn try_write_sync_data(&self, packet: &data::SyncPacket) -> Result<(), TryError<Self::Error>>;
fn try_write_iso_data(&self, packet: &data::IsoPacket) -> Result<(), TryError<Self::Error>>;

fn read<'a>(&self, buf: &'a mut [u8]) -> Result<ControllerToHostPacket<'a>, Self::Error>;
fn try_read<'a>(&self, buf: &'a mut [u8]) -> Result<ControllerToHostPacket<'a>, TryError<Self::Error>>;
}

#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum TryError<E> {
Error(E),
Busy,
}
49 changes: 45 additions & 4 deletions src/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ use core::future::Future;

use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
use embedded_io::ReadExactError;
use embedded_io::{ErrorType, ReadExactError};

use crate::controller::blocking::TryError;
use crate::{ControllerToHostPacket, FromHciBytesError, HostToControllerPacket, ReadHci, ReadHciError, WriteHci};

/// A packet-oriented HCI Transport Layer
pub trait Transport {
type Error: embedded_io::Error;
pub trait Transport: embedded_io::ErrorType {
/// Read a complete HCI packet into the rx buffer
fn read<'a>(&self, rx: &'a mut [u8]) -> impl Future<Output = Result<ControllerToHostPacket<'a>, Self::Error>>;
/// Write a complete HCI packet from the tx buffer
Expand Down Expand Up @@ -73,14 +73,23 @@ impl<M: RawMutex, R: embedded_io_async::Read, W: embedded_io_async::Write> Seria
}
}

impl<
M: RawMutex,
R: embedded_io::ErrorType<Error = E>,
W: embedded_io::ErrorType<Error = E>,
E: embedded_io::Error,
> ErrorType for SerialTransport<M, R, W>
{
type Error = Error<E>;
}

impl<
M: RawMutex,
R: embedded_io_async::Read<Error = E>,
W: embedded_io_async::Write<Error = E>,
E: embedded_io::Error,
> Transport for SerialTransport<M, R, W>
{
type Error = Error<E>;
async fn read<'a>(&self, rx: &'a mut [u8]) -> Result<ControllerToHostPacket<'a>, Self::Error> {
let mut r = self.reader.lock().await;
ControllerToHostPacket::read_hci_async(&mut *r, rx)
Expand All @@ -97,6 +106,25 @@ impl<
}
}

impl<M: RawMutex, R: embedded_io::Read<Error = E>, W: embedded_io::Write<Error = E>, E: embedded_io::Error>
blocking::Transport for SerialTransport<M, R, W>
{
fn read<'a>(&self, rx: &'a mut [u8]) -> Result<ControllerToHostPacket<'a>, TryError<Self::Error>> {
let mut r = self.reader.try_lock().map_err(|_| TryError::Busy)?;
ControllerToHostPacket::read_hci(&mut *r, rx)
.map_err(Error::Read)
.map_err(TryError::Error)
}

fn write<T: HostToControllerPacket>(&self, tx: &T) -> Result<(), TryError<Self::Error>> {
let mut w = self.writer.try_lock().map_err(|_| TryError::Busy)?;
WithIndicator(tx)
.write_hci(&mut *w)
.map_err(|e| Error::Write(e))
.map_err(TryError::Error)
}
}

/// Wrapper for a [`HostToControllerPacket`] that will write the [`PacketKind`](crate::PacketKind) indicator byte before the packet itself
/// when serialized with [`WriteHci`].
///
Expand Down Expand Up @@ -127,3 +155,16 @@ impl<'a, T: HostToControllerPacket> WriteHci for WithIndicator<'a, T> {
self.0.write_hci_async(writer).await
}
}

pub mod blocking {
use super::*;
use crate::controller::blocking::TryError;

/// A packet-oriented HCI Transport Layer
pub trait Transport: embedded_io::ErrorType {
/// Read a complete HCI packet into the rx buffer
fn read<'a>(&self, rx: &'a mut [u8]) -> Result<ControllerToHostPacket<'a>, TryError<Self::Error>>;
/// Write a complete HCI packet from the tx buffer
fn write<T: HostToControllerPacket>(&self, val: &T) -> Result<(), TryError<Self::Error>>;
}
}

0 comments on commit 32eba64

Please sign in to comment.