Subject: [PATCH 5/5] ieee802154: add serial dongle driver

From: Dmitry Eremin-Solenikov <[email-masked]>
Date: Tue, 26 May 2009 15:23:08 +0400

Add a tty ldisc supporting IEEE 802.15.4 over serial line. Currently
the only protocol implemented/device supported is our firmware for
Freescale 13192 evaluation boards.

Signed-off-by: Dmitry Eremin-Solenikov <[email-masked]>
Signed-off-by: Sergey Lapin <[email-masked]>
---
 drivers/ieee802154/Kconfig  |    3 +
 drivers/ieee802154/Makefile |    1 +
 drivers/ieee802154/serial.c | 1001 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/tty.h         |    3 +-
 4 files changed, 1007 insertions(+), 1 deletions(-)
 create mode 100644 drivers/ieee802154/serial.c

diff --git a/drivers/ieee802154/Kconfig b/drivers/ieee802154/Kconfig
index 6f03394..fdc9792 100644
--- a/drivers/ieee802154/Kconfig
+++ b/drivers/ieee802154/Kconfig
@@ -19,5 +19,8 @@ config IEEE802154_FAKELB
           This driver can also be built as a module. To do so say M here.
 	  The module will be called 'fakelb'.
 
+config IEEE802154_SERIAL
+	tristate "Simple LR-WPAN UART driver"
+
 endif
 
diff --git a/drivers/ieee802154/Makefile b/drivers/ieee802154/Makefile
index c3d1db4..a8ce95f 100644
--- a/drivers/ieee802154/Makefile
+++ b/drivers/ieee802154/Makefile
@@ -1,3 +1,4 @@
 obj-$(CONFIG_IEEE802154_FAKELB) += fakelb.o
+obj-$(CONFIG_IEEE802154_SERIAL) += serial.o
 
 EXTRA_CFLAGS += -DDEBUG -DCONFIG_FFD
diff --git a/drivers/ieee802154/serial.c b/drivers/ieee802154/serial.c
new file mode 100644
index 0000000..4a222ec
--- /dev/null
+++ b/drivers/ieee802154/serial.c
@@ -0,0 +1,1001 @@
+/*
+ * ZigBee TTY line discipline.
+ *
+ * Provides interface between ZigBee stack and IEEE 802.15.4 compatible
+ * firmware over serial line. Communication protocol is described below.
+ *
+ * Copyright (C) 2007, 2008 Siemens AG
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Written by:
+ * Maxim Gorbachyov <[email-masked]>
+ * Maxim Osipov <[email-masked]>
+ * Sergey Lapin <[email-masked]>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/tty.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/ieee802154/dev.h>
+
+
+/* NOTE: be sure to use here the same values as in the firmware */
+#define START_BYTE1	'z'
+#define START_BYTE2	'b'
+#define MAX_DATA_SIZE	127
+
+#define IDLE_MODE	0x00
+#define RX_MODE		0x02
+#define TX_MODE		0x03
+#define FORCE_TRX_OFF	0xF0
+
+#define STATUS_SUCCESS	0
+#define STATUS_RX_ON	1
+#define STATUS_TX_ON	2
+#define STATUS_TRX_OFF	3
+#define STATUS_IDLE	4
+#define STATUS_BUSY	5
+#define STATUS_BUSY_RX	6
+#define STATUS_BUSY_TX	7
+#define STATUS_ERR	8
+
+/* We re-use PPP ioctl for our purposes */
+#define	PPPIOCGUNIT	_IOR('t', 86, int)	/* get ppp unit number */
+
+/*
+ * The following messages are used to control ZigBee firmware.
+ * All communication has request/response format,
+ * except of asynchronous incoming data stream (DATA_RECV_* messages).
+ */
+enum {
+	NO_ID			= 0, /* means no pending id */
+
+	/* Driver to Firmware */
+	CMD_OPEN		= 0x01, /* u8 id */
+	CMD_CLOSE		= 0x02, /* u8 id */
+	CMD_SET_CHANNEL		= 0x04, /* u8 id, u8 channel */
+	CMD_ED			= 0x05, /* u8 id */
+	CMD_CCA			= 0x06, /* u8 id */
+	CMD_SET_STATE		= 0x07, /* u8 id, u8 flag */
+	DATA_XMIT_BLOCK		= 0x09, /* u8 id, u8 len, u8 data[len] */
+	DATA_XMIT_STREAM	= 0x0a, /* u8 id, u8 c */
+	RESP_RECV_BLOCK		= 0x0b, /* u8 id, u8 status */
+	RESP_RECV_STREAM	= 0x0c, /* u8 id, u8 status */
+
+	/* Firmware to Driver */
+	RESP_OPEN		= 0x81, /* u8 id, u8 status */
+	RESP_CLOSE		= 0x82, /* u8 id, u8 status */
+	RESP_SET_CHANNEL 	= 0x84, /* u8 id, u8 status */
+	RESP_ED			= 0x85, /* u8 id, u8 status, u8 level */
+	RESP_CCA		= 0x86, /* u8 id, u8 status */
+	RESP_SET_STATE		= 0x87, /* u8 id, u8 status */
+	RESP_XMIT_BLOCK		= 0x89, /* u8 id, u8 status */
+	RESP_XMIT_STREAM	= 0x8a, /* u8 id, u8 status */
+	DATA_RECV_BLOCK		= 0x8b, /* u8 id, u8 lq, u8 len, u8 data[len] */
+	DATA_RECV_STREAM	= 0x8c  /* u8 id, u8 c */
+};
+
+enum {
+	STATE_WAIT_START1,
+	STATE_WAIT_START2,
+	STATE_WAIT_COMMAND,
+	STATE_WAIT_PARAM1,
+	STATE_WAIT_PARAM2,
+	STATE_WAIT_DATA
+};
+
+struct zb_device {
+	/* Relative devices */
+	struct tty_struct	*tty;
+	struct ieee802154_dev	*dev;
+
+	/* locks the ldisc for the command */
+	struct mutex		mutex;
+
+	/* command completition */
+	wait_queue_head_t	wq;
+	phy_status_t		status;
+	u8			ed;
+
+	/* Internal state */
+	struct completion	open_done;
+	unsigned char		opened;
+	u8			pending_id;
+	unsigned int		pending_size;
+	u8			*pending_data;
+	/* FIXME: WE NEED LOCKING!!! */
+
+	/* Command (rx) processing */
+	int			state;
+	unsigned char		id;
+	unsigned char		param1;
+	unsigned char		param2;
+	unsigned char		index;
+	unsigned char		data[MAX_DATA_SIZE];
+};
+
+/*****************************************************************************
+ * ZigBee serial device protocol handling
+ *****************************************************************************/
+static int _open_dev(struct zb_device *zbdev);
+
+static int
+_send_pending_data(struct zb_device *zbdev)
+{
+	unsigned int j;
+	struct tty_struct *tty;
+
+	BUG_ON(!zbdev);
+	tty = zbdev->tty;
+	if (!tty)
+		return -ENODEV;
+
+	zbdev->status = PHY_INVAL;
+
+	/* Debug info */
+	printk(KERN_INFO "%lu %s, %d bytes:", jiffies, __func__, zbdev->pending_size);
+	for (j = 0; j < zbdev->pending_size; ++j)
+		printk(KERN_CONT " 0x%02X", zbdev->pending_data[j]);
+	printk(KERN_CONT "\n");
+
+	if (tty->driver->ops->write(tty, zbdev->pending_data, zbdev->pending_size) != zbdev->pending_size) {
+		printk(KERN_ERR "%s: device write failed\n", __func__);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int
+send_cmd(struct zb_device *zbdev, u8 id)
+{
+	u8 len = 0, buf[4];	/* 4 because of 2 start bytes, id and optional extra */
+
+	/* Check arguments */
+	BUG_ON(!zbdev);
+
+	if (!zbdev->opened) {
+		if (!_open_dev(zbdev))
+			return -EAGAIN;
+	}
+
+	pr_debug("%s(): id = %u\n", __func__, id);
+	if (zbdev->pending_size) {
+		printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
+			__func__, zbdev->pending_id);
+		BUG();
+	}
+
+	/* Prepare a message */
+	buf[len++] = START_BYTE1;
+	buf[len++] = START_BYTE2;
+	buf[len++] = id;
+
+	zbdev->pending_id = id;
+	zbdev->pending_size = len;
+	zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
+	if (!zbdev->pending_data) {
+		printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
+		zbdev->pending_id = 0;
+		zbdev->pending_size = 0;
+		return -ENOMEM;
+	}
+	memcpy(zbdev->pending_data, buf, len);
+
+	return _send_pending_data(zbdev);
+}
+
+static int
+send_cmd2(struct zb_device *zbdev, u8 id, u8 extra)
+{
+	u8 len = 0, buf[4];	/* 4 because of 2 start bytes, id and optional extra */
+
+	/* Check arguments */
+	BUG_ON(!zbdev);
+
+	if (!zbdev->opened) {
+		if (!_open_dev(zbdev))
+			return -EAGAIN;
+	}
+
+	pr_debug("%s(): id = %u\n", __func__, id);
+	if (zbdev->pending_size) {
+		printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
+			__func__, zbdev->pending_id);
+		BUG();
+	}
+
+	/* Prepare a message */
+	buf[len++] = START_BYTE1;
+	buf[len++] = START_BYTE2;
+	buf[len++] = id;
+	buf[len++] = extra;
+
+	zbdev->pending_id = id;
+	zbdev->pending_size = len;
+	zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
+	if (!zbdev->pending_data) {
+		printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
+		zbdev->pending_id = 0;
+		zbdev->pending_size = 0;
+		return -ENOMEM;
+	}
+	memcpy(zbdev->pending_data, buf, len);
+
+	return _send_pending_data(zbdev);
+}
+
+static int
+send_block(struct zb_device *zbdev, u8 len, u8 *data)
+{
+	u8 i = 0, buf[4];	/* 4 because of 2 start bytes, id and len */
+
+	/* Check arguments */
+	BUG_ON(!zbdev);
+
+	if (!zbdev->opened) {
+		if (!_open_dev(zbdev))
+			return -EAGAIN;
+	}
+
+	pr_debug("%s(): id = %u\n", __func__, DATA_XMIT_BLOCK);
+	if (zbdev->pending_size) {
+		printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
+			__func__, zbdev->pending_id);
+		BUG();
+	}
+
+	/* Prepare a message */
+	buf[i++] = START_BYTE1;
+	buf[i++] = START_BYTE2;
+	buf[i++] = DATA_XMIT_BLOCK;
+	buf[i++] = len;
+
+	zbdev->pending_id = DATA_XMIT_BLOCK;
+	zbdev->pending_size = i + len;
+	zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
+	if (!zbdev->pending_data) {
+		printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
+		zbdev->pending_id = 0;
+		zbdev->pending_size = 0;
+		return -ENOMEM;
+	}
+	memcpy(zbdev->pending_data, buf, i);
+	memcpy(zbdev->pending_data + i, data, len);
+
+	return _send_pending_data(zbdev);
+}
+
+static void
+cleanup(struct zb_device *zbdev)
+{
+	zbdev->state = STATE_WAIT_START1;
+	zbdev->id = 0;
+	zbdev->param1 = 0;
+	zbdev->param2 = 0;
+	zbdev->index = 0;
+}
+
+static int
+is_command(unsigned char c)
+{
+	switch (c) {
+	/* ids we can get here: */
+	case RESP_OPEN:
+	case RESP_CLOSE:
+	case RESP_SET_CHANNEL:
+	case RESP_ED:
+	case RESP_CCA:
+	case RESP_SET_STATE:
+	case RESP_XMIT_BLOCK:
+	case RESP_XMIT_STREAM:
+	case DATA_RECV_BLOCK:
+	case DATA_RECV_STREAM:
+		return 1;
+	}
+	return 0;
+}
+
+static int
+_match_pending_id(struct zb_device *zbdev)
+{
+	return ((CMD_OPEN == zbdev->pending_id && RESP_OPEN == zbdev->id)
+		|| (CMD_CLOSE == zbdev->pending_id && RESP_CLOSE == zbdev->id)
+		|| (CMD_SET_CHANNEL == zbdev->pending_id && RESP_SET_CHANNEL == zbdev->id)
+		|| (CMD_ED == zbdev->pending_id && RESP_ED == zbdev->id)
+		|| (CMD_CCA == zbdev->pending_id && RESP_CCA == zbdev->id)
+		|| (CMD_SET_STATE == zbdev->pending_id && RESP_SET_STATE == zbdev->id)
+		|| (DATA_XMIT_BLOCK == zbdev->pending_id && RESP_XMIT_BLOCK == zbdev->id)
+		|| (DATA_XMIT_STREAM == zbdev->pending_id && RESP_XMIT_STREAM == zbdev->id)
+		|| DATA_RECV_BLOCK == zbdev->id
+		|| DATA_RECV_STREAM == zbdev->id);
+}
+
+static void serial_net_rx(struct zb_device *zbdev)
+{
+	/* zbdev->param1 is LQI
+	 * zbdev->param2 is length of data
+	 * zbdev->data is data itself
+	 */
+	struct sk_buff *skb;
+	skb = alloc_skb(zbdev->param2, GFP_ATOMIC);
+	skb_put(skb, zbdev->param2);
+	skb_copy_to_linear_data(skb, zbdev->data, zbdev->param2);
+	ieee802154_rx_irqsafe(zbdev->dev, skb, zbdev->param1);
+}
+
+static void
+process_command(struct zb_device *zbdev)
+{
+	/* Command processing */
+	if (!_match_pending_id(zbdev))
+		return;
+
+	if (RESP_OPEN == zbdev->id && STATUS_SUCCESS == zbdev->param1) {
+		zbdev->opened = 1;
+		pr_debug("Opened device\n");
+		complete(&zbdev->open_done);
+		/* Input is not processed during output, so
+		 * using completion is not possible during output.
+		 * so we need to handle open as any other command
+		 * and hope for best
+		 */
+		return;
+	}
+
+	if (!zbdev->opened)
+		return;
+
+	zbdev->pending_id = 0;
+	kfree(zbdev->pending_data);
+	zbdev->pending_data = NULL;
+	zbdev->pending_size = 0;
+	if (zbdev->id != DATA_RECV_BLOCK)
+		switch (zbdev->param1) {
+		case STATUS_SUCCESS:
+			zbdev->status = PHY_SUCCESS;
+			break;
+		case STATUS_RX_ON:
+			zbdev->status = PHY_RX_ON;
+			break;
+		case STATUS_TX_ON:
+			zbdev->status = PHY_TX_ON;
+			break;
+		case STATUS_TRX_OFF:
+			zbdev->status = PHY_TRX_OFF;
+			break;
+		case STATUS_BUSY:
+			zbdev->status = PHY_BUSY;
+			break;
+		case STATUS_IDLE:
+			zbdev->status = PHY_IDLE;
+			break;
+		case STATUS_BUSY_RX:
+			zbdev->status = PHY_BUSY_RX;
+			break;
+		case STATUS_BUSY_TX:
+			zbdev->status = PHY_BUSY_TX;
+			break;
+		default:
+			printk(KERN_ERR "%s: bad status received from firmware: %u\n",
+				__func__, zbdev->param1);
+			zbdev->status = PHY_ERROR;
+			break;
+		}
+
+	switch (zbdev->id) {
+	case RESP_ED:
+		zbdev->ed = zbdev->param2;
+		break;
+	case DATA_RECV_BLOCK:
+		pr_debug("Received block, lqi %02x, len %02x\n", zbdev->param1, zbdev->param2);
+		/* zbdev->param1 is LQ, zbdev->param2 is length */
+		serial_net_rx(zbdev);
+		break;
+	case DATA_RECV_STREAM:
+		/* TODO: update firmware to use this */
+		break;
+	}
+
+	wake_up(&zbdev->wq);
+}
+
+static void
+process_char(struct zb_device *zbdev, unsigned char c)
+{
+	/* Data processing */
+	pr_debug("Char: %d (0x%02x)\n", c, c);
+	switch (zbdev->state) {
+	case STATE_WAIT_START1:
+		if (START_BYTE1 == c)
+			zbdev->state = STATE_WAIT_START2;
+		break;
+
+	case STATE_WAIT_START2:
+		if (START_BYTE2 == c)
+			zbdev->state = STATE_WAIT_COMMAND;
+		else
+			cleanup(zbdev);
+		break;
+
+	case STATE_WAIT_COMMAND:
+		if (is_command(c)) {
+			zbdev->id = c;
+			zbdev->state = STATE_WAIT_PARAM1;
+		} else {
+			cleanup(zbdev);
+			printk(KERN_ERR "%s, unexpected command id: %x\n", __func__, c);
+		}
+		break;
+
+	case STATE_WAIT_PARAM1:
+		zbdev->param1 = c;
+		if ((RESP_ED == zbdev->id) || (DATA_RECV_BLOCK == zbdev->id))
+			zbdev->state = STATE_WAIT_PARAM2;
+		else {
+			process_command(zbdev);
+			cleanup(zbdev);
+		}
+		break;
+
+	case STATE_WAIT_PARAM2:
+		zbdev->param2 = c;
+		if (RESP_ED == zbdev->id) {
+			process_command(zbdev);
+			cleanup(zbdev);
+		} else if (DATA_RECV_BLOCK == zbdev->id)
+			zbdev->state = STATE_WAIT_DATA;
+		else
+			cleanup(zbdev);
+		break;
+
+	case STATE_WAIT_DATA:
+		if (zbdev->index < sizeof(zbdev->data)) {
+			zbdev->data[zbdev->index] = c;
+			zbdev->index++;
+			/* Pending data is received, param2 is length for DATA_RECV_BLOCK */
+			if (zbdev->index == zbdev->param2) {
+				process_command(zbdev);
+				cleanup(zbdev);
+			}
+		} else {
+			printk(KERN_ERR "%s(): data size is greater "
+				"than buffer available\n", __func__);
+			cleanup(zbdev);
+		}
+		break;
+
+	default:
+		cleanup(zbdev);
+	}
+}
+
+/*****************************************************************************
+ * Device operations for IEEE 802.15.4 PHY side interface ZigBee stack
+ *****************************************************************************/
+
+static int _open_dev(struct zb_device *zbdev)
+{
+	int retries;
+	u8 len = 0, buf[4];	/* 4 because of 2 start bytes, id and optional extra */
+
+	/* Check arguments */
+	BUG_ON(!zbdev);
+	if (zbdev->opened)
+		return 1;
+
+	pr_debug("%s()\n", __func__);
+	if (zbdev->pending_size) {
+		printk(KERN_ERR "%s(): cmd is already pending, id = %u\n",
+			__func__, zbdev->pending_id);
+		BUG();
+	}
+
+	/* Prepare a message */
+	buf[len++] = START_BYTE1;
+	buf[len++] = START_BYTE2;
+	buf[len++] = CMD_OPEN;
+
+	zbdev->pending_id = CMD_OPEN;
+	zbdev->pending_size = len;
+	zbdev->pending_data = kzalloc(zbdev->pending_size, GFP_KERNEL);
+	if (!zbdev->pending_data) {
+		printk(KERN_ERR "%s(): unable to allocate memory\n", __func__);
+		zbdev->pending_id = 0;
+		zbdev->pending_size = 0;
+		return -ENOMEM;
+	}
+	memcpy(zbdev->pending_data, buf, len);
+
+	retries = 5;
+	while (!zbdev->opened && retries) {
+		if (_send_pending_data(zbdev) != 0)
+			return 0;
+
+		/* 3 second before retransmission */
+		wait_for_completion_interruptible_timeout(&zbdev->open_done, msecs_to_jiffies(1000));
+		--retries;
+	}
+
+	zbdev->pending_id = 0;
+	kfree(zbdev->pending_data);
+	zbdev->pending_data = NULL;
+	zbdev->pending_size = 0;
+
+	if (zbdev->opened) {
+		printk(KERN_INFO "Opened connection to device\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+/* Valid channels: 1-16 */
+static phy_status_t
+ieee802154_serial_set_channel(struct ieee802154_dev *dev, int channel)
+{
+	struct zb_device *zbdev;
+	phy_status_t ret;
+
+	pr_debug("%s\n", __func__);
+
+	zbdev = dev->priv;
+	if (NULL == zbdev) {
+		printk(KERN_ERR "%s: wrong phy\n", __func__);
+		return PHY_INVAL;
+	}
+
+	if (mutex_lock_interruptible(&zbdev->mutex))
+		return PHY_ERROR;
+	/* Our channels are actually from 11 to 26
+	 * We have IEEE802.15.4 channel no from 0 to 26.
+	 * channels 0-10 are not valid for us */
+	BUG_ON(channel < 11 || channel > 26);
+	/* ...  but our crappy firmware numbers channels from 1 to 16
+	 * which is a mystery. We suould enforce that using PIB API
+	 * but additional checking here won't kill, and gcc will
+	 * optimize this stuff anyway. */
+	BUG_ON((channel - 10) < 1 && (channel - 10) > 16);
+
+	if (send_cmd2(zbdev, CMD_SET_CHANNEL, channel - 10) != 0) {
+		ret = PHY_ERROR;
+		goto out;
+	}
+
+	if (wait_event_interruptible_timeout(zbdev->wq, zbdev->status != PHY_INVAL, msecs_to_jiffies(1000)) > 0)
+		ret = zbdev->status;
+	else
+		ret = PHY_ERROR;
+
+	if (ret == PHY_SUCCESS)
+		zbdev->dev->current_channel = channel;
+out:
+	mutex_unlock(&zbdev->mutex);
+	pr_debug("%s end\n", __func__);
+	return ret;
+}
+
+static phy_status_t
+ieee802154_serial_ed(struct ieee802154_dev *dev, u8 *level)
+{
+	struct zb_device *zbdev;
+	phy_status_t ret;
+
+	pr_debug("%s\n", __func__);
+
+	zbdev = dev->priv;
+	if (NULL == zbdev) {
+		printk(KERN_ERR "%s: wrong phy\n", __func__);
+		return PHY_INVAL;
+	}
+
+	if (mutex_lock_interruptible(&zbdev->mutex))
+		return PHY_ERROR;
+
+#if 0
+	if (send_cmd(zbdev, CMD_ED) != 0) {
+		ret = PHY_ERROR;
+		goto out;
+	}
+
+	if (wait_event_interruptible_timeout(zbdev->wq, zbdev->status != PHY_INVAL, msecs_to_jiffies(1000)) > 0) {
+		*level = zbdev->ed;
+		ret = zbdev->status;
+	} else
+		ret = PHY_ERROR;
+out:
+#else
+	/* Lets suppose we have energy on all channels
+	 * till we fix something regarding hardware or driver */
+	*level = 0xbe;
+	ret = PHY_SUCCESS;
+#endif
+	mutex_unlock(&zbdev->mutex);
+	pr_debug("%s end\n", __func__);
+	return ret;
+}
+
+static phy_status_t
+ieee802154_serial_cca(struct ieee802154_dev *dev)
+{
+	struct zb_device *zbdev;
+	phy_status_t ret;
+
+	pr_debug("%s\n", __func__);
+
+	zbdev = dev->priv;
+	if (NULL == zbdev) {
+		printk(KERN_ERR "%s: wrong phy\n", __func__);
+		return PHY_INVAL;
+	}
+
+	if (mutex_lock_interruptible(&zbdev->mutex))
+		return PHY_ERROR;
+
+	if (send_cmd(zbdev, CMD_CCA) != 0) {
+		ret = PHY_ERROR;
+		goto out;
+	}
+
+	if (wait_event_interruptible_timeout(zbdev->wq, zbdev->status != PHY_INVAL, msecs_to_jiffies(1000)) > 0)
+		ret = zbdev->status;
+	else
+		ret = PHY_ERROR;
+out:
+	mutex_unlock(&zbdev->mutex);
+	pr_debug("%s end\n", __func__);
+	return ret;
+}
+
+static phy_status_t
+ieee802154_serial_set_state(struct ieee802154_dev *dev, phy_status_t state)
+{
+	struct zb_device *zbdev;
+	unsigned char flag;
+	phy_status_t ret;
+
+	pr_debug("%s %d\n", __func__, state);
+
+	zbdev = dev->priv;
+	if (NULL == zbdev) {
+		printk(KERN_ERR "%s: wrong phy\n", __func__);
+		return PHY_INVAL;
+	}
+
+	if (mutex_lock_interruptible(&zbdev->mutex))
+		return PHY_ERROR;
+
+	switch (state) {
+	case PHY_RX_ON:
+		flag = RX_MODE;
+		break;
+	case PHY_TX_ON:
+		flag = TX_MODE;
+		break;
+	case PHY_TRX_OFF:
+		flag = IDLE_MODE;
+		break;
+	case PHY_FORCE_TRX_OFF:
+		flag = FORCE_TRX_OFF;
+		break;
+	default:
+		ret = PHY_INVAL;
+		goto out;
+	}
+
+	if (send_cmd2(zbdev, CMD_SET_STATE, flag) != 0) {
+		ret = PHY_ERROR;
+		goto out;
+	}
+
+	if (wait_event_interruptible_timeout(zbdev->wq, zbdev->status != PHY_INVAL, msecs_to_jiffies(1000)) > 0)
+		ret = zbdev->status;
+	else
+		ret = PHY_ERROR;
+out:
+	mutex_unlock(&zbdev->mutex);
+	pr_debug("%s end\n", __func__);
+	return ret;
+}
+
+static phy_status_t
+ieee802154_serial_xmit(struct ieee802154_dev *dev, struct sk_buff *skb)
+{
+	struct zb_device *zbdev;
+	phy_status_t ret;
+
+	pr_debug("%s\n", __func__);
+
+	zbdev = dev->priv;
+	if (NULL == zbdev) {
+		printk(KERN_ERR "%s: wrong phy\n", __func__);
+		return PHY_INVAL;
+	}
+
+	if (mutex_lock_interruptible(&zbdev->mutex))
+		return PHY_ERROR;
+
+	if (send_block(zbdev, skb->len, skb->data) != 0) {
+		ret = PHY_ERROR;
+		goto out;
+	}
+
+	if (wait_event_interruptible_timeout(zbdev->wq, zbdev->status != PHY_INVAL, msecs_to_jiffies(1000)) > 0)
+		ret = zbdev->status;
+	else
+		ret = PHY_ERROR;
+out:
+
+	mutex_unlock(&zbdev->mutex);
+	pr_debug("%s end\n", __func__);
+	return ret;
+}
+
+/*****************************************************************************
+ * Line discipline interface for IEEE 802.15.4 serial device
+ *****************************************************************************/
+
+static struct ieee802154_ops serial_ops = {
+	.owner = THIS_MODULE,
+	.tx = ieee802154_serial_xmit,
+	.ed = ieee802154_serial_ed,
+	.cca = ieee802154_serial_cca,
+	.set_trx_state = ieee802154_serial_set_state,
+	.set_channel	= ieee802154_serial_set_channel,
+};
+
+static int dev_minor_match(struct device *dev, void *data)
+{
+	int *minor = data;
+	return MINOR(dev->devt) == *minor;
+}
+
+/*
+ * Called when a tty is put into ZB line discipline. Called in process context.
+ * Returns 0 on success.
+ */
+static int
+ieee802154_tty_open(struct tty_struct *tty)
+{
+	struct zb_device *zbdev = tty->disc_data;
+	int err;
+	int minor;
+
+	pr_debug("Openning ldisc\n");
+	if (!capable(CAP_NET_ADMIN))
+		return -EPERM;
+
+	if (zbdev)
+		return -EBUSY;
+
+	/* Allocate device structure */
+	zbdev = kzalloc(sizeof(struct zb_device), GFP_KERNEL);
+	if (NULL == zbdev) {
+		printk(KERN_ERR "%s: can't allocate zb_device structure.\n", __func__);
+		return -ENOMEM;
+	}
+	mutex_init(&zbdev->mutex);
+	init_completion(&zbdev->open_done);
+	init_waitqueue_head(&zbdev->wq);
+
+	zbdev->dev = ieee802154_alloc_device();
+	if (!zbdev->dev) {
+		err = -ENOMEM;
+		goto out_free_zb;
+	}
+
+	zbdev->dev->name		= "serialdev";
+	zbdev->dev->priv		= zbdev;
+	zbdev->dev->extra_tx_headroom	= 0;
+	zbdev->dev->channel_mask	= 0x7ff;
+	zbdev->dev->current_channel	= 11; /* it's 1st channel of 2.4 Ghz space */
+	zbdev->dev->flags		= IEEE802154_OPS_OMIT_CKSUM;
+
+	minor = tty->index + tty->driver->minor_start;
+	zbdev->dev->parent = class_find_device(tty_class, NULL, &minor, dev_minor_match);
+
+	zbdev->tty = tty;
+	cleanup(zbdev);
+
+	tty->disc_data = zbdev;
+	tty->receive_room = MAX_DATA_SIZE;
+	tty->low_latency = 1;
+
+	/* FIXME: why is this needed. Note don't use ldisc_ref here as the
+	   open path is before the ldisc is referencable */
+
+	if (tty->ldisc.ops->flush_buffer)
+		tty->ldisc.ops->flush_buffer(tty);
+	tty_driver_flush_buffer(tty);
+
+	err = ieee802154_register_device(zbdev->dev, &serial_ops);
+	/* we put it only after it has a chance to be get by network core */
+	if (zbdev->dev->parent)
+		put_device(zbdev->dev->parent);
+	if (err) {
+		printk(KERN_ERR "%s: device register failed\n", __func__);
+		goto out_free;
+	}
+
+	return 0;
+
+	ieee802154_unregister_device(zbdev->dev);
+
+out_free:
+	tty->disc_data = NULL;
+
+	ieee802154_free_device(zbdev->dev);
+out_free_zb:
+	kfree(zbdev);
+
+	return err;
+}
+
+/*
+ * Called when the tty is put into another line discipline or it hangs up. We
+ * have to wait for any cpu currently executing in any of the other zb_tty_*
+ * routines to finish before we can call zb_tty_close and free the
+ * zb_serial_dev struct. This routine must be called from process context, not
+ * interrupt or softirq context.
+ */
+static void
+ieee802154_tty_close(struct tty_struct *tty)
+{
+	struct zb_device *zbdev;
+
+	zbdev = tty->disc_data;
+	if (NULL == zbdev) {
+		printk(KERN_WARNING "%s: match is not found\n", __func__);
+		return;
+	}
+
+	tty->disc_data = NULL;
+	zbdev->tty = NULL;
+
+	ieee802154_unregister_device(zbdev->dev);
+
+	tty_ldisc_flush(tty);
+	tty_driver_flush_buffer(tty);
+
+	ieee802154_free_device(zbdev->dev);
+	kfree(zbdev);
+}
+
+/*
+ * Called on tty hangup in process context.
+ */
+static int
+ieee802154_tty_hangup(struct tty_struct *tty)
+{
+	ieee802154_tty_close(tty);
+	return 0;
+}
+
+/*
+ * Called in process context only. May be re-entered by multiple ioctl calling threads.
+ */
+static int
+ieee802154_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct zb_device *zbdev;
+	struct ifreq ifr;
+	struct ieee802154_priv *priv;
+	int err;
+	void __user *argp = (void __user *) arg;
+
+	pr_debug("cmd = 0x%x\n", cmd);
+	memset(&ifr, 0, sizeof(ifr));
+
+	zbdev = tty->disc_data;
+	if (NULL == zbdev) {
+		pr_debug("match is not found\n");
+		return -EINVAL;
+	}
+
+
+	switch (cmd) {
+	case PPPIOCGUNIT:
+		/* TODO: some error checking */
+		priv = ieee802154_to_priv(zbdev->dev);
+		BUG_ON(!priv->master);
+		err = -EFAULT;
+		if (copy_to_user(argp, priv->master->name, strlen(priv->master->name)))
+			break;
+		err = 0;
+		break;
+	default:
+		pr_debug("Unknown ioctl cmd: %u\n", cmd);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+/*
+ * This can now be called from hard interrupt level as well
+ * as soft interrupt level or mainline.
+ */
+static void
+ieee802154_tty_receive(struct tty_struct *tty, const unsigned char *buf, char *cflags, int count)
+{
+	struct zb_device *zbdev;
+	int i;
+
+	/* Debug info */
+	printk(KERN_INFO "%lu %s, received %d bytes:", jiffies, __func__, count);
+	for (i = 0; i < count; ++i)
+		printk(KERN_CONT " 0x%02X", buf[i]);
+	printk(KERN_CONT "\n");
+
+	/* Actual processing */
+	zbdev = tty->disc_data;
+	if (NULL == zbdev) {
+		printk(KERN_ERR "%s(): record for tty is not found\n", __func__);
+		return;
+	}
+	for (i = 0; i < count; ++i)
+		process_char(zbdev, buf[i]);
+#if 0
+	if (tty->driver->flush_chars)
+		tty->driver->flush_chars(tty);
+#endif
+	tty_unthrottle(tty);
+}
+
+/*
+ * Line discipline device structure
+ */
+static struct tty_ldisc_ops ieee802154_ldisc = {
+	.owner  = THIS_MODULE,
+	.magic	= TTY_LDISC_MAGIC,
+	.name	= "ieee802154-ldisc",
+	.open	= ieee802154_tty_open,
+	.close	= ieee802154_tty_close,
+	.hangup	= ieee802154_tty_hangup,
+	.receive_buf = ieee802154_tty_receive,
+	.ioctl	= ieee802154_tty_ioctl,
+};
+
+/*****************************************************************************
+ * Module service routinues
+ *****************************************************************************/
+
+static int __init ieee802154_serial_init(void)
+{
+	printk(KERN_INFO "Initializing ZigBee TTY interface\n");
+
+	if (tty_register_ldisc(N_IEEE802154, &ieee802154_ldisc) != 0) {
+		printk(KERN_ERR "%s: line discipline register failed\n", __func__);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void __exit ieee802154_serial_cleanup(void)
+{
+	if (tty_unregister_ldisc(N_IEEE802154) != 0)
+		printk(KERN_CRIT "failed to unregister ZigBee line discipline.\n");
+}
+
+module_init(ieee802154_serial_init);
+module_exit(ieee802154_serial_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_IEEE802154);
+
diff --git a/include/linux/tty.h b/include/linux/tty.h
index fc39db9..f533beb 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -23,7 +23,7 @@
  */
 #define NR_UNIX98_PTY_DEFAULT	4096      /* Default maximum for Unix98 ptys */
 #define NR_UNIX98_PTY_MAX	(1 << MINORBITS) /* Absolute limit */
-#define NR_LDISCS		19
+#define NR_LDISCS		20
 
 /* line disciplines */
 #define N_TTY		0
@@ -46,6 +46,7 @@
 #define N_GIGASET_M101	16	/* Siemens Gigaset M101 serial DECT adapter */
 #define N_SLCAN		17	/* Serial / USB serial CAN Adaptors */
 #define N_PPS		18	/* Pulse per Second */
+#define N_IEEE802154	19	/* Serial / USB serial IEEE802154.4 devices */
 
 /*
  * This character is the same as _POSIX_VDISABLE: it cannot be used as
-- 
1.6.2.4