--- a/.local-symbols
+++ b/.local-symbols
@@ -119,3 +119,4 @@ ATH10K_DEBUG=
 ATH10K_DEBUGFS=
 ATH10K_TRACING=
 ATH10K_DFS_CERTIFIED=
+ATH10K_PKTLOG=
--- a/drivers/net/wireless/ath/ath10k/Kconfig
+++ b/drivers/net/wireless/ath/ath10k/Kconfig
@@ -55,3 +55,12 @@ config ATH10K_DFS_CERTIFIED
 	---help---
 	This option enables DFS support for initiating radiation on
 	ath10k.
+
+config ATH10K_PKTLOG
+	bool "Atheros ath10k packet logging support"
+	depends on ATH10K_DEBUGFS
+	---help---
+	Say Y to dump frame information during frame transmission
+	and reception, rate information and ani state. The packet
+	log uses ring buffer to dump the data. The buffer size,
+	frame filters can be alterted by debugfs entries.
--- a/drivers/net/wireless/ath/ath10k/Makefile
+++ b/drivers/net/wireless/ath/ath10k/Makefile
@@ -20,6 +20,7 @@ ath10k_core-$(CPTCFG_NL80211_TESTMODE) +
 ath10k_core-$(CPTCFG_ATH10K_TRACING) += trace.o
 ath10k_core-$(CONFIG_THERMAL) += thermal.o
 ath10k_core-$(CPTCFG_MAC80211_DEBUGFS) += debugfs_sta.o
+ath10k_core-$(CPTCFG_ATH10K_PKTLOG) += pktlog.o
 ath10k_core-$(CONFIG_PM) += wow.o
 
 obj-$(CPTCFG_ATH10K_PCI) += ath10k_pci.o
--- a/drivers/net/wireless/ath/ath10k/core.h
+++ b/drivers/net/wireless/ath/ath10k/core.h
@@ -37,6 +37,7 @@
 #include "thermal.h"
 #include "wow.h"
 #include "swap.h"
+#include "pktlog.h"
 
 #define MS(_v, _f) (((_v) & _f##_MASK) >> _f##_LSB)
 #define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
@@ -456,6 +457,11 @@ struct ath10k_debug {
 	void *cal_data;
 
 	struct ath10k_fw_crash_data *fw_crash_data;
+#ifdef CPTCFG_ATH10K_PKTLOG
+	struct dentry *debugfs_pktlog;
+	struct ath_pktlog pktlog;
+	bool is_pkt_logging;
+#endif
 };
 
 enum ath10k_state {
--- a/drivers/net/wireless/ath/ath10k/debug.c
+++ b/drivers/net/wireless/ath/ath10k/debug.c
@@ -2362,6 +2362,7 @@ void ath10k_debug_destroy(struct ath10k
 	ar->debug.cal_data = NULL;
 
 	ath10k_debug_fw_stats_reset(ar);
+	ath10k_deinit_pktlog(ar);
 
 	kfree(ar->debug.tpc_stats);
 }
@@ -2445,6 +2446,7 @@ int ath10k_debug_register(struct ath10k
 
 	debugfs_create_file("pktlog_filter", S_IRUGO | S_IWUSR,
 			    ar->debug.debugfs_phy, ar, &fops_pktlog_filter);
+	ath10k_init_pktlog(ar);
 
 	debugfs_create_file("quiet_period", S_IRUGO | S_IWUSR,
 			    ar->debug.debugfs_phy, ar, &fops_quiet_period);
@@ -2470,6 +2472,7 @@ int ath10k_debug_register(struct ath10k
 void ath10k_debug_unregister(struct ath10k *ar)
 {
 	cancel_delayed_work_sync(&ar->debug.htt_stats_dwork);
+	ath10k_deinit_pktlog(ar);
 }
 
 #endif /* CPTCFG_ATH10K_DEBUGFS */
--- a/drivers/net/wireless/ath/ath10k/htt_rx.c
+++ b/drivers/net/wireless/ath/ath10k/htt_rx.c
@@ -1550,6 +1550,7 @@ static int ath10k_htt_rx_handle_amsdu(st
 	}
 
 	num_msdus = skb_queue_len(&amsdu);
+	ath10k_pktlog_rx(ar, &amsdu);
 	ath10k_htt_rx_h_ppdu(ar, &amsdu, rx_status, 0xffff);
 
 	/* only for ret = 1 indicates chained msdus */
@@ -1890,6 +1891,7 @@ static int ath10k_htt_rx_in_ord_ind(stru
 			 * should still give an idea about rx rate to the user.
 			 */
 			num_msdus += skb_queue_len(&amsdu);
+			ath10k_pktlog_rx(ar, &amsdu);
 			ath10k_htt_rx_h_ppdu(ar, &amsdu, status, vdev_id);
 			ath10k_htt_rx_h_filter(ar, &amsdu, status);
 			ath10k_htt_rx_h_mpdu(ar, &amsdu, status);
@@ -2433,6 +2435,7 @@ bool ath10k_htt_t2h_msg_handler(struct a
 					skb->len -
 					offsetof(struct htt_resp,
 						 pktlog_msg.payload));
+		ath10k_process_pktlog(ar, resp->pktlog_msg.payload);
 		break;
 	}
 	case HTT_T2H_MSG_TYPE_RX_FLUSH: {
@@ -2495,6 +2498,7 @@ void ath10k_htt_rx_pktlog_completion_han
 					     struct sk_buff *skb)
 {
 	trace_ath10k_htt_pktlog(ar, skb->data, skb->len);
+	ath10k_process_pktlog(ar, skb->data);
 	dev_kfree_skb_any(skb);
 }
 EXPORT_SYMBOL(ath10k_htt_rx_pktlog_completion_handler);
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/pktlog.c
@@ -0,0 +1,957 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * 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 <linux/debugfs.h>
+#include <linux/vmalloc.h>
+#include "core.h"
+#include "wmi-ops.h"
+#include "hif.h"
+#include "mac.h"
+
+static struct ath_pktlog_frm_hdr frame_header;
+
+static struct page *pktlog_virt_to_logical(void *addr)
+{
+	struct page *page;
+	unsigned long vpage = 0UL;
+
+	page = vmalloc_to_page(addr);
+	if (page) {
+		vpage = (unsigned long)page_address(page);
+		vpage |= ((unsigned long)addr & (PAGE_SIZE - 1));
+	}
+	return virt_to_page((void *)vpage);
+}
+
+static void ath_pktlog_release(struct ath_pktlog *pktlog)
+{
+	unsigned long page_cnt, vaddr;
+	struct page *page;
+
+	page_cnt =
+		((sizeof(*pktlog->buf) +
+		pktlog->buf_size) / PAGE_SIZE) + 1;
+
+	for (vaddr = (unsigned long)(pktlog->buf); vaddr <
+			(unsigned long)(pktlog->buf) +
+			(page_cnt * PAGE_SIZE);
+			vaddr += PAGE_SIZE) {
+		page = pktlog_virt_to_logical((void *)vaddr);
+		clear_bit(PG_reserved, &page->flags);
+	}
+
+	vfree(pktlog->buf);
+	pktlog->buf = NULL;
+}
+
+static int ath_alloc_pktlog_buf(struct ath10k *ar)
+{
+	u32 page_cnt;
+	unsigned long vaddr;
+	struct page *page;
+	struct ath_pktlog *pktlog = &ar->debug.pktlog;
+
+	if (pktlog->buf_size == 0)
+		return -EINVAL;
+
+	page_cnt = (sizeof(*pktlog->buf) +
+		    pktlog->buf_size) / PAGE_SIZE;
+
+	pktlog->buf =  vmalloc((page_cnt + 2) * PAGE_SIZE);
+	if (!pktlog->buf)
+		return -ENOMEM;
+
+	pktlog->buf = (struct ath_pktlog_buf *)
+				     (((unsigned long)
+				      (pktlog->buf)
+				     + PAGE_SIZE - 1) & PAGE_MASK);
+
+	for (vaddr = (unsigned long)(pktlog->buf);
+		      vaddr < ((unsigned long)(pktlog->buf)
+		      + (page_cnt * PAGE_SIZE)); vaddr += PAGE_SIZE) {
+		page = pktlog_virt_to_logical((void *)vaddr);
+		set_bit(PG_reserved, &page->flags);
+	}
+
+	return 0;
+}
+
+static void ath_init_pktlog_buf(struct ath_pktlog *pktlog)
+{
+	pktlog->buf->bufhdr.magic_num = PKTLOG_MAGIC_NUM;
+	pktlog->buf->bufhdr.version = CUR_PKTLOG_VER;
+	pktlog->buf->rd_offset = -1;
+	pktlog->buf->wr_offset = 0;
+	if (pktlog->filter == 0)
+		pktlog->filter = ATH_PKTLOG_FILTER_DEFAULT;
+}
+
+static inline void ath_pktlog_mov_rd_idx(struct ath_pktlog *pl_info,
+					 int32_t *rd_offset)
+{
+	int32_t boundary, cur_rec_hdr_size_offset;
+	struct ath_pktlog_buf *log_buf = pl_info->buf;
+	u16 payload_size;
+
+	cur_rec_hdr_size_offset = *rd_offset;
+	cur_rec_hdr_size_offset += pl_info->hdr_size_field_offset;
+	payload_size = *(u16 *)&log_buf->log_data[cur_rec_hdr_size_offset];
+
+	boundary = *rd_offset;
+	boundary += pl_info->hdr_size;
+	boundary += payload_size;
+
+	if (boundary <= pl_info->buf_size)
+		*rd_offset = boundary;
+	else
+		*rd_offset = payload_size;
+
+	if ((pl_info->buf_size - *rd_offset) < pl_info->hdr_size)
+		*rd_offset = 0;
+}
+
+static char *ath_pktlog_getbuf(struct ath_pktlog *pl_info,
+			       struct ath_pktlog_hdr_arg *hdr_arg)
+{
+	struct ath_pktlog_buf *log_buf;
+	int32_t cur_wr_offset, buf_size;
+	char *log_ptr;
+
+	log_buf = pl_info->buf;
+	buf_size = pl_info->buf_size;
+
+	spin_lock_bh(&pl_info->lock);
+	cur_wr_offset = log_buf->wr_offset;
+	/* Move read offset to the next entry if there is a buffer overlap */
+	if (log_buf->rd_offset >= 0) {
+		if ((cur_wr_offset <= log_buf->rd_offset) &&
+		    (cur_wr_offset + pl_info->hdr_size) >
+		     log_buf->rd_offset)
+			ath_pktlog_mov_rd_idx(pl_info, &log_buf->rd_offset);
+	} else {
+		log_buf->rd_offset = cur_wr_offset;
+	}
+
+	memcpy(&log_buf->log_data[cur_wr_offset],
+	       hdr_arg->pktlog_hdr, pl_info->hdr_size);
+
+	cur_wr_offset += pl_info->hdr_size;
+
+	if ((buf_size - cur_wr_offset) < hdr_arg->payload_size) {
+		while ((cur_wr_offset <= log_buf->rd_offset) &&
+		       (log_buf->rd_offset < buf_size))
+			  ath_pktlog_mov_rd_idx(pl_info, &log_buf->rd_offset);
+		cur_wr_offset = 0;
+	}
+
+	while ((cur_wr_offset <= log_buf->rd_offset) &&
+	       ((cur_wr_offset + hdr_arg->payload_size) > log_buf->rd_offset))
+			  ath_pktlog_mov_rd_idx(pl_info, &log_buf->rd_offset);
+
+	log_ptr = &log_buf->log_data[cur_wr_offset];
+
+	cur_wr_offset += hdr_arg->payload_size;
+
+	log_buf->wr_offset =
+		((buf_size - cur_wr_offset) >=
+		 pl_info->hdr_size) ? cur_wr_offset : 0;
+	spin_unlock_bh(&pl_info->lock);
+
+	return log_ptr;
+}
+
+static int  pktlog_pgfault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	unsigned long address = (unsigned long)vmf->virtual_address;
+
+	if (address == 0UL)
+		return VM_FAULT_NOPAGE;
+
+	if (vmf->pgoff > vma->vm_end)
+		return VM_FAULT_SIGBUS;
+
+	get_page(virt_to_page(address));
+	vmf->page = virt_to_page(address);
+	return VM_FAULT_MINOR;
+}
+
+static struct vm_operations_struct pktlog_vmops = {
+	.fault = pktlog_pgfault
+};
+
+static int ath_pktlog_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct ath10k *ar = file->private_data;
+
+	/* entire buffer should be mapped */
+	if (vma->vm_pgoff != 0)
+		return -EINVAL;
+
+	if (!ar->debug.pktlog.buf) {
+		pr_err("Can't allocate pktlog buf");
+		return -ENOMEM;
+	}
+
+	vma->vm_flags |= VM_LOCKED;
+	vma->vm_ops = &pktlog_vmops;
+
+	return 0;
+}
+
+static ssize_t ath_pktlog_read(struct file *file, char __user *userbuf,
+			       size_t count, loff_t *ppos)
+{
+	size_t bufhdr_size;
+	size_t nbytes = 0, ret_val = 0;
+	int rem_len;
+	int start_offset, end_offset;
+	int fold_offset, ppos_data, cur_rd_offset;
+	struct ath10k *ar = file->private_data;
+	struct ath_pktlog *info = &ar->debug.pktlog;
+	struct ath_pktlog_buf *log_buf = info->buf;
+
+	if (log_buf == NULL)
+		return 0;
+
+	bufhdr_size = sizeof(log_buf->bufhdr);
+
+	/* copy valid log entries from circular buffer into user space */
+	rem_len = count;
+
+	nbytes = 0;
+
+	if (*ppos < bufhdr_size) {
+		nbytes = min((int)(bufhdr_size -  *ppos), rem_len);
+		if (copy_to_user(userbuf,
+				 ((char *)&log_buf->bufhdr) + *ppos, nbytes))
+			return -EFAULT;
+		rem_len -= nbytes;
+		ret_val += nbytes;
+	}
+
+	spin_lock_bh(&info->lock);
+
+	start_offset = log_buf->rd_offset;
+
+	if ((rem_len == 0) || (start_offset < 0))
+		goto read_done;
+
+	fold_offset = -1;
+	cur_rd_offset = start_offset;
+
+	/* Find the last offset and fold-offset if the buffer is folded */
+	do {
+		int log_data_offset;
+		u16 cur_rec_hdr_size_offset, payload_size;
+
+		cur_rec_hdr_size_offset = cur_rd_offset +
+					  info->hdr_size_field_offset;
+		payload_size = *(u16 *)&log_buf->log_data[cur_rec_hdr_size_offset];
+
+		log_data_offset = cur_rd_offset + info->hdr_size;
+
+		if ((fold_offset == -1) &&
+		    ((info->buf_size - log_data_offset) <= payload_size))
+			fold_offset = log_data_offset - 1;
+
+		ath_pktlog_mov_rd_idx(info, &cur_rd_offset);
+
+		if ((fold_offset == -1) && (cur_rd_offset == 0) &&
+		    (cur_rd_offset != log_buf->wr_offset))
+			fold_offset = log_data_offset + payload_size - 1;
+
+		end_offset = log_data_offset + payload_size - 1;
+
+	} while (cur_rd_offset != log_buf->wr_offset);
+
+	ppos_data = *ppos + ret_val - bufhdr_size + start_offset;
+
+	if (fold_offset == -1) {
+		if (ppos_data > end_offset)
+			goto read_done;
+
+		nbytes = min(rem_len, end_offset - ppos_data + 1);
+		if (copy_to_user(userbuf + ret_val,
+				 log_buf->log_data + ppos_data, nbytes)) {
+			ret_val = -EFAULT;
+			goto out;
+		}
+		ret_val += nbytes;
+		rem_len -= nbytes;
+	} else {
+		if (ppos_data <= fold_offset) {
+			nbytes = min(rem_len, fold_offset - ppos_data + 1);
+			if (copy_to_user(userbuf + ret_val,
+					 log_buf->log_data + ppos_data,	nbytes)) {
+				ret_val = -EFAULT;
+				goto out;
+			}
+			ret_val += nbytes;
+			rem_len -= nbytes;
+		}
+
+		if (rem_len == 0)
+			goto read_done;
+
+		ppos_data =
+			*ppos + ret_val - (bufhdr_size +
+					(fold_offset - start_offset + 1));
+
+		if (ppos_data <= end_offset) {
+			nbytes = min(rem_len, end_offset - ppos_data + 1);
+			if (copy_to_user(userbuf + ret_val, log_buf->log_data
+					 + ppos_data,
+					 nbytes)) {
+				ret_val = -EFAULT;
+				goto out;
+			}
+			ret_val += nbytes;
+			rem_len -= nbytes;
+		}
+	}
+
+read_done:
+	*ppos += ret_val;
+out:
+	spin_unlock_bh(&info->lock);
+	return ret_val;
+}
+
+static const struct file_operations fops_pktlog_dump = {
+	.read = ath_pktlog_read,
+	.mmap = ath_pktlog_mmap,
+	.open = simple_open
+};
+
+static ssize_t write_pktlog_start(struct file *file, const char __user *ubuf,
+				  size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	struct ath_pktlog *pktlog = &ar->debug.pktlog;
+	u32 start_pktlog;
+	int err;
+
+	err = kstrtou32_from_user(ubuf, count, 0, &start_pktlog);
+	if (err)
+		return err;
+
+	if (ar->debug.is_pkt_logging && start_pktlog) {
+		pr_err("packet logging is inprogress\n");
+		return -EINVAL;
+	}
+	if (start_pktlog) {
+		if (pktlog->buf != NULL)
+			ath_pktlog_release(pktlog);
+
+		err = ath_alloc_pktlog_buf(ar);
+		if (err != 0)
+			return err;
+
+		ath_init_pktlog_buf(pktlog);
+		pktlog->buf->rd_offset = -1;
+		pktlog->buf->wr_offset = 0;
+		ar->debug.is_pkt_logging = 1;
+
+		ath10k_wmi_pdev_pktlog_enable(ar, pktlog->filter);
+	} else {
+		ath10k_wmi_pdev_pktlog_disable(ar);
+		ar->debug.is_pkt_logging = 0;
+	}
+
+	return count;
+}
+
+static ssize_t read_pktlog_start(struct file *file, char __user *ubuf,
+				 size_t count, loff_t *ppos)
+{
+	char buf[32];
+	struct ath10k *ar = file->private_data;
+	int len = 0;
+
+	len = scnprintf(buf, sizeof(buf) - len, "%d\n",
+			ar->debug.is_pkt_logging);
+	return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_pktlog_start = {
+	.read = read_pktlog_start,
+	.write = write_pktlog_start,
+	.open = simple_open
+};
+
+static ssize_t pktlog_size_write(struct file *file, const char __user *ubuf,
+				 size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	u32 pktlog_size;
+
+	if (kstrtou32_from_user(ubuf, count, 0, &pktlog_size))
+		return -EINVAL;
+
+	if (pktlog_size == ar->debug.pktlog.buf_size)
+		return count;
+
+	if (ar->debug.is_pkt_logging) {
+		pr_debug("Stop packet logging before changing the size\n");
+		return -EINVAL;
+	}
+
+	ar->debug.pktlog.buf_size = pktlog_size;
+
+	return count;
+}
+
+static ssize_t pktlog_size_read(struct file *file, char __user *ubuf,
+				size_t count, loff_t *ppos)
+{
+	char buf[32];
+	struct ath10k *ar = file->private_data;
+	int len = 0;
+
+	len = scnprintf(buf, sizeof(buf) - len, "%uL\n",
+			ar->debug.pktlog.buf_size);
+	return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_pktlog_size = {
+	.read = pktlog_size_read,
+	.write = pktlog_size_write,
+	.open = simple_open
+};
+
+static ssize_t pktlog_filter_write(struct file *file, const char __user *ubuf,
+				   size_t count, loff_t *ppos)
+{
+	struct ath10k *ar = file->private_data;
+	u32 filter;
+
+	if (kstrtou32_from_user(ubuf, count, 0, &filter))
+		return -EINVAL;
+
+	ar->debug.pktlog.filter = filter;
+
+	return count;
+}
+
+static ssize_t  pktlog_filter_read(struct file *file, char __user *ubuf,
+				   size_t count, loff_t *ppos)
+{
+	char buf[32];
+	struct ath10k *ar = file->private_data;
+	int len = 0;
+
+	len = scnprintf(buf, sizeof(buf) - len, "%08x\n",
+			ar->debug.pktlog.filter);
+
+	return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static const struct file_operations fops_pktlog_filter = {
+	.read = pktlog_filter_read,
+	.write = pktlog_filter_write,
+	.open = simple_open
+};
+
+static void ath_pktlog_init(struct ath10k *ar)
+{
+	spin_lock_init(&ar->debug.pktlog.lock);
+	ar->debug.pktlog.buf_size = ATH_DEBUGFS_PKTLOG_SIZE_DEFAULT;
+	ar->debug.pktlog.buf = NULL;
+	ar->debug.pktlog.filter = 0;
+}
+
+static void ath_pktlog_frame_header(void *data)
+{
+	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)data;
+
+	frame_header.framectrl = hdr->frame_control;
+	frame_header.seqctrl   = hdr->seq_ctrl;
+	if (ieee80211_has_tods(hdr->frame_control)) {
+		frame_header.bssid_tail = (hdr->addr1[ETH_ALEN - 2] << 8) |
+					  (hdr->addr1[ETH_ALEN - 1]);
+		frame_header.sa_tail    = (hdr->addr2[ETH_ALEN - 2] << 8) |
+					  (hdr->addr2[ETH_ALEN - 1]);
+		frame_header.da_tail    = (hdr->addr3[ETH_ALEN - 2] << 8) |
+					  (hdr->addr3[ETH_ALEN - 1]);
+	} else if (ieee80211_has_fromds(hdr->frame_control)) {
+		frame_header.bssid_tail = (hdr->addr2[ETH_ALEN - 2] << 8) |
+					  (hdr->addr2[ETH_ALEN - 1]);
+		frame_header.sa_tail    = (hdr->addr3[ETH_ALEN - 2] << 8) |
+					  (hdr->addr3[ETH_ALEN - 1]);
+		frame_header.da_tail    = (hdr->addr1[ETH_ALEN - 2] << 8) |
+					  (hdr->addr1[ETH_ALEN - 1]);
+	} else {
+		frame_header.bssid_tail = (hdr->addr3[ETH_ALEN - 2] << 8) |
+					  (hdr->addr3[ETH_ALEN - 1]);
+		frame_header.sa_tail    = (hdr->addr2[ETH_ALEN - 2] << 8) |
+					  (hdr->addr2[ETH_ALEN - 1]);
+		frame_header.da_tail    = (hdr->addr1[ETH_ALEN - 2] << 8) |
+					  (hdr->addr1[ETH_ALEN - 1]);
+	}
+}
+
+static int ath_pktlog_10_4_op_pull_hdr(struct ath_pktlog_hdr_arg *arg,
+				       u8 *data)
+{
+	struct ath_pktlog_10_4_hdr *hdr = (struct ath_pktlog_10_4_hdr *)data;
+
+	hdr->flags = __le16_to_cpu(hdr->flags);
+	hdr->missed_cnt = __le16_to_cpu(hdr->missed_cnt);
+	hdr->log_type = __le16_to_cpu(hdr->log_type);
+	hdr->size = __le16_to_cpu(hdr->size);
+	hdr->timestamp = __le32_to_cpu(hdr->timestamp);
+	hdr->type_specific_data = __le32_to_cpu(hdr->type_specific_data);
+
+	arg->log_type = hdr->log_type;
+	arg->payload = hdr->payload;
+	arg->payload_size = hdr->size;
+	arg->pktlog_hdr = data;
+
+	return 0;
+}
+
+static int ath_pktlog_op_pull_hdr(struct ath_pktlog_hdr_arg *arg,
+				  u8 *data)
+{
+	struct ath_pktlog_hdr *hdr = (struct ath_pktlog_hdr *)data;
+
+	hdr->flags = __le16_to_cpu(hdr->flags);
+	hdr->missed_cnt = __le16_to_cpu(hdr->missed_cnt);
+	hdr->log_type = __le16_to_cpu(hdr->log_type);
+	hdr->size = __le16_to_cpu(hdr->size);
+	hdr->timestamp = __le32_to_cpu(hdr->timestamp);
+
+	arg->log_type = hdr->log_type;
+	arg->payload = hdr->payload;
+	arg->payload_size = hdr->size;
+	arg->pktlog_hdr = data;
+
+	return 0;
+}
+
+static int ath_pktlog_10_4_op_type_tx_virt_addr(struct ath10k *ar, u8 *data)
+{
+	struct ath10k_vif *arvif;
+	u32 desc_id = *(u32 *)data;
+	u32 vdev_id;
+
+	/* vdev id is valid only if higher 8 bit has 0xff */
+	if (desc_id >> 24 == 0xFF) {
+
+		vdev_id = desc_id & 0xFFFFFF;
+
+		arvif = ath10k_get_arvif(ar, vdev_id);
+		if (!arvif) {
+			pr_err("no vif for vdev_id %d found\n", vdev_id);
+			return -EINVAL;
+		}
+
+		if (arvif->beacon_buf)
+			ath_pktlog_frame_header(arvif->beacon_buf);
+	}
+
+	return 0;
+}
+
+static int ath_pktlog_op_type_tx_virt_addr(struct ath10k *ar, u8 *data)
+{
+	struct sk_buff *skb = (struct sk_buff *) *((u32 *)(data));
+
+	if (skb)
+		ath_pktlog_frame_header(skb->data);
+
+	return 0;
+}
+
+static void ath_pktlog_op_type_txctl(struct ath_pktlog *pl_info,
+				      struct ath_pktlog_hdr_arg *hdr_arg)
+{
+	struct ath_pktlog_txctl *tx_log;
+
+	if (!(pl_info->filter & ATH_PKTLOG_TX))
+		return;
+
+	*(u16 *)(hdr_arg->pktlog_hdr + pl_info->hdr_size_field_offset) = sizeof(*tx_log);
+	hdr_arg->payload_size = sizeof(*tx_log);
+
+	tx_log = (struct ath_pktlog_txctl *)
+			ath_pktlog_getbuf(pl_info, hdr_arg);
+
+	memset(tx_log, 0, sizeof(*tx_log));
+
+	memcpy(&tx_log->frm_hdr, &frame_header, sizeof(tx_log->frm_hdr));
+	memcpy(tx_log->txdesc_ctl, hdr_arg->payload, sizeof(tx_log->txdesc_ctl));
+}
+
+static void ath_pktlog_10_4_op_type_txctl(struct ath_pktlog *pl_info,
+					   struct ath_pktlog_hdr_arg *hdr_arg)
+{
+	struct ath_pktlog_10_4_txctl *tx_log;
+
+	if (!(pl_info->filter & ATH_PKTLOG_TX))
+		return;
+
+	*(u16 *)(hdr_arg->pktlog_hdr + pl_info->hdr_size_field_offset) = sizeof(*tx_log);
+	hdr_arg->payload_size = sizeof(*tx_log);
+
+	tx_log = (struct ath_pktlog_10_4_txctl *)
+			ath_pktlog_getbuf(pl_info, hdr_arg);
+
+	memset(tx_log, 0, sizeof(*tx_log));
+
+	memcpy(&tx_log->frm_hdr, &frame_header, sizeof(tx_log->frm_hdr));
+	memcpy(tx_log->txdesc_ctl, hdr_arg->payload, sizeof(tx_log->txdesc_ctl));
+}
+
+static void ath_pktlog_op_pull_msdu_id(struct ath_pktlog_hdr_arg *hdr_arg,
+				       struct msdu_id_info_arg *info_arg)
+{
+	struct ath_pktlog_hdr *hdr;
+	struct msdu_id_info *info;
+
+	hdr = (struct ath_pktlog_hdr *)hdr_arg->pktlog_hdr;
+	info = (struct msdu_id_info *)hdr_arg->payload;
+
+	hdr->size = sizeof(*info);
+	hdr_arg->payload_size = sizeof(*info);
+
+	info_arg->num_msdu = info->msdu_info.num_msdu;
+	info_arg->bound_bmap = info->msdu_info.bound_bmap;
+	info_arg->id = info->id;
+	info_arg->msdu_info_size = sizeof(info->msdu_info);
+	info_arg->hdr_flags = hdr->flags;
+	info_arg->max_msdu = MAX_PKT_INFO_MSDU_ID;
+}
+
+static void ath_pktlog_10_4_op_pull_msdu_id(struct ath_pktlog_hdr_arg *hdr_arg,
+					    struct msdu_id_info_arg *info_arg)
+{
+	struct ath_pktlog_10_4_hdr *hdr;
+	struct msdu_id_10_4_info *info;
+
+	hdr = (struct ath_pktlog_10_4_hdr *)hdr_arg->pktlog_hdr;
+	info = (struct msdu_id_10_4_info *)hdr_arg->payload;
+
+	hdr->size = sizeof(*info);
+	hdr_arg->payload_size = sizeof(*info);
+
+	info_arg->num_msdu = info->msdu_info.num_msdu;
+	info_arg->bound_bmap = info->msdu_info.bound_bmap;
+	info_arg->id = info->id;
+	info_arg->msdu_info_size = sizeof(info->msdu_info);
+	info_arg->hdr_flags = hdr->flags;
+	info_arg->max_msdu = MAX_10_4_PKT_INFO_MSDU_ID;
+}
+
+void ath10k_pktlog_op_rx_process(struct ath_pktlog *info, u8 *data)
+{
+	struct htt_rx_desc *rx_desc;
+	struct ath_pktlog_hdr hdr;
+	struct ath_pktlog_hdr_arg hdr_arg;
+	char *log_data;
+
+	rx_desc = (struct htt_rx_desc *)(data - sizeof(*rx_desc));
+
+	hdr.flags = (1 << PKTLOG_FLG_FRM_TYPE_REMOTE_S);
+	hdr.missed_cnt = 0;
+	hdr.log_type = ATH_PKTLOG_TYPE_RX_STAT;
+	hdr.timestamp = rx_desc->ppdu_end.common.tsf_timestamp;
+	hdr.size = 248; /* rx desc size (excluding fw_rx_desc_base) */
+
+	hdr_arg.payload_size = hdr.size;
+	hdr_arg.pktlog_hdr = (u8 *)&hdr;
+
+	log_data = ath_pktlog_getbuf(info, &hdr_arg);
+	if (!log_data)
+		return;
+
+	/* The same rx desc structure (htt_rx_desc) is expanded for
+	 * all the chipsets. This expansion adds few variable in
+	 * htt_rx_desc here and there. It's very safe to copy each
+	 * sub descriptors (attention, frag_info,etc) individually
+	 * with applicable size. This copy method will avoid
+	 * regression if any new member gets added to htt_rx_desc
+	 * in future.
+	 */
+	memcpy(log_data, &rx_desc->attention, 4);
+	log_data += 4;
+
+	memcpy(log_data, &rx_desc->frag_info, 4);
+	log_data += 4;
+
+	memcpy(log_data, &rx_desc->mpdu_start, 12);
+	log_data += 12;
+
+	memcpy(log_data, &rx_desc->msdu_start, 12);
+	log_data += 12;
+
+	memcpy(log_data, &rx_desc->msdu_end, 20);
+	log_data += 20;
+
+	memcpy(log_data, &rx_desc->mpdu_end, 4);
+	log_data += 4;
+
+	memcpy(log_data, &rx_desc->ppdu_start, 40);
+	log_data += 40;
+
+	memcpy(log_data, &rx_desc->ppdu_end, 88);
+	log_data += 88;
+
+	memcpy(log_data, &rx_desc->rx_hdr_status, 64);
+}
+
+void ath10k_pktlog_10_4_op_rx_process(struct ath_pktlog *info, u8 *data)
+{
+	struct htt_rx_desc *rx_desc;
+	struct ath_pktlog_10_4_hdr hdr;
+	struct ath_pktlog_hdr_arg hdr_arg;
+	char *log_data;
+
+	rx_desc = (struct htt_rx_desc *)(data - sizeof(*rx_desc));
+
+	hdr.flags = (1 << PKTLOG_FLG_FRM_TYPE_REMOTE_S);
+	hdr.missed_cnt = 0;
+	hdr.log_type = ATH_PKTLOG_TYPE_RX_STAT;
+	hdr.timestamp = rx_desc->ppdu_end.common.tsf_timestamp;
+	hdr.size = 292; /* rx desc size (excluding fw_rx_desc_base) */
+	hdr.type_specific_data = 0;
+
+	hdr_arg.payload_size = hdr.size;
+	hdr_arg.pktlog_hdr = (u8 *)&hdr;
+
+	log_data = ath_pktlog_getbuf(info, &hdr_arg);
+	if (!log_data)
+		return;
+
+	memcpy(log_data, &rx_desc->attention, 4);
+	log_data += 4;
+
+	memcpy(log_data, &rx_desc->frag_info, 4);
+	log_data += 4;
+
+	memcpy(log_data, &rx_desc->mpdu_start, 12);
+	log_data += 12;
+
+	memcpy(log_data, &rx_desc->msdu_start, 16);
+	log_data += 16;
+
+	memcpy(log_data, &rx_desc->msdu_end, 40);
+	log_data += 40;
+
+	memcpy(log_data, &rx_desc->mpdu_end, 4);
+	log_data += 4;
+
+	memcpy(log_data, &rx_desc->ppdu_start, 40);
+	log_data += 40;
+
+	memcpy(log_data, &rx_desc->ppdu_end, 108);
+	log_data += 108;
+
+	memcpy(log_data, &rx_desc->rx_hdr_status, 64);
+}
+
+static const struct pktlog_ops pktlog_10_4_ops = {
+	.pull_pktlog = ath_pktlog_10_4_op_pull_hdr,
+	.pull_tx_msdu_id = ath_pktlog_10_4_op_pull_msdu_id,
+	.type_tx_virt_addr = ath_pktlog_10_4_op_type_tx_virt_addr,
+	.type_txctl = ath_pktlog_10_4_op_type_txctl,
+	.rx_process = ath10k_pktlog_10_4_op_rx_process,
+};
+
+static const struct pktlog_ops pktlog_ops = {
+	.pull_pktlog = ath_pktlog_op_pull_hdr,
+	.pull_tx_msdu_id = ath_pktlog_op_pull_msdu_id,
+	.type_tx_virt_addr = ath_pktlog_op_type_tx_virt_addr,
+	.type_txctl = ath_pktlog_op_type_txctl,
+	.rx_process = ath10k_pktlog_op_rx_process,
+};
+
+void ath10k_pktlog_prepare_callbacks(struct ath10k *ar)
+{
+	struct ath_pktlog *pktlog = &ar->debug.pktlog;
+
+	switch (ar->running_fw->fw_file.wmi_op_version) {
+	case ATH10K_FW_WMI_OP_VERSION_10_4:
+		pktlog->ops = &pktlog_10_4_ops;
+		pktlog->hdr_size = sizeof(struct ath_pktlog_10_4_hdr);
+		pktlog->hdr_size_field_offset =
+				   offsetof(struct ath_pktlog_10_4_hdr, size);
+		break;
+	default:
+		pktlog->ops = &pktlog_ops;
+		pktlog->hdr_size = sizeof(struct ath_pktlog_hdr);
+		pktlog->hdr_size_field_offset =
+				   offsetof(struct ath_pktlog_hdr, size);
+		break;
+	}
+}
+
+void ath10k_init_pktlog(struct ath10k *ar)
+{
+	ar->debug.debugfs_pktlog = debugfs_create_dir("pktlog",
+						      ar->debug.debugfs_phy);
+	debugfs_create_file("start", S_IRUGO | S_IWUSR,
+			    ar->debug.debugfs_pktlog, ar, &fops_pktlog_start);
+	debugfs_create_file("size", S_IRUGO | S_IWUSR,
+			    ar->debug.debugfs_pktlog, ar, &fops_pktlog_size);
+	debugfs_create_file("filter", S_IRUGO | S_IWUSR,
+			    ar->debug.debugfs_pktlog, ar, &fops_pktlog_filter);
+	debugfs_create_file("dump", S_IRUGO | S_IWUSR,
+			    ar->debug.debugfs_pktlog, ar, &fops_pktlog_dump);
+	ath_pktlog_init(ar);
+
+	ath10k_pktlog_prepare_callbacks(ar);
+}
+
+void ath10k_deinit_pktlog(struct ath10k *ar)
+{
+	struct ath_pktlog *pktlog = &ar->debug.pktlog;
+
+	if (pktlog->buf != NULL)
+		ath_pktlog_release(pktlog);
+}
+
+static void ath_pktlog_write_buf(struct ath_pktlog *pl_info,
+				 struct ath_pktlog_hdr_arg *hdr_arg,
+				 u32 filter)
+{
+	char *log_data;
+
+	if (!(pl_info->filter & filter))
+		return;
+
+	log_data = ath_pktlog_getbuf(pl_info, hdr_arg);
+	if (!log_data)
+		return;
+
+	memcpy(log_data, hdr_arg->payload, hdr_arg->payload_size);
+}
+
+static void ath_pktlog_txmsdu(struct ath10k *ar,
+			      struct ath_pktlog_hdr_arg *hdr_arg)
+{
+	struct ath_pktlog *pl_info = &ar->debug.pktlog;
+	struct ath10k_htt *htt = &ar->htt;
+	struct msdu_id_info_arg info_arg;
+	u16 msdu_len_list[MAX_PKT_INFO_MSDU_ID_COMMON], id;
+	struct sk_buff *msdu;
+	u32 i;
+	char *log_data;
+
+	if (!(pl_info->filter & ATH_PKTLOG_TX))
+		return;
+
+	ath_pktlog_pull_tx_msdu_id(pl_info, hdr_arg, &info_arg);
+
+	if (info_arg.max_msdu > MAX_PKT_INFO_MSDU_ID_COMMON) {
+		WARN_ON_ONCE(1);
+		info_arg.max_msdu = MAX_PKT_INFO_MSDU_ID_COMMON;
+	}
+
+	log_data = ath_pktlog_getbuf(pl_info, hdr_arg);
+	if (!log_data)
+		return;
+
+	memset(log_data, 0, hdr_arg->payload_size);
+	/* Copy the data until msdu_id offset */
+	memcpy(log_data, hdr_arg->payload, info_arg.msdu_info_size);
+	log_data += info_arg.msdu_info_size;
+
+	for (i = 0; i < info_arg.num_msdu; i++) {
+		id = __le16_to_cpu(info_arg.id[i]);
+		spin_lock_bh(&htt->tx_lock);
+		if (id >= htt->max_num_pending_tx) {
+			spin_unlock_bh(&htt->tx_lock);
+			continue;
+		}
+		msdu = idr_find(&htt->pending_tx, id);
+		if (!msdu) {
+			spin_unlock_bh(&htt->tx_lock);
+			continue;
+		}
+		msdu_len_list[i] =
+			(info_arg.hdr_flags & (1 << PKTLOG_FLG_FRM_TYPE_CLONE_S)) ?
+				200 : (u16)msdu->len;
+		spin_unlock_bh(&htt->tx_lock);
+	}
+
+	memcpy(log_data, msdu_len_list, (info_arg.max_msdu * 2));
+}
+
+void ath10k_process_pktlog(struct ath10k *ar, u8 *data)
+{
+	struct ath_pktlog *pl_info = &ar->debug.pktlog;
+	struct ath_pktlog_hdr_arg hdr_arg;
+	int ret;
+
+	if  (!ar->debug.is_pkt_logging)
+		return;
+
+	ret = ath_pktlog_pull_hdr(pl_info, &hdr_arg, data);
+	if (ret) {
+		pr_err("pktlog pull hdr failed: %d\n", ret);
+		return;
+	}
+
+	switch (hdr_arg.log_type) {
+	case ATH_PKTLOG_TYPE_TX_FRM_HDR:
+		ath_pktlog_frame_header(hdr_arg.payload);
+		break;
+	case ATH_PKTLOG_TYPE_TX_VIRT_ADDR:
+		ret = ath_pktlog_type_tx_virt_addr(ar, pl_info,
+						   hdr_arg.payload);
+		if (ret)
+			pr_err("failed to handle pktlog type tx virt addr: %d\n",
+			       ret);
+		break;
+	case ATH_PKTLOG_TYPE_TX_CTRL:
+		ath_pktlog_type_txctl(pl_info, &hdr_arg);
+		break;
+	case ATH_PKTLOG_TYPE_TX_STAT:
+		ath_pktlog_write_buf(pl_info, &hdr_arg, ATH_PKTLOG_TX);
+		break;
+	case ATH_PKTLOG_TYPE_TX_MSDU_ID:
+		ath_pktlog_txmsdu(ar, &hdr_arg);
+		break;
+	case ATH_PKTLOG_TYPE_RC_FIND:
+		ath_pktlog_write_buf(pl_info, &hdr_arg, ATH_PKTLOG_RCFIND);
+		break;
+	case ATH_PKTLOG_TYPE_RC_UPDATE:
+		ath_pktlog_write_buf(pl_info, &hdr_arg, ATH_PKTLOG_RCUPDATE);
+		break;
+	case ATH_PKTLOG_TYPE_RX_STAT:
+		ath_pktlog_write_buf(pl_info, &hdr_arg, ATH_PKTLOG_RX);
+		break;
+	default:
+		break;
+	}
+}
+
+void ath10k_pktlog_rx(struct ath10k *ar, struct sk_buff_head *amsdu)
+{
+	struct ath_pktlog *pl_info = &ar->debug.pktlog;
+	struct sk_buff *msdu;
+
+	if (!(pl_info->filter & ATH_PKTLOG_RX) || !ar->debug.is_pkt_logging)
+		return;
+
+	if (skb_queue_empty(amsdu))
+		return;
+
+	skb_queue_walk(amsdu, msdu)
+		ath10k_pktlog_rx_process(pl_info, msdu->data);
+}
--- /dev/null
+++ b/drivers/net/wireless/ath/ath10k/pktlog.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * 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 _PKTLOG_H_
+#define _PKTLOG_H_
+
+#ifdef CPTCFG_ATH10K_PKTLOG
+#define CUR_PKTLOG_VER          10010  /* Packet log version */
+#define PKTLOG_MAGIC_NUM        7735225
+
+/* Masks for setting pktlog events filters */
+#define ATH_PKTLOG_RX		0x000000001
+#define ATH_PKTLOG_TX		0x000000002
+#define ATH_PKTLOG_RCFIND	0x000000004
+#define ATH_PKTLOG_RCUPDATE	0x000000008
+
+#define ATH_DEBUGFS_PKTLOG_SIZE_DEFAULT (4 * 1024 * 1024)
+#define ATH_PKTLOG_FILTER_DEFAULT (ATH_PKTLOG_TX | ATH_PKTLOG_RX | \
+				   ATH_PKTLOG_RCFIND | ATH_PKTLOG_RCUPDATE)
+
+/* Types of packet log events */
+enum ath_pktlog_type {
+	ATH_PKTLOG_TYPE_TX_CTRL = 1,
+	ATH_PKTLOG_TYPE_TX_STAT,
+	ATH_PKTLOG_TYPE_TX_MSDU_ID,
+	ATH_PKTLOG_TYPE_TX_FRM_HDR,
+	ATH_PKTLOG_TYPE_RX_STAT,
+	ATH_PKTLOG_TYPE_RC_FIND,
+	ATH_PKTLOG_TYPE_RC_UPDATE,
+	ATH_PKTLOG_TYPE_TX_VIRT_ADDR,
+	ATH_PKTLOG_TYPE_DBG_PRINT,
+	ATH_PKTLOG_TYPE_MAX,
+};
+
+enum {
+	PKTLOG_FLG_FRM_TYPE_LOCAL_S = 0,
+	PKTLOG_FLG_FRM_TYPE_REMOTE_S,
+	PKTLOG_FLG_FRM_TYPE_CLONE_S,
+	PKTLOG_FLG_FRM_TYPE_UNKNOWN_S
+};
+
+struct ath_pktlog_hdr {
+	__le16 flags;
+	__le16 missed_cnt;
+	__le16 log_type; /* Type of log information foll this header */
+	__le16 size; /* Size of variable length log information in bytes */
+	__le32 timestamp;
+	u8 payload[0];
+}  __packed;
+
+struct ath_pktlog_10_4_hdr {
+	__le16 flags;
+	__le16 missed_cnt;
+	__le16 log_type;
+	__le16 size;
+	__le32 timestamp;
+	__le32 type_specific_data;
+	u8 payload[0];
+}  __packed;
+
+struct ath_pktlog_hdr_arg {
+	u16 log_type;
+	u8 *payload;
+	u16 payload_size;
+	u8 *pktlog_hdr;
+};
+
+#define MAX_PKT_INFO_MSDU_ID 192
+#define PKTLOG_MAX_TXCTL_WORDS 57
+
+struct ath_pktlog_frm_hdr {
+	__le16 framectrl;       /* frame control field from header */
+	__le16 seqctrl;         /* frame control field from header */
+	u16 bssid_tail;      /* last two octets of bssid */
+	u16 sa_tail;         /* last two octets of SA */
+	u16 da_tail;         /* last two octets of DA */
+	u16 resvd;
+} __packed;
+
+struct ath_pktlog_txctl {
+	struct ath_pktlog_frm_hdr frm_hdr;
+	u32 txdesc_ctl[PKTLOG_MAX_TXCTL_WORDS];     /* Tx descriptor words */
+} __packed;
+
+#define PKTLOG_10_4_MAX_TXCTL_WORDS	153
+#define MAX_PKT_INFO_MSDU_ID_COMMON     MAX_PKT_INFO_MSDU_ID
+
+struct ath_pktlog_10_4_txctl {
+	struct ath_pktlog_frm_hdr frm_hdr;
+	u32 txdesc_ctl[PKTLOG_10_4_MAX_TXCTL_WORDS];     /* Tx descriptor words */
+} __packed;
+
+struct ath_pktlog_bufhdr {
+	u32 magic_num;  /* Used by post processing scripts */
+	u32 version;    /* Set to CUR_PKTLOG_VER */
+};
+
+struct msdu_id_info {
+	struct {
+		u32 num_msdu;
+		u8 bound_bmap[MAX_PKT_INFO_MSDU_ID >> 3];
+	} msdu_info;
+	u16 id[MAX_PKT_INFO_MSDU_ID];
+} __packed;
+
+#define MAX_10_4_PKT_INFO_MSDU_ID 1
+struct msdu_id_10_4_info {
+	struct {
+		u32 num_msdu;
+		u8 bound_bmap[(MAX_10_4_PKT_INFO_MSDU_ID + 7) >> 3];
+	} msdu_info;
+	u16 id[MAX_10_4_PKT_INFO_MSDU_ID];
+} __packed;
+
+struct msdu_id_info_arg {
+	u32 num_msdu;
+	u8 *bound_bmap;
+	u16 *id;
+	u8 msdu_info_size;
+	u8 max_msdu;
+	__le16 hdr_flags;
+};
+
+struct ath_pktlog_buf {
+	struct ath_pktlog_bufhdr bufhdr;
+	int32_t rd_offset;
+	int32_t wr_offset;
+	char log_data[0];
+};
+
+struct pktlog_ops;
+
+struct ath_pktlog {
+	struct ath_pktlog_buf *buf;
+	u32 filter;
+	u32 buf_size;           /* Size of buffer in bytes */
+	spinlock_t lock;
+	const struct pktlog_ops *ops;
+	u8 hdr_size;
+	u8 hdr_size_field_offset;
+};
+
+struct pktlog_ops {
+	int (*pull_pktlog)(struct ath_pktlog_hdr_arg *arg, u8 *data);
+	void (*pull_tx_msdu_id)(struct ath_pktlog_hdr_arg *hdr_arg,
+			        struct msdu_id_info_arg *info_arg);
+	int (*type_tx_virt_addr)(struct ath10k *ar, u8 *data);
+	void (*type_txctl)(struct ath_pktlog *pl_info,
+			   struct ath_pktlog_hdr_arg *hdr_arg);
+	void (*rx_process)(struct ath_pktlog *pl_info, u8 *data);
+};
+
+static inline int
+ath_pktlog_pull_hdr(struct ath_pktlog *pl_info, struct ath_pktlog_hdr_arg *arg,
+		    u8 *data)
+{
+	if (!pl_info->ops->pull_pktlog)
+		return -EOPNOTSUPP;
+
+	return pl_info->ops->pull_pktlog(arg, data);
+}
+
+static inline int
+ath_pktlog_type_tx_virt_addr(struct ath10k *ar, struct ath_pktlog *pl_info,
+			     u8 *data)
+{
+	if (!pl_info->ops->type_tx_virt_addr)
+		return -EOPNOTSUPP;
+
+	return pl_info->ops->type_tx_virt_addr(ar, data);
+}
+
+static inline int
+ath_pktlog_type_txctl(struct ath_pktlog *pl_info,
+		      struct ath_pktlog_hdr_arg *hdr_arg)
+{
+	if (!pl_info->ops->type_txctl)
+		return -EOPNOTSUPP;
+
+	pl_info->ops->type_txctl(pl_info, hdr_arg);
+	return 0;
+}
+
+static inline int
+ath_pktlog_pull_tx_msdu_id(struct ath_pktlog *pl_info,
+			   struct ath_pktlog_hdr_arg *hdr_arg,
+			   struct msdu_id_info_arg *info_arg)
+{
+	if (!pl_info->ops->pull_tx_msdu_id)
+		return -EOPNOTSUPP;
+
+	pl_info->ops->pull_tx_msdu_id(hdr_arg, info_arg);
+	return 0;
+}
+
+static inline int
+ath10k_pktlog_rx_process(struct ath_pktlog *pl_info, u8 *data)
+{
+	if (!pl_info->ops->rx_process)
+		return -EOPNOTSUPP;
+
+	pl_info->ops->rx_process(pl_info, data);
+	return 0;
+}
+
+void ath10k_init_pktlog(struct ath10k *ar);
+void ath10k_deinit_pktlog(struct ath10k *ar);
+void ath10k_process_pktlog(struct ath10k *ar, u8 *data);
+void ath10k_pktlog_rx(struct ath10k *ar, struct sk_buff_head *amsdu);
+
+#else /* CPTCFG_ATH10K_PKTLOG */
+static inline void ath10k_init_pktlog(struct ath10k *ar)
+{
+}
+
+static inline void ath10k_deinit_pktlog(struct ath10k *ar)
+{
+}
+
+static inline void ath10k_process_pktlog(struct ath10k *ar, u8 *data)
+{
+}
+
+static inline void ath10k_pktlog_rx(struct ath10k *ar, struct sk_buff_head *amsdu)
+{
+}
+#endif /* CPTCFG_ATH10K_PKTLOG */
+
+#endif /* _PKTLOG_H_ */
--- a/drivers/net/wireless/ath/ath10k/debugfs_sta.c
+++ b/drivers/net/wireless/ath/ath10k/debugfs_sta.c
@@ -17,6 +17,7 @@
 #include "core.h"
 #include "wmi-ops.h"
 #include "debug.h"
+#include "hw.h"
 
 static void ath10k_sta_update_extd_stats_rx_duration(struct ath10k *ar,
 						     struct ath10k_fw_stats *stats)
--- a/drivers/net/wireless/ath/ath10k/hw.h
+++ b/drivers/net/wireless/ath/ath10k/hw.h
@@ -308,6 +308,25 @@ enum ath10k_mcast2ucast_mode {
 	ATH10K_MCAST2UCAST_ENABLED = 1,
 };
 
+struct ath10k_pktlog_hdr {
+	__le16 flags;
+	__le16 missed_cnt;
+	__le16 log_type;
+	__le16 size;
+	__le32 timestamp;
+	u8 payload[0];
+} __packed;
+
+struct ath10k_pktlog_10_4_hdr {
+	__le16 flags;
+	__le16 missed_cnt;
+	__le16 log_type;
+	__le16 size;
+	__le32 timestamp;
+	__le32 type_specific_data;
+	u8 payload[0];
+} __packed;
+
 enum ath10k_hw_rate_ofdm {
 	ATH10K_HW_RATE_OFDM_48M = 0,
 	ATH10K_HW_RATE_OFDM_24M,
