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