#include <linux/debugfs.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/poll.h>
 #include <linux/uaccess.h>
 
 #include "rt2x00.h"
 #include "rt2x00lib.h"
+#include "rt2x00dump.h"
 
 #define PRINT_LINE_LEN_MAX 32
 
         *     - eeprom offset/value files
         *     - bbp offset/value files
         *     - rf offset/value files
+        *   - frame dump folder
+        *     - frame dump file
         */
        struct dentry *driver_folder;
        struct dentry *driver_entry;
        struct dentry *bbp_val_entry;
        struct dentry *rf_off_entry;
        struct dentry *rf_val_entry;
+       struct dentry *frame_folder;
+       struct dentry *frame_dump_entry;
+
+       /*
+        * The frame dump file only allows a single reader,
+        * so we need to store the current state here.
+        */
+       unsigned long frame_dump_flags;
+#define FRAME_DUMP_FILE_OPEN   1
+
+       /*
+        * We queue each frame before dumping it to the user,
+        * per read command we will pass a single skb structure
+        * so we should be prepared to queue multiple sk buffers
+        * before sending it to userspace.
+        */
+       struct sk_buff_head frame_dump_skbqueue;
+       wait_queue_head_t frame_dump_waitqueue;
 
        /*
         * Driver and chipset files will use a data buffer
        unsigned int offset_rf;
 };
 
+void rt2x00debug_dump_frame(struct rt2x00_dev *rt2x00dev,
+                           struct sk_buff *skb)
+{
+       struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf;
+       struct skb_desc *desc = get_skb_desc(skb);
+       struct sk_buff *skbcopy;
+       struct rt2x00dump_hdr *dump_hdr;
+       struct timeval timestamp;
+       unsigned int ring_index;
+       unsigned int entry_index;
+
+       do_gettimeofday(×tamp);
+       ring_index = ARRAY_INDEX(desc->ring, rt2x00dev->rx);
+       entry_index = ARRAY_INDEX(desc->entry, desc->ring->entry);
+
+       if (!test_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags))
+               return;
+
+       if (skb_queue_len(&intf->frame_dump_skbqueue) > 20) {
+               DEBUG(rt2x00dev, "txrx dump queue length exceeded.\n");
+               return;
+       }
+
+       skbcopy = alloc_skb(sizeof(*dump_hdr) + desc->desc_len + desc->data_len,
+                           GFP_ATOMIC);
+       if (!skbcopy) {
+               DEBUG(rt2x00dev, "Failed to copy skb for dump.\n");
+               return;
+       }
+
+       dump_hdr = (struct rt2x00dump_hdr *)skb_put(skbcopy, sizeof(*dump_hdr));
+       dump_hdr->version = cpu_to_le32(DUMP_HEADER_VERSION);
+       dump_hdr->header_length = cpu_to_le32(sizeof(*dump_hdr));
+       dump_hdr->desc_length = cpu_to_le32(desc->desc_len);
+       dump_hdr->data_length = cpu_to_le32(desc->data_len);
+       dump_hdr->chip_rt = cpu_to_le16(rt2x00dev->chip.rt);
+       dump_hdr->chip_rf = cpu_to_le16(rt2x00dev->chip.rf);
+       dump_hdr->chip_rev = cpu_to_le32(rt2x00dev->chip.rev);
+       dump_hdr->type = cpu_to_le16(desc->frame_type);
+       dump_hdr->ring_index = ring_index;
+       dump_hdr->entry_index = entry_index;
+       dump_hdr->timestamp_sec = cpu_to_le32(timestamp.tv_sec);
+       dump_hdr->timestamp_usec = cpu_to_le32(timestamp.tv_usec);
+
+       memcpy(skb_put(skbcopy, desc->desc_len), desc->desc, desc->desc_len);
+       memcpy(skb_put(skbcopy, desc->data_len), desc->data, desc->data_len);
+
+       skb_queue_tail(&intf->frame_dump_skbqueue, skbcopy);
+       wake_up_interruptible(&intf->frame_dump_waitqueue);
+
+       /*
+        * Verify that the file has not been closed while we were working.
+        */
+       if (!test_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags))
+               skb_queue_purge(&intf->frame_dump_skbqueue);
+}
+
 static int rt2x00debug_file_open(struct inode *inode, struct file *file)
 {
        struct rt2x00debug_intf *intf = inode->i_private;
        return 0;
 }
 
+static int rt2x00debug_open_ring_dump(struct inode *inode, struct file *file)
+{
+       struct rt2x00debug_intf *intf = inode->i_private;
+       int retval;
+
+       retval = rt2x00debug_file_open(inode, file);
+       if (retval)
+               return retval;
+
+       if (test_and_set_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags)) {
+               rt2x00debug_file_release(inode, file);
+               return -EBUSY;
+       }
+
+       return 0;
+}
+
+static int rt2x00debug_release_ring_dump(struct inode *inode, struct file *file)
+{
+       struct rt2x00debug_intf *intf = inode->i_private;
+
+       skb_queue_purge(&intf->frame_dump_skbqueue);
+
+       clear_bit(FRAME_DUMP_FILE_OPEN, &intf->frame_dump_flags);
+
+       return rt2x00debug_file_release(inode, file);
+}
+
+static ssize_t rt2x00debug_read_ring_dump(struct file *file,
+                                         char __user *buf,
+                                         size_t length,
+                                         loff_t *offset)
+{
+       struct rt2x00debug_intf *intf = file->private_data;
+       struct sk_buff *skb;
+       size_t status;
+       int retval;
+
+       if (file->f_flags & O_NONBLOCK)
+               return -EAGAIN;
+
+       retval =
+           wait_event_interruptible(intf->frame_dump_waitqueue,
+                                    (skb =
+                                    skb_dequeue(&intf->frame_dump_skbqueue)));
+       if (retval)
+               return retval;
+
+       status = min((size_t)skb->len, length);
+       if (copy_to_user(buf, skb->data, status)) {
+               status = -EFAULT;
+               goto exit;
+       }
+
+       *offset += status;
+
+exit:
+       kfree_skb(skb);
+
+       return status;
+}
+
+static unsigned int rt2x00debug_poll_ring_dump(struct file *file,
+                                              poll_table *wait)
+{
+       struct rt2x00debug_intf *intf = file->private_data;
+
+       poll_wait(file, &intf->frame_dump_waitqueue, wait);
+
+       if (!skb_queue_empty(&intf->frame_dump_skbqueue))
+               return POLLOUT | POLLWRNORM;
+
+       return 0;
+}
+
+static const struct file_operations rt2x00debug_fop_ring_dump = {
+       .owner          = THIS_MODULE,
+       .read           = rt2x00debug_read_ring_dump,
+       .poll           = rt2x00debug_poll_ring_dump,
+       .open           = rt2x00debug_open_ring_dump,
+       .release        = rt2x00debug_release_ring_dump,
+};
+
 #define RT2X00DEBUGFS_OPS_READ(__name, __format, __type)       \
 static ssize_t rt2x00debug_read_##__name(struct file *file,    \
                                         char __user *buf,      \
 
 #undef RT2X00DEBUGFS_CREATE_REGISTER_ENTRY
 
+       intf->frame_folder =
+           debugfs_create_dir("frame", intf->driver_folder);
+       if (IS_ERR(intf->frame_folder))
+               goto exit;
+
+       intf->frame_dump_entry =
+           debugfs_create_file("dump", S_IRUGO, intf->frame_folder,
+                               intf, &rt2x00debug_fop_ring_dump);
+       if (IS_ERR(intf->frame_dump_entry))
+               goto exit;
+
+       skb_queue_head_init(&intf->frame_dump_skbqueue);
+       init_waitqueue_head(&intf->frame_dump_waitqueue);
+
        return;
 
 exit:
 
 void rt2x00debug_deregister(struct rt2x00_dev *rt2x00dev)
 {
-       const struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf;
+       struct rt2x00debug_intf *intf = rt2x00dev->debugfs_intf;
 
        if (unlikely(!intf))
                return;
 
+       skb_queue_purge(&intf->frame_dump_skbqueue);
+
+       debugfs_remove(intf->frame_dump_entry);
+       debugfs_remove(intf->frame_folder);
        debugfs_remove(intf->rf_val_entry);
        debugfs_remove(intf->rf_off_entry);
        debugfs_remove(intf->bbp_val_entry);
 
 
 #include "rt2x00.h"
 #include "rt2x00lib.h"
+#include "rt2x00dump.h"
 
 /*
  * Ring handler.
        }
 
        /*
-        * Send the tx_status to mac80211,
-        * that method also cleans up the skb structure.
+        * Send the tx_status to mac80211 & debugfs.
+        * mac80211 will clean up the skb structure.
         */
+       get_skb_desc(entry->skb)->frame_type = DUMP_FRAME_TXDONE;
+       rt2x00debug_dump_frame(rt2x00dev, entry->skb);
        ieee80211_tx_status_irqsafe(rt2x00dev->hw, entry->skb, tx_status);
        entry->skb = NULL;
 }
        rx_status->antenna = rt2x00dev->link.ant.active.rx;
 
        /*
-        * Send frame to mac80211
+        * Send frame to mac80211 & debugfs
         */
+       get_skb_desc(skb)->frame_type = DUMP_FRAME_RXDONE;
+       rt2x00debug_dump_frame(rt2x00dev, skb);
        ieee80211_rx_irqsafe(rt2x00dev->hw, skb, rx_status);
 }
 EXPORT_SYMBOL_GPL(rt2x00lib_rxdone);
         */
        skbdesc->entry->skb = skb;
        memcpy(&skbdesc->entry->tx_status.control, control, sizeof(*control));
+
+       /*
+        * The frame has been completely initialized and ready
+        * for sending to the device. The caller will push the
+        * frame to the device, but we are going to push the
+        * frame to debugfs here.
+        */
+       skbdesc->frame_type = DUMP_FRAME_TX;
+       rt2x00debug_dump_frame(rt2x00dev, skb);
 }
 EXPORT_SYMBOL_GPL(rt2x00lib_write_tx_desc);
 
 
--- /dev/null
+/*
+       Copyright (C) 2004 - 2007 rt2x00 SourceForge Project
+       <http://rt2x00.serialmonkey.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; if not, write to the
+       Free Software Foundation, Inc.,
+       59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+       Module: rt2x00dump
+       Abstract: Data structures for the rt2x00debug & userspace.
+ */
+
+#ifndef RT2X00DUMP_H
+#define RT2X00DUMP_H
+
+/**
+ * DOC: Introduction
+ *
+ * This header is intended to be exported to userspace,
+ * to make the structures and enumerations available to userspace
+ * applications. This means that all data types should be exportable.
+ *
+ * When rt2x00 is compiled with debugfs support enabled,
+ * it is possible to capture all data coming in and out of the device
+ * by reading the frame dump file. This file can have only a single reader.
+ * The following frames will be reported:
+ *   - All incoming frames (rx)
+ *   - All outgoing frames (tx, including beacon and atim)
+ *   - All completed frames (txdone including atim)
+ *
+ * The data is send to the file using the following format:
+ *
+ *   [rt2x00dump header][hardware descriptor][ieee802.11 frame]
+ *
+ * rt2x00dump header: The description of the dumped frame, as well as
+ *     additional information usefull for debugging. See &rt2x00dump_hdr.
+ * hardware descriptor: Descriptor that was used to receive or transmit
+ *     the frame.
+ * ieee802.11 frame: The actual frame that was received or transmitted.
+ */
+
+/**
+ * enum rt2x00_dump_type - Frame type
+ *
+ * These values are used for the @type member of &rt2x00dump_hdr.
+ * @DUMP_FRAME_RXDONE: This frame has been received by the hardware.
+ * @DUMP_FRAME_TX: This frame is queued for transmission to the hardware.
+ * @DUMP_FRAME_TXDONE: This frame indicates the device has handled
+ *     the tx event which has either succeeded or failed. A frame
+ *     with this type should also have been reported with as a
+ *     %DUMP_FRAME_TX frame.
+ */
+enum rt2x00_dump_type {
+       DUMP_FRAME_RXDONE = 1,
+       DUMP_FRAME_TX = 2,
+       DUMP_FRAME_TXDONE = 3,
+};
+
+/**
+ * struct rt2x00dump_hdr - Dump frame header
+ *
+ * Each frame dumped to the debugfs file starts with this header
+ * attached. This header contains the description of the actual
+ * frame which was dumped.
+ *
+ * New fields inside the structure must be appended to the end of
+ * the structure. This way userspace tools compiled for earlier
+ * header versions can still correctly handle the frame dump
+ * (although they will not handle all data passed to them in the dump).
+ *
+ * @version: Header version should always be set to %DUMP_HEADER_VERSION.
+ *     This field must be checked by userspace to determine if it can
+ *     handle this frame.
+ * @header_length: The length of the &rt2x00dump_hdr structure. This is
+ *     used for compatibility reasons so userspace can easily determine
+ *     the location of the next field in the dump.
+ * @desc_length: The length of the device descriptor.
+ * @data_length: The length of the frame data (including the ieee802.11 header.
+ * @chip_rt: RT chipset
+ * @chip_rf: RF chipset
+ * @chip_rev: Chipset revision
+ * @type: The frame type (&rt2x00_dump_type)
+ * @ring_index: The index number of the data ring.
+ * @entry_index: The index number of the entry inside the data ring.
+ * @timestamp_sec: Timestamp - seconds
+ * @timestamp_usec: Timestamp - microseconds
+ */
+struct rt2x00dump_hdr {
+       __le32 version;
+#define DUMP_HEADER_VERSION    2
+
+       __le32 header_length;
+       __le32 desc_length;
+       __le32 data_length;
+
+       __le16 chip_rt;
+       __le16 chip_rf;
+       __le32 chip_rev;
+
+       __le16 type;
+       __u8 ring_index;
+       __u8 entry_index;
+
+       __le32 timestamp_sec;
+       __le32 timestamp_usec;
+};
+
+#endif /* RT2X00DUMP_H */