]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/compat/linuxkpi/common/src/linux_80211_macops.c
LinuxKPI: 802.11: improve scan handling
[FreeBSD/FreeBSD.git] / sys / compat / linuxkpi / common / src / linux_80211_macops.c
1 /*-
2  * Copyright (c) 2021-2022 The FreeBSD Foundation
3  *
4  * This software was developed by Björn Zeeb under sponsorship from
5  * the FreeBSD Foundation.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31
32 #include <sys/param.h>
33 #include <sys/types.h>
34 #include <sys/kernel.h>
35 #include <sys/errno.h>
36
37 #define LINUXKPI_NET80211
38 #include <net/mac80211.h>
39
40 #include "linux_80211.h"
41
42 /* Could be a different tracing framework later. */
43 #ifdef LINUXKPI_DEBUG_80211
44 #define LKPI_80211_TRACE_MO(fmt, ...)                                   \
45     if (linuxkpi_debug_80211 & D80211_TRACE_MO)                         \
46         printf("LKPI_80211_TRACE_MO %s:%d: %d %d %u_" fmt "\n",         \
47             __func__, __LINE__, curcpu, curthread->td_tid,              \
48             (unsigned int)ticks, __VA_ARGS__)
49 #else
50 #define LKPI_80211_TRACE_MO(...)        do { } while(0)
51 #endif
52
53 int
54 lkpi_80211_mo_start(struct ieee80211_hw *hw)
55 {
56         struct lkpi_hw *lhw;
57         int error;
58
59         lhw = HW_TO_LHW(hw);
60         if (lhw->ops->start == NULL) {
61                 error = EOPNOTSUPP;
62                 goto out;
63         }
64
65         if ((lhw->sc_flags & LKPI_MAC80211_DRV_STARTED)) {
66                 /* Trying to start twice is an error. */
67                 error = EEXIST;
68                 goto out;
69         }
70         LKPI_80211_TRACE_MO("hw %p", hw);
71         error = lhw->ops->start(hw);
72         if (error == 0)
73                 lhw->sc_flags |= LKPI_MAC80211_DRV_STARTED;
74
75 out:
76         return (error);
77 }
78
79 void
80 lkpi_80211_mo_stop(struct ieee80211_hw *hw)
81 {
82         struct lkpi_hw *lhw;
83
84         lhw = HW_TO_LHW(hw);
85         if (lhw->ops->stop == NULL)
86                 return;
87
88         LKPI_80211_TRACE_MO("hw %p", hw);
89         lhw->ops->stop(hw);
90         lhw->sc_flags &= ~LKPI_MAC80211_DRV_STARTED;
91 }
92
93 int
94 lkpi_80211_mo_get_antenna(struct ieee80211_hw *hw, u32 *txs, u32 *rxs)
95 {
96         struct lkpi_hw *lhw;
97         int error;
98
99         lhw = HW_TO_LHW(hw);
100         if (lhw->ops->get_antenna == NULL) {
101                 error = EOPNOTSUPP;
102                 goto out;
103         }
104
105         LKPI_80211_TRACE_MO("hw %p", hw);
106         error = lhw->ops->get_antenna(hw, txs, rxs);
107
108 out:
109         return (error);
110 }
111
112 int
113 lkpi_80211_mo_set_frag_threshold(struct ieee80211_hw *hw, uint32_t frag_th)
114 {
115         struct lkpi_hw *lhw;
116         int error;
117
118         lhw = HW_TO_LHW(hw);
119         if (lhw->ops->set_frag_threshold == NULL) {
120                 error = EOPNOTSUPP;
121                 goto out;
122         }
123
124         LKPI_80211_TRACE_MO("hw %p frag_th %u", hw, frag_th);
125         error = lhw->ops->set_frag_threshold(hw, frag_th);
126
127 out:
128         return (error);
129 }
130
131 int
132 lkpi_80211_mo_set_rts_threshold(struct ieee80211_hw *hw, uint32_t rts_th)
133 {
134         struct lkpi_hw *lhw;
135         int error;
136
137         lhw = HW_TO_LHW(hw);
138         if (lhw->ops->set_rts_threshold == NULL) {
139                 error = EOPNOTSUPP;
140                 goto out;
141         }
142
143         LKPI_80211_TRACE_MO("hw %p rts_th %u", hw, rts_th);
144         error = lhw->ops->set_rts_threshold(hw, rts_th);
145
146 out:
147         return (error);
148 }
149
150
151 int
152 lkpi_80211_mo_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
153 {
154         struct lkpi_hw *lhw;
155         struct lkpi_vif *lvif;
156         int error;
157
158         lhw = HW_TO_LHW(hw);
159         if (lhw->ops->add_interface == NULL) {
160                 error = EOPNOTSUPP;
161                 goto out;
162         }
163
164         lvif = VIF_TO_LVIF(vif);
165         LKPI_80211_LVIF_LOCK(lvif);
166         if (lvif->added_to_drv) {
167                 LKPI_80211_LVIF_UNLOCK(lvif);
168                 /* Trying to add twice is an error. */
169                 error = EEXIST;
170                 goto out;
171         }
172         LKPI_80211_LVIF_UNLOCK(lvif);
173
174         LKPI_80211_TRACE_MO("hw %p vif %p", hw, vif);
175         error = lhw->ops->add_interface(hw, vif);
176         if (error == 0) {
177                 LKPI_80211_LVIF_LOCK(lvif);
178                 lvif->added_to_drv = true;
179                 LKPI_80211_LVIF_UNLOCK(lvif);
180         }
181
182 out:
183         return (error);
184 }
185
186 void
187 lkpi_80211_mo_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
188 {
189         struct lkpi_hw *lhw;
190         struct lkpi_vif *lvif;
191
192         lhw = HW_TO_LHW(hw);
193         if (lhw->ops->remove_interface == NULL)
194                 return;
195
196         lvif = VIF_TO_LVIF(vif);
197         LKPI_80211_LVIF_LOCK(lvif);
198         if (!lvif->added_to_drv) {
199                 LKPI_80211_LVIF_UNLOCK(lvif);
200                 return;
201         }
202         LKPI_80211_LVIF_UNLOCK(lvif);
203
204         LKPI_80211_TRACE_MO("hw %p vif %p", hw, vif);
205         lhw->ops->remove_interface(hw, vif);
206         LKPI_80211_LVIF_LOCK(lvif);
207         lvif->added_to_drv = false;
208         LKPI_80211_LVIF_UNLOCK(lvif);
209 }
210
211
212 int
213 lkpi_80211_mo_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
214     struct ieee80211_scan_request *sr)
215 {
216         struct lkpi_hw *lhw;
217         int error;
218
219         /*
220          * MUST NOT return EPERM as that is a "magic number 1" based on rtw88
221          * driver indicating hw_scan is not supported despite the ops call
222          * being available.
223          */
224
225         lhw = HW_TO_LHW(hw);
226         if (lhw->ops->hw_scan == NULL) {
227                 /* Return magic number to use sw scan. */
228                 error = 1;
229                 goto out;
230         }
231
232         LKPI_80211_TRACE_MO("CALLING hw %p vif %p sr %p", hw, vif, sr);
233         error = lhw->ops->hw_scan(hw, vif, sr);
234         LKPI_80211_TRACE_MO("RETURNING hw %p vif %p sr %p error %d", hw, vif, sr, error);
235
236 out:
237         return (error);
238 }
239
240 void
241 lkpi_80211_mo_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
242 {
243         struct lkpi_hw *lhw;
244
245         lhw = HW_TO_LHW(hw);
246         if (lhw->ops->cancel_hw_scan == NULL)
247                 return;
248
249         LKPI_80211_TRACE_MO("hw %p vif %p", hw, vif);
250         lhw->ops->cancel_hw_scan(hw, vif);
251 }
252
253 void
254 lkpi_80211_mo_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
255 {
256         struct lkpi_hw *lhw;
257
258         lhw = HW_TO_LHW(hw);
259         if (lhw->ops->sw_scan_complete == NULL)
260                 return;
261
262         LKPI_80211_TRACE_MO("hw %p vif %p", hw, vif);
263         lhw->ops->sw_scan_complete(hw, vif);
264         lhw->scan_flags &= ~LKPI_LHW_SCAN_RUNNING;
265 }
266
267 void
268 lkpi_80211_mo_sw_scan_start(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
269     const u8 *addr)
270 {
271         struct lkpi_hw *lhw;
272
273         lhw = HW_TO_LHW(hw);
274         if (lhw->ops->sw_scan_start == NULL)
275                 return;
276
277         LKPI_80211_TRACE_MO("hw %p vif %p", hw, vif);
278         lhw->ops->sw_scan_start(hw, vif, addr);
279 }
280
281
282 /*
283  * We keep the Linux type here;  it really is an uintptr_t.
284  */
285 u64
286 lkpi_80211_mo_prepare_multicast(struct ieee80211_hw *hw,
287     struct netdev_hw_addr_list *mc_list)
288 {
289         struct lkpi_hw *lhw;
290         u64 ptr;
291
292         lhw = HW_TO_LHW(hw);
293         if (lhw->ops->prepare_multicast == NULL)
294                 return (0);
295
296         LKPI_80211_TRACE_MO("hw %p mc_list %p", hw, mc_list);
297         ptr = lhw->ops->prepare_multicast(hw, mc_list);
298         return (ptr);
299 }
300
301 void
302 lkpi_80211_mo_configure_filter(struct ieee80211_hw *hw, unsigned int changed_flags,
303     unsigned int *total_flags, u64 mc_ptr)
304 {
305         struct lkpi_hw *lhw;
306
307         lhw = HW_TO_LHW(hw);
308         if (lhw->ops->configure_filter == NULL)
309                 return;
310
311         if (mc_ptr == 0)
312                 return;
313
314         LKPI_80211_TRACE_MO("hw %p changed_flags %#x total_flags %p mc_ptr %ju", hw, changed_flags, total_flags, (uintmax_t)mc_ptr);
315         lhw->ops->configure_filter(hw, changed_flags, total_flags, mc_ptr);
316 }
317
318
319 /*
320  * So far we only called sta_{add,remove} as an alternative to sta_state.
321  * Let's keep the implementation simpler and hide sta_{add,remove} under the
322  * hood here calling them if state_state is not available from mo_sta_state.
323  */
324 static int
325 lkpi_80211_mo_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
326     struct ieee80211_sta *sta)
327 {
328         struct lkpi_hw *lhw;
329         struct lkpi_sta *lsta;
330         int error;
331
332         lhw = HW_TO_LHW(hw);
333         if (lhw->ops->sta_add == NULL) {
334                 error = EOPNOTSUPP;
335                 goto out;
336         }
337
338         lsta = STA_TO_LSTA(sta);
339         if (lsta->added_to_drv) {
340                 error = EEXIST;
341                 goto out;
342         }
343
344         LKPI_80211_TRACE_MO("hw %p vif %p sta %p", hw, vif, sta);
345         error = lhw->ops->sta_add(hw, vif, sta);
346         if (error == 0)
347                 lsta->added_to_drv = true;
348
349 out:
350         return error;
351 }
352
353 static int
354 lkpi_80211_mo_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
355     struct ieee80211_sta *sta)
356 {
357         struct lkpi_hw *lhw;
358         struct lkpi_sta *lsta;
359         int error;
360
361         lhw = HW_TO_LHW(hw);
362         if (lhw->ops->sta_remove == NULL) {
363                 error = EOPNOTSUPP;
364                 goto out;
365         }
366
367         lsta = STA_TO_LSTA(sta);
368         if (!lsta->added_to_drv) {
369                 /* If we never added the sta, do not complain on cleanup. */
370                 error = 0;
371                 goto out;
372         }
373
374         LKPI_80211_TRACE_MO("hw %p vif %p sta %p", hw, vif, sta);
375         error = lhw->ops->sta_remove(hw, vif, sta);
376         if (error == 0)
377                 lsta->added_to_drv = false;
378
379 out:
380         return error;
381 }
382
383 int
384 lkpi_80211_mo_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
385     struct lkpi_sta *lsta, enum ieee80211_sta_state nstate)
386 {
387         struct lkpi_hw *lhw;
388         struct ieee80211_sta *sta;
389         int error;
390
391         lhw = HW_TO_LHW(hw);
392         sta = LSTA_TO_STA(lsta);
393         if (lhw->ops->sta_state != NULL) {
394                 LKPI_80211_TRACE_MO("hw %p vif %p sta %p nstate %d", hw, vif, sta, nstate);
395                 error = lhw->ops->sta_state(hw, vif, sta, lsta->state, nstate);
396                 if (error == 0) {
397                         if (nstate == IEEE80211_STA_NOTEXIST)
398                                 lsta->added_to_drv = false;
399                         else
400                                 lsta->added_to_drv = true;
401                         lsta->state = nstate;
402                 }
403                 goto out;
404         }
405
406         /* XXX-BZ is the change state AUTH or ASSOC here? */
407         if (lsta->state < IEEE80211_STA_ASSOC && nstate == IEEE80211_STA_ASSOC) {
408                 error = lkpi_80211_mo_sta_add(hw, vif, sta);
409                 if (error == 0)
410                         lsta->added_to_drv = true;
411         } else if (lsta->state >= IEEE80211_STA_ASSOC &&
412             nstate < IEEE80211_STA_ASSOC) {
413                 error = lkpi_80211_mo_sta_remove(hw, vif, sta);
414                 if (error == 0)
415                         lsta->added_to_drv = false;
416         } else
417                 /* Nothing to do. */
418                 error = 0;
419         if (error == 0)
420                 lsta->state = nstate;
421
422 out:
423         /* XXX-BZ should we manage state in here? */
424         return (error);
425 }
426
427 int
428 lkpi_80211_mo_config(struct ieee80211_hw *hw, uint32_t changed)
429 {
430         struct lkpi_hw *lhw;
431         int error;
432
433         lhw = HW_TO_LHW(hw);
434         if (lhw->ops->config == NULL) {
435                 error = EOPNOTSUPP;
436                 goto out;
437         }
438
439         LKPI_80211_TRACE_MO("hw %p changed %u", hw, changed);
440         error = lhw->ops->config(hw, changed);
441
442 out:
443         return (error);
444 }
445
446
447 int
448 lkpi_80211_mo_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
449     struct ieee80211_chanctx_conf *chanctx_conf)
450 {
451         struct lkpi_hw *lhw;
452         int error;
453
454         lhw = HW_TO_LHW(hw);
455         if (lhw->ops->assign_vif_chanctx == NULL) {
456                 error = EOPNOTSUPP;
457                 goto out;
458         }
459
460         LKPI_80211_TRACE_MO("hw %p vif %p chanctx_conf %p", hw, vif, chanctx_conf);
461         error = lhw->ops->assign_vif_chanctx(hw, vif, NULL, chanctx_conf);
462         if (error == 0)
463                 vif->chanctx_conf = chanctx_conf;
464
465 out:
466         return (error);
467 }
468
469 void
470 lkpi_80211_mo_unassign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
471     struct ieee80211_chanctx_conf **chanctx_conf)
472 {
473         struct lkpi_hw *lhw;
474
475         lhw = HW_TO_LHW(hw);
476         if (lhw->ops->unassign_vif_chanctx == NULL)
477                 return;
478
479         if (*chanctx_conf == NULL)
480                 return;
481
482         LKPI_80211_TRACE_MO("hw %p vif %p chanctx_conf %p", hw, vif, *chanctx_conf);
483         lhw->ops->unassign_vif_chanctx(hw, vif, NULL, *chanctx_conf);
484         *chanctx_conf = NULL;
485 }
486
487
488 int
489 lkpi_80211_mo_add_chanctx(struct ieee80211_hw *hw,
490     struct ieee80211_chanctx_conf *chanctx_conf)
491 {
492         struct lkpi_hw *lhw;
493         int error;
494
495         lhw = HW_TO_LHW(hw);
496         if (lhw->ops->add_chanctx == NULL) {
497                 error = EOPNOTSUPP;
498                 goto out;
499         }
500
501         LKPI_80211_TRACE_MO("hw %p chanctx_conf %p", hw, chanctx_conf);
502         error = lhw->ops->add_chanctx(hw, chanctx_conf);
503
504 out:
505         return (error);
506 }
507
508 void
509 lkpi_80211_mo_change_chanctx(struct ieee80211_hw *hw,
510     struct ieee80211_chanctx_conf *chanctx_conf, uint32_t changed)
511 {
512         struct lkpi_hw *lhw;
513
514         lhw = HW_TO_LHW(hw);
515         if (lhw->ops->change_chanctx == NULL)
516                 return;
517
518         LKPI_80211_TRACE_MO("hw %p chanctx_conf %p changed %u", hw, chanctx_conf, changed);
519         lhw->ops->change_chanctx(hw, chanctx_conf, changed);
520 }
521
522 void
523 lkpi_80211_mo_remove_chanctx(struct ieee80211_hw *hw,
524     struct ieee80211_chanctx_conf *chanctx_conf)
525 {
526         struct lkpi_hw *lhw;
527
528         lhw = HW_TO_LHW(hw);
529         if (lhw->ops->remove_chanctx == NULL)
530                 return;
531
532         LKPI_80211_TRACE_MO("hw %p chanctx_conf %p", hw, chanctx_conf);
533         lhw->ops->remove_chanctx(hw, chanctx_conf);
534 }
535
536 void
537 lkpi_80211_mo_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
538     struct ieee80211_bss_conf *conf, uint64_t changed)
539 {
540         struct lkpi_hw *lhw;
541
542         lhw = HW_TO_LHW(hw);
543         if (lhw->ops->bss_info_changed == NULL)
544                 return;
545
546         LKPI_80211_TRACE_MO("hw %p vif %p conf %p changed %#jx", hw, vif, conf, (uintmax_t)changed);
547         lhw->ops->bss_info_changed(hw, vif, conf, changed);
548 }
549
550
551 int
552 lkpi_80211_mo_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
553     uint16_t ac, const struct ieee80211_tx_queue_params *txqp)
554 {
555         struct lkpi_hw *lhw;
556         int error;
557
558         lhw = HW_TO_LHW(hw);
559         if (lhw->ops->conf_tx == NULL) {
560                 error = EOPNOTSUPP;
561                 goto out;
562         }
563
564         LKPI_80211_TRACE_MO("hw %p vif %p ac %u txpq %p", hw, vif, ac, txqp);
565         error = lhw->ops->conf_tx(hw, vif, 0, ac, txqp);
566
567 out:
568         return (error);
569 }
570
571 void
572 lkpi_80211_mo_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
573     uint32_t nqueues, bool drop)
574 {
575         struct lkpi_hw *lhw;
576
577         lhw = HW_TO_LHW(hw);
578         if (lhw->ops->flush == NULL)
579                 return;
580
581         LKPI_80211_TRACE_MO("hw %p vif %p nqueues %u drop %d", hw, vif, nqueues, drop);
582         lhw->ops->flush(hw, vif, nqueues, drop);
583 }
584
585 void
586 lkpi_80211_mo_mgd_prepare_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
587     struct ieee80211_prep_tx_info *txinfo)
588 {
589         struct lkpi_hw *lhw;
590
591         lhw = HW_TO_LHW(hw);
592         if (lhw->ops->mgd_prepare_tx == NULL)
593                 return;
594
595         LKPI_80211_TRACE_MO("hw %p vif %p txinfo %p", hw, vif, txinfo);
596         lhw->ops->mgd_prepare_tx(hw, vif, txinfo);
597 }
598
599 void
600 lkpi_80211_mo_mgd_complete_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
601     struct ieee80211_prep_tx_info *txinfo)
602 {
603         struct lkpi_hw *lhw;
604
605         lhw = HW_TO_LHW(hw);
606         if (lhw->ops->mgd_complete_tx == NULL)
607                 return;
608
609         LKPI_80211_TRACE_MO("hw %p vif %p txinfo %p", hw, vif, txinfo);
610         lhw->ops->mgd_complete_tx(hw, vif, txinfo);
611 }
612
613 void
614 lkpi_80211_mo_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *txctrl,
615     struct sk_buff *skb)
616 {
617         struct lkpi_hw *lhw;
618
619         lhw = HW_TO_LHW(hw);
620         if (lhw->ops->tx == NULL)
621                 return;
622
623         LKPI_80211_TRACE_MO("hw %p txctrl %p skb %p", hw, txctrl, skb);
624         lhw->ops->tx(hw, txctrl, skb);
625 }
626
627 void
628 lkpi_80211_mo_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq)
629 {
630         struct lkpi_hw *lhw;
631
632         lhw = HW_TO_LHW(hw);
633         if (lhw->ops->wake_tx_queue == NULL)
634                 return;
635
636         LKPI_80211_TRACE_MO("hw %p txq %p", hw, txq);
637         lhw->ops->wake_tx_queue(hw, txq);
638 }
639
640 void
641 lkpi_80211_mo_sync_rx_queues(struct ieee80211_hw *hw)
642 {
643         struct lkpi_hw *lhw;
644
645         lhw = HW_TO_LHW(hw);
646         if (lhw->ops->sync_rx_queues == NULL)
647                 return;
648
649         LKPI_80211_TRACE_MO("hw %p", hw);
650         lhw->ops->sync_rx_queues(hw);
651 }
652
653 void
654 lkpi_80211_mo_sta_pre_rcu_remove(struct ieee80211_hw *hw,
655     struct ieee80211_vif *vif, struct ieee80211_sta *sta)
656 {
657         struct lkpi_hw *lhw;
658
659         lhw = HW_TO_LHW(hw);
660         if (lhw->ops->sta_pre_rcu_remove == NULL)
661                 return;
662
663         LKPI_80211_TRACE_MO("hw %p vif %p sta %p", hw, vif, sta);
664         lhw->ops->sta_pre_rcu_remove(hw, vif, sta);
665 }
666
667 int
668 lkpi_80211_mo_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
669     struct ieee80211_vif *vif, struct ieee80211_sta *sta,
670     struct ieee80211_key_conf *kc)
671 {
672         struct lkpi_hw *lhw;
673         int error;
674
675         lhw = HW_TO_LHW(hw);
676         if (lhw->ops->set_key == NULL) {
677                 error = EOPNOTSUPP;
678                 goto out;
679         }
680
681         LKPI_80211_TRACE_MO("hw %p cmd %d vif %p sta %p kc %p", hw, cmd, vif, sta, kc);
682         error = lhw->ops->set_key(hw, cmd, vif, sta, kc);
683
684 out:
685         return (error);
686 }