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 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->link_info_changed == NULL && 544 lhw->ops->bss_info_changed == NULL) 545 return; 546 547 LKPI_80211_TRACE_MO("hw %p vif %p conf %p changed %#jx", hw, vif, conf, (uintmax_t)changed); 548 if (lhw->ops->link_info_changed != NULL) 549 lhw->ops->link_info_changed(hw, vif, conf, changed); 550 else 551 lhw->ops->bss_info_changed(hw, vif, conf, changed); 552 } 553 554 int 555 lkpi_80211_mo_conf_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 556 uint32_t link_id, uint16_t ac, const struct ieee80211_tx_queue_params *txqp) 557 { 558 struct lkpi_hw *lhw; 559 int error; 560 561 lhw = HW_TO_LHW(hw); 562 if (lhw->ops->conf_tx == NULL) { 563 error = EOPNOTSUPP; 564 goto out; 565 } 566 567 LKPI_80211_TRACE_MO("hw %p vif %p link_id %u ac %u txpq %p", 568 hw, vif, link_id, ac, txqp); 569 error = lhw->ops->conf_tx(hw, vif, link_id, ac, txqp); 570 571 out: 572 return (error); 573 } 574 575 void 576 lkpi_80211_mo_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 577 uint32_t nqueues, bool drop) 578 { 579 struct lkpi_hw *lhw; 580 581 lhw = HW_TO_LHW(hw); 582 if (lhw->ops->flush == NULL) 583 return; 584 585 LKPI_80211_TRACE_MO("hw %p vif %p nqueues %u drop %d", hw, vif, nqueues, drop); 586 lhw->ops->flush(hw, vif, nqueues, drop); 587 } 588 589 void 590 lkpi_80211_mo_mgd_prepare_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 591 struct ieee80211_prep_tx_info *txinfo) 592 { 593 struct lkpi_hw *lhw; 594 595 lhw = HW_TO_LHW(hw); 596 if (lhw->ops->mgd_prepare_tx == NULL) 597 return; 598 599 LKPI_80211_TRACE_MO("hw %p vif %p txinfo %p", hw, vif, txinfo); 600 lhw->ops->mgd_prepare_tx(hw, vif, txinfo); 601 } 602 603 void 604 lkpi_80211_mo_mgd_complete_tx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, 605 struct ieee80211_prep_tx_info *txinfo) 606 { 607 struct lkpi_hw *lhw; 608 609 lhw = HW_TO_LHW(hw); 610 if (lhw->ops->mgd_complete_tx == NULL) 611 return; 612 613 LKPI_80211_TRACE_MO("hw %p vif %p txinfo %p", hw, vif, txinfo); 614 lhw->ops->mgd_complete_tx(hw, vif, txinfo); 615 } 616 617 void 618 lkpi_80211_mo_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *txctrl, 619 struct sk_buff *skb) 620 { 621 struct lkpi_hw *lhw; 622 623 lhw = HW_TO_LHW(hw); 624 if (lhw->ops->tx == NULL) 625 return; 626 627 LKPI_80211_TRACE_MO("hw %p txctrl %p skb %p", hw, txctrl, skb); 628 lhw->ops->tx(hw, txctrl, skb); 629 } 630 631 void 632 lkpi_80211_mo_wake_tx_queue(struct ieee80211_hw *hw, struct ieee80211_txq *txq) 633 { 634 struct lkpi_hw *lhw; 635 636 lhw = HW_TO_LHW(hw); 637 if (lhw->ops->wake_tx_queue == NULL) 638 return; 639 640 LKPI_80211_TRACE_MO("hw %p txq %p", hw, txq); 641 lhw->ops->wake_tx_queue(hw, txq); 642 } 643 644 void 645 lkpi_80211_mo_sync_rx_queues(struct ieee80211_hw *hw) 646 { 647 struct lkpi_hw *lhw; 648 649 lhw = HW_TO_LHW(hw); 650 if (lhw->ops->sync_rx_queues == NULL) 651 return; 652 653 LKPI_80211_TRACE_MO("hw %p", hw); 654 lhw->ops->sync_rx_queues(hw); 655 } 656 657 void 658 lkpi_80211_mo_sta_pre_rcu_remove(struct ieee80211_hw *hw, 659 struct ieee80211_vif *vif, struct ieee80211_sta *sta) 660 { 661 struct lkpi_hw *lhw; 662 663 lhw = HW_TO_LHW(hw); 664 if (lhw->ops->sta_pre_rcu_remove == NULL) 665 return; 666 667 LKPI_80211_TRACE_MO("hw %p vif %p sta %p", hw, vif, sta); 668 lhw->ops->sta_pre_rcu_remove(hw, vif, sta); 669 } 670 671 int 672 lkpi_80211_mo_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, 673 struct ieee80211_vif *vif, struct ieee80211_sta *sta, 674 struct ieee80211_key_conf *kc) 675 { 676 struct lkpi_hw *lhw; 677 int error; 678 679 lhw = HW_TO_LHW(hw); 680 if (lhw->ops->set_key == NULL) { 681 error = EOPNOTSUPP; 682 goto out; 683 } 684 685 LKPI_80211_TRACE_MO("hw %p cmd %d vif %p sta %p kc %p", hw, cmd, vif, sta, kc); 686 error = lhw->ops->set_key(hw, cmd, vif, sta, kc); 687 688 out: 689 return (error); 690 } 691