diff mbox

[09/10] ieee802154: add serial dongle driver

Message ID 1243868091-5315-10-git-send-email-dbaryshkov@gmail.com
State RFC, archived
Delegated to: David Miller
Headers show

Commit Message

Dmitry Baryshkov June 1, 2009, 2:54 p.m. UTC
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 <dbaryshkov@gmail.com>
Signed-off-by: Sergey Lapin <slapin@ossfans.org>
---
 drivers/ieee802154/Kconfig  |    3 +
 drivers/ieee802154/Makefile |    1 +
 drivers/ieee802154/serial.c |  999 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/tty.h         |    3 +-
 4 files changed, 1005 insertions(+), 1 deletions(-)
 create mode 100644 drivers/ieee802154/serial.c

Comments

Alan Cox June 1, 2009, 3:27 p.m. UTC | #1
> +	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);

Where do you check that the tty has enough space ?


> +	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);

In all these ERR cases what stops a remote device from having fun spewing
garbage at you and filling the log ?

>
> +	 * 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);

Shouldn't be driver specific hacks in the ldisc surely - or is the ldisc
only applicable to a specific single bit of hardware ?

> + minor = tty->index + tty->driver->minor_start;
> +	zbdev->dev->parent = class_find_device(tty_class, NULL, &minor, dev_minor_match);
> +

That sort of thing shouldn't be buried in the depths of a driver. I'm not
entirely averse to that sort of thing but it belongs in a helper in the
tty layer.

> +	zbdev->tty = tty;

Refcounting ?

> +	cleanup(zbdev);
> +
> +	tty->disc_data = zbdev;
> +	tty->receive_room = MAX_DATA_SIZE;
> +	tty->low_latency = 1;

You can't go around mashing driver internal values - many drivers don't
support low_latency mode and this will make them crash.

> +
> +	/* FIXME: why is this needed. Note don't use ldisc_ref here as the
> +	   open path is before the ldisc is referencable */
> +

[Because otherwise after a SET_LDISC there may be bits from the old stuff
 left over - this one will go away as I finish the ldisc switching
 rewrite that is in the ttydev tree]

> +/*
> + * 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;

Again you want a refcount on these I suspect. You may actually be safe
anyway but it would be cleaner to take/drop refs.



> +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;
> +	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;
> +	}

If that is NULL it's a serious bug so WARN on it I think

> +	default:
> +		pr_debug("Unknown ioctl cmd: %u\n", cmd);
> +		return -EINVAL;

This will break default ioctl processing. You probably want to call into
some of the generic handlers at this point depending upon your hardware.
Either way -EINVAL is wrong.

> +	}
> +	return 0;

Unreachable

> +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");

I don't think the above is meant to be there....

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Dmitry Baryshkov June 1, 2009, 8:29 p.m. UTC | #2
Thanks for the review, Alan,

On Mon, Jun 01, 2009 at 04:27:32PM +0100, Alan Cox wrote:
> > +	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);
> 
> Where do you check that the tty has enough space ?

Basically that means checking for ->write result, doesn't it?

> 
> 
> > +	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);
> 
> In all these ERR cases what stops a remote device from having fun spewing
> garbage at you and filling the log ?

And what stops your precious IDE controller from spewing garbage and
filling the log? Nothing I think. I'll lower the priority of the
messages though.

> > +	 * 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);
> 
> Shouldn't be driver specific hacks in the ldisc surely - or is the ldisc
> only applicable to a specific single bit of hardware ?

Currently it's applicable to only one (well, two) type of evkits from
FreeScale flashed with pretty much specific firmware. For other evkits
one have to write firmware on his own. Unlike bluetooth there is no
standard for such communication over serial proto. Also if there will be
any RS-232 device which implements IEEE 802.15.4 PHY layer and doesn't
follow this protocol, we can extend the ldisc to be more like hci-uart:
a multiplexor of protocols.

> > + minor = tty->index + tty->driver->minor_start;
> > +	zbdev->dev->parent = class_find_device(tty_class, NULL, &minor, dev_minor_match);
> > +
> 
> That sort of thing shouldn't be buried in the depths of a driver. I'm not
> entirely averse to that sort of thing but it belongs in a helper in the
> tty layer.

Fine with me. I'll move this into helper in tty layer.

> > +	zbdev->tty = tty;
> 
> Refcounting ?

Sample code? SLIP, hci-uart, ppp don't do it.

> > +	cleanup(zbdev);
> > +
> > +	tty->disc_data = zbdev;
> > +	tty->receive_room = MAX_DATA_SIZE;
> > +	tty->low_latency = 1;
> 
> You can't go around mashing driver internal values - many drivers don't
> support low_latency mode and this will make them crash.

C&P from drivers/bluetooth/hci-ldisc.c, around line 466. We had problems
working with low-latency unset.

> > +/*
> > + * 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;
> 
> Again you want a refcount on these I suspect. You may actually be safe
> anyway but it would be cleaner to take/drop refs.

See above. Didn't see refounting in any of ldiscs checked.
That's not a 'no, I won't do it', but rather 'wait, all those are also
buggy ?!'

> > +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;
> > +	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;
> > +	}
> 
> If that is NULL it's a serious bug so WARN on it I think

I tend to agree.


> > +	default:
> > +		pr_debug("Unknown ioctl cmd: %u\n", cmd);
> > +		return -EINVAL;
> 
> This will break default ioctl processing. You probably want to call into
> some of the generic handlers at this point depending upon your hardware.
> Either way -EINVAL is wrong.


n_tty_ioctl_helper ?

> > +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");
> 
> I don't think the above is meant to be there....

In final version, it will go away.
Alan Cox June 1, 2009, 9:52 p.m. UTC | #3
> > > +	return _send_pending_data(zbdev);
> > 
> > Where do you check that the tty has enough space ?
> 
> Basically that means checking for ->write result, doesn't it?

You should check write_room before writing if you want some control - you
can also then respect internal flow control via the wakeup mechanism.

Something like:
                /* Wait for space */
                while ((space = tty_write_room(tty)) < len) {
                        if (file->f_flags & O_NONBLOCK) {
                                err = -EAGAIN;
                                break;
                        }
                        interruptible_sleep_on(&tty->write_wait);
                        if (signal_pending(current)) {
                                err = -EINTR;
                                break;
                        }
                }
                /* Shove the entire frame down */
                tty->ops->write(tty, data, len);
 
> > In all these ERR cases what stops a remote device from having fun spewing
> > garbage at you and filling the log ?
> 
> And what stops your precious IDE controller from spewing garbage and
> filling the log? Nothing I think. I'll lower the priority of the
> messages though.

The fact the drive is entirely under my control and not potentially being
spewed at from a hostile network. Also the fact that it takes about 30
seconds to spank a misbehaving drive and reset it. The network laye
generally uses time checks (search for ratelimit()).

> any RS-232 device which implements IEEE 802.15.4 PHY layer and doesn't
> follow this protocol, we can extend the ldisc to be more like hci-uart:
> a multiplexor of protocols.

Ok

> > > +	zbdev->tty = tty;
> > 
> > Refcounting ?
> 
> Sample code? SLIP, hci-uart, ppp don't do it.

No I'm still running around clobbering them all - and slip needed a
partial rewrite first.

	zbdev->tty = tty_kref_get(tty);

	tty_kref_put(foo);

> > You can't go around mashing driver internal values - many drivers don't
> > support low_latency mode and this will make them crash.
> 
> C&P from drivers/bluetooth/hci-ldisc.c, around line 466. We had problems
> working with low-latency unset.

The bluetooth one needs killing too. I will do that tomorrow ...

How many releases ago - the entire tty buffering layer has been rewritten
over time. tty->low_latency requires driver specific support that most
don't have. Also as of 2.6.30rc you'll get debug spew if you misuse it.

If you still need it we need to know why.


> That's not a 'no, I won't do it', but rather 'wait, all those are also
> buggy ?!'

The tty layer has a lot of "yes, those are also buggy" - which is why I'm
currently half way through systematically brutalising it.


> > > +	default:
> > > +		pr_debug("Unknown ioctl cmd: %u\n", cmd);
> > > +		return -EINVAL;
> > 
> > This will break default ioctl processing. You probably want to call into
> > some of the generic handlers at this point depending upon your hardware.
> > Either way -EINVAL is wrong.
> 
> 
> n_tty_ioctl_helper ?

That depends what you need. tty_mode_ioctl() gives you all the mode
stuff, n_tty_ioctl_helper adds things like software flow management which
don't actually look relevant to your ldisc ?

Any other queries let me know, and if the tty->low_latency is still
needed let me know and we'll figure out why between us, as it should not
be.

Alan
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Sergey Lapin June 2, 2009, 2:43 p.m. UTC | #4
> > C&P from drivers/bluetooth/hci-ldisc.c, around line 466. We had problems
> > working with low-latency unset.
> 
> The bluetooth one needs killing too. I will do that tomorrow ...
> 
> How many releases ago - the entire tty buffering layer has been rewritten
> over time. tty->low_latency requires driver specific support that most
> don't have. Also as of 2.6.30rc you'll get debug spew if you misuse it.
> 
> If you still need it we need to know why.

As we're tested it here, we don't need tty->low_latency anymore.
So that can go away. Thanks!

--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/ieee802154/Kconfig b/drivers/ieee802154/Kconfig
index d1799e3..0e65572 100644
--- a/drivers/ieee802154/Kconfig
+++ b/drivers/ieee802154/Kconfig
@@ -30,5 +30,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 2bd7bdf..ca41e99 100644
--- a/drivers/ieee802154/Makefile
+++ b/drivers/ieee802154/Makefile
@@ -1,4 +1,5 @@ 
 obj-$(CONFIG_IEEE802154_FAKEHARD) += fakehard.o
 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..5c452dd
--- /dev/null
+++ b/drivers/ieee802154/serial.c
@@ -0,0 +1,999 @@ 
+/*
+ * 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 <maxim.gorbachev@siemens.com>
+ * Maxim Osipov <maxim.osipov@siemens.com>
+ * Sergey Lapin <sergey.lapin@siemens.com>
+ */
+
+#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/mac802154.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_FLAGS_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;
+	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 */
+		BUG_ON(!zbdev->dev->netdev);
+		err = -EFAULT;
+		if (copy_to_user(argp, zbdev->dev->netdev->name, strlen(zbdev->dev->netdev->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