/* * Intel Wireless Multicomm 3200 WiFi driver * * Copyright (C) 2009 Intel Corporation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * * Intel Corporation * Samuel Ortiz * Zhu Yi * */ #include #include #include #include #include "iwm.h" #include "debug.h" #include "bus.h" #include "umac.h" #include "commands.h" #include "hal.h" #include "fw.h" #include "rx.h" static struct iwm_conf def_iwm_conf = { .sdio_ior_timeout = 5000, .calib_map = BIT(PHY_CALIBRATE_DC_CMD) | BIT(PHY_CALIBRATE_LO_CMD) | BIT(PHY_CALIBRATE_TX_IQ_CMD) | BIT(PHY_CALIBRATE_RX_IQ_CMD) | BIT(SHILOH_PHY_CALIBRATE_BASE_BAND_CMD), .reset_on_fatal_err = 1, .auto_connect = 1, .wimax_not_present = 0, .enable_qos = 1, .mode = UMAC_MODE_BSS, /* UMAC configuration */ .power_index = 0, .frag_threshold = IEEE80211_MAX_FRAG_THRESHOLD, .rts_threshold = IEEE80211_MAX_RTS_THRESHOLD, .cts_to_self = 0, .assoc_timeout = 2, .roam_timeout = 10, .wireless_mode = WIRELESS_MODE_11A | WIRELESS_MODE_11G, .coexist_mode = COEX_MODE_CM, /* IBSS */ .ibss_band = UMAC_BAND_2GHZ, .ibss_channel = 1, .mac_addr = {0x00, 0x02, 0xb3, 0x01, 0x02, 0x03}, }; static int modparam_reset; module_param_named(reset, modparam_reset, bool, 0644); MODULE_PARM_DESC(reset, "reset on firmware errors (default 0 [not reset])"); int iwm_mode_to_nl80211_iftype(int mode) { switch (mode) { case UMAC_MODE_BSS: return NL80211_IFTYPE_STATION; case UMAC_MODE_IBSS: return NL80211_IFTYPE_ADHOC; default: return NL80211_IFTYPE_UNSPECIFIED; } return 0; } static void iwm_statistics_request(struct work_struct *work) { struct iwm_priv *iwm = container_of(work, struct iwm_priv, stats_request.work); iwm_send_umac_stats_req(iwm, 0); } int __iwm_up(struct iwm_priv *iwm); int __iwm_down(struct iwm_priv *iwm); static void iwm_reset_worker(struct work_struct *work) { struct iwm_priv *iwm; struct iwm_umac_profile *profile = NULL; int uninitialized_var(ret), retry = 0; iwm = container_of(work, struct iwm_priv, reset_worker); /* * XXX: The iwm->mutex is introduced purely for this reset work, * because the other users for iwm_up and iwm_down are only netdev * ndo_open and ndo_stop which are already protected by rtnl. * Please remove iwm->mutex together if iwm_reset_worker() is not * required in the future. */ if (!mutex_trylock(&iwm->mutex)) { IWM_WARN(iwm, "We are in the middle of interface bringing " "UP/DOWN. Skip driver resetting.\n"); return; } if (iwm->umac_profile_active) { profile = kmalloc(sizeof(struct iwm_umac_profile), GFP_KERNEL); if (profile) memcpy(profile, iwm->umac_profile, sizeof(*profile)); else IWM_ERR(iwm, "Couldn't alloc memory for profile\n"); } __iwm_down(iwm); while (retry++ < 3) { ret = __iwm_up(iwm); if (!ret) break; schedule_timeout_uninterruptible(10 * HZ); } if (ret) { IWM_WARN(iwm, "iwm_up() failed: %d\n", ret); kfree(profile); goto out; } if (profile) { IWM_DBG_MLME(iwm, DBG, "Resend UMAC profile\n"); memcpy(iwm->umac_profile, profile, sizeof(*profile)); iwm_send_mlme_profile(iwm); kfree(profile); } out: mutex_unlock(&iwm->mutex); } static void iwm_watchdog(unsigned long data) { struct iwm_priv *iwm = (struct iwm_priv *)data; IWM_WARN(iwm, "Watchdog expired: UMAC stalls!\n"); if (modparam_reset) schedule_work(&iwm->reset_worker); } int iwm_priv_init(struct iwm_priv *iwm) { int i; char name[32]; iwm->status = 0; INIT_LIST_HEAD(&iwm->pending_notif); init_waitqueue_head(&iwm->notif_queue); init_waitqueue_head(&iwm->nonwifi_queue); init_waitqueue_head(&iwm->wifi_ntfy_queue); init_waitqueue_head(&iwm->mlme_queue); memcpy(&iwm->conf, &def_iwm_conf, sizeof(struct iwm_conf)); spin_lock_init(&iwm->tx_credit.lock); INIT_LIST_HEAD(&iwm->wifi_pending_cmd); INIT_LIST_HEAD(&iwm->nonwifi_pending_cmd); iwm->wifi_seq_num = UMAC_WIFI_SEQ_NUM_BASE; iwm->nonwifi_seq_num = UMAC_NONWIFI_SEQ_NUM_BASE; spin_lock_init(&iwm->cmd_lock); iwm->scan_id = 1; INIT_DELAYED_WORK(&iwm->stats_request, iwm_statistics_request); INIT_WORK(&iwm->reset_worker, iwm_reset_worker); INIT_LIST_HEAD(&iwm->bss_list); skb_queue_head_init(&iwm->rx_list); INIT_LIST_HEAD(&iwm->rx_tickets); for (i = 0; i < IWM_RX_ID_HASH; i++) INIT_LIST_HEAD(&iwm->rx_packets[i]); INIT_WORK(&iwm->rx_worker, iwm_rx_worker); iwm->rx_wq = create_singlethread_workqueue(KBUILD_MODNAME "_rx"); if (!iwm->rx_wq) return -EAGAIN; for (i = 0; i < IWM_TX_QUEUES; i++) { INIT_WORK(&iwm->txq[i].worker, iwm_tx_worker); snprintf(name, 32, KBUILD_MODNAME "_tx_%d", i); iwm->txq[i].id = i; iwm->txq[i].wq = create_singlethread_workqueue(name); if (!iwm->txq[i].wq) return -EAGAIN; skb_queue_head_init(&iwm->txq[i].queue); } for (i = 0; i < IWM_NUM_KEYS; i++) memset(&iwm->keys[i], 0, sizeof(struct iwm_key)); iwm->default_key = -1; init_timer(&iwm->watchdog); iwm->watchdog.function = iwm_watchdog; iwm->watchdog.data = (unsigned long)iwm; mutex_init(&iwm->mutex); return 0; } void iwm_priv_deinit(struct iwm_priv *iwm) { int i; for (i = 0; i < IWM_TX_QUEUES; i++) destroy_workqueue(iwm->txq[i].wq); destroy_workqueue(iwm->rx_wq); } /* * We reset all the structures, and we reset the UMAC. * After calling this routine, you're expected to reload * the firmware. */ void iwm_reset(struct iwm_priv *iwm) { struct iwm_notif *notif, *next; if (test_bit(IWM_STATUS_READY, &iwm->status)) iwm_target_reset(iwm); iwm->status = 0; iwm->scan_id = 1; list_for_each_entry_safe(notif, next, &iwm->pending_notif, pending) { list_del(¬if->pending); kfree(notif->buf); kfree(notif); } iwm_cmd_flush(iwm); flush_workqueue(iwm->rx_wq); iwm_link_off(iwm); } /* * Notification code: * * We're faced with the following issue: Any host command can * have an answer or not, and if there's an answer to expect, * it can be treated synchronously or asynchronously. * To work around the synchronous answer case, we implemented * our notification mechanism. * When a code path needs to wait for a command response * synchronously, it calls notif_handle(), which waits for the * right notification to show up, and then process it. Before * starting to wait, it registered as a waiter for this specific * answer (by toggling a bit in on of the handler_map), so that * the rx code knows that it needs to send a notification to the * waiting processes. It does so by calling iwm_notif_send(), * which adds the notification to the pending notifications list, * and then wakes the waiting processes up. */ int iwm_notif_send(struct iwm_priv *iwm, struct iwm_wifi_cmd *cmd, u8 cmd_id, u8 source, u8 *buf, unsigned long buf_size) { struct iwm_notif *notif; notif = kzalloc(sizeof(struct iwm_notif), GFP_KERNEL); if (!notif) { IWM_ERR(iwm, "Couldn't alloc memory for notification\n"); return -ENOMEM; } INIT_LIST_HEAD(¬if->pending); notif->cmd = cmd; notif->cmd_id = cmd_id; notif->src = source; notif->buf = kzalloc(buf_size, GFP_KERNEL); if (!notif->buf) { IWM_ERR(iwm, "Couldn't alloc notification buffer\n"); kfree(notif); return -ENOMEM; } notif->buf_size = buf_size; memcpy(notif->buf, buf, buf_size); list_add_tail(¬if->pending, &iwm->pending_notif); wake_up_interruptible(&iwm->notif_queue); return 0; } static struct iwm_notif *iwm_notif_find(struct iwm_priv *iwm, u32 cmd, u8 source) { struct iwm_notif *notif, *next; list_for_each_entry_safe(notif, next, &iwm->pending_notif, pending) { if ((notif->cmd_id == cmd) && (notif->src == source)) { list_del(¬if->pending); return notif; } } return NULL; } static struct iwm_notif *iwm_notif_wait(struct iwm_priv *iwm, u32 cmd, u8 source, long timeout) { int ret; struct iwm_notif *notif; unsigned long *map = NULL; switch (source) { case IWM_SRC_LMAC: map = &iwm->lmac_handler_map[0]; break; case IWM_SRC_UMAC: map = &iwm->umac_handler_map[0]; break; case IWM_SRC_UDMA: map = &iwm->udma_handler_map[0]; break; } set_bit(cmd, map); ret = wait_event_interruptible_timeout(iwm->notif_queue, ((notif = iwm_notif_find(iwm, cmd, source)) != NULL), timeout); clear_bit(cmd, map); if (!ret) return NULL; return notif; } int iwm_notif_handle(struct iwm_priv *iwm, u32 cmd, u8 source, long timeout) { int ret; struct iwm_notif *notif; notif = iwm_notif_wait(iwm, cmd, source, timeout); if (!notif) return -ETIME; ret = iwm_rx_handle_resp(iwm, notif->buf, notif->buf_size, notif->cmd); kfree(notif->buf); kfree(notif); return ret; } static int iwm_config_boot_params(struct iwm_priv *iwm) { struct iwm_udma_nonwifi_cmd target_cmd; int ret; /* check Wimax is off and config debug monitor */ if (iwm->conf.wimax_not_present) { u32 data1 = 0x1f; u32 addr1 = 0x606BE258; u32 data2_set = 0x0; u32 data2_clr = 0x1; u32 addr2 = 0x606BE100; u32 data3 = 0x1; u32 addr3 = 0x606BEC00; target_cmd.resp = 0; target_cmd.handle_by_hw = 0; target_cmd.eop = 1; target_cmd.opcode = UMAC_HDI_OUT_OPCODE_WRITE; target_cmd.addr = cpu_to_le32(addr1); target_cmd.op1_sz = cpu_to_le32(sizeof(u32)); target_cmd.op2 = 0; ret = iwm_hal_send_target_cmd(iwm, &target_cmd, &data1); if (ret < 0) { IWM_ERR(iwm, "iwm_hal_send_target_cmd failed\n"); return ret; } target_cmd.opcode = UMAC_HDI_OUT_OPCODE_READ_MODIFY_WRITE; target_cmd.addr = cpu_to_le32(addr2); target_cmd.op1_sz = cpu_to_le32(data2_set); target_cmd.op2 = cpu_to_le32(data2_clr); ret = iwm_hal_send_target_cmd(iwm, &target_cmd, &data1); if (ret < 0) { IWM_ERR(iwm, "iwm_hal_send_target_cmd failed\n"); return ret; } target_cmd.opcode = UMAC_HDI_OUT_OPCODE_WRITE; target_cmd.addr = cpu_to_le32(addr3); target_cmd.op1_sz = cpu_to_le32(sizeof(u32)); target_cmd.op2 = 0; ret = iwm_hal_send_target_cmd(iwm, &target_cmd, &data3); if (ret < 0) { IWM_ERR(iwm, "iwm_hal_send_target_cmd failed\n"); return ret; } } return 0; } void iwm_init_default_profile(struct iwm_priv *iwm, struct iwm_umac_profile *profile) { memset(profile, 0, sizeof(struct iwm_umac_profile)); profile->sec.auth_type = UMAC_AUTH_TYPE_OPEN; profile->sec.flags = UMAC_SEC_FLG_LEGACY_PROFILE; profile->sec.ucast_cipher = UMAC_CIPHER_TYPE_NONE; profile->sec.mcast_cipher = UMAC_CIPHER_TYPE_NONE; if (iwm->conf.enable_qos) profile->flags |= cpu_to_le16(UMAC_PROFILE_QOS_ALLOWED); profile->wireless_mode = iwm->conf.wireless_mode; profile->mode = cpu_to_le32(iwm->conf.mode); profile->ibss.atim = 0; profile->ibss.beacon_interval = 100; profile->ibss.join_only = 0; profile->ibss.band = iwm->conf.ibss_band; profile->ibss.channel = iwm->conf.ibss_channel; } void iwm_link_on(struct iwm_priv *iwm) { netif_carrier_on(iwm_to_ndev(iwm)); netif_tx_wake_all_queues(iwm_to_ndev(iwm)); iwm_send_umac_stats_req(iwm, 0); } void iwm_link_off(struct iwm_priv *iwm) { struct iw_statistics *wstats = &iwm->wstats; int i; netif_tx_stop_all_queues(iwm_to_ndev(iwm)); netif_carrier_off(iwm_to_ndev(iwm)); for (i = 0; i < IWM_TX_QUEUES; i++) { skb_queue_purge(&iwm->txq[i].queue); iwm->txq[i].concat_count = 0; iwm->txq[i].concat_ptr = iwm->txq[i].concat_buf; flush_workqueue(iwm->txq[i].wq); } iwm_rx_free(iwm); cancel_delayed_work_sync(&iwm->stats_request); memset(wstats, 0, sizeof(struct iw_statistics)); wstats->qual.updated = IW_QUAL_ALL_INVALID; del_timer_sync(&iwm->watchdog); } static void iwm_bss_list_clean(struct iwm_priv *iwm) { struct iwm_bss_info *bss, *next; list_for_each_entry_safe(bss, next, &iwm->bss_list, node) { list_del(&bss->node); kfree(bss->bss); kfree(bss); } } static int iwm_channels_init(struct iwm_priv *iwm) { int ret; #ifdef CONFIG_IWM_B0_HW_SUPPORT if (iwm->conf.hw_b0) { IWM_INFO(iwm, "Workaround EEPROM channels for B0 hardware\n"); return 0; } #endif ret = iwm_send_umac_channel_list(iwm); if (ret) { IWM_ERR(iwm, "Send channel list failed\n"); return ret; } ret = iwm_notif_handle(iwm, UMAC_CMD_OPCODE_GET_CHAN_INFO_LIST, IWM_SRC_UMAC, WAIT_NOTIF_TIMEOUT); if (ret) { IWM_ERR(iwm, "Didn't get a channel list notification\n"); return ret; } return 0; } int __iwm_up(struct iwm_priv *iwm) { int ret; struct iwm_notif *notif_reboot, *notif_ack = NULL; ret = iwm_bus_enable(iwm); if (ret) { IWM_ERR(iwm, "Couldn't enable function\n"); return ret; } iwm_rx_setup_handlers(iwm); /* Wait for initial BARKER_REBOOT from hardware */ notif_reboot = iwm_notif_wait(iwm, IWM_BARKER_REBOOT_NOTIFICATION, IWM_SRC_UDMA, 2 * HZ); if (!notif_reboot) { IWM_ERR(iwm, "Wait for REBOOT_BARKER timeout\n"); goto err_disable; } /* We send the barker back */ ret = iwm_bus_send_chunk(iwm, notif_reboot->buf, 16); if (ret) { IWM_ERR(iwm, "REBOOT barker response failed\n"); kfree(notif_reboot); goto err_disable; } kfree(notif_reboot->buf); kfree(notif_reboot); /* Wait for ACK_BARKER from hardware */ notif_ack = iwm_notif_wait(iwm, IWM_ACK_BARKER_NOTIFICATION, IWM_SRC_UDMA, 2 * HZ); if (!notif_ack) { IWM_ERR(iwm, "Wait for ACK_BARKER timeout\n"); goto err_disable; } kfree(notif_ack->buf); kfree(notif_ack); /* We start to config static boot parameters */ ret = iwm_config_boot_params(iwm); if (ret) { IWM_ERR(iwm, "Config boot parameters failed\n"); goto err_disable; } ret = iwm_read_mac(iwm, iwm_to_ndev(iwm)->dev_addr); if (ret) { IWM_ERR(iwm, "MAC reading failed\n"); goto err_disable; } /* We can load the FWs */ ret = iwm_load_fw(iwm); if (ret) { IWM_ERR(iwm, "FW loading failed\n"); goto err_disable; } /* We configure the UMAC and enable the wifi module */ ret = iwm_send_umac_config(iwm, cpu_to_le32(UMAC_RST_CTRL_FLG_WIFI_CORE_EN) | cpu_to_le32(UMAC_RST_CTRL_FLG_WIFI_LINK_EN) | cpu_to_le32(UMAC_RST_CTRL_FLG_WIFI_MLME_EN)); if (ret) { IWM_ERR(iwm, "UMAC config failed\n"); goto err_fw; } ret = iwm_notif_handle(iwm, UMAC_NOTIFY_OPCODE_WIFI_CORE_STATUS, IWM_SRC_UMAC, WAIT_NOTIF_TIMEOUT); if (ret) { IWM_ERR(iwm, "Didn't get a wifi core status notification\n"); goto err_fw; } if (iwm->core_enabled != (UMAC_NTFY_WIFI_CORE_STATUS_LINK_EN | UMAC_NTFY_WIFI_CORE_STATUS_MLME_EN)) { IWM_DBG_BOOT(iwm, DBG, "Not all cores enabled:0x%x\n", iwm->core_enabled); ret = iwm_notif_handle(iwm, UMAC_NOTIFY_OPCODE_WIFI_CORE_STATUS, IWM_SRC_UMAC, WAIT_NOTIF_TIMEOUT); if (ret) { IWM_ERR(iwm, "Didn't get a core status notification\n"); goto err_fw; } if (iwm->core_enabled != (UMAC_NTFY_WIFI_CORE_STATUS_LINK_EN | UMAC_NTFY_WIFI_CORE_STATUS_MLME_EN)) { IWM_ERR(iwm, "Not all cores enabled: 0x%x\n", iwm->core_enabled); goto err_fw; } else { IWM_INFO(iwm, "All cores enabled\n"); } } ret = iwm_channels_init(iwm); if (ret < 0) { IWM_ERR(iwm, "Couldn't init channels\n"); goto err_fw; } /* Set the READY bit to indicate interface is brought up successfully */ set_bit(IWM_STATUS_READY, &iwm->status); return 0; err_fw: iwm_eeprom_exit(iwm); err_disable: ret = iwm_bus_disable(iwm); if (ret < 0) IWM_ERR(iwm, "Couldn't disable function\n"); return -EIO; } int iwm_up(struct iwm_priv *iwm) { int ret; mutex_lock(&iwm->mutex); ret = __iwm_up(iwm); mutex_unlock(&iwm->mutex); return ret; } int __iwm_down(struct iwm_priv *iwm) { int ret; /* The interface is already down */ if (!test_bit(IWM_STATUS_READY, &iwm->status)) return 0; if (iwm->scan_request) { cfg80211_scan_done(iwm->scan_request, true); iwm->scan_request = NULL; } clear_bit(IWM_STATUS_READY, &iwm->status); iwm_eeprom_exit(iwm); iwm_bss_list_clean(iwm); iwm_init_default_profile(iwm, iwm->umac_profile); iwm->umac_profile_active = false; iwm->default_key = -1; iwm->core_enabled = 0; ret = iwm_bus_disable(iwm); if (ret < 0) { IWM_ERR(iwm, "Couldn't disable function\n"); return ret; } return 0; } int iwm_down(struct iwm_priv *iwm) { int ret; mutex_lock(&iwm->mutex); ret = __iwm_down(iwm); mutex_unlock(&iwm->mutex); return ret; }