Skip to main content

openipc_core/
realtek_tx.rs

1use crate::radiotap::{
2    parse_radiotap_tx_mode, radiotap_len, ChannelBandwidth, RadiotapError, TxMode, TxModeKind,
3};
4
5pub const TX_DESC_SIZE: usize = 40;
6
7const MGN_1M: u8 = 0x02;
8const MGN_2M: u8 = 0x04;
9const MGN_5_5M: u8 = 0x0b;
10const MGN_6M: u8 = 0x0c;
11const MGN_9M: u8 = 0x12;
12const MGN_11M: u8 = 0x16;
13const MGN_12M: u8 = 0x18;
14const MGN_18M: u8 = 0x24;
15const MGN_24M: u8 = 0x30;
16const MGN_36M: u8 = 0x48;
17const MGN_48M: u8 = 0x60;
18const MGN_54M: u8 = 0x6c;
19const MGN_MCS0: u8 = 0x80;
20const MGN_VHT1SS_MCS0: u8 = 0xa0;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct RealtekTxOptions {
24    pub current_channel: u8,
25    pub is_8814a: bool,
26    pub legacy_8812_descriptor: bool,
27    pub tx_mode_default: Option<TxMode>,
28}
29
30impl Default for RealtekTxOptions {
31    fn default() -> Self {
32        Self {
33            current_channel: 36,
34            is_8814a: false,
35            legacy_8812_descriptor: false,
36            tx_mode_default: None,
37        }
38    }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum RealtekTxError {
43    Radiotap(RadiotapError),
44    PayloadTooLarge,
45}
46
47impl std::fmt::Display for RealtekTxError {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Self::Radiotap(err) => write!(f, "{err}"),
51            Self::PayloadTooLarge => write!(f, "802.11 TX payload is too large for descriptor"),
52        }
53    }
54}
55
56impl std::error::Error for RealtekTxError {}
57
58impl From<RadiotapError> for RealtekTxError {
59    fn from(value: RadiotapError) -> Self {
60        Self::Radiotap(value)
61    }
62}
63
64pub fn build_usb_tx_frame(
65    radiotap_packet: &[u8],
66    options: RealtekTxOptions,
67) -> Result<Vec<u8>, RealtekTxError> {
68    let rtap_len = radiotap_len(radiotap_packet)?;
69    let tx_mode = parse_radiotap_tx_mode(radiotap_packet)?
70        .or(options.tx_mode_default)
71        .unwrap_or_else(TxMode::legacy_1m);
72    let payload = &radiotap_packet[rtap_len..];
73    if payload.len() > u16::MAX as usize {
74        return Err(RealtekTxError::PayloadTooLarge);
75    }
76
77    let mut fixed_rate = tx_mode_fixed_rate(tx_mode);
78    if options.current_channel > 14 && is_cck_rate(fixed_rate) {
79        fixed_rate = MGN_6M;
80    }
81
82    let mut out = vec![0; TX_DESC_SIZE + payload.len()];
83    set_bits_le32(
84        &mut out,
85        20,
86        5,
87        2,
88        tx_mode.bandwidth.realtek_desc_bits() as u32,
89    );
90    let use_8814_descriptor_exceptions = options.is_8814a && !options.legacy_8812_descriptor;
91
92    set_bits_le32(&mut out, 0, 26, 1, 1);
93    if !use_8814_descriptor_exceptions {
94        set_bits_le32(&mut out, 0, 31, 1, 1);
95    }
96    set_bits_le32(&mut out, 0, 0, 16, payload.len() as u32);
97    set_bits_le32(&mut out, 0, 16, 8, TX_DESC_SIZE as u32);
98    set_bits_le32(&mut out, 4, 0, 7, 0x01);
99    set_bits_le32(&mut out, 0, 24, 1, 1);
100    set_bits_le32(
101        &mut out,
102        4,
103        16,
104        5,
105        if tx_mode.kind == TxModeKind::Vht {
106            9
107        } else {
108            8
109        },
110    );
111    set_bits_le32(&mut out, 4, 8, 5, 0x12);
112    if use_8814_descriptor_exceptions {
113        set_bits_le32(&mut out, 24, 0, 12, 0x001);
114        set_bits_le32(&mut out, 32, 15, 1, 1);
115    } else {
116        set_bits_le32(&mut out, 8, 24, 6, 0x3f);
117        set_bits_le32(&mut out, 16, 18, 6, 12);
118        set_bits_le32(&mut out, 24, 0, 12, 0x001);
119        set_bits_le32(&mut out, 32, 15, 1, 1);
120    }
121    set_bits_le32(&mut out, 16, 17, 1, 1);
122    if tx_mode.short_gi {
123        set_bits_le32(&mut out, 20, 4, 1, 1);
124    }
125    set_bits_le32(&mut out, 12, 8, 1, 1);
126    set_bits_le32(&mut out, 16, 0, 7, mrate_to_hw_rate(fixed_rate) as u32);
127    if tx_mode.ldpc {
128        set_bits_le32(&mut out, 20, 7, 1, 1);
129    }
130    set_bits_le32(&mut out, 20, 8, 2, u32::from(tx_mode.stbc));
131    tx_desc_checksum(&mut out[..TX_DESC_SIZE]);
132    out[TX_DESC_SIZE..].copy_from_slice(payload);
133    Ok(out)
134}
135
136fn tx_mode_fixed_rate(mode: TxMode) -> u8 {
137    match mode.kind {
138        TxModeKind::Legacy => mode.legacy_rate_500kbps,
139        TxModeKind::Ht => MGN_MCS0 + mode.ht_mcs,
140        TxModeKind::Vht => {
141            MGN_VHT1SS_MCS0 + ((mode.vht_nss.clamp(1, 4) - 1) * 10 + mode.vht_mcs.min(9))
142        }
143    }
144}
145
146fn set_bits_le32(bytes: &mut [u8], offset: usize, bit_offset: u8, bit_len: u8, value: u32) {
147    let mut word = u32::from_le_bytes(
148        bytes[offset..offset + 4]
149            .try_into()
150            .expect("descriptor offset is in range"),
151    );
152    let mask = if bit_len == 32 {
153        u32::MAX
154    } else {
155        ((1u32 << bit_len) - 1) << bit_offset
156    };
157    word = (word & !mask) | ((value << bit_offset) & mask);
158    bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
159}
160
161fn tx_desc_checksum(desc: &mut [u8]) {
162    set_bits_le32(desc, 28, 0, 16, 0);
163    let mut checksum = 0u16;
164    for idx in 0..16 {
165        let offset = idx * 2;
166        checksum ^= u16::from_le_bytes([desc[offset], desc[offset + 1]]);
167    }
168    set_bits_le32(desc, 28, 0, 16, checksum as u32);
169}
170
171const fn is_cck_rate(rate: u8) -> bool {
172    matches!(rate, MGN_1M | MGN_2M | MGN_5_5M | MGN_11M)
173}
174
175const fn mrate_to_hw_rate(rate: u8) -> u8 {
176    match rate {
177        MGN_1M => 0x00,
178        MGN_2M => 0x01,
179        MGN_5_5M => 0x02,
180        MGN_11M => 0x03,
181        MGN_6M => 0x04,
182        MGN_9M => 0x05,
183        MGN_12M => 0x06,
184        MGN_18M => 0x07,
185        MGN_24M => 0x08,
186        MGN_36M => 0x09,
187        MGN_48M => 0x0a,
188        MGN_54M => 0x0b,
189        MGN_MCS0..=0x9f => 0x0c + (rate - MGN_MCS0),
190        MGN_VHT1SS_MCS0..=0xc7 => 0x2c + (rate - MGN_VHT1SS_MCS0),
191        _ => 0x00,
192    }
193}
194
195#[allow(dead_code)]
196const fn bandwidth_from_desc_bits(bits: u8) -> ChannelBandwidth {
197    match bits {
198        1 => ChannelBandwidth::Mhz40,
199        2 => ChannelBandwidth::Mhz80,
200        _ => ChannelBandwidth::Mhz20,
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use crate::ieee80211::build_wfb_header_with_frame_type;
208    use crate::radiotap::{build_radiotap_header, TxRadioParams, FRAME_TYPE_RTS};
209    use crate::ChannelId;
210
211    fn le32(bytes: &[u8], offset: usize) -> u32 {
212        u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap())
213    }
214
215    fn bits(word: u32, offset: u8, len: u8) -> u32 {
216        (word >> offset) & ((1u32 << len) - 1)
217    }
218
219    #[test]
220    fn builds_descriptor_and_strips_radiotap() {
221        let params = TxRadioParams::default();
222        let mut packet = build_radiotap_header(params);
223        packet.extend_from_slice(&build_wfb_header_with_frame_type(
224            ChannelId::default_video(),
225            [0x10, 0x00],
226            FRAME_TYPE_RTS,
227        ));
228        packet.extend_from_slice(&[1, 2, 3]);
229
230        let usb = build_usb_tx_frame(&packet, RealtekTxOptions::default()).unwrap();
231        assert_eq!(usb.len(), TX_DESC_SIZE + 27);
232        assert_eq!(bits(le32(&usb, 0), 0, 16), 27);
233        assert_eq!(bits(le32(&usb, 0), 16, 8), TX_DESC_SIZE as u32);
234        assert_eq!(bits(le32(&usb, 0), 26, 1), 1);
235        assert_eq!(bits(le32(&usb, 0), 27, 1), 0);
236        assert_eq!(bits(le32(&usb, 0), 31, 1), 1);
237        assert_eq!(bits(le32(&usb, 4), 16, 5), 8);
238        assert_eq!(bits(le32(&usb, 8), 24, 6), 0x3f);
239        assert_eq!(bits(le32(&usb, 16), 18, 6), 12);
240        assert_eq!(bits(le32(&usb, 16), 0, 7), 0x0c);
241        assert_eq!(bits(le32(&usb, 24), 0, 12), 1);
242        assert_eq!(bits(le32(&usb, 32), 15, 1), 1);
243        assert_eq!(
244            &usb[TX_DESC_SIZE..TX_DESC_SIZE + 2],
245            &[FRAME_TYPE_RTS, 0x01]
246        );
247    }
248
249    #[test]
250    fn rtl8812_descriptor_copies_80211_sequence() {
251        let params = TxRadioParams::default();
252        let mut packet = build_radiotap_header(params);
253        let mut frame = build_wfb_header_with_frame_type(
254            ChannelId::default_video(),
255            [0x10, 0x00],
256            FRAME_TYPE_RTS,
257        );
258        frame[22] = 0x50;
259        frame[23] = 0x12;
260        packet.extend_from_slice(&frame);
261
262        let usb = build_usb_tx_frame(&packet, RealtekTxOptions::default()).unwrap();
263        assert_eq!(bits(le32(&usb, 36), 12, 12), 0);
264        assert_eq!(bits(le32(&usb, 32), 15, 1), 1);
265    }
266
267    #[test]
268    fn rtl8814_descriptor_keeps_kernel_matching_exceptions() {
269        let params = TxRadioParams::default();
270        let mut packet = build_radiotap_header(params);
271        packet.extend_from_slice(&build_wfb_header_with_frame_type(
272            ChannelId::default_video(),
273            [0x10, 0x00],
274            FRAME_TYPE_RTS,
275        ));
276
277        let usb = build_usb_tx_frame(
278            &packet,
279            RealtekTxOptions {
280                is_8814a: true,
281                ..RealtekTxOptions::default()
282            },
283        )
284        .unwrap();
285        assert_eq!(bits(le32(&usb, 0), 27, 1), 0);
286        assert_eq!(bits(le32(&usb, 0), 31, 1), 0);
287        assert_eq!(bits(le32(&usb, 24), 0, 12), 1);
288        assert_eq!(bits(le32(&usb, 32), 15, 1), 1);
289        assert_eq!(bits(le32(&usb, 8), 24, 6), 0);
290        assert_eq!(bits(le32(&usb, 16), 18, 6), 0);
291    }
292
293    #[test]
294    fn rtl8814_descriptor_can_use_legacy_8812_shape() {
295        let params = TxRadioParams::default();
296        let mut packet = build_radiotap_header(params);
297        packet.extend_from_slice(&build_wfb_header_with_frame_type(
298            ChannelId::default_video(),
299            [0x10, 0x00],
300            FRAME_TYPE_RTS,
301        ));
302
303        let usb = build_usb_tx_frame(
304            &packet,
305            RealtekTxOptions {
306                is_8814a: true,
307                legacy_8812_descriptor: true,
308                ..RealtekTxOptions::default()
309            },
310        )
311        .unwrap();
312        assert_eq!(bits(le32(&usb, 0), 31, 1), 1);
313        assert_eq!(bits(le32(&usb, 8), 24, 6), 0x3f);
314        assert_eq!(bits(le32(&usb, 16), 18, 6), 12);
315    }
316
317    #[test]
318    fn rateless_radiotap_uses_default_tx_mode() {
319        let mut packet = vec![
320            0x00, 0x00, 0x0c, 0x00, 0x00, 0x80, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
321        ];
322        packet.extend_from_slice(&build_wfb_header_with_frame_type(
323            ChannelId::default_video(),
324            [0x10, 0x00],
325            FRAME_TYPE_RTS,
326        ));
327
328        let mut mode = TxMode::ht(5);
329        mode.bandwidth = ChannelBandwidth::Mhz40;
330        mode.short_gi = true;
331        let usb = build_usb_tx_frame(
332            &packet,
333            RealtekTxOptions {
334                tx_mode_default: Some(mode),
335                ..RealtekTxOptions::default()
336            },
337        )
338        .unwrap();
339        assert_eq!(bits(le32(&usb, 16), 0, 7), 0x0c + 5);
340        assert_eq!(bits(le32(&usb, 20), 5, 2), 1);
341        assert_eq!(bits(le32(&usb, 20), 4, 1), 1);
342    }
343}