Subject: [PATCH 1/4] [net-2.6.22] IrDA: IrLAP raw mode

From: Samuel Ortiz <[email-masked]>
Date: Sat, 17 Mar 2007 03:57:29 +0200

This patch allows us to bypass the IrDA stack down to the IrLAP level.
Sending and receiving frames is done through a character device.
This is useful for e.g. doing real IrDA sniffing, testing external IrDA
stacks and for Lirc (once I will add the framing disabling switch).

Signed-off-by: Samuel Ortiz <[email-masked]>
---
 include/net/irda/irlap.h     |    5 +
 include/net/irda/irlap_raw.h |   57 +++++++
 net/irda/Kconfig             |   10 ++
 net/irda/Makefile            |    1 +
 net/irda/irlap.c             |    5 +
 net/irda/irlap_event.c       |    3 +
 net/irda/irlap_frame.c       |    4 +
 net/irda/irlap_raw.c         |  358 ++++++++++++++++++++++++++++++++++++++++++
 net/irda/irsysctl.c          |   16 ++-
 9 files changed, 458 insertions(+), 1 deletions(-)
 create mode 100644 include/net/irda/irlap_raw.h
 create mode 100644 net/irda/irlap_raw.c

diff --git a/include/net/irda/irlap.h b/include/net/irda/irlap.h
index e77eb88..1b24cfc 100644
--- a/include/net/irda/irlap.h
+++ b/include/net/irda/irlap.h
@@ -36,6 +36,7 @@
 #include <net/irda/qos.h>		/* struct qos_info */
 #include <net/irda/discovery.h>		/* discovery_t */
 #include <net/irda/irlap_event.h>	/* IRLAP_STATE, ... */
+#include <net/irda/irlap_raw.h>	        /* IRLAP raw definitions */
 #include <net/irda/irmod.h>		/* struct notify_t */
 
 #define CONFIG_IRDA_DYNAMIC_WINDOW 1
@@ -208,6 +209,10 @@ struct irlap_cb {
 	int    xbofs_delay;   /* Nr of XBOF's used to MTT */
 	int    bofs_count;    /* Negotiated extra BOFs */
 	int    next_bofs;     /* Negotiated extra BOFs after next frame */
+#ifdef CONFIG_IRDA_RAW
+	int    raw_mode;
+	struct irlap_raw * irlap_raw;
+#endif
 };
 
 /* 
diff --git a/include/net/irda/irlap_raw.h b/include/net/irda/irlap_raw.h
new file mode 100644
index 0000000..ce90563
--- /dev/null
+++ b/include/net/irda/irlap_raw.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2007 Samuel Ortiz ([email-masked])
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef _IRLAP_RAW_H
+#define _IRLAP_RAW_H
+
+struct irlap_cb;
+
+#ifdef CONFIG_IRDA_RAW
+
+extern int irlap_raw_recv_frame(struct sk_buff *skb, struct net_device *dev);
+extern int irlap_raw_register_device(struct net_device * dev);
+extern int irlap_raw_unregister_device(struct net_device * dev);
+extern int irlap_raw_mode(struct irlap_cb *self);
+
+#else
+
+static inline int irlap_raw_recv_frame(struct sk_buff *skb,
+				       struct net_device *dev)
+{
+        if (skb == NULL)
+                return -EINVAL;
+
+        if (dev->atalk_ptr == NULL)
+                return -ENODEV;
+
+        return 0;
+}
+
+static inline int irlap_raw_register_device(struct net_device * dev)
+{
+        if (dev == NULL)
+                return -ENODEV;
+        return 0;
+}
+
+static inline int irlap_raw_unregister_device(struct net_device * dev)
+{
+        if (dev == NULL)
+                return -ENODEV;
+        return 0;
+}
+
+static inline int irlap_raw_mode(struct irlap_cb *self)
+{
+        return 0;
+}
+
+#endif
+
+#endif
diff --git a/net/irda/Kconfig b/net/irda/Kconfig
index 9efb17b..310a036 100644
--- a/net/irda/Kconfig
+++ b/net/irda/Kconfig
@@ -92,5 +92,15 @@ config IRDA_DEBUG
 
 	  If unsure, say Y (since it makes it easier to find the bugs).
 
+config IRDA_RAW
+	bool "IrLAP raw"
+	depends on IRDA
+	help
+	  Say Y if you want to bypass the IrDA stack, down to the IrLAP level.
+	  This option allows you to send and receive IrLAP frames from
+	  userspace, by writing and reading to a character device
+	  (/dev/irdaX_raw). Note that the IrDA stack will be mostly disabled.
+	  If unsure, say N.
+
 source "drivers/net/irda/Kconfig"
 
diff --git a/net/irda/Makefile b/net/irda/Makefile
index d1366c2..11d5ffb 100644
--- a/net/irda/Makefile
+++ b/net/irda/Makefile
@@ -13,3 +13,4 @@ irda-y := iriap.o iriap_event.o irlmp.o irlmp_event.o irlmp_frame.o \
 	  discovery.o parameters.o irmod.o
 irda-$(CONFIG_PROC_FS) += irproc.o
 irda-$(CONFIG_SYSCTL) += irsysctl.o
+irda-$(CONFIG_IRDA_RAW) += irlap_raw.o
diff --git a/net/irda/irlap.c b/net/irda/irlap.c
index d93ebd1..656c949 100644
--- a/net/irda/irlap.c
+++ b/net/irda/irlap.c
@@ -173,6 +173,8 @@ struct irlap_cb *irlap_open(struct net_device *dev, struct qos_info *qos,
 
 	irlmp_register_link(self, self->saddr, &self->notify);
 
+	irlap_raw_register_device(dev);
+
 	return self;
 }
 EXPORT_SYMBOL(irlap_open);
@@ -234,6 +236,9 @@ void irlap_close(struct irlap_cb *self)
 		IRDA_DEBUG(1, "%s(), Didn't find myself!\n", __FUNCTION__);
 		return;
 	}
+
+	irlap_raw_unregister_device(self->netdev);
+
 	__irlap_close(lap);
 }
 EXPORT_SYMBOL(irlap_close);
diff --git a/net/irda/irlap_event.c b/net/irda/irlap_event.c
index 7b6433f..3a11c3e 100644
--- a/net/irda/irlap_event.c
+++ b/net/irda/irlap_event.c
@@ -241,6 +241,9 @@ void irlap_do_event(struct irlap_cb *self, IRLAP_EVENT event,
 	if (!self || self->magic != LAP_MAGIC)
 		return;
 
+	if (irlap_raw_mode(self))
+		return;
+
 	IRDA_DEBUG(3, "%s(), event = %s, state = %s\n", __FUNCTION__,
 		   irlap_event[event], irlap_state[self->state]);
 
diff --git a/net/irda/irlap_frame.c b/net/irda/irlap_frame.c
index 803ac41..5a3a868 100644
--- a/net/irda/irlap_frame.c
+++ b/net/irda/irlap_frame.c
@@ -1325,6 +1325,10 @@ int irlap_driver_rcv(struct sk_buff *skb, struct net_device *dev,
 		return -1;
 	}
 
+       if (irlap_raw_mode(self)) {
+               return irlap_raw_recv_frame(skb, dev);
+       }
+
 	/* We are no longer an "old" protocol, so we need to handle
 	 * share and non linear skbs. This should never happen, so
 	 * we don't need to be clever about it. Jean II */
diff --git a/net/irda/irlap_raw.c b/net/irda/irlap_raw.c
new file mode 100644
index 0000000..705424e
--- /dev/null
+++ b/net/irda/irlap_raw.c
@@ -0,0 +1,358 @@
+/*
+ * net/irda/irlap_raw.c Raw IrLAP character device.
+ * Copyright (C) 2007 Samuel Ortiz ([email-masked])
+ *
+ * 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, version 2.
+ *
+ * From /dev/irdaN_raw, you can read and write raw IrLAP frames.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/miscdevice.h>
+#include <linux/wait.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irda_device.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlap_raw.h>
+
+/* We keep the last 10 IrLAP frames */
+unsigned int sysctl_raw_ring_length = 10;
+
+struct irlap_raw {
+	struct list_head    list;
+	char                name[IFNAMSIZ + 8];
+	struct miscdevice   misc_device;
+	struct irlap_cb     * irlap;
+	struct sk_buff_head skb_ring;
+	wait_queue_head_t   rx_wait;
+	unsigned int        refcount;
+	struct mutex        rx_mutex;
+	struct mutex        tx_mutex;
+	struct work_struct  skb_ring_work;
+};
+
+static LIST_HEAD(irlap_raw_list);
+static DEFINE_MUTEX(irlap_raw_mutex);
+
+static void irlap_raw_skb_ring_gc(struct work_struct *work)
+{
+	struct irlap_raw * raw =
+		container_of(work, struct irlap_raw, skb_ring_work);
+
+	IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+	if (mutex_lock_interruptible(&raw->rx_mutex))
+		return;
+
+	while(skb_queue_len(&raw->skb_ring) > sysctl_raw_ring_length) {
+		struct sk_buff * old_skb;
+
+		IRDA_DEBUG(3, "%s(): We still have %d entries\n",
+			   __FUNCTION__, skb_queue_len(&raw->skb_ring));
+
+		old_skb = skb_dequeue_tail(&raw->skb_ring);
+		dev_kfree_skb(old_skb);
+	}
+
+	mutex_unlock(&raw->rx_mutex);
+}
+
+int irlap_raw_recv_frame(struct sk_buff *skb, struct net_device *dev)
+{
+	struct irlap_cb * irlap;
+	struct irlap_raw * raw;
+
+	IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+	irlap = dev->atalk_ptr;
+	if (irlap == NULL)
+		return -ENODEV;
+
+	if (skb == NULL)
+		return -EINVAL;
+
+	raw = irlap->irlap_raw;
+	if (raw == NULL)
+		return -ENODEV;
+
+	skb_queue_head(&raw->skb_ring, skb);
+
+	wake_up_interruptible(&raw->rx_wait);
+
+	/*
+	 * We call the "garbage collector" in process
+	 * context, so that we can lock the skb removal
+	 * against read().
+	 */
+	if (skb_queue_len(&raw->skb_ring) > sysctl_raw_ring_length) {
+		IRDA_DEBUG(3, "%s(): We have %d entries, time to clean up\n",
+			   __FUNCTION__, skb_queue_len(&raw->skb_ring));
+		schedule_work(&raw->skb_ring_work);
+	}
+
+	return 0;
+}
+
+ssize_t irlap_raw_read(struct file * file, char __user * buf,
+		       size_t count, loff_t * ppos)
+{
+	int ret = 0;
+	struct irlap_raw * raw;
+	struct sk_buff * frame;
+
+	IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+	raw = file->private_data;
+
+	ret = mutex_lock_interruptible(&raw->rx_mutex);
+	if (ret)
+		return ret;
+
+	ret = wait_event_interruptible(raw->rx_wait,
+				       !skb_queue_empty(&raw->skb_ring));
+	if (ret < 0)
+		goto out;
+
+	if (skb_queue_empty(&raw->skb_ring)) {
+		ret = -EINTR;
+		IRDA_DEBUG(3, "%s(): Queue is empty\n", __FUNCTION__);
+		goto out;
+	}
+
+	frame = skb_dequeue_tail(&raw->skb_ring);
+
+	if (frame == NULL) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	if (copy_to_user(buf, frame->data, frame->len)) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	ret = frame->len;
+	dev_kfree_skb(frame);
+
+ out:
+	mutex_unlock(&raw->rx_mutex);
+
+	IRDA_DEBUG(3, "%s(): Read %d bytes\n", __FUNCTION__, ret);
+	return ret;
+}
+
+extern void irlap_queue_xmit(struct irlap_cb *self, struct sk_buff *skb);
+ssize_t irlap_raw_write(struct file * file, const char __user * buf,
+			size_t count, loff_t * ppos)
+{
+	int ret;
+	struct irlap_raw * raw;
+	struct sk_buff *tx_skb;
+
+	IRDA_DEBUG(3, "%s(): Trying to write %d bytes\n", __FUNCTION__, count);
+
+	raw = file->private_data;
+
+	ret = mutex_lock_interruptible(&raw->tx_mutex);
+	if (ret)
+		return ret;
+
+	if (count == 0)
+		goto out;
+
+	tx_skb = alloc_skb(IRDA_SIR_MAX_FRAME, GFP_KERNEL);
+	if (tx_skb == NULL) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (copy_from_user(skb_put(tx_skb, count), buf, count)) {
+		ret = -EFAULT;
+		goto out;
+	}
+
+	irlap_queue_xmit(raw->irlap, tx_skb);
+
+	ret = count;
+ out:
+	mutex_unlock(&raw->tx_mutex);
+
+	IRDA_DEBUG(3, "%s(): Wrote %d bytes\n", __FUNCTION__, ret);
+	return ret;
+}
+
+unsigned int irlap_raw_poll(struct file * file,
+			    struct poll_table_struct * table)
+{
+	int ret = 0;
+	struct irlap_raw * raw;
+
+	raw = file->private_data;
+
+        poll_wait(file, &raw->rx_wait, table);
+
+        if (!skb_queue_empty(&raw->skb_ring))
+                ret = POLLIN | POLLRDNORM;
+	else
+		ret = POLLOUT | POLLWRNORM;
+
+	return ret;
+}
+
+int irlap_raw_open(struct inode * inode, struct file * file)
+{
+	unsigned int minor = iminor(inode);
+	int ret = -ENODEV;
+	struct irlap_raw * raw, * tmp;
+
+	IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+	mutex_lock_interruptible(&irlap_raw_mutex);
+	list_for_each_entry_safe(raw, tmp, &irlap_raw_list, list) {
+		if (raw->misc_device.minor == minor) {
+			IRDA_DEBUG(3, "%s(): Found raw (minor: %d)\n",
+				   __FUNCTION__, minor);
+			file->private_data = raw;
+			raw->irlap->raw_mode = 1;
+			raw->refcount++;
+			mutex_unlock(&irlap_raw_mutex);
+			return 0;
+		}
+	}
+
+	mutex_unlock(&irlap_raw_mutex);
+	return ret;
+}
+
+int irlap_raw_release(struct inode * inode, struct file * file)
+{
+	struct irlap_raw * raw;
+
+	if (file->private_data) {
+		mutex_lock_interruptible(&irlap_raw_mutex);
+
+		raw = file->private_data;
+		if (raw->refcount > 0)
+			raw->refcount--;
+
+		if (raw->refcount == 0) {
+			skb_queue_purge(&raw->skb_ring);
+			raw->irlap->raw_mode = 0;
+		}
+
+		file->private_data = NULL;
+
+		mutex_unlock(&irlap_raw_mutex);
+	}
+
+	return 0;
+}
+
+static const struct file_operations irlap_raw_fops = {
+	.owner		= THIS_MODULE,
+	.read		= irlap_raw_read,
+	.write          = irlap_raw_write,
+	.poll		= irlap_raw_poll,
+	.open		= irlap_raw_open,
+	.release	= irlap_raw_release,
+};
+
+
+int irlap_raw_register_device(struct net_device * dev)
+{
+	int ret = 0;
+	struct irlap_cb * irlap;
+	struct irlap_raw * raw;
+
+	if (dev == NULL)
+		return -ENODEV;
+
+	raw = kzalloc(sizeof(struct irlap_raw), GFP_KERNEL);
+	if (raw == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&raw->list);
+	INIT_WORK(&raw->skb_ring_work, irlap_raw_skb_ring_gc);
+	mutex_init(&raw->rx_mutex);
+	mutex_init(&raw->tx_mutex);
+	raw->refcount = 0;
+	skb_queue_head_init(&raw->skb_ring);
+
+	sprintf(raw->name, "%s_raw", dev->name);
+	raw->misc_device.minor = MISC_DYNAMIC_MINOR;
+	raw->misc_device.name = raw->name;
+	raw->misc_device.fops = &irlap_raw_fops;
+
+	list_add(&raw->list, &irlap_raw_list);
+
+	irlap = dev->atalk_ptr;
+	if (irlap == NULL) {
+		ret = -ENODEV;
+		goto out_err;
+	}
+
+        /* We'll enter raw mode whenever the device is open */
+	irlap->raw_mode = 0;
+	irlap->irlap_raw = raw;
+
+	init_waitqueue_head(&raw->rx_wait);
+
+	raw->irlap = irlap;
+
+	ret = misc_register(&raw->misc_device);
+	if (!ret) {
+		printk(KERN_INFO "Registered %s raw IrLAP device\n", raw->name);
+		return ret;
+	}
+
+ out_err:
+	irlap->irlap_raw = NULL;
+	kfree(raw);
+	return ret;
+
+}
+
+int irlap_raw_unregister_device(struct net_device * dev)
+{
+	struct irlap_cb * irlap;
+	struct irlap_raw * raw;
+
+	irlap = dev->atalk_ptr;
+	if (irlap == NULL)
+		return -ENODEV;
+
+	raw = irlap->irlap_raw;
+	mutex_lock_interruptible(&irlap_raw_mutex);
+	if (raw->refcount) {
+		mutex_unlock(&irlap_raw_mutex);
+		return -EBUSY;
+	}
+
+	printk(KERN_INFO "%s unregistered\n", raw->name);
+
+	list_del(&raw->list);
+	misc_deregister(&raw->misc_device);
+
+	mutex_unlock(&irlap_raw_mutex);
+
+	kfree(raw);
+
+	return 0;
+}
+
+inline int irlap_raw_mode(struct irlap_cb *self)
+{
+        return self->raw_mode;
+}
diff --git a/net/irda/irsysctl.c b/net/irda/irsysctl.c
index 2e968e7..a7e967d 100644
--- a/net/irda/irsysctl.c
+++ b/net/irda/irsysctl.c
@@ -35,7 +35,7 @@
 enum { DISCOVERY=1, DEVNAME, DEBUG, FAST_POLL, DISCOVERY_SLOTS,
        DISCOVERY_TIMEOUT, SLOT_TIMEOUT, MAX_BAUD_RATE, MIN_TX_TURN_TIME,
        MAX_TX_DATA_SIZE, MAX_TX_WINDOW, MAX_NOREPLY_TIME, WARN_NOREPLY_TIME,
-       LAP_KEEPALIVE_TIME };
+       LAP_KEEPALIVE_TIME, RAW_RING_LENGTH };
 
 extern int  sysctl_discovery;
 extern int  sysctl_discovery_slots;
@@ -50,6 +50,9 @@ extern int  sysctl_max_tx_window;
 extern int  sysctl_max_noreply_time;
 extern int  sysctl_warn_noreply_time;
 extern int  sysctl_lap_keepalive_time;
+#ifdef CONFIG_IRDA_RAW
+extern unsigned int  sysctl_raw_ring_length;
+#endif
 
 /* this is needed for the proc_dointvec_minmax - Jean II */
 static int max_discovery_slots = 16;		/* ??? */
@@ -237,6 +240,17 @@ static ctl_table irda_table[] = {
 		.extra1		= &min_lap_keepalive_time,
 		.extra2		= &max_lap_keepalive_time
 	},
+#ifdef CONFIG_IRDA_RAW
+	{
+		.ctl_name	= RAW_RING_LENGTH,
+		.procname	= "raw_ring_length",
+		.data		= &sysctl_raw_ring_length,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec
+	},
+#endif
+
 	{ .ctl_name = 0 }
 };
 
-- 
1.5.0.2

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys-and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV