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