Hi,
v6:
- rebased on top of Marcel's "Bluetooth: Provide L2CAP ops callback
for memcpy_fromiovec" patch
- re-wrote the debugfs psm value handler using DEFINE_SIMPLE_ATTRIBUTE
- renamed and refactored the module init/exit functions, also checked
that debugfs_remove() will gracefully accept NULL values
- added comment to disconnect_devices() why we have two separate
lists
v5:
- memory buffer made const in patch 1
- misc changes and refactoring in patch 2
v4:
- renamed l2cap_from_skbuf() to more logical l2cap_copy_into_skbuff()
in l2cap_core.c (patch 1)
- fixed the module usage count code in patch 4
Usage:
In the slave side do this:
$ modprobe bluetooth_6lowpan
$ echo 62 > /sys/kernel/debug/bluetooth/6lowpan_psm
$ hciconfig hci0 leadv
In the master side do this:
$ modprobe bluetooth_6lowpan
$ echo 62 > /sys/kernel/debug/bluetooth/6lowpan_psm
$ echo 'connect E0:06:E6:B7:2A:73 1' > \
/sys/kernel/debug/bluetooth/6lowpan_control
Cheers,
Jukka
Jukka Rissanen (4):
Bluetooth: 6LoWPAN: Use connected oriented channel instead of fixed
one
Bluetooth: 6LoWPAN: Create a kernel module
Bluetooth: 6LoWPAN: Count module usage
Bluetooth: 6LoWPAN: Remove network devices when unloading
include/net/bluetooth/hci.h | 1 -
include/net/bluetooth/hci_core.h | 1 -
include/net/bluetooth/l2cap.h | 1 -
net/bluetooth/6lowpan.c | 826 +++++++++++++++++++++++++++++----------
net/bluetooth/6lowpan.h | 47 ---
net/bluetooth/Kconfig | 6 +-
net/bluetooth/Makefile | 4 +-
net/bluetooth/hci_core.c | 45 ---
net/bluetooth/hci_event.c | 3 -
net/bluetooth/l2cap_core.c | 20 +-
10 files changed, 636 insertions(+), 318 deletions(-)
delete mode 100644 net/bluetooth/6lowpan.h
--
1.8.3.1
Hi Jukka,
>>>>> Create a CoC dynamically instead of one fixed channel for communication
>>>>> to peer devices.
>>>>>
>>>
>>>>> +static struct l2cap_ops bt_6lowpan_chan_ops;
>>>>
>>>> Why this does need a forward declaration. Lets avoid this. And just a reminder that l2cap_ops need to be const now.
>>>
>>> The chan_ops is needed when setting up the channel in new_connection cb,
>>> I did not see a way to avoid forward declaration here.
>>
>> then why does l2cap_sock.c does not need this forward declaration?
>>
>> Either your code is doing something funky or our l2cap_ops is not flexible. I think we should not require this forward declaration. Can we fix this?
>
> Hmm, it might be that I am doing something wrong. So the current code
> allocates a new channel in new_connection cb, because of that the
> channel's ops field need to be set and forward decl. is needed.
> l2cap_sock.c does the allocation of chan differently but I was not quite
> able to understand the magic behind it.
no idea on how this actually works. That is more a question for Gustavo. I wonder though why new_connection needs to set l2cap_chan_ops. Since it is known to the caller in l2cap_core.c. So once the new l2cap_chan is created, we can just assign the ops in l2cap_core.c. This is something you might want to figure out and then document what is going on there.
>>>>> + /* Remember the skb so that we can send EAGAIN to the caller if
>>>>> + * we run out of credits.
>>>>> + */
>>>>> + chan->data = skb;
>>>>
>>>> Isn't this one dangerous? Who owns the skb and also more important who frees it? Don't we have to reference the skb somehow?
>>>
>>> Sure, I will add ref here.
>>
>> Only if it is needed. I am still curious on how owns this SKB and who is responsible for freeing it. I want to make sure we do not leak memory here.
>
> Well, the skb comes from netdev so it should not disappear during BT
> transmit so in theory we would not need to ref the skb. Taking ref just
> increments the skb->users field so this is not very big issue.
I am just careful to no leak memory from the netdev.
>>>>> +static struct sk_buff *chan_alloc_skb_cb(struct l2cap_chan *chan,
>>>>> + unsigned long hdr_len,
>>>>> + unsigned long len, int nb)
>>>>> +{
>>>>> + return bt_skb_alloc(hdr_len + len, GFP_ATOMIC);
>>>>> +}
>>>>
>>>> this should no longer be there and just use the default one.
>>>>
>>>
>>> Using GFP_KERNEL in standard l2cap_chan_no_alloc_skb() will lead to this
>>> "might sleep" bug:
>>
>> So why is that? Who is calling l2cap_chan_send and where is it calling it from. The current assumption is that it is called from a context that can sleep. Is the netdev context not able to sleep when doing TX? Can we change this to a netdev context that can sleep?
>
> I am guessing that the culprit is the hard xmit functions as seen in the
> backtrace. I do not think there is a way to change this, we have created
> a network device and our xmit function is now being called whatever way
> netdev decides.
>
> [ 43.409028] [<f848faf2>] bt_xmit+0x1f2/0x2f0 [bluetooth_6lowpan]
> [ 43.409028] [<c1710a1c>] dev_hard_start_xmit+0x2ec/0x5e0
> [ 43.409028] [<c18221ab>] ? _raw_spin_lock+0x6b/0x80
> [ 43.409028] [<c1711080>] __dev_queue_xmit+0x370/0x630
> [ 43.409028] [<c1710d10>] ? dev_hard_start_xmit+0x5e0/0x5e0
> [ 43.409028] [<f848fbf0>] ? bt_xmit+0x2f0/0x2f0 [bluetooth_6lowpan]
> [ 43.409028] [<c171134f>] dev_queue_xmit+0xf/0x20
I took the l2cap_chan_no_alloc_skb out now. Lets leave this to the users. However for 6loWPAN add a comment that explain why we need GFP_ATOMIC.
Regards
Marcel
Hi Marcel,
On ma, 2014-06-16 at 13:49 +0200, Marcel Holtmann wrote:
> Hi Jukka,
>
> >>> Create a CoC dynamically instead of one fixed channel for communication
> >>> to peer devices.
> >>>
> >
> >>> +static struct l2cap_ops bt_6lowpan_chan_ops;
> >>
> >> Why this does need a forward declaration. Lets avoid this. And just a reminder that l2cap_ops need to be const now.
> >
> > The chan_ops is needed when setting up the channel in new_connection cb,
> > I did not see a way to avoid forward declaration here.
>
> then why does l2cap_sock.c does not need this forward declaration?
>
> Either your code is doing something funky or our l2cap_ops is not flexible. I think we should not require this forward declaration. Can we fix this?
Hmm, it might be that I am doing something wrong. So the current code
allocates a new channel in new_connection cb, because of that the
channel's ops field need to be set and forward decl. is needed.
l2cap_sock.c does the allocation of chan differently but I was not quite
able to understand the magic behind it.
>
> >>> + /* Remember the skb so that we can send EAGAIN to the caller if
> >>> + * we run out of credits.
> >>> + */
> >>> + chan->data = skb;
> >>
> >> Isn't this one dangerous? Who owns the skb and also more important who frees it? Don't we have to reference the skb somehow?
> >
> > Sure, I will add ref here.
>
> Only if it is needed. I am still curious on how owns this SKB and who is responsible for freeing it. I want to make sure we do not leak memory here.
Well, the skb comes from netdev so it should not disappear during BT
transmit so in theory we would not need to ref the skb. Taking ref just
increments the skb->users field so this is not very big issue.
>
> >>> +static struct sk_buff *chan_alloc_skb_cb(struct l2cap_chan *chan,
> >>> + unsigned long hdr_len,
> >>> + unsigned long len, int nb)
> >>> +{
> >>> + return bt_skb_alloc(hdr_len + len, GFP_ATOMIC);
> >>> +}
> >>
> >> this should no longer be there and just use the default one.
> >>
> >
> > Using GFP_KERNEL in standard l2cap_chan_no_alloc_skb() will lead to this
> > "might sleep" bug:
>
> So why is that? Who is calling l2cap_chan_send and where is it calling it from. The current assumption is that it is called from a context that can sleep. Is the netdev context not able to sleep when doing TX? Can we change this to a netdev context that can sleep?
I am guessing that the culprit is the hard xmit functions as seen in the
backtrace. I do not think there is a way to change this, we have created
a network device and our xmit function is now being called whatever way
netdev decides.
[ 43.409028] [<f848faf2>] bt_xmit+0x1f2/0x2f0 [bluetooth_6lowpan]
[ 43.409028] [<c1710a1c>] dev_hard_start_xmit+0x2ec/0x5e0
[ 43.409028] [<c18221ab>] ? _raw_spin_lock+0x6b/0x80
[ 43.409028] [<c1711080>] __dev_queue_xmit+0x370/0x630
[ 43.409028] [<c1710d10>] ? dev_hard_start_xmit+0x5e0/0x5e0
[ 43.409028] [<f848fbf0>] ? bt_xmit+0x2f0/0x2f0 [bluetooth_6lowpan]
[ 43.409028] [<c171134f>] dev_queue_xmit+0xf/0x20
>
> Do we need to provide the gfp_t to alloc_skb or should we let all implementations provide their own version of it. The more issues we uncover with this, the more I think we need to start re-thinking core parts of L2CAP packet handling.
What about keeping it simple and allowing alloc_skb cb to allocate the
memory anyway it wants to.
>
> What we might need is something that can handle iovev/msghdr and fragment it on the spot into a proper SKB with fragments. And something that can take a SKB from a higher layer (for example netdev) and fragment that into ACL packets.
>
> Regards
>
> Marcel
>
Cheers,
Jukka
Hi Jukka,
>>> Create a CoC dynamically instead of one fixed channel for communication
>>> to peer devices.
>>>
>
>>> +static struct l2cap_ops bt_6lowpan_chan_ops;
>>
>> Why this does need a forward declaration. Lets avoid this. And just a reminder that l2cap_ops need to be const now.
>
> The chan_ops is needed when setting up the channel in new_connection cb,
> I did not see a way to avoid forward declaration here.
then why does l2cap_sock.c does not need this forward declaration?
Either your code is doing something funky or our l2cap_ops is not flexible. I think we should not require this forward declaration. Can we fix this?
>>> + /* Remember the skb so that we can send EAGAIN to the caller if
>>> + * we run out of credits.
>>> + */
>>> + chan->data = skb;
>>
>> Isn't this one dangerous? Who owns the skb and also more important who frees it? Don't we have to reference the skb somehow?
>
> Sure, I will add ref here.
Only if it is needed. I am still curious on how owns this SKB and who is responsible for freeing it. I want to make sure we do not leak memory here.
>>> +static struct sk_buff *chan_alloc_skb_cb(struct l2cap_chan *chan,
>>> + unsigned long hdr_len,
>>> + unsigned long len, int nb)
>>> +{
>>> + return bt_skb_alloc(hdr_len + len, GFP_ATOMIC);
>>> +}
>>
>> this should no longer be there and just use the default one.
>>
>
> Using GFP_KERNEL in standard l2cap_chan_no_alloc_skb() will lead to this
> "might sleep" bug:
So why is that? Who is calling l2cap_chan_send and where is it calling it from. The current assumption is that it is called from a context that can sleep. Is the netdev context not able to sleep when doing TX? Can we change this to a netdev context that can sleep?
Do we need to provide the gfp_t to alloc_skb or should we let all implementations provide their own version of it. The more issues we uncover with this, the more I think we need to start re-thinking core parts of L2CAP packet handling.
What we might need is something that can handle iovev/msghdr and fragment it on the spot into a proper SKB with fragments. And something that can take a SKB from a higher layer (for example netdev) and fragment that into ACL packets.
Regards
Marcel
Hi Marcel,
On ma, 2014-06-16 at 11:45 +0200, Marcel Holtmann wrote:
> Hi Jukka,
>
> > Create a CoC dynamically instead of one fixed channel for communication
> > to peer devices.
> >
> > +static struct l2cap_ops bt_6lowpan_chan_ops;
>
> Why this does need a forward declaration. Lets avoid this. And just a reminder that l2cap_ops need to be const now.
The chan_ops is needed when setting up the channel in new_connection cb,
I did not see a way to avoid forward declaration here.
>
> > + /* Remember the skb so that we can send EAGAIN to the caller if
> > + * we run out of credits.
> > + */
> > + chan->data = skb;
>
> Isn't this one dangerous? Who owns the skb and also more important who frees it? Don't we have to reference the skb somehow?
Sure, I will add ref here.
> > +static struct sk_buff *chan_alloc_skb_cb(struct l2cap_chan *chan,
> > + unsigned long hdr_len,
> > + unsigned long len, int nb)
> > +{
> > + return bt_skb_alloc(hdr_len + len, GFP_ATOMIC);
> > +}
>
> this should no longer be there and just use the default one.
>
Using GFP_KERNEL in standard l2cap_chan_no_alloc_skb() will lead to this
"might sleep" bug:
[ 43.409028] WARNING: CPU: 0 PID: 282
at /home/jukka/src/linux/kernel/locking/lockdep.c:2742
lockdep_trace_alloc+0x100/0x110()
[ 43.409028] DEBUG_LOCKS_WARN_ON(irqs_disabled_flags(flags))
[ 43.409028] Modules linked in: bluetooth_6lowpan 6lowpan_iphc rfcomm
bnep nfc ecb btusb bluetooth rfkill snd_intel8x0 snd_ac97_codec ac97_bus
parport_pc parport
[ 43.409028] CPU: 0 PID: 282 Comm: systemd-journal Not tainted
3.14.0-bt6lowpan #2
[ 43.409028] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS
VirtualBox 12/01/2006
[ 43.409028] 00000000 00000000 f5c09b60 c181a1b8 f5c09ba0 f5c09b90
c1045b2e c1a59112
[ 43.409028] f5c09bbc 0000011a c1a4eb90 00000ab6 c108e2d0 c108e2d0
00000046 00000080
[ 43.409028] 00000023 f5c09ba8 c1045b83 00000009 f5c09ba0 c1a59112
f5c09bbc f5c09bc8
[ 43.409028] Call Trace:
[ 43.409028] [<c181a1b8>] dump_stack+0x4b/0x75
[ 43.409028] [<c1045b2e>] warn_slowpath_common+0x7e/0xa0
[ 43.409028] [<c108e2d0>] ? lockdep_trace_alloc+0x100/0x110
[ 43.409028] [<c108e2d0>] ? lockdep_trace_alloc+0x100/0x110
[ 43.409028] [<c1045b83>] warn_slowpath_fmt+0x33/0x40
[ 43.409028] [<c108e2d0>] lockdep_trace_alloc+0x100/0x110
[ 43.409028] [<c1141068>] kmem_cache_alloc+0x28/0x220
[ 43.409028] [<c1700cd4>] ? __alloc_skb+0x44/0x280
[ 43.409028] [<c1700cd4>] __alloc_skb+0x44/0x280
[ 43.409028] [<f848f04d>] l2cap_chan_no_alloc_skb+0x1d/0x40
[bluetooth_6lowpan]
[ 43.409028] [<f826c999>] l2cap_chan_send+0x5a9/0xfb0 [bluetooth]
[ 43.409028] [<c108b6f6>] ? get_lock_stats+0x16/0x40
[ 43.409028] [<c108b8ad>] ? put_lock_stats.isra.21+0xd/0x30
[ 43.409028] [<f848f895>] send_pkt+0x55/0xc0 [bluetooth_6lwwpan]
[ 43.409028] [<f848faf2>] bt_xmit+0x1f2/0x2f0 [bluetooth_6lowpan]
[ 43.409028] [<c1710a1c>] dev_hard_start_xmit+0x2ec/0x5e0
[ 43.409028] [<c18221ab>] ? _raw_spin_lock+0x6b/0x80
[ 43.409028] [<c1711080>] __dev_queue_xmit+0x370/0x630
[ 43.409028] [<c1710d10>] ? dev_hard_start_xmit+0x5e0/0x5e0
[ 43.409028] [<f848fbf0>] ? bt_xmit+0x2f0/0x2f0 [bluetooth_6lowpan]
[ 43.409028] [<c171134f>] dev_queue_xmit+0xf/0x20
[ 43.409028] [<c1717d42>] neigh_connected_output+0x132/0x190
[ 43.409028] [<c17a9e73>] ? ip6_finish_output2+0x173/0x8d0
[ 43.409028] [<c17a9e73>] ip6_finish_output2+0x173/0x8d0
[ 43.409028] [<c17a9d4a>] ? ip6_finish_output2+0x4a/0x8d0
[ 43.409028] [<c17af445>] ip6_finish_output+0x75/0x1c0
[ 43.409028] [<c17af617>] ip6_output+0x87/0x270
[ 43.409028] [<c17bc7f0>] ? ip6_blackhole_route+0x2a0/0x2a0
[ 43.409028] [<c17cf61d>] mld_sendpack+0x49d/0x640
[ 43.409028] [<c17cff51>] ? mld_ifc_timer_expire+0x191/0x2c0
[ 43.409028] [<c17cff51>] mld_ifc_timer_expire+0x191/0x2c0
[ 43.409028] [<c1050b65>] call_timer_fn+0x85/0x190
[ 43.409028] [<c1050ae0>] ? process_timeout+0x10/0x10
[ 43.409028] [<c108d8b4>] ? trace_hardirqs_on_caller+0xe4/0x210
[ 43.409028] [<c17cfdc0>] ? mld_send_initial_cr.part.31+0xa0/0xa0
[ 43.409028] [<c1051322>] run_timer_softirq+0x182/0x260
[ 43.409028] [<c104a040>] ? __tasklet_hrtimer_trampoline+0x40/0x40
[ 43.409028] [<c139acbf>] ? __this_cpu_preempt_check+0xf/0x20
[ 43.409028] [<c17cfdc0>] ? mld_send_initial_cr.part.31+0xa0/0xa0
[ 43.409028] [<c104a14d>] __do_softirq+0x10d/0x2e0
[ 43.409028] [<c104a040>] ? __tasklet_hrtimer_trampoline+0x40/0x40
[ 43.409028] [<c100410c>] do_softirq_own_stack+0x2c/0x40
[ 43.409028] <IRQ> [<c104a5e6>] irq_exit+0x86/0xb0
[ 43.409028] [<c182aa28>] smp_apic_timer_interrupt+0x38/0x50
[ 43.409028] [<c182372a>] apic_timer_interrupt+0x32/0x38
[ 43.409028] [<c1140a3a>] ? kmem_cache_free+0x8a/0x240
[ 43.409028] [<c108b8ad>] ? put_lock_stats.isra.21+0xd/0x30
[ 43.409028] [<c112f00a>] ? remove_vma+0x4a/0x50
[ 43.409028] [<c112f00a>] remove_vma+0x4a/0x50
[ 43.409028] [<c1130b2d>] do_munmap+0x20d/0x2c0
[ 43.409028] [<c1130c17>] vm_munmap+0x37/0x50
[ 43.409028] [<c1131890>] SyS_munmap+0x20/0x30
[ 43.409028] [<c18232f6>] syscall_call+0x7/0xb
[ 43.409028] [<c1820000>] ? mutex_unlock+0x10/0x10
[ 43.409028] ---[ end trace c7c40884ae627621 ]---
[ 43.409028] BUG: sleeping function called from invalid context
at /home/jukka/src//linux/mm/slub.c:969
[ 43.409028] in_atomic(): 1, irqs_disabled(): 1, pid: 282, name:
systemd-journal
[ 43.409028] INFO: lockdep is turned off.
Cheers,
Jukka
Hi Jukka,
> Create a CoC dynamically instead of one fixed channel for communication
> to peer devices.
>
> Signed-off-by: Jukka Rissanen <[email protected]>
> ---
> include/net/bluetooth/hci.h | 1 -
> include/net/bluetooth/hci_core.h | 1 -
> include/net/bluetooth/l2cap.h | 1 -
> net/bluetooth/6lowpan.c | 756 ++++++++++++++++++++++++++++-----------
> net/bluetooth/6lowpan.h | 47 ---
> net/bluetooth/hci_core.c | 45 ---
> net/bluetooth/hci_event.c | 3 -
> net/bluetooth/l2cap_core.c | 20 +-
> 8 files changed, 561 insertions(+), 313 deletions(-)
> delete mode 100644 net/bluetooth/6lowpan.h
>
> diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> index 16587dc..3f95aba 100644
> --- a/include/net/bluetooth/hci.h
> +++ b/include/net/bluetooth/hci.h
> @@ -139,7 +139,6 @@ enum {
> HCI_PERIODIC_INQ,
> HCI_FAST_CONNECTABLE,
> HCI_BREDR_ENABLED,
> - HCI_6LOWPAN_ENABLED,
> HCI_LE_SCAN_INTERRUPTED,
> };
>
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index b386bf1..c3a2954 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -520,7 +520,6 @@ enum {
> HCI_CONN_AES_CCM,
> HCI_CONN_POWER_SAVE,
> HCI_CONN_REMOTE_OOB,
> - HCI_CONN_6LOWPAN,
> };
>
> static inline bool hci_conn_ssp_enabled(struct hci_conn *conn)
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index e4f4ca6..eb38493 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -137,7 +137,6 @@ struct l2cap_conninfo {
> #define L2CAP_FC_L2CAP 0x02
> #define L2CAP_FC_CONNLESS 0x04
> #define L2CAP_FC_A2MP 0x08
> -#define L2CAP_FC_6LOWPAN 0x3e /* reserved and temporary value */
>
> /* L2CAP Control Field bit masks */
> #define L2CAP_CTRL_SAR 0xC000
> diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
> index 8796ffa..5f2af0f 100644
> --- a/net/bluetooth/6lowpan.c
> +++ b/net/bluetooth/6lowpan.c
> @@ -1,5 +1,5 @@
> /*
> - Copyright (c) 2013 Intel Corp.
> + 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
> @@ -14,6 +14,7 @@
> #include <linux/if_arp.h>
> #include <linux/netdevice.h>
> #include <linux/etherdevice.h>
> +#include <linux/debugfs.h>
>
> #include <net/ipv6.h>
> #include <net/ip6_route.h>
> @@ -25,16 +26,21 @@
> #include <net/bluetooth/hci_core.h>
> #include <net/bluetooth/l2cap.h>
>
> -#include "6lowpan.h"
> -
> #include <net/6lowpan.h> /* for the compression support */
>
> +#define VERSION "1.0"
might want to start with version 0.1 until everything is official and stable.
> +
> +static struct dentry *lowpan_psm_debugfs;
> +static struct dentry *lowpan_control_debugfs;
I would add an empty line here to clearly show what is debugfs and what is channel ops.
> +static struct l2cap_ops bt_6lowpan_chan_ops;
Why this does need a forward declaration. Lets avoid this. And just a reminder that l2cap_ops need to be const now.
> +
> #define IFACE_NAME_TEMPLATE "bt%d"
> #define EUI64_ADDR_LEN 8
>
> struct skb_cb {
> struct in6_addr addr;
> - struct l2cap_conn *conn;
> + struct l2cap_chan *chan;
> + int status;
> };
> #define lowpan_cb(skb) ((struct skb_cb *)((skb)->cb))
>
> @@ -48,9 +54,19 @@ struct skb_cb {
> static LIST_HEAD(bt_6lowpan_devices);
> static DEFINE_RWLOCK(devices_lock);
>
> +/* If psm is set to 0 (default value), then 6lowpan is disabled.
> + * Other values are used to indicate a Protocol Service Multiplexer
> + * value for 6lowpan.
> + */
> +static u16 psm_6lowpan;
> +
> +/* We are listening incoming connections via this channel
> + */
> +static struct l2cap_chan *listen_chan;
> +
> struct lowpan_peer {
> struct list_head list;
> - struct l2cap_conn *conn;
> + struct l2cap_chan *chan;
>
> /* peer addresses in various formats */
> unsigned char eui64_addr[EUI64_ADDR_LEN];
> @@ -101,13 +117,26 @@ static inline struct lowpan_peer *peer_lookup_ba(struct lowpan_dev *dev,
> ba, type);
>
> list_for_each_entry_safe(peer, tmp, &dev->peers, list) {
> - BT_DBG("addr %pMR type %d",
> - &peer->conn->hcon->dst, peer->conn->hcon->dst_type);
> + BT_DBG("dst addr %pMR dst type %d",
> + &peer->chan->dst, peer->chan->dst_type);
>
> - if (bacmp(&peer->conn->hcon->dst, ba))
> + if (bacmp(&peer->chan->dst, ba))
> continue;
>
> - if (type == peer->conn->hcon->dst_type)
> + if (type == peer->chan->dst_type)
> + return peer;
> + }
> +
> + return NULL;
> +}
> +
> +static inline struct lowpan_peer *peer_lookup_chan(struct lowpan_dev *dev,
> + struct l2cap_chan *chan)
> +{
> + struct lowpan_peer *peer, *tmp;
> +
> + list_for_each_entry_safe(peer, tmp, &dev->peers, list) {
> + if (peer->chan == chan)
> return peer;
> }
>
> @@ -120,7 +149,7 @@ static inline struct lowpan_peer *peer_lookup_conn(struct lowpan_dev *dev,
> struct lowpan_peer *peer, *tmp;
>
> list_for_each_entry_safe(peer, tmp, &dev->peers, list) {
> - if (peer->conn == conn)
> + if (peer->chan->conn == conn)
> return peer;
> }
>
> @@ -185,7 +214,7 @@ static int give_skb_to_upper(struct sk_buff *skb, struct net_device *dev)
> }
>
> static int process_data(struct sk_buff *skb, struct net_device *netdev,
> - struct l2cap_conn *conn)
> + struct l2cap_chan *chan)
> {
> const u8 *saddr, *daddr;
> u8 iphc0, iphc1;
> @@ -196,7 +225,7 @@ static int process_data(struct sk_buff *skb, struct net_device *netdev,
> dev = lowpan_dev(netdev);
>
> read_lock_irqsave(&devices_lock, flags);
> - peer = peer_lookup_conn(dev, conn);
> + peer = peer_lookup_chan(dev, chan);
> read_unlock_irqrestore(&devices_lock, flags);
> if (!peer)
> goto drop;
> @@ -225,7 +254,7 @@ drop:
> }
>
> static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
> - struct l2cap_conn *conn)
> + struct l2cap_chan *chan)
> {
> struct sk_buff *local_skb;
> int ret;
> @@ -269,7 +298,7 @@ static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
> if (!local_skb)
> goto drop;
>
> - ret = process_data(local_skb, dev, conn);
> + ret = process_data(local_skb, dev, chan);
> if (ret != NET_RX_SUCCESS)
> goto drop;
>
> @@ -286,147 +315,37 @@ static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
> return NET_RX_SUCCESS;
>
> drop:
> + dev->stats.rx_dropped++;
> kfree_skb(skb);
> return NET_RX_DROP;
> }
>
> /* Packet from BT LE device */
> -int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb)
> +static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
> {
> struct lowpan_dev *dev;
> struct lowpan_peer *peer;
> int err;
>
> - peer = lookup_peer(conn);
> + peer = lookup_peer(chan->conn);
> if (!peer)
> return -ENOENT;
>
> - dev = lookup_dev(conn);
> + dev = lookup_dev(chan->conn);
> if (!dev || !dev->netdev)
> return -ENOENT;
>
> - err = recv_pkt(skb, dev->netdev, conn);
> + err = recv_pkt(skb, dev->netdev, chan);
> +
> BT_DBG("recv pkt %d", err);
>
> return err;
> }
>
> -static inline int skbuff_copy(void *msg, int len, int count, int mtu,
> - struct sk_buff *skb, struct net_device *dev)
> -{
> - struct sk_buff **frag;
> - int sent = 0;
> -
> - memcpy(skb_put(skb, count), msg, count);
> -
> - sent += count;
> - msg += count;
> - len -= count;
> -
> - dev->stats.tx_bytes += count;
> - dev->stats.tx_packets++;
> -
> - raw_dump_table(__func__, "Sending", skb->data, skb->len);
> -
> - /* Continuation fragments (no L2CAP header) */
> - frag = &skb_shinfo(skb)->frag_list;
> - while (len > 0) {
> - struct sk_buff *tmp;
> -
> - count = min_t(unsigned int, mtu, len);
> -
> - tmp = bt_skb_alloc(count, GFP_ATOMIC);
> - if (!tmp)
> - return -ENOMEM;
> -
> - *frag = tmp;
> -
> - memcpy(skb_put(*frag, count), msg, count);
> -
> - raw_dump_table(__func__, "Sending fragment",
> - (*frag)->data, count);
> -
> - (*frag)->priority = skb->priority;
> -
> - sent += count;
> - msg += count;
> - len -= count;
> -
> - skb->len += (*frag)->len;
> - skb->data_len += (*frag)->len;
> -
> - frag = &(*frag)->next;
> -
> - dev->stats.tx_bytes += count;
> - dev->stats.tx_packets++;
> - }
> -
> - return sent;
> -}
> -
> -static struct sk_buff *create_pdu(struct l2cap_conn *conn, void *msg,
> - size_t len, u32 priority,
> - struct net_device *dev)
> -{
> - struct sk_buff *skb;
> - int err, count;
> - struct l2cap_hdr *lh;
> -
> - /* FIXME: This mtu check should be not needed and atm is only used for
> - * testing purposes
> - */
> - if (conn->mtu > (L2CAP_LE_MIN_MTU + L2CAP_HDR_SIZE))
> - conn->mtu = L2CAP_LE_MIN_MTU + L2CAP_HDR_SIZE;
> -
> - count = min_t(unsigned int, (conn->mtu - L2CAP_HDR_SIZE), len);
> -
> - BT_DBG("conn %p len %zu mtu %d count %d", conn, len, conn->mtu, count);
> -
> - skb = bt_skb_alloc(count + L2CAP_HDR_SIZE, GFP_ATOMIC);
> - if (!skb)
> - return ERR_PTR(-ENOMEM);
> -
> - skb->priority = priority;
> -
> - lh = (struct l2cap_hdr *)skb_put(skb, L2CAP_HDR_SIZE);
> - lh->cid = cpu_to_le16(L2CAP_FC_6LOWPAN);
> - lh->len = cpu_to_le16(len);
> -
> - err = skbuff_copy(msg, len, count, conn->mtu, skb, dev);
> - if (unlikely(err < 0)) {
> - kfree_skb(skb);
> - BT_DBG("skbuff copy %d failed", err);
> - return ERR_PTR(err);
> - }
> -
> - return skb;
> -}
> -
> -static int conn_send(struct l2cap_conn *conn,
> - void *msg, size_t len, u32 priority,
> - struct net_device *dev)
> -{
> - struct sk_buff *skb;
> -
> - skb = create_pdu(conn, msg, len, priority, dev);
> - if (IS_ERR(skb))
> - return -EINVAL;
> -
> - BT_DBG("conn %p skb %p len %d priority %u", conn, skb, skb->len,
> - skb->priority);
> -
> - hci_send_acl(conn->hchan, skb, ACL_START);
> -
> - return 0;
> -}
> -
> static u8 get_addr_type_from_eui64(u8 byte)
> {
> - /* Is universal(0) or local(1) bit, */
> - if (byte & 0x02)
> - return ADDR_LE_DEV_RANDOM;
> -
> - return ADDR_LE_DEV_PUBLIC;
> + /* Is universal(0) or local(1) bit */
> + return ((byte & 0x02) ? BDADDR_LE_RANDOM : BDADDR_LE_PUBLIC);
> }
>
> static void copy_to_bdaddr(struct in6_addr *ip6_daddr, bdaddr_t *addr)
> @@ -475,7 +394,7 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
> if (ipv6_addr_is_multicast(&hdr->daddr)) {
> memcpy(&lowpan_cb(skb)->addr, &hdr->daddr,
> sizeof(struct in6_addr));
> - lowpan_cb(skb)->conn = NULL;
> + lowpan_cb(skb)->chan = NULL;
> } else {
> unsigned long flags;
>
> @@ -484,9 +403,8 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
> */
> convert_dest_bdaddr(&hdr->daddr, &addr, &addr_type);
>
> - BT_DBG("dest addr %pMR type %s IP %pI6c", &addr,
> - addr_type == ADDR_LE_DEV_PUBLIC ? "PUBLIC" : "RANDOM",
> - &hdr->daddr);
> + BT_DBG("dest addr %pMR type %d IP %pI6c", &addr,
> + addr_type, &hdr->daddr);
>
> read_lock_irqsave(&devices_lock, flags);
> peer = peer_lookup_ba(dev, &addr, addr_type);
> @@ -501,7 +419,7 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
>
> memcpy(&lowpan_cb(skb)->addr, &hdr->daddr,
> sizeof(struct in6_addr));
> - lowpan_cb(skb)->conn = peer->conn;
> + lowpan_cb(skb)->chan = peer->chan;
> }
>
> saddr = dev->netdev->dev_addr;
> @@ -510,14 +428,42 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
> }
>
> /* Packet to BT LE device */
> -static int send_pkt(struct l2cap_conn *conn, const void *saddr,
> - const void *daddr, struct sk_buff *skb,
> +static int send_pkt(struct l2cap_chan *chan, struct sk_buff *skb,
> struct net_device *netdev)
> {
> - raw_dump_table(__func__, "raw skb data dump before fragmentation",
> - skb->data, skb->len);
> + struct msghdr msg;
> + struct iovec iov;
> + int err;
> +
> + /* Remember the skb so that we can send EAGAIN to the caller if
> + * we run out of credits.
> + */
> + chan->data = skb;
Isn't this one dangerous? Who owns the skb and also more important who frees it? Don't we have to reference the skb somehow?
> +
> + memset(&msg, 0, sizeof(msg));
> + msg.msg_iov = &iov;
> + msg.msg_iovlen = 1;
> + iov.iov_base = skb->data;
> + iov.iov_len = skb->len;
> +
> + err = l2cap_chan_send(chan, &msg, skb->len);
> + if (err > 0) {
> + netdev->stats.tx_bytes += err;
> + netdev->stats.tx_packets++;
> + return 0;
> + }
> +
> + if (!err)
> + err = lowpan_cb(skb)->status;
> +
> + if (err < 0) {
> + if (err == -EAGAIN)
> + netdev->stats.tx_dropped++;
> + else
> + netdev->stats.tx_errors++;
> + }
>
> - return conn_send(conn, skb->data, skb->len, 0, netdev);
> + return err;
> }
>
> static void send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
> @@ -540,8 +486,7 @@ static void send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
> list_for_each_entry_safe(pentry, ptmp, &dev->peers, list) {
> local_skb = skb_clone(skb, GFP_ATOMIC);
>
> - send_pkt(pentry->conn, netdev->dev_addr,
> - pentry->eui64_addr, local_skb, netdev);
> + send_pkt(pentry->chan, local_skb, netdev);
>
> kfree_skb(local_skb);
> }
> @@ -553,7 +498,6 @@ static void send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
> static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
> {
> int err = 0;
> - unsigned char *eui64_addr;
> struct lowpan_dev *dev;
> struct lowpan_peer *peer;
> bdaddr_t addr;
> @@ -568,21 +512,20 @@ static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
> unsigned long flags;
>
> convert_dest_bdaddr(&lowpan_cb(skb)->addr, &addr, &addr_type);
> - eui64_addr = lowpan_cb(skb)->addr.s6_addr + 8;
> dev = lowpan_dev(netdev);
>
> read_lock_irqsave(&devices_lock, flags);
> peer = peer_lookup_ba(dev, &addr, addr_type);
> read_unlock_irqrestore(&devices_lock, flags);
>
> - BT_DBG("xmit %s to %pMR type %s IP %pI6c peer %p",
> - netdev->name, &addr,
> - addr_type == ADDR_LE_DEV_PUBLIC ? "PUBLIC" : "RANDOM",
> + BT_DBG("xmit %s to %pMR type %d IP %pI6c peer %p",
> + netdev->name, &addr, addr_type,
> &lowpan_cb(skb)->addr, peer);
>
> - if (peer && peer->conn)
> - err = send_pkt(peer->conn, netdev->dev_addr,
> - eui64_addr, skb, netdev);
> + if (peer && peer->chan)
> + err = send_pkt(peer->chan, skb, netdev);
> + else
> + err = -ENOENT;
> }
> dev_kfree_skb(skb);
>
> @@ -634,7 +577,7 @@ static void set_addr(u8 *eui, u8 *addr, u8 addr_type)
> eui[7] = addr[0];
>
> /* Universal/local bit set, BT 6lowpan draft ch. 3.2.1 */
> - if (addr_type == ADDR_LE_DEV_PUBLIC)
> + if (addr_type == BDADDR_LE_PUBLIC)
> eui[0] &= ~0x02;
> else
> eui[0] |= 0x02;
> @@ -673,26 +616,65 @@ static bool is_bt_6lowpan(struct hci_conn *hcon)
> if (hcon->type != LE_LINK)
> return false;
>
> - return test_bit(HCI_CONN_6LOWPAN, &hcon->flags);
> + if (!psm_6lowpan)
> + return false;
> +
> + return true;
> +}
> +
> +static struct l2cap_chan *chan_create(void)
> +{
> + struct l2cap_chan *chan;
> +
> + chan = l2cap_chan_create();
> + if (!chan)
> + return NULL;
> +
> + l2cap_chan_set_defaults(chan);
> +
> + chan->chan_type = L2CAP_CHAN_CONN_ORIENTED;
> + chan->mode = L2CAP_MODE_LE_FLOWCTL;
> + chan->omtu = 65535;
> + chan->imtu = chan->omtu;
> + chan->ops = &bt_6lowpan_chan_ops;
> +
> + return chan;
> +}
> +
> +static struct l2cap_chan *chan_open(struct l2cap_chan *pchan)
> +{
> + struct l2cap_chan *chan;
> +
> + chan = chan_create();
> + if (!chan)
> + return NULL;
> +
> + chan->remote_mps = chan->omtu;
> + chan->mps = chan->omtu;
> +
> + chan->state = BT_CONNECTED;
> +
> + return chan;
> }
>
> -static int add_peer_conn(struct l2cap_conn *conn, struct lowpan_dev *dev)
> +static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
> + struct lowpan_dev *dev)
> {
> struct lowpan_peer *peer;
> unsigned long flags;
>
> peer = kzalloc(sizeof(*peer), GFP_ATOMIC);
> if (!peer)
> - return -ENOMEM;
> + return NULL;
>
> - peer->conn = conn;
> + peer->chan = chan;
> memset(&peer->peer_addr, 0, sizeof(struct in6_addr));
>
> /* RFC 2464 ch. 5 */
> peer->peer_addr.s6_addr[0] = 0xFE;
> peer->peer_addr.s6_addr[1] = 0x80;
> - set_addr((u8 *)&peer->peer_addr.s6_addr + 8, conn->hcon->dst.b,
> - conn->hcon->dst_type);
> + set_addr((u8 *)&peer->peer_addr.s6_addr + 8, chan->dst.b,
> + chan->dst_type);
>
> memcpy(&peer->eui64_addr, (u8 *)&peer->peer_addr.s6_addr + 8,
> EUI64_ADDR_LEN);
> @@ -706,40 +688,24 @@ static int add_peer_conn(struct l2cap_conn *conn, struct lowpan_dev *dev)
> INIT_DELAYED_WORK(&dev->notify_peers, do_notify_peers);
> schedule_delayed_work(&dev->notify_peers, msecs_to_jiffies(100));
>
> - return 0;
> + return peer->chan;
> }
>
> -/* This gets called when BT LE 6LoWPAN device is connected. We then
> - * create network device that acts as a proxy between BT LE device
> - * and kernel network stack.
> - */
> -int bt_6lowpan_add_conn(struct l2cap_conn *conn)
> +static int setup_netdev(struct l2cap_chan *chan, struct lowpan_dev **dev)
> {
> - struct lowpan_peer *peer = NULL;
> - struct lowpan_dev *dev;
> struct net_device *netdev;
> int err = 0;
> unsigned long flags;
>
> - if (!is_bt_6lowpan(conn->hcon))
> - return 0;
> -
> - peer = lookup_peer(conn);
> - if (peer)
> - return -EEXIST;
> -
> - dev = lookup_dev(conn);
> - if (dev)
> - return add_peer_conn(conn, dev);
> -
> - netdev = alloc_netdev(sizeof(*dev), IFACE_NAME_TEMPLATE, netdev_setup);
> + netdev = alloc_netdev(sizeof(struct lowpan_dev), IFACE_NAME_TEMPLATE,
> + netdev_setup);
> if (!netdev)
> return -ENOMEM;
>
> - set_dev_addr(netdev, &conn->hcon->src, conn->hcon->src_type);
> + set_dev_addr(netdev, &chan->src, chan->src_type);
>
> netdev->netdev_ops = &netdev_ops;
> - SET_NETDEV_DEV(netdev, &conn->hcon->dev);
> + SET_NETDEV_DEV(netdev, &chan->conn->hcon->dev);
> SET_NETDEV_DEVTYPE(netdev, &bt_type);
>
> err = register_netdev(netdev);
> @@ -749,28 +715,53 @@ int bt_6lowpan_add_conn(struct l2cap_conn *conn)
> goto out;
> }
>
> - BT_DBG("ifindex %d peer bdaddr %pMR my addr %pMR",
> - netdev->ifindex, &conn->hcon->dst, &conn->hcon->src);
> + BT_DBG("ifindex %d peer bdaddr %pMR type %d my addr %pMR type %d",
> + netdev->ifindex, &chan->dst, chan->dst_type,
> + &chan->src, chan->src_type);
> set_bit(__LINK_STATE_PRESENT, &netdev->state);
>
> - dev = netdev_priv(netdev);
> - dev->netdev = netdev;
> - dev->hdev = conn->hcon->hdev;
> - INIT_LIST_HEAD(&dev->peers);
> + *dev = netdev_priv(netdev);
> + (*dev)->netdev = netdev;
> + (*dev)->hdev = chan->conn->hcon->hdev;
> + INIT_LIST_HEAD(&(*dev)->peers);
>
> write_lock_irqsave(&devices_lock, flags);
> - INIT_LIST_HEAD(&dev->list);
> - list_add(&dev->list, &bt_6lowpan_devices);
> + INIT_LIST_HEAD(&(*dev)->list);
> + list_add(&(*dev)->list, &bt_6lowpan_devices);
> write_unlock_irqrestore(&devices_lock, flags);
>
> - ifup(netdev);
> -
> - return add_peer_conn(conn, dev);
> + return 0;
>
> out:
> return err;
> }
>
> +static inline void chan_ready_cb(struct l2cap_chan *chan)
> +{
> + struct lowpan_dev *dev;
> +
> + dev = lookup_dev(chan->conn);
> +
> + BT_DBG("chan %p conn %p dev %p", chan, chan->conn, dev);
> +
> + if (!dev) {
> + if (setup_netdev(chan, &dev) < 0) {
> + l2cap_chan_del(chan, -ENOENT);
> + return;
> + }
> + }
> +
> + add_peer_chan(chan, dev);
> + ifup(dev->netdev);
> +}
> +
> +static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *chan)
> +{
> + BT_DBG("chan %p", chan);
> +
> + return chan_open(chan);
> +}
> +
> static void delete_netdev(struct work_struct *work)
> {
> struct lowpan_dev *entry = container_of(work, struct lowpan_dev,
> @@ -781,26 +772,38 @@ static void delete_netdev(struct work_struct *work)
> /* The entry pointer is deleted in device_event() */
> }
>
> -int bt_6lowpan_del_conn(struct l2cap_conn *conn)
> +static void chan_close_cb(struct l2cap_chan *chan)
> {
> struct lowpan_dev *entry, *tmp;
> struct lowpan_dev *dev = NULL;
> struct lowpan_peer *peer;
> int err = -ENOENT;
> unsigned long flags;
> - bool last = false;
> + bool last = false, removed = true;
>
> - if (!conn || !is_bt_6lowpan(conn->hcon))
> - return 0;
> + BT_DBG("chan %p conn %p", chan, chan->conn);
> +
> + if (chan->conn && chan->conn->hcon) {
> + if (!is_bt_6lowpan(chan->conn->hcon))
> + return;
> +
> + /* If conn is set, then the netdev is also there and we should
> + * not remove it.
> + */
> + removed = false;
> + }
>
> write_lock_irqsave(&devices_lock, flags);
>
> list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices, list) {
> dev = lowpan_dev(entry->netdev);
> - peer = peer_lookup_conn(dev, conn);
> + peer = peer_lookup_chan(dev, chan);
> if (peer) {
> last = peer_del(dev, peer);
> err = 0;
> + BT_DBG("dev %p removing %speer %p", dev,
> + last ? "last " : "1 ", peer);
> + kfree(peer);
> break;
> }
> }
> @@ -810,18 +813,352 @@ int bt_6lowpan_del_conn(struct l2cap_conn *conn)
>
> cancel_delayed_work_sync(&dev->notify_peers);
>
> - /* bt_6lowpan_del_conn() is called with hci dev lock held which
> - * means that we must delete the netdevice in worker thread.
> - */
> - INIT_WORK(&entry->delete_netdev, delete_netdev);
> - schedule_work(&entry->delete_netdev);
> + if (!removed) {
> + INIT_WORK(&entry->delete_netdev, delete_netdev);
> + schedule_work(&entry->delete_netdev);
> + }
> } else {
> write_unlock_irqrestore(&devices_lock, flags);
> }
>
> + return;
> +}
> +
> +static void chan_state_change_cb(struct l2cap_chan *chan, int state, int err)
> +{
> + BT_DBG("chan %p conn %p", chan, chan->conn);
> +}
> +
> +static struct sk_buff *chan_alloc_skb_cb(struct l2cap_chan *chan,
> + unsigned long hdr_len,
> + unsigned long len, int nb)
> +{
> + return bt_skb_alloc(hdr_len + len, GFP_ATOMIC);
> +}
this should no longer be there and just use the default one.
> +
> +static void chan_suspend_cb(struct l2cap_chan *chan)
> +{
> + struct sk_buff *skb = chan->data;
> +
> + BT_DBG("chan %p conn %p skb %p", chan, chan->conn, skb);
> +
> + lowpan_cb(skb)->status = -EAGAIN;
> +}
> +
> +static void chan_resume_cb(struct l2cap_chan *chan)
> +{
> + struct sk_buff *skb = chan->data;
> +
> + BT_DBG("chan %p conn %p skb %p", chan, chan->conn, skb);
> +
> + lowpan_cb(skb)->status = 0;
> +}
> +
> +static long chan_get_sndtimeo_cb(struct l2cap_chan *chan)
> +{
> + return msecs_to_jiffies(1000);
> +}
> +
> +static void chan_teardown_cb(struct l2cap_chan *chan, int err)
> +{
> + BT_DBG("chan %p conn %p err %d", chan, chan->conn, err);
> +}
> +
> +static struct l2cap_ops bt_6lowpan_chan_ops = {
> + .name = "L2CAP 6LoWPAN channel",
> + .new_connection = chan_new_conn_cb,
> + .recv = chan_recv_cb,
> + .teardown = chan_teardown_cb,
> + .close = chan_close_cb,
> + .state_change = chan_state_change_cb,
> + .ready = chan_ready_cb,
> + .resume = chan_resume_cb,
> + .suspend = chan_suspend_cb,
> + .get_sndtimeo = chan_get_sndtimeo_cb,
> + .alloc_skb = chan_alloc_skb_cb,
l2cap_chan_no_alloc_skb,
> + .memcpy_fromiovec = l2cap_chan_no_memcpy_fromiovec,
> +
> + .defer = l2cap_chan_no_defer,
> + .set_shutdown = l2cap_chan_no_set_shutdown,
> +};
> +
> +static inline __u8 bdaddr_type(__u8 type)
> +{
> + if (type == ADDR_LE_DEV_PUBLIC)
> + return BDADDR_LE_PUBLIC;
> + else
> + return BDADDR_LE_RANDOM;
> +}
> +
> +static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
> +{
> + struct l2cap_chan *pchan;
> + int err;
> +
> + pchan = chan_create();
> + if (!pchan)
> + return -EINVAL;
> +
> + err = l2cap_chan_connect(pchan, cpu_to_le16(psm_6lowpan), 0,
> + addr, dst_type);
> +
> + BT_DBG("chan %p err %d", pchan, err);
> +
> return err;
> }
>
> +static void chan_close(struct l2cap_chan *chan, int reason)
> +{
> + l2cap_chan_lock(chan);
> + l2cap_chan_close(chan, reason);
> + l2cap_chan_unlock(chan);
> + l2cap_chan_put(chan);
> +}
> +
> +static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
> +{
> + struct lowpan_peer *peer;
> +
> + BT_DBG("conn %p dst type %d", conn, dst_type);
> +
> + peer = lookup_peer(conn);
> + if (!peer)
> + return -ENOENT;
> +
> + chan_close(peer->chan, ENOENT);
> +
> + return 0;
> +}
> +
> +static struct l2cap_chan *bt_6lowpan_listen(void)
> +{
> + bdaddr_t *addr = BDADDR_ANY;
> + struct l2cap_chan *pchan;
> + int err;
> +
> + if (psm_6lowpan == 0)
> + return NULL;
> +
> + pchan = chan_create();
> + if (!pchan)
> + return NULL;
> +
> + pchan->state = BT_LISTEN;
> + pchan->src_type = BDADDR_LE_PUBLIC;
> +
> + BT_DBG("psm 0x%04x chan %p src type %d", psm_6lowpan, pchan,
> + pchan->src_type);
> +
> + err = l2cap_add_psm(pchan, addr, cpu_to_le16(psm_6lowpan));
> + if (err) {
> + BT_ERR("psm cannot be added err %d", err);
> + return NULL;
> + }
> +
> + return pchan;
> +}
> +
> +static int get_l2cap_conn(char *buf, bdaddr_t *addr, u8 *addr_type,
> + struct l2cap_conn **conn)
> +{
> + struct hci_conn *hcon;
> + struct hci_dev *hdev;
> + bdaddr_t *src = BDADDR_ANY;
> + 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;
> +
> + hdev = hci_get_route(addr, src);
> + if (!hdev)
> + return -ENOENT;
> +
> + hci_dev_lock(hdev);
> + hcon = hci_conn_hash_lookup_ba(hdev, LE_LINK, addr);
> + hci_dev_unlock(hdev);
> +
> + if (!hcon)
> + return -ENOENT;
> +
> + *conn = (struct l2cap_conn *)hcon->l2cap_data;
> +
> + BT_DBG("conn %p dst %pMR type %d", *conn, &hcon->dst, hcon->dst_type);
> +
> + return 0;
> +}
> +
> +static void disconnect_all_peers(void)
> +{
> + struct lowpan_dev *entry, *tmp_dev;
> + struct lowpan_peer *peer, *tmp_peer, *new_peer;
> + struct list_head peers;
> + unsigned long flags;
> +
> + INIT_LIST_HEAD(&peers);
> +
> + /* We make a separate list of peers as the close_cb() will
> + * modify the device peers list so it is better not to mess
> + * with the same list at the same time.
> + */
> +
> + read_lock_irqsave(&devices_lock, flags);
> +
> + list_for_each_entry_safe(entry, tmp_dev, &bt_6lowpan_devices, list) {
> + list_for_each_entry_safe(peer, tmp_peer, &entry->peers, list) {
> + new_peer = kmalloc(sizeof(*new_peer), GFP_ATOMIC);
> + if (!new_peer)
> + break;
> +
> + new_peer->chan = peer->chan;
> + INIT_LIST_HEAD(&new_peer->list);
> +
> + list_add(&new_peer->list, &peers);
> + }
> + }
> +
> + read_unlock_irqrestore(&devices_lock, flags);
> +
> + list_for_each_entry_safe(peer, tmp_peer, &peers, list) {
> + chan_close(peer->chan, ENOENT);
> + kfree(peer);
> + }
> +}
> +
> +static int lowpan_psm_set(void *data, u64 val)
> +{
> + u16 psm;
> +
> + psm = val;
> + if (psm == 0 || psm_6lowpan != psm)
> + /* Disconnect existing connections if 6lowpan is
> + * disabled (psm = 0), or if psm changes.
> + */
> + disconnect_all_peers();
> +
> + psm_6lowpan = psm;
> +
> + if (listen_chan)
> + chan_close(listen_chan, 0);
> +
> + listen_chan = bt_6lowpan_listen();
> +
> + return 0;
> +}
> +
> +static int lowpan_psm_get(void *data, u64 *val)
> +{
> + *val = psm_6lowpan;
> + return 0;
> +}
> +
> +DEFINE_SIMPLE_ATTRIBUTE(lowpan_psm_fops, lowpan_psm_get,
> + lowpan_psm_set, "%llu\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);
> + int ret;
> + bdaddr_t addr;
> + u8 addr_type;
> + struct l2cap_conn *conn = NULL;
> +
> + if (copy_from_user(buf, user_buffer, buf_size))
> + return -EFAULT;
> +
> + buf[buf_size] = '\0';
> +
> + if (memcmp(buf, "connect ", 8) == 0) {
> +
> + ret = get_l2cap_conn(&buf[8], &addr, &addr_type, &conn);
> + if (ret == -EINVAL)
> + return ret;
> +
> + if (listen_chan) {
> + chan_close(listen_chan, 0);
> + listen_chan = NULL;
> + }
> +
> + if (conn) {
> + struct lowpan_peer *peer;
> +
> + if (!is_bt_6lowpan(conn->hcon))
> + return -EINVAL;
> +
> + peer = lookup_peer(conn);
> + if (peer) {
> + BT_DBG("6LoWPAN connection already exists");
> + return -EALREADY;
> + }
> +
> + BT_DBG("conn %p dst %pMR type %d user %d", conn,
> + &conn->hcon->dst, conn->hcon->dst_type,
> + addr_type);
> + }
> +
> + ret = bt_6lowpan_connect(&addr, addr_type);
> + if (ret < 0)
> + return ret;
> +
> + return count;
> + }
> +
> + if (memcmp(buf, "disconnect ", 11) == 0) {
> +
> + ret = get_l2cap_conn(&buf[11], &addr, &addr_type, &conn);
> + if (ret < 0)
> + return ret;
> +
> + ret = bt_6lowpan_disconnect(conn, addr_type);
> + if (ret < 0)
> + return ret;
> +
> + return count;
> + }
> +
> + return count;
> +}
> +
> +static int lowpan_control_show(struct seq_file *f, void *ptr)
> +{
> + struct lowpan_dev *entry, *tmp_dev;
> + struct lowpan_peer *peer, *tmp_peer;
> + unsigned long flags;
> +
> + read_lock_irqsave(&devices_lock, flags);
> +
> + list_for_each_entry_safe(entry, tmp_dev, &bt_6lowpan_devices, list) {
> + list_for_each_entry_safe(peer, tmp_peer, &entry->peers, list)
> + seq_printf(f, "%pMR (type %u)\n",
> + &peer->chan->dst, peer->chan->dst_type);
> + }
> +
> + read_unlock_irqrestore(&devices_lock, flags);
> +
> + 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 device_event(struct notifier_block *unused,
> unsigned long event, void *ptr)
> {
> @@ -856,10 +1193,23 @@ static struct notifier_block bt_6lowpan_dev_notifier = {
>
> int bt_6lowpan_init(void)
> {
> + lowpan_psm_debugfs = debugfs_create_file("6lowpan_psm", 0644,
> + bt_debugfs, NULL,
> + &lowpan_psm_fops);
> + lowpan_control_debugfs = debugfs_create_file("6lowpan_control", 0644,
> + bt_debugfs, NULL,
> + &lowpan_control_fops);
> +
> return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
> }
>
> -void bt_6lowpan_cleanup(void)
> +void bt_6lowpan_exit(void)
> {
> + debugfs_remove(lowpan_psm_debugfs);
> + debugfs_remove(lowpan_control_debugfs);
> +
> + if (listen_chan)
> + chan_close(listen_chan, 0);
> +
> unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
> }
> diff --git a/net/bluetooth/6lowpan.h b/net/bluetooth/6lowpan.h
> deleted file mode 100644
> index 5d281f1..0000000
> --- a/net/bluetooth/6lowpan.h
> +++ /dev/null
> @@ -1,47 +0,0 @@
> -/*
> - Copyright (c) 2013 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.
> -*/
> -
> -#ifndef __6LOWPAN_H
> -#define __6LOWPAN_H
> -
> -#include <linux/errno.h>
> -#include <linux/skbuff.h>
> -#include <net/bluetooth/l2cap.h>
> -
> -#if IS_ENABLED(CONFIG_BT_6LOWPAN)
> -int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb);
> -int bt_6lowpan_add_conn(struct l2cap_conn *conn);
> -int bt_6lowpan_del_conn(struct l2cap_conn *conn);
> -int bt_6lowpan_init(void);
> -void bt_6lowpan_cleanup(void);
> -#else
> -static int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb)
> -{
> - return -EOPNOTSUPP;
> -}
> -static int bt_6lowpan_add_conn(struct l2cap_conn *conn)
> -{
> - return -EOPNOTSUPP;
> -}
> -int bt_6lowpan_del_conn(struct l2cap_conn *conn)
> -{
> - return -EOPNOTSUPP;
> -}
> -static int bt_6lowpan_init(void)
> -{
> - return -EOPNOTSUPP;
> -}
> -static void bt_6lowpan_cleanup(void) { }
> -#endif
> -
> -#endif /* __6LOWPAN_H */
> diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
> index 0a43cce..fb532ca 100644
> --- a/net/bluetooth/hci_core.c
> +++ b/net/bluetooth/hci_core.c
> @@ -928,49 +928,6 @@ static int adv_channel_map_get(void *data, u64 *val)
> DEFINE_SIMPLE_ATTRIBUTE(adv_channel_map_fops, adv_channel_map_get,
> adv_channel_map_set, "%llu\n");
>
> -static ssize_t lowpan_read(struct file *file, char __user *user_buf,
> - size_t count, loff_t *ppos)
> -{
> - struct hci_dev *hdev = file->private_data;
> - char buf[3];
> -
> - buf[0] = test_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags) ? 'Y' : 'N';
> - buf[1] = '\n';
> - buf[2] = '\0';
> - return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
> -}
> -
> -static ssize_t lowpan_write(struct file *fp, const char __user *user_buffer,
> - size_t count, loff_t *position)
> -{
> - struct hci_dev *hdev = fp->private_data;
> - bool enable;
> - char buf[32];
> - size_t buf_size = min(count, (sizeof(buf)-1));
> -
> - if (copy_from_user(buf, user_buffer, buf_size))
> - return -EFAULT;
> -
> - buf[buf_size] = '\0';
> -
> - if (strtobool(buf, &enable) < 0)
> - return -EINVAL;
> -
> - if (enable == test_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags))
> - return -EALREADY;
> -
> - change_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags);
> -
> - return count;
> -}
> -
> -static const struct file_operations lowpan_debugfs_fops = {
> - .open = simple_open,
> - .read = lowpan_read,
> - .write = lowpan_write,
> - .llseek = default_llseek,
> -};
> -
> static int le_auto_conn_show(struct seq_file *sf, void *ptr)
> {
> struct hci_dev *hdev = sf->private;
> @@ -1881,8 +1838,6 @@ static int __hci_init(struct hci_dev *hdev)
> hdev, &conn_max_interval_fops);
> debugfs_create_file("adv_channel_map", 0644, hdev->debugfs,
> hdev, &adv_channel_map_fops);
> - debugfs_create_file("6lowpan", 0644, hdev->debugfs, hdev,
> - &lowpan_debugfs_fops);
> debugfs_create_file("le_auto_conn", 0644, hdev->debugfs, hdev,
> &le_auto_conn_fops);
> debugfs_create_u16("discov_interleaved_timeout", 0644,
> diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
> index ec1b174..360c1c6 100644
> --- a/net/bluetooth/hci_event.c
> +++ b/net/bluetooth/hci_event.c
> @@ -4055,9 +4055,6 @@ static void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
> conn->handle = __le16_to_cpu(ev->handle);
> conn->state = BT_CONNECTED;
>
> - if (test_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags))
> - set_bit(HCI_CONN_6LOWPAN, &conn->flags);
> -
> hci_conn_add_sysfs(conn);
>
> hci_proto_connect_cfm(conn, ev->status);
> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> index eb0d43d..1d466d2 100644
> --- a/net/bluetooth/l2cap_core.c
> +++ b/net/bluetooth/l2cap_core.c
> @@ -40,7 +40,6 @@
> #include "smp.h"
> #include "a2mp.h"
> #include "amp.h"
> -#include "6lowpan.h"
>
> #define LE_FLOWCTL_MAX_CREDITS 65535
>
> @@ -205,6 +204,7 @@ done:
> write_unlock(&chan_list_lock);
> return err;
> }
> +EXPORT_SYMBOL_GPL(l2cap_add_psm);
>
> int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid)
> {
> @@ -437,6 +437,7 @@ struct l2cap_chan *l2cap_chan_create(void)
>
> return chan;
> }
> +EXPORT_SYMBOL_GPL(l2cap_chan_create);
>
> static void l2cap_chan_destroy(struct kref *kref)
> {
> @@ -464,6 +465,7 @@ void l2cap_chan_put(struct l2cap_chan *c)
>
> kref_put(&c->kref, l2cap_chan_destroy);
> }
> +EXPORT_SYMBOL_GPL(l2cap_chan_put);
>
> void l2cap_chan_set_defaults(struct l2cap_chan *chan)
> {
> @@ -482,6 +484,7 @@ void l2cap_chan_set_defaults(struct l2cap_chan *chan)
>
> set_bit(FLAG_FORCE_ACTIVE, &chan->flags);
> }
> +EXPORT_SYMBOL_GPL(l2cap_chan_set_defaults);
>
> static void l2cap_le_flowctl_init(struct l2cap_chan *chan)
> {
> @@ -614,6 +617,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
>
> return;
> }
> +EXPORT_SYMBOL_GPL(l2cap_chan_del);
>
> void l2cap_conn_update_id_addr(struct hci_conn *hcon)
> {
> @@ -717,6 +721,7 @@ void l2cap_chan_close(struct l2cap_chan *chan, int reason)
> break;
> }
> }
> +EXPORT_SYMBOL(l2cap_chan_close);
>
> static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
> {
> @@ -1460,8 +1465,6 @@ static void l2cap_le_conn_ready(struct l2cap_conn *conn)
>
> BT_DBG("");
>
> - bt_6lowpan_add_conn(conn);
> -
> /* Check if we have socket listening on cid */
> pchan = l2cap_global_chan_by_scid(BT_LISTEN, L2CAP_CID_ATT,
> &hcon->src, &hcon->dst);
> @@ -2549,6 +2552,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
>
> return err;
> }
> +EXPORT_SYMBOL_GPL(l2cap_chan_send);
>
> static void l2cap_send_srej(struct l2cap_chan *chan, u16 txseq)
> {
> @@ -6927,10 +6931,6 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
> l2cap_conn_del(conn->hcon, EACCES);
> break;
>
> - case L2CAP_FC_6LOWPAN:
> - bt_6lowpan_recv(conn, skb);
> - break;
> -
> default:
> l2cap_data_channel(conn, cid, skb);
> break;
> @@ -7177,6 +7177,7 @@ done:
> hci_dev_put(hdev);
> return err;
> }
> +EXPORT_SYMBOL_GPL(l2cap_chan_connect);
>
> /* ---- L2CAP interface with lower layer (HCI) ---- */
>
> @@ -7239,8 +7240,6 @@ void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
> {
> BT_DBG("hcon %p reason %d", hcon, reason);
>
> - bt_6lowpan_del_conn(hcon->l2cap_data);
> -
> l2cap_conn_del(hcon, bt_to_errno(reason));
> }
>
> @@ -7523,14 +7522,11 @@ int __init l2cap_init(void)
> debugfs_create_u16("l2cap_le_default_mps", 0644, bt_debugfs,
> &le_default_mps);
>
> - bt_6lowpan_init();
> -
> return 0;
> }
>
> void l2cap_exit(void)
> {
> - bt_6lowpan_cleanup();
> debugfs_remove(l2cap_debugfs);
> l2cap_cleanup_sockets();
> }
Regards
Marcel
On pe, 2014-06-13 at 15:53 +0300, Jukka Rissanen wrote:
> Create a CoC dynamically instead of one fixed channel for communication
> to peer devices.
>
> +static void chan_close(struct l2cap_chan *chan, int reason)
> +{
> + l2cap_chan_lock(chan);
> + l2cap_chan_close(chan, reason);
> + l2cap_chan_unlock(chan);
> + l2cap_chan_put(chan);
> +}
> +
This channel locking is wrong as I get locking errors and eventually the
kernel will freeze. I will send a new version fixing this.
Cheers,
Jukka
Count how many 6LoWPAN connections there exists so that we
do not unload the module if there are still connections alive.
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/bluetooth/6lowpan.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index b477e15..dd72ad2 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -101,6 +101,8 @@ static inline bool peer_del(struct lowpan_dev *dev, struct lowpan_peer *peer)
{
list_del(&peer->list);
+ module_put(THIS_MODULE);
+
if (atomic_dec_and_test(&dev->peer_count)) {
BT_DBG("last peer");
return true;
@@ -752,6 +754,9 @@ static inline void chan_ready_cb(struct l2cap_chan *chan)
}
}
+ if (!try_module_get(THIS_MODULE))
+ return;
+
add_peer_chan(chan, dev);
ifup(dev->netdev);
}
--
1.8.3.1
When the module is unloaded, unregister the network device
so that the system does not try to access non-existing device.
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/bluetooth/6lowpan.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index dd72ad2..5c9bc63 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -606,6 +606,17 @@ static void ifup(struct net_device *netdev)
rtnl_unlock();
}
+static void ifdown(struct net_device *netdev)
+{
+ int err;
+
+ rtnl_lock();
+ err = dev_close(netdev);
+ if (err < 0)
+ BT_INFO("iface %s cannot be closed (%d)", netdev->name, err);
+ rtnl_unlock();
+}
+
static void do_notify_peers(struct work_struct *work)
{
struct lowpan_dev *dev = container_of(work, struct lowpan_dev,
@@ -819,6 +830,8 @@ static void chan_close_cb(struct l2cap_chan *chan)
cancel_delayed_work_sync(&dev->notify_peers);
+ ifdown(dev->netdev);
+
if (!removed) {
INIT_WORK(&entry->delete_netdev, delete_netdev);
schedule_work(&entry->delete_netdev);
@@ -1165,6 +1178,43 @@ static const struct file_operations lowpan_control_fops = {
.release = single_release,
};
+static void disconnect_devices(void)
+{
+ struct lowpan_dev *entry, *tmp, *new_dev;
+ struct list_head devices;
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&devices);
+
+ /* We make a separate list of devices because the unregister_netdev()
+ * will call device_event() which will also want to modify the same
+ * devices list.
+ */
+
+ read_lock_irqsave(&devices_lock, flags);
+
+ list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices, list) {
+ new_dev = kmalloc(sizeof(*new_dev), GFP_ATOMIC);
+ if (!new_dev)
+ break;
+
+ new_dev->netdev = entry->netdev;
+ INIT_LIST_HEAD(&new_dev->list);
+
+ list_add(&new_dev->list, &devices);
+ }
+
+ read_unlock_irqrestore(&devices_lock, flags);
+
+ list_for_each_entry_safe(entry, tmp, &devices, list) {
+ ifdown(entry->netdev);
+ BT_DBG("Unregistering netdev %s %p",
+ entry->netdev->name, entry->netdev);
+ unregister_netdev(entry->netdev);
+ kfree(entry);
+ }
+}
+
static int device_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
@@ -1181,6 +1231,8 @@ static int device_event(struct notifier_block *unused,
list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices,
list) {
if (entry->netdev == netdev) {
+ BT_DBG("Unregistered netdev %s %p",
+ netdev->name, netdev);
list_del(&entry->list);
kfree(entry);
break;
@@ -1217,6 +1269,8 @@ static void __exit bt_6lowpan_exit(void)
if (listen_chan)
chan_close(listen_chan, 0);
+ disconnect_devices();
+
unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
}
--
1.8.3.1
Create a CoC dynamically instead of one fixed channel for communication
to peer devices.
Signed-off-by: Jukka Rissanen <[email protected]>
---
include/net/bluetooth/hci.h | 1 -
include/net/bluetooth/hci_core.h | 1 -
include/net/bluetooth/l2cap.h | 1 -
net/bluetooth/6lowpan.c | 756 ++++++++++++++++++++++++++++-----------
net/bluetooth/6lowpan.h | 47 ---
net/bluetooth/hci_core.c | 45 ---
net/bluetooth/hci_event.c | 3 -
net/bluetooth/l2cap_core.c | 20 +-
8 files changed, 561 insertions(+), 313 deletions(-)
delete mode 100644 net/bluetooth/6lowpan.h
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 16587dc..3f95aba 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -139,7 +139,6 @@ enum {
HCI_PERIODIC_INQ,
HCI_FAST_CONNECTABLE,
HCI_BREDR_ENABLED,
- HCI_6LOWPAN_ENABLED,
HCI_LE_SCAN_INTERRUPTED,
};
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index b386bf1..c3a2954 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -520,7 +520,6 @@ enum {
HCI_CONN_AES_CCM,
HCI_CONN_POWER_SAVE,
HCI_CONN_REMOTE_OOB,
- HCI_CONN_6LOWPAN,
};
static inline bool hci_conn_ssp_enabled(struct hci_conn *conn)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index e4f4ca6..eb38493 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -137,7 +137,6 @@ struct l2cap_conninfo {
#define L2CAP_FC_L2CAP 0x02
#define L2CAP_FC_CONNLESS 0x04
#define L2CAP_FC_A2MP 0x08
-#define L2CAP_FC_6LOWPAN 0x3e /* reserved and temporary value */
/* L2CAP Control Field bit masks */
#define L2CAP_CTRL_SAR 0xC000
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 8796ffa..5f2af0f 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -1,5 +1,5 @@
/*
- Copyright (c) 2013 Intel Corp.
+ 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
@@ -14,6 +14,7 @@
#include <linux/if_arp.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
+#include <linux/debugfs.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
@@ -25,16 +26,21 @@
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/l2cap.h>
-#include "6lowpan.h"
-
#include <net/6lowpan.h> /* for the compression support */
+#define VERSION "1.0"
+
+static struct dentry *lowpan_psm_debugfs;
+static struct dentry *lowpan_control_debugfs;
+static struct l2cap_ops bt_6lowpan_chan_ops;
+
#define IFACE_NAME_TEMPLATE "bt%d"
#define EUI64_ADDR_LEN 8
struct skb_cb {
struct in6_addr addr;
- struct l2cap_conn *conn;
+ struct l2cap_chan *chan;
+ int status;
};
#define lowpan_cb(skb) ((struct skb_cb *)((skb)->cb))
@@ -48,9 +54,19 @@ struct skb_cb {
static LIST_HEAD(bt_6lowpan_devices);
static DEFINE_RWLOCK(devices_lock);
+/* If psm is set to 0 (default value), then 6lowpan is disabled.
+ * Other values are used to indicate a Protocol Service Multiplexer
+ * value for 6lowpan.
+ */
+static u16 psm_6lowpan;
+
+/* We are listening incoming connections via this channel
+ */
+static struct l2cap_chan *listen_chan;
+
struct lowpan_peer {
struct list_head list;
- struct l2cap_conn *conn;
+ struct l2cap_chan *chan;
/* peer addresses in various formats */
unsigned char eui64_addr[EUI64_ADDR_LEN];
@@ -101,13 +117,26 @@ static inline struct lowpan_peer *peer_lookup_ba(struct lowpan_dev *dev,
ba, type);
list_for_each_entry_safe(peer, tmp, &dev->peers, list) {
- BT_DBG("addr %pMR type %d",
- &peer->conn->hcon->dst, peer->conn->hcon->dst_type);
+ BT_DBG("dst addr %pMR dst type %d",
+ &peer->chan->dst, peer->chan->dst_type);
- if (bacmp(&peer->conn->hcon->dst, ba))
+ if (bacmp(&peer->chan->dst, ba))
continue;
- if (type == peer->conn->hcon->dst_type)
+ if (type == peer->chan->dst_type)
+ return peer;
+ }
+
+ return NULL;
+}
+
+static inline struct lowpan_peer *peer_lookup_chan(struct lowpan_dev *dev,
+ struct l2cap_chan *chan)
+{
+ struct lowpan_peer *peer, *tmp;
+
+ list_for_each_entry_safe(peer, tmp, &dev->peers, list) {
+ if (peer->chan == chan)
return peer;
}
@@ -120,7 +149,7 @@ static inline struct lowpan_peer *peer_lookup_conn(struct lowpan_dev *dev,
struct lowpan_peer *peer, *tmp;
list_for_each_entry_safe(peer, tmp, &dev->peers, list) {
- if (peer->conn == conn)
+ if (peer->chan->conn == conn)
return peer;
}
@@ -185,7 +214,7 @@ static int give_skb_to_upper(struct sk_buff *skb, struct net_device *dev)
}
static int process_data(struct sk_buff *skb, struct net_device *netdev,
- struct l2cap_conn *conn)
+ struct l2cap_chan *chan)
{
const u8 *saddr, *daddr;
u8 iphc0, iphc1;
@@ -196,7 +225,7 @@ static int process_data(struct sk_buff *skb, struct net_device *netdev,
dev = lowpan_dev(netdev);
read_lock_irqsave(&devices_lock, flags);
- peer = peer_lookup_conn(dev, conn);
+ peer = peer_lookup_chan(dev, chan);
read_unlock_irqrestore(&devices_lock, flags);
if (!peer)
goto drop;
@@ -225,7 +254,7 @@ drop:
}
static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
- struct l2cap_conn *conn)
+ struct l2cap_chan *chan)
{
struct sk_buff *local_skb;
int ret;
@@ -269,7 +298,7 @@ static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
if (!local_skb)
goto drop;
- ret = process_data(local_skb, dev, conn);
+ ret = process_data(local_skb, dev, chan);
if (ret != NET_RX_SUCCESS)
goto drop;
@@ -286,147 +315,37 @@ static int recv_pkt(struct sk_buff *skb, struct net_device *dev,
return NET_RX_SUCCESS;
drop:
+ dev->stats.rx_dropped++;
kfree_skb(skb);
return NET_RX_DROP;
}
/* Packet from BT LE device */
-int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb)
+static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
{
struct lowpan_dev *dev;
struct lowpan_peer *peer;
int err;
- peer = lookup_peer(conn);
+ peer = lookup_peer(chan->conn);
if (!peer)
return -ENOENT;
- dev = lookup_dev(conn);
+ dev = lookup_dev(chan->conn);
if (!dev || !dev->netdev)
return -ENOENT;
- err = recv_pkt(skb, dev->netdev, conn);
+ err = recv_pkt(skb, dev->netdev, chan);
+
BT_DBG("recv pkt %d", err);
return err;
}
-static inline int skbuff_copy(void *msg, int len, int count, int mtu,
- struct sk_buff *skb, struct net_device *dev)
-{
- struct sk_buff **frag;
- int sent = 0;
-
- memcpy(skb_put(skb, count), msg, count);
-
- sent += count;
- msg += count;
- len -= count;
-
- dev->stats.tx_bytes += count;
- dev->stats.tx_packets++;
-
- raw_dump_table(__func__, "Sending", skb->data, skb->len);
-
- /* Continuation fragments (no L2CAP header) */
- frag = &skb_shinfo(skb)->frag_list;
- while (len > 0) {
- struct sk_buff *tmp;
-
- count = min_t(unsigned int, mtu, len);
-
- tmp = bt_skb_alloc(count, GFP_ATOMIC);
- if (!tmp)
- return -ENOMEM;
-
- *frag = tmp;
-
- memcpy(skb_put(*frag, count), msg, count);
-
- raw_dump_table(__func__, "Sending fragment",
- (*frag)->data, count);
-
- (*frag)->priority = skb->priority;
-
- sent += count;
- msg += count;
- len -= count;
-
- skb->len += (*frag)->len;
- skb->data_len += (*frag)->len;
-
- frag = &(*frag)->next;
-
- dev->stats.tx_bytes += count;
- dev->stats.tx_packets++;
- }
-
- return sent;
-}
-
-static struct sk_buff *create_pdu(struct l2cap_conn *conn, void *msg,
- size_t len, u32 priority,
- struct net_device *dev)
-{
- struct sk_buff *skb;
- int err, count;
- struct l2cap_hdr *lh;
-
- /* FIXME: This mtu check should be not needed and atm is only used for
- * testing purposes
- */
- if (conn->mtu > (L2CAP_LE_MIN_MTU + L2CAP_HDR_SIZE))
- conn->mtu = L2CAP_LE_MIN_MTU + L2CAP_HDR_SIZE;
-
- count = min_t(unsigned int, (conn->mtu - L2CAP_HDR_SIZE), len);
-
- BT_DBG("conn %p len %zu mtu %d count %d", conn, len, conn->mtu, count);
-
- skb = bt_skb_alloc(count + L2CAP_HDR_SIZE, GFP_ATOMIC);
- if (!skb)
- return ERR_PTR(-ENOMEM);
-
- skb->priority = priority;
-
- lh = (struct l2cap_hdr *)skb_put(skb, L2CAP_HDR_SIZE);
- lh->cid = cpu_to_le16(L2CAP_FC_6LOWPAN);
- lh->len = cpu_to_le16(len);
-
- err = skbuff_copy(msg, len, count, conn->mtu, skb, dev);
- if (unlikely(err < 0)) {
- kfree_skb(skb);
- BT_DBG("skbuff copy %d failed", err);
- return ERR_PTR(err);
- }
-
- return skb;
-}
-
-static int conn_send(struct l2cap_conn *conn,
- void *msg, size_t len, u32 priority,
- struct net_device *dev)
-{
- struct sk_buff *skb;
-
- skb = create_pdu(conn, msg, len, priority, dev);
- if (IS_ERR(skb))
- return -EINVAL;
-
- BT_DBG("conn %p skb %p len %d priority %u", conn, skb, skb->len,
- skb->priority);
-
- hci_send_acl(conn->hchan, skb, ACL_START);
-
- return 0;
-}
-
static u8 get_addr_type_from_eui64(u8 byte)
{
- /* Is universal(0) or local(1) bit, */
- if (byte & 0x02)
- return ADDR_LE_DEV_RANDOM;
-
- return ADDR_LE_DEV_PUBLIC;
+ /* Is universal(0) or local(1) bit */
+ return ((byte & 0x02) ? BDADDR_LE_RANDOM : BDADDR_LE_PUBLIC);
}
static void copy_to_bdaddr(struct in6_addr *ip6_daddr, bdaddr_t *addr)
@@ -475,7 +394,7 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
if (ipv6_addr_is_multicast(&hdr->daddr)) {
memcpy(&lowpan_cb(skb)->addr, &hdr->daddr,
sizeof(struct in6_addr));
- lowpan_cb(skb)->conn = NULL;
+ lowpan_cb(skb)->chan = NULL;
} else {
unsigned long flags;
@@ -484,9 +403,8 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
*/
convert_dest_bdaddr(&hdr->daddr, &addr, &addr_type);
- BT_DBG("dest addr %pMR type %s IP %pI6c", &addr,
- addr_type == ADDR_LE_DEV_PUBLIC ? "PUBLIC" : "RANDOM",
- &hdr->daddr);
+ BT_DBG("dest addr %pMR type %d IP %pI6c", &addr,
+ addr_type, &hdr->daddr);
read_lock_irqsave(&devices_lock, flags);
peer = peer_lookup_ba(dev, &addr, addr_type);
@@ -501,7 +419,7 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
memcpy(&lowpan_cb(skb)->addr, &hdr->daddr,
sizeof(struct in6_addr));
- lowpan_cb(skb)->conn = peer->conn;
+ lowpan_cb(skb)->chan = peer->chan;
}
saddr = dev->netdev->dev_addr;
@@ -510,14 +428,42 @@ static int header_create(struct sk_buff *skb, struct net_device *netdev,
}
/* Packet to BT LE device */
-static int send_pkt(struct l2cap_conn *conn, const void *saddr,
- const void *daddr, struct sk_buff *skb,
+static int send_pkt(struct l2cap_chan *chan, struct sk_buff *skb,
struct net_device *netdev)
{
- raw_dump_table(__func__, "raw skb data dump before fragmentation",
- skb->data, skb->len);
+ struct msghdr msg;
+ struct iovec iov;
+ int err;
+
+ /* Remember the skb so that we can send EAGAIN to the caller if
+ * we run out of credits.
+ */
+ chan->data = skb;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ iov.iov_base = skb->data;
+ iov.iov_len = skb->len;
+
+ err = l2cap_chan_send(chan, &msg, skb->len);
+ if (err > 0) {
+ netdev->stats.tx_bytes += err;
+ netdev->stats.tx_packets++;
+ return 0;
+ }
+
+ if (!err)
+ err = lowpan_cb(skb)->status;
+
+ if (err < 0) {
+ if (err == -EAGAIN)
+ netdev->stats.tx_dropped++;
+ else
+ netdev->stats.tx_errors++;
+ }
- return conn_send(conn, skb->data, skb->len, 0, netdev);
+ return err;
}
static void send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
@@ -540,8 +486,7 @@ static void send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
list_for_each_entry_safe(pentry, ptmp, &dev->peers, list) {
local_skb = skb_clone(skb, GFP_ATOMIC);
- send_pkt(pentry->conn, netdev->dev_addr,
- pentry->eui64_addr, local_skb, netdev);
+ send_pkt(pentry->chan, local_skb, netdev);
kfree_skb(local_skb);
}
@@ -553,7 +498,6 @@ static void send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
{
int err = 0;
- unsigned char *eui64_addr;
struct lowpan_dev *dev;
struct lowpan_peer *peer;
bdaddr_t addr;
@@ -568,21 +512,20 @@ static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
unsigned long flags;
convert_dest_bdaddr(&lowpan_cb(skb)->addr, &addr, &addr_type);
- eui64_addr = lowpan_cb(skb)->addr.s6_addr + 8;
dev = lowpan_dev(netdev);
read_lock_irqsave(&devices_lock, flags);
peer = peer_lookup_ba(dev, &addr, addr_type);
read_unlock_irqrestore(&devices_lock, flags);
- BT_DBG("xmit %s to %pMR type %s IP %pI6c peer %p",
- netdev->name, &addr,
- addr_type == ADDR_LE_DEV_PUBLIC ? "PUBLIC" : "RANDOM",
+ BT_DBG("xmit %s to %pMR type %d IP %pI6c peer %p",
+ netdev->name, &addr, addr_type,
&lowpan_cb(skb)->addr, peer);
- if (peer && peer->conn)
- err = send_pkt(peer->conn, netdev->dev_addr,
- eui64_addr, skb, netdev);
+ if (peer && peer->chan)
+ err = send_pkt(peer->chan, skb, netdev);
+ else
+ err = -ENOENT;
}
dev_kfree_skb(skb);
@@ -634,7 +577,7 @@ static void set_addr(u8 *eui, u8 *addr, u8 addr_type)
eui[7] = addr[0];
/* Universal/local bit set, BT 6lowpan draft ch. 3.2.1 */
- if (addr_type == ADDR_LE_DEV_PUBLIC)
+ if (addr_type == BDADDR_LE_PUBLIC)
eui[0] &= ~0x02;
else
eui[0] |= 0x02;
@@ -673,26 +616,65 @@ static bool is_bt_6lowpan(struct hci_conn *hcon)
if (hcon->type != LE_LINK)
return false;
- return test_bit(HCI_CONN_6LOWPAN, &hcon->flags);
+ if (!psm_6lowpan)
+ return false;
+
+ return true;
+}
+
+static struct l2cap_chan *chan_create(void)
+{
+ struct l2cap_chan *chan;
+
+ chan = l2cap_chan_create();
+ if (!chan)
+ return NULL;
+
+ l2cap_chan_set_defaults(chan);
+
+ chan->chan_type = L2CAP_CHAN_CONN_ORIENTED;
+ chan->mode = L2CAP_MODE_LE_FLOWCTL;
+ chan->omtu = 65535;
+ chan->imtu = chan->omtu;
+ chan->ops = &bt_6lowpan_chan_ops;
+
+ return chan;
+}
+
+static struct l2cap_chan *chan_open(struct l2cap_chan *pchan)
+{
+ struct l2cap_chan *chan;
+
+ chan = chan_create();
+ if (!chan)
+ return NULL;
+
+ chan->remote_mps = chan->omtu;
+ chan->mps = chan->omtu;
+
+ chan->state = BT_CONNECTED;
+
+ return chan;
}
-static int add_peer_conn(struct l2cap_conn *conn, struct lowpan_dev *dev)
+static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
+ struct lowpan_dev *dev)
{
struct lowpan_peer *peer;
unsigned long flags;
peer = kzalloc(sizeof(*peer), GFP_ATOMIC);
if (!peer)
- return -ENOMEM;
+ return NULL;
- peer->conn = conn;
+ peer->chan = chan;
memset(&peer->peer_addr, 0, sizeof(struct in6_addr));
/* RFC 2464 ch. 5 */
peer->peer_addr.s6_addr[0] = 0xFE;
peer->peer_addr.s6_addr[1] = 0x80;
- set_addr((u8 *)&peer->peer_addr.s6_addr + 8, conn->hcon->dst.b,
- conn->hcon->dst_type);
+ set_addr((u8 *)&peer->peer_addr.s6_addr + 8, chan->dst.b,
+ chan->dst_type);
memcpy(&peer->eui64_addr, (u8 *)&peer->peer_addr.s6_addr + 8,
EUI64_ADDR_LEN);
@@ -706,40 +688,24 @@ static int add_peer_conn(struct l2cap_conn *conn, struct lowpan_dev *dev)
INIT_DELAYED_WORK(&dev->notify_peers, do_notify_peers);
schedule_delayed_work(&dev->notify_peers, msecs_to_jiffies(100));
- return 0;
+ return peer->chan;
}
-/* This gets called when BT LE 6LoWPAN device is connected. We then
- * create network device that acts as a proxy between BT LE device
- * and kernel network stack.
- */
-int bt_6lowpan_add_conn(struct l2cap_conn *conn)
+static int setup_netdev(struct l2cap_chan *chan, struct lowpan_dev **dev)
{
- struct lowpan_peer *peer = NULL;
- struct lowpan_dev *dev;
struct net_device *netdev;
int err = 0;
unsigned long flags;
- if (!is_bt_6lowpan(conn->hcon))
- return 0;
-
- peer = lookup_peer(conn);
- if (peer)
- return -EEXIST;
-
- dev = lookup_dev(conn);
- if (dev)
- return add_peer_conn(conn, dev);
-
- netdev = alloc_netdev(sizeof(*dev), IFACE_NAME_TEMPLATE, netdev_setup);
+ netdev = alloc_netdev(sizeof(struct lowpan_dev), IFACE_NAME_TEMPLATE,
+ netdev_setup);
if (!netdev)
return -ENOMEM;
- set_dev_addr(netdev, &conn->hcon->src, conn->hcon->src_type);
+ set_dev_addr(netdev, &chan->src, chan->src_type);
netdev->netdev_ops = &netdev_ops;
- SET_NETDEV_DEV(netdev, &conn->hcon->dev);
+ SET_NETDEV_DEV(netdev, &chan->conn->hcon->dev);
SET_NETDEV_DEVTYPE(netdev, &bt_type);
err = register_netdev(netdev);
@@ -749,28 +715,53 @@ int bt_6lowpan_add_conn(struct l2cap_conn *conn)
goto out;
}
- BT_DBG("ifindex %d peer bdaddr %pMR my addr %pMR",
- netdev->ifindex, &conn->hcon->dst, &conn->hcon->src);
+ BT_DBG("ifindex %d peer bdaddr %pMR type %d my addr %pMR type %d",
+ netdev->ifindex, &chan->dst, chan->dst_type,
+ &chan->src, chan->src_type);
set_bit(__LINK_STATE_PRESENT, &netdev->state);
- dev = netdev_priv(netdev);
- dev->netdev = netdev;
- dev->hdev = conn->hcon->hdev;
- INIT_LIST_HEAD(&dev->peers);
+ *dev = netdev_priv(netdev);
+ (*dev)->netdev = netdev;
+ (*dev)->hdev = chan->conn->hcon->hdev;
+ INIT_LIST_HEAD(&(*dev)->peers);
write_lock_irqsave(&devices_lock, flags);
- INIT_LIST_HEAD(&dev->list);
- list_add(&dev->list, &bt_6lowpan_devices);
+ INIT_LIST_HEAD(&(*dev)->list);
+ list_add(&(*dev)->list, &bt_6lowpan_devices);
write_unlock_irqrestore(&devices_lock, flags);
- ifup(netdev);
-
- return add_peer_conn(conn, dev);
+ return 0;
out:
return err;
}
+static inline void chan_ready_cb(struct l2cap_chan *chan)
+{
+ struct lowpan_dev *dev;
+
+ dev = lookup_dev(chan->conn);
+
+ BT_DBG("chan %p conn %p dev %p", chan, chan->conn, dev);
+
+ if (!dev) {
+ if (setup_netdev(chan, &dev) < 0) {
+ l2cap_chan_del(chan, -ENOENT);
+ return;
+ }
+ }
+
+ add_peer_chan(chan, dev);
+ ifup(dev->netdev);
+}
+
+static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *chan)
+{
+ BT_DBG("chan %p", chan);
+
+ return chan_open(chan);
+}
+
static void delete_netdev(struct work_struct *work)
{
struct lowpan_dev *entry = container_of(work, struct lowpan_dev,
@@ -781,26 +772,38 @@ static void delete_netdev(struct work_struct *work)
/* The entry pointer is deleted in device_event() */
}
-int bt_6lowpan_del_conn(struct l2cap_conn *conn)
+static void chan_close_cb(struct l2cap_chan *chan)
{
struct lowpan_dev *entry, *tmp;
struct lowpan_dev *dev = NULL;
struct lowpan_peer *peer;
int err = -ENOENT;
unsigned long flags;
- bool last = false;
+ bool last = false, removed = true;
- if (!conn || !is_bt_6lowpan(conn->hcon))
- return 0;
+ BT_DBG("chan %p conn %p", chan, chan->conn);
+
+ if (chan->conn && chan->conn->hcon) {
+ if (!is_bt_6lowpan(chan->conn->hcon))
+ return;
+
+ /* If conn is set, then the netdev is also there and we should
+ * not remove it.
+ */
+ removed = false;
+ }
write_lock_irqsave(&devices_lock, flags);
list_for_each_entry_safe(entry, tmp, &bt_6lowpan_devices, list) {
dev = lowpan_dev(entry->netdev);
- peer = peer_lookup_conn(dev, conn);
+ peer = peer_lookup_chan(dev, chan);
if (peer) {
last = peer_del(dev, peer);
err = 0;
+ BT_DBG("dev %p removing %speer %p", dev,
+ last ? "last " : "1 ", peer);
+ kfree(peer);
break;
}
}
@@ -810,18 +813,352 @@ int bt_6lowpan_del_conn(struct l2cap_conn *conn)
cancel_delayed_work_sync(&dev->notify_peers);
- /* bt_6lowpan_del_conn() is called with hci dev lock held which
- * means that we must delete the netdevice in worker thread.
- */
- INIT_WORK(&entry->delete_netdev, delete_netdev);
- schedule_work(&entry->delete_netdev);
+ if (!removed) {
+ INIT_WORK(&entry->delete_netdev, delete_netdev);
+ schedule_work(&entry->delete_netdev);
+ }
} else {
write_unlock_irqrestore(&devices_lock, flags);
}
+ return;
+}
+
+static void chan_state_change_cb(struct l2cap_chan *chan, int state, int err)
+{
+ BT_DBG("chan %p conn %p", chan, chan->conn);
+}
+
+static struct sk_buff *chan_alloc_skb_cb(struct l2cap_chan *chan,
+ unsigned long hdr_len,
+ unsigned long len, int nb)
+{
+ return bt_skb_alloc(hdr_len + len, GFP_ATOMIC);
+}
+
+static void chan_suspend_cb(struct l2cap_chan *chan)
+{
+ struct sk_buff *skb = chan->data;
+
+ BT_DBG("chan %p conn %p skb %p", chan, chan->conn, skb);
+
+ lowpan_cb(skb)->status = -EAGAIN;
+}
+
+static void chan_resume_cb(struct l2cap_chan *chan)
+{
+ struct sk_buff *skb = chan->data;
+
+ BT_DBG("chan %p conn %p skb %p", chan, chan->conn, skb);
+
+ lowpan_cb(skb)->status = 0;
+}
+
+static long chan_get_sndtimeo_cb(struct l2cap_chan *chan)
+{
+ return msecs_to_jiffies(1000);
+}
+
+static void chan_teardown_cb(struct l2cap_chan *chan, int err)
+{
+ BT_DBG("chan %p conn %p err %d", chan, chan->conn, err);
+}
+
+static struct l2cap_ops bt_6lowpan_chan_ops = {
+ .name = "L2CAP 6LoWPAN channel",
+ .new_connection = chan_new_conn_cb,
+ .recv = chan_recv_cb,
+ .teardown = chan_teardown_cb,
+ .close = chan_close_cb,
+ .state_change = chan_state_change_cb,
+ .ready = chan_ready_cb,
+ .resume = chan_resume_cb,
+ .suspend = chan_suspend_cb,
+ .get_sndtimeo = chan_get_sndtimeo_cb,
+ .alloc_skb = chan_alloc_skb_cb,
+ .memcpy_fromiovec = l2cap_chan_no_memcpy_fromiovec,
+
+ .defer = l2cap_chan_no_defer,
+ .set_shutdown = l2cap_chan_no_set_shutdown,
+};
+
+static inline __u8 bdaddr_type(__u8 type)
+{
+ if (type == ADDR_LE_DEV_PUBLIC)
+ return BDADDR_LE_PUBLIC;
+ else
+ return BDADDR_LE_RANDOM;
+}
+
+static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
+{
+ struct l2cap_chan *pchan;
+ int err;
+
+ pchan = chan_create();
+ if (!pchan)
+ return -EINVAL;
+
+ err = l2cap_chan_connect(pchan, cpu_to_le16(psm_6lowpan), 0,
+ addr, dst_type);
+
+ BT_DBG("chan %p err %d", pchan, err);
+
return err;
}
+static void chan_close(struct l2cap_chan *chan, int reason)
+{
+ l2cap_chan_lock(chan);
+ l2cap_chan_close(chan, reason);
+ l2cap_chan_unlock(chan);
+ l2cap_chan_put(chan);
+}
+
+static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
+{
+ struct lowpan_peer *peer;
+
+ BT_DBG("conn %p dst type %d", conn, dst_type);
+
+ peer = lookup_peer(conn);
+ if (!peer)
+ return -ENOENT;
+
+ chan_close(peer->chan, ENOENT);
+
+ return 0;
+}
+
+static struct l2cap_chan *bt_6lowpan_listen(void)
+{
+ bdaddr_t *addr = BDADDR_ANY;
+ struct l2cap_chan *pchan;
+ int err;
+
+ if (psm_6lowpan == 0)
+ return NULL;
+
+ pchan = chan_create();
+ if (!pchan)
+ return NULL;
+
+ pchan->state = BT_LISTEN;
+ pchan->src_type = BDADDR_LE_PUBLIC;
+
+ BT_DBG("psm 0x%04x chan %p src type %d", psm_6lowpan, pchan,
+ pchan->src_type);
+
+ err = l2cap_add_psm(pchan, addr, cpu_to_le16(psm_6lowpan));
+ if (err) {
+ BT_ERR("psm cannot be added err %d", err);
+ return NULL;
+ }
+
+ return pchan;
+}
+
+static int get_l2cap_conn(char *buf, bdaddr_t *addr, u8 *addr_type,
+ struct l2cap_conn **conn)
+{
+ struct hci_conn *hcon;
+ struct hci_dev *hdev;
+ bdaddr_t *src = BDADDR_ANY;
+ 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;
+
+ hdev = hci_get_route(addr, src);
+ if (!hdev)
+ return -ENOENT;
+
+ hci_dev_lock(hdev);
+ hcon = hci_conn_hash_lookup_ba(hdev, LE_LINK, addr);
+ hci_dev_unlock(hdev);
+
+ if (!hcon)
+ return -ENOENT;
+
+ *conn = (struct l2cap_conn *)hcon->l2cap_data;
+
+ BT_DBG("conn %p dst %pMR type %d", *conn, &hcon->dst, hcon->dst_type);
+
+ return 0;
+}
+
+static void disconnect_all_peers(void)
+{
+ struct lowpan_dev *entry, *tmp_dev;
+ struct lowpan_peer *peer, *tmp_peer, *new_peer;
+ struct list_head peers;
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&peers);
+
+ /* We make a separate list of peers as the close_cb() will
+ * modify the device peers list so it is better not to mess
+ * with the same list at the same time.
+ */
+
+ read_lock_irqsave(&devices_lock, flags);
+
+ list_for_each_entry_safe(entry, tmp_dev, &bt_6lowpan_devices, list) {
+ list_for_each_entry_safe(peer, tmp_peer, &entry->peers, list) {
+ new_peer = kmalloc(sizeof(*new_peer), GFP_ATOMIC);
+ if (!new_peer)
+ break;
+
+ new_peer->chan = peer->chan;
+ INIT_LIST_HEAD(&new_peer->list);
+
+ list_add(&new_peer->list, &peers);
+ }
+ }
+
+ read_unlock_irqrestore(&devices_lock, flags);
+
+ list_for_each_entry_safe(peer, tmp_peer, &peers, list) {
+ chan_close(peer->chan, ENOENT);
+ kfree(peer);
+ }
+}
+
+static int lowpan_psm_set(void *data, u64 val)
+{
+ u16 psm;
+
+ psm = val;
+ if (psm == 0 || psm_6lowpan != psm)
+ /* Disconnect existing connections if 6lowpan is
+ * disabled (psm = 0), or if psm changes.
+ */
+ disconnect_all_peers();
+
+ psm_6lowpan = psm;
+
+ if (listen_chan)
+ chan_close(listen_chan, 0);
+
+ listen_chan = bt_6lowpan_listen();
+
+ return 0;
+}
+
+static int lowpan_psm_get(void *data, u64 *val)
+{
+ *val = psm_6lowpan;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(lowpan_psm_fops, lowpan_psm_get,
+ lowpan_psm_set, "%llu\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);
+ int ret;
+ bdaddr_t addr;
+ u8 addr_type;
+ struct l2cap_conn *conn = NULL;
+
+ if (copy_from_user(buf, user_buffer, buf_size))
+ return -EFAULT;
+
+ buf[buf_size] = '\0';
+
+ if (memcmp(buf, "connect ", 8) == 0) {
+
+ ret = get_l2cap_conn(&buf[8], &addr, &addr_type, &conn);
+ if (ret == -EINVAL)
+ return ret;
+
+ if (listen_chan) {
+ chan_close(listen_chan, 0);
+ listen_chan = NULL;
+ }
+
+ if (conn) {
+ struct lowpan_peer *peer;
+
+ if (!is_bt_6lowpan(conn->hcon))
+ return -EINVAL;
+
+ peer = lookup_peer(conn);
+ if (peer) {
+ BT_DBG("6LoWPAN connection already exists");
+ return -EALREADY;
+ }
+
+ BT_DBG("conn %p dst %pMR type %d user %d", conn,
+ &conn->hcon->dst, conn->hcon->dst_type,
+ addr_type);
+ }
+
+ ret = bt_6lowpan_connect(&addr, addr_type);
+ if (ret < 0)
+ return ret;
+
+ return count;
+ }
+
+ if (memcmp(buf, "disconnect ", 11) == 0) {
+
+ ret = get_l2cap_conn(&buf[11], &addr, &addr_type, &conn);
+ if (ret < 0)
+ return ret;
+
+ ret = bt_6lowpan_disconnect(conn, addr_type);
+ if (ret < 0)
+ return ret;
+
+ return count;
+ }
+
+ return count;
+}
+
+static int lowpan_control_show(struct seq_file *f, void *ptr)
+{
+ struct lowpan_dev *entry, *tmp_dev;
+ struct lowpan_peer *peer, *tmp_peer;
+ unsigned long flags;
+
+ read_lock_irqsave(&devices_lock, flags);
+
+ list_for_each_entry_safe(entry, tmp_dev, &bt_6lowpan_devices, list) {
+ list_for_each_entry_safe(peer, tmp_peer, &entry->peers, list)
+ seq_printf(f, "%pMR (type %u)\n",
+ &peer->chan->dst, peer->chan->dst_type);
+ }
+
+ read_unlock_irqrestore(&devices_lock, flags);
+
+ 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 device_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
@@ -856,10 +1193,23 @@ static struct notifier_block bt_6lowpan_dev_notifier = {
int bt_6lowpan_init(void)
{
+ lowpan_psm_debugfs = debugfs_create_file("6lowpan_psm", 0644,
+ bt_debugfs, NULL,
+ &lowpan_psm_fops);
+ lowpan_control_debugfs = debugfs_create_file("6lowpan_control", 0644,
+ bt_debugfs, NULL,
+ &lowpan_control_fops);
+
return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
}
-void bt_6lowpan_cleanup(void)
+void bt_6lowpan_exit(void)
{
+ debugfs_remove(lowpan_psm_debugfs);
+ debugfs_remove(lowpan_control_debugfs);
+
+ if (listen_chan)
+ chan_close(listen_chan, 0);
+
unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
}
diff --git a/net/bluetooth/6lowpan.h b/net/bluetooth/6lowpan.h
deleted file mode 100644
index 5d281f1..0000000
--- a/net/bluetooth/6lowpan.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- Copyright (c) 2013 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.
-*/
-
-#ifndef __6LOWPAN_H
-#define __6LOWPAN_H
-
-#include <linux/errno.h>
-#include <linux/skbuff.h>
-#include <net/bluetooth/l2cap.h>
-
-#if IS_ENABLED(CONFIG_BT_6LOWPAN)
-int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb);
-int bt_6lowpan_add_conn(struct l2cap_conn *conn);
-int bt_6lowpan_del_conn(struct l2cap_conn *conn);
-int bt_6lowpan_init(void);
-void bt_6lowpan_cleanup(void);
-#else
-static int bt_6lowpan_recv(struct l2cap_conn *conn, struct sk_buff *skb)
-{
- return -EOPNOTSUPP;
-}
-static int bt_6lowpan_add_conn(struct l2cap_conn *conn)
-{
- return -EOPNOTSUPP;
-}
-int bt_6lowpan_del_conn(struct l2cap_conn *conn)
-{
- return -EOPNOTSUPP;
-}
-static int bt_6lowpan_init(void)
-{
- return -EOPNOTSUPP;
-}
-static void bt_6lowpan_cleanup(void) { }
-#endif
-
-#endif /* __6LOWPAN_H */
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 0a43cce..fb532ca 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -928,49 +928,6 @@ static int adv_channel_map_get(void *data, u64 *val)
DEFINE_SIMPLE_ATTRIBUTE(adv_channel_map_fops, adv_channel_map_get,
adv_channel_map_set, "%llu\n");
-static ssize_t lowpan_read(struct file *file, char __user *user_buf,
- size_t count, loff_t *ppos)
-{
- struct hci_dev *hdev = file->private_data;
- char buf[3];
-
- buf[0] = test_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags) ? 'Y' : 'N';
- buf[1] = '\n';
- buf[2] = '\0';
- return simple_read_from_buffer(user_buf, count, ppos, buf, 2);
-}
-
-static ssize_t lowpan_write(struct file *fp, const char __user *user_buffer,
- size_t count, loff_t *position)
-{
- struct hci_dev *hdev = fp->private_data;
- bool enable;
- char buf[32];
- size_t buf_size = min(count, (sizeof(buf)-1));
-
- if (copy_from_user(buf, user_buffer, buf_size))
- return -EFAULT;
-
- buf[buf_size] = '\0';
-
- if (strtobool(buf, &enable) < 0)
- return -EINVAL;
-
- if (enable == test_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags))
- return -EALREADY;
-
- change_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags);
-
- return count;
-}
-
-static const struct file_operations lowpan_debugfs_fops = {
- .open = simple_open,
- .read = lowpan_read,
- .write = lowpan_write,
- .llseek = default_llseek,
-};
-
static int le_auto_conn_show(struct seq_file *sf, void *ptr)
{
struct hci_dev *hdev = sf->private;
@@ -1881,8 +1838,6 @@ static int __hci_init(struct hci_dev *hdev)
hdev, &conn_max_interval_fops);
debugfs_create_file("adv_channel_map", 0644, hdev->debugfs,
hdev, &adv_channel_map_fops);
- debugfs_create_file("6lowpan", 0644, hdev->debugfs, hdev,
- &lowpan_debugfs_fops);
debugfs_create_file("le_auto_conn", 0644, hdev->debugfs, hdev,
&le_auto_conn_fops);
debugfs_create_u16("discov_interleaved_timeout", 0644,
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index ec1b174..360c1c6 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -4055,9 +4055,6 @@ static void hci_le_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
conn->handle = __le16_to_cpu(ev->handle);
conn->state = BT_CONNECTED;
- if (test_bit(HCI_6LOWPAN_ENABLED, &hdev->dev_flags))
- set_bit(HCI_CONN_6LOWPAN, &conn->flags);
-
hci_conn_add_sysfs(conn);
hci_proto_connect_cfm(conn, ev->status);
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index eb0d43d..1d466d2 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -40,7 +40,6 @@
#include "smp.h"
#include "a2mp.h"
#include "amp.h"
-#include "6lowpan.h"
#define LE_FLOWCTL_MAX_CREDITS 65535
@@ -205,6 +204,7 @@ done:
write_unlock(&chan_list_lock);
return err;
}
+EXPORT_SYMBOL_GPL(l2cap_add_psm);
int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid)
{
@@ -437,6 +437,7 @@ struct l2cap_chan *l2cap_chan_create(void)
return chan;
}
+EXPORT_SYMBOL_GPL(l2cap_chan_create);
static void l2cap_chan_destroy(struct kref *kref)
{
@@ -464,6 +465,7 @@ void l2cap_chan_put(struct l2cap_chan *c)
kref_put(&c->kref, l2cap_chan_destroy);
}
+EXPORT_SYMBOL_GPL(l2cap_chan_put);
void l2cap_chan_set_defaults(struct l2cap_chan *chan)
{
@@ -482,6 +484,7 @@ void l2cap_chan_set_defaults(struct l2cap_chan *chan)
set_bit(FLAG_FORCE_ACTIVE, &chan->flags);
}
+EXPORT_SYMBOL_GPL(l2cap_chan_set_defaults);
static void l2cap_le_flowctl_init(struct l2cap_chan *chan)
{
@@ -614,6 +617,7 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
return;
}
+EXPORT_SYMBOL_GPL(l2cap_chan_del);
void l2cap_conn_update_id_addr(struct hci_conn *hcon)
{
@@ -717,6 +721,7 @@ void l2cap_chan_close(struct l2cap_chan *chan, int reason)
break;
}
}
+EXPORT_SYMBOL(l2cap_chan_close);
static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
{
@@ -1460,8 +1465,6 @@ static void l2cap_le_conn_ready(struct l2cap_conn *conn)
BT_DBG("");
- bt_6lowpan_add_conn(conn);
-
/* Check if we have socket listening on cid */
pchan = l2cap_global_chan_by_scid(BT_LISTEN, L2CAP_CID_ATT,
&hcon->src, &hcon->dst);
@@ -2549,6 +2552,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len)
return err;
}
+EXPORT_SYMBOL_GPL(l2cap_chan_send);
static void l2cap_send_srej(struct l2cap_chan *chan, u16 txseq)
{
@@ -6927,10 +6931,6 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
l2cap_conn_del(conn->hcon, EACCES);
break;
- case L2CAP_FC_6LOWPAN:
- bt_6lowpan_recv(conn, skb);
- break;
-
default:
l2cap_data_channel(conn, cid, skb);
break;
@@ -7177,6 +7177,7 @@ done:
hci_dev_put(hdev);
return err;
}
+EXPORT_SYMBOL_GPL(l2cap_chan_connect);
/* ---- L2CAP interface with lower layer (HCI) ---- */
@@ -7239,8 +7240,6 @@ void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
{
BT_DBG("hcon %p reason %d", hcon, reason);
- bt_6lowpan_del_conn(hcon->l2cap_data);
-
l2cap_conn_del(hcon, bt_to_errno(reason));
}
@@ -7523,14 +7522,11 @@ int __init l2cap_init(void)
debugfs_create_u16("l2cap_le_default_mps", 0644, bt_debugfs,
&le_default_mps);
- bt_6lowpan_init();
-
return 0;
}
void l2cap_exit(void)
{
- bt_6lowpan_cleanup();
debugfs_remove(l2cap_debugfs);
l2cap_cleanup_sockets();
}
--
1.8.3.1
Instead of adding the 6LoWPAN functionality to Bluetooth module,
we create a separate kernel module for it.
Usage:
In the slave side do this:
$ modprobe bluetooth_6lowpan
$ echo 62 > /sys/kernel/debug/bluetooth/6lowpan_psm
$ hciconfig hci0 leadv
In the master side do this:
$ modprobe bluetooth_6lowpan
$ echo 62 > /sys/kernel/debug/bluetooth/6lowpan_psm
$ echo 'connect E0:06:E6:B7:2A:73 1' > \
/sys/kernel/debug/bluetooth/6lowpan_control
The 6LoWPAN functionality can be controlled by psm value. If it
is left to 0, then the module is disabled and all the 6LoWPAN
connections are dropped if there were any. In the above example,
the psm value is just an example and not a real value for
6LoWPAN service. The real psm value is yet to be defined in
Bluetooth specification.
The 6lowpan controlling interface is a temporary solution
until the specifications are ready.
Signed-off-by: Jukka Rissanen <[email protected]>
---
net/bluetooth/6lowpan.c | 13 +++++++++++--
net/bluetooth/Kconfig | 6 +++---
net/bluetooth/Makefile | 4 +++-
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 5f2af0f..b477e15 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -14,6 +14,7 @@
#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>
@@ -1191,7 +1192,7 @@ static struct notifier_block bt_6lowpan_dev_notifier = {
.notifier_call = device_event,
};
-int bt_6lowpan_init(void)
+static int __init bt_6lowpan_init(void)
{
lowpan_psm_debugfs = debugfs_create_file("6lowpan_psm", 0644,
bt_debugfs, NULL,
@@ -1203,7 +1204,7 @@ int bt_6lowpan_init(void)
return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
}
-void bt_6lowpan_exit(void)
+static void __exit bt_6lowpan_exit(void)
{
debugfs_remove(lowpan_psm_debugfs);
debugfs_remove(lowpan_control_debugfs);
@@ -1213,3 +1214,11 @@ void bt_6lowpan_exit(void)
unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
}
+
+module_init(bt_6lowpan_init);
+module_exit(bt_6lowpan_exit);
+
+MODULE_AUTHOR("Jukka Rissanen <[email protected]>");
+MODULE_DESCRIPTION("Bluetooth 6LoWPAN");
+MODULE_VERSION(VERSION);
+MODULE_LICENSE("GPL");
diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig
index 06ec144..f5afaa2 100644
--- a/net/bluetooth/Kconfig
+++ b/net/bluetooth/Kconfig
@@ -6,7 +6,6 @@ menuconfig BT
tristate "Bluetooth subsystem support"
depends on NET && !S390
depends on RFKILL || !RFKILL
- select 6LOWPAN_IPHC if BT_6LOWPAN
select CRC16
select CRYPTO
select CRYPTO_BLKCIPHER
@@ -41,10 +40,11 @@ menuconfig BT
more information, see <http://www.bluez.org/>.
config BT_6LOWPAN
- bool "Bluetooth 6LoWPAN support"
+ tristate "Bluetooth 6LoWPAN support"
depends on BT && IPV6
+ select 6LOWPAN_IPHC if BT_6LOWPAN
help
- IPv6 compression over Bluetooth.
+ IPv6 compression over Bluetooth Low Energy.
source "net/bluetooth/rfcomm/Kconfig"
diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
index ca51246..886e9aa 100644
--- a/net/bluetooth/Makefile
+++ b/net/bluetooth/Makefile
@@ -7,10 +7,12 @@ 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 sco.o lib.o \
a2mp.o amp.o
-bluetooth-$(CONFIG_BT_6LOWPAN) += 6lowpan.o
subdir-ccflags-y += -D__CHECK_ENDIAN__
--
1.8.3.1