diff mbox

[5/6] carl9170: firmware parser and debugfs code

Message ID 26406f83a79441eeb347f00f340229b06ad39317.1283729571.git.chunkeey@googlemail.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Christian Lamparter Sept. 5, 2010, 11:09 p.m. UTC
None
diff mbox

Patch

diff --git a/drivers/net/wireless/ath/carl9170/debug.c b/drivers/net/wireless/ath/carl9170/debug.c
new file mode 100644
index 0000000..3e9b0e8
--- /dev/null
+++ b/drivers/net/wireless/ath/carl9170/debug.c
@@ -0,0 +1,908 @@ 
+/*
+ * Atheros CARL9170 driver
+ *
+ * debug(fs) probing
+ *
+ * Copyright 2008, Johannes Berg <johannes@sipsolutions.net>
+ * Copyright 2009, 2010, Christian Lamparter <chunkeey@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, see
+ * http://www.gnu.org/licenses/.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *    Copyright (c) 2008-2009 Atheros Communications, 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/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/vmalloc.h>
+#include "carl9170.h"
+#include "cmd.h"
+
+#define ADD(buf, off, max, fmt, args...)				\
+	off += snprintf(&buf[off], max - off, fmt, ##args);
+
+static int carl9170_debugfs_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+struct carl9170_debugfs_fops {
+	unsigned int read_bufsize;
+	mode_t attr;
+	char *(*read)(struct ar9170 *ar, char *buf, size_t bufsize,
+		      ssize_t *len);
+	ssize_t (*write)(struct ar9170 *aru, const char *buf, size_t size);
+	const struct file_operations fops;
+
+	enum carl9170_device_state req_dev_state;
+};
+
+static ssize_t carl9170_debugfs_read(struct file *file, char __user *userbuf,
+				     size_t count, loff_t *ppos)
+{
+	struct carl9170_debugfs_fops *dfops;
+	struct ar9170 *ar;
+	char *buf = NULL, *res_buf = NULL;
+	ssize_t ret = 0;
+	int err = 0;
+
+	if (!count)
+		return 0;
+
+	ar = file->private_data;
+
+	if (!ar)
+		return -ENODEV;
+	dfops = container_of(file->f_op, struct carl9170_debugfs_fops, fops);
+
+	if (!dfops->read)
+		return -ENOSYS;
+
+	if (dfops->read_bufsize) {
+		buf = vmalloc(dfops->read_bufsize);
+		if (!buf)
+			return -ENOMEM;
+	}
+
+	mutex_lock(&ar->mutex);
+	if (!CHK_DEV_STATE(ar, dfops->req_dev_state)) {
+		err = -ENODEV;
+		res_buf = buf;
+		goto out_free;
+	}
+
+	res_buf = dfops->read(ar, buf, dfops->read_bufsize, &ret);
+
+	if (ret > 0)
+		err = simple_read_from_buffer(userbuf, count, ppos,
+					      res_buf, ret);
+	else
+		err = ret;
+
+	WARN_ON_ONCE(dfops->read_bufsize && (res_buf != buf));
+
+out_free:
+	vfree(res_buf);
+	mutex_unlock(&ar->mutex);
+	return err;
+}
+
+static ssize_t carl9170_debugfs_write(struct file *file,
+	const char __user *userbuf, size_t count, loff_t *ppos)
+{
+	struct carl9170_debugfs_fops *dfops;
+	struct ar9170 *ar;
+	char *buf = NULL;
+	int err = 0;
+
+	if (!count)
+		return 0;
+
+	if (count > PAGE_SIZE)
+		return -E2BIG;
+
+	ar = file->private_data;
+
+	if (!ar)
+		return -ENODEV;
+	dfops = container_of(file->f_op, struct carl9170_debugfs_fops, fops);
+
+	if (!dfops->write)
+		return -ENOSYS;
+
+	buf = vmalloc(count);
+	if (!buf)
+		return -ENOMEM;
+
+	if (copy_from_user(buf, userbuf, count)) {
+		err = -EFAULT;
+		goto out_free;
+	}
+
+	if (mutex_trylock(&ar->mutex) == 0) {
+		err = -EAGAIN;
+		goto out_free;
+	}
+
+	if (!CHK_DEV_STATE(ar, dfops->req_dev_state)) {
+		err = -ENODEV;
+		goto out_unlock;
+	}
+
+	err = dfops->write(ar, buf, count);
+	if (err)
+		goto out_unlock;
+
+out_unlock:
+	mutex_unlock(&ar->mutex);
+
+out_free:
+	vfree(buf);
+	return err;
+}
+
+#define __DEBUGFS_DECLARE_FILE(name, _read, _write, _read_bufsize,	\
+			       _attr, _dstate)				\
+static const struct carl9170_debugfs_fops carl_debugfs_##name ##_ops = {\
+	.read_bufsize = _read_bufsize,					\
+	.read = _read,							\
+	.write = _write,						\
+	.attr = _attr,							\
+	.req_dev_state = _dstate,					\
+	.fops = {							\
+		.open	= carl9170_debugfs_open,			\
+		.read	= carl9170_debugfs_read,			\
+		.write	= carl9170_debugfs_write,			\
+		.owner	= THIS_MODULE					\
+	},								\
+}
+
+#define DEBUGFS_DECLARE_FILE(name, _read, _write, _read_bufsize, _attr)	\
+	__DEBUGFS_DECLARE_FILE(name, _read, _write, _read_bufsize,	\
+			       _attr, CARL9170_STARTED)			\
+
+#define DEBUGFS_DECLARE_RO_FILE(name, _read_bufsize)			\
+	DEBUGFS_DECLARE_FILE(name, carl9170_debugfs_##name ##_read,	\
+			     NULL, _read_bufsize, S_IRUSR)
+
+#define DEBUGFS_DECLARE_WO_FILE(name)					\
+	DEBUGFS_DECLARE_FILE(name, NULL, carl9170_debugfs_##name ##_write,\
+			     0, S_IWUSR)
+
+#define DEBUGFS_DECLARE_RW_FILE(name, _read_bufsize)			\
+	DEBUGFS_DECLARE_FILE(name, carl9170_debugfs_##name ##_read,	\
+			     carl9170_debugfs_##name ##_write,		\
+			     _read_bufsize, S_IRUSR | S_IWUSR)
+
+#define __DEBUGFS_DECLARE_RW_FILE(name, _read_bufsize, _dstate)		\
+	__DEBUGFS_DECLARE_FILE(name, carl9170_debugfs_##name ##_read,	\
+			     carl9170_debugfs_##name ##_write,		\
+			     _read_bufsize, S_IRUSR | S_IWUSR, _dstate)
+
+#define DEBUGFS_READONLY_FILE(name, _read_bufsize, fmt, value...)	\
+static char *carl9170_debugfs_ ##name ## _read(struct ar9170 *ar,	\
+					     char *buf, size_t buf_size,\
+					     ssize_t *len)		\
+{									\
+	ADD(buf, *len, buf_size, fmt "\n", ##value);			\
+	return buf;							\
+}									\
+DEBUGFS_DECLARE_RO_FILE(name, _read_bufsize)
+
+static char *carl9170_debugfs_mem_usage_read(struct ar9170 *ar, char *buf,
+					     size_t bufsize, ssize_t *len)
+{
+	ADD(buf, *len, bufsize, "jar: [");
+
+	spin_lock_bh(&ar->mem_lock);
+
+	*len += bitmap_scnprintf(&buf[*len], bufsize - *len,
+				  ar->mem_bitmap, ar->fw.mem_blocks);
+
+	ADD(buf, *len, bufsize, "]\n");
+
+	ADD(buf, *len, bufsize, "cookies: used:%3d / total:%3d, allocs:%d\n",
+	    bitmap_weight(ar->mem_bitmap, ar->fw.mem_blocks),
+	    ar->fw.mem_blocks, atomic_read(&ar->mem_allocs));
+
+	ADD(buf, *len, bufsize, "memory: free:%3d (%3d KiB) / total:%3d KiB)\n",
+	    atomic_read(&ar->mem_free_blocks),
+	    (atomic_read(&ar->mem_free_blocks) * ar->fw.mem_block_size) / 1024,
+	    (ar->fw.mem_blocks * ar->fw.mem_block_size) / 1024);
+
+	spin_unlock_bh(&ar->mem_lock);
+
+	return buf;
+}
+DEBUGFS_DECLARE_RO_FILE(mem_usage, 512);
+
+static char *carl9170_debugfs_qos_stat_read(struct ar9170 *ar, char *buf,
+					    size_t bufsize, ssize_t *len)
+{
+	ADD(buf, *len, bufsize, "%s QoS AC\n", modparam_noht ? "Hardware" :
+	    "Software");
+
+	ADD(buf, *len, bufsize, "[     VO            VI       "
+				 "     BE            BK      ]\n");
+
+	spin_lock_bh(&ar->tx_stats_lock);
+	ADD(buf, *len, bufsize, "[length/limit  length/limit  "
+				 "length/limit  length/limit ]\n"
+				"[   %3d/%3d       %3d/%3d    "
+				 "   %3d/%3d       %3d/%3d   ]\n\n",
+	    ar->tx_stats[0].len, ar->tx_stats[0].limit,
+	    ar->tx_stats[1].len, ar->tx_stats[1].limit,
+	    ar->tx_stats[2].len, ar->tx_stats[2].limit,
+	    ar->tx_stats[3].len, ar->tx_stats[3].limit);
+
+	ADD(buf, *len, bufsize, "[    total         total     "
+				 "    total         total    ]\n"
+				"[%10d    %10d    %10d    %10d   ]\n\n",
+	    ar->tx_stats[0].count, ar->tx_stats[1].count,
+	    ar->tx_stats[2].count, ar->tx_stats[3].count);
+
+	spin_unlock_bh(&ar->tx_stats_lock);
+
+	ADD(buf, *len, bufsize, "[  pend/waittx   pend/waittx "
+				 "  pend/waittx   pend/waittx]\n"
+				"[   %3d/%3d       %3d/%3d    "
+				 "   %3d/%3d       %3d/%3d   ]\n\n",
+	    skb_queue_len(&ar->tx_pending[0]),
+	    skb_queue_len(&ar->tx_status[0]),
+	    skb_queue_len(&ar->tx_pending[1]),
+	    skb_queue_len(&ar->tx_status[1]),
+	    skb_queue_len(&ar->tx_pending[2]),
+	    skb_queue_len(&ar->tx_status[2]),
+	    skb_queue_len(&ar->tx_pending[3]),
+	    skb_queue_len(&ar->tx_status[3]));
+
+	return buf;
+}
+DEBUGFS_DECLARE_RO_FILE(qos_stat, 512);
+
+static void carl9170_debugfs_format_frame(struct ar9170 *ar,
+	struct sk_buff *skb, const char *prefix, char *buf,
+	ssize_t *off, ssize_t bufsize)
+{
+	struct _carl9170_tx_superframe *txc = (void *) skb->data;
+	struct ieee80211_tx_info *txinfo = IEEE80211_SKB_CB(skb);
+	struct carl9170_tx_info *arinfo = (void *) txinfo->rate_driver_data;
+	struct ieee80211_hdr *hdr = (void *) txc->frame_data;
+
+	ADD(buf, *off, bufsize, "%s %p, c:%2x, DA:%pM, sq:%4d, mc:%.4x, "
+	    "pc:%.8x, to:%d ms\n", prefix, skb, txc->s.cookie,
+	    ieee80211_get_DA(hdr), get_seq_h(hdr),
+	    le16_to_cpu(txc->f.mac_control), le32_to_cpu(txc->f.phy_control),
+	    jiffies_to_msecs(jiffies - arinfo->timeout));
+}
+
+
+static char *carl9170_debugfs_ampdu_state_read(struct ar9170 *ar, char *buf,
+					       size_t bufsize, ssize_t *len)
+{
+	struct carl9170_sta_tid *iter;
+	struct sk_buff *skb;
+	int cnt = 0, fc;
+	int offset;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(iter, &ar->tx_ampdu_list, list) {
+
+		spin_lock_bh(&iter->lock);
+		ADD(buf, *len, bufsize, "Entry: #%2d TID:%1d, BSN:%4d, "
+		    "SNX:%4d, HSN:%4d, BAW:%2d, state:%1d, toggles:%d\n",
+		    cnt, iter->tid, iter->bsn, iter->snx, iter->hsn,
+		    iter->max, iter->state, iter->counter);
+
+		ADD(buf, *len, bufsize, "\tWindow:  [");
+
+		*len += bitmap_scnprintf(&buf[*len], bufsize - *len,
+			iter->bitmap, CARL9170_BAW_BITS);
+
+#define BM_STR_OFF(offset)					\
+	((CARL9170_BAW_BITS - (offset) - 1) / 4 +		\
+	 (CARL9170_BAW_BITS - (offset) - 1) / 32 + 1)
+
+		ADD(buf, *len, bufsize, ",W]\n");
+
+		offset = BM_STR_OFF(0);
+		ADD(buf, *len, bufsize, "\tBase Seq: %*s\n", offset, "T");
+
+		offset = BM_STR_OFF(SEQ_DIFF(iter->snx, iter->bsn));
+		ADD(buf, *len, bufsize, "\tNext Seq: %*s\n", offset, "W");
+
+		offset = BM_STR_OFF(((int)iter->hsn - (int)iter->bsn) %
+				     CARL9170_BAW_BITS);
+		ADD(buf, *len, bufsize, "\tLast Seq: %*s\n", offset, "N");
+
+		ADD(buf, *len, bufsize, "\tPre-Aggregation reorder buffer: "
+		    " currently queued:%d\n", skb_queue_len(&iter->queue));
+
+		fc = 0;
+		skb_queue_walk(&iter->queue, skb) {
+			char prefix[32];
+
+			snprintf(prefix, sizeof(prefix), "\t\t%3d :", fc);
+			carl9170_debugfs_format_frame(ar, skb, prefix, buf,
+						      len, bufsize);
+
+			fc++;
+		}
+		spin_unlock_bh(&iter->lock);
+		cnt++;
+	}
+	rcu_read_unlock();
+
+	return buf;
+}
+DEBUGFS_DECLARE_RO_FILE(ampdu_state, 8000);
+
+static void carl9170_debugfs_queue_dump(struct ar9170 *ar, char *buf,
+	ssize_t *len, size_t bufsize, struct sk_buff_head *queue)
+{
+	struct sk_buff *skb;
+	char prefix[16];
+	int fc = 0;
+
+	spin_lock_bh(&queue->lock);
+	skb_queue_walk(queue, skb) {
+		snprintf(prefix, sizeof(prefix), "%3d :", fc);
+		carl9170_debugfs_format_frame(ar, skb, prefix, buf,
+					      len, bufsize);
+		fc++;
+	}
+	spin_unlock_bh(&queue->lock);
+}
+
+#define DEBUGFS_QUEUE_DUMP(q, qi)					\
+static char *carl9170_debugfs_##q ##_##qi ##_read(struct ar9170 *ar,	\
+	char *buf, size_t bufsize, ssize_t *len)			\
+{									\
+	carl9170_debugfs_queue_dump(ar, buf, len, bufsize, &ar->q[qi]);	\
+	return buf;							\
+}									\
+DEBUGFS_DECLARE_RO_FILE(q##_##qi, 8000);
+
+static char *carl9170_debugfs_sta_psm_read(struct ar9170 *ar, char *buf,
+					   size_t bufsize, ssize_t *len)
+{
+	ADD(buf, *len, bufsize, "psm state: %s\n", (ar->ps.off_override ?
+	    "FORCE CAM" : (ar->ps.state ? "PSM" : "CAM")));
+
+	ADD(buf, *len, bufsize, "sleep duration: %d ms.\n", ar->ps.sleep_ms);
+	ADD(buf, *len, bufsize, "last power-state transition: %d ms ago.\n",
+	    jiffies_to_msecs(jiffies - ar->ps.last_action));
+	ADD(buf, *len, bufsize, "last CAM->PSM transition: %d ms ago.\n",
+	    jiffies_to_msecs(jiffies - ar->ps.last_slept));
+
+	return buf;
+}
+DEBUGFS_DECLARE_RO_FILE(sta_psm, 160);
+
+static char *carl9170_debugfs_tx_stuck_read(struct ar9170 *ar, char *buf,
+					    size_t bufsize, ssize_t *len)
+{
+	int i;
+
+	for (i = 0; i < ar->hw->queues; i++) {
+		ADD(buf, *len, bufsize, "TX queue [%d]: %10d max:%10d ms.\n",
+		    i, ieee80211_queue_stopped(ar->hw, i) ?
+		    jiffies_to_msecs(jiffies - ar->queue_stop_timeout[i]) : 0,
+		    jiffies_to_msecs(ar->max_queue_stop_timeout[i]));
+
+		ar->max_queue_stop_timeout[i] = 0;
+	}
+
+	return buf;
+}
+DEBUGFS_DECLARE_RO_FILE(tx_stuck, 180);
+
+static char *carl9170_debugfs_phy_noise_read(struct ar9170 *ar, char *buf,
+					     size_t bufsize, ssize_t *len)
+{
+	int err;
+
+	err = carl9170_get_noisefloor(ar);
+	if (err) {
+		*len = err;
+		return buf;
+	}
+
+	ADD(buf, *len, bufsize, "Chain 1: %10d dBm, ext. chan.:%10d dBm\n",
+	    ar->noise[1], ar->noise[4]);
+	ADD(buf, *len, bufsize, "Chain 2: %10d dBm, ext. chan.:%10d dBm\n",
+	    ar->noise[2], ar->noise[5]);
+	ADD(buf, *len, bufsize, "Combined %10d dBm, ext. chan.:%10d dBm\n",
+	    ar->noise[0], ar->noise[3]);
+
+	return buf;
+}
+DEBUGFS_DECLARE_RO_FILE(phy_noise, 180);
+
+static char *carl9170_debugfs_vif_dump_read(struct ar9170 *ar, char *buf,
+					    size_t bufsize, ssize_t *len)
+{
+	struct carl9170_vif_info *iter;
+	int i = 0;
+
+	ADD(buf, *len, bufsize, "registered VIFs:%d \\ %d\n",
+	    ar->vifs, ar->fw.vif_num);
+
+	ADD(buf, *len, bufsize, "VIF bitmap: [");
+
+	*len += bitmap_scnprintf(&buf[*len], bufsize - *len,
+				 &ar->vif_bitmap, ar->fw.vif_num);
+
+	ADD(buf, *len, bufsize, "]\n");
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(iter, &ar->vif_list, list) {
+		struct ieee80211_vif *vif = carl9170_get_vif(iter);
+		ADD(buf, *len, bufsize, "\t%d = [%s VIF, id:%d, type:%x "
+		    " mac:%pM %s]\n", i, (carl9170_get_main_vif(ar) == vif ?
+		    "Master" : " Slave"), iter->id, vif->type, vif->addr,
+		    iter->enable_beacon ? "beaconing " : "");
+		i++;
+	}
+	rcu_read_unlock();
+
+	return buf;
+}
+DEBUGFS_DECLARE_RO_FILE(vif_dump, 8000);
+
+#define UPDATE_COUNTER(ar, name)	({				\
+	u32 __tmp[ARRAY_SIZE(name##_regs)];				\
+	unsigned int __i, __err = -ENODEV;				\
+									\
+	for (__i = 0; __i < ARRAY_SIZE(name##_regs); __i++) {		\
+		__tmp[__i] = name##_regs[__i].reg;			\
+		ar->debug.stats.name##_counter[__i] = 0;		\
+	}								\
+									\
+	if (IS_STARTED(ar))						\
+		__err = carl9170_read_mreg(ar, ARRAY_SIZE(name##_regs),	\
+			__tmp, ar->debug.stats.name##_counter);		\
+	(__err); })
+
+#define TALLY_SUM_UP(ar, name)	do {					\
+	unsigned int __i;						\
+									\
+	for (__i = 0; __i < ARRAY_SIZE(name##_regs); __i++) {		\
+		ar->debug.stats.name##_sum[__i] +=			\
+			ar->debug.stats.name##_counter[__i];		\
+	}								\
+} while (0)
+
+#define DEBUGFS_HW_TALLY_FILE(name, f)					\
+static char *carl9170_debugfs_##name ## _read(struct ar9170 *ar,	\
+	 char *dum, size_t bufsize, ssize_t *ret)			\
+{									\
+	char *buf;							\
+	int i, max_len, err;						\
+									\
+	max_len = ARRAY_SIZE(name##_regs) * 80;				\
+	buf = vmalloc(max_len);						\
+	if (!buf)							\
+		return NULL;						\
+									\
+	err = UPDATE_COUNTER(ar, name);					\
+	if (err) {							\
+		*ret = err;						\
+		return buf;						\
+	}								\
+									\
+	TALLY_SUM_UP(ar, name);						\
+									\
+	for (i = 0; i < ARRAY_SIZE(name##_regs); i++) {			\
+		ADD(buf, *ret, max_len, "%22s = %" f "[+%" f "]\n",	\
+		    name##_regs[i].nreg, ar->debug.stats.name ##_sum[i],\
+		    ar->debug.stats.name ##_counter[i]);		\
+	}								\
+									\
+	return buf;							\
+}									\
+DEBUGFS_DECLARE_RO_FILE(name, 0);
+
+#define DEBUGFS_HW_REG_FILE(name, f)					\
+static char *carl9170_debugfs_##name ## _read(struct ar9170 *ar,	\
+	char *dum, size_t bufsize, ssize_t *ret)			\
+{									\
+	char *buf;							\
+	int i, max_len, err;						\
+									\
+	max_len = ARRAY_SIZE(name##_regs) * 80;				\
+	buf = vmalloc(max_len);						\
+	if (!buf)							\
+		return NULL;						\
+									\
+	err = UPDATE_COUNTER(ar, name);					\
+	if (err) {							\
+		*ret = err;						\
+		return buf;						\
+	}								\
+									\
+	for (i = 0; i < ARRAY_SIZE(name##_regs); i++) {			\
+		ADD(buf, *ret, max_len, "%22s = %" f "\n",		\
+		    name##_regs[i].nreg,				\
+		    ar->debug.stats.name##_counter[i]);			\
+	}								\
+									\
+	return buf;							\
+}									\
+DEBUGFS_DECLARE_RO_FILE(name, 0);
+
+static ssize_t carl9170_debugfs_hw_ioread32_write(struct ar9170 *ar,
+	const char *buf, size_t count)
+{
+	int err = 0, i, n = 0, max_len = 32, res;
+	unsigned int reg, tmp;
+
+	if (!count)
+		return 0;
+
+	if (count > max_len)
+		return -E2BIG;
+
+	res = sscanf(buf, "0x%X %d", &reg, &n);
+	if (res < 1) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (res == 1)
+		n = 1;
+
+	if (n > 15) {
+		err = -EMSGSIZE;
+		goto out;
+	}
+
+	if ((reg >= 0x280000) || ((reg + (n << 2)) >= 0x280000)) {
+		err = -EADDRNOTAVAIL;
+		goto out;
+	}
+
+	if (reg & 3) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	for (i = 0; i < n; i++) {
+		err = carl9170_read_reg(ar, reg + (i << 2), &tmp);
+		if (err)
+			goto out;
+
+		ar->debug.ring[ar->debug.ring_tail].reg = reg + (i << 2);
+		ar->debug.ring[ar->debug.ring_tail].value = tmp;
+		ar->debug.ring_tail++;
+		ar->debug.ring_tail %= CARL9170_DEBUG_RING_SIZE;
+	}
+
+out:
+	return err ? err : count;
+}
+
+static char *carl9170_debugfs_hw_ioread32_read(struct ar9170 *ar, char *buf,
+					       size_t bufsize, ssize_t *ret)
+{
+	int i = 0;
+
+	while (ar->debug.ring_head != ar->debug.ring_tail) {
+		ADD(buf, *ret, bufsize, "%.8x = %.8x\n",
+		    ar->debug.ring[ar->debug.ring_head].reg,
+		    ar->debug.ring[ar->debug.ring_head].value);
+
+		ar->debug.ring_head++;
+		ar->debug.ring_head %= CARL9170_DEBUG_RING_SIZE;
+
+		if (i++ == 64)
+			break;
+	}
+	ar->debug.ring_head = ar->debug.ring_tail;
+	return buf;
+}
+DEBUGFS_DECLARE_RW_FILE(hw_ioread32, CARL9170_DEBUG_RING_SIZE * 40);
+
+static ssize_t carl9170_debugfs_bug_write(struct ar9170 *ar, const char *buf,
+					  size_t count)
+{
+	int err;
+
+	if (count < 1)
+		return -EINVAL;
+
+	switch (buf[0]) {
+	case 'F':
+		ar->needs_full_reset = true;
+		break;
+
+	case 'R':
+		if (!IS_STARTED(ar)) {
+			err = -EAGAIN;
+			goto out;
+		}
+
+		ar->needs_full_reset = false;
+		break;
+
+	case 'M':
+		err = carl9170_mac_reset(ar);
+		if (err < 0)
+			count = err;
+
+		goto out;
+
+	case 'P':
+		err = carl9170_set_channel(ar, ar->hw->conf.channel,
+			ar->hw->conf.channel_type, CARL9170_RFI_COLD);
+		if (err < 0)
+			count = err;
+
+		goto out;
+
+	default:
+		return -EINVAL;
+	}
+
+	carl9170_restart(ar, CARL9170_RR_USER_REQUEST);
+
+out:
+	return count;
+}
+
+static char *carl9170_debugfs_bug_read(struct ar9170 *ar, char *buf,
+				       size_t bufsize, ssize_t *ret)
+{
+	ADD(buf, *ret, bufsize, "[P]hy reinit, [R]estart, [F]ull usb reset, "
+	    "[M]ac reset\n");
+	ADD(buf, *ret, bufsize, "firmware restarts:%d, last reason:%d\n",
+		ar->restart_counter, ar->last_reason);
+	ADD(buf, *ret, bufsize, "phy reinit errors:%d (%d)\n",
+		ar->total_chan_fail, ar->chan_fail);
+	ADD(buf, *ret, bufsize, "reported firmware errors:%d\n",
+		ar->fw.err_counter);
+	ADD(buf, *ret, bufsize, "reported firmware BUGs:%d\n",
+		ar->fw.bug_counter);
+	ADD(buf, *ret, bufsize, "pending restart requests:%d\n",
+		atomic_read(&ar->pending_restarts));
+	return buf;
+}
+__DEBUGFS_DECLARE_RW_FILE(bug, 400, CARL9170_STOPPED);
+
+static const char *erp_modes[] = {
+	[CARL9170_ERP_INVALID] = "INVALID",
+	[CARL9170_ERP_AUTO] = "Automatic",
+	[CARL9170_ERP_MAC80211] = "Set by MAC80211",
+	[CARL9170_ERP_OFF] = "Force Off",
+	[CARL9170_ERP_RTS] = "Force RTS",
+	[CARL9170_ERP_CTS] = "Force CTS"
+};
+
+static char *carl9170_debugfs_erp_read(struct ar9170 *ar, char *buf,
+				       size_t bufsize, ssize_t *ret)
+{
+	ADD(buf, *ret, bufsize, "ERP Setting: (%d) -> %s\n", ar->erp_mode,
+	    erp_modes[ar->erp_mode]);
+	return buf;
+}
+
+static ssize_t carl9170_debugfs_erp_write(struct ar9170 *ar, const char *buf,
+					  size_t count)
+{
+	int res, val;
+
+	if (count < 1)
+		return -EINVAL;
+
+	res = sscanf(buf, "%d", &val);
+	if (res != 1)
+		return -EINVAL;
+
+	if (!((val > CARL9170_ERP_INVALID) &&
+	      (val < __CARL9170_ERP_NUM)))
+		return -EINVAL;
+
+	ar->erp_mode = val;
+	return count;
+}
+
+DEBUGFS_DECLARE_RW_FILE(erp, 80);
+
+static ssize_t carl9170_debugfs_hw_iowrite32_write(struct ar9170 *ar,
+	const char *buf, size_t count)
+{
+	int err = 0, max_len = 22, res;
+	u32 reg, val;
+
+	if (!count)
+		return 0;
+
+	if (count > max_len)
+		return -E2BIG;
+
+	res = sscanf(buf, "0x%X 0x%X", &reg, &val);
+	if (res != 2) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	if (reg <= 0x100000 || reg >= 0x280000) {
+		err = -EADDRNOTAVAIL;
+		goto out;
+	}
+
+	if (reg & 3) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	err = carl9170_write_reg(ar, reg, val);
+	if (err)
+		goto out;
+
+out:
+	return err ? err : count;
+}
+DEBUGFS_DECLARE_WO_FILE(hw_iowrite32);
+
+DEBUGFS_HW_TALLY_FILE(hw_tx_tally, "u");
+DEBUGFS_HW_TALLY_FILE(hw_rx_tally, "u");
+DEBUGFS_HW_TALLY_FILE(hw_phy_errors, "u");
+DEBUGFS_HW_REG_FILE(hw_wlan_queue, ".8x");
+DEBUGFS_HW_REG_FILE(hw_pta_queue, ".8x");
+DEBUGFS_HW_REG_FILE(hw_ampdu_info, ".8x");
+DEBUGFS_QUEUE_DUMP(tx_status, 0);
+DEBUGFS_QUEUE_DUMP(tx_status, 1);
+DEBUGFS_QUEUE_DUMP(tx_status, 2);
+DEBUGFS_QUEUE_DUMP(tx_status, 3);
+DEBUGFS_QUEUE_DUMP(tx_pending, 0);
+DEBUGFS_QUEUE_DUMP(tx_pending, 1);
+DEBUGFS_QUEUE_DUMP(tx_pending, 2);
+DEBUGFS_QUEUE_DUMP(tx_pending, 3);
+DEBUGFS_READONLY_FILE(usb_tx_anch_urbs, 20, "%d",
+		      atomic_read(&ar->tx_anch_urbs));
+DEBUGFS_READONLY_FILE(usb_rx_anch_urbs, 20, "%d",
+		      atomic_read(&ar->rx_anch_urbs));
+DEBUGFS_READONLY_FILE(usb_rx_work_urbs, 20, "%d",
+		      atomic_read(&ar->rx_work_urbs));
+DEBUGFS_READONLY_FILE(usb_rx_pool_urbs, 20, "%d",
+		      atomic_read(&ar->rx_pool_urbs));
+
+DEBUGFS_READONLY_FILE(tx_total_queued, 20, "%d",
+		      atomic_read(&ar->tx_total_queued));
+DEBUGFS_READONLY_FILE(tx_ampdu_scheduler, 20, "%d",
+		      atomic_read(&ar->tx_ampdu_scheduler));
+DEBUGFS_READONLY_FILE(tx_ampdu_timeout, 20, "%d",
+		      ar->tx_ampdu_timeout);
+
+DEBUGFS_READONLY_FILE(tx_total_pending, 20, "%d",
+		      atomic_read(&ar->tx_total_pending));
+
+DEBUGFS_READONLY_FILE(tx_ampdu_list_len, 20, "%d",
+		      ar->tx_ampdu_list_len);
+
+DEBUGFS_READONLY_FILE(tx_ampdu_upload, 20, "%d",
+		      atomic_read(&ar->tx_ampdu_upload));
+
+DEBUGFS_READONLY_FILE(tx_janitor_last_run, 64, "last run:%d ms ago",
+	jiffies_to_msecs(jiffies - ar->tx_janitor_last_run));
+
+DEBUGFS_READONLY_FILE(tx_dropped, 20, "%d", ar->tx_dropped);
+
+DEBUGFS_READONLY_FILE(rx_dropped, 20, "%d", ar->rx_dropped);
+
+DEBUGFS_READONLY_FILE(sniffer_enabled, 20, "%d", ar->sniffer_enabled);
+DEBUGFS_READONLY_FILE(rx_software_decryption, 20, "%d",
+		      ar->rx_software_decryption);
+DEBUGFS_READONLY_FILE(ampdu_factor, 20, "%d",
+		      ar->current_factor);
+DEBUGFS_READONLY_FILE(ampdu_density, 20, "%d",
+		      ar->current_density);
+
+DEBUGFS_READONLY_FILE(beacon_int, 20, "%d TU", ar->global_beacon_int);
+DEBUGFS_READONLY_FILE(pretbtt, 20, "%d TU", ar->global_pretbtt);
+
+void carl9170_debugfs_register(struct ar9170 *ar)
+{
+	ar->debug_dir = debugfs_create_dir(KBUILD_MODNAME,
+		ar->hw->wiphy->debugfsdir);
+
+#define DEBUGFS_ADD(name)						\
+	debugfs_create_file(#name, carl_debugfs_##name ##_ops.attr,	\
+			    ar->debug_dir, ar,				\
+			    &carl_debugfs_##name ## _ops.fops);
+
+	DEBUGFS_ADD(usb_tx_anch_urbs);
+	DEBUGFS_ADD(usb_rx_pool_urbs);
+	DEBUGFS_ADD(usb_rx_anch_urbs);
+	DEBUGFS_ADD(usb_rx_work_urbs);
+
+	DEBUGFS_ADD(tx_total_queued);
+	DEBUGFS_ADD(tx_total_pending);
+	DEBUGFS_ADD(tx_dropped);
+	DEBUGFS_ADD(tx_stuck);
+	DEBUGFS_ADD(tx_ampdu_upload);
+	DEBUGFS_ADD(tx_ampdu_scheduler);
+	DEBUGFS_ADD(tx_ampdu_list_len);
+
+	DEBUGFS_ADD(rx_dropped);
+	DEBUGFS_ADD(sniffer_enabled);
+	DEBUGFS_ADD(rx_software_decryption);
+
+	DEBUGFS_ADD(mem_usage);
+	DEBUGFS_ADD(qos_stat);
+	DEBUGFS_ADD(sta_psm);
+	DEBUGFS_ADD(ampdu_state);
+
+	DEBUGFS_ADD(hw_tx_tally);
+	DEBUGFS_ADD(hw_rx_tally);
+	DEBUGFS_ADD(hw_phy_errors);
+	DEBUGFS_ADD(phy_noise);
+
+	DEBUGFS_ADD(hw_wlan_queue);
+	DEBUGFS_ADD(hw_pta_queue);
+	DEBUGFS_ADD(hw_ampdu_info);
+
+	DEBUGFS_ADD(ampdu_density);
+	DEBUGFS_ADD(ampdu_factor);
+
+	DEBUGFS_ADD(tx_ampdu_timeout);
+
+	DEBUGFS_ADD(tx_janitor_last_run);
+
+	DEBUGFS_ADD(tx_status_0);
+	DEBUGFS_ADD(tx_status_1);
+	DEBUGFS_ADD(tx_status_2);
+	DEBUGFS_ADD(tx_status_3);
+
+	DEBUGFS_ADD(tx_pending_0);
+	DEBUGFS_ADD(tx_pending_1);
+	DEBUGFS_ADD(tx_pending_2);
+	DEBUGFS_ADD(tx_pending_3);
+
+	DEBUGFS_ADD(hw_ioread32);
+	DEBUGFS_ADD(hw_iowrite32);
+	DEBUGFS_ADD(bug);
+
+	DEBUGFS_ADD(erp);
+
+	DEBUGFS_ADD(vif_dump);
+
+	DEBUGFS_ADD(beacon_int);
+	DEBUGFS_ADD(pretbtt);
+
+#undef DEBUGFS_ADD
+}
+
+void carl9170_debugfs_unregister(struct ar9170 *ar)
+{
+	debugfs_remove_recursive(ar->debug_dir);
+}
diff --git a/drivers/net/wireless/ath/carl9170/debug.h b/drivers/net/wireless/ath/carl9170/debug.h
new file mode 100644
index 0000000..ea4b975
--- /dev/null
+++ b/drivers/net/wireless/ath/carl9170/debug.h
@@ -0,0 +1,134 @@ 
+/*
+ * Atheros CARL9170 driver
+ *
+ * debug header
+ *
+ * Copyright 2010, Christian Lamparter <chunkeey@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, see
+ * http://www.gnu.org/licenses/.
+ *
+ * This file incorporates work covered by the following copyright and
+ * permission notice:
+ *    Copyright (c) 2007-2008 Atheros Communications, 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 __DEBUG_H
+#define __DEBUG_H
+
+#include "eeprom.h"
+#include "wlan.h"
+#include "hw.h"
+#include "fwdesc.h"
+#include "fwcmd.h"
+#include "../regd.h"
+
+struct hw_stat_reg_entry {
+	u32 reg;
+	char nreg[32];
+};
+
+#define	STAT_MAC_REG(reg)	\
+	{ (AR9170_MAC_REG_##reg), #reg }
+
+#define	STAT_PTA_REG(reg)	\
+	{ (AR9170_PTA_REG_##reg), #reg }
+
+#define	STAT_USB_REG(reg)	\
+	{ (AR9170_USB_REG_##reg), #reg }
+
+static const struct hw_stat_reg_entry hw_rx_tally_regs[] = {
+	STAT_MAC_REG(RX_CRC32),		STAT_MAC_REG(RX_CRC16),
+	STAT_MAC_REG(RX_TIMEOUT_COUNT),	STAT_MAC_REG(RX_ERR_DECRYPTION_UNI),
+	STAT_MAC_REG(RX_ERR_DECRYPTION_MUL), STAT_MAC_REG(RX_MPDU),
+	STAT_MAC_REG(RX_DROPPED_MPDU),	STAT_MAC_REG(RX_DEL_MPDU),
+};
+
+static const struct hw_stat_reg_entry hw_phy_errors_regs[] = {
+	STAT_MAC_REG(RX_PHY_MISC_ERROR), STAT_MAC_REG(RX_PHY_XR_ERROR),
+	STAT_MAC_REG(RX_PHY_OFDM_ERROR), STAT_MAC_REG(RX_PHY_CCK_ERROR),
+	STAT_MAC_REG(RX_PHY_HT_ERROR), STAT_MAC_REG(RX_PHY_TOTAL),
+};
+
+static const struct hw_stat_reg_entry hw_tx_tally_regs[] = {
+	STAT_MAC_REG(TX_TOTAL),		STAT_MAC_REG(TX_UNDERRUN),
+	STAT_MAC_REG(TX_RETRY),
+};
+
+static const struct hw_stat_reg_entry hw_wlan_queue_regs[] = {
+	STAT_MAC_REG(DMA_STATUS),	STAT_MAC_REG(DMA_TRIGGER),
+	STAT_MAC_REG(DMA_TXQ0_ADDR),	STAT_MAC_REG(DMA_TXQ0_CURR_ADDR),
+	STAT_MAC_REG(DMA_TXQ1_ADDR),	STAT_MAC_REG(DMA_TXQ1_CURR_ADDR),
+	STAT_MAC_REG(DMA_TXQ2_ADDR),	STAT_MAC_REG(DMA_TXQ2_CURR_ADDR),
+	STAT_MAC_REG(DMA_TXQ3_ADDR),	STAT_MAC_REG(DMA_TXQ3_CURR_ADDR),
+	STAT_MAC_REG(DMA_RXQ_ADDR),	STAT_MAC_REG(DMA_RXQ_CURR_ADDR),
+};
+
+static const struct hw_stat_reg_entry hw_ampdu_info_regs[] = {
+	STAT_MAC_REG(AMPDU_DENSITY),	STAT_MAC_REG(AMPDU_FACTOR),
+};
+
+static const struct hw_stat_reg_entry hw_pta_queue_regs[] = {
+	STAT_PTA_REG(DN_CURR_ADDRH),	STAT_PTA_REG(DN_CURR_ADDRL),
+	STAT_PTA_REG(UP_CURR_ADDRH),	STAT_PTA_REG(UP_CURR_ADDRL),
+	STAT_PTA_REG(DMA_STATUS),	STAT_PTA_REG(DMA_MODE_CTRL),
+};
+
+#define	DEFINE_TALLY(name)					\
+	u32 name##_sum[ARRAY_SIZE(name##_regs)],		\
+	    name##_counter[ARRAY_SIZE(name##_regs)]		\
+
+#define	DEFINE_STAT(name)					\
+	u32 name##_counter[ARRAY_SIZE(name##_regs)]		\
+
+struct ath_stats {
+	DEFINE_TALLY(hw_tx_tally);
+	DEFINE_TALLY(hw_rx_tally);
+	DEFINE_TALLY(hw_phy_errors);
+	DEFINE_STAT(hw_wlan_queue);
+	DEFINE_STAT(hw_pta_queue);
+	DEFINE_STAT(hw_ampdu_info);
+};
+
+struct carl9170_debug_mem_rbe {
+	u32 reg;
+	u32 value;
+};
+
+#define	CARL9170_DEBUG_RING_SIZE			64
+
+struct carl9170_debug {
+	struct ath_stats stats;
+	struct carl9170_debug_mem_rbe ring[CARL9170_DEBUG_RING_SIZE];
+	struct mutex ring_lock;
+	unsigned int ring_head, ring_tail;
+	struct delayed_work update_tally;
+};
+
+struct ar9170;
+
+void carl9170_debugfs_register(struct ar9170 *ar);
+void carl9170_debugfs_unregister(struct ar9170 *ar);
+#endif /* __DEBUG_H */
diff --git a/drivers/net/wireless/ath/carl9170/fw.c b/drivers/net/wireless/ath/carl9170/fw.c
new file mode 100644
index 0000000..3661546
--- /dev/null
+++ b/drivers/net/wireless/ath/carl9170/fw.c
@@ -0,0 +1,395 @@ 
+/*
+ * Atheros CARL9170 driver
+ *
+ * firmware parser
+ *
+ * Copyright 2009, 2010, Christian Lamparter <chunkeey@googlemail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, see
+ * http://www.gnu.org/licenses/.
+ */
+
+#include <linux/kernel.h>
+#include <linux/firmware.h>
+#include <linux/crc32.h>
+#include "carl9170.h"
+#include "fwcmd.h"
+#include "version.h"
+
+#define MAKE_STR(symbol) #symbol
+#define TO_STR(symbol) MAKE_STR(symbol)
+#define CARL9170FW_API_VER_STR TO_STR(CARL9170FW_API_MAX_VER)
+MODULE_VERSION(CARL9170FW_API_VER_STR ":" CARL9170FW_VERSION_GIT);
+
+static const u8 otus_magic[4] = { OTUS_MAGIC };
+
+static const void *carl9170_fw_find_desc(struct ar9170 *ar, const u8 descid[4],
+	const unsigned int len, const u8 compatible_revision)
+{
+	const struct carl9170fw_desc_head *iter;
+
+	carl9170fw_for_each_hdr(iter, ar->fw.desc) {
+		if (carl9170fw_desc_cmp(iter, descid, len,
+					compatible_revision))
+			return (void *)iter;
+	}
+
+	/* needed to find the LAST desc */
+	if (carl9170fw_desc_cmp(iter, descid, len,
+				compatible_revision))
+		return (void *)iter;
+
+	return NULL;
+}
+
+static int carl9170_fw_verify_descs(struct ar9170 *ar,
+	const struct carl9170fw_desc_head *head, unsigned int max_len)
+{
+	const struct carl9170fw_desc_head *pos;
+	unsigned long pos_addr, end_addr;
+	unsigned int pos_length;
+
+	if (max_len < sizeof(*pos))
+		return -ENODATA;
+
+	max_len = min_t(unsigned int, CARL9170FW_DESC_MAX_LENGTH, max_len);
+
+	pos = head;
+	pos_addr = (unsigned long) pos;
+	end_addr = pos_addr + max_len;
+
+	while (pos_addr < end_addr) {
+		if (pos_addr + sizeof(*head) > end_addr)
+			return -E2BIG;
+
+		pos_length = le16_to_cpu(pos->length);
+
+		if (pos_length < sizeof(*head))
+			return -EBADMSG;
+
+		if (pos_length > max_len)
+			return -EOVERFLOW;
+
+		if (pos_addr + pos_length > end_addr)
+			return -EMSGSIZE;
+
+		if (carl9170fw_desc_cmp(pos, LAST_MAGIC,
+					CARL9170FW_LAST_DESC_SIZE,
+					CARL9170FW_LAST_DESC_CUR_VER))
+			return 0;
+
+		pos_addr += pos_length;
+		pos = (void *)pos_addr;
+		max_len -= pos_length;
+	}
+	return -EINVAL;
+}
+
+static void carl9170_fw_info(struct ar9170 *ar)
+{
+	const struct carl9170fw_motd_desc *motd_desc;
+	unsigned int str_ver_len;
+	u32 fw_date;
+
+	dev_info(&ar->udev->dev, "driver   API: %s 2%03d-%02d-%02d [%d-%d]\n",
+		CARL9170FW_VERSION_GIT, CARL9170FW_VERSION_YEAR,
+		CARL9170FW_VERSION_MONTH, CARL9170FW_VERSION_DAY,
+		CARL9170FW_API_MIN_VER, CARL9170FW_API_MAX_VER);
+
+	motd_desc = carl9170_fw_find_desc(ar, MOTD_MAGIC,
+		sizeof(*motd_desc), CARL9170FW_MOTD_DESC_CUR_VER);
+
+	if (motd_desc) {
+		str_ver_len = strnlen(motd_desc->release,
+			CARL9170FW_MOTD_RELEASE_LEN);
+
+		fw_date = le32_to_cpu(motd_desc->fw_year_month_day);
+
+		dev_info(&ar->udev->dev, "firmware API: %.*s 2%03d-%02d-%02d\n",
+			 str_ver_len, motd_desc->release,
+			 CARL9170FW_GET_YEAR(fw_date),
+			 CARL9170FW_GET_MONTH(fw_date),
+			 CARL9170FW_GET_DAY(fw_date));
+
+		strlcpy(ar->hw->wiphy->fw_version, motd_desc->release,
+			sizeof(ar->hw->wiphy->fw_version));
+	}
+}
+
+static bool valid_dma_addr(const u32 address)
+{
+	if (address >= AR9170_SRAM_OFFSET &&
+	    address < (AR9170_SRAM_OFFSET + AR9170_SRAM_SIZE))
+		return true;
+
+	return false;
+}
+
+static bool valid_cpu_addr(const u32 address)
+{
+	if (valid_dma_addr(address) || (address >= AR9170_PRAM_OFFSET &&
+	    address < (AR9170_PRAM_OFFSET + AR9170_PRAM_SIZE)))
+		return true;
+
+	return false;
+}
+
+static int carl9170_fw(struct ar9170 *ar, const __u8 *data, size_t len)
+{
+	const struct carl9170fw_otus_desc *otus_desc;
+	const struct carl9170fw_chk_desc *chk_desc;
+	const struct carl9170fw_last_desc *last_desc;
+
+	last_desc = carl9170_fw_find_desc(ar, LAST_MAGIC,
+		sizeof(*last_desc), CARL9170FW_LAST_DESC_CUR_VER);
+	if (!last_desc)
+		return -EINVAL;
+
+	otus_desc = carl9170_fw_find_desc(ar, OTUS_MAGIC,
+		sizeof(*otus_desc), CARL9170FW_OTUS_DESC_CUR_VER);
+	if (!otus_desc) {
+		dev_err(&ar->udev->dev, "failed to find compatible firmware "
+			"descriptor.\n");
+		return -ENODATA;
+	}
+
+	chk_desc = carl9170_fw_find_desc(ar, CHK_MAGIC,
+		sizeof(*chk_desc), CARL9170FW_CHK_DESC_CUR_VER);
+
+	if (chk_desc) {
+		unsigned long fin, diff;
+		unsigned int dsc_len;
+		u32 crc32;
+
+		dsc_len = min_t(unsigned int, len,
+			(unsigned long)chk_desc - (unsigned long)otus_desc);
+
+		fin = (unsigned long) last_desc + sizeof(*last_desc);
+		diff = fin - (unsigned long) otus_desc;
+
+		if (diff < len)
+			len -= diff;
+
+		if (len < 256)
+			return -EIO;
+
+		crc32 = crc32_le(~0, data, len);
+		if (cpu_to_le32(crc32) != chk_desc->fw_crc32) {
+			dev_err(&ar->udev->dev, "fw checksum test failed.\n");
+			return -ENOEXEC;
+		}
+
+		crc32 = crc32_le(crc32, (void *)otus_desc, dsc_len);
+		if (cpu_to_le32(crc32) != chk_desc->hdr_crc32) {
+			dev_err(&ar->udev->dev, "descriptor check failed.\n");
+			return -EINVAL;
+		}
+	} else {
+		dev_warn(&ar->udev->dev, "Unprotected firmware image.\n");
+	}
+
+#define SUPP(feat)						\
+	(carl9170fw_supports(otus_desc->feature_set, feat))
+
+	if (!SUPP(CARL9170FW_DUMMY_FEATURE)) {
+		dev_err(&ar->udev->dev, "invalid firmware descriptor "
+			"format detected.\n");
+		return -EINVAL;
+	}
+
+	ar->fw.api_version = otus_desc->api_ver;
+
+	if (ar->fw.api_version < CARL9170FW_API_MIN_VER ||
+	    ar->fw.api_version > CARL9170FW_API_MAX_VER) {
+		dev_err(&ar->udev->dev, "unsupported firmware api version.\n");
+		return -EINVAL;
+	}
+
+	if (!SUPP(CARL9170FW_COMMAND_PHY) || SUPP(CARL9170FW_UNUSABLE) ||
+	    !SUPP(CARL9170FW_HANDLE_BACK_REQ)) {
+		dev_err(&ar->udev->dev, "firmware does support "
+			"mandatory features.\n");
+		return -ECANCELED;
+	}
+
+	if (ilog2(le32_to_cpu(otus_desc->feature_set)) >=
+		__CARL9170FW_FEATURE_NUM) {
+		dev_warn(&ar->udev->dev, "driver does not support all "
+			 "firmware features.\n");
+	}
+
+	if (!SUPP(CARL9170FW_COMMAND_CAM)) {
+		dev_info(&ar->udev->dev, "crypto offloading is disabled "
+			 "by firmware.\n");
+		ar->disable_offload = true;
+	}
+
+	if (SUPP(CARL9170FW_PSM))
+		ar->hw->flags |= IEEE80211_HW_SUPPORTS_PS;
+
+	if (!SUPP(CARL9170FW_USB_INIT_FIRMWARE)) {
+		dev_err(&ar->udev->dev, "firmware does not provide "
+			"mandatory interfaces.\n");
+		return -EINVAL;
+	}
+
+	if (SUPP(CARL9170FW_MINIBOOT))
+		ar->fw.offset = le16_to_cpu(otus_desc->miniboot_size);
+	else
+		ar->fw.offset = 0;
+
+	if (SUPP(CARL9170FW_USB_DOWN_STREAM)) {
+		ar->hw->extra_tx_headroom += sizeof(struct ar9170_stream);
+		ar->fw.tx_stream = true;
+	}
+
+	if (SUPP(CARL9170FW_USB_UP_STREAM))
+		ar->fw.rx_stream = true;
+
+	ar->fw.vif_num = otus_desc->vif_num;
+	ar->fw.cmd_bufs = otus_desc->cmd_bufs;
+	ar->fw.address = le32_to_cpu(otus_desc->fw_address);
+	ar->fw.rx_size = le16_to_cpu(otus_desc->rx_max_frame_len);
+	ar->fw.mem_blocks = min_t(unsigned int, otus_desc->tx_descs, 0xfe);
+	atomic_set(&ar->mem_free_blocks, ar->fw.mem_blocks);
+	ar->fw.mem_block_size = le16_to_cpu(otus_desc->tx_frag_len);
+
+	if (ar->fw.vif_num >= AR9170_MAX_VIRTUAL_MAC || !ar->fw.vif_num ||
+	    ar->fw.mem_blocks < 16 || !ar->fw.cmd_bufs ||
+	    ar->fw.mem_block_size < 64 || ar->fw.mem_block_size > 512 ||
+	    ar->fw.rx_size > 32768 || ar->fw.rx_size < 4096 ||
+	    !valid_cpu_addr(ar->fw.address)) {
+		dev_err(&ar->udev->dev, "firmware shows obvious signs of "
+			"malicious tampering.\n");
+		return -EINVAL;
+	}
+
+	ar->fw.beacon_addr = le32_to_cpu(otus_desc->bcn_addr);
+	ar->fw.beacon_max_len = le16_to_cpu(otus_desc->bcn_len);
+
+	if (valid_dma_addr(ar->fw.beacon_addr) && ar->fw.beacon_max_len >=
+	    AR9170_MAC_BCN_LENGTH_MAX) {
+		ar->hw->wiphy->interface_modes |= BIT(NL80211_IFTYPE_ADHOC);
+
+		if (SUPP(CARL9170FW_WLANTX_CAB)) {
+			ar->hw->wiphy->interface_modes |=
+				BIT(NL80211_IFTYPE_AP);
+		}
+	}
+
+#undef SUPPORTED
+	return 0;
+}
+
+static struct carl9170fw_desc_head *
+carl9170_find_fw_desc(struct ar9170 *ar, const __u8 *fw_data, const size_t len)
+
+{
+	int scan = 0, found = 0;
+
+	if (!carl9170fw_size_check(len)) {
+		dev_err(&ar->udev->dev, "firmware size is out of bound.\n");
+		return NULL;
+	}
+
+	while (scan < len - sizeof(struct carl9170fw_desc_head)) {
+		if (fw_data[scan++] == otus_magic[found])
+			found++;
+		else
+			found = 0;
+
+		if (scan >= len)
+			break;
+
+		if (found == sizeof(otus_magic))
+			break;
+	}
+
+	if (found != sizeof(otus_magic))
+		return NULL;
+
+	return (void *)&fw_data[scan - found];
+}
+
+int carl9170_fw_fix_eeprom(struct ar9170 *ar)
+{
+	const struct carl9170fw_fix_desc *fix_desc = NULL;
+	unsigned int i, n, off;
+	u32 *data = (void *)&ar->eeprom;
+
+	fix_desc = carl9170_fw_find_desc(ar, FIX_MAGIC,
+		sizeof(*fix_desc), CARL9170FW_FIX_DESC_CUR_VER);
+
+	if (!fix_desc)
+		return 0;
+
+	n = (le16_to_cpu(fix_desc->head.length) - sizeof(*fix_desc)) /
+	    sizeof(struct carl9170fw_fix_entry);
+
+	for (i = 0; i < n; i++) {
+		off = le32_to_cpu(fix_desc->data[i].address) -
+		      AR9170_EEPROM_START;
+
+		if (off >= sizeof(struct ar9170_eeprom) || (off & 3)) {
+			dev_err(&ar->udev->dev, "Skip invalid entry %d\n", i);
+			continue;
+		}
+
+		data[off / sizeof(*data)] &=
+			le32_to_cpu(fix_desc->data[i].mask);
+		data[off / sizeof(*data)] |=
+			le32_to_cpu(fix_desc->data[i].value);
+	}
+
+	return 0;
+}
+
+int carl9170_parse_firmware(struct ar9170 *ar)
+{
+	const struct carl9170fw_desc_head *fw_desc = NULL;
+	const struct firmware *fw = ar->fw.fw;
+	unsigned long header_offset = 0;
+	int err;
+
+	if (WARN_ON(!fw))
+		return -EINVAL;
+
+	fw_desc = carl9170_find_fw_desc(ar, fw->data, fw->size);
+
+	if (!fw_desc) {
+		dev_err(&ar->udev->dev, "unsupported firmware.\n");
+		return -ENODATA;
+	}
+
+	header_offset = (unsigned long)fw_desc - (unsigned long)fw->data;
+
+	err = carl9170_fw_verify_descs(ar, fw_desc, fw->size - header_offset);
+	if (err) {
+		dev_err(&ar->udev->dev, "damaged firmware (%d).\n", err);
+		return err;
+	}
+
+	ar->fw.desc = fw_desc;
+
+	carl9170_fw_info(ar);
+
+	err = carl9170_fw(ar, fw->data, fw->size);
+	if (err) {
+		dev_err(&ar->udev->dev, "failed to parse firmware (%d).\n",
+			err);
+		return err;
+	}
+
+	return 0;
+}