main.c revision e8975581f63870be42ff4662b293d1b0c8c21350
1/* 2 * Copyright (C) 2008, cozybit Inc. 3 * Copyright (C) 2003-2006, Marvell International Ltd. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or (at 8 * your option) any later version. 9 */ 10#include "libertas_tf.h" 11#include "linux/etherdevice.h" 12 13#define DRIVER_RELEASE_VERSION "004.p0" 14/* thinfirm version: 5.132.X.pX */ 15#define LBTF_FW_VER_MIN 0x05840300 16#define LBTF_FW_VER_MAX 0x0584ffff 17#define QOS_CONTROL_LEN 2 18 19static const char lbtf_driver_version[] = "THINFIRM-USB8388-" DRIVER_RELEASE_VERSION; 20struct workqueue_struct *lbtf_wq; 21 22static const struct ieee80211_channel lbtf_channels[] = { 23 { .center_freq = 2412, .hw_value = 1 }, 24 { .center_freq = 2417, .hw_value = 2 }, 25 { .center_freq = 2422, .hw_value = 3 }, 26 { .center_freq = 2427, .hw_value = 4 }, 27 { .center_freq = 2432, .hw_value = 5 }, 28 { .center_freq = 2437, .hw_value = 6 }, 29 { .center_freq = 2442, .hw_value = 7 }, 30 { .center_freq = 2447, .hw_value = 8 }, 31 { .center_freq = 2452, .hw_value = 9 }, 32 { .center_freq = 2457, .hw_value = 10 }, 33 { .center_freq = 2462, .hw_value = 11 }, 34 { .center_freq = 2467, .hw_value = 12 }, 35 { .center_freq = 2472, .hw_value = 13 }, 36 { .center_freq = 2484, .hw_value = 14 }, 37}; 38 39/* This table contains the hardware specific values for the modulation rates. */ 40static const struct ieee80211_rate lbtf_rates[] = { 41 { .bitrate = 10, 42 .hw_value = 0, }, 43 { .bitrate = 20, 44 .hw_value = 1, 45 .flags = IEEE80211_RATE_SHORT_PREAMBLE }, 46 { .bitrate = 55, 47 .hw_value = 2, 48 .flags = IEEE80211_RATE_SHORT_PREAMBLE }, 49 { .bitrate = 110, 50 .hw_value = 3, 51 .flags = IEEE80211_RATE_SHORT_PREAMBLE }, 52 { .bitrate = 60, 53 .hw_value = 5, 54 .flags = 0 }, 55 { .bitrate = 90, 56 .hw_value = 6, 57 .flags = 0 }, 58 { .bitrate = 120, 59 .hw_value = 7, 60 .flags = 0 }, 61 { .bitrate = 180, 62 .hw_value = 8, 63 .flags = 0 }, 64 { .bitrate = 240, 65 .hw_value = 9, 66 .flags = 0 }, 67 { .bitrate = 360, 68 .hw_value = 10, 69 .flags = 0 }, 70 { .bitrate = 480, 71 .hw_value = 11, 72 .flags = 0 }, 73 { .bitrate = 540, 74 .hw_value = 12, 75 .flags = 0 }, 76}; 77 78static void lbtf_cmd_work(struct work_struct *work) 79{ 80 struct lbtf_private *priv = container_of(work, struct lbtf_private, 81 cmd_work); 82 spin_lock_irq(&priv->driver_lock); 83 /* command response? */ 84 if (priv->cmd_response_rxed) { 85 priv->cmd_response_rxed = 0; 86 spin_unlock_irq(&priv->driver_lock); 87 lbtf_process_rx_command(priv); 88 spin_lock_irq(&priv->driver_lock); 89 } 90 91 if (priv->cmd_timed_out && priv->cur_cmd) { 92 struct cmd_ctrl_node *cmdnode = priv->cur_cmd; 93 94 if (++priv->nr_retries > 10) { 95 lbtf_complete_command(priv, cmdnode, 96 -ETIMEDOUT); 97 priv->nr_retries = 0; 98 } else { 99 priv->cur_cmd = NULL; 100 101 /* Stick it back at the _top_ of the pending 102 * queue for immediate resubmission */ 103 list_add(&cmdnode->list, &priv->cmdpendingq); 104 } 105 } 106 priv->cmd_timed_out = 0; 107 spin_unlock_irq(&priv->driver_lock); 108 109 if (!priv->fw_ready) 110 return; 111 /* Execute the next command */ 112 if (!priv->cur_cmd) 113 lbtf_execute_next_command(priv); 114} 115 116/** 117 * lbtf_setup_firmware: initialize firmware. 118 * 119 * @priv A pointer to struct lbtf_private structure 120 * 121 * Returns: 0 on success. 122 */ 123static int lbtf_setup_firmware(struct lbtf_private *priv) 124{ 125 int ret = -1; 126 127 /* 128 * Read priv address from HW 129 */ 130 memset(priv->current_addr, 0xff, ETH_ALEN); 131 ret = lbtf_update_hw_spec(priv); 132 if (ret) { 133 ret = -1; 134 goto done; 135 } 136 137 lbtf_set_mac_control(priv); 138 lbtf_set_radio_control(priv); 139 140 ret = 0; 141done: 142 return ret; 143} 144 145/** 146 * This function handles the timeout of command sending. 147 * It will re-send the same command again. 148 */ 149static void command_timer_fn(unsigned long data) 150{ 151 struct lbtf_private *priv = (struct lbtf_private *)data; 152 unsigned long flags; 153 154 spin_lock_irqsave(&priv->driver_lock, flags); 155 156 if (!priv->cur_cmd) { 157 printk(KERN_DEBUG "libertastf: command timer expired; " 158 "no pending command\n"); 159 goto out; 160 } 161 162 printk(KERN_DEBUG "libertas: command %x timed out\n", 163 le16_to_cpu(priv->cur_cmd->cmdbuf->command)); 164 165 priv->cmd_timed_out = 1; 166 queue_work(lbtf_wq, &priv->cmd_work); 167out: 168 spin_unlock_irqrestore(&priv->driver_lock, flags); 169} 170 171static int lbtf_init_adapter(struct lbtf_private *priv) 172{ 173 memset(priv->current_addr, 0xff, ETH_ALEN); 174 mutex_init(&priv->lock); 175 176 priv->vif = NULL; 177 setup_timer(&priv->command_timer, command_timer_fn, 178 (unsigned long)priv); 179 180 INIT_LIST_HEAD(&priv->cmdfreeq); 181 INIT_LIST_HEAD(&priv->cmdpendingq); 182 183 spin_lock_init(&priv->driver_lock); 184 185 /* Allocate the command buffers */ 186 if (lbtf_allocate_cmd_buffer(priv)) 187 return -1; 188 189 return 0; 190} 191 192static void lbtf_free_adapter(struct lbtf_private *priv) 193{ 194 lbtf_free_cmd_buffer(priv); 195 del_timer(&priv->command_timer); 196} 197 198static int lbtf_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb) 199{ 200 struct lbtf_private *priv = hw->priv; 201 202 priv->skb_to_tx = skb; 203 queue_work(lbtf_wq, &priv->tx_work); 204 /* 205 * queue will be restarted when we receive transmission feedback if 206 * there are no buffered multicast frames to send 207 */ 208 ieee80211_stop_queues(priv->hw); 209 return 0; 210} 211 212static void lbtf_tx_work(struct work_struct *work) 213{ 214 struct lbtf_private *priv = container_of(work, struct lbtf_private, 215 tx_work); 216 unsigned int len; 217 struct ieee80211_tx_info *info; 218 struct txpd *txpd; 219 struct sk_buff *skb = NULL; 220 int err; 221 222 if ((priv->vif->type == NL80211_IFTYPE_AP) && 223 (!skb_queue_empty(&priv->bc_ps_buf))) 224 skb = skb_dequeue(&priv->bc_ps_buf); 225 else if (priv->skb_to_tx) { 226 skb = priv->skb_to_tx; 227 priv->skb_to_tx = NULL; 228 } else 229 return; 230 231 len = skb->len; 232 info = IEEE80211_SKB_CB(skb); 233 txpd = (struct txpd *) skb_push(skb, sizeof(struct txpd)); 234 235 if (priv->surpriseremoved) { 236 dev_kfree_skb_any(skb); 237 return; 238 } 239 240 memset(txpd, 0, sizeof(struct txpd)); 241 /* Activate per-packet rate selection */ 242 txpd->tx_control |= cpu_to_le32(MRVL_PER_PACKET_RATE | 243 ieee80211_get_tx_rate(priv->hw, info)->hw_value); 244 245 /* copy destination address from 802.11 header */ 246 memcpy(txpd->tx_dest_addr_high, skb->data + sizeof(struct txpd) + 4, 247 ETH_ALEN); 248 txpd->tx_packet_length = cpu_to_le16(len); 249 txpd->tx_packet_location = cpu_to_le32(sizeof(struct txpd)); 250 BUG_ON(priv->tx_skb); 251 spin_lock_irq(&priv->driver_lock); 252 priv->tx_skb = skb; 253 err = priv->hw_host_to_card(priv, MVMS_DAT, skb->data, skb->len); 254 spin_unlock_irq(&priv->driver_lock); 255 if (err) { 256 dev_kfree_skb_any(skb); 257 priv->tx_skb = NULL; 258 } 259} 260 261static int lbtf_op_start(struct ieee80211_hw *hw) 262{ 263 struct lbtf_private *priv = hw->priv; 264 void *card = priv->card; 265 int ret = -1; 266 267 if (!priv->fw_ready) 268 /* Upload firmware */ 269 if (priv->hw_prog_firmware(card)) 270 goto err_prog_firmware; 271 272 /* poke the firmware */ 273 priv->capability = WLAN_CAPABILITY_SHORT_PREAMBLE; 274 priv->radioon = RADIO_ON; 275 priv->mac_control = CMD_ACT_MAC_RX_ON | CMD_ACT_MAC_TX_ON; 276 ret = lbtf_setup_firmware(priv); 277 if (ret) 278 goto err_prog_firmware; 279 280 if ((priv->fwrelease < LBTF_FW_VER_MIN) || 281 (priv->fwrelease > LBTF_FW_VER_MAX)) { 282 ret = -1; 283 goto err_prog_firmware; 284 } 285 286 printk(KERN_INFO "libertastf: Marvell WLAN 802.11 thinfirm adapter\n"); 287 return 0; 288 289err_prog_firmware: 290 priv->hw_reset_device(card); 291 return ret; 292} 293 294static void lbtf_op_stop(struct ieee80211_hw *hw) 295{ 296 struct lbtf_private *priv = hw->priv; 297 unsigned long flags; 298 struct sk_buff *skb; 299 300 struct cmd_ctrl_node *cmdnode; 301 /* Flush pending command nodes */ 302 spin_lock_irqsave(&priv->driver_lock, flags); 303 list_for_each_entry(cmdnode, &priv->cmdpendingq, list) { 304 cmdnode->result = -ENOENT; 305 cmdnode->cmdwaitqwoken = 1; 306 wake_up_interruptible(&cmdnode->cmdwait_q); 307 } 308 309 spin_unlock_irqrestore(&priv->driver_lock, flags); 310 cancel_work_sync(&priv->cmd_work); 311 cancel_work_sync(&priv->tx_work); 312 while ((skb = skb_dequeue(&priv->bc_ps_buf))) 313 dev_kfree_skb_any(skb); 314 priv->radioon = RADIO_OFF; 315 lbtf_set_radio_control(priv); 316 317 return; 318} 319 320static int lbtf_op_add_interface(struct ieee80211_hw *hw, 321 struct ieee80211_if_init_conf *conf) 322{ 323 struct lbtf_private *priv = hw->priv; 324 if (priv->vif != NULL) 325 return -EOPNOTSUPP; 326 327 priv->vif = conf->vif; 328 switch (conf->type) { 329 case NL80211_IFTYPE_MESH_POINT: 330 case NL80211_IFTYPE_AP: 331 lbtf_set_mode(priv, LBTF_AP_MODE); 332 break; 333 case NL80211_IFTYPE_STATION: 334 lbtf_set_mode(priv, LBTF_STA_MODE); 335 break; 336 default: 337 priv->vif = NULL; 338 return -EOPNOTSUPP; 339 } 340 lbtf_set_mac_address(priv, (u8 *) conf->mac_addr); 341 return 0; 342} 343 344static void lbtf_op_remove_interface(struct ieee80211_hw *hw, 345 struct ieee80211_if_init_conf *conf) 346{ 347 struct lbtf_private *priv = hw->priv; 348 349 if (priv->vif->type == NL80211_IFTYPE_AP || 350 priv->vif->type == NL80211_IFTYPE_MESH_POINT) 351 lbtf_beacon_ctrl(priv, 0, 0); 352 lbtf_set_mode(priv, LBTF_PASSIVE_MODE); 353 lbtf_set_bssid(priv, 0, NULL); 354 priv->vif = NULL; 355} 356 357static int lbtf_op_config(struct ieee80211_hw *hw, u32 changed) 358{ 359 struct lbtf_private *priv = hw->priv; 360 struct ieee80211_conf *conf = &hw->conf; 361 362 if (conf->channel->center_freq != priv->cur_freq) { 363 priv->cur_freq = conf->channel->center_freq; 364 lbtf_set_channel(priv, conf->channel->hw_value); 365 } 366 return 0; 367} 368 369static int lbtf_op_config_interface(struct ieee80211_hw *hw, 370 struct ieee80211_vif *vif, 371 struct ieee80211_if_conf *conf) 372{ 373 struct lbtf_private *priv = hw->priv; 374 struct sk_buff *beacon; 375 376 switch (priv->vif->type) { 377 case NL80211_IFTYPE_AP: 378 case NL80211_IFTYPE_MESH_POINT: 379 beacon = ieee80211_beacon_get(hw, vif); 380 if (beacon) { 381 lbtf_beacon_set(priv, beacon); 382 kfree_skb(beacon); 383 lbtf_beacon_ctrl(priv, 1, hw->conf.beacon_int); 384 } 385 break; 386 default: 387 break; 388 } 389 390 if (conf->bssid) { 391 u8 null_bssid[ETH_ALEN] = {0}; 392 bool activate = compare_ether_addr(conf->bssid, null_bssid); 393 lbtf_set_bssid(priv, activate, conf->bssid); 394 } 395 396 return 0; 397} 398 399#define SUPPORTED_FIF_FLAGS (FIF_PROMISC_IN_BSS | FIF_ALLMULTI) 400static void lbtf_op_configure_filter(struct ieee80211_hw *hw, 401 unsigned int changed_flags, 402 unsigned int *new_flags, 403 int mc_count, struct dev_mc_list *mclist) 404{ 405 struct lbtf_private *priv = hw->priv; 406 int old_mac_control = priv->mac_control; 407 int i; 408 changed_flags &= SUPPORTED_FIF_FLAGS; 409 *new_flags &= SUPPORTED_FIF_FLAGS; 410 411 if (!changed_flags) 412 return; 413 414 if (*new_flags & (FIF_PROMISC_IN_BSS)) 415 priv->mac_control |= CMD_ACT_MAC_PROMISCUOUS_ENABLE; 416 else 417 priv->mac_control &= ~CMD_ACT_MAC_PROMISCUOUS_ENABLE; 418 if (*new_flags & (FIF_ALLMULTI) || 419 mc_count > MRVDRV_MAX_MULTICAST_LIST_SIZE) { 420 priv->mac_control |= CMD_ACT_MAC_ALL_MULTICAST_ENABLE; 421 priv->mac_control &= ~CMD_ACT_MAC_MULTICAST_ENABLE; 422 } else if (mc_count) { 423 priv->mac_control |= CMD_ACT_MAC_MULTICAST_ENABLE; 424 priv->mac_control &= ~CMD_ACT_MAC_ALL_MULTICAST_ENABLE; 425 priv->nr_of_multicastmacaddr = mc_count; 426 for (i = 0; i < mc_count; i++) { 427 if (!mclist) 428 break; 429 memcpy(&priv->multicastlist[i], mclist->da_addr, 430 ETH_ALEN); 431 mclist = mclist->next; 432 } 433 lbtf_cmd_set_mac_multicast_addr(priv); 434 } else { 435 priv->mac_control &= ~(CMD_ACT_MAC_MULTICAST_ENABLE | 436 CMD_ACT_MAC_ALL_MULTICAST_ENABLE); 437 if (priv->nr_of_multicastmacaddr) { 438 priv->nr_of_multicastmacaddr = 0; 439 lbtf_cmd_set_mac_multicast_addr(priv); 440 } 441 } 442 443 444 if (priv->mac_control != old_mac_control) 445 lbtf_set_mac_control(priv); 446} 447 448static void lbtf_op_bss_info_changed(struct ieee80211_hw *hw, 449 struct ieee80211_vif *vif, 450 struct ieee80211_bss_conf *bss_conf, 451 u32 changes) 452{ 453 struct lbtf_private *priv = hw->priv; 454 455 if (changes & BSS_CHANGED_ERP_PREAMBLE) { 456 if (bss_conf->use_short_preamble) 457 priv->preamble = CMD_TYPE_SHORT_PREAMBLE; 458 else 459 priv->preamble = CMD_TYPE_LONG_PREAMBLE; 460 lbtf_set_radio_control(priv); 461 } 462 463 return; 464} 465 466static const struct ieee80211_ops lbtf_ops = { 467 .tx = lbtf_op_tx, 468 .start = lbtf_op_start, 469 .stop = lbtf_op_stop, 470 .add_interface = lbtf_op_add_interface, 471 .remove_interface = lbtf_op_remove_interface, 472 .config = lbtf_op_config, 473 .config_interface = lbtf_op_config_interface, 474 .configure_filter = lbtf_op_configure_filter, 475 .bss_info_changed = lbtf_op_bss_info_changed, 476}; 477 478int lbtf_rx(struct lbtf_private *priv, struct sk_buff *skb) 479{ 480 struct ieee80211_rx_status stats; 481 struct rxpd *prxpd; 482 int need_padding; 483 unsigned int flags; 484 struct ieee80211_hdr *hdr; 485 486 prxpd = (struct rxpd *) skb->data; 487 488 stats.flag = 0; 489 if (!(prxpd->status & cpu_to_le16(MRVDRV_RXPD_STATUS_OK))) 490 stats.flag |= RX_FLAG_FAILED_FCS_CRC; 491 stats.freq = priv->cur_freq; 492 stats.band = IEEE80211_BAND_2GHZ; 493 stats.signal = prxpd->snr; 494 stats.noise = prxpd->nf; 495 stats.qual = prxpd->snr - prxpd->nf; 496 /* Marvell rate index has a hole at value 4 */ 497 if (prxpd->rx_rate > 4) 498 --prxpd->rx_rate; 499 stats.rate_idx = prxpd->rx_rate; 500 skb_pull(skb, sizeof(struct rxpd)); 501 502 hdr = (struct ieee80211_hdr *)skb->data; 503 flags = le32_to_cpu(*(__le32 *)(skb->data + 4)); 504 505 need_padding = ieee80211_is_data_qos(hdr->frame_control); 506 need_padding ^= ieee80211_has_a4(hdr->frame_control); 507 need_padding ^= ieee80211_is_data_qos(hdr->frame_control) && 508 (*ieee80211_get_qos_ctl(hdr) & 509 IEEE80211_QOS_CONTROL_A_MSDU_PRESENT); 510 511 if (need_padding) { 512 memmove(skb->data + 2, skb->data, skb->len); 513 skb_reserve(skb, 2); 514 } 515 516 ieee80211_rx_irqsafe(priv->hw, skb, &stats); 517 return 0; 518} 519EXPORT_SYMBOL_GPL(lbtf_rx); 520 521/** 522 * lbtf_add_card: Add and initialize the card, no fw upload yet. 523 * 524 * @card A pointer to card 525 * 526 * Returns: pointer to struct lbtf_priv. 527 */ 528struct lbtf_private *lbtf_add_card(void *card, struct device *dmdev) 529{ 530 struct ieee80211_hw *hw; 531 struct lbtf_private *priv = NULL; 532 533 hw = ieee80211_alloc_hw(sizeof(struct lbtf_private), &lbtf_ops); 534 if (!hw) 535 goto done; 536 537 priv = hw->priv; 538 if (lbtf_init_adapter(priv)) 539 goto err_init_adapter; 540 541 priv->hw = hw; 542 priv->card = card; 543 priv->tx_skb = NULL; 544 545 hw->queues = 1; 546 hw->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING; 547 hw->extra_tx_headroom = sizeof(struct txpd); 548 memcpy(priv->channels, lbtf_channels, sizeof(lbtf_channels)); 549 memcpy(priv->rates, lbtf_rates, sizeof(lbtf_rates)); 550 priv->band.n_bitrates = ARRAY_SIZE(lbtf_rates); 551 priv->band.bitrates = priv->rates; 552 priv->band.n_channels = ARRAY_SIZE(lbtf_channels); 553 priv->band.channels = priv->channels; 554 hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; 555 skb_queue_head_init(&priv->bc_ps_buf); 556 557 SET_IEEE80211_DEV(hw, dmdev); 558 559 INIT_WORK(&priv->cmd_work, lbtf_cmd_work); 560 INIT_WORK(&priv->tx_work, lbtf_tx_work); 561 if (ieee80211_register_hw(hw)) 562 goto err_init_adapter; 563 564 goto done; 565 566err_init_adapter: 567 lbtf_free_adapter(priv); 568 ieee80211_free_hw(hw); 569 priv = NULL; 570 571done: 572 return priv; 573} 574EXPORT_SYMBOL_GPL(lbtf_add_card); 575 576 577int lbtf_remove_card(struct lbtf_private *priv) 578{ 579 struct ieee80211_hw *hw = priv->hw; 580 581 priv->surpriseremoved = 1; 582 del_timer(&priv->command_timer); 583 lbtf_free_adapter(priv); 584 priv->hw = NULL; 585 ieee80211_unregister_hw(hw); 586 ieee80211_free_hw(hw); 587 588 return 0; 589} 590EXPORT_SYMBOL_GPL(lbtf_remove_card); 591 592void lbtf_send_tx_feedback(struct lbtf_private *priv, u8 retrycnt, u8 fail) 593{ 594 struct ieee80211_tx_info *info = IEEE80211_SKB_CB(priv->tx_skb); 595 memset(&info->status, 0, sizeof(info->status)); 596 /* 597 * Commented out, otherwise we never go beyond 1Mbit/s using mac80211 598 * default pid rc algorithm. 599 * 600 * info->status.retry_count = MRVL_DEFAULT_RETRIES - retrycnt; 601 */ 602 info->status.excessive_retries = fail ? 1 : 0; 603 if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) && !fail) 604 info->flags |= IEEE80211_TX_STAT_ACK; 605 skb_pull(priv->tx_skb, sizeof(struct txpd)); 606 ieee80211_tx_status_irqsafe(priv->hw, priv->tx_skb); 607 priv->tx_skb = NULL; 608 if (!priv->skb_to_tx && skb_queue_empty(&priv->bc_ps_buf)) 609 ieee80211_wake_queues(priv->hw); 610 else 611 queue_work(lbtf_wq, &priv->tx_work); 612} 613EXPORT_SYMBOL_GPL(lbtf_send_tx_feedback); 614 615void lbtf_bcn_sent(struct lbtf_private *priv) 616{ 617 struct sk_buff *skb = NULL; 618 619 if (priv->vif->type != NL80211_IFTYPE_AP) 620 return; 621 622 if (skb_queue_empty(&priv->bc_ps_buf)) { 623 bool tx_buff_bc = 0; 624 625 while ((skb = ieee80211_get_buffered_bc(priv->hw, priv->vif))) { 626 skb_queue_tail(&priv->bc_ps_buf, skb); 627 tx_buff_bc = 1; 628 } 629 if (tx_buff_bc) { 630 ieee80211_stop_queues(priv->hw); 631 queue_work(lbtf_wq, &priv->tx_work); 632 } 633 } 634 635 skb = ieee80211_beacon_get(priv->hw, priv->vif); 636 637 if (skb) { 638 lbtf_beacon_set(priv, skb); 639 kfree_skb(skb); 640 } 641} 642EXPORT_SYMBOL_GPL(lbtf_bcn_sent); 643 644static int __init lbtf_init_module(void) 645{ 646 lbtf_wq = create_workqueue("libertastf"); 647 if (lbtf_wq == NULL) { 648 printk(KERN_ERR "libertastf: couldn't create workqueue\n"); 649 return -ENOMEM; 650 } 651 return 0; 652} 653 654static void __exit lbtf_exit_module(void) 655{ 656 destroy_workqueue(lbtf_wq); 657} 658 659module_init(lbtf_init_module); 660module_exit(lbtf_exit_module); 661 662MODULE_DESCRIPTION("Libertas WLAN Thinfirm Driver Library"); 663MODULE_AUTHOR("Cozybit Inc."); 664MODULE_LICENSE("GPL"); 665