Sniff mode issues regarding Sony Ericsson headsets: kernel patch proposal.

Fabien Chevalier
2008-09-03T19:37:00Z

[-- Attachment #1: Type: text/plain, Size: 5498 bytes --]

Hi Marcel,

Due to job requirement, I recently fined tuned a kernel to work well
with some Sony Erisson A2DP Headsets (namely Motorola HBH-DS970 and
HBH-DS980): this means making sure to force the other side to exit sniff
mode early on before the AVDTP signalling comes into play.

If i remember well we had a talk a while ago about the way the sniff
mode was handled by the kernel due to various issues with some bluetooth
keyboards. The conclusion we had at that time, the best way to handle
this issue was to add a socket option on an L2CAP channel to force the
other side to exit sniff mode even if it was the one who initially
entered the sniff mode.

I used this approach and wrote & tested a patch against 2.6.26-mh3.
Patch is attached and seems to solve the issue.

*but*

During validation testing we found yet another issue that seems to be
related to the way we handle the sniff mode.
The syndrom is that if the user screws up at enterring the pin code
during pairing bluez fails to close the acl connection properly,
which means subsequent attempts result in a "connection already exist"
error. From the user point of view it is impossible to try to pair again
to the same headset...which is quite annoying.
Below is an hci trace of the issue:
< ACL data: handle 1 flags 0x02 dlen 12
L2CAP(s): Connect req: psm 25 scid 0x0040
> HCI Event: Number of Completed Packets (0x13) plen 5
handle 1 packets 1
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0051 scid 0x0040 result 1 status 1
Connection pending - Authentication pending
> HCI Event: PIN Code Request (0x16) plen 6
bdaddr 00:1C:A4:2C:FA:4A
< HCI Command: PIN Code Request Reply (0x01|0x000d) plen 23
bdaddr 00:1C:A4:2C:FA:4A len 4 pin '1111'
> HCI Event: Command Complete (0x0e) plen 10
PIN Code Request Reply (0x01|0x000d) ncmd 2
status 0x00 bdaddr 00:1C:A4:2C:FA:4A
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0051 scid 0x0040 result 3 status 0
Connection refused - security block
> HCI Event: Mode Change (0x14) plen 6
status 0x00 handle 1 mode 0x02 interval 2048
Mode: Sniff
< HCI Command: Disconnect (0x01|0x0006) plen 3
handle 1 reason 0x13
Reason: Remote User Terminated Connection
> HCI Event: Command Status (0x0f) plen 4
Disconnect (0x01|0x0006) status 0x00 ncmd 1
> HCI Event: Disconn Complete (0x05) plen 4
status 0x0c handle 1 reason 0x1f
Error: Command Disallowed
> HCI Event: Command Status (0x0f) plen 4
Unknown (0x00|0x0000) status 0x00 ncmd 2

I hacked the kernel to try to see if forcing the headset to exit sniff
mode before sending disconnect would solve the issue.... well it did :-)
Trace below shows this working:
< ACL data: handle 1 flags 0x02 dlen 12
L2CAP(s): Connect req: psm 25 scid 0x0040
> HCI Event: Read Remote Supported Features (0x0b) plen 11
status 0x00 handle 1
Features: 0xff 0x2e 0x2d 0xfa 0x98 0x39 0x00 0x80
> HCI Event: Command Status (0x0f) plen 4
Unknown (0x00|0x0000) status 0x00 ncmd 1
> HCI Event: Command Complete (0x0e) plen 6
Write Link Policy Settings (0x02|0x000d) ncmd 2
status 0x00 handle 1
> HCI Event: Number of Completed Packets (0x13) plen 5
handle 1 packets 1
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0053 scid 0x0040 result 1 status 1
Connection pending - Authentication pending
> HCI Event: PIN Code Request (0x16) plen 6
bdaddr 00:1C:A4:2C:FA:4A
< HCI Command: PIN Code Request Reply (0x01|0x000d) plen 23
bdaddr 00:1C:A4:2C:FA:4A len 4 pin '1111'
> HCI Event: Command Complete (0x0e) plen 10
PIN Code Request Reply (0x01|0x000d) ncmd 2
status 0x00 bdaddr 00:1C:A4:2C:FA:4A
> ACL data: handle 1 flags 0x02 dlen 16
L2CAP(s): Connect rsp: dcid 0x0053 scid 0x0040 result 3 status 0
Connection refused - security block
> HCI Event: Mode Change (0x14) plen 6
status 0x00 handle 1 mode 0x02 interval 2048
Mode: Sniff
< HCI Command: Exit Sniff Mode (0x02|0x0004) plen 2
handle 1
> HCI Event: Command Status (0x0f) plen 4
Exit Sniff Mode (0x02|0x0004) status 0x00 ncmd 1
< HCI Command: Disconnect (0x01|0x0006) plen 3
handle 1 reason 0x13
Reason: Remote User Terminated Connection
> HCI Event: Command Status (0x0f) plen 4
Disconnect (0x01|0x0006) status 0x00 ncmd 0
> HCI Event: Mode Change (0x14) plen 6
status 0x00 handle 1 mode 0x00 interval 0
Mode: Active
> HCI Event: Command Status (0x0f) plen 4
Unknown (0x00|0x0000) status 0x00 ncmd 1
> HCI Event: Disconn Complete (0x05) plen 4
status 0x00 handle 1 reason 0x16
Reason: Connection Terminated by Local Host
> HCI Event: Command Status (0x0f) plen 4
Unknown (0x00|0x0000) status 0x00 ncmd 2

Conclusion: to have those bloody Sony Erisson headsets working we have
to change two things in the kernel:
1) Provide a way for a L2CAP socket user to alter sniff mode exit
behaviour
2) Make sure we exit sniff mode before to disconnect

Question 1: Are you interested in reviewing then merging my patches if i
try to fix thoses issues ?
Question 2: I have the gut feeling that we should change default
behaviour to the behaviour required by those headsets, and provide a
socket option for the bluetooth HID, rather than the other way round.
What do you think ?

If we can come on an agreement on the proper way to fix this issue then
i should come with a patch in a 3-4 days timeframe.

Regards,

Fabien

[-- Attachment #2: a2dp-sniff-mode-issue.diff --]
[-- Type: text/plain, Size: 3306 bytes --]

diff -ru -x '*.so' -x '*.lds' -x '*.a' -x '*.elf' -x '.*' -x '*.order' -x '*.ko' -x '*.mod.c' -x '*.o' -x '*.cmd' linux-2.6.26/include/net/bluetooth/hci_core.h linux-2.6.26+mh3+fch/include/net/bluetooth/hci_core.h
--- linux-2.6.26/include/net/bluetooth/hci_core.h 2008-09-01 17:06:22.000000000 +0200
+++ linux-2.6.26+mh3+fch/include/net/bluetooth/hci_core.h 2008-09-01 12:22:36.000000000 +0200
@@ -170,6 +170,7 @@
__u32 link_mode;
__u8 auth_type;
__u8 power_save;
+ __u8 force_active_mode;
unsigned long pend;

unsigned int sent;
diff -ru -x '*.so' -x '*.lds' -x '*.a' -x '*.elf' -x '.*' -x '*.order' -x '*.ko' -x '*.mod.c' -x '*.o' -x '*.cmd' linux-2.6.26/include/net/bluetooth/l2cap.h linux-2.6.26+mh3+fch/include/net/bluetooth/l2cap.h
--- linux-2.6.26/include/net/bluetooth/l2cap.h 2008-07-13 23:51:29.000000000 +0200
+++ linux-2.6.26+mh3+fch/include/net/bluetooth/l2cap.h 2008-09-01 16:20:17.000000000 +0200
@@ -62,6 +62,8 @@
#define L2CAP_LM_RELIABLE 0x0010
#define L2CAP_LM_SECURE 0x0020

+#define L2CAP_FORCE_ACTIVE_MODE 0x04
+
/* L2CAP command codes */
#define L2CAP_COMMAND_REJ 0x01
#define L2CAP_CONN_REQ 0x02
Seulement dans linux-2.6.26+mh3+fch/kernel: bounds.s
Seulement dans linux-2.6.26+mh3+fch/kernel: timeconst.h
Seulement dans linux-2.6.26+mh3+fch/lib: crc32table.h
Seulement dans linux-2.6.26+mh3+fch/lib: gen_crc32table
Seulement dans linux-2.6.26+mh3+fch/: Module.symvers
diff -ru -x '*.so' -x '*.lds' -x '*.a' -x '*.elf' -x '.*' -x '*.order' -x '*.ko' -x '*.mod.c' -x '*.o' -x '*.cmd' linux-2.6.26/net/bluetooth/hci_conn.c linux-2.6.26+mh3+fch/net/bluetooth/hci_conn.c
--- linux-2.6.26/net/bluetooth/hci_conn.c 2008-09-01 17:06:22.000000000 +0200
+++ linux-2.6.26+mh3+fch/net/bluetooth/hci_conn.c 2008-09-01 16:16:33.000000000 +0200
@@ -214,6 +214,7 @@
conn->state = BT_OPEN;

conn->power_save = 1;
+ conn->force_active_mode = 0;

switch (type) {
case ACL_LINK:
@@ -464,7 +465,10 @@
if (test_bit(HCI_RAW, &hdev->flags))
return;

- if (conn->mode != HCI_CM_SNIFF || !conn->power_save)
+ if (conn->mode != HCI_CM_SNIFF)
+ goto timer;
+
+ if (!conn->power_save && !conn->force_active_mode)
goto timer;

if (!test_and_set_bit(HCI_CONN_MODE_CHANGE_PEND, &conn->pend)) {
diff -ru -x '*.so' -x '*.lds' -x '*.a' -x '*.elf' -x '.*' -x '*.order' -x '*.ko' -x '*.mod.c' -x '*.o' -x '*.cmd' linux-2.6.26/net/bluetooth/l2cap.c linux-2.6.26+mh3+fch/net/bluetooth/l2cap.c
--- linux-2.6.26/net/bluetooth/l2cap.c 2008-09-01 17:06:22.000000000 +0200
+++ linux-2.6.26+mh3+fch/net/bluetooth/l2cap.c 2008-09-01 17:18:23.000000000 +0200
@@ -1133,6 +1133,20 @@
l2cap_pi(sk)->link_mode = opt;
break;

+ case L2CAP_FORCE_ACTIVE_MODE:
+ if (sk->sk_state != BT_CONNECTED) {
+ err = -ENOTCONN;
+ break;
+ }
+
+ if (get_user(opt, (u32 __user *) optval)) {
+ err = -EFAULT;
+ break;
+ }
+
+ l2cap_pi(sk)->conn->hcon->force_active_mode = opt;
+ break;
+
default:
err = -ENOPROTOOPT;
break;
@@ -1189,6 +1203,17 @@

break;

+ case L2CAP_FORCE_ACTIVE_MODE:
+ if (sk->sk_state != BT_CONNECTED) {
+ err = -ENOTCONN;
+ break;
+ }
+
+ if (put_user(l2cap_pi(sk)->conn->hcon->force_active_mode,
+ (u32 __user *) optval))
+ err = -EFAULT;
+ break;
+
default:
err = -ENOPROTOOPT;
break;