diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index b766f0739..730c78f5e 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -912,6 +912,16 @@ fn main() {
         println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]);
     }
 
+    // ========
+    // stm32wb tl_mbox link sections
+
+    if chip_name.starts_with("stm32wb") {
+        let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string();
+        fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap();
+        println!("cargo:rustc-link-search={}", out_dir.display());
+        println!("cargo:rerun-if-changed=tl_mbox.x.in");
+    }
+
     // =======
     // Features for targeting groups of chips
 
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index c9df5c1b2..b9d7a15c7 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -41,8 +41,6 @@ pub mod crc;
 pub mod flash;
 #[cfg(all(spi_v1, rcc_f4))]
 pub mod i2s;
-#[cfg(stm32wb)]
-pub mod ipcc;
 pub mod pwm;
 #[cfg(quadspi)]
 pub mod qspi;
diff --git a/embassy-stm32/src/tl_mbox/ble.rs b/embassy-stm32/src/tl_mbox/ble.rs
index d8bf14d4f..062377999 100644
--- a/embassy-stm32/src/tl_mbox/ble.rs
+++ b/embassy-stm32/src/tl_mbox/ble.rs
@@ -1,5 +1,3 @@
-use core::mem::MaybeUninit;
-
 use embassy_futures::block_on;
 
 use super::cmd::CmdSerial;
@@ -10,17 +8,17 @@ 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;
+use crate::tl_mbox::ipcc::Ipcc;
 
 pub struct Ble;
 
 impl Ble {
-    pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
+    pub fn enable() {
         unsafe {
             LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr());
 
-            TL_BLE_TABLE = MaybeUninit::new(BleTable {
+            TL_BLE_TABLE.as_mut_ptr().write_volatile(BleTable {
                 pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(),
                 pcs_buffer: CS_BUFFER.as_mut_ptr().cast(),
                 pevt_queue: EVT_QUEUE.as_ptr().cast(),
@@ -28,12 +26,10 @@ impl Ble {
             });
         }
 
-        ipcc.c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true);
-
-        Ble
+        Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true);
     }
 
-    pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
+    pub fn evt_handler() {
         unsafe {
             let mut node_ptr = core::ptr::null_mut();
             let node_ptr_ptr: *mut _ = &mut node_ptr;
@@ -48,10 +44,10 @@ impl Ble {
             }
         }
 
-        ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL);
+        Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL);
     }
 
-    pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
+    pub fn send_cmd(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;
@@ -63,6 +59,6 @@ impl Ble {
             cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8;
         }
 
-        ipcc.c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL);
+        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 aaa6ce177..25a065ba4 100644
--- a/embassy-stm32/src/tl_mbox/channels.rs
+++ b/embassy-stm32/src/tl_mbox/channels.rs
@@ -50,7 +50,7 @@
 //!
 
 pub mod cpu1 {
-    use crate::ipcc::IpccChannel;
+    use crate::tl_mbox::ipcc::IpccChannel;
 
     // Not used currently but reserved
     pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1;
@@ -75,7 +75,7 @@ pub mod cpu1 {
 }
 
 pub mod cpu2 {
-    use crate::ipcc::IpccChannel;
+    use crate::tl_mbox::ipcc::IpccChannel;
 
     pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1;
     pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2;
diff --git a/embassy-stm32/src/tl_mbox/evt.rs b/embassy-stm32/src/tl_mbox/evt.rs
index 770133f29..47a8b72fd 100644
--- a/embassy-stm32/src/tl_mbox/evt.rs
+++ b/embassy-stm32/src/tl_mbox/evt.rs
@@ -3,7 +3,7 @@ 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;
+use crate::tl_mbox::mm::MemoryManager;
 
 /// the payload of [`Evt`] for a command status event
 #[derive(Copy, Clone)]
@@ -131,9 +131,6 @@ impl EvtBox {
 
 impl Drop for EvtBox {
     fn drop(&mut self) {
-        use crate::ipcc::Ipcc;
-
-        let mut ipcc = Ipcc::new_inner(unsafe { crate::Peripherals::steal() }.IPCC);
-        mm::MemoryManager::evt_drop(self.ptr, &mut ipcc);
+        MemoryManager::evt_drop(self.ptr);
     }
 }
diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/tl_mbox/ipcc.rs
similarity index 65%
rename from embassy-stm32/src/ipcc.rs
rename to embassy-stm32/src/tl_mbox/ipcc.rs
index 2b9caf8e5..d1ac731ed 100644
--- a/embassy-stm32/src/ipcc.rs
+++ b/embassy-stm32/src/tl_mbox/ipcc.rs
@@ -1,6 +1,4 @@
-use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
-
-use crate::ipcc::sealed::Instance;
+use self::sealed::Instance;
 use crate::peripherals::IPCC;
 use crate::rcc::sealed::RccPeripheral;
 
@@ -22,29 +20,17 @@ pub enum IpccChannel {
     Channel6 = 5,
 }
 
-pub(crate) mod sealed {
+pub mod sealed {
     pub trait Instance: crate::rcc::RccPeripheral {
         fn regs() -> crate::pac::ipcc::Ipcc;
         fn set_cpu2(enabled: bool);
     }
 }
 
-pub struct Ipcc<'d> {
-    _peri: PeripheralRef<'d, IPCC>,
-}
+pub struct Ipcc;
 
-impl<'d> Ipcc<'d> {
-    pub fn new(peri: impl Peripheral<P = IPCC> + 'd, _config: Config) -> Self {
-        Self::new_inner(peri)
-    }
-
-    pub(crate) fn new_inner(peri: impl Peripheral<P = IPCC> + 'd) -> Self {
-        into_ref!(peri);
-
-        Self { _peri: peri }
-    }
-
-    pub fn init(&mut self) {
+impl Ipcc {
+    pub fn enable(_config: Config) {
         IPCC::enable();
         IPCC::reset();
         IPCC::set_cpu2(true);
@@ -61,56 +47,60 @@ impl<'d> Ipcc<'d> {
         }
     }
 
-    pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+    pub fn c1_set_rx_channel(channel: IpccChannel, enabled: bool) {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
         unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
     }
 
-    pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool {
+    pub fn c1_get_rx_channel(channel: IpccChannel) -> bool {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
         unsafe { !regs.cpu(0).mr().read().chom(channel as usize) }
     }
 
-    pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+    #[allow(dead_code)]
+    pub fn c2_set_rx_channel(channel: IpccChannel, enabled: bool) {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
         unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
     }
 
-    pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool {
+    #[allow(dead_code)]
+    pub fn c2_get_rx_channel(channel: IpccChannel) -> bool {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
         unsafe { !regs.cpu(1).mr().read().chom(channel as usize) }
     }
 
-    pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+    pub fn c1_set_tx_channel(channel: IpccChannel, enabled: bool) {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
         unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
     }
 
-    pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool {
+    pub fn c1_get_tx_channel(channel: IpccChannel) -> bool {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
         unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) }
     }
 
-    pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+    #[allow(dead_code)]
+    pub fn c2_set_tx_channel(channel: IpccChannel, enabled: bool) {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
         unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
     }
 
-    pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool {
+    #[allow(dead_code)]
+    pub fn c2_get_tx_channel(channel: IpccChannel) -> bool {
         let regs = IPCC::regs();
 
         // If bit is set to 1 then interrupt is disabled
@@ -118,53 +108,51 @@ impl<'d> Ipcc<'d> {
     }
 
     /// clears IPCC receive channel status for CPU1
-    pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) {
+    pub fn c1_clear_flag_channel(channel: IpccChannel) {
         let regs = IPCC::regs();
 
         unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) }
     }
 
+    #[allow(dead_code)]
     /// clears IPCC receive channel status for CPU2
-    pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) {
+    pub fn c2_clear_flag_channel(channel: IpccChannel) {
         let regs = IPCC::regs();
 
         unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) }
     }
 
-    pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) {
+    pub fn c1_set_flag_channel(channel: IpccChannel) {
         let regs = IPCC::regs();
 
         unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) }
     }
 
-    pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) {
+    #[allow(dead_code)]
+    pub fn c2_set_flag_channel(channel: IpccChannel) {
         let regs = IPCC::regs();
 
         unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) }
     }
 
-    pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool {
+    pub fn c1_is_active_flag(channel: IpccChannel) -> bool {
         let regs = IPCC::regs();
 
         unsafe { regs.cpu(0).sr().read().chf(channel as usize) }
     }
 
-    pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool {
+    pub fn c2_is_active_flag(channel: IpccChannel) -> bool {
         let regs = IPCC::regs();
 
         unsafe { regs.cpu(1).sr().read().chf(channel as usize) }
     }
 
-    pub fn is_tx_pending(&self, channel: IpccChannel) -> bool {
-        !self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel)
+    pub fn is_tx_pending(channel: IpccChannel) -> bool {
+        !Self::c1_is_active_flag(channel) && Self::c1_get_tx_channel(channel)
     }
 
-    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 _ }
+    pub fn is_rx_pending(channel: IpccChannel) -> bool {
+        Self::c2_is_active_flag(channel) && Self::c1_get_rx_channel(channel)
     }
 }
 
diff --git a/embassy-stm32/src/tl_mbox/mm.rs b/embassy-stm32/src/tl_mbox/mm.rs
index f99ffa399..e28a6aa0c 100644
--- a/embassy-stm32/src/tl_mbox/mm.rs
+++ b/embassy-stm32/src/tl_mbox/mm.rs
@@ -1,22 +1,20 @@
-use core::mem::MaybeUninit;
-
 use super::evt::EvtPacket;
 use super::unsafe_linked_list::LinkedListNode;
 use super::{
     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;
+use crate::tl_mbox::ipcc::Ipcc;
 
 pub struct MemoryManager;
 
 impl MemoryManager {
-    pub fn new() -> Self {
+    pub fn enable() {
         unsafe {
             LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr());
             LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr());
 
-            TL_MEM_MANAGER_TABLE = MaybeUninit::new(MemManagerTable {
+            TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable {
                 spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(),
                 spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(),
                 ble_pool: EVT_POOL.as_ptr().cast(),
@@ -26,31 +24,29 @@ impl MemoryManager {
                 traces_pool_size: 0,
             });
         }
-
-        MemoryManager
     }
 
-    pub fn evt_handler(ipcc: &mut Ipcc) {
-        ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false);
+    pub fn evt_handler() {
+        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);
+        Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
     }
 
-    pub fn evt_drop(evt: *mut EvtPacket, ipcc: &mut Ipcc) {
+    pub fn evt_drop(evt: *mut EvtPacket) {
         unsafe {
             let list_node = evt.cast();
 
             LinkedListNode::remove_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);
+        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);
+            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);
+            Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL);
         }
     }
 
diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs
index dc6104cc3..616f7dc56 100644
--- a/embassy-stm32/src/tl_mbox/mod.rs
+++ b/embassy-stm32/src/tl_mbox/mod.rs
@@ -1,6 +1,9 @@
 use core::mem::MaybeUninit;
 
+use atomic_polyfill::{compiler_fence, Ordering};
 use bit_field::BitField;
+use embassy_cortex_m::interrupt::{Interrupt, InterruptExt};
+use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
 use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
 use embassy_sync::channel::Channel;
 
@@ -12,13 +15,16 @@ use self::shci::{shci_ble_init, ShciBleInitCmdParam};
 use self::sys::Sys;
 use self::unsafe_linked_list::LinkedListNode;
 use crate::interrupt;
-use crate::ipcc::Ipcc;
+use crate::peripherals::IPCC;
+pub use crate::tl_mbox::ipcc::Config;
+use crate::tl_mbox::ipcc::Ipcc;
 
 mod ble;
 mod channels;
 mod cmd;
 mod consts;
 mod evt;
+mod ipcc;
 mod mm;
 mod shci;
 mod sys;
@@ -58,13 +64,34 @@ pub struct FusInfoTable {
 pub struct ReceiveInterruptHandler {}
 
 impl interrupt::Handler<interrupt::IPCC_C1_RX> for ReceiveInterruptHandler {
-    unsafe fn on_interrupt() {}
+    unsafe fn on_interrupt() {
+        // info!("ipcc rx interrupt");
+
+        if Ipcc::is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) {
+            sys::Sys::evt_handler();
+        } else if Ipcc::is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) {
+            ble::Ble::evt_handler();
+        } else {
+            todo!()
+        }
+    }
 }
 
 pub struct TransmitInterruptHandler {}
 
 impl interrupt::Handler<interrupt::IPCC_C1_TX> for TransmitInterruptHandler {
-    unsafe fn on_interrupt() {}
+    unsafe fn on_interrupt() {
+        // info!("ipcc tx interrupt");
+
+        if Ipcc::is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) {
+            // TODO: handle this case
+            let _ = sys::Sys::cmd_evt_handler();
+        } else if Ipcc::is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) {
+            mm::MemoryManager::evt_handler();
+        } else {
+            todo!()
+        }
+    }
 }
 
 /// # Version
@@ -289,21 +316,24 @@ static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251
 // 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,
-    _mm: MemoryManager,
+pub struct TlMbox<'d> {
+    _ipcc: PeripheralRef<'d, IPCC>,
 }
 
-impl TlMbox {
+impl<'d> TlMbox<'d> {
     /// initializes low-level transport between CPU1 and BLE stack on CPU2
-    pub fn init(
-        ipcc: &mut Ipcc,
+    pub fn new(
+        ipcc: impl Peripheral<P = IPCC> + 'd,
         _irqs: impl interrupt::Binding<interrupt::IPCC_C1_RX, ReceiveInterruptHandler>
             + interrupt::Binding<interrupt::IPCC_C1_TX, TransmitInterruptHandler>,
-    ) -> TlMbox {
+        config: Config,
+    ) -> Self {
+        into_ref!(ipcc);
+
         unsafe {
-            TL_REF_TABLE = MaybeUninit::new(RefTable {
+            compiler_fence(Ordering::AcqRel);
+
+            TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable {
                 device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(),
                 ble_table: TL_BLE_TABLE.as_ptr(),
                 thread_table: TL_THREAD_TABLE.as_ptr(),
@@ -316,6 +346,10 @@ impl TlMbox {
                 ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(),
             });
 
+            // info!("TL_REF_TABLE addr: {:x}", TL_REF_TABLE.as_ptr() as usize);
+
+            compiler_fence(Ordering::AcqRel);
+
             TL_SYS_TABLE = MaybeUninit::zeroed();
             TL_DEVICE_INFO_TABLE = MaybeUninit::zeroed();
             TL_BLE_TABLE = MaybeUninit::zeroed();
@@ -334,33 +368,24 @@ impl TlMbox {
             CS_BUFFER = MaybeUninit::zeroed();
             BLE_CMD_BUFFER = MaybeUninit::zeroed();
             HCI_ACL_DATA_BUFFER = MaybeUninit::zeroed();
+
+            compiler_fence(Ordering::AcqRel);
         }
 
-        ipcc.init();
+        Ipcc::enable(config);
 
-        let _sys = Sys::new(ipcc);
-        let _ble = Ble::new(ipcc);
-        let _mm = MemoryManager::new();
+        Sys::enable();
+        Ble::enable();
+        MemoryManager::enable();
 
-        //        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();
+        // enable interrupts
+        unsafe { crate::interrupt::IPCC_C1_RX::steal() }.unpend();
+        unsafe { crate::interrupt::IPCC_C1_TX::steal() }.unpend();
 
-        TlMbox { _sys, _ble, _mm }
+        unsafe { crate::interrupt::IPCC_C1_RX::steal() }.enable();
+        unsafe { crate::interrupt::IPCC_C1_TX::steal() }.enable();
+
+        Self { _ipcc: ipcc }
     }
 
     pub fn wireless_fw_info(&self) -> Option<WirelessFwInfoTable> {
@@ -374,42 +399,19 @@ impl TlMbox {
         }
     }
 
-    pub fn shci_ble_init(&self, ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
-        shci_ble_init(ipcc, param);
+    pub fn shci_ble_init(&self, param: ShciBleInitCmdParam) {
+        shci_ble_init(param);
     }
 
-    pub fn send_ble_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
-        ble::Ble::send_cmd(ipcc, buf);
+    pub fn send_ble_cmd(&self, buf: &[u8]) {
+        ble::Ble::send_cmd(buf);
     }
 
-    // pub fn send_sys_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) {
-    //     sys::Sys::send_cmd(ipcc, buf);
+    // pub fn send_sys_cmd(&self, buf: &[u8]) {
+    //     sys::Sys::send_cmd(buf);
     // }
 
     pub async fn read(&self) -> EvtBox {
         TL_CHANNEL.recv().await
     }
-
-    #[allow(dead_code)]
-    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!()
-        }
-    }
-
-    #[allow(dead_code)]
-    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
index 61fd9e4a3..6b5b2dd19 100644
--- a/embassy-stm32/src/tl_mbox/shci.rs
+++ b/embassy-stm32/src/tl_mbox/shci.rs
@@ -3,7 +3,7 @@
 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;
+use crate::tl_mbox::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;
@@ -76,7 +76,7 @@ pub struct ShciBleInitCmdPacket {
     param: ShciBleInitCmdParam,
 }
 
-pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
+pub fn shci_ble_init(param: ShciBleInitCmdParam) {
     let mut packet = ShciBleInitCmdPacket {
         header: ShciHeader::default(),
         param,
@@ -95,7 +95,7 @@ pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) {
 
         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);
+        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 31ebde721..9685fb920 100644
--- a/embassy-stm32/src/tl_mbox/sys.rs
+++ b/embassy-stm32/src/tl_mbox/sys.rs
@@ -1,5 +1,3 @@
-use core::mem::MaybeUninit;
-
 use embassy_futures::block_on;
 
 use super::cmd::{CmdPacket, CmdSerial};
@@ -7,27 +5,25 @@ use super::consts::TlPacketType;
 use super::evt::{CcEvt, EvtBox, EvtSerial};
 use super::unsafe_linked_list::LinkedListNode;
 use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_CHANNEL, TL_REF_TABLE, TL_SYS_TABLE};
-use crate::ipcc::Ipcc;
+use crate::tl_mbox::ipcc::Ipcc;
 
 pub struct Sys;
 
 impl Sys {
-    pub(crate) fn new(ipcc: &mut Ipcc) -> Self {
+    pub fn enable() {
         unsafe {
             LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr());
 
-            TL_SYS_TABLE = MaybeUninit::new(SysTable {
+            TL_SYS_TABLE.as_mut_ptr().write_volatile(SysTable {
                 pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(),
                 sys_queue: SYSTEM_EVT_QUEUE.as_ptr(),
             });
         }
 
-        ipcc.c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true);
-
-        Sys
+        Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true);
     }
 
-    pub(crate) fn evt_handler(ipcc: &mut Ipcc) {
+    pub fn evt_handler() {
         unsafe {
             let mut node_ptr = core::ptr::null_mut();
             let node_ptr_ptr: *mut _ = &mut node_ptr;
@@ -43,11 +39,11 @@ impl Sys {
             }
         }
 
-        ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL);
+        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);
+    pub fn cmd_evt_handler() -> CcEvt {
+        Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false);
 
         // ST's command response data structure is really convoluted.
         //
@@ -68,11 +64,11 @@ impl Sys {
     }
 
     #[allow(dead_code)]
-    pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) {
+    pub fn send_cmd(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: *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());
@@ -80,8 +76,8 @@ impl Sys {
             let 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);
+            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/tests/stm32/memory_ble.x b/embassy-stm32/tl_mbox.x.in
similarity index 58%
rename from tests/stm32/memory_ble.x
rename to embassy-stm32/tl_mbox.x.in
index 4332e2c72..b6eecb429 100644
--- a/tests/stm32/memory_ble.x
+++ b/embassy-stm32/tl_mbox.x.in
@@ -1,21 +1,13 @@
-/*
-    Memory size for STM32WB55xG with 512K FLASH
-*/
-
-MEMORY
+MEMORY 
 {
-    FLASH (rx)                 : ORIGIN = 0x08000000, LENGTH = 512K
-    RAM (xrw)                  : ORIGIN = 0x20000008, LENGTH = 0x2FFF8
     RAM_SHARED (xrw)           : ORIGIN = 0x20030000, LENGTH = 10K
 }
 
-/* Place stack at the end of SRAM1 */
-_stack_start = ORIGIN(RAM) + LENGTH(RAM);
-
 /*
  * Scatter the mailbox interface memory sections in shared memory
  */
-SECTIONS {
+SECTIONS
+{
     TL_REF_TABLE                     (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED
 
     MB_MEM1 (NOLOAD)                          : { *(MB_MEM1) } >RAM_SHARED
diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml
index 3c7e3e874..8cfac772a 100644
--- a/examples/stm32wb/Cargo.toml
+++ b/examples/stm32wb/Cargo.toml
@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
 embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] }
 embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
 embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
-embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "exti"]  }
+embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"]  }
 
 defmt = "0.3"
 defmt-rtt = "0.4"
diff --git a/examples/stm32wb/build.rs b/examples/stm32wb/build.rs
index 30691aa97..29b3a9b2a 100644
--- a/examples/stm32wb/build.rs
+++ b/examples/stm32wb/build.rs
@@ -1,35 +1,11 @@
-//! This build script copies the `memory.x` file from the crate root into
-//! a directory where the linker can always find it at build time.
-//! For many projects this is optional, as the linker always searches the
-//! project root directory -- wherever `Cargo.toml` is. However, if you
-//! are using a workspace or have a more complicated build setup, this
-//! build script becomes required. Additionally, by requesting that
-//! Cargo re-run the build script whenever `memory.x` is changed,
-//! updating `memory.x` ensures a rebuild of the application with the
-//! new memory settings.
-
-use std::env;
-use std::fs::File;
-use std::io::Write;
-use std::path::PathBuf;
-
-fn main() {
-    // Put `memory.x` in our output directory and ensure it's
-    // on the linker search path.
-    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
-    File::create(out.join("memory.x"))
-        .unwrap()
-        .write_all(include_bytes!("memory.x"))
-        .unwrap();
-    println!("cargo:rustc-link-search={}", out.display());
-
-    // By default, Cargo will re-run a build script whenever
-    // any file in the project changes. By specifying `memory.x`
-    // here, we ensure the build script is only re-run when
-    // `memory.x` is changed.
-    println!("cargo:rerun-if-changed=memory.x");
+use std::error::Error;
 
+fn main() -> Result<(), Box<dyn Error>> {
     println!("cargo:rustc-link-arg-bins=--nmagic");
     println!("cargo:rustc-link-arg-bins=-Tlink.x");
+    println!("cargo:rerun-if-changed=link.x");
+    println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x");
     println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
+
+    Ok(())
 }
diff --git a/examples/stm32wb/memory.x b/examples/stm32wb/memory.x
deleted file mode 100644
index e1f0530bd..000000000
--- a/examples/stm32wb/memory.x
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-    The size of this file must be exactly the same as in other memory_xx.x files.
-    Memory size for STM32WB55xC with 256K FLASH
-*/
-
-MEMORY
-{
-    FLASH (rx)                 : ORIGIN = 0x08000000, LENGTH = 256K
-    RAM (xrw)                  : ORIGIN = 0x20000000, LENGTH = 192K
-    RAM_SHARED (xrw)           : ORIGIN = 0x20030000, LENGTH = 10K
-}
-
-/* 
-    Memory size for STM32WB55xG with 512K FLASH
-
-    MEMORY
-    {
-        FLASH (rx)                 : ORIGIN = 0x08000000, LENGTH = 512K
-        RAM (xrw)                  : ORIGIN = 0x20000008, LENGTH = 0x2FFF8
-        RAM_SHARED (xrw)           : ORIGIN = 0x20030000, LENGTH = 10K
-    }
-*/
-
-/* Place stack at the end of SRAM1 */
-_stack_start = ORIGIN(RAM) + LENGTH(RAM);
-
-/*
- * Scatter the mailbox interface memory sections in shared memory
- */
-SECTIONS {
-    TL_REF_TABLE                     (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED
-
-    MB_MEM1 (NOLOAD)                          : { *(MB_MEM1) } >RAM_SHARED
-    MB_MEM2 (NOLOAD)                          : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED
-}
diff --git a/examples/stm32wb/src/bin/tl_mbox.rs b/examples/stm32wb/src/bin/tl_mbox.rs
index 326e4be85..8f4e70af0 100644
--- a/examples/stm32wb/src/bin/tl_mbox.rs
+++ b/examples/stm32wb/src/bin/tl_mbox.rs
@@ -4,8 +4,7 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::ipcc::{Config, Ipcc};
-use embassy_stm32::tl_mbox::TlMbox;
+use embassy_stm32::tl_mbox::{Config, TlMbox};
 use embassy_stm32::{bind_interrupts, tl_mbox};
 use embassy_time::{Duration, Timer};
 use {defmt_rtt as _, panic_probe as _};
@@ -45,14 +44,12 @@ async fn main(_spawner: Spawner) {
     info!("Hello World!");
 
     let config = Config::default();
-    let mut ipcc = Ipcc::new(p.IPCC, config);
-
-    let mbox = TlMbox::init(&mut ipcc, Irqs);
+    let mbox = TlMbox::new(p.IPCC, Irqs, config);
 
     loop {
         let wireless_fw_info = mbox.wireless_fw_info();
         match wireless_fw_info {
-            None => error!("not yet initialized"),
+            None => info!("not yet initialized"),
             Some(fw_info) => {
                 let version_major = fw_info.version_major();
                 let version_minor = fw_info.version_minor();
@@ -70,6 +67,9 @@ async fn main(_spawner: Spawner) {
             }
         }
 
-        Timer::after(Duration::from_millis(500)).await;
+        Timer::after(Duration::from_millis(50)).await;
     }
+
+    info!("Test OK");
+    cortex_m::asm::bkpt();
 }
diff --git a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
index 7a69f26b7..1724d946f 100644
--- a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
+++ b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs
@@ -4,8 +4,7 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::ipcc::{Config, Ipcc};
-use embassy_stm32::tl_mbox::TlMbox;
+use embassy_stm32::tl_mbox::{Config, TlMbox};
 use embassy_stm32::{bind_interrupts, tl_mbox};
 use {defmt_rtt as _, panic_probe as _};
 
@@ -44,12 +43,7 @@ async fn main(_spawner: Spawner) {
     info!("Hello World!");
 
     let config = Config::default();
-    let mut ipcc = Ipcc::new(p.IPCC, config);
-
-    let mbox = TlMbox::init(&mut ipcc, Irqs);
-
-    // initialize ble stack, does not return a response
-    mbox.shci_ble_init(&mut ipcc, Default::default());
+    let mbox = TlMbox::new(p.IPCC, Irqs, config);
 
     info!("waiting for coprocessor to boot");
     let event_box = mbox.read().await;
@@ -74,10 +68,11 @@ async fn main(_spawner: Spawner) {
         );
     }
 
-    mbox.shci_ble_init(&mut ipcc, Default::default());
+    // initialize ble stack, does not return a response
+    mbox.shci_ble_init(Default::default());
 
     info!("resetting BLE");
-    mbox.send_ble_cmd(&mut ipcc, &[0x01, 0x03, 0x0c, 0x00, 0x00]);
+    mbox.send_ble_cmd(&[0x01, 0x03, 0x0c, 0x00, 0x00]);
 
     let event_box = mbox.read().await;
 
@@ -92,8 +87,12 @@ async fn main(_spawner: Spawner) {
 
     info!(
         "==> kind: {:#04x}, code: {:#04x}, payload_length: {}, payload: {:#04x}",
-        kind, code, payload_len, payload
+        kind,
+        code,
+        payload_len,
+        payload[3..]
     );
 
-    loop {}
+    info!("Test OK");
+    cortex_m::asm::bkpt();
 }
diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml
index 29c4799a1..426d6e67f 100644
--- a/tests/stm32/.cargo/config.toml
+++ b/tests/stm32/.cargo/config.toml
@@ -17,4 +17,4 @@ rustflags = [
 target = "thumbv7m-none-eabi"
 
 [env]
-DEFMT_LOG = "trace"
+DEFMT_LOG = "trace"
\ No newline at end of file
diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml
index bd8d90abe..4c0597746 100644
--- a/tests/stm32/Cargo.toml
+++ b/tests/stm32/Cargo.toml
@@ -7,12 +7,12 @@ autobins = false
 
 [features]
 stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"]     # Blue Pill
-stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc", "chrono", "not-gpdma"]     # Nucleo
+stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "not-gpdma"]     # Nucleo "sdmmc"
 stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"]     # Nucleo
 stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"]     # Nucleo
 stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"]     # Nucleo
 stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo
-stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma"]     # Nucleo
+stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma", "ble"]     # Nucleo
 stm32h563zi = ["embassy-stm32/stm32h563zi"]     # Nucleo
 stm32u585ai = ["embassy-stm32/stm32u585ai"]     # IoT board
 
@@ -45,8 +45,8 @@ chrono = { version = "^0.4", default-features = false, optional = true}
 # BEGIN TESTS
 # Generated by gen_test.py. DO NOT EDIT.
 [[bin]]
-name = "ble"
-path = "src/bin/ble.rs"
+name = "tl_mbox"
+path = "src/bin/tl_mbox.rs"
 required-features = [ "ble",]
 
 [[bin]]
diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs
index ca76b70bb..b4583147e 100644
--- a/tests/stm32/build.rs
+++ b/tests/stm32/build.rs
@@ -9,17 +9,22 @@ fn main() -> Result<(), Box<dyn Error>> {
     println!("cargo:rustc-link-arg-bins=--nmagic");
 
     // too little RAM to run from RAM.
-    if cfg!(any(feature = "stm32f103c8", feature = "stm32c031c6")) {
+    if cfg!(any(
+        feature = "stm32f103c8",
+        feature = "stm32c031c6",
+        feature = "stm32wb55rg"
+    )) {
         println!("cargo:rustc-link-arg-bins=-Tlink.x");
         println!("cargo:rerun-if-changed=link.x");
-    } else if cfg!(feature = "stm32wb55rg") {
-        println!("cargo:rustc-link-arg-bins=-Tlink.x");
-        fs::write(out.join("memory.x"), include_bytes!("memory_ble.x")).unwrap();
     } else {
         println!("cargo:rustc-link-arg-bins=-Tlink_ram.x");
         println!("cargo:rerun-if-changed=link_ram.x");
     }
 
+    if cfg!(feature = "stm32wb55rg") {
+        println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x");
+    }
+
     println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
 
     Ok(())
diff --git a/tests/stm32/src/bin/ble.rs b/tests/stm32/src/bin/tl_mbox.rs
similarity index 79%
rename from tests/stm32/src/bin/ble.rs
rename to tests/stm32/src/bin/tl_mbox.rs
index aedf9a380..626e7ac6f 100644
--- a/tests/stm32/src/bin/ble.rs
+++ b/tests/stm32/src/bin/tl_mbox.rs
@@ -7,24 +7,23 @@
 #[path = "../example_common.rs"]
 mod example_common;
 use embassy_executor::Spawner;
-use embassy_stm32::interrupt;
-use embassy_stm32::ipcc::{Config, Ipcc};
-use embassy_stm32::tl_mbox::TlMbox;
+use embassy_stm32::tl_mbox::{Config, TlMbox};
+use embassy_stm32::{bind_interrupts, tl_mbox};
 use embassy_time::{Duration, Timer};
 use example_common::*;
 
+bind_interrupts!(struct Irqs{
+    IPCC_C1_RX => tl_mbox::ReceiveInterruptHandler;
+    IPCC_C1_TX => tl_mbox::TransmitInterruptHandler;
+});
+
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(config());
     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);
+    let mbox = TlMbox::new(p.IPCC, Irqs, config);
 
     loop {
         let wireless_fw_info = mbox.wireless_fw_info();