diff options
| author | Dario Nieuwenhuis <[email protected]> | 2023-03-27 10:43:53 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-03-27 10:43:53 +0000 |
| commit | cde6f0f8628b50008df8cdeb52e80c186f63863a (patch) | |
| tree | f4d0304ac729d9e4234f26b49b43f7e093c7f300 /src | |
| parent | fd3de78b43e1c3607c68a2e12987a28bf60888e2 (diff) | |
| parent | c7646eb699e194a7c692b95b49adc76d6d3295ea (diff) | |
Merge pull request #50 from kbleeke/async-ioctls
Rework Ioctls to an async state machine
Diffstat (limited to 'src')
| -rw-r--r-- | src/control.rs | 30 | ||||
| -rw-r--r-- | src/ioctl.rs | 111 | ||||
| -rw-r--r-- | src/lib.rs | 32 | ||||
| -rw-r--r-- | src/runner.rs | 108 |
4 files changed, 185 insertions, 96 deletions
diff --git a/src/control.rs b/src/control.rs index 79677b554..0dbf6d44f 100644 --- a/src/control.rs +++ b/src/control.rs | |||
| @@ -1,8 +1,6 @@ | |||
| 1 | use core::cell::Cell; | ||
| 2 | use core::cmp::{max, min}; | 1 | use core::cmp::{max, min}; |
| 3 | 2 | ||
| 4 | use ch::driver::LinkState; | 3 | use ch::driver::LinkState; |
| 5 | use embassy_futures::yield_now; | ||
| 6 | use embassy_net_driver_channel as ch; | 4 | use embassy_net_driver_channel as ch; |
| 7 | use embassy_time::{Duration, Timer}; | 5 | use embassy_time::{Duration, Timer}; |
| 8 | 6 | ||
| @@ -10,21 +8,18 @@ pub use crate::bus::SpiBusCyw43; | |||
| 10 | use crate::consts::*; | 8 | use crate::consts::*; |
| 11 | use crate::events::{Event, EventQueue}; | 9 | use crate::events::{Event, EventQueue}; |
| 12 | use crate::fmt::Bytes; | 10 | use crate::fmt::Bytes; |
| 11 | use crate::ioctl::{IoctlState, IoctlType}; | ||
| 13 | use crate::structs::*; | 12 | use crate::structs::*; |
| 14 | use crate::{countries, IoctlState, IoctlType, PowerManagementMode}; | 13 | use crate::{countries, PowerManagementMode}; |
| 15 | 14 | ||
| 16 | pub struct Control<'a> { | 15 | pub struct Control<'a> { |
| 17 | state_ch: ch::StateRunner<'a>, | 16 | state_ch: ch::StateRunner<'a>, |
| 18 | event_sub: &'a EventQueue, | 17 | event_sub: &'a EventQueue, |
| 19 | ioctl_state: &'a Cell<IoctlState>, | 18 | ioctl_state: &'a IoctlState, |
| 20 | } | 19 | } |
| 21 | 20 | ||
| 22 | impl<'a> Control<'a> { | 21 | impl<'a> Control<'a> { |
| 23 | pub(crate) fn new( | 22 | pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a EventQueue, ioctl_state: &'a IoctlState) -> Self { |
| 24 | state_ch: ch::StateRunner<'a>, | ||
| 25 | event_sub: &'a EventQueue, | ||
| 26 | ioctl_state: &'a Cell<IoctlState>, | ||
| 27 | ) -> Self { | ||
| 28 | Self { | 23 | Self { |
| 29 | state_ch, | 24 | state_ch, |
| 30 | event_sub, | 25 | event_sub, |
| @@ -285,21 +280,8 @@ impl<'a> Control<'a> { | |||
| 285 | async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { | 280 | async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { |
| 286 | // TODO cancel ioctl on future drop. | 281 | // TODO cancel ioctl on future drop. |
| 287 | 282 | ||
| 288 | while !matches!(self.ioctl_state.get(), IoctlState::Idle) { | 283 | self.ioctl_state.do_ioctl(kind, cmd, iface, buf).await; |
| 289 | yield_now().await; | 284 | let resp_len = self.ioctl_state.wait_complete().await; |
| 290 | } | ||
| 291 | |||
| 292 | self.ioctl_state.set(IoctlState::Pending { kind, cmd, iface, buf }); | ||
| 293 | |||
| 294 | let resp_len = loop { | ||
| 295 | if let IoctlState::Done { resp_len } = self.ioctl_state.get() { | ||
| 296 | break resp_len; | ||
| 297 | } | ||
| 298 | yield_now().await; | ||
| 299 | }; | ||
| 300 | |||
| 301 | self.ioctl_state.set(IoctlState::Idle); | ||
| 302 | |||
| 303 | resp_len | 285 | resp_len |
| 304 | } | 286 | } |
| 305 | } | 287 | } |
diff --git a/src/ioctl.rs b/src/ioctl.rs new file mode 100644 index 000000000..6a7465593 --- /dev/null +++ b/src/ioctl.rs | |||
| @@ -0,0 +1,111 @@ | |||
| 1 | use core::cell::{Cell, RefCell}; | ||
| 2 | use core::future::poll_fn; | ||
| 3 | use core::task::{Poll, Waker}; | ||
| 4 | |||
| 5 | use embassy_sync::waitqueue::WakerRegistration; | ||
| 6 | |||
| 7 | #[derive(Clone, Copy)] | ||
| 8 | pub enum IoctlType { | ||
| 9 | Get = 0, | ||
| 10 | Set = 2, | ||
| 11 | } | ||
| 12 | |||
| 13 | #[derive(Clone, Copy)] | ||
| 14 | pub struct PendingIoctl { | ||
| 15 | pub buf: *mut [u8], | ||
| 16 | pub kind: IoctlType, | ||
| 17 | pub cmd: u32, | ||
| 18 | pub iface: u32, | ||
| 19 | } | ||
| 20 | |||
| 21 | #[derive(Clone, Copy)] | ||
| 22 | enum IoctlStateInner { | ||
| 23 | Pending(PendingIoctl), | ||
| 24 | Sent { buf: *mut [u8] }, | ||
| 25 | Done { resp_len: usize }, | ||
| 26 | } | ||
| 27 | |||
| 28 | #[derive(Default)] | ||
| 29 | struct Wakers { | ||
| 30 | control: WakerRegistration, | ||
| 31 | runner: WakerRegistration, | ||
| 32 | } | ||
| 33 | |||
| 34 | pub struct IoctlState { | ||
| 35 | state: Cell<IoctlStateInner>, | ||
| 36 | wakers: RefCell<Wakers>, | ||
| 37 | } | ||
| 38 | |||
| 39 | impl IoctlState { | ||
| 40 | pub fn new() -> Self { | ||
| 41 | Self { | ||
| 42 | state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), | ||
| 43 | wakers: Default::default(), | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | fn wake_control(&self) { | ||
| 48 | self.wakers.borrow_mut().control.wake(); | ||
| 49 | } | ||
| 50 | |||
| 51 | fn register_control(&self, waker: &Waker) { | ||
| 52 | self.wakers.borrow_mut().control.register(waker); | ||
| 53 | } | ||
| 54 | |||
| 55 | fn wake_runner(&self) { | ||
| 56 | self.wakers.borrow_mut().runner.wake(); | ||
| 57 | } | ||
| 58 | |||
| 59 | fn register_runner(&self, waker: &Waker) { | ||
| 60 | self.wakers.borrow_mut().runner.register(waker); | ||
| 61 | } | ||
| 62 | |||
| 63 | pub async fn wait_complete(&self) -> usize { | ||
| 64 | poll_fn(|cx| { | ||
| 65 | if let IoctlStateInner::Done { resp_len } = self.state.get() { | ||
| 66 | Poll::Ready(resp_len) | ||
| 67 | } else { | ||
| 68 | self.register_control(cx.waker()); | ||
| 69 | Poll::Pending | ||
| 70 | } | ||
| 71 | }) | ||
| 72 | .await | ||
| 73 | } | ||
| 74 | |||
| 75 | pub async fn wait_pending(&self) -> PendingIoctl { | ||
| 76 | let pending = poll_fn(|cx| { | ||
| 77 | if let IoctlStateInner::Pending(pending) = self.state.get() { | ||
| 78 | warn!("found pending ioctl"); | ||
| 79 | Poll::Ready(pending) | ||
| 80 | } else { | ||
| 81 | self.register_runner(cx.waker()); | ||
| 82 | Poll::Pending | ||
| 83 | } | ||
| 84 | }) | ||
| 85 | .await; | ||
| 86 | |||
| 87 | self.state.set(IoctlStateInner::Sent { buf: pending.buf }); | ||
| 88 | pending | ||
| 89 | } | ||
| 90 | |||
| 91 | pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { | ||
| 92 | warn!("doing ioctl"); | ||
| 93 | self.state | ||
| 94 | .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); | ||
| 95 | self.wake_runner(); | ||
| 96 | self.wait_complete().await | ||
| 97 | } | ||
| 98 | |||
| 99 | pub fn ioctl_done(&self, response: &[u8]) { | ||
| 100 | if let IoctlStateInner::Sent { buf } = self.state.get() { | ||
| 101 | warn!("ioctl complete"); | ||
| 102 | // TODO fix this | ||
| 103 | (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); | ||
| 104 | |||
| 105 | self.state.set(IoctlStateInner::Done { | ||
| 106 | resp_len: response.len(), | ||
| 107 | }); | ||
| 108 | self.wake_control(); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
diff --git a/src/lib.rs b/src/lib.rs index af8f74a6d..069ca40f4 100644 --- a/src/lib.rs +++ b/src/lib.rs | |||
| @@ -11,17 +11,17 @@ mod bus; | |||
| 11 | mod consts; | 11 | mod consts; |
| 12 | mod countries; | 12 | mod countries; |
| 13 | mod events; | 13 | mod events; |
| 14 | mod ioctl; | ||
| 14 | mod structs; | 15 | mod structs; |
| 15 | 16 | ||
| 16 | mod control; | 17 | mod control; |
| 17 | mod nvram; | 18 | mod nvram; |
| 18 | mod runner; | 19 | mod runner; |
| 19 | 20 | ||
| 20 | use core::cell::Cell; | ||
| 21 | |||
| 22 | use embassy_net_driver_channel as ch; | 21 | use embassy_net_driver_channel as ch; |
| 23 | use embedded_hal_1::digital::OutputPin; | 22 | use embedded_hal_1::digital::OutputPin; |
| 24 | use events::EventQueue; | 23 | use events::EventQueue; |
| 24 | use ioctl::IoctlState; | ||
| 25 | 25 | ||
| 26 | use crate::bus::Bus; | 26 | use crate::bus::Bus; |
| 27 | pub use crate::bus::SpiBusCyw43; | 27 | pub use crate::bus::SpiBusCyw43; |
| @@ -30,12 +30,6 @@ pub use crate::runner::Runner; | |||
| 30 | 30 | ||
| 31 | const MTU: usize = 1514; | 31 | const MTU: usize = 1514; |
| 32 | 32 | ||
| 33 | #[derive(Clone, Copy)] | ||
| 34 | pub enum IoctlType { | ||
| 35 | Get = 0, | ||
| 36 | Set = 2, | ||
| 37 | } | ||
| 38 | |||
| 39 | #[allow(unused)] | 33 | #[allow(unused)] |
| 40 | #[derive(Clone, Copy, PartialEq, Eq)] | 34 | #[derive(Clone, Copy, PartialEq, Eq)] |
| 41 | enum Core { | 35 | enum Core { |
| @@ -106,26 +100,8 @@ const CHIP: Chip = Chip { | |||
| 106 | chanspec_ctl_sb_mask: 0x0700, | 100 | chanspec_ctl_sb_mask: 0x0700, |
| 107 | }; | 101 | }; |
| 108 | 102 | ||
| 109 | #[derive(Clone, Copy)] | ||
| 110 | enum IoctlState { | ||
| 111 | Idle, | ||
| 112 | |||
| 113 | Pending { | ||
| 114 | kind: IoctlType, | ||
| 115 | cmd: u32, | ||
| 116 | iface: u32, | ||
| 117 | buf: *mut [u8], | ||
| 118 | }, | ||
| 119 | Sent { | ||
| 120 | buf: *mut [u8], | ||
| 121 | }, | ||
| 122 | Done { | ||
| 123 | resp_len: usize, | ||
| 124 | }, | ||
| 125 | } | ||
| 126 | |||
| 127 | pub struct State { | 103 | pub struct State { |
| 128 | ioctl_state: Cell<IoctlState>, | 104 | ioctl_state: IoctlState, |
| 129 | ch: ch::State<MTU, 4, 4>, | 105 | ch: ch::State<MTU, 4, 4>, |
| 130 | events: EventQueue, | 106 | events: EventQueue, |
| 131 | } | 107 | } |
| @@ -133,7 +109,7 @@ pub struct State { | |||
| 133 | impl State { | 109 | impl State { |
| 134 | pub fn new() -> Self { | 110 | pub fn new() -> Self { |
| 135 | Self { | 111 | Self { |
| 136 | ioctl_state: Cell::new(IoctlState::Idle), | 112 | ioctl_state: IoctlState::new(), |
| 137 | ch: ch::State::new(), | 113 | ch: ch::State::new(), |
| 138 | events: EventQueue::new(), | 114 | events: EventQueue::new(), |
| 139 | } | 115 | } |
diff --git a/src/runner.rs b/src/runner.rs index 9945af3fc..4abccf48b 100644 --- a/src/runner.rs +++ b/src/runner.rs | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | use core::cell::Cell; | ||
| 2 | use core::slice; | 1 | use core::slice; |
| 3 | 2 | ||
| 3 | use embassy_futures::select::{select3, Either3}; | ||
| 4 | use embassy_futures::yield_now; | 4 | use embassy_futures::yield_now; |
| 5 | use embassy_net_driver_channel as ch; | 5 | use embassy_net_driver_channel as ch; |
| 6 | use embassy_sync::pubsub::PubSubBehavior; | 6 | use embassy_sync::pubsub::PubSubBehavior; |
| @@ -12,9 +12,10 @@ pub use crate::bus::SpiBusCyw43; | |||
| 12 | use crate::consts::*; | 12 | use crate::consts::*; |
| 13 | use crate::events::{EventQueue, EventStatus}; | 13 | use crate::events::{EventQueue, EventStatus}; |
| 14 | use crate::fmt::Bytes; | 14 | use crate::fmt::Bytes; |
| 15 | use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; | ||
| 15 | use crate::nvram::NVRAM; | 16 | use crate::nvram::NVRAM; |
| 16 | use crate::structs::*; | 17 | use crate::structs::*; |
| 17 | use crate::{events, Core, IoctlState, IoctlType, CHIP, MTU}; | 18 | use crate::{events, Core, CHIP, MTU}; |
| 18 | 19 | ||
| 19 | #[cfg(feature = "firmware-logs")] | 20 | #[cfg(feature = "firmware-logs")] |
| 20 | struct LogState { | 21 | struct LogState { |
| @@ -40,7 +41,7 @@ pub struct Runner<'a, PWR, SPI> { | |||
| 40 | ch: ch::Runner<'a, MTU>, | 41 | ch: ch::Runner<'a, MTU>, |
| 41 | bus: Bus<PWR, SPI>, | 42 | bus: Bus<PWR, SPI>, |
| 42 | 43 | ||
| 43 | ioctl_state: &'a Cell<IoctlState>, | 44 | ioctl_state: &'a IoctlState, |
| 44 | ioctl_id: u16, | 45 | ioctl_id: u16, |
| 45 | sdpcm_seq: u8, | 46 | sdpcm_seq: u8, |
| 46 | sdpcm_seq_max: u8, | 47 | sdpcm_seq_max: u8, |
| @@ -59,7 +60,7 @@ where | |||
| 59 | pub(crate) fn new( | 60 | pub(crate) fn new( |
| 60 | ch: ch::Runner<'a, MTU>, | 61 | ch: ch::Runner<'a, MTU>, |
| 61 | bus: Bus<PWR, SPI>, | 62 | bus: Bus<PWR, SPI>, |
| 62 | ioctl_state: &'a Cell<IoctlState>, | 63 | ioctl_state: &'a IoctlState, |
| 63 | events: &'a EventQueue, | 64 | events: &'a EventQueue, |
| 64 | ) -> Self { | 65 | ) -> Self { |
| 65 | Self { | 66 | Self { |
| @@ -226,19 +227,22 @@ where | |||
| 226 | #[cfg(feature = "firmware-logs")] | 227 | #[cfg(feature = "firmware-logs")] |
| 227 | self.log_read().await; | 228 | self.log_read().await; |
| 228 | 229 | ||
| 229 | // Send stuff | 230 | let ev = || async { |
| 230 | // TODO flow control not yet complete | 231 | // TODO use IRQs |
| 231 | if !self.has_credit() { | 232 | yield_now().await; |
| 232 | warn!("TX stalled"); | 233 | }; |
| 233 | } else { | 234 | |
| 234 | if let IoctlState::Pending { kind, cmd, iface, buf } = self.ioctl_state.get() { | 235 | if self.has_credit() { |
| 235 | self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; | 236 | let ioctl = self.ioctl_state.wait_pending(); |
| 236 | self.ioctl_state.set(IoctlState::Sent { buf }); | 237 | let tx = self.ch.tx_buf(); |
| 237 | } | 238 | |
| 238 | if !self.has_credit() { | 239 | match select3(ioctl, tx, ev()).await { |
| 239 | warn!("TX stalled"); | 240 | Either3::First(PendingIoctl { buf, kind, cmd, iface }) => { |
| 240 | } else { | 241 | warn!("ioctl"); |
| 241 | if let Some(packet) = self.ch.try_tx_buf() { | 242 | self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; |
| 243 | } | ||
| 244 | Either3::Second(packet) => { | ||
| 245 | warn!("packet"); | ||
| 242 | trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); | 246 | trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); |
| 243 | 247 | ||
| 244 | let mut buf = [0; 512]; | 248 | let mut buf = [0; 512]; |
| @@ -281,28 +285,46 @@ where | |||
| 281 | self.bus.wlan_write(&buf[..(total_len / 4)]).await; | 285 | self.bus.wlan_write(&buf[..(total_len / 4)]).await; |
| 282 | self.ch.tx_done(); | 286 | self.ch.tx_done(); |
| 283 | } | 287 | } |
| 288 | Either3::Third(()) => { | ||
| 289 | // Receive stuff | ||
| 290 | let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; | ||
| 291 | |||
| 292 | if irq & IRQ_F2_PACKET_AVAILABLE != 0 { | ||
| 293 | let mut status = 0xFFFF_FFFF; | ||
| 294 | while status == 0xFFFF_FFFF { | ||
| 295 | status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; | ||
| 296 | } | ||
| 297 | |||
| 298 | if status & STATUS_F2_PKT_AVAILABLE != 0 { | ||
| 299 | let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; | ||
| 300 | self.bus.wlan_read(&mut buf, len).await; | ||
| 301 | trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); | ||
| 302 | self.rx(&slice8_mut(&mut buf)[..len as usize]); | ||
| 303 | } | ||
| 304 | } | ||
| 305 | } | ||
| 284 | } | 306 | } |
| 285 | } | 307 | } else { |
| 308 | warn!("TX stalled"); | ||
| 309 | ev().await; | ||
| 286 | 310 | ||
| 287 | // Receive stuff | 311 | // Receive stuff |
| 288 | let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; | 312 | let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; |
| 289 | 313 | ||
| 290 | if irq & IRQ_F2_PACKET_AVAILABLE != 0 { | 314 | if irq & IRQ_F2_PACKET_AVAILABLE != 0 { |
| 291 | let mut status = 0xFFFF_FFFF; | 315 | let mut status = 0xFFFF_FFFF; |
| 292 | while status == 0xFFFF_FFFF { | 316 | while status == 0xFFFF_FFFF { |
| 293 | status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; | 317 | status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; |
| 294 | } | 318 | } |
| 295 | 319 | ||
| 296 | if status & STATUS_F2_PKT_AVAILABLE != 0 { | 320 | if status & STATUS_F2_PKT_AVAILABLE != 0 { |
| 297 | let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; | 321 | let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; |
| 298 | self.bus.wlan_read(&mut buf, len).await; | 322 | self.bus.wlan_read(&mut buf, len).await; |
| 299 | trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); | 323 | trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); |
| 300 | self.rx(&slice8_mut(&mut buf)[..len as usize]); | 324 | self.rx(&slice8_mut(&mut buf)[..len as usize]); |
| 325 | } | ||
| 301 | } | 326 | } |
| 302 | } | 327 | } |
| 303 | |||
| 304 | // TODO use IRQs | ||
| 305 | yield_now().await; | ||
| 306 | } | 328 | } |
| 307 | } | 329 | } |
| 308 | 330 | ||
| @@ -340,19 +362,17 @@ where | |||
| 340 | let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); | 362 | let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); |
| 341 | trace!(" {:?}", cdc_header); | 363 | trace!(" {:?}", cdc_header); |
| 342 | 364 | ||
| 343 | if let IoctlState::Sent { buf } = self.ioctl_state.get() { | 365 | if cdc_header.id == self.ioctl_id { |
| 344 | if cdc_header.id == self.ioctl_id { | 366 | if cdc_header.status != 0 { |
| 345 | if cdc_header.status != 0 { | 367 | // TODO: propagate error instead |
| 346 | // TODO: propagate error instead | 368 | panic!("IOCTL error {}", cdc_header.status as i32); |
| 347 | panic!("IOCTL error {}", cdc_header.status as i32); | 369 | } |
| 348 | } | ||
| 349 | 370 | ||
| 350 | let resp_len = cdc_header.len as usize; | 371 | let resp_len = cdc_header.len as usize; |
| 351 | info!("IOCTL Response: {:02x}", Bytes(&payload[CdcHeader::SIZE..][..resp_len])); | 372 | let response = &payload[CdcHeader::SIZE..][..resp_len]; |
| 373 | info!("IOCTL Response: {:02x}", Bytes(response)); | ||
| 352 | 374 | ||
| 353 | (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); | 375 | self.ioctl_state.ioctl_done(response); |
| 354 | self.ioctl_state.set(IoctlState::Done { resp_len }); | ||
| 355 | } | ||
| 356 | } | 376 | } |
| 357 | } | 377 | } |
| 358 | CHANNEL_TYPE_EVENT => { | 378 | CHANNEL_TYPE_EVENT => { |
