[RFCv2,bluetooth-next,19/19] 6lowpan: bluetooth: add new implementation
diff mbox

Message ID 20160807143056.3116-20-aar@pengutronix.de
State New
Headers show

Commit Message

Alexander Aring Aug. 7, 2016, 2:30 p.m. UTC
This patch adds a new btle 6lowpan implementation in version 0.2. This
new implementation has a new userspace API for debugfs.

To test it:

Node A:
btmgmt power off
echo "1" > /sys/kernel/debug/bluetooth/6lowpan_enable
btmgmt power on

hciconfig hci0 leadv

Node B:
btmgmt power off
echo "1" > /sys/kernel/debug/bluetooth/6lowpan_enable
btmgmt power on

hcitool lecc $1
echo "connect $1 1" > /sys/kernel/debug/bluetooth/hci0/6lowpan_control

Where $1 is the address of Node A.

When the hci get's powered on the 6LoWPAN interface will be created with
a device address of 0...0, after the first connection is established
then the device address will be set. During this time it's not possible
to set the interface up.

The "6lowpan_enable" debugfs entry need to be 1 before setting hci power
on.

Signed-off-by: Alexander Aring <aar@pengutronix.de>
---
 include/net/bluetooth/hci_core.h |   4 +
 net/bluetooth/6lowpan.c          | 924 +++++++++++++++++++++++++++++++++++++++
 net/bluetooth/Makefile           |   3 +
 3 files changed, 931 insertions(+)
 create mode 100644 net/bluetooth/6lowpan.c

Patch
diff mbox

diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index f808aa0..6d9ce5f 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -403,6 +403,10 @@  struct hci_dev {
 	struct led_trigger	*power_led;
 #endif
 
+#if IS_ENABLED(CONFIG_BT_6LOWPAN)
+	struct net_device *ldev;
+#endif
+
 	int (*open)(struct hci_dev *hdev);
 	int (*close)(struct hci_dev *hdev);
 	int (*flush)(struct hci_dev *hdev);
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
new file mode 100644
index 0000000..9ca9448
--- /dev/null
+++ b/net/bluetooth/6lowpan.c
@@ -0,0 +1,924 @@ 
+/*
+   (C) 2016 Pengutronix, Alexander Aring <aar@pengutronix.de>
+   Copyright (c) 2013-2014 Intel Corp.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License version 2 and
+   only 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.
+*/
+
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+
+#include <net/ipv6.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/l2cap.h>
+
+#include <net/6lowpan.h>
+
+#define LOWPAN_BTLE_VERSION "0.2"
+
+#define lowpan_le48_to_be48(dst, src) baswap((bdaddr_t *)dst, (bdaddr_t *)src)
+#define lowpan_be48_to_le48(dst, src) baswap((bdaddr_t *)dst, (bdaddr_t *)src)
+
+struct lowpan_btle_cb {
+	struct l2cap_chan *chan;
+};
+
+struct lowpan_btle_dev {
+	struct hci_dev *hdev;
+	struct workqueue_struct *workqueue;
+	struct l2cap_chan *listen;
+	struct list_head peers;
+	struct dentry *control;
+};
+
+/* This flag avoids to run list_del_rcu in close when no ready callback was
+ * run before. This seems to be possible by l2cap_chan_timeout.
+ */
+#define LOWPAN_BTLE_PEER_WAS_READY	0
+
+struct lowpan_peer {
+	struct l2cap_chan *chan;
+	unsigned long flags;
+	struct list_head list;
+};
+
+struct lowpan_chan_data {
+	struct lowpan_peer peer;
+	struct net_device *dev;
+};
+
+struct lowpan_xmit_work {
+	struct work_struct work;
+	struct l2cap_chan *chan;
+	struct net_device *dev;
+	unsigned int uncompressed_len;
+	struct sk_buff *skb;
+};
+
+static inline struct lowpan_btle_cb *
+lowpan_btle_cb(const struct sk_buff *skb)
+{
+	return (struct lowpan_btle_cb *)skb->cb;
+}
+
+static inline struct lowpan_chan_data *
+lowpan_chan_data(const struct l2cap_chan *chan)
+{
+	return (struct lowpan_chan_data *)chan->data;
+}
+
+static inline struct lowpan_btle_dev *
+lowpan_btle_dev(const struct net_device *dev)
+{
+	return (struct lowpan_btle_dev *)lowpan_dev(dev)->priv;
+}
+
+static inline struct lowpan_peer *
+lowpan_lookup_peer(struct lowpan_btle_dev *btdev, bdaddr_t *addr)
+{
+	struct lowpan_peer *entry;
+
+	list_for_each_entry_rcu(entry, &btdev->peers, list) {
+		if (!bacmp(&entry->chan->dst, addr))
+			return entry;
+	}
+
+	return NULL;
+}
+
+static struct dentry *lowpan_enabled_dentry;
+static bool lowpan_enabled;
+
+static int lowpan_give_skb_to_device(struct sk_buff *skb)
+{
+	skb->protocol = htons(ETH_P_IPV6);
+	skb->dev->stats.rx_packets++;
+	skb->dev->stats.rx_bytes += skb->len;
+
+	return netif_rx_ni(skb);
+}
+
+static int lowpan_rx_handlers_result(struct sk_buff *skb, lowpan_rx_result res)
+{
+	switch (res) {
+	case RX_CONTINUE:
+		/* nobody cared about this packet */
+		net_warn_ratelimited("%s: received unknown dispatch\n",
+				     __func__);
+
+		/* fall-through */
+	case RX_DROP_UNUSABLE:
+		kfree_skb(skb);
+
+		/* fall-through */
+	case RX_DROP:
+		return NET_RX_DROP;
+	case RX_QUEUED:
+		return lowpan_give_skb_to_device(skb);
+	default:
+		break;
+	}
+
+	return NET_RX_DROP;
+}
+
+static lowpan_rx_result lowpan_rx_h_iphc(struct sk_buff *skb)
+{
+	struct l2cap_chan *chan = lowpan_btle_cb(skb)->chan;
+	bdaddr_t daddr, saddr;
+	int ret;
+
+	if (!lowpan_is_iphc(*skb_network_header(skb)))
+		return RX_CONTINUE;
+
+	BT_DBG("recv %s dst: %pMR type %d src: %pMR chan %p",
+	       skb->dev->name, &chan->dst, chan->dst_type, &chan->src, chan);
+
+	/* bluetooth chan view is vice-versa */
+	bacpy(&daddr, &chan->src);
+	bacpy(&saddr, &chan->dst);
+
+	ret = lowpan_header_decompress(skb, skb->dev, &daddr, &saddr);
+	if (ret < 0)
+		return RX_DROP_UNUSABLE;
+
+	return RX_QUEUED;
+}
+
+static int lowpan_invoke_rx_handlers(struct sk_buff *skb)
+{
+	lowpan_rx_result res;
+
+#define CALL_RXH(rxh)			\
+	do {				\
+		res = rxh(skb);		\
+		if (res != RX_CONTINUE)	\
+			goto rxh_next;	\
+	} while (0)
+
+	/* likely at first */
+	CALL_RXH(lowpan_rx_h_iphc);
+
+rxh_next:
+	return lowpan_rx_handlers_result(skb, res);
+#undef CALL_RXH
+}
+
+static int lowpan_chan_recv(struct l2cap_chan *chan, struct sk_buff *skb)
+{
+	struct lowpan_chan_data *data = lowpan_chan_data(chan);
+	struct net_device *dev = data->dev;
+	int ret;
+
+	/* TODO handle BT_CONNECTED in bluetooth subsytem?
+	 * when calling recv callback, I hit that case somehow
+	 */
+	if (!netif_running(dev) || chan->state != BT_CONNECTED ||
+	    !skb->len || !lowpan_is_iphc(skb->data[0]))
+		goto drop;
+
+	/* Replacing skb->dev and followed rx handlers will manipulate skb. */
+	skb = skb_unshare(skb, GFP_ATOMIC);
+	if (!skb)
+		goto out;
+
+	skb->dev = dev;
+	skb_reset_network_header(skb);
+
+	/* remember that one for dst bdaddr. TODO handle that as priv data for
+	 * lowpan_invoke_rx_handlers parameter. Not necessary for skb->cb.
+	 */
+	lowpan_btle_cb(skb)->chan = chan;
+
+	ret = lowpan_invoke_rx_handlers(skb);
+	if (ret == NET_RX_DROP)
+		BT_DBG("recv %s dropped chan %p", skb->dev->name, chan);
+
+	return 0;
+
+drop:
+	kfree_skb(skb);
+out:
+	/* we handle to free skb on error, so must 0
+	 * seems that callback free the skb on error
+	 * otherwise.
+	 */
+	return 0;
+}
+
+static void lowpan_xmit_worker(struct work_struct *work)
+{
+	struct lowpan_btle_dev *btdev;
+	struct lowpan_xmit_work *xw;
+	struct l2cap_chan *chan;
+	struct net_device *dev;
+	struct msghdr msg = { };
+	struct kvec iv;
+	int ret;
+
+	xw = container_of(work, struct lowpan_xmit_work, work);
+	dev = xw->dev;
+	chan = xw->chan;
+	btdev = lowpan_btle_dev(dev);
+
+	iv.iov_base = xw->skb->data;
+	iv.iov_len = xw->skb->len;
+	iov_iter_kvec(&msg.msg_iter, WRITE | ITER_KVEC, &iv, 1, xw->skb->len);
+
+	BT_DBG("l2cap_chan_send %s dst: %pMR type %d src: %pMR chan %p",
+	       dev->name, &chan->dst, chan->dst_type, &chan->src, chan);
+
+	l2cap_chan_lock(chan);
+
+	ret = l2cap_chan_send(chan, &msg, xw->skb->len);
+	BT_DBG("transmit return value %d", ret);
+	if (ret < 0) {
+		BT_DBG("send %s failed chan %p", dev->name, chan);
+		kfree_skb(xw->skb);
+	} else {
+		consume_skb(xw->skb);
+		dev->stats.tx_bytes += xw->uncompressed_len;
+		dev->stats.tx_packets++;
+	}
+
+	l2cap_chan_unlock(chan);
+	l2cap_chan_put(chan);
+
+	kfree(xw);
+}
+
+static void lowpan_send_unicast_pkt(struct net_device *dev,
+				    struct l2cap_chan *chan,
+				    struct sk_buff *skb,
+				    unsigned int uncompressed_len)
+{
+	struct lowpan_xmit_work *xw;
+
+	/* copy to xmit work buffer */
+	xw = kzalloc(sizeof(*xw), GFP_ATOMIC);
+	if (!xw)
+		return;
+
+	/* chan->lock mutex need to be hold so change context to workqueue */
+	INIT_WORK(&xw->work, lowpan_xmit_worker);
+	xw->uncompressed_len = uncompressed_len;
+	/* freeing protected by ifdown workqueue sync */
+	xw->dev = dev;
+	/* disallow freeing of skb while context switch */
+	xw->skb = skb_get(skb);
+	/* disallow freeing while context switch */
+	l2cap_chan_hold(chan);
+	xw->chan = chan;
+
+	queue_work(lowpan_btle_dev(dev)->workqueue, &xw->work);
+}
+
+static void lowpan_send_mcast_pkt(struct net_device *dev, struct sk_buff *skb,
+				  unsigned int uncompressed_len)
+{
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+	struct lowpan_peer *peer;
+
+	BT_DBG("xmit %s starts multicasting", dev->name);
+
+	/* We need to send the packet to every device behind this
+	 * interface, because multicasting.
+	 *
+	 * TODO, rfc7668:
+	 *
+	 * If the 6LBR needs to send
+	 * a multicast packet to all its 6LNs, it has to replicate the packet
+	 * and unicast it on each link.  However, this may not be energy
+	 * efficient, and particular care must be taken if the central is
+	 * battery powered.  To further conserve power, the 6LBR MUST keep track
+	 * of multicast listeners at Bluetooth LE link-level granularity (not at
+	 * subnet granularity), and it MUST NOT forward multicast packets to
+	 * 6LNs that have not registered as listeners for multicast groups the
+	 * packets belong to.
+	 */
+	rcu_read_lock();
+
+	list_for_each_entry_rcu(peer, &btdev->peers, list)
+		lowpan_send_unicast_pkt(dev, peer->chan, skb, uncompressed_len);
+
+	rcu_read_unlock();
+}
+
+static netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct lowpan_addr_info *info = lowpan_addr_info(skb);
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+	unsigned int uncompressed_len = skb->len;
+	struct lowpan_peer *peer;
+	bdaddr_t daddr, saddr;
+	int ret;
+
+	/* We must take a copy of the skb before we modify/replace the ipv6
+	 * header as the header could be used elsewhere.
+	 */
+	skb = skb_unshare(skb, GFP_ATOMIC);
+	if (!skb) {
+		kfree_skb(skb);
+		goto out;
+	}
+
+	lowpan_be48_to_le48(&daddr, info->daddr);
+	lowpan_be48_to_le48(&saddr, info->saddr);
+
+	BT_DBG("xmit ndisc %s dst: %pMR src: %pMR",
+	       dev->name, &daddr, &saddr);
+
+	ret = lowpan_header_compress(skb, dev, &daddr, &saddr);
+	if (ret < 0) {
+		kfree_skb(skb);
+		goto out;
+	}
+
+	/* this should never be the case, otherwise iphc is broken */
+	WARN_ON_ONCE(skb->len > dev->mtu);
+
+	if (!memcmp(&daddr, dev->broadcast, dev->addr_len)) {
+		lowpan_send_mcast_pkt(dev, skb, uncompressed_len);
+	} else {
+		btdev = lowpan_btle_dev(dev);
+
+		rcu_read_lock();
+
+		peer = lowpan_lookup_peer(btdev, &daddr);
+		if (peer)
+			lowpan_send_unicast_pkt(dev, peer->chan, skb,
+						uncompressed_len);
+
+		rcu_read_unlock();
+	}
+
+	consume_skb(skb);
+
+out:
+	return NETDEV_TX_OK;
+}
+
+static int lowpan_open(struct net_device *dev)
+{
+	if (!memcmp(dev->dev_addr, BDADDR_ANY, dev->addr_len))
+		return -ENOTCONN;
+	else
+		return 0;
+}
+
+static int lowpan_stop(struct net_device *dev)
+{
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+
+	/* synchronize with xmit worker */
+	flush_workqueue(btdev->workqueue);
+	return 0;
+}
+
+static struct sk_buff *lowpan_chan_alloc_skb(struct l2cap_chan *chan,
+					     unsigned long hdr_len,
+					     unsigned long len, int nb)
+{
+	return bt_skb_alloc(hdr_len + len, GFP_KERNEL);
+}
+
+static long lowpan_chan_get_sndtimeo(struct l2cap_chan *chan)
+{
+	return L2CAP_CONN_TIMEOUT;
+}
+
+static struct l2cap_chan *lowpan_chan_create(struct net_device *dev)
+{
+	struct lowpan_chan_data *data;
+	struct l2cap_chan *chan;
+
+	chan = l2cap_chan_create_priv(sizeof(*data));
+	if (!chan)
+		return ERR_PTR(-ENOMEM);
+
+	l2cap_chan_set_defaults(chan);
+	chan->chan_type = L2CAP_CHAN_CONN_ORIENTED;
+	chan->mode = L2CAP_MODE_LE_FLOWCTL;
+	chan->imtu = dev->mtu;
+
+	data = chan->data;
+	data->peer.chan = chan;
+	data->dev = dev;
+	dev_hold(dev);
+
+	return chan;
+}
+
+static struct l2cap_chan *lowpan_chan_new_conn(struct l2cap_chan *pchan)
+{
+	struct lowpan_chan_data *data = lowpan_chan_data(pchan);
+	struct l2cap_chan *chan;
+
+	chan = lowpan_chan_create(data->dev);
+	if (IS_ERR(chan))
+		return NULL;
+
+	chan->ops = pchan->ops;
+	return chan;
+}
+
+static void lowpan_chan_ready(struct l2cap_chan *chan)
+{
+	struct lowpan_chan_data *data = lowpan_chan_data(chan);
+	struct net_device *dev = data->dev;
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+
+	rtnl_lock();
+
+	/* first connection which will be established */
+	if (list_empty(&btdev->peers)) {
+		bdaddr_t bdaddr;
+		u8 bdaddr_type;
+
+		/* set RPA device address to 6lo interface */
+		hci_copy_identity_address(btdev->hdev, &bdaddr, &bdaddr_type);
+
+		lowpan_le48_to_be48(dev->dev_addr, &bdaddr);
+		dev_open(dev);
+	}
+
+	BT_DBG("%s chan %p ready ", dev->name, chan);
+
+	/* make it visible for xmit */
+	list_add_tail_rcu(&data->peer.list, &btdev->peers);
+	synchronize_rcu();
+
+	set_bit(LOWPAN_BTLE_PEER_WAS_READY, &data->peer.flags);
+	rtnl_unlock();
+}
+
+static void lowpan_chan_close(struct l2cap_chan *chan)
+{
+	struct lowpan_chan_data *data = lowpan_chan_data(chan);
+	struct net_device *dev = data->dev;
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+
+	rtnl_lock();
+
+	BT_DBG("%s chan %p closed ", dev->name, chan);
+
+	if (test_and_clear_bit(LOWPAN_BTLE_PEER_WAS_READY, &data->peer.flags)) {
+		/* make it unvisible for xmit */
+		list_del_rcu(&data->peer.list);
+		synchronize_rcu();
+	}
+
+	/* if no peers are connected anymore */
+	if (list_empty(&btdev->peers)) {
+		dev_close(dev);
+		memcpy(dev->dev_addr, BDADDR_ANY, dev->addr_len);
+	}
+
+	rtnl_unlock();
+	dev_put(dev);
+}
+
+static const struct l2cap_ops lowpan_chan_ops = {
+	.name			= "L2CAP 6LoWPAN channel",
+	.new_connection		= lowpan_chan_new_conn,
+	.recv			= lowpan_chan_recv,
+	.close			= lowpan_chan_close,
+	.state_change		= l2cap_chan_no_state_change,
+	.ready			= lowpan_chan_ready,
+	.get_sndtimeo		= lowpan_chan_get_sndtimeo,
+	.alloc_skb		= lowpan_chan_alloc_skb,
+	.teardown		= l2cap_chan_no_teardown,
+	.defer			= l2cap_chan_no_defer,
+	.set_shutdown		= l2cap_chan_no_set_shutdown,
+	.resume			= l2cap_chan_no_resume,
+	.suspend		= l2cap_chan_no_suspend,
+};
+
+static int lowpan_change_mtu(struct net_device *dev, int new_mtu)
+{
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+
+	/* if dev is down, peers list are protected by RTNL */
+	if (netif_running(dev) || !list_empty(&btdev->peers))
+		return -EBUSY;
+
+	if (new_mtu < IPV6_MIN_MTU)
+		return -EINVAL;
+
+	dev->mtu = new_mtu;
+	return 0;
+}
+
+static const struct net_device_ops netdev_ops = {
+	.ndo_init		= lowpan_dev_init,
+	.ndo_open		= lowpan_open,
+	.ndo_stop		= lowpan_stop,
+	.ndo_start_xmit		= lowpan_xmit,
+	.ndo_change_mtu		= lowpan_change_mtu,
+};
+
+static void lowpan_free_netdev(struct net_device *dev)
+{
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+
+	destroy_workqueue(btdev->workqueue);
+	debugfs_remove(btdev->control);
+	hci_dev_put(btdev->hdev);
+}
+
+static void lowpan_setup(struct net_device *dev)
+{
+	memset(dev->broadcast, 0xff, sizeof(bdaddr_t));
+
+	dev->netdev_ops	= &netdev_ops;
+	dev->destructor	= lowpan_free_netdev;
+	dev->features	|= NETIF_F_NETNS_LOCAL;
+}
+
+static struct device_type bt_type = {
+	.name	= "bluetooth",
+};
+
+static struct l2cap_chan *lowpan_listen_chan_new_conn(struct l2cap_chan *pchan)
+{
+	struct l2cap_chan *chan;
+
+	chan = lowpan_chan_create(pchan->data);
+	if (IS_ERR(chan))
+		return NULL;
+
+	/* change ops with more functionality than listen,
+	 * which also free chan->data stuff.
+	 */
+	chan->ops = &lowpan_chan_ops;
+
+	return chan;
+}
+
+static const struct l2cap_ops lowpan_listen_chan_ops = {
+	.name			= "L2CAP 6LoWPAN listen channel",
+	.new_connection		= lowpan_listen_chan_new_conn,
+	.recv			= l2cap_chan_no_recv,
+	.close			= l2cap_chan_no_close,
+	.state_change		= l2cap_chan_no_state_change,
+	.ready			= l2cap_chan_no_ready,
+	.get_sndtimeo		= l2cap_chan_no_get_sndtimeo,
+	.alloc_skb		= l2cap_chan_no_alloc_skb,
+	.teardown		= l2cap_chan_no_teardown,
+	.defer			= l2cap_chan_no_defer,
+	.set_shutdown		= l2cap_chan_no_set_shutdown,
+	.resume			= l2cap_chan_no_resume,
+	.suspend		= l2cap_chan_no_suspend,
+};
+
+static int lowpan_create_listen_chan(struct net_device *dev)
+{
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(dev);
+	struct l2cap_chan *chan;
+	u8 bdaddr_type;
+	int ret;
+
+	/* don't use lowpan_chan_create here, because less functionality */
+	chan = l2cap_chan_create();
+	if (!chan)
+		return -ENOMEM;
+
+	chan->data = dev;
+	chan->ops = &lowpan_listen_chan_ops;
+	hci_copy_identity_address(btdev->hdev, &chan->src, &bdaddr_type);
+	if (bdaddr_type == ADDR_LE_DEV_PUBLIC)
+		chan->src_type = BDADDR_LE_PUBLIC;
+	else
+		chan->src_type = BDADDR_LE_RANDOM;
+
+	chan->state = BT_LISTEN;
+	atomic_set(&chan->nesting, L2CAP_NESTING_PARENT);
+
+	BT_DBG("chan %p src type %d", chan, chan->src_type);
+	ret = l2cap_add_psm(chan, BDADDR_ANY, cpu_to_le16(L2CAP_PSM_IPSP));
+	if (ret < 0) {
+		l2cap_chan_put(chan);
+		BT_ERR("psm cannot be added err %d", ret);
+		return ret;
+	}
+	btdev->listen = chan;
+
+	return 0;
+}
+
+static const struct file_operations lowpan_control_fops;
+
+static struct net_device *lowpan_btle_newlink(struct hci_dev *hdev)
+{
+	struct lowpan_btle_dev *btdev;
+	struct net_device *dev;
+	int err = -ENOMEM;
+
+	__module_get(THIS_MODULE);
+
+	dev = alloc_netdev(LOWPAN_PRIV_SIZE(sizeof(struct lowpan_btle_dev)),
+			   LOWPAN_IFNAME_TEMPLATE, NET_NAME_ENUM, lowpan_setup);
+	if (!dev)
+		goto out;
+
+	dev->addr_assign_type = NET_ADDR_PERM;
+	dev->addr_len = sizeof(bdaddr_t);
+	memcpy(dev->dev_addr, BDADDR_ANY, dev->addr_len);
+
+	SET_NETDEV_DEV(dev, &hdev->dev);
+	SET_NETDEV_DEVTYPE(dev, &bt_type);
+
+	btdev = lowpan_btle_dev(dev);
+	/* avoid freeing */
+	btdev->hdev = hci_dev_hold(hdev);
+	INIT_LIST_HEAD(&btdev->peers);
+	hdev->ldev = dev;
+
+	btdev->workqueue = alloc_ordered_workqueue(dev->name, WQ_MEM_RECLAIM);
+	if (!btdev->workqueue)
+		goto free_netdev;
+
+	err = lowpan_create_listen_chan(dev);
+	if (err < 0)
+		goto destroy_workqueue;
+
+	/* ignore error handling here, we cannot call unregister anymore
+	 * It's a bug, but it's debugfs... so ignore it.
+	 */
+	btdev->control = debugfs_create_file("6lowpan_control", 0644,
+					     hdev->debugfs, hdev,
+					     &lowpan_control_fops);
+	if (!btdev->control) {
+		err = -ENOMEM;
+		goto chan_close;
+	}
+
+	err = lowpan_register_netdevice(dev, LOWPAN_LLTYPE_BTLE);
+	if (err < 0)
+		goto debugfs_cleanup;
+
+	return dev;
+
+debugfs_cleanup:
+	debugfs_remove(btdev->control);
+chan_close:
+	l2cap_chan_close(btdev->listen, 0);
+	l2cap_chan_put(btdev->listen);
+destroy_workqueue:
+	destroy_workqueue(btdev->workqueue);
+free_netdev:
+	free_netdev(dev);
+out:
+	return ERR_PTR(err);
+}
+
+static void lowpan_btle_dellink(struct net_device *dev)
+{
+	lowpan_btle_dev(dev)->hdev->ldev = NULL;
+	debugfs_remove(lowpan_btle_dev(dev)->control);
+	lowpan_unregister_netdevice(dev);
+	module_put(THIS_MODULE);
+}
+
+static int lowpan_parse_le_bdaddr(struct hci_dev *hdev, unsigned char *buf,
+				  bdaddr_t *addr, u8 *addr_type)
+{
+	int n;
+
+	n = sscanf(buf, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx %hhu",
+		   &addr->b[5], &addr->b[4], &addr->b[3],
+		   &addr->b[2], &addr->b[1], &addr->b[0],
+		   addr_type);
+	if (n < 7)
+		return -EINVAL;
+
+	/* check if we handle le addresses */
+	if (!bdaddr_type_is_le(*addr_type))
+		return -EINVAL;
+
+	return n;
+}
+
+static ssize_t lowpan_control_write(struct file *fp,
+				    const char __user *user_buffer,
+				    size_t count, loff_t *position)
+{
+	char buf[32] = { };
+	size_t buf_size = min(count, sizeof(buf) - 1);
+	struct seq_file *file = fp->private_data;
+	struct hci_dev *hdev = file->private;
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(hdev->ldev);
+	struct lowpan_peer *peer;
+	/* slave address */
+	bdaddr_t addr;
+	u8 addr_type;
+	int ret;
+
+	if (copy_from_user(buf, user_buffer, buf_size))
+		return -EFAULT;
+
+	if (memcmp(buf, "connect ", 8) == 0) {
+		struct lowpan_peer *peer;
+		struct l2cap_chan *chan;
+
+		ret = lowpan_parse_le_bdaddr(hdev, &buf[8], &addr, &addr_type);
+		if (ret < 0)
+			return ret;
+
+		/* check if we already know that slave */
+		rcu_read_lock();
+		peer = lowpan_lookup_peer(btdev, &addr);
+		if (peer) {
+			rcu_read_unlock();
+			BT_DBG("6LoWPAN connection already exists");
+			return -EEXIST;
+		}
+		rcu_read_unlock();
+
+		chan = lowpan_chan_create(hdev->ldev);
+		if (IS_ERR(chan))
+			return PTR_ERR(chan);
+		chan->ops = &lowpan_chan_ops;
+
+		ret = l2cap_hdev_chan_connect(hdev, chan,
+					      cpu_to_le16(L2CAP_PSM_IPSP),
+					      0, &addr, addr_type);
+		if (ret < 0) {
+			l2cap_chan_put(chan);
+			return ret;
+		}
+
+		return count;
+	} else if (memcmp(buf, "disconnect ", 11) == 0) {
+		ret = lowpan_parse_le_bdaddr(hdev, &buf[11], &addr, &addr_type);
+		if (ret < 0)
+			return ret;
+
+		/* check if we don't know that slave */
+		rcu_read_lock();
+		peer = lowpan_lookup_peer(btdev, &addr);
+		if (!peer) {
+			rcu_read_unlock();
+			BT_DBG("6LoWPAN connection not found in peers");
+			return -ENOENT;
+		}
+		rcu_read_unlock();
+
+		/* first delete the peer out of list,
+		 * which makes it visiable to netdev,
+		 * will be done by close callback.
+		 */
+		l2cap_chan_close(peer->chan, 0);
+		l2cap_chan_put(peer->chan);
+	} else {
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static int lowpan_control_show(struct seq_file *f, void *ptr)
+{
+	struct hci_dev *hdev = f->private;
+	struct lowpan_btle_dev *btdev = lowpan_btle_dev(hdev->ldev);
+	struct lowpan_peer *peer;
+
+	rcu_read_lock();
+
+	list_for_each_entry_rcu(peer, &btdev->peers, list) {
+		seq_printf(f, "%pMR (type %u) state: %s\n",
+			   &peer->chan->dst, peer->chan->dst_type,
+			   state_to_string(peer->chan->state));
+	}
+
+	rcu_read_unlock();
+
+	return 0;
+}
+
+static int lowpan_control_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, lowpan_control_show, inode->i_private);
+}
+
+static const struct file_operations lowpan_control_fops = {
+	.open		= lowpan_control_open,
+	.read		= seq_read,
+	.write		= lowpan_control_write,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int lowpan_hci_dev_event(struct notifier_block *unused,
+				unsigned long event, void *ptr)
+{
+	struct lowpan_btle_dev *btdev;
+	struct hci_dev *hdev = ptr;
+	struct net_device *dev;
+	int ret = NOTIFY_OK;
+
+	rtnl_lock();
+
+	/* bluetooth handles that event type as int,
+	 * but there is no overflow yet.
+	 */
+	switch (event) {
+	case HCI_DEV_UP:
+		if (lowpan_enabled && !hdev->ldev) {
+			dev = lowpan_btle_newlink(hdev);
+			if (IS_ERR(dev)) {
+				BT_ERR("failed to create 6lowpan interface\n");
+				break;
+			}
+		}
+
+		ret = NOTIFY_DONE;
+		break;
+	case HCI_DEV_DOWN:
+	case HCI_DEV_UNREG:
+		if (!hdev->ldev)
+			break;
+
+		btdev = lowpan_btle_dev(hdev->ldev);
+		l2cap_chan_close(btdev->listen, 0);
+		l2cap_chan_put(btdev->listen);
+
+		lowpan_btle_dellink(hdev->ldev);
+
+		ret = NOTIFY_DONE;
+		break;
+	default:
+		break;
+	}
+
+	rtnl_unlock();
+
+	return ret;
+}
+
+static struct notifier_block lowpan_hci_dev_notifier = {
+	.notifier_call = lowpan_hci_dev_event,
+};
+
+static int lowpan_enabled_set(void *data, u64 val)
+{
+	if (val != 0 && val != 1)
+		return -EINVAL;
+
+	lowpan_enabled = val;
+	return 0;
+}
+
+static int lowpan_enabled_get(void *data, u64 *val)
+{
+	*val = lowpan_enabled;
+	return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(lowpan_enabled_fops, lowpan_enabled_get,
+			lowpan_enabled_set, "%llu\n");
+
+static int __init bt_6lowpan_init(void)
+{
+	int ret;
+
+	lowpan_enabled_dentry = debugfs_create_file("6lowpan_enable", 0644,
+						    bt_debugfs, NULL,
+						    &lowpan_enabled_fops);
+	if (!lowpan_enabled_dentry)
+		return -ENOMEM;
+
+	ret = register_hci_dev_notifier(&lowpan_hci_dev_notifier);
+	if (ret < 0)
+		debugfs_remove(lowpan_enabled_dentry);
+
+	return ret;
+}
+
+static void __exit bt_6lowpan_exit(void)
+{
+	unregister_hci_dev_notifier(&lowpan_hci_dev_notifier);
+	debugfs_remove(lowpan_enabled_dentry);
+}
+
+module_init(bt_6lowpan_init);
+module_exit(bt_6lowpan_exit);
+
+MODULE_AUTHOR("Jukka Rissanen <jukka.rissanen@linux.intel.com>");
+MODULE_DESCRIPTION("Bluetooth 6LoWPAN");
+MODULE_VERSION(LOWPAN_BTLE_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
index e347828..b3ff12e 100644
--- a/net/bluetooth/Makefile
+++ b/net/bluetooth/Makefile
@@ -7,6 +7,9 @@  obj-$(CONFIG_BT_RFCOMM)	+= rfcomm/
 obj-$(CONFIG_BT_BNEP)	+= bnep/
 obj-$(CONFIG_BT_CMTP)	+= cmtp/
 obj-$(CONFIG_BT_HIDP)	+= hidp/
+obj-$(CONFIG_BT_6LOWPAN) += bluetooth_6lowpan.o
+
+bluetooth_6lowpan-y := 6lowpan.o
 
 bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \
 	hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o lib.o \