main.c revision de9cc7a4e6f975ca5e91cf8745b3e35a7e780bae
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 == IEEE80211_IF_TYPE_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 IEEE80211_IF_TYPE_MESH_POINT: 330 case IEEE80211_IF_TYPE_AP: 331 lbtf_set_mode(priv, LBTF_AP_MODE); 332 break; 333 case IEEE80211_IF_TYPE_STA: 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 == IEEE80211_IF_TYPE_AP || 350 priv->vif->type == IEEE80211_IF_TYPE_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, struct ieee80211_conf *conf) 358{ 359 struct lbtf_private *priv = hw->priv; 360 if (conf->channel->center_freq != priv->cur_freq) { 361 priv->cur_freq = conf->channel->center_freq; 362 lbtf_set_channel(priv, conf->channel->hw_value); 363 } 364 return 0; 365} 366 367static int lbtf_op_config_interface(struct ieee80211_hw *hw, 368 struct ieee80211_vif *vif, 369 struct ieee80211_if_conf *conf) 370{ 371 struct lbtf_private *priv = hw->priv; 372 struct sk_buff *beacon; 373 374 switch (priv->vif->type) { 375 case IEEE80211_IF_TYPE_AP: 376 case IEEE80211_IF_TYPE_MESH_POINT: 377 beacon = ieee80211_beacon_get(hw, vif); 378 if (beacon) { 379 lbtf_beacon_set(priv, beacon); 380 kfree_skb(beacon); 381 lbtf_beacon_ctrl(priv, 1, hw->conf.beacon_int); 382 } 383 break; 384 default: 385 break; 386 } 387 388 if (conf->bssid) { 389 u8 null_bssid[ETH_ALEN] = {0}; 390 bool activate = compare_ether_addr(conf->bssid, null_bssid); 391 lbtf_set_bssid(priv, activate, conf->bssid); 392 } 393 394 return 0; 395} 396 397#define SUPPORTED_FIF_FLAGS (FIF_PROMISC_IN_BSS | FIF_ALLMULTI) 398static void lbtf_op_configure_filter(struct ieee80211_hw *hw, 399 unsigned int changed_flags, 400 unsigned int *new_flags, 401 int mc_count, struct dev_mc_list *mclist) 402{ 403 struct lbtf_private *priv = hw->priv; 404 int old_mac_control = priv->mac_control; 405 int i; 406 changed_flags &= SUPPORTED_FIF_FLAGS; 407 *new_flags &= SUPPORTED_FIF_FLAGS; 408 409 if (!changed_flags) 410 return; 411 412 if (*new_flags & (FIF_PROMISC_IN_BSS)) 413 priv->mac_control |= CMD_ACT_MAC_PROMISCUOUS_ENABLE; 414 else 415 priv->mac_control &= ~CMD_ACT_MAC_PROMISCUOUS_ENABLE; 416 if (*new_flags & (FIF_ALLMULTI) || 417 mc_count > MRVDRV_MAX_MULTICAST_LIST_SIZE) { 418 priv->mac_control |= CMD_ACT_MAC_ALL_MULTICAST_ENABLE; 419 priv->mac_control &= ~CMD_ACT_MAC_MULTICAST_ENABLE; 420 } else if (mc_count) { 421 priv->mac_control |= CMD_ACT_MAC_MULTICAST_ENABLE; 422 priv->mac_control &= ~CMD_ACT_MAC_ALL_MULTICAST_ENABLE; 423 priv->nr_of_multicastmacaddr = mc_count; 424 for (i = 0; i < mc_count; i++) { 425 if (!mclist) 426 break; 427 memcpy(&priv->multicastlist[i], mclist->da_addr, 428 ETH_ALEN); 429 mclist = mclist->next; 430 } 431 lbtf_cmd_set_mac_multicast_addr(priv); 432 } else { 433 priv->mac_control &= ~(CMD_ACT_MAC_MULTICAST_ENABLE | 434 CMD_ACT_MAC_ALL_MULTICAST_ENABLE); 435 if (priv->nr_of_multicastmacaddr) { 436 priv->nr_of_multicastmacaddr = 0; 437 lbtf_cmd_set_mac_multicast_addr(priv); 438 } 439 } 440 441 442 if (priv->mac_control != old_mac_control) 443 lbtf_set_mac_control(priv); 444} 445 446static void lbtf_op_bss_info_changed(struct ieee80211_hw *hw, 447 struct ieee80211_vif *vif, 448 struct ieee80211_bss_conf *bss_conf, 449 u32 changes) 450{ 451 struct lbtf_private *priv = hw->priv; 452 453 if (changes & BSS_CHANGED_ERP_PREAMBLE) { 454 if (bss_conf->use_short_preamble) 455 priv->preamble = CMD_TYPE_SHORT_PREAMBLE; 456 else 457 priv->preamble = CMD_TYPE_LONG_PREAMBLE; 458 lbtf_set_radio_control(priv); 459 } 460 461 return; 462} 463 464static const struct ieee80211_ops lbtf_ops = { 465 .tx = lbtf_op_tx, 466 .start = lbtf_op_start, 467 .stop = lbtf_op_stop, 468 .add_interface = lbtf_op_add_interface, 469 .remove_interface = lbtf_op_remove_interface, 470 .config = lbtf_op_config, 471 .config_interface = lbtf_op_config_interface, 472 .configure_filter = lbtf_op_configure_filter, 473 .bss_info_changed = lbtf_op_bss_info_changed, 474}; 475 476int lbtf_rx(struct lbtf_private *priv, struct sk_buff *skb) 477{ 478 struct ieee80211_rx_status stats; 479 struct rxpd *prxpd; 480 int need_padding; 481 unsigned int flags; 482 struct ieee80211_hdr *hdr; 483 484 prxpd = (struct rxpd *) skb->data; 485 486 stats.flag = 0; 487 if (!(prxpd->status & cpu_to_le16(MRVDRV_RXPD_STATUS_OK))) 488 stats.flag |= RX_FLAG_FAILED_FCS_CRC; 489 stats.freq = priv->cur_freq; 490 stats.band = IEEE80211_BAND_2GHZ; 491 stats.signal = prxpd->snr; 492 stats.noise = prxpd->nf; 493 stats.qual = prxpd->snr - prxpd->nf; 494 /* Marvell rate index has a hole at value 4 */ 495 if (prxpd->rx_rate > 4) 496 --prxpd->rx_rate; 497 stats.rate_idx = prxpd->rx_rate; 498 skb_pull(skb, sizeof(struct rxpd)); 499 500 hdr = (struct ieee80211_hdr *)skb->data; 501 flags = le32_to_cpu(*(__le32 *)(skb->data + 4)); 502 503 need_padding = ieee80211_is_data_qos(hdr->frame_control); 504 need_padding ^= ieee80211_has_a4(hdr->frame_control); 505 need_padding ^= ieee80211_is_data_qos(hdr->frame_control) && 506 (*ieee80211_get_qos_ctl(hdr) & 507 IEEE80211_QOS_CONTROL_A_MSDU_PRESENT); 508 509 if (need_padding) { 510 memmove(skb->data + 2, skb->data, skb->len); 511 skb_reserve(skb, 2); 512 } 513 514 ieee80211_rx_irqsafe(priv->hw, skb, &stats); 515 return 0; 516} 517EXPORT_SYMBOL_GPL(lbtf_rx); 518 519/** 520 * lbtf_add_card: Add and initialize the card, no fw upload yet. 521 * 522 * @card A pointer to card 523 * 524 * Returns: pointer to struct lbtf_priv. 525 */ 526struct lbtf_private *lbtf_add_card(void *card, struct device *dmdev) 527{ 528 struct ieee80211_hw *hw; 529 struct lbtf_private *priv = NULL; 530 531 hw = ieee80211_alloc_hw(sizeof(struct lbtf_private), &lbtf_ops); 532 if (!hw) 533 goto done; 534 535 priv = hw->priv; 536 if (lbtf_init_adapter(priv)) 537 goto err_init_adapter; 538 539 priv->hw = hw; 540 priv->card = card; 541 priv->tx_skb = NULL; 542 543 hw->queues = 1; 544 hw->flags = IEEE80211_HW_HOST_BROADCAST_PS_BUFFERING; 545 hw->extra_tx_headroom = sizeof(struct txpd); 546 memcpy(priv->channels, lbtf_channels, sizeof(lbtf_channels)); 547 memcpy(priv->rates, lbtf_rates, sizeof(lbtf_rates)); 548 priv->band.n_bitrates = ARRAY_SIZE(lbtf_rates); 549 priv->band.bitrates = priv->rates; 550 priv->band.n_channels = ARRAY_SIZE(lbtf_channels); 551 priv->band.channels = priv->channels; 552 hw->wiphy->bands[IEEE80211_BAND_2GHZ] = &priv->band; 553 skb_queue_head_init(&priv->bc_ps_buf); 554 555 SET_IEEE80211_DEV(hw, dmdev); 556 557 INIT_WORK(&priv->cmd_work, lbtf_cmd_work); 558 INIT_WORK(&priv->tx_work, lbtf_tx_work); 559 if (ieee80211_register_hw(hw)) 560 goto err_init_adapter; 561 562 goto done; 563 564err_init_adapter: 565 lbtf_free_adapter(priv); 566 ieee80211_free_hw(hw); 567 priv = NULL; 568 569done: 570 return priv; 571} 572EXPORT_SYMBOL_GPL(lbtf_add_card); 573 574 575int lbtf_remove_card(struct lbtf_private *priv) 576{ 577 struct ieee80211_hw *hw = priv->hw; 578 579 priv->surpriseremoved = 1; 580 del_timer(&priv->command_timer); 581 lbtf_free_adapter(priv); 582 priv->hw = NULL; 583 ieee80211_unregister_hw(hw); 584 ieee80211_free_hw(hw); 585 586 return 0; 587} 588EXPORT_SYMBOL_GPL(lbtf_remove_card); 589 590void lbtf_send_tx_feedback(struct lbtf_private *priv, u8 retrycnt, u8 fail) 591{ 592 struct ieee80211_tx_info *info = IEEE80211_SKB_CB(priv->tx_skb); 593 memset(&info->status, 0, sizeof(info->status)); 594 /* 595 * Commented out, otherwise we never go beyond 1Mbit/s using mac80211 596 * default pid rc algorithm. 597 * 598 * info->status.retry_count = MRVL_DEFAULT_RETRIES - retrycnt; 599 */ 600 info->status.excessive_retries = fail ? 1 : 0; 601 if (!(info->flags & IEEE80211_TX_CTL_NO_ACK) && !fail) 602 info->flags |= IEEE80211_TX_STAT_ACK; 603 skb_pull(priv->tx_skb, sizeof(struct txpd)); 604 ieee80211_tx_status_irqsafe(priv->hw, priv->tx_skb); 605 priv->tx_skb = NULL; 606 if (!priv->skb_to_tx && skb_queue_empty(&priv->bc_ps_buf)) 607 ieee80211_wake_queues(priv->hw); 608 else 609 queue_work(lbtf_wq, &priv->tx_work); 610} 611EXPORT_SYMBOL_GPL(lbtf_send_tx_feedback); 612 613void lbtf_bcn_sent(struct lbtf_private *priv) 614{ 615 struct sk_buff *skb = NULL; 616 617 if (priv->vif->type != IEEE80211_IF_TYPE_AP) 618 return; 619 620 if (skb_queue_empty(&priv->bc_ps_buf)) { 621 bool tx_buff_bc = 0; 622 623 while ((skb = ieee80211_get_buffered_bc(priv->hw, priv->vif))) { 624 skb_queue_tail(&priv->bc_ps_buf, skb); 625 tx_buff_bc = 1; 626 } 627 if (tx_buff_bc) { 628 ieee80211_stop_queues(priv->hw); 629 queue_work(lbtf_wq, &priv->tx_work); 630 } 631 } 632 633 skb = ieee80211_beacon_get(priv->hw, priv->vif); 634 635 if (skb) { 636 lbtf_beacon_set(priv, skb); 637 kfree_skb(skb); 638 } 639} 640EXPORT_SYMBOL_GPL(lbtf_bcn_sent); 641 642static int __init lbtf_init_module(void) 643{ 644 lbtf_wq = create_workqueue("libertastf"); 645 if (lbtf_wq == NULL) { 646 printk(KERN_ERR "libertastf: couldn't create workqueue\n"); 647 return -ENOMEM; 648 } 649 return 0; 650} 651 652static void __exit lbtf_exit_module(void) 653{ 654 destroy_workqueue(lbtf_wq); 655} 656 657module_init(lbtf_init_module); 658module_exit(lbtf_exit_module); 659 660MODULE_DESCRIPTION("Libertas WLAN Thinfirm Driver Library"); 661MODULE_AUTHOR("Cozybit Inc."); 662MODULE_LICENSE("GPL"); 663