diff options
Diffstat (limited to 'drivers/net/wireless/ath/wil6210')
24 files changed, 2324 insertions, 350 deletions
diff --git a/drivers/net/wireless/ath/wil6210/Kconfig b/drivers/net/wireless/ath/wil6210/Kconfig index ce8c0381825e..481680a3aa55 100644 --- a/drivers/net/wireless/ath/wil6210/Kconfig +++ b/drivers/net/wireless/ath/wil6210/Kconfig @@ -39,3 +39,12 @@ config WIL6210_TRACING option if you are interested in debugging the driver. If unsure, say Y to make it easier to debug problems. + +config WIL6210_PLATFORM_MSM + bool "wil6210 MSM platform specific support" + depends on WIL6210 + depends on ARCH_MSM + default y + ---help--- + Say Y here to enable wil6210 driver support for MSM + platform specific features diff --git a/drivers/net/wireless/ath/wil6210/Makefile b/drivers/net/wireless/ath/wil6210/Makefile index c7a3465fd02a..8ad4b5f97e04 100644 --- a/drivers/net/wireless/ath/wil6210/Makefile +++ b/drivers/net/wireless/ath/wil6210/Makefile @@ -10,7 +10,12 @@ wil6210-y += interrupt.o wil6210-y += txrx.o wil6210-y += debug.o wil6210-y += rx_reorder.o +wil6210-y += ioctl.o +wil6210-y += fw.o wil6210-$(CONFIG_WIL6210_TRACING) += trace.o +wil6210-y += wil_platform.o +wil6210-$(CONFIG_WIL6210_PLATFORM_MSM) += wil_platform_msm.o +wil6210-y += ethtool.o # for tracing framework to find trace.h CFLAGS_trace.o := -I$(src) diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index 4ac2c208c9ba..d9f4b30dd343 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -296,6 +296,7 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, n = min(request->n_channels, 4U); for (i = 0; i < n; i++) { int ch = request->channels[i]->hw_value; + if (ch == 0) { wil_err(wil, "Scan requested for unknown frequency %dMhz\n", @@ -308,15 +309,47 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, request->channels[i]->center_freq); } + if (request->ie_len) + print_hex_dump_bytes("Scan IE ", DUMP_PREFIX_OFFSET, + request->ie, request->ie_len); + else + wil_dbg_misc(wil, "Scan has no IE's\n"); + + rc = wmi_set_ie(wil, WMI_FRAME_PROBE_REQ, request->ie_len, + request->ie); + if (rc) { + wil_err(wil, "Aborting scan, set_ie failed: %d\n", rc); + goto out; + } + rc = wmi_send(wil, WMI_START_SCAN_CMDID, &cmd, sizeof(cmd.cmd) + cmd.cmd.num_channels * sizeof(cmd.cmd.channel_list[0])); - if (rc) +out: + if (rc) { + del_timer_sync(&wil->scan_timer); wil->scan_request = NULL; + } return rc; } +static void wil_print_connect_params(struct wil6210_priv *wil, + struct cfg80211_connect_params *sme) +{ + wil_info(wil, "Connecting to:\n"); + if (sme->channel) { + wil_info(wil, " Channel: %d freq %d\n", + sme->channel->hw_value, sme->channel->center_freq); + } + if (sme->bssid) + wil_info(wil, " BSSID: %pM\n", sme->bssid); + if (sme->ssid) + print_hex_dump(KERN_INFO, " SSID: ", DUMP_PREFIX_OFFSET, + 16, 1, sme->ssid, sme->ssid_len, true); + wil_info(wil, " Privacy: %s\n", sme->privacy ? "secure" : "open"); +} + static int wil_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_connect_params *sme) @@ -333,6 +366,8 @@ static int wil_cfg80211_connect(struct wiphy *wiphy, test_bit(wil_status_fwconnected, &wil->status)) return -EALREADY; + wil_print_connect_params(wil, sme); + bss = cfg80211_get_bss(wiphy, sme->channel, sme->bssid, sme->ssid, sme->ssid_len, WLAN_CAPABILITY_ESS, WLAN_CAPABILITY_ESS); @@ -358,22 +393,22 @@ static int wil_cfg80211_connect(struct wiphy *wiphy, sme->ie_len); goto out; } - /* - * For secure assoc, send: - * (1) WMI_DELETE_CIPHER_KEY_CMD - * (2) WMI_SET_APPIE_CMD - */ + /* For secure assoc, send WMI_DELETE_CIPHER_KEY_CMD */ rc = wmi_del_cipher_key(wil, 0, bss->bssid); if (rc) { wil_err(wil, "WMI_DELETE_CIPHER_KEY_CMD failed\n"); goto out; } - /* WMI_SET_APPIE_CMD */ - rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_REQ, sme->ie_len, sme->ie); - if (rc) { - wil_err(wil, "WMI_SET_APPIE_CMD failed\n"); - goto out; - } + } + + /* WMI_SET_APPIE_CMD. ie may contain rsn info as well as other info + * elements. Send it also in case it's empty, to erase previously set + * ies in FW. + */ + rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_REQ, sme->ie_len, sme->ie); + if (rc) { + wil_err(wil, "WMI_SET_APPIE_CMD failed\n"); + goto out; } /* WMI_CONNECT_CMD */ @@ -619,6 +654,45 @@ static int wil_fix_bcon(struct wil6210_priv *wil, return rc; } +static int wil_cfg80211_change_beacon(struct wiphy *wiphy, + struct net_device *ndev, + struct cfg80211_beacon_data *bcon) +{ + struct wil6210_priv *wil = wiphy_to_wil(wiphy); + int rc; + + wil_dbg_misc(wil, "%s()\n", __func__); + + if (wil_fix_bcon(wil, bcon)) { + wil_dbg_misc(wil, "Fixed bcon\n"); + wil_print_bcon_data(bcon); + } + + /* FW do not form regular beacon, so bcon IE's are not set + * For the DMG bcon, when it will be supported, bcon IE's will + * be reused; add something like: + * wmi_set_ie(wil, WMI_FRAME_BEACON, bcon->beacon_ies_len, + * bcon->beacon_ies); + */ + rc = wmi_set_ie(wil, WMI_FRAME_PROBE_RESP, + bcon->proberesp_ies_len, + bcon->proberesp_ies); + if (rc) { + wil_err(wil, "set_ie(PROBE_RESP) failed\n"); + return rc; + } + + rc = wmi_set_ie(wil, WMI_FRAME_ASSOC_RESP, + bcon->assocresp_ies_len, + bcon->assocresp_ies); + if (rc) { + wil_err(wil, "set_ie(ASSOC_RESP) failed\n"); + return rc; + } + + return 0; +} + static int wil_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev, struct cfg80211_ap_settings *info) @@ -654,14 +728,12 @@ static int wil_cfg80211_start_ap(struct wiphy *wiphy, wil_print_bcon_data(bcon); } - mutex_lock(&wil->mutex); + wil_set_recovery_state(wil, fw_recovery_idle); - rc = wil_reset(wil); - if (rc) - goto out; + mutex_lock(&wil->mutex); - /* Rx VRING. */ - rc = wil_rx_init(wil); + __wil_down(wil); + rc = __wil_up(wil); if (rc) goto out; @@ -669,9 +741,6 @@ static int wil_cfg80211_start_ap(struct wiphy *wiphy, if (rc) goto out; - /* MAC address - pre-requisite for other commands */ - wmi_set_mac_address(wil, ndev->dev_addr); - /* IE's */ /* bcon 'head IE's are not relevant for 60g band */ /* @@ -693,7 +762,6 @@ static int wil_cfg80211_start_ap(struct wiphy *wiphy, if (rc) goto out; - netif_carrier_on(ndev); out: @@ -704,17 +772,23 @@ out: static int wil_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *ndev) { - int rc = 0; + int rc, rc1; struct wil6210_priv *wil = wiphy_to_wil(wiphy); wil_dbg_misc(wil, "%s()\n", __func__); + wil_set_recovery_state(wil, fw_recovery_idle); + mutex_lock(&wil->mutex); rc = wmi_pcp_stop(wil); + __wil_down(wil); + rc1 = __wil_up(wil); + mutex_unlock(&wil->mutex); - return rc; + + return min(rc, rc1); } static int wil_cfg80211_del_station(struct wiphy *wiphy, @@ -744,6 +818,7 @@ static struct cfg80211_ops wil_cfg80211_ops = { .del_key = wil_cfg80211_del_key, .set_default_key = wil_cfg80211_set_default_key, /* AP mode */ + .change_beacon = wil_cfg80211_change_beacon, .start_ap = wil_cfg80211_start_ap, .stop_ap = wil_cfg80211_stop_ap, .del_station = wil_cfg80211_del_station, @@ -753,6 +828,7 @@ static void wil_wiphy_init(struct wiphy *wiphy) { /* TODO: set real value */ wiphy->max_scan_ssids = 10; + wiphy->max_scan_ie_len = WMI_MAX_IE_LEN; wiphy->max_num_pmkids = 0 /* TODO: */; wiphy->interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP) | @@ -762,8 +838,8 @@ static void wil_wiphy_init(struct wiphy *wiphy) */ wiphy->flags |= WIPHY_FLAG_HAVE_AP_SME | WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD; - dev_warn(wiphy_dev(wiphy), "%s : flags = 0x%08x\n", - __func__, wiphy->flags); + dev_dbg(wiphy_dev(wiphy), "%s : flags = 0x%08x\n", + __func__, wiphy->flags); wiphy->probe_resp_offload = NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS | NL80211_PROBE_RESP_OFFLOAD_SUPPORT_WPS2 | @@ -784,7 +860,9 @@ struct wireless_dev *wil_cfg80211_init(struct device *dev) int rc = 0; struct wireless_dev *wdev; - wdev = kzalloc(sizeof(struct wireless_dev), GFP_KERNEL); + dev_dbg(dev, "%s()\n", __func__); + + wdev = kzalloc(sizeof(*wdev), GFP_KERNEL); if (!wdev) return ERR_PTR(-ENOMEM); @@ -816,6 +894,8 @@ void wil_wdev_free(struct wil6210_priv *wil) { struct wireless_dev *wdev = wil_to_wdev(wil); + dev_dbg(wil_to_dev(wil), "%s()\n", __func__); + if (!wdev) return; diff --git a/drivers/net/wireless/ath/wil6210/debug.c b/drivers/net/wireless/ath/wil6210/debug.c index 9eeabf4a5879..8d99021d27a8 100644 --- a/drivers/net/wireless/ath/wil6210/debug.c +++ b/drivers/net/wireless/ath/wil6210/debug.c @@ -17,43 +17,37 @@ #include "wil6210.h" #include "trace.h" -int wil_err(struct wil6210_priv *wil, const char *fmt, ...) +void wil_err(struct wil6210_priv *wil, const char *fmt, ...) { struct net_device *ndev = wil_to_ndev(wil); struct va_format vaf = { .fmt = fmt, }; va_list args; - int ret; va_start(args, fmt); vaf.va = &args; - ret = netdev_err(ndev, "%pV", &vaf); + netdev_err(ndev, "%pV", &vaf); trace_wil6210_log_err(&vaf); va_end(args); - - return ret; } -int wil_info(struct wil6210_priv *wil, const char *fmt, ...) +void wil_info(struct wil6210_priv *wil, const char *fmt, ...) { struct net_device *ndev = wil_to_ndev(wil); struct va_format vaf = { .fmt = fmt, }; va_list args; - int ret; va_start(args, fmt); vaf.va = &args; - ret = netdev_info(ndev, "%pV", &vaf); + netdev_info(ndev, "%pV", &vaf); trace_wil6210_log_info(&vaf); va_end(args); - - return ret; } -int wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...) +void wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...) { struct va_format vaf = { .fmt = fmt, @@ -64,6 +58,4 @@ int wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...) vaf.va = &args; trace_wil6210_log_dbg(&vaf); va_end(args); - - return 0; } diff --git a/drivers/net/wireless/ath/wil6210/debugfs.c b/drivers/net/wireless/ath/wil6210/debugfs.c index 8f66186adb8c..54a6ddc6301b 100644 --- a/drivers/net/wireless/ath/wil6210/debugfs.c +++ b/drivers/net/wireless/ath/wil6210/debugfs.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,6 +22,7 @@ #include <linux/power_supply.h> #include "wil6210.h" +#include "wmi.h" #include "txrx.h" /* Nasty hack. Better have per device instances */ @@ -29,6 +30,21 @@ static u32 mem_addr; static u32 dbg_txdesc_index; static u32 dbg_vring_index; /* 24+ for Rx, 0..23 for Tx */ +enum dbg_off_type { + doff_u32 = 0, + doff_x32 = 1, + doff_ulong = 2, + doff_io32 = 3, +}; + +/* offset to "wil" */ +struct dbg_off { + const char *name; + umode_t mode; + ulong off; + enum dbg_off_type type; +}; + static void wil_print_vring(struct seq_file *s, struct wil6210_priv *wil, const char *name, struct vring *vring, char _s, char _h) @@ -45,20 +61,22 @@ static void wil_print_vring(struct seq_file *s, struct wil6210_priv *wil, if (x) seq_printf(s, "0x%08x\n", ioread32(x)); else - seq_printf(s, "???\n"); + seq_puts(s, "???\n"); if (vring->va && (vring->size < 1025)) { uint i; + for (i = 0; i < vring->size; i++) { volatile struct vring_tx_desc *d = &vring->va[i].tx; + if ((i % 64) == 0 && (i != 0)) - seq_printf(s, "\n"); + seq_puts(s, "\n"); seq_printf(s, "%c", (d->dma.status & BIT(0)) ? _s : (vring->ctx[i].skb ? _h : 'h')); } - seq_printf(s, "\n"); + seq_puts(s, "\n"); } - seq_printf(s, "}\n"); + seq_puts(s, "}\n"); } static int wil_vring_debugfs_show(struct seq_file *s, void *data) @@ -69,7 +87,7 @@ static int wil_vring_debugfs_show(struct seq_file *s, void *data) wil_print_vring(s, wil, "rx", &wil->vring_rx, 'S', '_'); for (i = 0; i < ARRAY_SIZE(wil->vring_tx); i++) { - struct vring *vring = &(wil->vring_tx[i]); + struct vring *vring = &wil->vring_tx[i]; struct vring_tx_data *txdata = &wil->vring_tx_data[i]; if (vring->va) { @@ -147,7 +165,7 @@ static void wil_print_ring(struct seq_file *s, const char *prefix, if (!wmi_addr(wil, r.base) || !wmi_addr(wil, r.tail) || !wmi_addr(wil, r.head)) { - seq_printf(s, " ??? pointers are garbage?\n"); + seq_puts(s, " ??? pointers are garbage?\n"); goto out; } @@ -166,6 +184,7 @@ static void wil_print_ring(struct seq_file *s, const char *prefix, le32_to_cpu(d.addr)); if (0 == wmi_read_hdr(wil, d.addr, &hdr)) { u16 len = le16_to_cpu(hdr.len); + seq_printf(s, " -> %04x %04x %04x %02x\n", le16_to_cpu(hdr.seq), len, le16_to_cpu(hdr.type), hdr.flags); @@ -183,6 +202,7 @@ static void wil_print_ring(struct seq_file *s, const char *prefix, wil_memcpy_fromio_32(databuf, src, len); while (n < len) { int l = min(len - n, 16); + hex_dump_to_buffer(databuf + n, l, 16, 1, printbuf, sizeof(printbuf), @@ -192,11 +212,11 @@ static void wil_print_ring(struct seq_file *s, const char *prefix, } } } else { - seq_printf(s, "\n"); + seq_puts(s, "\n"); } } out: - seq_printf(s, "}\n"); + seq_puts(s, "}\n"); } static int wil_mbox_debugfs_show(struct seq_file *s, void *data) @@ -244,9 +264,9 @@ DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, wil_debugfs_iomem_x32_get, static struct dentry *wil_debugfs_create_iomem_x32(const char *name, umode_t mode, struct dentry *parent, - void __iomem *value) + void *value) { - return debugfs_create_file(name, mode, parent, (void * __force)value, + return debugfs_create_file(name, mode, parent, value, &fops_iomem_x32); } @@ -255,11 +275,13 @@ static int wil_debugfs_ulong_set(void *data, u64 val) *(ulong *)data = val; return 0; } + static int wil_debugfs_ulong_get(void *data, u64 *val) { *val = *(ulong *)data; return 0; } + DEFINE_SIMPLE_ATTRIBUTE(wil_fops_ulong, wil_debugfs_ulong_get, wil_debugfs_ulong_set, "%llu\n"); @@ -270,6 +292,62 @@ static struct dentry *wil_debugfs_create_ulong(const char *name, umode_t mode, return debugfs_create_file(name, mode, parent, value, &wil_fops_ulong); } +/** + * wil6210_debugfs_init_offset - create set of debugfs files + * @wil - driver's context, used for printing + * @dbg - directory on the debugfs, where files will be created + * @base - base address used in address calculation + * @tbl - table with file descriptions. Should be terminated with empty element. + * + * Creates files accordingly to the @tbl. + */ +static void wil6210_debugfs_init_offset(struct wil6210_priv *wil, + struct dentry *dbg, void *base, + const struct dbg_off * const tbl) +{ + int i; + + for (i = 0; tbl[i].name; i++) { + struct dentry *f; + + switch (tbl[i].type) { + case doff_u32: + f = debugfs_create_u32(tbl[i].name, tbl[i].mode, dbg, + base + tbl[i].off); + break; + case doff_x32: + f = debugfs_create_x32(tbl[i].name, tbl[i].mode, dbg, + base + tbl[i].off); + break; + case doff_ulong: + f = wil_debugfs_create_ulong(tbl[i].name, tbl[i].mode, + dbg, base + tbl[i].off); + break; + case doff_io32: + f = wil_debugfs_create_iomem_x32(tbl[i].name, + tbl[i].mode, dbg, + base + tbl[i].off); + break; + default: + f = ERR_PTR(-EINVAL); + } + if (IS_ERR_OR_NULL(f)) + wil_err(wil, "Create file \"%s\": err %ld\n", + tbl[i].name, PTR_ERR(f)); + } +} + +static const struct dbg_off isr_off[] = { + {"ICC", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, ICC), doff_io32}, + {"ICR", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, ICR), doff_io32}, + {"ICM", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, ICM), doff_io32}, + {"ICS", S_IWUSR, offsetof(struct RGF_ICR, ICS), doff_io32}, + {"IMV", S_IRUGO | S_IWUSR, offsetof(struct RGF_ICR, IMV), doff_io32}, + {"IMS", S_IWUSR, offsetof(struct RGF_ICR, IMS), doff_io32}, + {"IMC", S_IWUSR, offsetof(struct RGF_ICR, IMC), doff_io32}, + {}, +}; + static int wil6210_debugfs_create_ISR(struct wil6210_priv *wil, const char *name, struct dentry *parent, u32 off) @@ -279,24 +357,19 @@ static int wil6210_debugfs_create_ISR(struct wil6210_priv *wil, if (IS_ERR_OR_NULL(d)) return -ENODEV; - wil_debugfs_create_iomem_x32("ICC", S_IRUGO | S_IWUSR, d, - wil->csr + off); - wil_debugfs_create_iomem_x32("ICR", S_IRUGO | S_IWUSR, d, - wil->csr + off + 4); - wil_debugfs_create_iomem_x32("ICM", S_IRUGO | S_IWUSR, d, - wil->csr + off + 8); - wil_debugfs_create_iomem_x32("ICS", S_IWUSR, d, - wil->csr + off + 12); - wil_debugfs_create_iomem_x32("IMV", S_IRUGO | S_IWUSR, d, - wil->csr + off + 16); - wil_debugfs_create_iomem_x32("IMS", S_IWUSR, d, - wil->csr + off + 20); - wil_debugfs_create_iomem_x32("IMC", S_IWUSR, d, - wil->csr + off + 24); + wil6210_debugfs_init_offset(wil, d, (void * __force)wil->csr + off, + isr_off); return 0; } +static const struct dbg_off pseudo_isr_off[] = { + {"CAUSE", S_IRUGO, HOSTADDR(RGF_DMA_PSEUDO_CAUSE), doff_io32}, + {"MASK_SW", S_IRUGO, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW), doff_io32}, + {"MASK_FW", S_IRUGO, HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_FW), doff_io32}, + {}, +}; + static int wil6210_debugfs_create_pseudo_ISR(struct wil6210_priv *wil, struct dentry *parent) { @@ -305,16 +378,19 @@ static int wil6210_debugfs_create_pseudo_ISR(struct wil6210_priv *wil, if (IS_ERR_OR_NULL(d)) return -ENODEV; - wil_debugfs_create_iomem_x32("CAUSE", S_IRUGO, d, wil->csr + - HOSTADDR(RGF_DMA_PSEUDO_CAUSE)); - wil_debugfs_create_iomem_x32("MASK_SW", S_IRUGO, d, wil->csr + - HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW)); - wil_debugfs_create_iomem_x32("MASK_FW", S_IRUGO, d, wil->csr + - HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_FW)); + wil6210_debugfs_init_offset(wil, d, (void * __force)wil->csr, + pseudo_isr_off); return 0; } +static const struct dbg_off itr_cnt_off[] = { + {"TRSH", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_CNT_TRSH), doff_io32}, + {"DATA", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_CNT_DATA), doff_io32}, + {"CTL", S_IRUGO | S_IWUSR, HOSTADDR(RGF_DMA_ITR_CNT_CRL), doff_io32}, + {}, +}; + static int wil6210_debugfs_create_ITR_CNT(struct wil6210_priv *wil, struct dentry *parent) { @@ -323,12 +399,8 @@ static int wil6210_debugfs_create_ITR_CNT(struct wil6210_priv *wil, if (IS_ERR_OR_NULL(d)) return -ENODEV; - wil_debugfs_create_iomem_x32("TRSH", S_IRUGO | S_IWUSR, d, wil->csr + - HOSTADDR(RGF_DMA_ITR_CNT_TRSH)); - wil_debugfs_create_iomem_x32("DATA", S_IRUGO | S_IWUSR, d, wil->csr + - HOSTADDR(RGF_DMA_ITR_CNT_DATA)); - wil_debugfs_create_iomem_x32("CTL", S_IRUGO | S_IWUSR, d, wil->csr + - HOSTADDR(RGF_DMA_ITR_CNT_CRL)); + wil6210_debugfs_init_offset(wil, d, (void * __force)wil->csr, + itr_cnt_off); return 0; } @@ -359,7 +431,7 @@ static const struct file_operations fops_memread = { }; static ssize_t wil_read_file_ioblob(struct file *file, char __user *user_buf, - size_t count, loff_t *ppos) + size_t count, loff_t *ppos) { enum { max_count = 4096 }; struct debugfs_blob_wrapper *blob = file->private_data; @@ -411,6 +483,7 @@ struct dentry *wil_debugfs_create_ioblob(const char *name, { return debugfs_create_file(name, mode, parent, blob, &fops_ioblob); } + /*---reset---*/ static ssize_t wil_write_file_reset(struct file *file, const char __user *buf, size_t len, loff_t *ppos) @@ -436,6 +509,7 @@ static const struct file_operations fops_reset = { .write = wil_write_file_reset, .open = simple_open, }; + /*---write channel 1..4 to rxon for it, 0 to rxoff---*/ static ssize_t wil_write_file_rxon(struct file *file, const char __user *buf, size_t len, loff_t *ppos) @@ -446,6 +520,7 @@ static ssize_t wil_write_file_rxon(struct file *file, const char __user *buf, bool on; char *kbuf = kmalloc(len + 1, GFP_KERNEL); + if (!kbuf) return -ENOMEM; if (copy_from_user(kbuf, buf, len)) { @@ -482,6 +557,7 @@ static const struct file_operations fops_rxon = { .write = wil_write_file_rxon, .open = simple_open, }; + /*---tx_mgmt---*/ /* Write mgmt frame to this file to send it */ static ssize_t wil_write_file_txmgmt(struct file *file, const char __user *buf, @@ -492,8 +568,8 @@ static ssize_t wil_write_file_txmgmt(struct file *file, const char __user *buf, struct wireless_dev *wdev = wil_to_wdev(wil); struct cfg80211_mgmt_tx_params params; int rc; - void *frame = kmalloc(len, GFP_KERNEL); + if (!frame) return -ENOMEM; @@ -562,8 +638,10 @@ static void wil_seq_hexdump(struct seq_file *s, void *p, int len, { char printbuf[16 * 3 + 2]; int i = 0; + while (i < len) { int l = min(len - i, 16); + hex_dump_to_buffer(p + i, l, 16, 1, printbuf, sizeof(printbuf), false); seq_printf(s, "%s%s\n", prefix, printbuf); @@ -601,10 +679,8 @@ static int wil_txdesc_debugfs_show(struct seq_file *s, void *data) struct wil6210_priv *wil = s->private; struct vring *vring; bool tx = (dbg_vring_index < WIL6210_MAX_TX_RINGS); - if (tx) - vring = &(wil->vring_tx[dbg_vring_index]); - else - vring = &wil->vring_rx; + + vring = tx ? &wil->vring_tx[dbg_vring_index] : &wil->vring_rx; if (!vring->va) { if (tx) @@ -619,7 +695,7 @@ static int wil_txdesc_debugfs_show(struct seq_file *s, void *data) * only field used, .dma.length, is the same */ volatile struct vring_tx_desc *d = - &(vring->va[dbg_txdesc_index].tx); + &vring->va[dbg_txdesc_index].tx; volatile u32 *u = (volatile u32 *)d; struct sk_buff *skb = vring->ctx[dbg_txdesc_index].skb; @@ -639,7 +715,7 @@ static int wil_txdesc_debugfs_show(struct seq_file *s, void *data) wil_seq_print_skb(s, skb); kfree_skb(skb); } - seq_printf(s, "}\n"); + seq_puts(s, "}\n"); } else { if (tx) seq_printf(s, "[%2d] TxDesc index (%d) >= size (%d)\n", @@ -666,16 +742,79 @@ static const struct file_operations fops_txdesc = { }; /*---------beamforming------------*/ +static char *wil_bfstatus_str(u32 status) +{ + switch (status) { + case 0: + return "Failed"; + case 1: + return "OK"; + case 2: + return "Retrying"; + default: + return "??"; + } +} + +static bool is_all_zeros(void * const x_, size_t sz) +{ + /* if reply is all-0, ignore this CID */ + u32 *x = x_; + int n; + + for (n = 0; n < sz / sizeof(*x); n++) + if (x[n]) + return false; + + return true; +} + static int wil_bf_debugfs_show(struct seq_file *s, void *data) { + int rc; + int i; struct wil6210_priv *wil = s->private; - seq_printf(s, - "TSF : 0x%016llx\n" - "TxMCS : %d\n" - "Sectors(rx:tx) my %2d:%2d peer %2d:%2d\n", - wil->stats.tsf, wil->stats.bf_mcs, - wil->stats.my_rx_sector, wil->stats.my_tx_sector, - wil->stats.peer_rx_sector, wil->stats.peer_tx_sector); + struct wmi_notify_req_cmd cmd = { + .interval_usec = 0, + }; + struct { + struct wil6210_mbox_hdr_wmi wmi; + struct wmi_notify_req_done_event evt; + } __packed reply; + + for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { + u32 status; + + cmd.cid = i; + rc = wmi_call(wil, WMI_NOTIFY_REQ_CMDID, &cmd, sizeof(cmd), + WMI_NOTIFY_REQ_DONE_EVENTID, &reply, + sizeof(reply), 20); + /* if reply is all-0, ignore this CID */ + if (rc || is_all_zeros(&reply.evt, sizeof(reply.evt))) + continue; + + status = le32_to_cpu(reply.evt.status); + seq_printf(s, "CID %d {\n" + " TSF = 0x%016llx\n" + " TxMCS = %2d TxTpt = %4d\n" + " SQI = %4d\n" + " Status = 0x%08x %s\n" + " Sectors(rx:tx) my %2d:%2d peer %2d:%2d\n" + " Goodput(rx:tx) %4d:%4d\n" + "}\n", + i, + le64_to_cpu(reply.evt.tsf), + le16_to_cpu(reply.evt.bf_mcs), + le32_to_cpu(reply.evt.tx_tpt), + reply.evt.sqi, + status, wil_bfstatus_str(status), + le16_to_cpu(reply.evt.my_rx_sector), + le16_to_cpu(reply.evt.my_tx_sector), + le16_to_cpu(reply.evt.other_rx_sector), + le16_to_cpu(reply.evt.other_tx_sector), + le32_to_cpu(reply.evt.rx_goodput), + le32_to_cpu(reply.evt.tx_goodput)); + } return 0; } @@ -690,6 +829,7 @@ static const struct file_operations fops_bf = { .read = seq_read, .llseek = seq_lseek, }; + /*---------SSID------------*/ static ssize_t wil_read_file_ssid(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) @@ -752,10 +892,10 @@ static int wil_temp_debugfs_show(struct seq_file *s, void *data) { struct wil6210_priv *wil = s->private; u32 t_m, t_r; - int rc = wmi_get_temperature(wil, &t_m, &t_r); + if (rc) { - seq_printf(s, "Failed\n"); + seq_puts(s, "Failed\n"); return 0; } @@ -811,6 +951,7 @@ static int wil_link_debugfs_show(struct seq_file *s, void *data) for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { struct wil_sta_info *p = &wil->sta[i]; char *status = "unknown"; + switch (p->status) { case wil_sta_unused: status = "unused "; @@ -871,7 +1012,6 @@ static int wil_info_debugfs_show(struct seq_file *s, void *data) rxf_old = rxf; txf_old = txf; - #define CHECK_QSTATE(x) (state & BIT(__QUEUE_STATE_ ## x)) ? \ " " __stringify(x) : "" @@ -901,11 +1041,77 @@ static const struct file_operations fops_info = { .llseek = seq_lseek, }; +/*---------recovery------------*/ +/* mode = [manual|auto] + * state = [idle|pending|running] + */ +static ssize_t wil_read_file_recovery(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + char buf[80]; + int n; + static const char * const sstate[] = {"idle", "pending", "running"}; + + n = snprintf(buf, sizeof(buf), "mode = %s\nstate = %s\n", + no_fw_recovery ? "manual" : "auto", + sstate[wil->recovery_state]); + + n = min_t(int, n, sizeof(buf)); + + return simple_read_from_buffer(user_buf, count, ppos, + buf, n); +} + +static ssize_t wil_write_file_recovery(struct file *file, + const char __user *buf_, + size_t count, loff_t *ppos) +{ + struct wil6210_priv *wil = file->private_data; + static const char run_command[] = "run"; + char buf[sizeof(run_command) + 1]; /* to detect "runx" */ + ssize_t rc; + + if (wil->recovery_state != fw_recovery_pending) { + wil_err(wil, "No recovery pending\n"); + return -EINVAL; + } + + if (*ppos != 0) { + wil_err(wil, "Offset [%d]\n", (int)*ppos); + return -EINVAL; + } + + if (count > sizeof(buf)) { + wil_err(wil, "Input too long, len = %d\n", (int)count); + return -EINVAL; + } + + rc = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, buf_, count); + if (rc < 0) + return rc; + + buf[rc] = '\0'; + if (0 == strcmp(buf, run_command)) + wil_set_recovery_state(wil, fw_recovery_running); + else + wil_err(wil, "Bad recovery command \"%s\"\n", buf); + + return rc; +} + +static const struct file_operations fops_recovery = { + .read = wil_read_file_recovery, + .write = wil_write_file_recovery, + .open = simple_open, +}; + /*---------Station matrix------------*/ static void wil_print_rxtid(struct seq_file *s, struct wil_tid_ampdu_rx *r) { int i; u16 index = ((r->head_seq_num - r->ssn) & 0xfff) % r->buf_size; + seq_printf(s, "0x%03x [", r->head_seq_num); for (i = 0; i < r->buf_size; i++) { if (i == index) @@ -920,10 +1126,12 @@ static int wil_sta_debugfs_show(struct seq_file *s, void *data) { struct wil6210_priv *wil = s->private; int i, tid; + unsigned long flags; for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { struct wil_sta_info *p = &wil->sta[i]; char *status = "unknown"; + switch (p->status) { case wil_sta_unused: status = "unused "; @@ -939,13 +1147,16 @@ static int wil_sta_debugfs_show(struct seq_file *s, void *data) (p->data_port_open ? " data_port_open" : "")); if (p->status == wil_sta_connected) { + spin_lock_irqsave(&p->tid_rx_lock, flags); for (tid = 0; tid < WIL_STA_TID_NUM; tid++) { struct wil_tid_ampdu_rx *r = p->tid_rx[tid]; + if (r) { seq_printf(s, "[%2d] ", tid); wil_print_rxtid(s, r); } } + spin_unlock_irqrestore(&p->tid_rx_lock, flags); } } @@ -985,6 +1196,89 @@ static void wil6210_debugfs_init_blobs(struct wil6210_priv *wil, } } +/* misc files */ +static const struct { + const char *name; + umode_t mode; + const struct file_operations *fops; +} dbg_files[] = { + {"mbox", S_IRUGO, &fops_mbox}, + {"vrings", S_IRUGO, &fops_vring}, + {"stations", S_IRUGO, &fops_sta}, + {"desc", S_IRUGO, &fops_txdesc}, + {"bf", S_IRUGO, &fops_bf}, + {"ssid", S_IRUGO | S_IWUSR, &fops_ssid}, + {"mem_val", S_IRUGO, &fops_memread}, + {"reset", S_IWUSR, &fops_reset}, + {"rxon", S_IWUSR, &fops_rxon}, + {"tx_mgmt", S_IWUSR, &fops_txmgmt}, + {"wmi_send", S_IWUSR, &fops_wmi}, + {"temp", S_IRUGO, &fops_temp}, + {"freq", S_IRUGO, &fops_freq}, + {"link", S_IRUGO, &fops_link}, + {"info", S_IRUGO, &fops_info}, + {"recovery", S_IRUGO | S_IWUSR, &fops_recovery}, +}; + +static void wil6210_debugfs_init_files(struct wil6210_priv *wil, + struct dentry *dbg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dbg_files); i++) + debugfs_create_file(dbg_files[i].name, dbg_files[i].mode, dbg, + wil, dbg_files[i].fops); +} + +/* interrupt control blocks */ +static const struct { + const char *name; + u32 icr_off; +} dbg_icr[] = { + {"USER_ICR", HOSTADDR(RGF_USER_USER_ICR)}, + {"DMA_EP_TX_ICR", HOSTADDR(RGF_DMA_EP_TX_ICR)}, + {"DMA_EP_RX_ICR", HOSTADDR(RGF_DMA_EP_RX_ICR)}, + {"DMA_EP_MISC_ICR", HOSTADDR(RGF_DMA_EP_MISC_ICR)}, +}; + +static void wil6210_debugfs_init_isr(struct wil6210_priv *wil, + struct dentry *dbg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dbg_icr); i++) + wil6210_debugfs_create_ISR(wil, dbg_icr[i].name, dbg, + dbg_icr[i].icr_off); +} + +#define WIL_FIELD(name, mode, type) { __stringify(name), mode, \ + offsetof(struct wil6210_priv, name), type} + +/* fields in struct wil6210_priv */ +static const struct dbg_off dbg_wil_off[] = { + WIL_FIELD(secure_pcp, S_IRUGO | S_IWUSR, doff_u32), + WIL_FIELD(status, S_IRUGO | S_IWUSR, doff_ulong), + WIL_FIELD(fw_version, S_IRUGO, doff_u32), + WIL_FIELD(hw_version, S_IRUGO, doff_x32), + WIL_FIELD(recovery_count, S_IRUGO, doff_u32), + {}, +}; + +static const struct dbg_off dbg_wil_regs[] = { + {"RGF_MAC_MTRL_COUNTER_0", S_IRUGO, HOSTADDR(RGF_MAC_MTRL_COUNTER_0), + doff_io32}, + {"RGF_USER_USAGE_1", S_IRUGO, HOSTADDR(RGF_USER_USAGE_1), doff_io32}, + {}, +}; + +/* static parameters */ +static const struct dbg_off dbg_statics[] = { + {"desc_index", S_IRUGO | S_IWUSR, (ulong)&dbg_txdesc_index, doff_u32}, + {"vring_index", S_IRUGO | S_IWUSR, (ulong)&dbg_vring_index, doff_u32}, + {"mem_addr", S_IRUGO | S_IWUSR, (ulong)&mem_addr, doff_u32}, + {}, +}; + int wil6210_debugfs_init(struct wil6210_priv *wil) { struct dentry *dbg = wil->debug = debugfs_create_dir(WIL_NAME, @@ -993,51 +1287,17 @@ int wil6210_debugfs_init(struct wil6210_priv *wil) if (IS_ERR_OR_NULL(dbg)) return -ENODEV; - debugfs_create_file("mbox", S_IRUGO, dbg, wil, &fops_mbox); - debugfs_create_file("vrings", S_IRUGO, dbg, wil, &fops_vring); - debugfs_create_file("stations", S_IRUGO, dbg, wil, &fops_sta); - debugfs_create_file("desc", S_IRUGO, dbg, wil, &fops_txdesc); - debugfs_create_u32("desc_index", S_IRUGO | S_IWUSR, dbg, - &dbg_txdesc_index); - debugfs_create_u32("vring_index", S_IRUGO | S_IWUSR, dbg, - &dbg_vring_index); - - debugfs_create_file("bf", S_IRUGO, dbg, wil, &fops_bf); - debugfs_create_file("ssid", S_IRUGO | S_IWUSR, dbg, wil, &fops_ssid); - debugfs_create_u32("secure_pcp", S_IRUGO | S_IWUSR, dbg, - &wil->secure_pcp); - wil_debugfs_create_ulong("status", S_IRUGO | S_IWUSR, dbg, - &wil->status); - debugfs_create_u32("fw_version", S_IRUGO, dbg, &wil->fw_version); - debugfs_create_x32("hw_version", S_IRUGO, dbg, &wil->hw_version); - - wil6210_debugfs_create_ISR(wil, "USER_ICR", dbg, - HOSTADDR(RGF_USER_USER_ICR)); - wil6210_debugfs_create_ISR(wil, "DMA_EP_TX_ICR", dbg, - HOSTADDR(RGF_DMA_EP_TX_ICR)); - wil6210_debugfs_create_ISR(wil, "DMA_EP_RX_ICR", dbg, - HOSTADDR(RGF_DMA_EP_RX_ICR)); - wil6210_debugfs_create_ISR(wil, "DMA_EP_MISC_ICR", dbg, - HOSTADDR(RGF_DMA_EP_MISC_ICR)); - wil6210_debugfs_create_pseudo_ISR(wil, dbg); - wil6210_debugfs_create_ITR_CNT(wil, dbg); + wil6210_debugfs_init_files(wil, dbg); + wil6210_debugfs_init_isr(wil, dbg); + wil6210_debugfs_init_blobs(wil, dbg); + wil6210_debugfs_init_offset(wil, dbg, wil, dbg_wil_off); + wil6210_debugfs_init_offset(wil, dbg, (void * __force)wil->csr, + dbg_wil_regs); + wil6210_debugfs_init_offset(wil, dbg, NULL, dbg_statics); - wil_debugfs_create_iomem_x32("RGF_USER_USAGE_1", S_IRUGO, dbg, - wil->csr + - HOSTADDR(RGF_USER_USAGE_1)); - debugfs_create_u32("mem_addr", S_IRUGO | S_IWUSR, dbg, &mem_addr); - debugfs_create_file("mem_val", S_IRUGO, dbg, wil, &fops_memread); - - debugfs_create_file("reset", S_IWUSR, dbg, wil, &fops_reset); - debugfs_create_file("rxon", S_IWUSR, dbg, wil, &fops_rxon); - debugfs_create_file("tx_mgmt", S_IWUSR, dbg, wil, &fops_txmgmt); - debugfs_create_file("wmi_send", S_IWUSR, dbg, wil, &fops_wmi); - debugfs_create_file("temp", S_IRUGO, dbg, wil, &fops_temp); - debugfs_create_file("freq", S_IRUGO, dbg, wil, &fops_freq); - debugfs_create_file("link", S_IRUGO, dbg, wil, &fops_link); - debugfs_create_file("info", S_IRUGO, dbg, wil, &fops_info); + wil6210_debugfs_create_pseudo_ISR(wil, dbg); - wil6210_debugfs_init_blobs(wil, dbg); + wil6210_debugfs_create_ITR_CNT(wil, dbg); return 0; } diff --git a/drivers/net/wireless/ath/wil6210/ethtool.c b/drivers/net/wireless/ath/wil6210/ethtool.c new file mode 100644 index 000000000000..d686638972be --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/ethtool.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/etherdevice.h> +#include <linux/pci.h> +#include <linux/rtnetlink.h> +#include <net/cfg80211.h> + +#include "wil6210.h" + +static int wil_ethtoolops_begin(struct net_device *ndev) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + + mutex_lock(&wil->mutex); + + wil_dbg_misc(wil, "%s()\n", __func__); + + return 0; +} + +static void wil_ethtoolops_complete(struct net_device *ndev) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + + wil_dbg_misc(wil, "%s()\n", __func__); + + mutex_unlock(&wil->mutex); +} + +static int wil_ethtoolops_get_coalesce(struct net_device *ndev, + struct ethtool_coalesce *cp) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + u32 itr_en, itr_val = 0; + + wil_dbg_misc(wil, "%s()\n", __func__); + + itr_en = ioread32(wil->csr + HOSTADDR(RGF_DMA_ITR_CNT_CRL)); + if (itr_en & BIT_DMA_ITR_CNT_CRL_EN) + itr_val = ioread32(wil->csr + HOSTADDR(RGF_DMA_ITR_CNT_TRSH)); + + cp->rx_coalesce_usecs = itr_val; + + return 0; +} + +static int wil_ethtoolops_set_coalesce(struct net_device *ndev, + struct ethtool_coalesce *cp) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + + wil_dbg_misc(wil, "%s(%d usec)\n", __func__, cp->rx_coalesce_usecs); + + if (wil->wdev->iftype == NL80211_IFTYPE_MONITOR) { + wil_dbg_misc(wil, "No IRQ coalescing in monitor mode\n"); + return -EINVAL; + } + + /* only @rx_coalesce_usecs supported, ignore + * other parameters + */ + + if (cp->rx_coalesce_usecs > WIL6210_ITR_TRSH_MAX) + goto out_bad; + + wil->itr_trsh = cp->rx_coalesce_usecs; + wil_set_itr_trsh(wil); + + return 0; + +out_bad: + wil_dbg_misc(wil, "Unsupported coalescing params. Raw command:\n"); + print_hex_dump_debug("DBG[MISC] coal ", DUMP_PREFIX_OFFSET, 16, 4, + cp, sizeof(*cp), false); + return -EINVAL; +} + +static const struct ethtool_ops wil_ethtool_ops = { + .begin = wil_ethtoolops_begin, + .complete = wil_ethtoolops_complete, + .get_drvinfo = cfg80211_get_drvinfo, + .get_coalesce = wil_ethtoolops_get_coalesce, + .set_coalesce = wil_ethtoolops_set_coalesce, +}; + +void wil_set_ethtoolops(struct net_device *ndev) +{ + ndev->ethtool_ops = &wil_ethtool_ops; +} diff --git a/drivers/net/wireless/ath/wil6210/fw.c b/drivers/net/wireless/ath/wil6210/fw.c new file mode 100644 index 000000000000..8c6f3b041f77 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/fw.c @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/crc32.h> +#include "wil6210.h" +#include "fw.h" + +MODULE_FIRMWARE(WIL_FW_NAME); + +/* target operations */ +/* register read */ +#define R(a) ioread32(wil->csr + HOSTADDR(a)) +/* register write. wmb() to make sure it is completed */ +#define W(a, v) do { iowrite32(v, wil->csr + HOSTADDR(a)); wmb(); } while (0) +/* register set = read, OR, write */ +#define S(a, v) W(a, R(a) | v) +/* register clear = read, AND with inverted, write */ +#define C(a, v) W(a, R(a) & ~v) + +static +void wil_memset_toio_32(volatile void __iomem *dst, u32 val, + size_t count) +{ + volatile u32 __iomem *d = dst; + + for (count += 4; count > 4; count -= 4) + __raw_writel(val, d++); +} + +#include "fw_inc.c" diff --git a/drivers/net/wireless/ath/wil6210/fw.h b/drivers/net/wireless/ath/wil6210/fw.h new file mode 100644 index 000000000000..7a2c6c129ad5 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/fw.h @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define WIL_FW_SIGNATURE (0x36323130) /* '0126' */ +#define WIL_FW_FMT_VERSION (1) /* format version driver supports */ + +enum wil_fw_record_type { + wil_fw_type_comment = 1, + wil_fw_type_data = 2, + wil_fw_type_fill = 3, + wil_fw_type_action = 4, + wil_fw_type_verify = 5, + wil_fw_type_file_header = 6, + wil_fw_type_direct_write = 7, + wil_fw_type_gateway_data = 8, + wil_fw_type_gateway_data4 = 9, +}; + +struct wil_fw_record_head { + __le16 type; /* enum wil_fw_record_type */ + __le16 flags; /* to be defined */ + __le32 size; /* whole record, bytes after head */ +} __packed; + +/* data block. write starting from @addr + * data_size inferred from the @head.size. For this case, + * data_size = @head.size - offsetof(struct wil_fw_record_data, data) + */ +struct wil_fw_record_data { /* type == wil_fw_type_data */ + __le32 addr; + __le32 data[0]; /* [data_size], see above */ +} __packed; + +/* fill with constant @value, @size bytes starting from @addr */ +struct wil_fw_record_fill { /* type == wil_fw_type_fill */ + __le32 addr; + __le32 value; + __le32 size; +} __packed; + +/* free-form comment + * for informational purpose, data_size is @head.size from record header + */ +struct wil_fw_record_comment { /* type == wil_fw_type_comment */ + u8 data[0]; /* free-form data [data_size], see above */ +} __packed; + +/* perform action + * data_size = @head.size - offsetof(struct wil_fw_record_action, data) + */ +struct wil_fw_record_action { /* type == wil_fw_type_action */ + __le32 action; /* action to perform: reset, wait for fw ready etc. */ + __le32 data[0]; /* action specific, [data_size], see above */ +} __packed; + +/* data block for struct wil_fw_record_direct_write */ +struct wil_fw_data_dwrite { + __le32 addr; + __le32 value; + __le32 mask; +} __packed; + +/* write @value to the @addr, + * preserve original bits accordingly to the @mask + * data_size is @head.size where @head is record header + */ +struct wil_fw_record_direct_write { /* type == wil_fw_type_direct_write */ + struct wil_fw_data_dwrite data[0]; +} __packed; + +/* verify condition: [@addr] & @mask == @value + * if condition not met, firmware download fails + */ +struct wil_fw_record_verify { /* type == wil_fw_verify */ + __le32 addr; /* read from this address */ + __le32 value; /* reference value */ + __le32 mask; /* mask for verification */ +} __packed; + +/* file header + * First record of every file + */ +struct wil_fw_record_file_header { + __le32 signature ; /* Wilocity signature */ + __le32 reserved; + __le32 crc; /* crc32 of the following data */ + __le32 version; /* format version */ + __le32 data_len; /* total data in file, including this record */ + u8 comment[32]; /* short description */ +} __packed; + +/* 1-dword gateway */ +/* data block for the struct wil_fw_record_gateway_data */ +struct wil_fw_data_gw { + __le32 addr; + __le32 value; +} __packed; + +/* gateway write block. + * write starting address and values from the data buffer + * through the gateway + * data_size inferred from the @head.size. For this case, + * data_size = @head.size - offsetof(struct wil_fw_record_gateway_data, data) + */ +struct wil_fw_record_gateway_data { /* type == wil_fw_type_gateway_data */ + __le32 gateway_addr_addr; + __le32 gateway_value_addr; + __le32 gateway_cmd_addr; + __le32 gateway_ctrl_address; +#define WIL_FW_GW_CTL_BUSY BIT(29) /* gateway busy performing operation */ +#define WIL_FW_GW_CTL_RUN BIT(30) /* start gateway operation */ + __le32 command; + struct wil_fw_data_gw data[0]; /* total size [data_size], see above */ +} __packed; + +/* 4-dword gateway */ +/* data block for the struct wil_fw_record_gateway_data4 */ +struct wil_fw_data_gw4 { + __le32 addr; + __le32 value[4]; +} __packed; + +/* gateway write block. + * write starting address and values from the data buffer + * through the gateway + * data_size inferred from the @head.size. For this case, + * data_size = @head.size - offsetof(struct wil_fw_record_gateway_data4, data) + */ +struct wil_fw_record_gateway_data4 { /* type == wil_fw_type_gateway_data4 */ + __le32 gateway_addr_addr; + __le32 gateway_value_addr[4]; + __le32 gateway_cmd_addr; + __le32 gateway_ctrl_address; /* same logic as for 1-dword gw */ + __le32 command; + struct wil_fw_data_gw4 data[0]; /* total size [data_size], see above */ +} __packed; diff --git a/drivers/net/wireless/ath/wil6210/fw_inc.c b/drivers/net/wireless/ath/wil6210/fw_inc.c new file mode 100644 index 000000000000..44cb71f5ea5b --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/fw_inc.c @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Algorithmic part of the firmware download. + * To be included in the container file providing framework + */ + +#define wil_err_fw(wil, fmt, arg...) wil_err(wil, "ERR[ FW ]" fmt, ##arg) +#define wil_dbg_fw(wil, fmt, arg...) wil_dbg(wil, "DBG[ FW ]" fmt, ##arg) +#define wil_hex_dump_fw(prefix_str, prefix_type, rowsize, \ + groupsize, buf, len, ascii) \ + print_hex_dump_debug("DBG[ FW ]" prefix_str, \ + prefix_type, rowsize, \ + groupsize, buf, len, ascii) + +#define FW_ADDR_CHECK(ioaddr, val, msg) do { \ + ioaddr = wmi_buffer(wil, val); \ + if (!ioaddr) { \ + wil_err_fw(wil, "bad " msg ": 0x%08x\n", \ + le32_to_cpu(val)); \ + return -EINVAL; \ + } \ + } while (0) + +/** + * wil_fw_verify - verify firmware file validity + * + * perform various checks for the firmware file header. + * records are not validated. + * + * Return file size or negative error + */ +static int wil_fw_verify(struct wil6210_priv *wil, const u8 *data, size_t size) +{ + const struct wil_fw_record_head *hdr = (const void *)data; + struct wil_fw_record_file_header fh; + const struct wil_fw_record_file_header *fh_; + u32 crc; + u32 dlen; + + if (size % 4) { + wil_err_fw(wil, "image size not aligned: %zu\n", size); + return -EINVAL; + } + /* have enough data for the file header? */ + if (size < sizeof(*hdr) + sizeof(fh)) { + wil_err_fw(wil, "file too short: %zu bytes\n", size); + return -EINVAL; + } + + /* start with the file header? */ + if (le16_to_cpu(hdr->type) != wil_fw_type_file_header) { + wil_err_fw(wil, "no file header\n"); + return -EINVAL; + } + + /* data_len */ + fh_ = (struct wil_fw_record_file_header *)&hdr[1]; + dlen = le32_to_cpu(fh_->data_len); + if (dlen % 4) { + wil_err_fw(wil, "data length not aligned: %lu\n", (ulong)dlen); + return -EINVAL; + } + if (size < dlen) { + wil_err_fw(wil, "file truncated at %zu/%lu\n", + size, (ulong)dlen); + return -EINVAL; + } + if (dlen < sizeof(*hdr) + sizeof(fh)) { + wil_err_fw(wil, "data length too short: %lu\n", (ulong)dlen); + return -EINVAL; + } + + /* signature */ + if (le32_to_cpu(fh_->signature) != WIL_FW_SIGNATURE) { + wil_err_fw(wil, "bad header signature: 0x%08x\n", + le32_to_cpu(fh_->signature)); + return -EINVAL; + } + + /* version */ + if (le32_to_cpu(fh_->version) > WIL_FW_FMT_VERSION) { + wil_err_fw(wil, "unsupported header version: %d\n", + le32_to_cpu(fh_->version)); + return -EINVAL; + } + + /* checksum. ~crc32(~0, data, size) when fh.crc set to 0*/ + fh = *fh_; + fh.crc = 0; + + crc = crc32_le(~0, (unsigned char const *)hdr, sizeof(*hdr)); + crc = crc32_le(crc, (unsigned char const *)&fh, sizeof(fh)); + crc = crc32_le(crc, (unsigned char const *)&fh_[1], + dlen - sizeof(*hdr) - sizeof(fh)); + crc = ~crc; + + if (crc != le32_to_cpu(fh_->crc)) { + wil_err_fw(wil, "checksum mismatch:" + " calculated for %lu bytes 0x%08x != 0x%08x\n", + (ulong)dlen, crc, le32_to_cpu(fh_->crc)); + return -EINVAL; + } + + return (int)dlen; +} + +static int fw_handle_comment(struct wil6210_priv *wil, const void *data, + size_t size) +{ + wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, data, size, true); + + return 0; +} + +static int fw_handle_data(struct wil6210_priv *wil, const void *data, + size_t size) +{ + const struct wil_fw_record_data *d = data; + void __iomem *dst; + size_t s = size - sizeof(*d); + + if (size < sizeof(*d) + sizeof(u32)) { + wil_err_fw(wil, "data record too short: %zu\n", size); + return -EINVAL; + } + + FW_ADDR_CHECK(dst, d->addr, "address"); + wil_dbg_fw(wil, "write [0x%08x] <== %zu bytes\n", le32_to_cpu(d->addr), + s); + wil_memcpy_toio_32(dst, d->data, s); + wmb(); /* finish before processing next record */ + + return 0; +} + +static int fw_handle_fill(struct wil6210_priv *wil, const void *data, + size_t size) +{ + const struct wil_fw_record_fill *d = data; + void __iomem *dst; + u32 v; + size_t s = (size_t)le32_to_cpu(d->size); + + if (size != sizeof(*d)) { + wil_err_fw(wil, "bad size for fill record: %zu\n", size); + return -EINVAL; + } + + if (s < sizeof(u32)) { + wil_err_fw(wil, "fill size too short: %zu\n", s); + return -EINVAL; + } + + if (s % sizeof(u32)) { + wil_err_fw(wil, "fill size not aligned: %zu\n", s); + return -EINVAL; + } + + FW_ADDR_CHECK(dst, d->addr, "address"); + + v = le32_to_cpu(d->value); + wil_dbg_fw(wil, "fill [0x%08x] <== 0x%08x, %zu bytes\n", + le32_to_cpu(d->addr), v, s); + wil_memset_toio_32(dst, v, s); + wmb(); /* finish before processing next record */ + + return 0; +} + +static int fw_handle_file_header(struct wil6210_priv *wil, const void *data, + size_t size) +{ + const struct wil_fw_record_file_header *d = data; + + if (size != sizeof(*d)) { + wil_err_fw(wil, "file header length incorrect: %zu\n", size); + return -EINVAL; + } + + wil_dbg_fw(wil, "new file, ver. %d, %i bytes\n", + d->version, d->data_len); + wil_hex_dump_fw("", DUMP_PREFIX_OFFSET, 16, 1, d->comment, + sizeof(d->comment), true); + + return 0; +} + +static int fw_handle_direct_write(struct wil6210_priv *wil, const void *data, + size_t size) +{ + const struct wil_fw_record_direct_write *d = data; + const struct wil_fw_data_dwrite *block = d->data; + int n, i; + + if (size % sizeof(*block)) { + wil_err_fw(wil, "record size not aligned on %zu: %zu\n", + sizeof(*block), size); + return -EINVAL; + } + n = size / sizeof(*block); + + for (i = 0; i < n; i++) { + void __iomem *dst; + u32 m = le32_to_cpu(block[i].mask); + u32 v = le32_to_cpu(block[i].value); + u32 x, y; + + FW_ADDR_CHECK(dst, block[i].addr, "address"); + + x = ioread32(dst); + y = (x & m) | (v & ~m); + wil_dbg_fw(wil, "write [0x%08x] <== 0x%08x " + "(old 0x%08x val 0x%08x mask 0x%08x)\n", + le32_to_cpu(block[i].addr), y, x, v, m); + iowrite32(y, dst); + wmb(); /* finish before processing next record */ + } + + return 0; +} + +static int gw_write(struct wil6210_priv *wil, void __iomem *gwa_addr, + void __iomem *gwa_cmd, void __iomem *gwa_ctl, u32 gw_cmd, + u32 a) +{ + unsigned delay = 0; + + iowrite32(a, gwa_addr); + iowrite32(gw_cmd, gwa_cmd); + wmb(); /* finish before activate gw */ + + iowrite32(WIL_FW_GW_CTL_RUN, gwa_ctl); /* activate gw */ + do { + udelay(1); /* typical time is few usec */ + if (delay++ > 100) { + wil_err_fw(wil, "gw timeout\n"); + return -EINVAL; + } + } while (ioread32(gwa_ctl) & WIL_FW_GW_CTL_BUSY); /* gw done? */ + + return 0; +} + +static int fw_handle_gateway_data(struct wil6210_priv *wil, const void *data, + size_t size) +{ + const struct wil_fw_record_gateway_data *d = data; + const struct wil_fw_data_gw *block = d->data; + void __iomem *gwa_addr; + void __iomem *gwa_val; + void __iomem *gwa_cmd; + void __iomem *gwa_ctl; + u32 gw_cmd; + int n, i; + + if (size < sizeof(*d) + sizeof(*block)) { + wil_err_fw(wil, "gateway record too short: %zu\n", size); + return -EINVAL; + } + + if ((size - sizeof(*d)) % sizeof(*block)) { + wil_err_fw(wil, "gateway record data size" + " not aligned on %zu: %zu\n", + sizeof(*block), size - sizeof(*d)); + return -EINVAL; + } + n = (size - sizeof(*d)) / sizeof(*block); + + gw_cmd = le32_to_cpu(d->command); + + wil_dbg_fw(wil, "gw write record [%3d] blocks, cmd 0x%08x\n", + n, gw_cmd); + + FW_ADDR_CHECK(gwa_addr, d->gateway_addr_addr, "gateway_addr_addr"); + FW_ADDR_CHECK(gwa_val, d->gateway_value_addr, "gateway_value_addr"); + FW_ADDR_CHECK(gwa_cmd, d->gateway_cmd_addr, "gateway_cmd_addr"); + FW_ADDR_CHECK(gwa_ctl, d->gateway_ctrl_address, "gateway_ctrl_address"); + + wil_dbg_fw(wil, "gw addresses: addr 0x%08x val 0x%08x" + " cmd 0x%08x ctl 0x%08x\n", + le32_to_cpu(d->gateway_addr_addr), + le32_to_cpu(d->gateway_value_addr), + le32_to_cpu(d->gateway_cmd_addr), + le32_to_cpu(d->gateway_ctrl_address)); + + for (i = 0; i < n; i++) { + int rc; + u32 a = le32_to_cpu(block[i].addr); + u32 v = le32_to_cpu(block[i].value); + + wil_dbg_fw(wil, " gw write[%3d] [0x%08x] <== 0x%08x\n", + i, a, v); + + iowrite32(v, gwa_val); + rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a); + if (rc) + return rc; + } + + return 0; +} + +static int fw_handle_gateway_data4(struct wil6210_priv *wil, const void *data, + size_t size) +{ + const struct wil_fw_record_gateway_data4 *d = data; + const struct wil_fw_data_gw4 *block = d->data; + void __iomem *gwa_addr; + void __iomem *gwa_val[ARRAY_SIZE(block->value)]; + void __iomem *gwa_cmd; + void __iomem *gwa_ctl; + u32 gw_cmd; + int n, i, k; + + if (size < sizeof(*d) + sizeof(*block)) { + wil_err_fw(wil, "gateway4 record too short: %zu\n", size); + return -EINVAL; + } + + if ((size - sizeof(*d)) % sizeof(*block)) { + wil_err_fw(wil, "gateway4 record data size" + " not aligned on %zu: %zu\n", + sizeof(*block), size - sizeof(*d)); + return -EINVAL; + } + n = (size - sizeof(*d)) / sizeof(*block); + + gw_cmd = le32_to_cpu(d->command); + + wil_dbg_fw(wil, "gw4 write record [%3d] blocks, cmd 0x%08x\n", + n, gw_cmd); + + FW_ADDR_CHECK(gwa_addr, d->gateway_addr_addr, "gateway_addr_addr"); + for (k = 0; k < ARRAY_SIZE(block->value); k++) + FW_ADDR_CHECK(gwa_val[k], d->gateway_value_addr[k], + "gateway_value_addr"); + FW_ADDR_CHECK(gwa_cmd, d->gateway_cmd_addr, "gateway_cmd_addr"); + FW_ADDR_CHECK(gwa_ctl, d->gateway_ctrl_address, "gateway_ctrl_address"); + + wil_dbg_fw(wil, "gw4 addresses: addr 0x%08x cmd 0x%08x ctl 0x%08x\n", + le32_to_cpu(d->gateway_addr_addr), + le32_to_cpu(d->gateway_cmd_addr), + le32_to_cpu(d->gateway_ctrl_address)); + wil_hex_dump_fw("val addresses: ", DUMP_PREFIX_NONE, 16, 4, + d->gateway_value_addr, sizeof(d->gateway_value_addr), + false); + + for (i = 0; i < n; i++) { + int rc; + u32 a = le32_to_cpu(block[i].addr); + u32 v[ARRAY_SIZE(block->value)]; + + for (k = 0; k < ARRAY_SIZE(block->value); k++) + v[k] = le32_to_cpu(block[i].value[k]); + + wil_dbg_fw(wil, " gw4 write[%3d] [0x%08x] <==\n", i, a); + wil_hex_dump_fw(" val ", DUMP_PREFIX_NONE, 16, 4, v, + sizeof(v), false); + + for (k = 0; k < ARRAY_SIZE(block->value); k++) + iowrite32(v[k], gwa_val[k]); + rc = gw_write(wil, gwa_addr, gwa_cmd, gwa_ctl, gw_cmd, a); + if (rc) + return rc; + } + + return 0; +} + +static const struct { + int type; + int (*handler)(struct wil6210_priv *wil, const void *data, size_t size); +} wil_fw_handlers[] = { + {wil_fw_type_comment, fw_handle_comment}, + {wil_fw_type_data, fw_handle_data}, + {wil_fw_type_fill, fw_handle_fill}, + /* wil_fw_type_action */ + /* wil_fw_type_verify */ + {wil_fw_type_file_header, fw_handle_file_header}, + {wil_fw_type_direct_write, fw_handle_direct_write}, + {wil_fw_type_gateway_data, fw_handle_gateway_data}, + {wil_fw_type_gateway_data4, fw_handle_gateway_data4}, +}; + +static int wil_fw_handle_record(struct wil6210_priv *wil, int type, + const void *data, size_t size) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wil_fw_handlers); i++) { + if (wil_fw_handlers[i].type == type) + return wil_fw_handlers[i].handler(wil, data, size); + } + + wil_err_fw(wil, "unknown record type: %d\n", type); + return -EINVAL; +} + +/** + * wil_fw_load - load FW into device + * + * Load the FW and uCode code and data to the corresponding device + * memory regions + * + * Return error code + */ +static int wil_fw_load(struct wil6210_priv *wil, const void *data, size_t size) +{ + int rc = 0; + const struct wil_fw_record_head *hdr; + size_t s, hdr_sz; + + for (hdr = data;; hdr = (const void *)hdr + s, size -= s) { + if (size < sizeof(*hdr)) + break; + hdr_sz = le32_to_cpu(hdr->size); + s = sizeof(*hdr) + hdr_sz; + if (s > size) + break; + if (hdr_sz % 4) { + wil_err_fw(wil, "unaligned record size: %zu\n", + hdr_sz); + return -EINVAL; + } + rc = wil_fw_handle_record(wil, le16_to_cpu(hdr->type), + &hdr[1], hdr_sz); + if (rc) + return rc; + } + if (size) { + wil_err_fw(wil, "unprocessed bytes: %zu\n", size); + if (size >= sizeof(*hdr)) { + wil_err_fw(wil, "Stop at offset %ld" + " record type %d [%zd bytes]\n", + (const void *)hdr - data, + le16_to_cpu(hdr->type), hdr_sz); + } + return -EINVAL; + } + /* Mark FW as loaded from host */ + S(RGF_USER_USAGE_6, 1); + + return rc; +} + +/** + * wil_request_firmware - Request firmware and load to device + * + * Request firmware image from the file and load it to device + * + * Return error code + */ +int wil_request_firmware(struct wil6210_priv *wil, const char *name) +{ + int rc, rc1; + const struct firmware *fw; + size_t sz; + const void *d; + + rc = request_firmware(&fw, name, wil_to_pcie_dev(wil)); + if (rc) { + wil_err_fw(wil, "Failed to load firmware %s\n", name); + return rc; + } + wil_dbg_fw(wil, "Loading <%s>, %zu bytes\n", name, fw->size); + + for (sz = fw->size, d = fw->data; sz; sz -= rc1, d += rc1) { + rc1 = wil_fw_verify(wil, d, sz); + if (rc1 < 0) { + rc = rc1; + goto out; + } + rc = wil_fw_load(wil, d, rc1); + if (rc < 0) + goto out; + } + +out: + release_firmware(fw); + return rc; +} diff --git a/drivers/net/wireless/ath/wil6210/interrupt.c b/drivers/net/wireless/ath/wil6210/interrupt.c index 67f1002a03a1..90f416f239bd 100644 --- a/drivers/net/wireless/ath/wil6210/interrupt.c +++ b/drivers/net/wireless/ath/wil6210/interrupt.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -135,7 +135,7 @@ static void wil6210_unmask_irq_pseudo(struct wil6210_priv *wil) HOSTADDR(RGF_DMA_PSEUDO_CAUSE_MASK_SW)); } -void wil6210_disable_irq(struct wil6210_priv *wil) +void wil_mask_irq(struct wil6210_priv *wil) { wil_dbg_irq(wil, "%s()\n", __func__); @@ -145,7 +145,7 @@ void wil6210_disable_irq(struct wil6210_priv *wil) wil6210_mask_irq_pseudo(wil); } -void wil6210_enable_irq(struct wil6210_priv *wil) +void wil_unmask_irq(struct wil6210_priv *wil) { wil_dbg_irq(wil, "%s()\n", __func__); @@ -157,17 +157,7 @@ void wil6210_enable_irq(struct wil6210_priv *wil) offsetof(struct RGF_ICR, ICC)); /* interrupt moderation parameters */ - if (wil->wdev->iftype == NL80211_IFTYPE_MONITOR) { - /* disable interrupt moderation for monitor - * to get better timestamp precision - */ - iowrite32(0, wil->csr + HOSTADDR(RGF_DMA_ITR_CNT_CRL)); - } else { - iowrite32(WIL6210_ITR_TRSH, - wil->csr + HOSTADDR(RGF_DMA_ITR_CNT_TRSH)); - iowrite32(BIT_DMA_ITR_CNT_CRL_EN, - wil->csr + HOSTADDR(RGF_DMA_ITR_CNT_CRL)); - } + wil_set_itr_trsh(wil); wil6210_unmask_irq_pseudo(wil); wil6210_unmask_irq_tx(wil); @@ -196,8 +186,13 @@ static irqreturn_t wil6210_irq_rx(int irq, void *cookie) wil_dbg_irq(wil, "RX done\n"); isr &= ~BIT_DMA_EP_RX_ICR_RX_DONE; if (test_bit(wil_status_reset_done, &wil->status)) { - wil_dbg_txrx(wil, "NAPI(Rx) schedule\n"); - napi_schedule(&wil->napi_rx); + if (test_bit(wil_status_napi_en, &wil->status)) { + wil_dbg_txrx(wil, "NAPI(Rx) schedule\n"); + napi_schedule(&wil->napi_rx); + } else { + wil_err(wil, "Got Rx interrupt while " + "stopping interface\n"); + } } else { wil_err(wil, "Got Rx interrupt while in reset\n"); } @@ -506,7 +501,8 @@ free0: return rc; } -/* can't use wil_ioread32_and_clear because ICC value is not ser yet */ + +/* can't use wil_ioread32_and_clear because ICC value is not set yet */ static inline void wil_clear32(void __iomem *addr) { u32 x = ioread32(addr); @@ -522,11 +518,15 @@ void wil6210_clear_irq(struct wil6210_priv *wil) offsetof(struct RGF_ICR, ICR)); wil_clear32(wil->csr + HOSTADDR(RGF_DMA_EP_MISC_ICR) + offsetof(struct RGF_ICR, ICR)); + wmb(); /* make sure write completed */ } int wil6210_init_irq(struct wil6210_priv *wil, int irq) { int rc; + + wil_dbg_misc(wil, "%s() n_msi=%d\n", __func__, wil->n_msi); + if (wil->n_msi == 3) rc = wil6210_request_3msi(wil, irq); else @@ -534,17 +534,14 @@ int wil6210_init_irq(struct wil6210_priv *wil, int irq) wil6210_thread_irq, wil->n_msi ? 0 : IRQF_SHARED, WIL_NAME, wil); - if (rc) - return rc; - - wil6210_enable_irq(wil); - - return 0; + return rc; } void wil6210_fini_irq(struct wil6210_priv *wil, int irq) { - wil6210_disable_irq(wil); + wil_dbg_misc(wil, "%s()\n", __func__); + + wil_mask_irq(wil); free_irq(irq, wil); if (wil->n_msi == 3) { free_irq(irq + 1, wil); diff --git a/drivers/net/wireless/ath/wil6210/ioctl.c b/drivers/net/wireless/ath/wil6210/ioctl.c new file mode 100644 index 000000000000..e9c0673819c6 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/ioctl.c @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/uaccess.h> + +#include "wil6210.h" +#include <uapi/linux/wil6210_uapi.h> + +#define wil_hex_dump_ioctl(prefix_str, buf, len) \ + print_hex_dump_debug("DBG[IOC ]" prefix_str, \ + DUMP_PREFIX_OFFSET, 16, 1, buf, len, true) +#define wil_dbg_ioctl(wil, fmt, arg...) wil_dbg(wil, "DBG[IOC ]" fmt, ##arg) + +static void __iomem *wil_ioc_addr(struct wil6210_priv *wil, uint32_t addr, + uint32_t size, enum wil_memio_op op) +{ + void __iomem *a; + u32 off; + + switch (op & wil_mmio_addr_mask) { + case wil_mmio_addr_linker: + a = wmi_buffer(wil, cpu_to_le32(addr)); + break; + case wil_mmio_addr_ahb: + a = wmi_addr(wil, addr); + break; + case wil_mmio_addr_bar: + a = wmi_addr(wil, addr + WIL6210_FW_HOST_OFF); + break; + default: + wil_err(wil, "Unsupported address mode, op = 0x%08x\n", op); + return NULL; + } + + off = a - wil->csr; + if (size >= WIL6210_MEM_SIZE - off) { + wil_err(wil, "Requested block does not fit into memory: " + "off = 0x%08x size = 0x%08x\n", off, size); + return NULL; + } + + return a; +} + +static int wil_ioc_memio_dword(struct wil6210_priv *wil, void __user *data) +{ + struct wil_memio io; + void __iomem *a; + bool need_copy = false; + + if (copy_from_user(&io, data, sizeof(io))) + return -EFAULT; + + wil_dbg_ioctl(wil, "IO: addr = 0x%08x val = 0x%08x op = 0x%08x\n", + io.addr, io.val, io.op); + + a = wil_ioc_addr(wil, io.addr, sizeof(u32), io.op); + if (!a) { + wil_err(wil, "invalid address 0x%08x, op = 0x%08x\n", io.addr, + io.op); + return -EINVAL; + } + /* operation */ + switch (io.op & wil_mmio_op_mask) { + case wil_mmio_read: + io.val = ioread32(a); + need_copy = true; + break; + case wil_mmio_write: + iowrite32(io.val, a); + wmb(); /* make sure write propagated to HW */ + break; + default: + wil_err(wil, "Unsupported operation, op = 0x%08x\n", io.op); + return -EINVAL; + } + + if (need_copy) { + wil_dbg_ioctl(wil, "IO done: addr = 0x%08x" + " val = 0x%08x op = 0x%08x\n", + io.addr, io.val, io.op); + if (copy_to_user(data, &io, sizeof(io))) + return -EFAULT; + } + + return 0; +} + +static int wil_ioc_memio_block(struct wil6210_priv *wil, void __user *data) +{ + struct wil_memio_block io; + void *block; + void __iomem *a; + int rc = 0; + + if (copy_from_user(&io, data, sizeof(io))) + return -EFAULT; + + wil_dbg_ioctl(wil, "IO: addr = 0x%08x size = 0x%08x op = 0x%08x\n", + io.addr, io.size, io.op); + + /* size */ + if (io.size % 4) { + wil_err(wil, "size is not multiple of 4: 0x%08x\n", io.size); + return -EINVAL; + } + + a = wil_ioc_addr(wil, io.addr, io.size, io.op); + if (!a) { + wil_err(wil, "invalid address 0x%08x, op = 0x%08x\n", io.addr, + io.op); + return -EINVAL; + } + + block = kmalloc(io.size, GFP_USER); + if (!block) + return -ENOMEM; + + /* operation */ + switch (io.op & wil_mmio_op_mask) { + case wil_mmio_read: + wil_memcpy_fromio_32(block, a, io.size); + wil_hex_dump_ioctl("Read ", block, io.size); + if (copy_to_user(io.block, block, io.size)) { + rc = -EFAULT; + goto out_free; + } + break; + case wil_mmio_write: + if (copy_from_user(block, io.block, io.size)) { + rc = -EFAULT; + goto out_free; + } + wil_memcpy_toio_32(a, block, io.size); + wmb(); /* make sure write propagated to HW */ + wil_hex_dump_ioctl("Write ", block, io.size); + break; + default: + wil_err(wil, "Unsupported operation, op = 0x%08x\n", io.op); + rc = -EINVAL; + break; + } + +out_free: + kfree(block); + return rc; +} + +int wil_ioctl(struct wil6210_priv *wil, void __user *data, int cmd) +{ + switch (cmd) { + case WIL_IOCTL_MEMIO: + return wil_ioc_memio_dword(wil, data); + case WIL_IOCTL_MEMIO_BLOCK: + return wil_ioc_memio_block(wil, data); + default: + wil_dbg_ioctl(wil, "Unsupported IOCTL 0x%04x\n", cmd); + return -ENOIOCTLCMD; + } +} diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c index 3704d2a434f3..6500caf8d609 100644 --- a/drivers/net/wireless/ath/wil6210/main.c +++ b/drivers/net/wireless/ath/wil6210/main.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,10 +20,26 @@ #include "wil6210.h" #include "txrx.h" +#include "wmi.h" -static bool no_fw_recovery; +#define WAIT_FOR_DISCONNECT_TIMEOUT_MS 2000 +#define WAIT_FOR_DISCONNECT_INTERVAL_MS 10 + +bool no_fw_recovery; module_param(no_fw_recovery, bool, S_IRUGO | S_IWUSR); -MODULE_PARM_DESC(no_fw_recovery, " disable FW error recovery"); +MODULE_PARM_DESC(no_fw_recovery, " disable automatic FW error recovery"); + +static bool no_fw_load = true; +module_param(no_fw_load, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(no_fw_load, " do not download FW, use one in on-card flash."); + +static unsigned int itr_trsh = WIL6210_ITR_TRSH_DEFAULT; + +module_param(itr_trsh, uint, S_IRUGO); +MODULE_PARM_DESC(itr_trsh, " Interrupt moderation threshold, usecs."); + +#define RST_DELAY (20) /* msec, for loop in @wil_target_reset */ +#define RST_COUNT (1 + 1000/RST_DELAY) /* round up to be above 1 sec total */ /* * Due to a hardware issue, @@ -64,6 +80,7 @@ static void wil_disconnect_cid(struct wil6210_priv *wil, int cid) struct net_device *ndev = wil_to_ndev(wil); struct wireless_dev *wdev = wil->wdev; struct wil_sta_info *sta = &wil->sta[cid]; + wil_dbg_misc(wil, "%s(CID %d, status %d)\n", __func__, cid, sta->status); @@ -83,9 +100,16 @@ static void wil_disconnect_cid(struct wil6210_priv *wil, int cid) } for (i = 0; i < WIL_STA_TID_NUM; i++) { - struct wil_tid_ampdu_rx *r = sta->tid_rx[i]; + struct wil_tid_ampdu_rx *r; + unsigned long flags; + + spin_lock_irqsave(&sta->tid_rx_lock, flags); + + r = sta->tid_rx[i]; sta->tid_rx[i] = NULL; wil_tid_ampdu_rx_free(wil, r); + + spin_unlock_irqrestore(&sta->tid_rx_lock, flags); } for (i = 0; i < ARRAY_SIZE(wil->vring_tx); i++) { if (wil->vring2cid_tid[i][0] == cid) @@ -167,17 +191,38 @@ static void wil_scan_timer_fn(ulong x) schedule_work(&wil->fw_error_worker); } +static int wil_wait_for_recovery(struct wil6210_priv *wil) +{ + if (wait_event_interruptible(wil->wq, wil->recovery_state != + fw_recovery_pending)) { + wil_err(wil, "Interrupt, canceling recovery\n"); + return -ERESTARTSYS; + } + if (wil->recovery_state != fw_recovery_running) { + wil_info(wil, "Recovery cancelled\n"); + return -EINTR; + } + wil_info(wil, "Proceed with recovery\n"); + return 0; +} + +void wil_set_recovery_state(struct wil6210_priv *wil, int state) +{ + wil_dbg_misc(wil, "%s(%d -> %d)\n", __func__, + wil->recovery_state, state); + + wil->recovery_state = state; + wake_up_interruptible(&wil->wq); +} + static void wil_fw_error_worker(struct work_struct *work) { - struct wil6210_priv *wil = container_of(work, - struct wil6210_priv, fw_error_worker); + struct wil6210_priv *wil = container_of(work, struct wil6210_priv, + fw_error_worker); struct wireless_dev *wdev = wil->wdev; wil_dbg_misc(wil, "fw error worker\n"); - if (no_fw_recovery) - return; - /* increment @recovery_count if less then WIL6210_FW_RECOVERY_TO * passed since last recovery attempt */ @@ -200,12 +245,15 @@ static void wil_fw_error_worker(struct work_struct *work) case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_MONITOR: - wil_info(wil, "fw error recovery started (try %d)...\n", + wil_info(wil, "fw error recovery requested (try %d)...\n", wil->recovery_count); - wil_reset(wil); + if (!no_fw_recovery) + wil->recovery_state = fw_recovery_running; + if (0 != wil_wait_for_recovery(wil)) + break; - /* need to re-allocate Rx ring after reset */ - wil_rx_init(wil); + __wil_down(wil); + __wil_up(wil); break; case NL80211_IFTYPE_AP: case NL80211_IFTYPE_P2P_GO: @@ -220,6 +268,7 @@ static void wil_fw_error_worker(struct work_struct *work) static int wil_find_free_vring(struct wil6210_priv *wil) { int i; + for (i = 0; i < WIL6210_MAX_TX_RINGS; i++) { if (!wil->vring_tx[i].va) return i; @@ -254,14 +303,19 @@ static void wil_connect_worker(struct work_struct *work) int wil_priv_init(struct wil6210_priv *wil) { + uint i; + wil_dbg_misc(wil, "%s()\n", __func__); memset(wil->sta, 0, sizeof(wil->sta)); + for (i = 0; i < WIL6210_MAX_CID; i++) + spin_lock_init(&wil->sta[i].tid_rx_lock); mutex_init(&wil->mutex); mutex_init(&wil->wmi_mutex); init_completion(&wil->wmi_ready); + init_completion(&wil->wmi_call); wil->pending_connect_cid = -1; setup_timer(&wil->connect_timer, wil_connect_timer_fn, (ulong)wil); @@ -274,6 +328,7 @@ int wil_priv_init(struct wil6210_priv *wil) INIT_LIST_HEAD(&wil->pending_wmi_ev); spin_lock_init(&wil->wmi_ev_lock); + init_waitqueue_head(&wil->wq); wil->wmi_wq = create_singlethread_workqueue(WIL_NAME"_wmi"); if (!wil->wmi_wq) @@ -286,18 +341,24 @@ int wil_priv_init(struct wil6210_priv *wil) } wil->last_fw_recovery = jiffies; + wil->itr_trsh = itr_trsh; return 0; } void wil6210_disconnect(struct wil6210_priv *wil, const u8 *bssid) { + wil_dbg_misc(wil, "%s()\n", __func__); + del_timer_sync(&wil->connect_timer); _wil6210_disconnect(wil, bssid); } void wil_priv_deinit(struct wil6210_priv *wil) { + wil_dbg_misc(wil, "%s()\n", __func__); + + wil_set_recovery_state(wil, fw_recovery_idle); del_timer_sync(&wil->scan_timer); cancel_work_sync(&wil->disconnect_worker); cancel_work_sync(&wil->fw_error_worker); @@ -309,7 +370,29 @@ void wil_priv_deinit(struct wil6210_priv *wil) destroy_workqueue(wil->wmi_wq); } -static void wil_target_reset(struct wil6210_priv *wil) +/* target operations */ +/* register read */ +#define R(a) ioread32(wil->csr + HOSTADDR(a)) +/* register write. wmb() to make sure it is completed */ +#define W(a, v) do { iowrite32(v, wil->csr + HOSTADDR(a)); wmb(); } while (0) +/* register set = read, OR, write */ +#define S(a, v) W(a, R(a) | v) +/* register clear = read, AND with inverted, write */ +#define C(a, v) W(a, R(a) & ~v) + +static inline void wil_halt_cpu(struct wil6210_priv *wil) +{ + W(RGF_USER_USER_CPU_0, BIT_USER_USER_CPU_MAN_RST); + W(RGF_USER_MAC_CPU_0, BIT_USER_MAC_CPU_MAN_RST); +} + +static inline void wil_release_cpu(struct wil6210_priv *wil) +{ + /* Start CPU */ + W(RGF_USER_USER_CPU_0, 1); +} + +static int wil_target_reset(struct wil6210_priv *wil) { int delay = 0; u32 hw_state; @@ -318,57 +401,41 @@ static void wil_target_reset(struct wil6210_priv *wil) wil_dbg_misc(wil, "Resetting \"%s\"...\n", wil->board->name); - /* register read */ -#define R(a) ioread32(wil->csr + HOSTADDR(a)) - /* register write */ -#define W(a, v) iowrite32(v, wil->csr + HOSTADDR(a)) - /* register set = read, OR, write */ -#define S(a, v) W(a, R(a) | v) - /* register clear = read, AND with inverted, write */ -#define C(a, v) W(a, R(a) & ~v) - wil->hw_version = R(RGF_USER_FW_REV_ID); rev_id = wil->hw_version & 0xff; /* Clear MAC link up */ S(RGF_HP_CTRL, BIT(15)); - /* hpal_perst_from_pad_src_n_mask */ - S(RGF_USER_CLKS_CTL_SW_RST_MASK_0, BIT(6)); - /* car_perst_rst_src_n_mask */ - S(RGF_USER_CLKS_CTL_SW_RST_MASK_0, BIT(7)); - wmb(); /* order is important here */ + S(RGF_USER_CLKS_CTL_SW_RST_MASK_0, BIT_HPAL_PERST_FROM_PAD); + S(RGF_USER_CLKS_CTL_SW_RST_MASK_0, BIT_CAR_PERST_RST); + + wil_halt_cpu(wil); + C(RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_CAR_AHB_SW_SEL); /* 40 MHz */ if (is_sparrow) { W(RGF_USER_CLKS_CTL_EXT_SW_RST_VEC_0, 0x3ff81f); - wmb(); /* order is important here */ + W(RGF_USER_CLKS_CTL_EXT_SW_RST_VEC_1, 0xf); } - W(RGF_USER_MAC_CPU_0, BIT(1)); /* mac_cpu_man_rst */ - W(RGF_USER_USER_CPU_0, BIT(1)); /* user_cpu_man_rst */ - wmb(); /* order is important here */ - W(RGF_USER_CLKS_CTL_SW_RST_VEC_2, 0xFE000000); W(RGF_USER_CLKS_CTL_SW_RST_VEC_1, 0x0000003F); - W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, is_sparrow ? 0x000000B0 : 0x00000170); - W(RGF_USER_CLKS_CTL_SW_RST_VEC_0, 0xFFE7FC00); - wmb(); /* order is important here */ + W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, is_sparrow ? 0x000000f0 : 0x00000170); + W(RGF_USER_CLKS_CTL_SW_RST_VEC_0, 0xFFE7FE00); if (is_sparrow) { W(RGF_USER_CLKS_CTL_EXT_SW_RST_VEC_0, 0x0); - wmb(); /* order is important here */ + W(RGF_USER_CLKS_CTL_EXT_SW_RST_VEC_1, 0x0); } W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, 0); W(RGF_USER_CLKS_CTL_SW_RST_VEC_2, 0); W(RGF_USER_CLKS_CTL_SW_RST_VEC_1, 0); W(RGF_USER_CLKS_CTL_SW_RST_VEC_0, 0); - wmb(); /* order is important here */ if (is_sparrow) { W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, 0x00000003); /* reset A2 PCIE AHB */ W(RGF_USER_CLKS_CTL_SW_RST_VEC_2, 0x00008000); - } else { W(RGF_USER_CLKS_CTL_SW_RST_VEC_3, 0x00000001); if (rev_id == 1) { @@ -378,21 +445,19 @@ static void wil_target_reset(struct wil6210_priv *wil) W(RGF_PCIE_LOS_COUNTER_CTL, BIT(6) | BIT(8)); W(RGF_USER_CLKS_CTL_SW_RST_VEC_2, 0x00008000); } - } /* TODO: check order here!!! Erez code is different */ W(RGF_USER_CLKS_CTL_SW_RST_VEC_0, 0); - wmb(); /* order is important here */ - /* wait until device ready */ + /* wait until device ready. typical time is 200..250 msec */ do { - msleep(1); + msleep(RST_DELAY); hw_state = R(RGF_USER_HW_MACHINE_STATE); - if (delay++ > 100) { + if (delay++ > RST_COUNT) { wil_err(wil, "Reset not completed, hw_state 0x%08x\n", hw_state); - return; + return -ETIME; } } while (hw_state != HW_MACHINE_BOOT_DONE); @@ -401,15 +466,35 @@ static void wil_target_reset(struct wil6210_priv *wil) W(RGF_PCIE_LOS_COUNTER_CTL, BIT(8)); C(RGF_USER_CLKS_CTL_0, BIT_USER_CLKS_RST_PWGD); - wmb(); /* order is important here */ - wil_dbg_misc(wil, "Reset completed in %d ms\n", delay); + wil_dbg_misc(wil, "Reset completed in %d ms\n", delay * RST_DELAY); + return 0; +} + +/** + * wil_set_itr_trsh: - apply interrupt coalescing params + */ +void wil_set_itr_trsh(struct wil6210_priv *wil) +{ + /* disable, use usec resolution */ + W(RGF_DMA_ITR_CNT_CRL, BIT_DMA_ITR_CNT_CRL_EXT_TICK); + + /* disable interrupt moderation for monitor + * to get better timestamp precision + */ + if (wil->wdev->iftype == NL80211_IFTYPE_MONITOR) + return; + + wil_info(wil, "set ITR_TRSH = %d usec\n", wil->itr_trsh); + W(RGF_DMA_ITR_CNT_TRSH, wil->itr_trsh); + W(RGF_DMA_ITR_CNT_CRL, BIT_DMA_ITR_CNT_CRL_EN | + BIT_DMA_ITR_CNT_CRL_EXT_TICK); /* start it */ +} #undef R #undef W #undef S #undef C -} void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r) { @@ -424,6 +509,7 @@ static int wil_wait_for_fw_ready(struct wil6210_priv *wil) { ulong to = msecs_to_jiffies(1000); ulong left = wait_for_completion_timeout(&wil->wmi_ready, to); + if (0 == left) { wil_err(wil, "Firmware not ready\n"); return -ETIME; @@ -443,15 +529,15 @@ int wil_reset(struct wil6210_priv *wil) { int rc; + wil_dbg_misc(wil, "%s()\n", __func__); + WARN_ON(!mutex_is_locked(&wil->mutex)); + WARN_ON(test_bit(wil_status_napi_en, &wil->status)); cancel_work_sync(&wil->disconnect_worker); wil6210_disconnect(wil, NULL); wil->status = 0; /* prevent NAPI from being scheduled */ - if (test_bit(wil_status_napi_en, &wil->status)) { - napi_synchronize(&wil->napi_rx); - } if (wil->scan_request) { wil_dbg_misc(wil, "Abort scan_request 0x%p\n", @@ -461,24 +547,50 @@ int wil_reset(struct wil6210_priv *wil) wil->scan_request = NULL; } - wil6210_disable_irq(wil); + wil_mask_irq(wil); wmi_event_flush(wil); flush_workqueue(wil->wmi_wq_conn); flush_workqueue(wil->wmi_wq); - /* TODO: put MAC in reset */ - wil_target_reset(wil); - + rc = wil_target_reset(wil); wil_rx_fini(wil); + if (rc) + return rc; + + if (!no_fw_load) { + wil_info(wil, "Use firmware <%s>\n", WIL_FW_NAME); + wil_halt_cpu(wil); + /* Loading f/w from the file */ + rc = wil_request_firmware(wil, WIL_FW_NAME); + if (rc) + return rc; + + /* clear any interrupts which on-card-firmware may have set */ + wil6210_clear_irq(wil); + { /* CAF_ICR - clear and mask */ + u32 a = HOSTADDR(RGF_CAF_ICR) + + offsetof(struct RGF_ICR, ICR); + u32 m = HOSTADDR(RGF_CAF_ICR) + + offsetof(struct RGF_ICR, IMV); + u32 icr = ioread32(wil->csr + a); + + iowrite32(icr, wil->csr + a); /* W1C */ + iowrite32(~0, wil->csr + m); + wmb(); /* wait for completion */ + } + wil_release_cpu(wil); + } else { + wil_info(wil, "Use firmware from on-card flash\n"); + } /* init after reset */ wil->pending_connect_cid = -1; reinit_completion(&wil->wmi_ready); + reinit_completion(&wil->wmi_call); - /* TODO: release MAC reset */ - wil6210_enable_irq(wil); + wil_unmask_irq(wil); /* we just started MAC, wait for FW ready */ rc = wil_wait_for_fw_ready(wil); @@ -489,6 +601,7 @@ int wil_reset(struct wil6210_priv *wil) void wil_fw_error_recovery(struct wil6210_priv *wil) { wil_dbg_misc(wil, "starting fw error recovery\n"); + wil->recovery_state = fw_recovery_pending; schedule_work(&wil->fw_error_worker); } @@ -514,7 +627,7 @@ void wil_link_off(struct wil6210_priv *wil) netif_carrier_off(ndev); } -static int __wil_up(struct wil6210_priv *wil) +int __wil_up(struct wil6210_priv *wil) { struct net_device *ndev = wil_to_ndev(wil); struct wireless_dev *wdev = wil->wdev; @@ -560,11 +673,15 @@ static int __wil_up(struct wil6210_priv *wil) /* MAC address - pre-requisite for other commands */ wmi_set_mac_address(wil, ndev->dev_addr); - + wil_dbg_misc(wil, "NAPI enable\n"); napi_enable(&wil->napi_rx); napi_enable(&wil->napi_tx); set_bit(wil_status_napi_en, &wil->status); + if (wil->platform_ops.bus_request) + wil->platform_ops.bus_request(wil->platform_handle, + WIL_MAX_BUS_REQUEST_KBPS); + return 0; } @@ -572,6 +689,8 @@ int wil_up(struct wil6210_priv *wil) { int rc; + wil_dbg_misc(wil, "%s()\n", __func__); + mutex_lock(&wil->mutex); rc = __wil_up(wil); mutex_unlock(&wil->mutex); @@ -579,13 +698,23 @@ int wil_up(struct wil6210_priv *wil) return rc; } -static int __wil_down(struct wil6210_priv *wil) +int __wil_down(struct wil6210_priv *wil) { + int iter = WAIT_FOR_DISCONNECT_TIMEOUT_MS / + WAIT_FOR_DISCONNECT_INTERVAL_MS; + WARN_ON(!mutex_is_locked(&wil->mutex)); - clear_bit(wil_status_napi_en, &wil->status); - napi_disable(&wil->napi_rx); - napi_disable(&wil->napi_tx); + if (wil->platform_ops.bus_request) + wil->platform_ops.bus_request(wil->platform_handle, 0); + + wil_disable_irq(wil); + if (test_and_clear_bit(wil_status_napi_en, &wil->status)) { + napi_disable(&wil->napi_rx); + napi_disable(&wil->napi_tx); + wil_dbg_misc(wil, "NAPI disable\n"); + } + wil_enable_irq(wil); if (wil->scan_request) { wil_dbg_misc(wil, "Abort scan_request 0x%p\n", @@ -595,7 +724,24 @@ static int __wil_down(struct wil6210_priv *wil) wil->scan_request = NULL; } - wil6210_disconnect(wil, NULL); + if (test_bit(wil_status_fwconnected, &wil->status) || + test_bit(wil_status_fwconnecting, &wil->status)) + wmi_send(wil, WMI_DISCONNECT_CMDID, NULL, 0); + + /* make sure wil is idle (not connected) */ + mutex_unlock(&wil->mutex); + while (iter--) { + int idle = !test_bit(wil_status_fwconnected, &wil->status) && + !test_bit(wil_status_fwconnecting, &wil->status); + if (idle) + break; + msleep(WAIT_FOR_DISCONNECT_INTERVAL_MS); + } + mutex_lock(&wil->mutex); + + if (!iter) + wil_err(wil, "timeout waiting for idle FW/HW\n"); + wil_rx_fini(wil); return 0; @@ -605,6 +751,9 @@ int wil_down(struct wil6210_priv *wil) { int rc; + wil_dbg_misc(wil, "%s()\n", __func__); + + wil_set_recovery_state(wil, fw_recovery_idle); mutex_lock(&wil->mutex); rc = __wil_down(wil); mutex_unlock(&wil->mutex); diff --git a/drivers/net/wireless/ath/wil6210/netdev.c b/drivers/net/wireless/ath/wil6210/netdev.c index 7afce6e8c507..239965106c05 100644 --- a/drivers/net/wireless/ath/wil6210/netdev.c +++ b/drivers/net/wireless/ath/wil6210/netdev.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,11 +17,14 @@ #include <linux/etherdevice.h> #include "wil6210.h" +#include "txrx.h" static int wil_open(struct net_device *ndev) { struct wil6210_priv *wil = ndev_to_wil(ndev); + wil_dbg_misc(wil, "%s()\n", __func__); + return wil_up(wil); } @@ -29,6 +32,8 @@ static int wil_stop(struct net_device *ndev) { struct wil6210_priv *wil = ndev_to_wil(ndev); + wil_dbg_misc(wil, "%s()\n", __func__); + return wil_down(wil); } @@ -36,8 +41,10 @@ static int wil_change_mtu(struct net_device *ndev, int new_mtu) { struct wil6210_priv *wil = ndev_to_wil(ndev); - if (new_mtu < 68 || new_mtu > IEEE80211_MAX_DATA_LEN_DMG) + if (new_mtu < 68 || new_mtu > (TX_BUF_LEN - ETH_HLEN)) { + wil_err(wil, "invalid MTU %d\n", new_mtu); return -EINVAL; + } wil_dbg_misc(wil, "change MTU %d -> %d\n", ndev->mtu, new_mtu); ndev->mtu = new_mtu; @@ -45,6 +52,17 @@ static int wil_change_mtu(struct net_device *ndev, int new_mtu) return 0; } +static int wil_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) +{ + struct wil6210_priv *wil = ndev_to_wil(ndev); + + int ret = wil_ioctl(wil, ifr->ifr_data, cmd); + + wil_dbg_misc(wil, "ioctl(0x%04x) -> %d\n", cmd, ret); + + return ret; +} + static const struct net_device_ops wil_netdev_ops = { .ndo_open = wil_open, .ndo_stop = wil_stop, @@ -52,6 +70,7 @@ static const struct net_device_ops wil_netdev_ops = { .ndo_set_mac_address = eth_mac_addr, .ndo_validate_addr = eth_validate_addr, .ndo_change_mtu = wil_change_mtu, + .ndo_do_ioctl = wil_do_ioctl, }; static int wil6210_netdev_poll_rx(struct napi_struct *napi, int budget) @@ -121,6 +140,8 @@ void *wil_if_alloc(struct device *dev, void __iomem *csr) wil->csr = csr; wil->wdev = wdev; + wil_dbg_misc(wil, "%s()\n", __func__); + rc = wil_priv_init(wil); if (rc) { dev_err(dev, "wil_priv_init failed\n"); @@ -140,6 +161,7 @@ void *wil_if_alloc(struct device *dev, void __iomem *csr) } ndev->netdev_ops = &wil_netdev_ops; + wil_set_ethtoolops(ndev); ndev->ieee80211_ptr = wdev; ndev->hw_features = NETIF_F_HW_CSUM | NETIF_F_RXCSUM | NETIF_F_SG | NETIF_F_GRO; @@ -168,11 +190,17 @@ void *wil_if_alloc(struct device *dev, void __iomem *csr) void wil_if_free(struct wil6210_priv *wil) { struct net_device *ndev = wil_to_ndev(wil); + + wil_dbg_misc(wil, "%s()\n", __func__); + if (!ndev) return; - free_netdev(ndev); wil_priv_deinit(wil); + + wil_to_ndev(wil) = NULL; + free_netdev(ndev); + wil_wdev_free(wil); } @@ -181,6 +209,8 @@ int wil_if_add(struct wil6210_priv *wil) struct net_device *ndev = wil_to_ndev(wil); int rc; + wil_dbg_misc(wil, "%s()\n", __func__); + rc = register_netdev(ndev); if (rc < 0) { dev_err(&ndev->dev, "Failed to register netdev: %d\n", rc); @@ -196,5 +226,7 @@ void wil_if_remove(struct wil6210_priv *wil) { struct net_device *ndev = wil_to_ndev(wil); + wil_dbg_misc(wil, "%s()\n", __func__); + unregister_netdev(ndev); } diff --git a/drivers/net/wireless/ath/wil6210/pcie_bus.c b/drivers/net/wireless/ath/wil6210/pcie_bus.c index d3fbfa28db62..66626a8ee728 100644 --- a/drivers/net/wireless/ath/wil6210/pcie_bus.c +++ b/drivers/net/wireless/ath/wil6210/pcie_bus.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/pci.h> #include <linux/moduleparam.h> +#include <linux/interrupt.h> #include "wil6210.h" @@ -30,6 +31,28 @@ static bool debug_fw; /* = false; */ module_param(debug_fw, bool, S_IRUGO); MODULE_PARM_DESC(debug_fw, " load driver if FW not ready. For FW debug"); +void wil_disable_irq(struct wil6210_priv *wil) +{ + int irq = wil->pdev->irq; + + disable_irq(irq); + if (wil->n_msi == 3) { + disable_irq(irq + 1); + disable_irq(irq + 2); + } +} + +void wil_enable_irq(struct wil6210_priv *wil) +{ + int irq = wil->pdev->irq; + + enable_irq(irq); + if (wil->n_msi == 3) { + enable_irq(irq + 1); + enable_irq(irq + 2); + } +} + /* Bus ops */ static int wil_if_pcie_enable(struct wil6210_priv *wil) { @@ -41,6 +64,8 @@ static int wil_if_pcie_enable(struct wil6210_priv *wil) */ int msi_only = pdev->msi_enabled; + wil_dbg_misc(wil, "%s()\n", __func__); + pdev->msi_enabled = 0; pci_set_master(pdev); @@ -107,6 +132,8 @@ static int wil_if_pcie_disable(struct wil6210_priv *wil) { struct pci_dev *pdev = wil->pdev; + wil_dbg_misc(wil, "%s()\n", __func__); + pci_clear_master(pdev); /* disable and release IRQ */ wil6210_fini_irq(wil, pdev->irq); @@ -180,6 +207,10 @@ static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) wil->board = board; wil6210_clear_irq(wil); + + wil->platform_handle = + wil_platform_init(&pdev->dev, &wil->platform_ops); + /* FW should raise IRQ when ready */ rc = wil_if_pcie_enable(wil); if (rc) { @@ -204,6 +235,8 @@ static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) bus_disable: wil_if_pcie_disable(wil); if_free: + if (wil->platform_ops.uninit) + wil->platform_ops.uninit(wil->platform_handle); wil_if_free(wil); err_iounmap: pci_iounmap(pdev, csr); @@ -218,12 +251,17 @@ static int wil_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id) static void wil_pcie_remove(struct pci_dev *pdev) { struct wil6210_priv *wil = pci_get_drvdata(pdev); + void __iomem *csr = wil->csr; + + wil_dbg_misc(wil, "%s()\n", __func__); wil6210_debugfs_remove(wil); - wil_if_pcie_disable(wil); wil_if_remove(wil); + wil_if_pcie_disable(wil); + if (wil->platform_ops.uninit) + wil->platform_ops.uninit(wil->platform_handle); wil_if_free(wil); - pci_iounmap(pdev, wil->csr); + pci_iounmap(pdev, csr); pci_release_region(pdev, 0); pci_disable_device(pdev); } @@ -243,6 +281,8 @@ static const struct pci_device_id wil6210_pcie_ids[] = { .driver_data = (kernel_ulong_t)&wil_board_marlon }, { PCI_DEVICE(0x1ae9, 0x0310), .driver_data = (kernel_ulong_t)&wil_board_sparrow }, + { PCI_DEVICE(0x1ae9, 0x0302), /* same as above, firmware broken */ + .driver_data = (kernel_ulong_t)&wil_board_sparrow }, { /* end: all zeroes */ }, }; MODULE_DEVICE_TABLE(pci, wil6210_pcie_ids); diff --git a/drivers/net/wireless/ath/wil6210/rx_reorder.c b/drivers/net/wireless/ath/wil6210/rx_reorder.c index 180ca4793904..489cb73d139b 100644 --- a/drivers/net/wireless/ath/wil6210/rx_reorder.c +++ b/drivers/net/wireless/ath/wil6210/rx_reorder.c @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + #include "wil6210.h" #include "txrx.h" @@ -82,22 +98,25 @@ void wil_rx_reorder(struct wil6210_priv *wil, struct sk_buff *skb) int mid = wil_rxdesc_mid(d); u16 seq = wil_rxdesc_seq(d); struct wil_sta_info *sta = &wil->sta[cid]; - struct wil_tid_ampdu_rx *r = sta->tid_rx[tid]; + struct wil_tid_ampdu_rx *r; u16 hseq; int index; + unsigned long flags; wil_dbg_txrx(wil, "MID %d CID %d TID %d Seq 0x%03x\n", mid, cid, tid, seq); + spin_lock_irqsave(&sta->tid_rx_lock, flags); + + r = sta->tid_rx[tid]; if (!r) { + spin_unlock_irqrestore(&sta->tid_rx_lock, flags); wil_netif_rx_any(skb, ndev); return; } hseq = r->head_seq_num; - spin_lock(&r->reorder_lock); - /** Due to the race between WMI events, where BACK establishment * reported, and data Rx, few packets may be pass up before reorder * buffer get allocated. Catch up by pretending SSN is what we @@ -160,13 +179,14 @@ void wil_rx_reorder(struct wil6210_priv *wil, struct sk_buff *skb) wil_reorder_release(wil, r); out: - spin_unlock(&r->reorder_lock); + spin_unlock_irqrestore(&sta->tid_rx_lock, flags); } struct wil_tid_ampdu_rx *wil_tid_ampdu_rx_alloc(struct wil6210_priv *wil, int size, u16 ssn) { struct wil_tid_ampdu_rx *r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) return NULL; @@ -181,7 +201,6 @@ struct wil_tid_ampdu_rx *wil_tid_ampdu_rx_alloc(struct wil6210_priv *wil, return NULL; } - spin_lock_init(&r->reorder_lock); r->ssn = ssn; r->head_seq_num = ssn; r->buf_size = size; diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c index d3467943d39d..2936ef0c18cb 100644 --- a/drivers/net/wireless/ath/wil6210/txrx.c +++ b/drivers/net/wireless/ath/wil6210/txrx.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -52,6 +52,7 @@ static inline int wil_vring_is_full(struct vring *vring) { return wil_vring_next_tail(vring) == vring->swhead; } + /* * Available space in Tx Vring */ @@ -86,6 +87,8 @@ static int wil_vring_alloc(struct wil6210_priv *wil, struct vring *vring) size_t sz = vring->size * sizeof(vring->va[0]); uint i; + wil_dbg_misc(wil, "%s()\n", __func__); + BUILD_BUG_ON(sizeof(vring->va[0]) != 32); vring->swhead = 0; @@ -110,7 +113,8 @@ static int wil_vring_alloc(struct wil6210_priv *wil, struct vring *vring) * we can use any */ for (i = 0; i < vring->size; i++) { - volatile struct vring_tx_desc *_d = &(vring->va[i].tx); + volatile struct vring_tx_desc *_d = &vring->va[i].tx; + _d->dma.status = TX_DMA_STATUS_DU; } @@ -125,6 +129,7 @@ static void wil_txdesc_unmap(struct device *dev, struct vring_tx_desc *d, { dma_addr_t pa = wil_desc_addr(&d->dma.addr); u16 dmalen = le16_to_cpu(d->dma.length); + switch (ctx->mapped_as) { case wil_mapped_as_single: dma_unmap_single(dev, pa, dmalen, DMA_TO_DEVICE); @@ -143,6 +148,18 @@ static void wil_vring_free(struct wil6210_priv *wil, struct vring *vring, struct device *dev = wil_to_dev(wil); size_t sz = vring->size * sizeof(vring->va[0]); + if (tx) { + int vring_index = vring - wil->vring_tx; + + wil_dbg_misc(wil, "free Tx vring %d [%d] 0x%p:%pad 0x%p\n", + vring_index, vring->size, vring->va, + &vring->pa, vring->ctx); + } else { + wil_dbg_misc(wil, "free Rx vring [%d] 0x%p:%pad 0x%p\n", + vring->size, vring->va, + &vring->pa, vring->ctx); + } + while (!wil_vring_is_empty(vring)) { dma_addr_t pa; u16 dmalen; @@ -191,11 +208,12 @@ static int wil_vring_alloc_skb(struct wil6210_priv *wil, struct vring *vring, struct device *dev = wil_to_dev(wil); unsigned int sz = RX_BUF_LEN; struct vring_rx_desc dd, *d = ⅆ - volatile struct vring_rx_desc *_d = &(vring->va[i].rx); + volatile struct vring_rx_desc *_d = &vring->va[i].rx; dma_addr_t pa; /* TODO align */ struct sk_buff *skb = dev_alloc_skb(sz + headroom); + if (unlikely(!skb)) return -ENOMEM; @@ -274,9 +292,11 @@ static void wil_rx_add_radiotap_header(struct wil6210_priv *wil, */ int len = min_t(int, 8 + sizeof(phy_data), wil_rxdesc_phy_length(d)); + if (len > 8) { void *p = skb_tail_pointer(skb); void *pa = PTR_ALIGN(p, 8); + if (skb_tailroom(skb) >= len + (pa - p)) { phy_length = len - 8; memcpy(phy_data, pa, phy_length); @@ -372,13 +392,12 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil, int cid; struct wil_net_stats *stats; - BUILD_BUG_ON(sizeof(struct vring_rx_desc) > sizeof(skb->cb)); if (wil_vring_is_empty(vring)) return NULL; - _d = &(vring->va[vring->swhead].rx); + _d = &vring->va[vring->swhead].rx; if (!(_d->dma.status & RX_DMA_STATUS_DU)) { /* it is not error, we just reached end of Rx done area */ return NULL; @@ -414,7 +433,6 @@ static struct sk_buff *wil_vring_reap_rx(struct wil6210_priv *wil, cid = wil_rxdesc_cid(d); stats = &wil->sta[cid].stats; stats->last_mcs_rx = wil_rxdesc_mcs(d); - wil->stats.last_mcs_rx = stats->last_mcs_rx; /* use radiotap header only if required */ if (ndev->type == ARPHRD_IEEE80211_RADIOTAP) @@ -533,7 +551,7 @@ void wil_netif_rx_any(struct sk_buff *skb, struct net_device *ndev) [GRO_NORMAL] = "GRO_NORMAL", [GRO_DROP] = "GRO_DROP", }; - wil_dbg_txrx(wil, "Rx complete %d bytes => %s,\n", + wil_dbg_txrx(wil, "Rx complete %d bytes => %s\n", len, gro_res_str[rc]); } } @@ -574,7 +592,6 @@ void wil_rx_handle(struct wil6210_priv *wil, int *quota) else wil_netif_rx_any(skb, ndev); } - } wil_rx_refill(wil, v->size); } @@ -584,6 +601,8 @@ int wil_rx_init(struct wil6210_priv *wil) struct vring *vring = &wil->vring_rx; int rc; + wil_dbg_misc(wil, "%s()\n", __func__); + if (vring->va) { wil_err(wil, "Rx ring already allocated\n"); return -EINVAL; @@ -613,6 +632,8 @@ void wil_rx_fini(struct wil6210_priv *wil) { struct vring *vring = &wil->vring_rx; + wil_dbg_misc(wil, "%s()\n", __func__); + if (vring->va) wil_vring_free(wil, vring, 0); } @@ -647,6 +668,9 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, struct vring *vring = &wil->vring_tx[id]; struct vring_tx_data *txdata = &wil->vring_tx_data[id]; + wil_dbg_misc(wil, "%s() max_mpdu_size %d\n", __func__, + cmd.vring_cfg.tx_sw_ring.max_mpdu_size); + if (vring->va) { wil_err(wil, "Tx ring [%d] already allocated\n", id); rc = -EINVAL; @@ -696,6 +720,8 @@ void wil_vring_fini_tx(struct wil6210_priv *wil, int id) if (!vring->va) return; + wil_dbg_misc(wil, "%s() id=%d\n", __func__, id); + /* make sure NAPI won't touch this vring */ wil->vring_tx_data[id].enabled = 0; if (test_bit(wil_status_napi_en, &wil->status)) @@ -722,6 +748,7 @@ static struct vring *wil_find_tx_vring(struct wil6210_priv *wil, for (i = 0; i < ARRAY_SIZE(wil->vring2cid_tid); i++) { if (wil->vring2cid_tid[i][0] == cid) { struct vring *v = &wil->vring_tx[i]; + wil_dbg_txrx(wil, "%s(%pM) -> [%d]\n", __func__, eth->h_dest, i); if (v->va) { @@ -741,6 +768,7 @@ static void wil_set_da_for_vring(struct wil6210_priv *wil, { struct ethhdr *eth = (void *)skb->data; int cid = wil->vring2cid_tid[vring_index][0]; + memcpy(eth->h_dest, wil->sta[cid].addr, ETH_ALEN); } @@ -751,7 +779,7 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, * duplicate skb and send it to other active vrings */ static struct vring *wil_tx_bcast(struct wil6210_priv *wil, - struct sk_buff *skb) + struct sk_buff *skb) { struct vring *v, *v2; struct sk_buff *skb2; @@ -834,8 +862,8 @@ void wil_tx_desc_set_nr_frags(struct vring_tx_desc *d, int nr_frags) } static int wil_tx_desc_offload_cksum_set(struct wil6210_priv *wil, - struct vring_tx_desc *d, - struct sk_buff *skb) + struct vring_tx_desc *d, + struct sk_buff *skb) { int protocol; @@ -903,10 +931,9 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, 1 + nr_frags); return -ENOMEM; } - _d = &(vring->va[i].tx); + _d = &vring->va[i].tx; - pa = dma_map_single(dev, skb->data, - skb_headlen(skb), DMA_TO_DEVICE); + pa = dma_map_single(dev, skb->data, skb_headlen(skb), DMA_TO_DEVICE); wil_dbg_txrx(wil, "Tx skb %d bytes 0x%p -> %pad\n", skb_headlen(skb), skb->data, &pa); @@ -935,10 +962,11 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, const struct skb_frag_struct *frag = &skb_shinfo(skb)->frags[f]; int len = skb_frag_size(frag); + i = (swhead + f + 1) % vring->size; - _d = &(vring->va[i].tx); + _d = &vring->va[i].tx; pa = skb_frag_dma_map(dev, frag, 0, skb_frag_size(frag), - DMA_TO_DEVICE); + DMA_TO_DEVICE); if (unlikely(dma_mapping_error(dev, pa))) goto dma_error; vring->ctx[i].mapped_as = wil_mapped_as_page; @@ -983,7 +1011,7 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, i = (swhead + f) % vring->size; ctx = &vring->ctx[i]; - _d = &(vring->va[i].tx); + _d = &vring->va[i].tx; *d = *_d; _d->dma.status = TX_DMA_STATUS_DU; wil_txdesc_unmap(dev, d, ctx); @@ -997,7 +1025,6 @@ static int wil_tx_vring(struct wil6210_priv *wil, struct vring *vring, return -EINVAL; } - netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) { struct wil6210_priv *wil = ndev_to_wil(ndev); @@ -1025,15 +1052,15 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) pr_once_fw = false; /* find vring */ - if (is_unicast_ether_addr(eth->h_dest)) { + if (is_unicast_ether_addr(eth->h_dest)) vring = wil_find_tx_vring(wil, skb); - } else { + else vring = wil_tx_bcast(wil, skb); - } if (!vring) { wil_dbg_txrx(wil, "No Tx VRING found for %pM\n", eth->h_dest); goto drop; } + /* set up vring entry */ rc = wil_tx_vring(wil, vring, skb); diff --git a/drivers/net/wireless/ath/wil6210/txrx.h b/drivers/net/wireless/ath/wil6210/txrx.h index bc5706a4f007..de046716d2b7 100644 --- a/drivers/net/wireless/ath/wil6210/txrx.h +++ b/drivers/net/wireless/ath/wil6210/txrx.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,9 +20,9 @@ #define BUF_SW_OWNED (1) #define BUF_HW_OWNED (0) -/* size of max. Rx packet */ -#define RX_BUF_LEN (2048) -#define TX_BUF_LEN (2048) +/* size of max. Tx/Rx buffers, as supported by FW */ +#define RX_BUF_LEN (2242) +#define TX_BUF_LEN (2242) /* how many bytes to reserve for rtap header? */ #define WIL6210_RTAP_SIZE (128) @@ -237,7 +237,6 @@ struct vring_tx_mac { #define DMA_CFG_DESC_TX_0_L4_TYPE_LEN 2 #define DMA_CFG_DESC_TX_0_L4_TYPE_MSK 0xC0000000 /* L4 type: 0-UDP, 2-TCP */ - #define DMA_CFG_DESC_TX_OFFLOAD_CFG_MAC_LEN_POS 0 #define DMA_CFG_DESC_TX_OFFLOAD_CFG_MAC_LEN_LEN 7 #define DMA_CFG_DESC_TX_OFFLOAD_CFG_MAC_LEN_MSK 0x7F /* MAC hdr len */ @@ -246,7 +245,6 @@ struct vring_tx_mac { #define DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_LEN 1 #define DMA_CFG_DESC_TX_OFFLOAD_CFG_L3T_IPV4_MSK 0x80 /* 1-IPv4, 0-IPv6 */ - #define TX_DMA_STATUS_DU BIT(0) struct vring_tx_dma { @@ -347,7 +345,6 @@ struct vring_rx_mac { #define RX_DMA_ERROR_L3_ERR BIT(4) #define RX_DMA_ERROR_L4_ERR BIT(5) - /* Status field */ #define RX_DMA_STATUS_DU BIT(0) #define RX_DMA_STATUS_ERROR BIT(2) diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h index 67e9624f7111..ce6488e42091 100644 --- a/drivers/net/wireless/ath/wil6210/wil6210.h +++ b/drivers/net/wireless/ath/wil6210/wil6210.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -21,8 +21,14 @@ #include <linux/wireless.h> #include <net/cfg80211.h> #include <linux/timex.h> +#include "wil_platform.h" + +extern bool no_fw_recovery; #define WIL_NAME "wil6210" +#define WIL_FW_NAME "wil6210.fw" + +#define WIL_MAX_BUS_REQUEST_KBPS 800000 /* ~6.1Gbps */ struct wil_board { int board; @@ -47,7 +53,9 @@ static inline u32 WIL_GET_BITS(u32 x, int b0, int b1) #define WIL6210_MAX_TX_RINGS (24) /* HW limit */ #define WIL6210_MAX_CID (8) /* HW limit */ #define WIL6210_NAPI_BUDGET (16) /* arbitrary */ -#define WIL6210_ITR_TRSH (10000) /* arbitrary - about 15 IRQs/msec */ +/* Max supported by wil6210 value for interrupt threshold is 5sec. */ +#define WIL6210_ITR_TRSH_MAX (5000000) +#define WIL6210_ITR_TRSH_DEFAULT (300) /* usec */ #define WIL6210_FW_RECOVERY_RETRIES (5) /* try to recover this many times */ #define WIL6210_FW_RECOVERY_TO msecs_to_jiffies(5000) #define WIL6210_SCAN_TO msecs_to_jiffies(10000) @@ -86,22 +94,29 @@ struct RGF_ICR { /* registers - FW addresses */ #define RGF_USER_USAGE_1 (0x880004) +#define RGF_USER_USAGE_6 (0x880018) #define RGF_USER_HW_MACHINE_STATE (0x8801dc) #define HW_MACHINE_BOOT_DONE (0x3fffffd) #define RGF_USER_USER_CPU_0 (0x8801e0) + #define BIT_USER_USER_CPU_MAN_RST BIT(1) /* user_cpu_man_rst */ #define RGF_USER_MAC_CPU_0 (0x8801fc) + #define BIT_USER_MAC_CPU_MAN_RST BIT(1) /* mac_cpu_man_rst */ #define RGF_USER_USER_SCRATCH_PAD (0x8802bc) #define RGF_USER_FW_REV_ID (0x880a8c) /* chip revision */ #define RGF_USER_CLKS_CTL_0 (0x880abc) + #define BIT_USER_CLKS_CAR_AHB_SW_SEL BIT(1) /* ref clk/PLL */ #define BIT_USER_CLKS_RST_PWGD BIT(11) /* reset on "power good" */ #define RGF_USER_CLKS_CTL_SW_RST_VEC_0 (0x880b04) #define RGF_USER_CLKS_CTL_SW_RST_VEC_1 (0x880b08) #define RGF_USER_CLKS_CTL_SW_RST_VEC_2 (0x880b0c) #define RGF_USER_CLKS_CTL_SW_RST_VEC_3 (0x880b10) #define RGF_USER_CLKS_CTL_SW_RST_MASK_0 (0x880b14) + #define BIT_HPAL_PERST_FROM_PAD BIT(6) + #define BIT_CAR_PERST_RST BIT(7) #define RGF_USER_USER_ICR (0x880b4c) /* struct RGF_ICR */ #define BIT_USER_USER_ICR_SW_INT_2 BIT(18) #define RGF_USER_CLKS_CTL_EXT_SW_RST_VEC_0 (0x880c18) +#define RGF_USER_CLKS_CTL_EXT_SW_RST_VEC_1 (0x880c2c) #define RGF_DMA_EP_TX_ICR (0x881bb4) /* struct RGF_ICR */ #define BIT_DMA_EP_TX_ICR_TX_DONE BIT(0) @@ -133,6 +148,11 @@ struct RGF_ICR { #define RGF_HP_CTRL (0x88265c) #define RGF_PCIE_LOS_COUNTER_CTL (0x882dc4) +/* MAC timer, usec, for packet lifetime */ +#define RGF_MAC_MTRL_COUNTER_0 (0x886aa8) + +#define RGF_CAF_ICR (0x88946c) /* struct RGF_ICR */ + /* popular locations */ #define HOST_MBOX HOSTADDR(RGF_USER_USER_SCRATCH_PAD) #define HOST_SW_INT (HOSTADDR(RGF_USER_USER_ICR) + \ @@ -151,6 +171,7 @@ struct fw_map { u32 host; /* PCI/Host address - BAR0 + 0x880000 */ const char *name; /* for debugfs */ }; + /* array size should be in sync with actual definition in the wmi.c */ extern const struct fw_map fw_mapping[7]; @@ -300,18 +321,12 @@ struct pci_dev; * @timeout: reset timer value (in TUs). * @dialog_token: dialog token for aggregation session * @rcu_head: RCU head used for freeing this struct - * @reorder_lock: serializes access to reorder buffer, see below. * * This structure's lifetime is managed by RCU, assignments to * the array holding it must hold the aggregation mutex. * - * The @reorder_lock is used to protect the members of this - * struct, except for @timeout, @buf_size and @dialog_token, - * which are constant across the lifetime of the struct (the - * dialog token being used only for debugging). */ struct wil_tid_ampdu_rx { - spinlock_t reorder_lock; /* see above */ struct sk_buff **reorder_buf; unsigned long *reorder_time; struct timer_list session_timer; @@ -327,17 +342,6 @@ struct wil_tid_ampdu_rx { bool first_time; /* is it 1-st time this buffer used? */ }; -struct wil6210_stats { - u64 tsf; - u32 snr; - u16 last_mcs_rx; - u16 bf_mcs; /* last BF, used for Tx */ - u16 my_rx_sector; - u16 my_tx_sector; - u16 peer_rx_sector; - u16 peer_tx_sector; -}; - enum wil_sta_status { wil_sta_unused = 0, wil_sta_conn_pending = 1, @@ -371,10 +375,17 @@ struct wil_sta_info { bool data_port_open; /* can send any data, not only EAPOL */ /* Rx BACK */ struct wil_tid_ampdu_rx *tid_rx[WIL_STA_TID_NUM]; + spinlock_t tid_rx_lock; /* guarding tid_rx array */ unsigned long tid_rx_timer_expired[BITS_TO_LONGS(WIL_STA_TID_NUM)]; unsigned long tid_rx_stop_requested[BITS_TO_LONGS(WIL_STA_TID_NUM)]; }; +enum { + fw_recovery_idle = 0, + fw_recovery_pending = 1, + fw_recovery_running = 2, +}; + struct wil6210_priv { struct pci_dev *pdev; int n_msi; @@ -385,18 +396,22 @@ struct wil6210_priv { u32 hw_version; struct wil_board *board; u8 n_mids; /* number of additional MIDs as reported by FW */ - int recovery_count; /* num of FW recovery attempts in a short time */ + u32 recovery_count; /* num of FW recovery attempts in a short time */ + u32 recovery_state; /* FW recovery state machine */ unsigned long last_fw_recovery; /* jiffies of last fw recovery */ + wait_queue_head_t wq; /* for all wait_event() use */ /* profile */ u32 monitor_flags; u32 secure_pcp; /* create secure PCP? */ int sinfo_gen; + u32 itr_trsh; /* cached ISR registers */ u32 isr_misc; /* mailbox related */ struct mutex wmi_mutex; struct wil6210_mbox_ctl mbox_ctl; struct completion wmi_ready; + struct completion wmi_call; u16 wmi_seq; u16 reply_id; /**< wait for this WMI event */ void *reply_buf; @@ -430,11 +445,13 @@ struct wil6210_priv { struct mutex mutex; /* for wil6210_priv access in wil_{up|down} */ /* statistics */ - struct wil6210_stats stats; atomic_t isr_count_rx, isr_count_tx; /* debugfs */ struct dentry *debug; struct debugfs_blob_wrapper blobs[ARRAY_SIZE(fw_mapping)]; + + void *platform_handle; + struct wil_platform_ops platform_ops; }; #define wil_to_wiphy(i) (i->wdev->wiphy) @@ -444,10 +461,11 @@ struct wil6210_priv { #define wdev_to_wil(w) (struct wil6210_priv *)(wdev_priv(w)) #define wil_to_ndev(i) (wil_to_wdev(i)->netdev) #define ndev_to_wil(n) (wdev_to_wil(n->ieee80211_ptr)) +#define wil_to_pcie_dev(i) (&i->pdev->dev) -int wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...); -int wil_err(struct wil6210_priv *wil, const char *fmt, ...); -int wil_info(struct wil6210_priv *wil, const char *fmt, ...); +void wil_dbg_trace(struct wil6210_priv *wil, const char *fmt, ...); +void wil_err(struct wil6210_priv *wil, const char *fmt, ...); +void wil_info(struct wil6210_priv *wil, const char *fmt, ...); #define wil_dbg(wil, fmt, arg...) do { \ netdev_dbg(wil_to_ndev(wil), fmt, ##arg); \ wil_dbg_trace(wil, fmt, ##arg); \ @@ -458,6 +476,7 @@ int wil_info(struct wil6210_priv *wil, const char *fmt, ...); #define wil_dbg_wmi(wil, fmt, arg...) wil_dbg(wil, "DBG[ WMI]" fmt, ##arg) #define wil_dbg_misc(wil, fmt, arg...) wil_dbg(wil, "DBG[MISC]" fmt, ##arg) +#if defined(CONFIG_DYNAMIC_DEBUG) #define wil_hex_dump_txrx(prefix_str, prefix_type, rowsize, \ groupsize, buf, len, ascii) \ print_hex_dump_debug("DBG[TXRX]" prefix_str,\ @@ -469,6 +488,19 @@ int wil_info(struct wil6210_priv *wil, const char *fmt, ...); print_hex_dump_debug("DBG[ WMI]" prefix_str,\ prefix_type, rowsize, \ groupsize, buf, len, ascii) +#else /* defined(CONFIG_DYNAMIC_DEBUG) */ +static inline +void wil_hex_dump_txrx(const char *prefix_str, int prefix_type, int rowsize, + int groupsize, const void *buf, size_t len, bool ascii) +{ +} + +static inline +void wil_hex_dump_wmi(const char *prefix_str, int prefix_type, int rowsize, + int groupsize, const void *buf, size_t len, bool ascii) +{ +} +#endif /* defined(CONFIG_DYNAMIC_DEBUG) */ void wil_memcpy_fromio_32(void *dst, const volatile void __iomem *src, size_t count); @@ -482,13 +514,18 @@ void wil_if_remove(struct wil6210_priv *wil); int wil_priv_init(struct wil6210_priv *wil); void wil_priv_deinit(struct wil6210_priv *wil); int wil_reset(struct wil6210_priv *wil); +void wil_set_itr_trsh(struct wil6210_priv *wil); void wil_fw_error_recovery(struct wil6210_priv *wil); +void wil_set_recovery_state(struct wil6210_priv *wil, int state); void wil_link_on(struct wil6210_priv *wil); void wil_link_off(struct wil6210_priv *wil); int wil_up(struct wil6210_priv *wil); +int __wil_up(struct wil6210_priv *wil); int wil_down(struct wil6210_priv *wil); +int __wil_down(struct wil6210_priv *wil); void wil_mbox_ring_le2cpus(struct wil6210_mbox_ring *r); int wil_find_cid(struct wil6210_priv *wil, const u8 *mac); +void wil_set_ethtoolops(struct net_device *ndev); void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr); void __iomem *wmi_addr(struct wil6210_priv *wil, u32 ptr); @@ -519,8 +556,10 @@ int wmi_disconnect_sta(struct wil6210_priv *wil, const u8 *mac, u16 reason); void wil6210_clear_irq(struct wil6210_priv *wil); int wil6210_init_irq(struct wil6210_priv *wil, int irq); void wil6210_fini_irq(struct wil6210_priv *wil, int irq); -void wil6210_disable_irq(struct wil6210_priv *wil); -void wil6210_enable_irq(struct wil6210_priv *wil); +void wil_mask_irq(struct wil6210_priv *wil); +void wil_unmask_irq(struct wil6210_priv *wil); +void wil_disable_irq(struct wil6210_priv *wil); +void wil_enable_irq(struct wil6210_priv *wil); int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, struct cfg80211_mgmt_tx_params *params, u64 *cookie); @@ -556,4 +595,7 @@ void wil6210_unmask_irq_rx(struct wil6210_priv *wil); int wil_iftype_nl2wmi(enum nl80211_iftype type); +int wil_ioctl(struct wil6210_priv *wil, void __user *data, int cmd); +int wil_request_firmware(struct wil6210_priv *wil, const char *name); + #endif /* __WIL6210_H__ */ diff --git a/drivers/net/wireless/ath/wil6210/wil_platform.c b/drivers/net/wireless/ath/wil6210/wil_platform.c new file mode 100644 index 000000000000..8f1d78f8a74d --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/wil_platform.c @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "linux/device.h" +#include "wil_platform.h" + +#ifdef CONFIG_WIL6210_PLATFORM_MSM +#include "wil_platform_msm.h" +#endif + +/** + * wil_platform_init() - wil6210 platform module init + * + * The function must be called before all other functions in this module. + * It returns a handle which is used with the rest of the API + * + */ +void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops) +{ + void *handle = NULL; + + if (!ops) { + dev_err(dev, "Invalid parameter. Cannot init platform module\n"); + return NULL; + } + +#ifdef CONFIG_WIL6210_PLATFORM_MSM + handle = wil_platform_msm_init(dev, ops); + if (handle) + return handle; +#endif + + /* other platform specific init functions should be called here */ + + return handle; +} diff --git a/drivers/net/wireless/ath/wil6210/wil_platform.h b/drivers/net/wireless/ath/wil6210/wil_platform.h new file mode 100644 index 000000000000..158c73b049a9 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/wil_platform.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __WIL_PLATFORM_H__ +#define __WIL_PLATFORM_H__ + +struct device; + +/** + * struct wil_platform_ops - wil platform module callbacks + */ +struct wil_platform_ops { + int (*bus_request)(void *handle, uint32_t kbps /* KBytes/Sec */); + int (*suspend)(void *handle); + int (*resume)(void *handle); + void (*uninit)(void *handle); +}; + +void *wil_platform_init(struct device *dev, struct wil_platform_ops *ops); + +#endif /* __WIL_PLATFORM_H__ */ diff --git a/drivers/net/wireless/ath/wil6210/wil_platform_msm.c b/drivers/net/wireless/ath/wil6210/wil_platform_msm.c new file mode 100644 index 000000000000..b354a743240d --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/wil_platform_msm.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/msm-bus.h> + +#include "wil_platform.h" +#include "wil_platform_msm.h" + +/** + * struct wil_platform_msm - wil6210 msm platform module info + * + * @dev: device object + * @msm_bus_handle: handle for using msm_bus API + * @pdata: bus scale info retrieved from DT + */ +struct wil_platform_msm { + struct device *dev; + uint32_t msm_bus_handle; + struct msm_bus_scale_pdata *pdata; +}; + +#define KBTOB(a) (a * 1000ULL) + +/** + * wil_platform_get_pdata() - Generate bus client data from device tree + * provided by clients. + * + * dev: device object + * of_node: Device tree node to extract information from + * + * The function returns a valid pointer to the allocated bus-scale-pdata + * if the vectors were correctly read from the client's device node. + * Any error in reading or parsing the device node will return NULL + * to the caller. + */ +static struct msm_bus_scale_pdata *wil_platform_get_pdata( + struct device *dev, + struct device_node *of_node) +{ + struct msm_bus_scale_pdata *pdata; + struct msm_bus_paths *usecase; + int i, j, ret, len; + unsigned int num_usecases, num_paths, mem_size; + const uint32_t *vec_arr; + struct msm_bus_vectors *vectors; + + /* first read num_usecases and num_paths so we can calculate + * amount of memory to allocate + */ + ret = of_property_read_u32(of_node, "qcom,msm-bus,num-cases", + &num_usecases); + if (ret) { + dev_err(dev, "Error: num-usecases not found\n"); + return NULL; + } + + ret = of_property_read_u32(of_node, "qcom,msm-bus,num-paths", + &num_paths); + if (ret) { + dev_err(dev, "Error: num_paths not found\n"); + return NULL; + } + + /* pdata memory layout: + * msm_bus_scale_pdata + * msm_bus_paths[num_usecases] + * msm_bus_vectors[num_usecases][num_paths] + */ + mem_size = sizeof(struct msm_bus_scale_pdata) + + sizeof(struct msm_bus_paths) * num_usecases + + sizeof(struct msm_bus_vectors) * num_usecases * num_paths; + + pdata = kzalloc(mem_size, GFP_KERNEL); + if (!pdata) + return NULL; + + ret = of_property_read_string(of_node, "qcom,msm-bus,name", + &pdata->name); + if (ret) { + dev_err(dev, "Error: Client name not found\n"); + goto err; + } + + if (of_property_read_bool(of_node, "qcom,msm-bus,active-only")) { + pdata->active_only = 1; + } else { + dev_info(dev, "active_only flag absent.\n"); + dev_info(dev, "Using dual context by default\n"); + } + + pdata->num_usecases = num_usecases; + pdata->usecase = (struct msm_bus_paths *)(pdata + 1); + + vec_arr = of_get_property(of_node, "qcom,msm-bus,vectors-KBps", &len); + if (vec_arr == NULL) { + dev_err(dev, "Error: Vector array not found\n"); + goto err; + } + + if (len != num_usecases * num_paths * sizeof(uint32_t) * 4) { + dev_err(dev, "Error: Length-error on getting vectors\n"); + goto err; + } + + vectors = (struct msm_bus_vectors *)(pdata->usecase + num_usecases); + for (i = 0; i < num_usecases; i++) { + usecase = &pdata->usecase[i]; + usecase->num_paths = num_paths; + usecase->vectors = &vectors[i]; + + for (j = 0; j < num_paths; j++) { + int index = ((i * num_paths) + j) * 4; + + usecase->vectors[j].src = be32_to_cpu(vec_arr[index]); + usecase->vectors[j].dst = + be32_to_cpu(vec_arr[index + 1]); + usecase->vectors[j].ab = (uint64_t) + KBTOB(be32_to_cpu(vec_arr[index + 2])); + usecase->vectors[j].ib = (uint64_t) + KBTOB(be32_to_cpu(vec_arr[index + 3])); + } + } + + return pdata; + +err: + kfree(pdata); + + return NULL; +} + +/* wil_platform API (callbacks) */ + +static int wil_platform_bus_request(void *handle, + uint32_t kbps /* KBytes/Sec */) +{ + int rc, i; + struct wil_platform_msm *msm = (struct wil_platform_msm *)handle; + int vote = 0; /* vote 0 in case requested kbps cannot be satisfied */ + struct msm_bus_paths *usecase; + uint32_t usecase_kbps; + uint32_t min_kbps = ~0; + + /* find the lowest usecase that is bigger than requested kbps */ + for (i = 0; i < msm->pdata->num_usecases; i++) { + usecase = &msm->pdata->usecase[i]; + /* assume we have single path (vectors[0]). If we ever + * have multiple paths, need to define the behavior */ + usecase_kbps = div64_u64(usecase->vectors[0].ib, 1000); + if (usecase_kbps >= kbps && usecase_kbps < min_kbps) { + min_kbps = usecase_kbps; + vote = i; + } + } + + rc = msm_bus_scale_client_update_request(msm->msm_bus_handle, vote); + if (rc) + dev_err(msm->dev, "Failed msm_bus voting. kbps=%d vote=%d, rc=%d\n", + kbps, vote, rc); + else + /* TOOD: remove */ + dev_info(msm->dev, "msm_bus_scale_client_update_request succeeded. kbps=%d vote=%d\n", + kbps, vote); + + return rc; +} + +static void wil_platform_uninit(void *handle) +{ + struct wil_platform_msm *msm = (struct wil_platform_msm *)handle; + + dev_info(msm->dev, "wil_platform_uninit\n"); + + if (msm->msm_bus_handle) + msm_bus_scale_unregister_client(msm->msm_bus_handle); + + kfree(msm->pdata); + kfree(msm); +} + +static int wil_platform_msm_bus_register(struct wil_platform_msm *msm, + struct device_node *node) +{ + msm->pdata = wil_platform_get_pdata(msm->dev, node); + if (!msm->pdata) { + dev_err(msm->dev, "Failed getting DT info\n"); + return -EINVAL; + } + + msm->msm_bus_handle = msm_bus_scale_register_client(msm->pdata); + if (!msm->msm_bus_handle) { + dev_err(msm->dev, "Failed msm_bus registration\n"); + return -EINVAL; + } + + dev_info(msm->dev, "msm_bus registration succeeded! handle 0x%x\n", + msm->msm_bus_handle); + + return 0; +} + +/** + * wil_platform_msm_init() - wil6210 msm platform module init + * + * The function must be called before all other functions in this module. + * It returns a handle which is used with the rest of the API + * + */ +void *wil_platform_msm_init(struct device *dev, struct wil_platform_ops *ops) +{ + struct device_node *of_node; + struct wil_platform_msm *msm; + int rc; + + of_node = of_find_compatible_node(NULL, NULL, "qcom,wil6210"); + if (!of_node) { + /* this could mean non-msm platform */ + dev_err(dev, "DT node not found\n"); + return NULL; + } + + msm = kzalloc(sizeof(*msm), GFP_KERNEL); + if (!msm) + return NULL; + + msm->dev = dev; + + /* register with msm_bus module for scaling requests */ + rc = wil_platform_msm_bus_register(msm, of_node); + if (rc) + goto cleanup; + + memset(ops, 0, sizeof(*ops)); + ops->bus_request = wil_platform_bus_request; + ops->uninit = wil_platform_uninit; + + return (void *)msm; + +cleanup: + kfree(msm); + return NULL; +} diff --git a/drivers/net/wireless/ath/wil6210/wil_platform_msm.h b/drivers/net/wireless/ath/wil6210/wil_platform_msm.h new file mode 100644 index 000000000000..2f2229edb498 --- /dev/null +++ b/drivers/net/wireless/ath/wil6210/wil_platform_msm.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 Qualcomm Atheros, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __WIL_PLATFORM__MSM_H__ +#define __WIL_PLATFORM_MSM_H__ + +#include "wil_platform.h" + +void *wil_platform_msm_init(struct device *dev, struct wil_platform_ops *ops); + +#endif /* __WIL_PLATFORM__MSM_H__ */ diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c index 1d1d0afdd2e1..4311df982c60 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.c +++ b/drivers/net/wireless/ath/wil6210/wmi.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,6 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <linux/moduleparam.h> #include <linux/etherdevice.h> #include <linux/if_arp.h> @@ -22,6 +23,10 @@ #include "wmi.h" #include "trace.h" +static uint max_assoc_sta = 1; +module_param(max_assoc_sta, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(max_assoc_sta, " Max number of stations associated to the AP"); + /** * WMI event receiving - theory of operations * @@ -152,6 +157,7 @@ int wmi_read_hdr(struct wil6210_priv *wil, __le32 ptr, struct wil6210_mbox_hdr *hdr) { void __iomem *src = wmi_buffer(wil, ptr); + if (!src) return -EINVAL; @@ -273,6 +279,7 @@ static void wmi_evt_ready(struct wil6210_priv *wil, int id, void *d, int len) struct net_device *ndev = wil_to_ndev(wil); struct wireless_dev *wdev = wil->wdev; struct wmi_ready_event *evt = d; + wil->fw_version = le32_to_cpu(evt->sw_version); wil->n_mids = evt->numof_additional_mids; @@ -292,8 +299,9 @@ static void wmi_evt_fw_ready(struct wil6210_priv *wil, int id, void *d, { wil_dbg_wmi(wil, "WMI: got FW ready event\n"); + wil_set_recovery_state(wil, fw_recovery_idle); set_bit(wil_status_fwready, &wil->status); - /* reuse wmi_ready for the firmware ready indication */ + /* let the reset sequence continue */ complete(&wil->wmi_ready); } @@ -346,11 +354,11 @@ static void wmi_evt_rx_mgmt(struct wil6210_priv *wil, int id, void *d, int len) rx_mgmt_frame->bssid); cfg80211_put_bss(wiphy, bss); } else { - wil_err(wil, "cfg80211_inform_bss() failed\n"); + wil_err(wil, "cfg80211_inform_bss_frame() failed\n"); } } else { cfg80211_rx_mgmt(wil->wdev, freq, signal, - (void *)rx_mgmt_frame, d_len, 0, GFP_KERNEL); + (void *)rx_mgmt_frame, d_len, 0); } } @@ -482,33 +490,6 @@ static void wmi_evt_disconnect(struct wil6210_priv *wil, int id, mutex_unlock(&wil->mutex); } -static void wmi_evt_notify(struct wil6210_priv *wil, int id, void *d, int len) -{ - struct wmi_notify_req_done_event *evt = d; - - if (len < sizeof(*evt)) { - wil_err(wil, "Short NOTIFY event\n"); - return; - } - - wil->stats.tsf = le64_to_cpu(evt->tsf); - wil->stats.snr = le32_to_cpu(evt->snr_val); - wil->stats.bf_mcs = le16_to_cpu(evt->bf_mcs); - wil->stats.my_rx_sector = le16_to_cpu(evt->my_rx_sector); - wil->stats.my_tx_sector = le16_to_cpu(evt->my_tx_sector); - wil->stats.peer_rx_sector = le16_to_cpu(evt->other_rx_sector); - wil->stats.peer_tx_sector = le16_to_cpu(evt->other_tx_sector); - wil_dbg_wmi(wil, "Link status, MCS %d TSF 0x%016llx\n" - "BF status 0x%08x SNR 0x%08x SQI %d%%\n" - "Tx Tpt %d goodput %d Rx goodput %d\n" - "Sectors(rx:tx) my %d:%d peer %d:%d\n", - wil->stats.bf_mcs, wil->stats.tsf, evt->status, - wil->stats.snr, evt->sqi, le32_to_cpu(evt->tx_tpt), - le32_to_cpu(evt->tx_goodput), le32_to_cpu(evt->rx_goodput), - wil->stats.my_rx_sector, wil->stats.my_tx_sector, - wil->stats.peer_rx_sector, wil->stats.peer_tx_sector); -} - /* * Firmware reports EAPOL frame using WME event. * Reconstruct Ethernet frame and deliver it via normal Rx @@ -617,27 +598,40 @@ static void wmi_evt_ba_status(struct wil6210_priv *wil, int id, void *d, return; } + mutex_lock(&wil->mutex); + cid = wil->vring2cid_tid[evt->ringid][0]; if (cid >= WIL6210_MAX_CID) { wil_err(wil, "invalid CID %d for vring %d\n", cid, evt->ringid); - return; + goto out; } sta = &wil->sta[cid]; if (sta->status == wil_sta_unused) { wil_err(wil, "CID %d unused\n", cid); - return; + goto out; } wil_dbg_wmi(wil, "BACK for CID %d %pM\n", cid, sta->addr); for (i = 0; i < WIL_STA_TID_NUM; i++) { - struct wil_tid_ampdu_rx *r = sta->tid_rx[i]; + struct wil_tid_ampdu_rx *r; + unsigned long flags; + + spin_lock_irqsave(&sta->tid_rx_lock, flags); + + r = sta->tid_rx[i]; sta->tid_rx[i] = NULL; wil_tid_ampdu_rx_free(wil, r); + + spin_unlock_irqrestore(&sta->tid_rx_lock, flags); + if ((evt->status == WMI_BA_AGREED) && evt->agg_wsize) sta->tid_rx[i] = wil_tid_ampdu_rx_alloc(wil, evt->agg_wsize, 0); } + +out: + mutex_unlock(&wil->mutex); } static const struct { @@ -651,7 +645,6 @@ static const struct { {WMI_SCAN_COMPLETE_EVENTID, wmi_evt_scan_complete}, {WMI_CONNECT_EVENTID, wmi_evt_connect}, {WMI_DISCONNECT_EVENTID, wmi_evt_disconnect}, - {WMI_NOTIFY_REQ_DONE_EVENTID, wmi_evt_notify}, {WMI_EAPOL_RX_EVENTID, wmi_evt_eapol_rx}, {WMI_DATA_PORT_OPEN_EVENTID, wmi_evt_linkup}, {WMI_WBE_LINKDOWN_EVENTID, wmi_evt_linkdown}, @@ -676,7 +669,7 @@ void wmi_recv_cmd(struct wil6210_priv *wil) unsigned n; if (!test_bit(wil_status_reset_done, &wil->status)) { - wil_err(wil, "Reset not completed\n"); + wil_err(wil, "Reset in progress. Cannot handle WMI event\n"); return; } @@ -731,6 +724,7 @@ void wmi_recv_cmd(struct wil6210_priv *wil) struct wil6210_mbox_hdr_wmi *wmi = &evt->event.wmi; u16 id = le16_to_cpu(wmi->id); u32 tstamp = le32_to_cpu(wmi->timestamp); + wil_dbg_wmi(wil, "WMI event 0x%04x MID %d @%d msec\n", id, wmi->mid, tstamp); trace_wil6210_wmi_event(wmi, &wmi[1], @@ -771,8 +765,8 @@ int wmi_call(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len, wil->reply_id = reply_id; wil->reply_buf = reply; wil->reply_size = reply_size; - remain = wait_for_completion_timeout(&wil->wmi_ready, - msecs_to_jiffies(to_msec)); + remain = wait_for_completion_timeout(&wil->wmi_call, + msecs_to_jiffies(to_msec)); if (0 == remain) { wil_err(wil, "wmi_call(0x%04x->0x%04x) timeout %d msec\n", cmdid, reply_id, to_msec); @@ -822,7 +816,7 @@ int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, u8 chan) .network_type = wmi_nettype, .disable_sec_offload = 1, .channel = chan - 1, - .pcp_max_assoc_sta = WIL6210_MAX_CID, + .pcp_max_assoc_sta = max_assoc_sta, }; struct { struct wil6210_mbox_hdr_wmi wmi; @@ -832,6 +826,14 @@ int wmi_pcp_start(struct wil6210_priv *wil, int bi, u8 wmi_nettype, u8 chan) if (!wil->secure_pcp) cmd.disable_sec = 1; + if ((cmd.pcp_max_assoc_sta > WIL6210_MAX_CID) || + (cmd.pcp_max_assoc_sta <= 0)) { + wil_info(wil, + "Requested connection limit %u, valid values are 1 - %d. Setting to %d\n", + max_assoc_sta, WIL6210_MAX_CID, WIL6210_MAX_CID); + cmd.pcp_max_assoc_sta = WIL6210_MAX_CID; + } + /* * Processing time may be huge, in case of secure AP it takes about * 3500ms for FW to start AP @@ -968,8 +970,11 @@ int wmi_set_ie(struct wil6210_priv *wil, u8 type, u16 ie_len, const void *ie) int rc; u16 len = sizeof(struct wmi_set_appie_cmd) + ie_len; struct wmi_set_appie_cmd *cmd = kzalloc(len, GFP_KERNEL); + if (!cmd) return -ENOMEM; + if (!ie) + ie_len = 0; cmd->mgmt_frm_type = type; /* BUG: FW API define ieLen as u8. Will fix FW */ @@ -1143,6 +1148,9 @@ static void wmi_event_handle(struct wil6210_priv *wil, struct wil6210_mbox_hdr_wmi *wmi = (void *)(&hdr[1]); void *evt_data = (void *)(&wmi[1]); u16 id = le16_to_cpu(wmi->id); + + wil_dbg_wmi(wil, "Handle WMI 0x%04x (reply_id 0x%04x)\n", + id, wil->reply_id); /* check if someone waits for this event */ if (wil->reply_id && wil->reply_id == id) { if (wil->reply_buf) { @@ -1153,7 +1161,7 @@ static void wmi_event_handle(struct wil6210_priv *wil, len - sizeof(*wmi)); } wil_dbg_wmi(wil, "Complete WMI 0x%04x\n", id); - complete(&wil->wmi_ready); + complete(&wil->wmi_call); return; } /* unsolicited event */ @@ -1199,9 +1207,11 @@ void wmi_event_worker(struct work_struct *work) struct pending_wmi_event *evt; struct list_head *lh; + wil_dbg_wmi(wil, "Start %s\n", __func__); while ((lh = next_wmi_ev(wil)) != NULL) { evt = list_entry(lh, struct pending_wmi_event, list); wmi_event_handle(wil, &evt->event.hdr); kfree(evt); } + wil_dbg_wmi(wil, "Finished %s\n", __func__); } diff --git a/drivers/net/wireless/ath/wil6210/wmi.h b/drivers/net/wireless/ath/wil6210/wmi.h index 17334c852866..27b97432d1c2 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.h +++ b/drivers/net/wireless/ath/wil6210/wmi.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Qualcomm Atheros, Inc. + * Copyright (c) 2012-2014 Qualcomm Atheros, Inc. * Copyright (c) 2006-2012 Wilocity . * * Permission to use, copy, modify, and/or distribute this software for any @@ -179,7 +179,6 @@ enum wmi_crypto_type { WMI_CRYPT_AES_GCMP = 0x20, }; - enum wmi_connect_ctrl_flag_bits { WMI_CONNECT_ASSOC_POLICY_USER = 0x0001, WMI_CONNECT_SEND_REASSOC = 0x0002, @@ -219,7 +218,6 @@ struct wmi_disconnect_sta_cmd { __le16 disconnect_reason; } __packed; - /* * WMI_SET_PMK_CMDID */ @@ -234,7 +232,6 @@ struct wmi_set_pmk_cmd { u8 pmk[WMI_PMK_LEN]; } __packed; - /* * WMI_SET_PASSPHRASE_CMDID */ @@ -273,7 +270,6 @@ struct wmi_delete_cipher_key_cmd { u8 mac[WMI_MAC_LEN]; } __packed; - /* * WMI_START_SCAN_CMDID * @@ -325,7 +321,6 @@ struct wmi_probed_ssid_cmd { u8 ssid[WMI_MAX_SSID_LEN]; } __packed; - /* * WMI_SET_APPIE_CMDID * Add Application specified IE to a management frame @@ -351,7 +346,6 @@ struct wmi_set_appie_cmd { u8 ie_info[0]; } __packed; - /* * WMI_PXMT_RANGE_CFG_CMDID */ @@ -380,7 +374,6 @@ struct wmi_rf_mgmt_cmd { __le32 rf_mgmt_type; } __packed; - /* * WMI_RF_RX_TEST_CMDID */ @@ -426,7 +419,6 @@ struct wmi_bcon_ctrl_cmd { u8 disable_sec; } __packed; - /******* P2P ***********/ /* @@ -797,7 +789,6 @@ struct wmi_temp_sense_cmd { __le32 measure_marlon_r_en; } __packed; - /* * WMI Events */ @@ -887,7 +878,6 @@ enum wmi_event_id { * Events data structures */ - enum wmi_fw_status { WMI_FW_STATUS_SUCCESS, WMI_FW_STATUS_FAILURE, @@ -980,7 +970,7 @@ struct wmi_ready_event { * WMI_NOTIFY_REQ_DONE_EVENTID */ struct wmi_notify_req_done_event { - __le32 status; + __le32 status; /* beamforming status, 0: fail; 1: OK; 2: retrying */ __le64 tsf; __le32 snr_val; __le32 tx_tpt; @@ -1038,8 +1028,8 @@ struct wmi_disconnect_event { __le16 protocol_reason_status; /* reason code, see 802.11 spec. */ u8 bssid[WMI_MAC_LEN]; /* set if known */ u8 disconnect_reason; /* see wmi_disconnect_reason */ - u8 assoc_resp_len; /* not in use */ - u8 assoc_info[0]; /* not in use */ + u8 assoc_resp_len; /* not used */ + u8 assoc_info[0]; /* not used */ } __packed; /* @@ -1081,7 +1071,6 @@ struct wmi_delba_event { __le16 reason; } __packed; - /* * WMI_VRING_CFG_DONE_EVENTID */ @@ -1147,7 +1136,6 @@ struct wmi_data_port_open_event { u8 reserved[3]; } __packed; - /* * WMI_GET_PCP_CHANNEL_EVENTID */ @@ -1156,7 +1144,6 @@ struct wmi_get_pcp_channel_event { u8 reserved[3]; } __packed; - /* * WMI_PORT_ALLOCATED_EVENTID */ @@ -1260,7 +1247,6 @@ struct wmi_rx_mgmt_info { u8 channel; /* From Radio MNGR */ } __packed; - /* * WMI_TX_MGMT_PACKET_EVENTID */ |