Kernel panic with SCO socket - Debian kernel 2.6.15-8
Hi all,
I'm currently playing with a bluetooth dongle and a Motorola HS810 headset.
I wrote a sample program that mimics the establishment of a Headset Profile like connection. For now it just waits for user input, then sends the content of a PCM file over the SCO socket.
The thing basically works... exect after 1or 2 minutes playback, the program suddenly exits. Then i just have a few seconds left and *bang* . Kernel Panic :-(
The issue is 100% reproductible on my configuration :-(.
I attached the source of the program that makes my Linux crash... would eventually anybody have any idea of what goes wrong ??
Tip : here is the command line i use to start this program:
./hsplay 00:07:A4:4D:02:14 1 fallen-8kHz.wav 0
I also noticed that it crashes a lot faster when last parameter is 0...
Cheers,
Fabien
#include <getopt.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <regex.h>
#include <ctype.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sco.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
static int rfcomm_connect(bdaddr_t * dst, uint8_t channel)
{
struct sockaddr_rc addr;
int s;
if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, dst);
addr.rc_channel = channel;
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(s);
return -1;
}
return s;
}static int sco_connect(bdaddr_t * dst, uint16_t * handle,
uint16_t * mtu)
{
struct sockaddr_sco addr;
struct sco_conninfo conn;
struct sco_options opts;
int s;
unsigned int size;
if ((s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sco_family = AF_BLUETOOTH;
bacpy(&addr.sco_bdaddr, dst);
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(s);
return -1;
}
size = sizeof(conn);
if (getsockopt(s, SOL_SCO, SCO_CONNINFO, &conn, &size) < 0) {
close(s);
return -1;
}
size = sizeof(opts);
if (getsockopt(s, SOL_SCO, SCO_OPTIONS, &opts, &size) < 0) {
close(s);
return -1;
}
if (handle)
*handle = conn.hci_handle;
if (mtu)
*mtu = opts.mtu;
return s;
}#define PKT_SIZE 48
int main(int argc, char **argv)
{
bdaddr_t remote;
int rfcomm_socket;
int sco_socket;
char buff[256];
int res;
char *pcm_filename;
int pcm_fd;
int channel;
int delay;
if(argc != 5) {
printf("usage: hsplay bdaddr rfcomm_channel pcm_file delay\n");
printf("bdaddr = headet bd address\n");
printf("rfcomm_channel = rfcomm channel used for headset control\n");
printf("pcm_file = 8OOHz, 16 bits signed PCM file te read\n");
printf("delay = between two sco packets, 1500 usually works great\n");
exit(1);
}
str2ba(argv[1], &remote);
channel = atoi(argv[2]);
pcm_filename = argv[3];
delay = atoi(argv[4]);
rfcomm_socket = rfcomm_connect(&remote, channel);
assert(rfcomm_socket != -1);
res = write(rfcomm_socket, "RING", 4);
assert(res == 4);
memset(buff, 0, sizeof(buff));
printf("RFCOMM control channel opened, waiting for HS button push\n");
res = read(rfcomm_socket, buff, sizeof(buff));
assert(res > 0);
printf("Recv: %s\n", buff);
sco_socket = sco_connect(&remote, 0, 0);
assert(res > 0);
printf("SCO connected.\n");
pcm_fd = open(pcm_filename, 0);
assert(pcm_fd > 0);
lseek(pcm_fd, 44, SEEK_SET);
while((res = read(pcm_fd, buff, PKT_SIZE)) == PKT_SIZE)
{
res = send(sco_socket, buff, PKT_SIZE, 0);
if(res <= 0) {
perror("write");
exit(1);
}
if(delay > 0) {
usleep(delay);
}
}
return 0;
}Moin,
Am Sat, 18 Mar 2006 18:00:27 +0100 schrieb Fabien Chevalier -:
Quoted 2 lines
I wrote a sample program that mimics the establishment of a Headset Profile like connection.
Hehe, I recently did something similar (in Python, though) ...
Quoted 3 lines
The thing basically works... exect after 1or 2 minutes playback, the program suddenly exits.
Then i just have a few seconds left and *bang* . Kernel Panic :-(
... and had similar results. Just look at the memory consumption when this happens. Apparently all incoming SCO data is buffered in kernel memory for you to read, so you *must* read it, even if you only ever intend to send data.
I'd also suggest you simply use select() to wait for incoming data and then only send something when you received (and read!) something. This nicely gives the right timing without guessing the delay parameter.
For reference I've attached my headset emulator (to be used with pybluez): It discards incoming audio and sends a pure sine wave.
Signature · 4 lines
Henryk Plötz Grüße aus Berlin ~~~~~~~ Un-CDs, nein danke! http://www.heise.de/ct/cd-register/ ~~~~~~~ ~ Help Microsoft fight software piracy: Give Linux to a friend today! ~
#!/bin/env python
import bluetooth, select, math, struct
class Headset:
MAX_TRIES = 3
MAX_LINE_LENGTH = 100
FREQUENCY = 19.8
def __init__(self):
self.local_address = "00:80:98:34:38:9B"
self.connection = None
self.SCOserver = None
self.SCOconnection = None
self.unhandled = ""
self.peer = None
self.waiting_for_OK = None
self._audio_init = False
self.bind_RFCOMM()
def bind_RFCOMM(self):
tries = 0
self.serversock = bluetooth.BluetoothSocket(proto=bluetooth.RFCOMM)
while tries < Headset.MAX_TRIES:
tries = tries + 1
try:
port = bluetooth.get_available_port(bluetooth.RFCOMM) self.serversock.bind( (self.local_address, port) ) self.serversock.listen(1)
self.local_port = port
break
except bluetooth.BluetoothError, e:
if tries >= Headset.MAX_TRIES:
raise
bluetooth.advertise_service(self.serversock, "Python-Headset",
profiles = [bluetooth.HEADSET_PROFILE], service_classes = [bluetooth.HEADSET_CLASS])
print "Bound to '%s' on %s" % (self.local_address, self.local_port)
def bind_SCO(self):
self.SCOserver = bluetooth.BluetoothSocket(proto=bluetooth.SCO) self.SCOserver.bind( (self.local_address,) ) self.SCOserver.listen(1)
def close(self):
print "Closing"
try: self.SCOconnection.close()
except: pass
try: self.SCOserver.close()
except: pass
try: bluetooth.stop_advertising(self.serversock)
except: pass
try: self.serversock.close()
except: pass
try: self.connection.close()
except: pass
def run(self):
while True:
self.process_event()
def process_event(self, timeout = None):
rlist = [self.serversock, ]; wlist = []; xlist = [] if self.connection:
rlist.append(self.connection) xlist.append(self.connection)
if self.SCOserver:
rlist.append(self.SCOserver)
if self.SCOconnection:
rlist.append(self.SCOconnection)
rlist, wlist, xlist = select.select(rlist, wlist, xlist, timeout)
if self.serversock in rlist:
self.accept_connection()
if self.SCOserver in rlist:
self.accept_SCO()
if self.SCOconnection in rlist:
self.do_SCO_cycle()
if self.connection:
if self.connection in xlist:
print "X on connection"
if self.connection in rlist:
self.read_control()
def accept_connection(self):
conn, address = self.serversock.accept()
print "Have connection by", address
self.peer = address[0]
self.connection = conn
self.waiting_for_OK = True
self.write_control("AT+CKPD=200")
def accept_SCO(self):
conn, address = self.SCOserver.accept()
if address != self.peer:
print "Warning: unauthorized SCO connection by %s (!=%s). Closing." % (address, self.peer) conn.close()
else:
self.SCOconnection = conn
print "Have SCO connection"
def read_control(self):
try:
data = self.unhandled + self.connection.recv(65535)
except bluetooth.BluetoothError, e:
if str(e)[:5] == "(104,":
self.close_control()
return
else:
raise
line = None
if data[0] == "\r" and data[1] == "\n":
data = data[2:]
pos = None
for i in range(len(data)):
if i > 0 and data[i-1] == "\r" and data[i] == "\n":
pos = i+1
break
if pos is not None:
line = data[:pos-2]
data = data[pos:]
elif data[:3] == "AT+":
pos = None
for i in range(len(data)):
if data[i] == "\r":
pos = i
break
if pos is not None:
line = data[:pos]
data = data[pos+1:]
self.unhandled = data
if line is not None:
self.handle_command(line)
if len(self.unhandled) > Headset.MAX_LINE_LENGTH:
raise IOError("Maximum line length exceeded. Shouldn't happen.")
def write_control(self, data):
self.connection.send(data+"\r")
def handle_command(self, command):
if self.waiting_for_OK and command == "OK":
self.waiting_for_OK = False self.bind_SCO()
print "Established"
else:
print "Ignoring '%s'" % command
def do_SCO_cycle(self):
try:
data = self.SCOconnection.recv(1024)
except bluetooth.BluetoothError, e:
if str(e)[:5] == "(104,":
self.close_SCO()
return
else:
raise
if not hasattr(self, "_i"): self._i = 0
print "Have %s bytes SCO data (%i)" % (len(data), self._i) self._i = self._i + 1
response = []
while len(response) < len(data)/2:
response.append(self.nextaudio())
self.SCOconnection.send("".join(response))
def nextaudio(self):
if not self._audio_init:
self._x = 0.0
self._audio_init = True
sample = math.sin( math.radians( self._x ) ) * (1<<14) self._x = (self._x + Headset.FREQUENCY) % 360.0 return struct.pack("<h", sample)
def close_SCO(self):
self.SCOconnection.close()
self.SCOconnection = None
print "SCO channel closed"
def close_control(self):
if self.SCOconnection:
self.close_SCO()
self.SCOserver.close()
self.SCOserver = None
self.waiting_for_OK = None
self.connection.close()
self.connection = None
self.peer = None
print "Control channel closed"
if __name__ == "__main__":
headset = Headset()
try:
try:
headset.run()
except KeyboardInterrupt:
pass
finally:
headset.close()
Quoted 5 lines
Hehe, I recently did something similar (in Python, though) ...
Good to now i'm not alone playing with SCOs !!
Quoted 5 lines
Quoted 4 lines
The thing basically works... exect after 1or 2 minutes playback, the program suddenly exits. Then i just have a few seconds left and *bang* . Kernel Panic :-(
Quoted 11 lines
... and had similar results. Just look at the memory consumption when this happens. Apparently all incoming SCO data is buffered in kernel memory for you to read, so you *must* read it, even if you only ever intend to send data.
I'd also suggest you simply use
select()to wait for incoming data and then only send something when you received (and read!) something. This nicely gives the right timing without guessing the delay parameter.
Quite interesting is the way you solved the issue :-)
I'm gonna give a try to this select way of things :-)
By the way i just ran another test using hsplay. I basically send a SCO packet every second, which is obviously not enough to sustain the SCO packet flow.
Using hcidump i am able to see the SCO flow coming from the Headset.
... and no crash at all :-)
Which for me basically means that the bug is somewhere on the PC-->Heaset flow, where something must be handled the wrong way...
Cheers,
Fabien