| /* |
| * 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 <ilw@linux.intel.com> |
| * Samuel Ortiz <samuel.ortiz@intel.com> |
| * Zhu Yi <yi.zhu@intel.com> |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/netdevice.h> |
| #include <linux/sched.h> |
| #include <linux/ieee80211.h> |
| #include <linux/wireless.h> |
| #include <linux/slab.h> |
| #include <linux/moduleparam.h> |
| |
| #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(CALIB_CFG_DC_IDX) | |
| BIT(CALIB_CFG_LO_IDX) | |
| BIT(CALIB_CFG_TX_IQ_IDX) | |
| BIT(CALIB_CFG_RX_IQ_IDX) | |
| BIT(SHILOH_PHY_CALIBRATE_BASE_BAND_CMD), |
| .expected_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), |
| .ct_kill_entry = 110, |
| .ct_kill_exit = 110, |
| .reset_on_fatal_err = 1, |
| .auto_connect = 1, |
| .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 | |
| WIRELESS_MODE_11N, |
| |
| /* IBSS */ |
| .ibss_band = UMAC_BAND_2GHZ, |
| .ibss_channel = 1, |
| |
| .mac_addr = {0x00, 0x02, 0xb3, 0x01, 0x02, 0x03}, |
| }; |
| |
| static bool modparam_reset; |
| module_param_named(reset, modparam_reset, bool, 0644); |
| MODULE_PARM_DESC(reset, "reset on firmware errors (default 0 [not reset])"); |
| |
| static bool modparam_wimax_enable = true; |
| module_param_named(wimax_enable, modparam_wimax_enable, bool, 0644); |
| MODULE_PARM_DESC(wimax_enable, "Enable wimax core (default 1 [wimax enabled])"); |
| |
| 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); |
| } |
| |
| static void iwm_disconnect_work(struct work_struct *work) |
| { |
| struct iwm_priv *iwm = |
| container_of(work, struct iwm_priv, disconnect.work); |
| |
| if (iwm->umac_profile_active) |
| iwm_invalidate_mlme_profile(iwm); |
| |
| clear_bit(IWM_STATUS_ASSOCIATED, &iwm->status); |
| iwm->umac_profile_active = false; |
| memset(iwm->bssid, 0, ETH_ALEN); |
| iwm->channel = 0; |
| |
| iwm_link_off(iwm); |
| |
| wake_up_interruptible(&iwm->mlme_queue); |
| |
| cfg80211_disconnected(iwm_to_ndev(iwm), 0, NULL, 0, GFP_KERNEL); |
| } |
| |
| static void iwm_ct_kill_work(struct work_struct *work) |
| { |
| struct iwm_priv *iwm = |
| container_of(work, struct iwm_priv, ct_kill_delay.work); |
| struct wiphy *wiphy = iwm_to_wiphy(iwm); |
| |
| IWM_INFO(iwm, "CT kill delay timeout\n"); |
| |
| wiphy_rfkill_set_hw_state(wiphy, false); |
| } |
| |
| static int __iwm_up(struct iwm_priv *iwm); |
| static 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); |
| } else |
| clear_bit(IWM_STATUS_RESETTING, &iwm->status); |
| |
| out: |
| mutex_unlock(&iwm->mutex); |
| } |
| |
| static void iwm_auth_retry_worker(struct work_struct *work) |
| { |
| struct iwm_priv *iwm; |
| int i, ret; |
| |
| iwm = container_of(work, struct iwm_priv, auth_retry_worker); |
| if (iwm->umac_profile_active) { |
| ret = iwm_invalidate_mlme_profile(iwm); |
| if (ret < 0) |
| return; |
| } |
| |
| iwm->umac_profile->sec.auth_type = UMAC_AUTH_TYPE_LEGACY_PSK; |
| |
| ret = iwm_send_mlme_profile(iwm); |
| if (ret < 0) |
| return; |
| |
| for (i = 0; i < IWM_NUM_KEYS; i++) |
| if (iwm->keys[i].key_len) |
| iwm_set_key(iwm, 0, &iwm->keys[i]); |
| |
| iwm_set_tx_key(iwm, iwm->default_key); |
| } |
| |
| |
| |
| 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) |
| iwm_resetting(iwm); |
| } |
| |
| int iwm_priv_init(struct iwm_priv *iwm) |
| { |
| int i, j; |
| 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_DELAYED_WORK(&iwm->disconnect, iwm_disconnect_work); |
| INIT_DELAYED_WORK(&iwm->ct_kill_delay, iwm_ct_kill_work); |
| INIT_WORK(&iwm->reset_worker, iwm_reset_worker); |
| INIT_WORK(&iwm->auth_retry_worker, iwm_auth_retry_worker); |
| INIT_LIST_HEAD(&iwm->bss_list); |
| |
| skb_queue_head_init(&iwm->rx_list); |
| INIT_LIST_HEAD(&iwm->rx_tickets); |
| spin_lock_init(&iwm->ticket_lock); |
| for (i = 0; i < IWM_RX_ID_HASH; i++) { |
| INIT_LIST_HEAD(&iwm->rx_packets[i]); |
| spin_lock_init(&iwm->packet_lock[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); |
| skb_queue_head_init(&iwm->txq[i].stopped_queue); |
| spin_lock_init(&iwm->txq[i].lock); |
| } |
| |
| for (i = 0; i < IWM_NUM_KEYS; i++) |
| memset(&iwm->keys[i], 0, sizeof(struct iwm_key)); |
| |
| iwm->default_key = -1; |
| |
| for (i = 0; i < IWM_STA_TABLE_NUM; i++) |
| for (j = 0; j < IWM_UMAC_TID_NR; j++) { |
| mutex_init(&iwm->sta_table[i].tid_info[j].mutex); |
| iwm->sta_table[i].tid_info[j].stopped = false; |
| } |
| |
| init_timer(&iwm->watchdog); |
| iwm->watchdog.function = iwm_watchdog; |
| iwm->watchdog.data = (unsigned long)iwm; |
| mutex_init(&iwm->mutex); |
| |
| iwm->last_fw_err = kzalloc(sizeof(struct iwm_fw_error_hdr), |
| GFP_KERNEL); |
| if (iwm->last_fw_err == NULL) |
| return -ENOMEM; |
| |
| 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); |
| kfree(iwm->last_fw_err); |
| } |
| |
| /* |
| * 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); |
| |
| if (test_bit(IWM_STATUS_RESETTING, &iwm->status)) { |
| iwm->status = 0; |
| set_bit(IWM_STATUS_RESETTING, &iwm->status); |
| } else |
| 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); |
| } |
| |
| void iwm_resetting(struct iwm_priv *iwm) |
| { |
| set_bit(IWM_STATUS_RESETTING, &iwm->status); |
| |
| schedule_work(&iwm->reset_worker); |
| } |
| |
| /* |
| * 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; |
| |
| list_for_each_entry(notif, &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 (!modparam_wimax_enable) { |
| 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); |
| skb_queue_purge(&iwm->txq[i].stopped_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; |
| |
| kfree(iwm->req_ie); |
| iwm->req_ie = NULL; |
| iwm->req_ie_len = 0; |
| kfree(iwm->resp_ie); |
| iwm->resp_ie = NULL; |
| iwm->resp_ie_len = 0; |
| |
| 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; |
| |
| 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; |
| } |
| |
| static int __iwm_up(struct iwm_priv *iwm) |
| { |
| int ret; |
| struct iwm_notif *notif_reboot, *notif_ack = NULL; |
| struct wiphy *wiphy = iwm_to_wiphy(iwm); |
| u32 wireless_mode; |
| |
| 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; |
| } |
| memcpy(iwm_to_ndev(iwm)->perm_addr, iwm_to_ndev(iwm)->dev_addr, |
| ETH_ALEN); |
| |
| /* We can load the FWs */ |
| ret = iwm_load_fw(iwm); |
| if (ret) { |
| IWM_ERR(iwm, "FW loading failed\n"); |
| goto err_disable; |
| } |
| |
| ret = iwm_eeprom_fat_channels(iwm); |
| if (ret) { |
| IWM_ERR(iwm, "Couldnt read HT channels EEPROM entries\n"); |
| goto err_fw; |
| } |
| |
| /* |
| * Read our SKU capabilities. |
| * If it's valid, we AND the configured wireless mode with the |
| * device EEPROM value as the current profile wireless mode. |
| */ |
| wireless_mode = iwm_eeprom_wireless_mode(iwm); |
| if (wireless_mode) { |
| iwm->conf.wireless_mode &= wireless_mode; |
| if (iwm->umac_profile) |
| iwm->umac_profile->wireless_mode = |
| iwm->conf.wireless_mode; |
| } else |
| IWM_ERR(iwm, "Wrong SKU capabilities: 0x%x\n", |
| *((u16 *)iwm_eeprom_access(iwm, IWM_EEPROM_SKU_CAP))); |
| |
| snprintf(wiphy->fw_version, sizeof(wiphy->fw_version), "L%s_U%s", |
| iwm->lmac_version, iwm->umac_version); |
| |
| /* 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; |
| } |
| |
| static 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; |
| } |