---
 drivers/ieee802154/Kconfig  |    4 
 drivers/ieee802154/Makefile |    1 
 drivers/ieee802154/serial.c | 1047 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/tty.h         |    1 
 4 files changed, 1053 insertions(+), 0 deletions(-)
 create mode 100644 drivers/ieee802154/serial.c

diff --git a/drivers/ieee802154/Kconfig b/drivers/ieee802154/Kconfig
index e0ea26e..9a796f8 100644
--- a/drivers/ieee802154/Kconfig
+++ b/drivers/ieee802154/Kconfig
@@ -31,3 +31,7 @@ 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
+       depends on IEEE802154_DRIVERS && MAC802154
+       tristate "Simple LR-WPAN UART driver"
+
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..e4485bc
--- /dev/null
+++ b/drivers/ieee802154/serial.c
@@ -0,0 +1,1047 @@
+/*
+ * 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, 2009 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 protected]>
+ * Maxim Osipov <[email protected]>
+ * Sergey Lapin <[email protected]>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/tty.h>
+#include <linux/skbuff.h>
+#include <linux/sched.h>
+#include <net/mac802154.h>
+#include <net/wpan-phy.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
+
+#define STATUS_WAIT    ((u8) -1) /* waiting for the answer */
+
+/* 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] */
+       RESP_RECV_BLOCK         = 0x0b, /* u8 id, u8 status */
+       CMD_ADDRESS             = 0x0d, /* u8 id */
+
+       /* 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 */
+       DATA_RECV_BLOCK         = 0x8b, /* u8 id, u8 lq, u8 len, u8 data[len] */
+       RESP_ADDRESS            = 0x8d, /* u8 id, u8 status, u8 u8 u8 u8 u8 u8 
u8 u8 address */
+};
+
+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;
+       u8                      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)
+{
+       struct tty_struct *tty;
+
+       BUG_ON(!zbdev);
+       tty = zbdev->tty;
+       if (!tty)
+               return -ENODEV;
+
+       zbdev->status = STATUS_WAIT;
+
+       /* Debug info */
+       printk(KERN_INFO "%s, %d bytes\n", __func__,
+                       zbdev->pending_size);
+#ifdef DEBUG
+       print_hex_dump_bytes("send_pending_data ", DUMP_PREFIX_NONE,
+                       zbdev->pending_data, zbdev->pending_size);
+#endif
+
+       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;
+       /* 4 because of 2 start bytes, id and optional extra */
+       u8 buf[4];
+
+       /* 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;
+       /* 4 because of 2 start bytes, id and optional extra */
+       u8 buf[4];
+
+       /* 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 DATA_RECV_BLOCK:
+       case RESP_ADDRESS:
+               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_RECV_BLOCK == zbdev->id) ||
+               (CMD_ADDRESS == zbdev->pending_id &&
+                       RESP_ADDRESS == 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) {
+               /* XXX: w/around for old FW, REMOVE */
+               if (zbdev->param1 == STATUS_IDLE)
+                       zbdev->status = STATUS_SUCCESS;
+               else
+                       zbdev->status = zbdev->param1;
+       }
+
+       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 RESP_ADDRESS:
+               pr_debug("Received address, 
%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", 
+                       zbdev->data[0], zbdev->data[1], zbdev->data[2], 
zbdev->data[3],
+                       zbdev->data[4], zbdev->data[5], zbdev->data[6], 
zbdev->data[7]);
+               break;
+       }
+
+       wake_up(&zbdev->wq);
+}
+
+static void
+process_char(struct zb_device *zbdev, unsigned char c)
+{
+       /* Data processing */
+       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 if (RESP_ADDRESS == zbdev->id) {
+                       zbdev->param2 = 8;
+                       zbdev->state = STATE_WAIT_DATA;
+               } 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;
+       /* 4 because of 2 start bytes, id and optional extra */
+       u8 buf[4];
+
+       /* 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 int
+ieee802154_serial_set_channel(struct ieee802154_dev *dev, int page, int 
channel)
+{
+       struct zb_device *zbdev;
+       int ret = 0;
+
+       pr_debug("%s %d\n", __func__, channel);
+
+       zbdev = dev->priv;
+       if (NULL == zbdev) {
+               printk(KERN_ERR "%s: wrong phy\n", __func__);
+               return -EINVAL;
+       }
+
+       BUG_ON(page != 0);
+       /* 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 (mutex_lock_interruptible(&zbdev->mutex))
+               return -EINTR;
+       ret = send_cmd2(zbdev, CMD_SET_CHANNEL, channel - 10);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               if (zbdev->status != STATUS_SUCCESS)
+                       ret = -EBUSY;
+       } else
+               ret = -EINTR;
+
+       if (!ret)
+               zbdev->dev->phy->current_channel = channel;
+out:
+       mutex_unlock(&zbdev->mutex);
+       pr_debug("%s end\n", __func__);
+       return ret;
+}
+
+static int
+ieee802154_serial_ed(struct ieee802154_dev *dev, u8 *level)
+{
+       struct zb_device *zbdev;
+       int ret = 0;
+
+       pr_debug("%s\n", __func__);
+
+       zbdev = dev->priv;
+       if (NULL == zbdev) {
+               printk(KERN_ERR "%s: wrong phy\n", __func__);
+               return -EINVAL;
+       }
+
+       if (mutex_lock_interruptible(&zbdev->mutex))
+               return -EINTR;
+
+       ret = send_cmd(zbdev, CMD_ED);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               *level = zbdev->ed;
+               if (zbdev->status != STATUS_SUCCESS)
+                       ret = -EBUSY;
+       } else
+               ret = -ETIMEDOUT;
+out:
+
+       mutex_unlock(&zbdev->mutex);
+       pr_debug("%s end\n", __func__);
+       return ret;
+}
+
+static int
+ieee802154_serial_address(struct ieee802154_dev *dev, u8 addr[IEEE802154_ALEN])
+{
+       struct zb_device *zbdev;
+       int ret = 0;
+
+       pr_debug("%s\n", __func__);
+
+       zbdev = dev->priv;
+       if (NULL == zbdev) {
+               printk(KERN_ERR "%s: wrong phy\n", __func__);
+               return -EINVAL;
+       }
+
+       if (mutex_lock_interruptible(&zbdev->mutex))
+               return -EINTR;
+
+       ret = send_cmd(zbdev, CMD_ADDRESS);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               memcpy(addr, zbdev->data, sizeof addr);
+               if (zbdev->status != STATUS_SUCCESS)
+                       ret = -EBUSY;
+       } else
+               ret = -ETIMEDOUT;
+out:
+
+       mutex_unlock(&zbdev->mutex);
+       pr_debug("%s end\n", __func__);
+       return ret;
+}
+
+static int
+ieee802154_serial_start(struct ieee802154_dev *dev)
+{
+       struct zb_device *zbdev;
+       int ret = 0;
+
+       pr_debug("%s\n", __func__);
+
+       zbdev = dev->priv;
+       if (NULL == zbdev) {
+               printk(KERN_ERR "%s: wrong phy\n", __func__);
+               return -EINVAL;
+       }
+
+       if (mutex_lock_interruptible(&zbdev->mutex))
+               return -EINTR;
+
+       ret = send_cmd2(zbdev, CMD_SET_STATE, RX_MODE);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               if (zbdev->status != STATUS_SUCCESS)
+                       ret = -EBUSY;
+       } else
+               ret = -ETIMEDOUT;
+out:
+       mutex_unlock(&zbdev->mutex);
+       pr_debug("%s end\n", __func__);
+       return ret;
+}
+
+static void
+ieee802154_serial_stop(struct ieee802154_dev *dev)
+{
+       struct zb_device *zbdev;
+       pr_debug("%s\n", __func__);
+
+       zbdev = dev->priv;
+       if (NULL == zbdev) {
+               printk(KERN_ERR "%s: wrong phy\n", __func__);
+               return;
+       }
+
+       if (mutex_lock_interruptible(&zbdev->mutex))
+               return;
+
+
+       if (send_cmd2(zbdev, CMD_SET_STATE, FORCE_TRX_OFF) != 0)
+               goto out;
+
+       wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000));
+out:
+       mutex_unlock(&zbdev->mutex);
+       pr_debug("%s end\n", __func__);
+}
+
+static int
+ieee802154_serial_xmit(struct ieee802154_dev *dev, struct sk_buff *skb)
+{
+       struct zb_device *zbdev;
+       int ret;
+
+       pr_debug("%s\n", __func__);
+
+       zbdev = dev->priv;
+       if (NULL == zbdev) {
+               printk(KERN_ERR "%s: wrong phy\n", __func__);
+               return -EINVAL;
+       }
+
+       if (mutex_lock_interruptible(&zbdev->mutex))
+               return -EINTR;
+
+       ret = send_cmd(zbdev, CMD_CCA);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               if (zbdev->status != STATUS_SUCCESS) {
+                       ret = -EBUSY;
+                       goto out;
+               }
+       } else {
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+       ret = send_cmd2(zbdev, CMD_SET_STATE, TX_MODE);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               if (zbdev->status != STATUS_SUCCESS) {
+                       ret = -EBUSY;
+                       goto out;
+               }
+       } else {
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+       ret = send_block(zbdev, skb->len, skb->data);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               if (zbdev->status != STATUS_SUCCESS) {
+                       ret = -EBUSY;
+                       goto out;
+               }
+       } else {
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+       ret = send_cmd2(zbdev, CMD_SET_STATE, RX_MODE);
+       if (ret)
+               goto out;
+
+       if (wait_event_interruptible_timeout(zbdev->wq,
+                               zbdev->status != STATUS_WAIT,
+                               msecs_to_jiffies(1000)) > 0) {
+               if (zbdev->status != STATUS_SUCCESS) {
+                       ret = -EBUSY;
+                       goto out;
+               }
+       } else {
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+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,
+       .xmit           = ieee802154_serial_xmit,
+       .ed             = ieee802154_serial_ed,
+       .set_channel    = ieee802154_serial_set_channel,
+       .start          = ieee802154_serial_start,
+       .stop           = ieee802154_serial_stop,
+       .ieee_addr      = ieee802154_serial_address,
+};
+
+/*
+ * 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;
+       struct ieee802154_dev *dev;
+       int err;
+
+       pr_debug("Openning ldisc\n");
+       if (!capable(CAP_NET_ADMIN))
+               return -EPERM;
+
+       if (tty->disc_data)
+               return -EBUSY;
+
+       dev = ieee802154_alloc_device(sizeof(*zbdev), &serial_ops);
+       if (!dev)
+               return -ENOMEM;
+
+       zbdev = dev->priv;
+       zbdev->dev = dev;
+
+       mutex_init(&zbdev->mutex);
+       init_completion(&zbdev->open_done);
+       init_waitqueue_head(&zbdev->wq);
+
+       dev->extra_tx_headroom = 0;
+       /* only 2.4 GHz band */
+       dev->phy->channels_supported[0] = 0x7fff800;
+
+       dev->flags = IEEE802154_HW_OMIT_CKSUM;
+
+       dev->parent = tty->dev;
+
+       zbdev->tty = tty_kref_get(tty);
+
+       cleanup(zbdev);
+
+       tty->disc_data = zbdev;
+       tty->receive_room = MAX_DATA_SIZE;
+
+       /* 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(dev);
+       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;
+       tty_kref_put(tty);
+       zbdev->tty = NULL;
+
+       ieee802154_free_device(zbdev->dev);
+
+       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;
+       tty_kref_put(tty);
+       zbdev->tty = NULL;
+
+       ieee802154_unregister_device(zbdev->dev);
+
+       tty_ldisc_flush(tty);
+       tty_driver_flush_buffer(tty);
+
+       ieee802154_free_device(zbdev->dev);
+}
+
+/*
+ * 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;
+
+       pr_debug("cmd = 0x%x\n", cmd);
+
+       zbdev = tty->disc_data;
+       if (NULL == zbdev) {
+               pr_debug("match is not found\n");
+               return -EINVAL;
+       }
+
+       switch (cmd) {
+       case TCFLSH:
+               return tty_perform_flush(tty, arg);
+       default:
+               /* Try the mode commands */
+               return tty_mode_ioctl(tty, file, cmd, arg);
+       }
+}
+
+
+/*
+ * 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 "%s, received %d bytes\n", __func__,
+                       count);
+#ifdef DEBUG
+       print_hex_dump_bytes("ieee802154_tty_receive ", DUMP_PREFIX_NONE,
+                       buf, count);
+#endif
+
+       /* 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 54e4eaa..d03bd4d 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -50,6 +50,7 @@
 #define N_CAIF         20      /* CAIF protocol for talking to modems */
 #define N_GSM0710      21      /* GSM 0710 Mux */
 #define N_TI_WL                22      /* for TI's WL BT, FM, GPS combo chips 
*/
+#define N_IEEE802154   23      /* Serial / USB serial IEEE802154.4 devices */
 
 /*
  * This character is the same as _POSIX_VDISABLE: it cannot be used as


------------------------------------------------------------------------------
Colocation vs. Managed Hosting
A question and answer guide to determining the best fit
for your organization - today and in the future.
http://p.sf.net/sfu/internap-sfd2d
_______________________________________________
Linux-zigbee-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/linux-zigbee-devel

Reply via email to