rt2x00: Add TX/RX frame dumping facility
authorIvo van Doorn <ivdoorn@gmail.com>
Tue, 27 Nov 2007 20:49:29 +0000 (21:49 +0100)
committerDavid S. Miller <davem@davemloft.net>
Mon, 28 Jan 2008 23:05:08 +0000 (15:05 -0800)
This adds TX/RX frame dumping capabilities through debugfs.
The intention is that with this approach debugging of rt2x00 is
simplified since _all_ frames going in and out of the device
are send to debugfs as well along with additional information
like the hardware descriptor.

Based on the patch by Mattias Nissler.
Mattias also has some tools that will make the dumped frames
available to wireshark: http://www-user.rhrk.uni-kl.de/~nissler/rt2x00/

Signed-off-by: Ivo van Doorn <IvDoorn@gmail.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
drivers/net/wireless/rt2x00/rt2x00.h
drivers/net/wireless/rt2x00/rt2x00debug.c
drivers/net/wireless/rt2x00/rt2x00dev.c
drivers/net/wireless/rt2x00/rt2x00dump.h [new file with mode: 0644]
drivers/net/wireless/rt2x00/rt2x00lib.h
drivers/net/wireless/rt2x00/rt2x00pci.c
drivers/net/wireless/rt2x00/rt2x00usb.c

index 31e48c25a692f4ebeae44bf703c1b042d07672e0..ba874cff8648178a1058da05975842eea5655bc9 100644 (file)
@@ -623,7 +623,7 @@ struct rt2x00_dev {
         * required for deregistration of debugfs.
         */
 #ifdef CONFIG_RT2X00_LIB_DEBUGFS
-       const struct rt2x00debug_intf *debugfs_intf;
+       struct rt2x00debug_intf *debugfs_intf;
 #endif /* CONFIG_RT2X00_LIB_DEBUGFS */
 
        /*
@@ -790,6 +790,12 @@ struct rt2x00_dev {
 #define txringall_for_each(__dev, __entry) \
        ring_loop(__entry, (__dev)->tx, ring_end(__dev))
 
+/*
+ * Compute an array index from a pointer to an element and the base pointer.
+ */
+#define ARRAY_INDEX(__elem, __base) \
+       ( ((char *)(__elem) - (char *)(__base)) / sizeof(*(__elem)) )
+
 /*
  * Generic RF access.
  * The RF is being accessed by word index.
index 3aa7e0ab513bf47e05cf5829b757b4addbf3dffb..e72c98133c05b94f7d08208f2f91bd1944d44d5f 100644 (file)
 #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
 
@@ -58,6 +60,8 @@ struct rt2x00debug_intf {
         *     - 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;
@@ -72,6 +76,24 @@ struct rt2x00debug_intf {
        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
@@ -90,6 +112,63 @@ struct rt2x00debug_intf {
        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(&timestamp);
+       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;
@@ -111,6 +190,89 @@ static int rt2x00debug_file_release(struct inode *inode, struct file *file)
        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,      \
@@ -339,6 +501,20 @@ void rt2x00debug_register(struct rt2x00_dev *rt2x00dev)
 
 #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:
@@ -350,11 +526,15 @@ 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);
index 4f32ee8f4cb7aa40fbb2eedf8335b9a5ce83406d..48e251561f833fff36ef9afbeaee3f5817a0c1eb 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "rt2x00.h"
 #include "rt2x00lib.h"
+#include "rt2x00dump.h"
 
 /*
  * Ring handler.
@@ -511,9 +512,11 @@ void rt2x00lib_txdone(struct data_entry *entry,
        }
 
        /*
-        * 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;
 }
@@ -563,8 +566,10 @@ void rt2x00lib_rxdone(struct data_entry *entry, struct sk_buff *skb,
        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);
@@ -715,6 +720,15 @@ void rt2x00lib_write_tx_desc(struct rt2x00_dev *rt2x00dev,
         */
        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);
 
diff --git a/drivers/net/wireless/rt2x00/rt2x00dump.h b/drivers/net/wireless/rt2x00/rt2x00dump.h
new file mode 100644 (file)
index 0000000..99f3f36
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+       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 */
index 73194112f45c5031a76426b9d3957428f64a9a79..0bf10ffec70a832deff4fd7fa03ba2788ace51ec 100644 (file)
@@ -80,6 +80,7 @@ static inline void rt2x00lib_free_firmware(struct rt2x00_dev *rt2x00dev)
 #ifdef CONFIG_RT2X00_LIB_DEBUGFS
 void rt2x00debug_register(struct rt2x00_dev *rt2x00dev);
 void rt2x00debug_deregister(struct rt2x00_dev *rt2x00dev);
+void rt2x00debug_dump_frame(struct rt2x00_dev *rt2x00dev, struct sk_buff *skb);
 #else
 static inline void rt2x00debug_register(struct rt2x00_dev *rt2x00dev)
 {
@@ -88,6 +89,11 @@ static inline void rt2x00debug_register(struct rt2x00_dev *rt2x00dev)
 static inline void rt2x00debug_deregister(struct rt2x00_dev *rt2x00dev)
 {
 }
+
+static inline void rt2x00debug_dump_frame(struct rt2x00_dev *rt2x00dev,
+                                         struct skb_buff *skb)
+{
+}
 #endif /* CONFIG_RT2X00_LIB_DEBUGFS */
 
 /*
index c1d7c10e58fecc2413d9c1977a1019d1b5c11b62..483380819f9d929a4f150b177fb5d89a63612fc9 100644 (file)
@@ -178,8 +178,8 @@ void rt2x00pci_rxdone(struct rt2x00_dev *rt2x00dev)
                 * Fill in skb descriptor
                 */
                skbdesc = get_skb_desc(skb);
-               skbdesc->desc_len = desc.size;
-               skbdesc->data_len = entry->ring->desc_size;
+               skbdesc->desc_len = entry->ring->desc_size;
+               skbdesc->data_len = skb->len;
                skbdesc->desc = entry->priv;
                skbdesc->data = skb->data;
                skbdesc->ring = ring;
index fd6b61c9761910664e76b5ecacfa42871e11f630..9778fae313ab17d22080354a2bfcd4d1be777dd5 100644 (file)
@@ -307,9 +307,9 @@ static void rt2x00usb_interrupt_rxdone(struct urb *urb)
         * Fill in skb descriptor
         */
        skbdesc = get_skb_desc(entry->skb);
-       skbdesc->desc_len = desc.size;
-       skbdesc->data_len = entry->ring->desc_size;
-       skbdesc->desc = entry->skb->data + desc.size;
+       skbdesc->desc_len = entry->ring->desc_size;
+       skbdesc->data_len = entry->skb->len;
+       skbdesc->desc = entry->skb->data - skbdesc->desc_len;
        skbdesc->data = entry->skb->data;
        skbdesc->ring = ring;
        skbdesc->entry = entry;