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 } 687