From d97724cca3164250118c732759f403f4f94d4629 Mon Sep 17 00:00:00 2001
From: goueslati <ghaith.oueslati@habemus.com>
Date: Mon, 15 May 2023 10:25:02 +0100
Subject: [PATCH] tl_mbox read and write

---
 embassy-stm32/src/ipcc.rs                 |  15 ++
 embassy-stm32/src/tl_mbox/ble.rs          |  48 ++++++-
 embassy-stm32/src/tl_mbox/channels.rs     |   8 +-
 embassy-stm32/src/tl_mbox/consts.rs       |  53 +++++++
 embassy-stm32/src/tl_mbox/evt.rs          | 163 ++++++++++++++++++++++
 embassy-stm32/src/tl_mbox/mm.rs           |  47 ++++++-
 embassy-stm32/src/tl_mbox/mod.rs          |  77 ++++++++--
 embassy-stm32/src/tl_mbox/shci.rs         | 101 ++++++++++++++
 embassy-stm32/src/tl_mbox/sys.rs          |  69 ++++++++-
 examples/stm32wb/src/bin/tl_mbox.rs       |   6 +-
 examples/stm32wb/src/bin/tl_mbox_tx_rx.rs |  96 +++++++++++++
 11 files changed, 661 insertions(+), 22 deletions(-)
 create mode 100644 embassy-stm32/src/tl_mbox/consts.rs
 create mode 100644 embassy-stm32/src/tl_mbox/shci.rs
 create mode 100644 examples/stm32wb/src/bin/tl_mbox_tx_rx.rs

diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs
index 903aeca30..9af5f171f 100644
--- a/embassy-stm32/src/ipcc.rs
+++ b/embassy-stm32/src/ipcc.rs
@@ -158,6 +158,10 @@ impl<'d> Ipcc<'d> {
     pub fn is_rx_pending(&self, channel: IpccChannel) -> bool {
         self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel)
     }
+
+    pub fn as_mut_ptr(&self) -> *mut Self {
+        unsafe { &mut core::ptr::read(self) as *mut _ }
+    }
 }
 
 impl sealed::Instance for crate::peripherals::IPCC {
@@ -176,3 +180,14 @@ unsafe fn _configure_pwr() {
     // set RF wake-up clock = LSE
     rcc.csr().modify(|w| w.set_rfwkpsel(0b01));
 }
+
+// TODO: if anyone has a better idea, please let me know
+/// extension trait that constrains the [`Ipcc`] peripheral
+pub trait IpccExt<'d> {
+    fn constrain(self) -> Ipcc<'d>;
+}
+impl<'d> IpccExt<'d> for IPCC {
+    fn constrain(self) -> Ipcc<'d> {
+        Ipcc { _peri: self.into_ref() }
+    }
+}
diff --git a/embassy-stm32/src/tl_mbox/ble.rs b/embassy-stm32/src/tl_mbox/ble.rs
index a2c0758d1..a285e314c 100644
--- a/embassy-stm32/src/tl_mbox/ble.rs
+++ b/embassy-stm32/src/tl_mbox/ble.rs
@@ -1,13 +1,22 @@
 use core::mem::MaybeUninit;
 
-use super::unsafe_linked_list::LST_init_head;
-use super::{channels, BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE};
+use embassy_futures::block_on;
+
+use super::cmd::CmdSerial;
+use super::consts::TlPacketType;
+use super::evt::EvtBox;
+use super::unsafe_linked_list::{LST_init_head, LST_is_empty, LST_remove_head};
+use super::{
+    channels, BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE, TL_CHANNEL,
+    TL_REF_TABLE,
+};
 use crate::ipcc::Ipcc;
+use crate::tl_mbox::cmd::CmdPacket;
 
 pub struct Ble;
 
 impl Ble {
-    pub fn new(ipcc: &mut Ipcc) -> Self {
+    pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
         unsafe {
             LST_init_head(EVT_QUEUE.as_mut_ptr());
 
@@ -23,4 +32,37 @@ impl Ble {
 
         Ble
     }
+
+    pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
+        unsafe {
+            let mut node_ptr = core::ptr::null_mut();
+            let node_ptr_ptr: *mut _ = &mut node_ptr;
+
+            while !LST_is_empty(EVT_QUEUE.as_mut_ptr()) {
+                LST_remove_head(EVT_QUEUE.as_mut_ptr(), node_ptr_ptr);
+
+                let event = node_ptr.cast();
+                let event = EvtBox::new(event);
+
+                block_on(TL_CHANNEL.send(event));
+            }
+        }
+
+        ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL);
+    }
+
+    pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
+        unsafe {
+            let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer;
+            let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial;
+            let pcmd_serial_buf: *mut u8 = pcmd_serial.cast();
+
+            core::ptr::copy(buf.as_ptr(), pcmd_serial_buf, buf.len());
+
+            let mut cmd_packet = &mut *(*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer;
+            cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8;
+        }
+
+        ipcc.c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL);
+    }
 }
diff --git a/embassy-stm32/src/tl_mbox/channels.rs b/embassy-stm32/src/tl_mbox/channels.rs
index 1dde5d61c..aaa6ce177 100644
--- a/embassy-stm32/src/tl_mbox/channels.rs
+++ b/embassy-stm32/src/tl_mbox/channels.rs
@@ -52,9 +52,9 @@
 pub mod cpu1 {
     use crate::ipcc::IpccChannel;
 
-    #[allow(dead_code)] // Not used currently but reserved
+    // Not used currently but reserved
     pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1;
-    #[allow(dead_code)] // Not used currently but reserved
+    // Not used currently but reserved
     pub const IPCC_SYSTEM_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel2;
     #[allow(dead_code)] // Not used currently but reserved
     pub const IPCC_THREAD_OT_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3;
@@ -62,7 +62,7 @@ pub mod cpu1 {
     pub const IPCC_ZIGBEE_CMD_APPLI_CHANNEL: IpccChannel = IpccChannel::Channel3;
     #[allow(dead_code)] // Not used currently but reserved
     pub const IPCC_MAC_802_15_4_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3;
-    #[allow(dead_code)] // Not used currently but reserved
+    // Not used currently but reserved
     pub const IPCC_MM_RELEASE_BUFFER_CHANNEL: IpccChannel = IpccChannel::Channel4;
     #[allow(dead_code)] // Not used currently but reserved
     pub const IPCC_THREAD_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5;
@@ -88,7 +88,7 @@ pub mod cpu2 {
     #[allow(dead_code)] // Not used currently but reserved
     pub const IPCC_LDDTESTS_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3;
     #[allow(dead_code)] // Not used currently but reserved
-    pub const IPCC_BLE_LLDÇM0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3;
+    pub const IPCC_BLE_LLD_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3;
     #[allow(dead_code)] // Not used currently but reserved
     pub const IPCC_TRACES_CHANNEL: IpccChannel = IpccChannel::Channel4;
     #[allow(dead_code)] // Not used currently but reserved
diff --git a/embassy-stm32/src/tl_mbox/consts.rs b/embassy-stm32/src/tl_mbox/consts.rs
new file mode 100644
index 000000000..e16a26cd0
--- /dev/null
+++ b/embassy-stm32/src/tl_mbox/consts.rs
@@ -0,0 +1,53 @@
+#[derive(PartialEq)]
+#[repr(C)]
+pub enum TlPacketType {
+    BleCmd = 0x01,
+    AclData = 0x02,
+    BleEvt = 0x04,
+
+    OtCmd = 0x08,
+    OtRsp = 0x09,
+    CliCmd = 0x0A,
+    OtNot = 0x0C,
+    OtAck = 0x0D,
+    CliNot = 0x0E,
+    CliAck = 0x0F,
+
+    SysCmd = 0x10,
+    SysRsp = 0x11,
+    SysEvt = 0x12,
+
+    LocCmd = 0x20,
+    LocRsp = 0x21,
+
+    TracesApp = 0x40,
+    TracesWl = 0x41,
+}
+
+impl TryFrom<u8> for TlPacketType {
+    type Error = ();
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0x01 => Ok(TlPacketType::BleCmd),
+            0x02 => Ok(TlPacketType::AclData),
+            0x04 => Ok(TlPacketType::BleEvt),
+            0x08 => Ok(TlPacketType::OtCmd),
+            0x09 => Ok(TlPacketType::OtRsp),
+            0x0A => Ok(TlPacketType::CliCmd),
+            0x0C => Ok(TlPacketType::OtNot),
+            0x0D => Ok(TlPacketType::OtAck),
+            0x0E => Ok(TlPacketType::CliNot),
+            0x0F => Ok(TlPacketType::CliAck),
+            0x10 => Ok(TlPacketType::SysCmd),
+            0x11 => Ok(TlPacketType::SysRsp),
+            0x12 => Ok(TlPacketType::SysEvt),
+            0x20 => Ok(TlPacketType::LocCmd),
+            0x21 => Ok(TlPacketType::LocRsp),
+            0x40 => Ok(TlPacketType::TracesApp),
+            0x41 => Ok(TlPacketType::TracesWl),
+
+            _ => Err(()),
+        }
+    }
+}
diff --git a/embassy-stm32/src/tl_mbox/evt.rs b/embassy-stm32/src/tl_mbox/evt.rs
index 4244db810..8ae2a2559 100644
--- a/embassy-stm32/src/tl_mbox/evt.rs
+++ b/embassy-stm32/src/tl_mbox/evt.rs
@@ -1,3 +1,10 @@
+use core::mem::MaybeUninit;
+
+use super::cmd::{AclDataPacket, AclDataSerial};
+use super::consts::TlPacketType;
+use super::{PacketHeader, TL_EVT_HEADER_SIZE};
+use crate::tl_mbox::mm;
+
 /// the payload of [`Evt`] for a command status event
 #[derive(Copy, Clone)]
 #[repr(C, packed)]
@@ -6,3 +13,159 @@ pub struct CsEvt {
     pub num_cmd: u8,
     pub cmd_code: u16,
 }
+
+/// the payload of [`Evt`] for a command complete event
+#[derive(Clone, Copy, Default)]
+#[repr(C, packed)]
+pub struct CcEvt {
+    pub num_cmd: u8,
+    pub cmd_code: u8,
+    pub payload: [u8; 1],
+}
+
+#[derive(Clone, Copy, Default)]
+#[repr(C, packed)]
+pub struct Evt {
+    pub evt_code: u8,
+    pub payload_len: u8,
+    pub payload: [u8; 1],
+}
+
+#[derive(Clone, Copy, Default)]
+#[repr(C, packed)]
+pub struct EvtSerial {
+    pub kind: u8,
+    pub evt: Evt,
+}
+
+/// This format shall be used for all events (asynchronous and command response) reported
+/// by the CPU2 except for the command response of a system command where the header is not there
+/// and the format to be used shall be `EvtSerial`.
+///
+/// ### Note:
+/// Be careful that the asynchronous events reported by the CPU2 on the system channel do
+/// include the header and shall use `EvtPacket` format. Only the command response format on the
+/// system channel is different.
+#[derive(Clone, Copy, Default)]
+#[repr(C, packed)]
+pub struct EvtPacket {
+    pub header: PacketHeader,
+    pub evt_serial: EvtSerial,
+}
+
+/// Smart pointer to the [`EvtPacket`] that will dispose of it automatically on drop
+pub struct EvtBox {
+    ptr: *mut EvtPacket,
+}
+
+unsafe impl Send for EvtBox {}
+impl EvtBox {
+    pub(super) fn new(ptr: *mut EvtPacket) -> Self {
+        Self { ptr }
+    }
+
+    /// Copies the event data from inner pointer and returns and event structure
+    pub fn evt(&self) -> EvtPacket {
+        let mut evt = MaybeUninit::uninit();
+        unsafe {
+            self.ptr.copy_to(evt.as_mut_ptr(), 1);
+            evt.assume_init()
+        }
+    }
+
+    /// Returns the size of a buffer required to hold this event
+    pub fn size(&self) -> Result<usize, ()> {
+        unsafe {
+            let evt_kind = TlPacketType::try_from((*self.ptr).evt_serial.kind)?;
+
+            if evt_kind == TlPacketType::AclData {
+                let acl_data: *const AclDataPacket = self.ptr.cast();
+                let acl_serial: *const AclDataSerial = &(*acl_data).acl_data_serial;
+
+                Ok((*acl_serial).length as usize + 5)
+            } else {
+                let evt_data: *const EvtPacket = self.ptr.cast();
+                let evt_serial: *const EvtSerial = &(*evt_data).evt_serial;
+
+                Ok((*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE)
+            }
+        }
+    }
+
+    /// writes an underlying [`EvtPacket`] into the provided buffer. Returns the number of bytes that were
+    /// written. Returns an error if event kind is unkown or if provided buffer size is not enough
+    pub fn copy_into_slice(&self, buf: &mut [u8]) -> Result<usize, ()> {
+        // TODO: double check this
+        // unsafe {
+        //     let evt_kind = TlPacketType::try_from((*self.ptr).evt_serial.kind)?;
+
+        //     if let TlPacketType::AclData = evt_kind {
+        //         let acl_data: *const AclDataPacket = self.ptr.cast();
+        //         let acl_serial: *const AclDataSerial = &(*acl_data).acl_data_serial;
+        //         let acl_serial_buf: *const u8 = acl_serial.cast();
+
+        //         let len = (*acl_serial).length as usize + 5;
+        //         if len > buf.len() {
+        //             return Err(());
+        //         }
+
+        //         core::ptr::copy(acl_serial_buf, buf.as_mut_ptr(), len);
+
+        //         Ok(len)
+        //     } else {
+        //         let evt_data: *const EvtPacket = self.ptr.cast();
+        //         let evt_serial: *const EvtSerial = &(*evt_data).evt_serial;
+        //         let evt_serial_buf: *const u8 = evt_serial.cast();
+
+        //         let len = (*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE;
+        //         if len > buf.len() {
+        //             return Err(());
+        //         }
+
+        //         core::ptr::copy(evt_serial_buf, buf.as_mut_ptr(), len);
+
+        //         Ok(len)
+        //     }
+        // }
+
+        unsafe {
+            let evt_kind = TlPacketType::try_from((*self.ptr).evt_serial.kind)?;
+
+            let evt_data: *const EvtPacket = self.ptr.cast();
+            let evt_serial: *const EvtSerial = &(*evt_data).evt_serial;
+            let evt_serial_buf: *const u8 = evt_serial.cast();
+
+            let acl_data: *const AclDataPacket = self.ptr.cast();
+            let acl_serial: *const AclDataSerial = &(*acl_data).acl_data_serial;
+            let acl_serial_buf: *const u8 = acl_serial.cast();
+
+            if let TlPacketType::AclData = evt_kind {
+                let len = (*acl_serial).length as usize + 5;
+                if len > buf.len() {
+                    return Err(());
+                }
+
+                core::ptr::copy(evt_serial_buf, buf.as_mut_ptr(), len);
+
+                Ok(len)
+            } else {
+                let len = (*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE;
+                if len > buf.len() {
+                    return Err(());
+                }
+
+                core::ptr::copy(acl_serial_buf, buf.as_mut_ptr(), len);
+
+                Ok(len)
+            }
+        }
+    }
+}
+
+impl Drop for EvtBox {
+    fn drop(&mut self) {
+        use crate::ipcc::IpccExt;
+        let mut ipcc = unsafe { crate::Peripherals::steal() }.IPCC.constrain();
+        mm::MemoryManager::evt_drop(self.ptr, &mut ipcc);
+    }
+}
diff --git a/embassy-stm32/src/tl_mbox/mm.rs b/embassy-stm32/src/tl_mbox/mm.rs
index cf4797305..588b32919 100644
--- a/embassy-stm32/src/tl_mbox/mm.rs
+++ b/embassy-stm32/src/tl_mbox/mm.rs
@@ -1,10 +1,12 @@
 use core::mem::MaybeUninit;
 
-use super::unsafe_linked_list::LST_init_head;
+use super::evt::EvtPacket;
+use super::unsafe_linked_list::{LST_init_head, LST_insert_tail, LST_is_empty, LST_remove_head};
 use super::{
-    MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUFF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE, SYS_SPARE_EVT_BUF,
-    TL_MEM_MANAGER_TABLE,
+    channels, MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUFF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE,
+    SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE, TL_REF_TABLE,
 };
+use crate::ipcc::Ipcc;
 
 pub struct MemoryManager;
 
@@ -27,4 +29,43 @@ impl MemoryManager {
 
         MemoryManager
     }
+
+    pub fn evt_handler(ipcc: &mut Ipcc) {
+        ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false);
+        Self::send_free_buf();
+        ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
+    }
+
+    pub fn evt_drop(evt: *mut EvtPacket, ipcc: &mut Ipcc) {
+        unsafe {
+            let list_node = evt.cast();
+
+            LST_insert_tail(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), list_node);
+        }
+
+        let channel_is_busy = ipcc.c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
+
+        // postpone event buffer freeing to IPCC interrupt handler
+        if channel_is_busy {
+            ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true);
+        } else {
+            Self::send_free_buf();
+            ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
+        }
+    }
+
+    fn send_free_buf() {
+        unsafe {
+            let mut node_ptr = core::ptr::null_mut();
+            let node_ptr_ptr: *mut _ = &mut node_ptr;
+
+            while !LST_is_empty(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()) {
+                LST_remove_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), node_ptr_ptr);
+                LST_insert_tail(
+                    (*(*TL_REF_TABLE.as_ptr()).mem_manager_table).pevt_free_buffer_queue,
+                    node_ptr,
+                );
+            }
+        }
+    }
 }
diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs
index 0cee26b74..3651b8ea5 100644
--- a/embassy-stm32/src/tl_mbox/mod.rs
+++ b/embassy-stm32/src/tl_mbox/mod.rs
@@ -1,20 +1,27 @@
 use core::mem::MaybeUninit;
 
 use bit_field::BitField;
+use embassy_cortex_m::interrupt::InterruptExt;
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
+use embassy_sync::channel::Channel;
 
 use self::ble::Ble;
 use self::cmd::{AclDataPacket, CmdPacket};
-use self::evt::CsEvt;
+use self::evt::{CsEvt, EvtBox};
 use self::mm::MemoryManager;
+use self::shci::{shci_ble_init, ShciBleInitCmdParam};
 use self::sys::Sys;
 use self::unsafe_linked_list::LinkedListNode;
+use crate::_generated::interrupt::{IPCC_C1_RX, IPCC_C1_TX};
 use crate::ipcc::Ipcc;
 
 mod ble;
 mod channels;
 mod cmd;
+mod consts;
 mod evt;
 mod mm;
+mod shci;
 mod sys;
 mod unsafe_linked_list;
 
@@ -236,18 +243,14 @@ static mut FREE_BUFF_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
 // not in shared RAM
 static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
 
-#[allow(dead_code)] // Not used currently but reserved
-#[link_section = "MB_MEM1"]
-static mut TRACES_EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
-
 #[link_section = "MB_MEM2"]
 static mut CS_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE]> =
     MaybeUninit::uninit();
 
-#[link_section = "MB_MEM1"]
+#[link_section = "MB_MEM2"]
 static mut EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
 
-#[link_section = "MB_MEM1"]
+#[link_section = "MB_MEM2"]
 static mut SYSTEM_EVT_QUEUE: MaybeUninit<LinkedListNode> = MaybeUninit::uninit();
 
 #[link_section = "MB_MEM2"]
@@ -271,6 +274,9 @@ static mut BLE_CMD_BUFFER: MaybeUninit<CmdPacket> = MaybeUninit::uninit();
 //                                            "magic" numbers from ST ---v---v
 static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251]> = MaybeUninit::uninit();
 
+// TODO: get a better size, this is a placeholder
+pub(crate) static TL_CHANNEL: Channel<CriticalSectionRawMutex, EvtBox, 5> = Channel::new();
+
 pub struct TlMbox {
     _sys: Sys,
     _ble: Ble,
@@ -279,7 +285,7 @@ pub struct TlMbox {
 
 impl TlMbox {
     /// initializes low-level transport between CPU1 and BLE stack on CPU2
-    pub fn init(ipcc: &mut Ipcc) -> TlMbox {
+    pub fn init(ipcc: &mut Ipcc, rx_irq: IPCC_C1_RX, tx_irq: IPCC_C1_TX) -> TlMbox {
         unsafe {
             TL_REF_TABLE = MaybeUninit::new(RefTable {
                 device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(),
@@ -320,6 +326,24 @@ impl TlMbox {
         let _ble = Ble::new(ipcc);
         let _mm = MemoryManager::new();
 
+        rx_irq.disable();
+        tx_irq.disable();
+
+        rx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
+        tx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ());
+
+        rx_irq.set_handler(|ipcc| {
+            let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
+            Self::interrupt_ipcc_rx_handler(ipcc);
+        });
+        tx_irq.set_handler(|ipcc| {
+            let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() };
+            Self::interrupt_ipcc_tx_handler(ipcc);
+        });
+
+        rx_irq.enable();
+        tx_irq.enable();
+
         TlMbox { _sys, _ble, _mm }
     }
 
@@ -333,4 +357,41 @@ impl TlMbox {
             None
         }
     }
+
+    pub fn shci_ble_init(&self, ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
+        shci_ble_init(ipcc, param);
+    }
+
+    pub fn send_ble_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
+        ble::Ble::send_cmd(ipcc, buf);
+    }
+
+    // pub fn send_sys_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
+    //     sys::Sys::send_cmd(ipcc, buf);
+    // }
+
+    pub async fn read(&self) -> EvtBox {
+        TL_CHANNEL.recv().await
+    }
+
+    fn interrupt_ipcc_rx_handler(ipcc: &mut Ipcc) {
+        if ipcc.is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) {
+            sys::Sys::evt_handler(ipcc);
+        } else if ipcc.is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) {
+            ble::Ble::evt_handler(ipcc);
+        } else {
+            todo!()
+        }
+    }
+
+    fn interrupt_ipcc_tx_handler(ipcc: &mut Ipcc) {
+        if ipcc.is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) {
+            // TODO: handle this case
+            let _ = sys::Sys::cmd_evt_handler(ipcc);
+        } else if ipcc.is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) {
+            mm::MemoryManager::evt_handler(ipcc);
+        } else {
+            todo!()
+        }
+    }
 }
diff --git a/embassy-stm32/src/tl_mbox/shci.rs b/embassy-stm32/src/tl_mbox/shci.rs
new file mode 100644
index 000000000..7f224cb1a
--- /dev/null
+++ b/embassy-stm32/src/tl_mbox/shci.rs
@@ -0,0 +1,101 @@
+//! HCI commands for system channel
+
+use super::cmd::CmdPacket;
+use super::consts::TlPacketType;
+use super::{channels, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE, TL_SYS_TABLE};
+use crate::ipcc::Ipcc;
+
+const SCHI_OPCODE_BLE_INIT: u16 = 0xfc66;
+pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE;
+#[allow(dead_code)]
+const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE;
+
+#[derive(Clone, Copy)]
+#[repr(C, packed)]
+pub struct ShciBleInitCmdParam {
+    /// NOT USED CURRENTLY
+    pub p_ble_buffer_address: u32,
+
+    /// Size of the Buffer allocated in pBleBufferAddress
+    pub ble_buffer_size: u32,
+
+    pub num_attr_record: u16,
+    pub num_attr_serv: u16,
+    pub attr_value_arr_size: u16,
+    pub num_of_links: u8,
+    pub extended_packet_length_enable: u8,
+    pub pr_write_list_size: u8,
+    pub mb_lock_count: u8,
+
+    pub att_mtu: u16,
+    pub slave_sca: u16,
+    pub master_sca: u8,
+    pub ls_source: u8,
+    pub max_conn_event_length: u32,
+    pub hs_startup_time: u16,
+    pub viterbi_enable: u8,
+    pub ll_only: u8,
+    pub hw_version: u8,
+}
+
+impl Default for ShciBleInitCmdParam {
+    fn default() -> Self {
+        Self {
+            p_ble_buffer_address: 0,
+            ble_buffer_size: 0,
+            num_attr_record: 68,
+            num_attr_serv: 8,
+            attr_value_arr_size: 1344,
+            num_of_links: 2,
+            extended_packet_length_enable: 1,
+            pr_write_list_size: 0x3A,
+            mb_lock_count: 0x79,
+            att_mtu: 156,
+            slave_sca: 500,
+            master_sca: 0,
+            ls_source: 1,
+            max_conn_event_length: 0xFFFFFFFF,
+            hs_startup_time: 0x148,
+            viterbi_enable: 1,
+            ll_only: 0,
+            hw_version: 0,
+        }
+    }
+}
+
+#[derive(Clone, Copy, Default)]
+#[repr(C, packed)]
+pub struct ShciHeader {
+    metadata: [u32; 3],
+}
+
+#[derive(Clone, Copy)]
+#[repr(C, packed)]
+pub struct ShciBleInitCmdPacket {
+    header: ShciHeader,
+    param: ShciBleInitCmdParam,
+}
+
+pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
+    let mut packet = ShciBleInitCmdPacket {
+        header: ShciHeader::default(),
+        param,
+    };
+
+    let packet_ptr: *mut ShciBleInitCmdPacket = &mut packet;
+
+    unsafe {
+        let cmd_ptr: *mut CmdPacket = packet_ptr.cast();
+
+        (*cmd_ptr).cmd_serial.cmd.cmd_code = SCHI_OPCODE_BLE_INIT;
+        (*cmd_ptr).cmd_serial.cmd.payload_len = core::mem::size_of::<ShciBleInitCmdParam>() as u8;
+
+        let mut cmd_buf = &mut *(*TL_SYS_TABLE.as_mut_ptr()).pcmd_buffer;
+        core::ptr::write(cmd_buf, *cmd_ptr);
+
+        cmd_buf.cmd_serial.ty = TlPacketType::SysCmd as u8;
+
+        ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
+        ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
+    }
+}
diff --git a/embassy-stm32/src/tl_mbox/sys.rs b/embassy-stm32/src/tl_mbox/sys.rs
index 13ae7f9f9..122657550 100644
--- a/embassy-stm32/src/tl_mbox/sys.rs
+++ b/embassy-stm32/src/tl_mbox/sys.rs
@@ -1,13 +1,18 @@
 use core::mem::MaybeUninit;
 
-use super::unsafe_linked_list::LST_init_head;
-use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_SYS_TABLE};
+use embassy_futures::block_on;
+
+use super::cmd::{CmdPacket, CmdSerial};
+use super::consts::TlPacketType;
+use super::evt::{CcEvt, EvtBox, EvtSerial};
+use super::unsafe_linked_list::{LST_init_head, LST_is_empty, LST_remove_head};
+use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_CHANNEL, TL_REF_TABLE, TL_SYS_TABLE};
 use crate::ipcc::Ipcc;
 
 pub struct Sys;
 
 impl Sys {
-    pub fn new(ipcc: &mut Ipcc) -> Self {
+    pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
         unsafe {
             LST_init_head(SYSTEM_EVT_QUEUE.as_mut_ptr());
 
@@ -21,4 +26,62 @@ impl Sys {
 
         Sys
     }
+
+    pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
+        unsafe {
+            let mut node_ptr = core::ptr::null_mut();
+            let node_ptr_ptr: *mut _ = &mut node_ptr;
+
+            while !LST_is_empty(SYSTEM_EVT_QUEUE.as_mut_ptr()) {
+                LST_remove_head(SYSTEM_EVT_QUEUE.as_mut_ptr(), node_ptr_ptr);
+
+                let event = node_ptr.cast();
+                let event = EvtBox::new(event);
+
+                // TODO: not really happy about this
+                block_on(TL_CHANNEL.send(event));
+            }
+        }
+
+        ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL);
+    }
+
+    pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) -> CcEvt {
+        ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false);
+
+        // ST's command response data structure is really convoluted.
+        //
+        // for command response events on SYS channel, the header is missing
+        // and one should:
+        // 1. interpret the content of CMD_BUFFER as CmdPacket
+        // 2. Access CmdPacket's cmdserial field and interpret its content as EvtSerial
+        // 3. Access EvtSerial's evt field (as Evt) and interpret its payload as CcEvt
+        // 4. CcEvt type is the actual SHCI response
+        // 5. profit
+        unsafe {
+            let cmd: *const CmdPacket = (*TL_SYS_TABLE.as_ptr()).pcmd_buffer;
+            let cmd_serial: *const CmdSerial = &(*cmd).cmd_serial;
+            let evt_serial: *const EvtSerial = cmd_serial.cast();
+            let cc = (*evt_serial).evt.payload.as_ptr().cast();
+            *cc
+        }
+    }
+
+    #[allow(dead_code)]
+    pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
+        unsafe {
+            // TODO: check this
+            let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
+            let cmd_serial: *mut CmdSerial = &mut (*cmd_buffer).cmd_serial;
+            let cmd_serial_buf = cmd_serial.cast();
+
+            core::ptr::copy(buf.as_ptr(), cmd_serial_buf, buf.len());
+
+            let mut cmd_packet = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer;
+            cmd_packet.cmd_serial.ty = TlPacketType::SysCmd as u8;
+
+            ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL);
+            ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true);
+        }
+    }
 }
diff --git a/examples/stm32wb/src/bin/tl_mbox.rs b/examples/stm32wb/src/bin/tl_mbox.rs
index 6876526ae..ccd01cbc7 100644
--- a/examples/stm32wb/src/bin/tl_mbox.rs
+++ b/examples/stm32wb/src/bin/tl_mbox.rs
@@ -4,6 +4,7 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
+use embassy_stm32::interrupt;
 use embassy_stm32::ipcc::{Config, Ipcc};
 use embassy_stm32::tl_mbox::TlMbox;
 use embassy_time::{Duration, Timer};
@@ -40,7 +41,10 @@ async fn main(_spawner: Spawner) {
     let config = Config::default();
     let mut ipcc = Ipcc::new(p.IPCC, config);
 
-    let mbox = TlMbox::init(&mut ipcc);
+    let rx_irq = interrupt::take!(IPCC_C1_RX);
+    let tx_irq = interrupt::take!(IPCC_C1_TX);
+
+    let mbox = TlMbox::init(&mut ipcc, rx_irq, tx_irq);
 
     loop {
         let wireless_fw_info = mbox.wireless_fw_info();
diff --git a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
new file mode 100644
index 000000000..315172df8
--- /dev/null
+++ b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
@@ -0,0 +1,96 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_stm32::interrupt;
+use embassy_stm32::ipcc::{Config, Ipcc};
+use embassy_stm32::tl_mbox::TlMbox;
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+    /*
+        How to make this work:
+
+        - Obtain a NUCLEO-STM32WB55 from your preferred supplier.
+        - Download and Install STM32CubeProgrammer.
+        - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from
+          gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x
+        - Open STM32CubeProgrammer
+        - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware.
+        - Once complete, click connect to connect to the device.
+        - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services".
+        - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the
+          stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address.
+        - Select that file, the memory address, "verify download", and then "Firmware Upgrade".
+        - Disconnect from the device.
+        - In the examples folder for stm32wb, modify the memory.x file to match your target device.
+        - Run this example.
+
+        Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name.
+    */
+
+    let p = embassy_stm32::init(Default::default());
+    info!("Hello World!");
+
+    let config = Config::default();
+    let mut ipcc = Ipcc::new(p.IPCC, config);
+
+    let rx_irq = interrupt::take!(IPCC_C1_RX);
+    let tx_irq = interrupt::take!(IPCC_C1_TX);
+
+    let mbox = TlMbox::init(&mut ipcc, rx_irq, tx_irq);
+
+    // initialize ble stack, does not return a response
+    // mbox.shci_ble_init(&mut ipcc, Default::default());
+
+    info!("waiting for coprocessor to boot");
+    let event_box = mbox.read().await;
+
+    let mut payload = [0u8; 6];
+    event_box.copy_into_slice(&mut payload).unwrap();
+
+    let event_packet = event_box.evt();
+    let kind = event_packet.evt_serial.kind;
+
+    // means recieved SYS event, which indicates in this case that the coprocessor is ready
+    if kind == 0x12 {
+        let code = event_packet.evt_serial.evt.evt_code;
+        let payload_len = event_packet.evt_serial.evt.payload_len;
+
+        info!(
+            "==> kind: {:#04x}, code: {:#04x}, payload_length: {}, payload: {:#04x}",
+            kind,
+            code,
+            payload_len,
+            payload[3..]
+        );
+    }
+
+    mbox.shci_ble_init(&mut ipcc, Default::default());
+
+    info!("resetting BLE");
+    mbox.send_ble_cmd(&mut ipcc, &[0x01, 0x03, 0x0c]);
+
+    let event_box = mbox.read().await;
+
+    let mut payload = [0u8; 7];
+    event_box.copy_into_slice(&mut payload).unwrap();
+
+    let event_packet = event_box.evt();
+    let kind = event_packet.evt_serial.kind;
+
+    let code = event_packet.evt_serial.evt.evt_code;
+    let payload_len = event_packet.evt_serial.evt.payload_len;
+
+    info!(
+        "==> kind: {:#04x}, code: {:#04x}, payload_length: {}, payload: {:#04x}",
+        kind, code, payload_len, payload
+    );
+
+    loop {}
+}