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