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_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 chanctx_conf %p", hw, vif, chanctx_conf); 459 error = lhw->ops->assign_vif_chanctx(hw, vif, NULL, 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_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 chanctx_conf %p", hw, vif, *chanctx_conf); 481 lhw->ops->unassign_vif_chanctx(hw, vif, NULL, *chanctx_conf); 482 *chanctx_conf = NULL; 483 } 484 485 486 int 487 lkpi_80211_mo_add_chanctx(struct ieee80211_hw *hw, 488 struct ieee80211_chanctx_conf *chanctx_conf) 489 { 490 struct lkpi_hw *lhw; 491 int error; 492 493 lhw = HW_TO_LHW(hw); 494 if (lhw->ops->add_chanctx == NULL) { 495 error = EOPNOTSUPP; 496 goto out; 497 } 498 499 LKPI_80211_TRACE_MO("hw %p chanctx_conf %p", hw, chanctx_conf); 500 error = lhw->ops->add_chanctx(hw, chanctx_conf); 501 502 out: 503 return (error); 504 } 505 506 void 507 lkpi_80211_mo_change_chanctx(struct ieee80211_hw *hw, 508 struct ieee80211_chanctx_conf *chanctx_conf, uint32_t changed) 509 { 510 struct lkpi_hw *lhw; 511 512 lhw = HW_TO_LHW(hw); 513 if (lhw->ops->change_chanctx == NULL) 514 return; 515 516 LKPI_80211_TRACE_MO("hw %p chanctx_conf %p changed %u", hw, chanctx_conf, changed); 517 lhw->ops->change_chanctx(hw, chanctx_conf, changed); 518 } 519 520 void 521 lkpi_80211_mo_remove_chanctx(struct ieee80211_hw *hw, 522 struct ieee80211_chanctx_conf *chanctx_conf) 523 { 524 struct lkpi_hw *lhw; 525 526 lhw = HW_TO_LHW(hw); 527 if (lhw->ops->remove_chanctx == NULL) 528 return; 529 530 LKPI_80211_TRACE_MO("hw %p chanctx_conf %p", hw, chanctx_conf); 531 lhw->ops->remove_chanctx(hw, chanctx_conf); 532 } 533 534 void 535 lkpi_80211_mo_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 536 struct ieee80211_bss_conf *conf, uint64_t changed) 537 { 538 struct lkpi_hw *lhw; 539 540 lhw = HW_TO_LHW(hw); 541 if (lhw->ops->bss_info_changed == NULL) 542 return; 543 544 LKPI_80211_TRACE_MO("hw %p vif %p conf %p changed %#jx", hw, vif, conf, (uintmax_t)changed); 545 lhw->ops->bss_info_changed(hw, vif, conf, changed); 546 } 547 548 549 int 550 lkpi_80211_mo_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 551 uint16_t ac, const struct ieee80211_tx_queue_params *txqp) 552 { 553 struct lkpi_hw *lhw; 554 int error; 555 556 lhw = HW_TO_LHW(hw); 557 if (lhw->ops->conf_tx == NULL) { 558 error = EOPNOTSUPP; 559 goto out; 560 } 561 562 LKPI_80211_TRACE_MO("hw %p vif %p ac %u txpq %p", hw, vif, ac, txqp); 563 error = lhw->ops->conf_tx(hw, vif, 0, ac, txqp); 564 565 out: 566 return (error); 567 } 568 569 void 570 lkpi_80211_mo_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 571 uint32_t nqueues, bool drop) 572 { 573 struct lkpi_hw *lhw; 574 575 lhw = HW_TO_LHW(hw); 576 if (lhw->ops->flush == NULL) 577 return; 578 579 LKPI_80211_TRACE_MO("hw %p vif %p nqueues %u drop %d", hw, vif, nqueues, drop); 580 lhw->ops->flush(hw, vif, nqueues, drop); 581 } 582 583 void 584 lkpi_80211_mo_mgd_prepare_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 585 struct ieee80211_prep_tx_info *txinfo) 586 { 587 struct lkpi_hw *lhw; 588 589 lhw = HW_TO_LHW(hw); 590 if (lhw->ops->mgd_prepare_tx == NULL) 591 return; 592 593 LKPI_80211_TRACE_MO("hw %p vif %p txinfo %p", hw, vif, txinfo); 594 lhw->ops->mgd_prepare_tx(hw, vif, txinfo); 595 } 596 597 void 598 lkpi_80211_mo_mgd_complete_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_complete_tx == NULL) 605 return; 606 607 LKPI_80211_TRACE_MO("hw %p vif %p txinfo %p", hw, vif, txinfo); 608 lhw->ops->mgd_complete_tx(hw, vif, txinfo); 609 } 610 611 void 612 lkpi_80211_mo_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *txctrl, 613 struct sk_buff *skb) 614 { 615 struct lkpi_hw *lhw; 616 617 lhw = HW_TO_LHW(hw); 618 if (lhw->ops->tx == NULL) 619 return; 620 621 LKPI_80211_TRACE_MO("hw %p txctrl %p skb %p", hw, txctrl, skb); 622 lhw->ops->tx(hw, txctrl, skb); 623 } 624 625 void 626 lkpi_80211_mo_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq) 627 { 628 struct lkpi_hw *lhw; 629 630 lhw = HW_TO_LHW(hw); 631 if (lhw->ops->wake_tx_queue == NULL) 632 return; 633 634 LKPI_80211_TRACE_MO("hw %p txq %p", hw, txq); 635 lhw->ops->wake_tx_queue(hw, txq); 636 } 637 638 void 639 lkpi_80211_mo_sync_rx_queues(struct ieee80211_hw *hw) 640 { 641 struct lkpi_hw *lhw; 642 643 lhw = HW_TO_LHW(hw); 644 if (lhw->ops->sync_rx_queues == NULL) 645 return; 646 647 LKPI_80211_TRACE_MO("hw %p", hw); 648 lhw->ops->sync_rx_queues(hw); 649 } 650 651 void 652 lkpi_80211_mo_sta_pre_rcu_remove(struct ieee80211_hw *hw, 653 struct ieee80211_vif *vif, struct ieee80211_sta *sta) 654 { 655 struct lkpi_hw *lhw; 656 657 lhw = HW_TO_LHW(hw); 658 if (lhw->ops->sta_pre_rcu_remove == NULL) 659 return; 660 661 LKPI_80211_TRACE_MO("hw %p vif %p sta %p", hw, vif, sta); 662 lhw->ops->sta_pre_rcu_remove(hw, vif, sta); 663 } 664 665 int 666 lkpi_80211_mo_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, 667 struct ieee80211_vif *vif, struct ieee80211_sta *sta, 668 struct ieee80211_key_conf *kc) 669 { 670 struct lkpi_hw *lhw; 671 int error; 672 673 lhw = HW_TO_LHW(hw); 674 if (lhw->ops->set_key == NULL) { 675 error = EOPNOTSUPP; 676 goto out; 677 } 678 679 LKPI_80211_TRACE_MO("hw %p cmd %d vif %p sta %p kc %p", hw, cmd, vif, sta, kc); 680 error = lhw->ops->set_key(hw, cmd, vif, sta, kc); 681 682 out: 683 return (error); 684 } 685