All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
To: netdev@vger.kernel.org, linux-wireless@vger.kernel.org
Cc: slapin@ossfans.org, maxim.osipov@siemens.com,
	dmitry.baryshkov@siemens.com, oliver.fendt@siemens.com,
	Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Subject: [PATCH 5/5] ieee802154: add serial dongle driver
Date: Tue, 26 May 2009 15:23:08 +0400	[thread overview]
Message-ID: <1243336988-20109-5-git-send-email-dbaryshkov@gmail.com> (raw)
In-Reply-To: <1243336988-20109-4-git-send-email-dbaryshkov@gmail.com>

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 | 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 <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/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


  reply	other threads:[~2009-05-26 11:23 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-05-26 11:21 [RFC][WIP] IEEE 802.15.4 implementation for Linux Dmitry Eremin-Solenikov
2009-05-26 11:23 ` [PATCH 1/5] Add constants for the ieee 802.15.4/ZigBee stack Dmitry Eremin-Solenikov
2009-05-26 11:23   ` [PATCH 2/5] net: add IEEE 802.15.4 partial implementation Dmitry Eremin-Solenikov
2009-05-26 11:23     ` [PATCH 3/5] ieee802154: add socket address family code Dmitry Eremin-Solenikov
2009-05-26 11:23       ` [PATCH 4/5] ieee802154: add virtual loopback driver Dmitry Eremin-Solenikov
2009-05-26 11:23         ` Dmitry Eremin-Solenikov [this message]
2009-05-28  3:08     ` [PATCH 2/5] net: add IEEE 802.15.4 partial implementation Ben Hutchings
2009-05-28  6:35       ` Maxim Osipov
2009-05-28  8:48       ` Dmitry Eremin-Solenikov
2009-05-30 16:32         ` Ben Hutchings
2009-05-27  9:58 ` [RFC][WIP] IEEE 802.15.4 implementation for Linux Florian Fainelli
2009-05-27 12:46   ` Dmitry Eremin-Solenikov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1243336988-20109-5-git-send-email-dbaryshkov@gmail.com \
    --to=dbaryshkov@gmail.com \
    --cc=dmitry.baryshkov@siemens.com \
    --cc=linux-wireless@vger.kernel.org \
    --cc=maxim.osipov@siemens.com \
    --cc=netdev@vger.kernel.org \
    --cc=oliver.fendt@siemens.com \
    --cc=slapin@ossfans.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.