Skip to main content

wlan_common/
tx_vector.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::ie::SupportedRate;
6use crate::mac::WlanGi;
7use anyhow::{Error, bail};
8use fidl_fuchsia_wlan_common as fidl_common;
9use fidl_fuchsia_wlan_ieee80211 as fidl_ieee80211;
10use fidl_fuchsia_wlan_softmac as fidl_softmac;
11
12pub const HT_NUM_MCS: u8 = 32; // Only support MCS 0-31
13pub const HT_NUM_UNIQUE_MCS: u8 = 8;
14pub const ERP_NUM_TX_VECTOR: u8 = 8;
15
16const INVALID_TX_VECTOR_IDX: u16 = fidl_common::WLAN_TX_VECTOR_IDX_INVALID;
17
18const HT_NUM_GI: u8 = 2;
19const HT_NUM_CBW: u8 = 2;
20const HT_NUM_TX_VECTOR: u8 = HT_NUM_GI * HT_NUM_CBW * HT_NUM_MCS;
21
22const DSSS_CCK_NUM_TX_VECTOR: u8 = 4;
23
24pub const START_IDX: u16 = 1 + INVALID_TX_VECTOR_IDX;
25pub const HT_START_IDX: u16 = START_IDX;
26pub const ERP_START_IDX: u16 = HT_START_IDX + HT_NUM_TX_VECTOR as u16;
27pub const DSSS_CCK_START_IDX: u16 = ERP_START_IDX + ERP_NUM_TX_VECTOR as u16;
28pub const MAX_VALID_IDX: u16 = DSSS_CCK_START_IDX + DSSS_CCK_NUM_TX_VECTOR as u16 - 1;
29
30// Notes about HT:
31// Changing CBW (channel bandwidth) from 20 MHz to 40 MHz advances index by 32
32// Changing GI (gap interval) from 800 ns to 400 ns advances index by 64
33//
34//  Group   tx_vec_idx_t range    PHY   GI   CBW NSS MCS_IDX
35//  0         1 -  32             HT    800  20  -   0-31
36//  1        33 -  64             HT    800  40  -   0-31
37//  2        65 -  96             HT    400  20  -   0-31
38//  3        97 - 128             HT    400  40  -   0-31
39//  4       129 - 136             ERP   -    -   -   0-7
40//  5       137 - 138             DSSS  -    -   -   0-1
41//  6       139 - 140             CCK   -    -   -   2-3
42//
43// TODO(https://fxbug.dev/42094755) VHT will be inserted between HT and ERP.
44
45#[derive(PartialEq, Debug)]
46/// Encapsulates parameters for transmitting a packet over a PHY.
47///
48/// MCS index is defined in
49/// * HT: IEEE 802.11-2016 Table 19-27
50/// * VHT: IEEE 802.11-2016 Table 21-30
51///
52/// We extend the definition of MCS index beyond IEEE 802.11-2016 as follows:
53/// * For ERP/ERP-OFDM (WlanPhyType::Erp):
54///     * 0: BPSK,   1/2 -> Data rate  6 Mbps
55///     * 1: BPSK,   3/4 -> Data rate  9 Mbps
56///     * 2: QPSK,   1/2 -> Data rate 12 Mbps
57///     * 3: QPSK,   3/4 -> Data rate 18 Mbps
58///     * 4: 16-QAM, 1/2 -> Data rate 24 Mbps
59///     * 5: 16-QAM, 3/4 -> Data rate 36 Mbps
60///     * 6: 64-QAM, 2/3 -> Data rate 48 Mbps
61///     * 7: 64-QAM, 3/4 -> Data rate 54 Mbps
62/// * For DSSS, HR/DSSS, and ERP-DSSS/CCK (WlanPhyType::Dsss and WlanPhyType::Cck):
63///     * 0:  2 -> 1   Mbps DSSS
64///     * 1:  4 -> 2   Mbps DSSS
65///     * 2: 11 -> 5.5 Mbps CCK
66///     * 3: 22 -> 11  Mbps CCK
67pub struct TxVector {
68    phy: fidl_ieee80211::WlanPhyType,
69    gi: WlanGi,
70    cbw: fidl_ieee80211::ChannelBandwidth,
71    nss: u8, // Number of spatial streams for VHT and beyond.
72    // For HT,  see IEEE 802.11-2016 Table 19-27
73    // For VHT, see IEEE 802.11-2016 Table 21-30
74    // For ERP, see comment above (this is a Fuchsia extension)
75    mcs_idx: u8,
76}
77
78impl TxVector {
79    pub fn new(
80        phy: fidl_ieee80211::WlanPhyType,
81        gi: WlanGi,
82        cbw: fidl_ieee80211::ChannelBandwidth,
83        mcs_idx: u8,
84    ) -> Result<Self, Error> {
85        let supported_mcs = match phy {
86            fidl_ieee80211::WlanPhyType::Dsss => mcs_idx == 0 || mcs_idx == 1,
87            fidl_ieee80211::WlanPhyType::Hr => mcs_idx == 2 || mcs_idx == 3,
88            fidl_ieee80211::WlanPhyType::Ht => {
89                match gi {
90                    WlanGi::G_800NS | WlanGi::G_400NS => (),
91                    other => bail!("Unsupported GI for HT PHY: {:?}", other),
92                }
93                match cbw {
94                    fidl_ieee80211::ChannelBandwidth::Cbw20
95                    | fidl_ieee80211::ChannelBandwidth::Cbw40
96                    | fidl_ieee80211::ChannelBandwidth::Cbw40Below => (),
97                    other => bail!("Unsupported CBW for HT PHY: {:?}", other),
98                }
99                mcs_idx < HT_NUM_MCS
100            }
101            fidl_ieee80211::WlanPhyType::Erp => mcs_idx < ERP_NUM_TX_VECTOR,
102            other => bail!("Unsupported phy type: {:?}", other),
103        };
104        if supported_mcs {
105            let nss = match phy {
106                fidl_ieee80211::WlanPhyType::Ht => 1 + mcs_idx / HT_NUM_UNIQUE_MCS,
107                // TODO(https://fxbug.dev/42094755): Support VHT NSS
108                _ => 1,
109            };
110            Ok(Self { phy, gi, cbw, nss, mcs_idx })
111        } else {
112            bail!("Unsupported MCS {:?} for phy type {:?}", mcs_idx, phy);
113        }
114    }
115
116    pub fn phy(&self) -> fidl_ieee80211::WlanPhyType {
117        self.phy
118    }
119
120    pub fn from_supported_rate(erp_rate: &SupportedRate) -> Result<Self, Error> {
121        let (phy, mcs_idx) = match erp_rate.rate() {
122            2 => (fidl_ieee80211::WlanPhyType::Dsss, 0),
123            4 => (fidl_ieee80211::WlanPhyType::Dsss, 1),
124            11 => (fidl_ieee80211::WlanPhyType::Hr, 2),
125            22 => (fidl_ieee80211::WlanPhyType::Hr, 3),
126            12 => (fidl_ieee80211::WlanPhyType::Erp, 0),
127            18 => (fidl_ieee80211::WlanPhyType::Erp, 1),
128            24 => (fidl_ieee80211::WlanPhyType::Erp, 2),
129            36 => (fidl_ieee80211::WlanPhyType::Erp, 3),
130            48 => (fidl_ieee80211::WlanPhyType::Erp, 4),
131            72 => (fidl_ieee80211::WlanPhyType::Erp, 5),
132            96 => (fidl_ieee80211::WlanPhyType::Erp, 6),
133            108 => (fidl_ieee80211::WlanPhyType::Erp, 7),
134            other_rate => {
135                bail!("Invalid rate {} * 0.5 Mbps for 802.11a/b/g.", other_rate);
136            }
137        };
138        Self::new(phy, WlanGi::G_800NS, fidl_ieee80211::ChannelBandwidth::Cbw20, mcs_idx)
139    }
140
141    // We guarantee safety of the unwraps in the following two functions by testing all TxVecIdx
142    // values exhaustively.
143
144    pub fn from_idx(idx: TxVecIdx) -> Self {
145        let phy = idx.to_phy();
146        match phy {
147            fidl_ieee80211::WlanPhyType::Ht => {
148                let group_idx = (*idx - HT_START_IDX) / HT_NUM_MCS as u16;
149                let gi = match (group_idx / HT_NUM_CBW as u16) % HT_NUM_GI as u16 {
150                    1 => WlanGi::G_400NS,
151                    _ => WlanGi::G_800NS,
152                };
153                let cbw = match group_idx % HT_NUM_CBW as u16 {
154                    0 => fidl_ieee80211::ChannelBandwidth::Cbw20,
155                    _ => fidl_ieee80211::ChannelBandwidth::Cbw40,
156                };
157                let mcs_idx = ((*idx - HT_START_IDX) % HT_NUM_MCS as u16) as u8;
158                Self::new(phy, gi, cbw, mcs_idx).unwrap()
159            }
160            fidl_ieee80211::WlanPhyType::Erp => Self::new(
161                phy,
162                WlanGi::G_800NS,
163                fidl_ieee80211::ChannelBandwidth::Cbw20,
164                (*idx - ERP_START_IDX) as u8,
165            )
166            .unwrap(),
167            fidl_ieee80211::WlanPhyType::Dsss | fidl_ieee80211::WlanPhyType::Hr => Self::new(
168                phy,
169                WlanGi::G_800NS,
170                fidl_ieee80211::ChannelBandwidth::Cbw20,
171                (*idx - DSSS_CCK_START_IDX) as u8,
172            )
173            .unwrap(),
174            _ => unreachable!(),
175        }
176    }
177
178    pub fn to_idx(&self) -> TxVecIdx {
179        match self.phy {
180            fidl_ieee80211::WlanPhyType::Ht => {
181                let group_idx = match self.gi {
182                    WlanGi::G_400NS => HT_NUM_CBW as u16,
183                    _ => 0,
184                } + match self.cbw {
185                    fidl_ieee80211::ChannelBandwidth::Cbw40
186                    | fidl_ieee80211::ChannelBandwidth::Cbw40Below => 1,
187                    _ => 0,
188                };
189                TxVecIdx::new(HT_START_IDX + group_idx * HT_NUM_MCS as u16 + self.mcs_idx as u16)
190                    .unwrap()
191            }
192            fidl_ieee80211::WlanPhyType::Erp => {
193                TxVecIdx::new(ERP_START_IDX + self.mcs_idx as u16).unwrap()
194            }
195            fidl_ieee80211::WlanPhyType::Hr | fidl_ieee80211::WlanPhyType::Dsss => {
196                TxVecIdx::new(DSSS_CCK_START_IDX + self.mcs_idx as u16).unwrap()
197            }
198            _ => unreachable!(),
199        }
200    }
201
202    pub fn to_fidl_tx_info(
203        &self,
204        tx_flags: fidl_softmac::WlanTxInfoFlags,
205        minstrel_enabled: bool,
206    ) -> fidl_softmac::WlanTxInfo {
207        fidl_softmac::WlanTxInfo {
208            tx_flags: tx_flags.bits(),
209            valid_fields: (fidl_softmac::WlanTxInfoValid::CHANNEL_BANDWIDTH
210                | fidl_softmac::WlanTxInfoValid::PHY
211                | fidl_softmac::WlanTxInfoValid::MCS
212                | if minstrel_enabled {
213                    fidl_softmac::WlanTxInfoValid::TX_VECTOR_IDX
214                } else {
215                    fidl_softmac::WlanTxInfoValid::empty()
216                })
217            .bits(),
218            tx_vector_idx: self.to_idx().0,
219            phy: self.phy,
220            channel_bandwidth: self.cbw,
221            mcs: self.mcs_idx,
222        }
223    }
224}
225
226#[derive(Hash, PartialEq, Eq, Debug, Copy, Clone, Ord, PartialOrd)]
227pub struct TxVecIdx(u16);
228impl std::ops::Deref for TxVecIdx {
229    type Target = u16;
230    fn deref(&self) -> &u16 {
231        &self.0
232    }
233}
234
235impl TxVecIdx {
236    pub fn new(value: u16) -> Option<Self> {
237        if INVALID_TX_VECTOR_IDX < value && value <= MAX_VALID_IDX {
238            Some(Self(value))
239        } else {
240            None
241        }
242    }
243
244    // TODO(https://fxbug.dev/42163096): Add a const fn new when it's a stable feature.
245
246    pub fn to_erp_rate(&self) -> Option<SupportedRate> {
247        const ERP_RATE_LIST: [u8; ERP_NUM_TX_VECTOR as usize] = [12, 18, 24, 36, 48, 72, 96, 108];
248        if self.is_erp() {
249            Some(SupportedRate(ERP_RATE_LIST[(self.0 - ERP_START_IDX) as usize]))
250        } else {
251            None
252        }
253    }
254
255    pub fn to_phy(&self) -> fidl_ieee80211::WlanPhyType {
256        match self.0 {
257            idx if idx < HT_START_IDX + HT_NUM_TX_VECTOR as u16 => fidl_ieee80211::WlanPhyType::Ht,
258            idx if idx < ERP_START_IDX + ERP_NUM_TX_VECTOR as u16 => {
259                fidl_ieee80211::WlanPhyType::Erp
260            }
261            idx if idx < DSSS_CCK_START_IDX + 2 => fidl_ieee80211::WlanPhyType::Dsss,
262            idx if idx < DSSS_CCK_START_IDX + DSSS_CCK_NUM_TX_VECTOR as u16 => {
263                fidl_ieee80211::WlanPhyType::Hr
264            }
265            // This panic is unreachable for any TxVecIdx constructed with TxVecIdx::new.
266            // Verified by exhaustive test cases.
267            _ => panic!("TxVecIdx has invalid value"),
268        }
269    }
270
271    pub fn is_ht(&self) -> bool {
272        HT_START_IDX <= self.0 && self.0 < HT_START_IDX + HT_NUM_TX_VECTOR as u16
273    }
274
275    pub fn is_erp(&self) -> bool {
276        ERP_START_IDX <= self.0 && self.0 < ERP_START_IDX + ERP_NUM_TX_VECTOR as u16
277    }
278}
279
280impl std::fmt::Display for TxVecIdx {
281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282        let tx_vector = TxVector::from_idx(*self);
283        write!(f, "TxVecIdx {:3}: {:?}", self.0, tx_vector)
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn valid_tx_vector_idxs() {
293        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
294            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
295            idx.to_phy(); // Shouldn't panic for any value.
296        }
297        assert!(
298            TxVecIdx::new(INVALID_TX_VECTOR_IDX).is_none(),
299            "Should not be able to construct invalid tx vector idx"
300        );
301        assert!(
302            TxVecIdx::new(MAX_VALID_IDX + 1).is_none(),
303            "Should not be able to construct invalid tx vector idx"
304        );
305    }
306
307    #[test]
308    fn erp_rates() {
309        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
310            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
311            assert_eq!(idx.is_erp(), idx.to_erp_rate().is_some());
312        }
313    }
314
315    #[test]
316    fn phy_types() {
317        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
318            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
319            if idx.is_erp() {
320                assert_eq!(idx.to_phy(), fidl_ieee80211::WlanPhyType::Erp);
321            } else if idx.is_ht() {
322                assert_eq!(idx.to_phy(), fidl_ieee80211::WlanPhyType::Ht);
323            } else {
324                assert!(
325                    idx.to_phy() == fidl_ieee80211::WlanPhyType::Dsss
326                        || idx.to_phy() == fidl_ieee80211::WlanPhyType::Hr
327                );
328            }
329        }
330    }
331
332    #[test]
333    fn to_and_from_idx() {
334        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
335            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
336            let tx_vector = TxVector::from_idx(idx);
337            assert_eq!(idx, tx_vector.to_idx());
338        }
339    }
340
341    #[test]
342    fn ht_and_erp_phy_types() {
343        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
344            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
345            let tx_vector = TxVector::from_idx(idx);
346            if idx.is_erp() {
347                assert_eq!(tx_vector.phy(), fidl_ieee80211::WlanPhyType::Erp);
348            } else if idx.is_ht() {
349                assert_eq!(tx_vector.phy(), fidl_ieee80211::WlanPhyType::Ht);
350            }
351        }
352    }
353
354    #[test]
355    fn from_erp_rates() {
356        for idx in INVALID_TX_VECTOR_IDX + 1..=MAX_VALID_IDX {
357            let idx = TxVecIdx::new(idx).expect("Could not make TxVecIdx from valid index");
358            if idx.is_erp() {
359                let erp_rate = idx.to_erp_rate().unwrap();
360                let tx_vector = TxVector::from_supported_rate(&erp_rate)
361                    .expect("Could not make TxVector from ERP rate.");
362                assert_eq!(idx, tx_vector.to_idx());
363            }
364        }
365    }
366}