Remaining part of the USB-BT driver. Events are commands are tested and working. ACLcode is untested and needs review. Lacks some general headers to be able to compile it, as I am still considering making some org. changes. Commiting for possible reviews or suggestions.

git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@23034 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Oliver Ruiz Dorantes
2007-11-30 19:59:29 +00:00
parent d89505dc24
commit 90e2260736
9 changed files with 1791 additions and 0 deletions
@@ -0,0 +1,33 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#ifndef _H2CFG_H_
#define _H2CFG_H_
/* TODO: move exclusive header for drivers*/
#define BT_DRIVER_SUPPORTS_CMD 1
#define BT_DRIVER_SUPPORTS_EVT 1
#define BT_DRIVER_SUPPORTS_ESCO 0
#define BT_DRIVER_SUPPORTS_SCO 0
#define BT_DRIVER_SUPPORTS_ACL 0
#define BT_DRIVER_RXCOVERAGE (BT_DRIVER_SUPPORTS_EVT+BT_DRIVER_SUPPORTS_ACL+BT_DRIVER_SUPPORTS_SCO+BT_DRIVER_SUPPORTS_ESCO)
#define BT_DRIVER_TXCOVERAGE (BT_DRIVER_SUPPORTS_CMD+BT_DRIVER_SUPPORTS_ACL+BT_DRIVER_SUPPORTS_SCO+BT_DRIVER_SUPPORTS_ESCO)
#if BT_DRIVER_RXCOVERAGE<1 || BT_DRIVER_TXCOVERAGE<1
#error incomplete Bluetooth driver Commands and Events should be implemented
#endif
////////////////////////////////////
#define BT_SURVIVE_WITHOUT_HCI
#define BT_SURVIVE_WITHOUT_NET_BUFFERS
//#define BT_IOCTLS_PASS_SIZE
#endif
@@ -0,0 +1,824 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <KernelExport.h>
#include <ByteOrder.h>
#include <Drivers.h>
#include <USB_spec.h>
#include <USB.h>
#include <net/net_buffer.h>
#include "snet_buffer.h"
#include <bluetooth_util.h>
#include <btHCI.h>
#define BT_DEBUG_THIS_MODULE
#include <btDebug.h>
#include "h2generic.h"
#include "h2transactions.h"
#include "h2util.h"
#include "h2cfg.h"
int32 api_version = B_CUR_DRIVER_API_VERSION;
/* Modules */
static char* usb_name = B_USB_MODULE_NAME;
static char* hci_name = B_BT_HCI_MODULE_NAME;
usb_module_info *usb = NULL;
bt_hci_module_info *hci = NULL;
struct net_buffer_module_info *nb = NULL;
/* Driver Global data */
static char *publish_names[MAX_BT_GENERIC_USB_DEVICES];
int32 dev_count = 0; /* number of connected devices */
static bt_usb_dev* bt_usb_devices[MAX_BT_GENERIC_USB_DEVICES];
sem_id dev_table_sem = -1; /* sem to synchronize access to device table */
usb_support_descriptor supported_devices[] =
{
/* Generic Bluetooth USB device */
/* Class, SubClass, and Protocol codes that describe a Bluetooth device */
{ UDCLASS_WIRELESS, UDSUBCLASS_RF, UDPROTO_BLUETOOTH , 0 , 0 },
/* Generic devices */
/* Broadcom BCM2035 */
{ 0, 0, 0, 0x0a5c, 0x200a },
{ 0, 0, 0, 0x0a5c, 0x2009 },
/* Devices taken from the linux Driver */
/* AVM BlueFRITZ! USB v2.0 */
{ 0, 0, 0, 0x057c , 0x3800 },
/* Bluetooth Ultraport Module from IBM */
{ 0, 0, 0, 0x04bf , 0x030a },
/* ALPS Modules with non-standard id */
{ 0, 0, 0, 0x044e , 0x3001 },
{ 0, 0, 0, 0x044e , 0x3002 },
/* Ericsson with non-standard id */
{ 0, 0, 0, 0x0bdb , 0x1002 }
};
/* add a device to the list of connected devices */
static bt_usb_dev*
spawn_device(const usb_device* usb_dev)
{
int32 i;
status_t err = B_OK;
bt_usb_dev* new_bt_dev = NULL;
flowf("add_device()\n");
/* 16 usb dongles... u are unsane */
if (dev_count >= MAX_BT_GENERIC_USB_DEVICES) {
flowf("device table full\n");
goto exit;
}
/* try the allocation */
new_bt_dev = (bt_usb_dev*)malloc(sizeof(bt_usb_dev));
if ( new_bt_dev == NULL ) {
flowf("no memoery allocating\n");
goto exit;
}
memset(new_bt_dev, 0, sizeof(bt_usb_dev) );
/* We will need this sem for some flow control */
new_bt_dev->cmd_complete = create_sem(1, ID "cmd_complete");
if (new_bt_dev->cmd_complete < 0) {
err = new_bt_dev->cmd_complete;
goto bail0;
}
/* and this for something else */
new_bt_dev->lock = create_sem(1, ID "lock");
if (new_bt_dev->lock < 0) {
err = new_bt_dev->lock;
goto bail1;
}
/* find a free slot and fill out the name */
acquire_sem(dev_table_sem);
for (i = 0; i < MAX_BT_GENERIC_USB_DEVICES; i++) {
if (bt_usb_devices[i] == NULL) {
bt_usb_devices[i] = new_bt_dev;
sprintf(new_bt_dev->name, "%s/%ld", DEVICE_PATH, i);
new_bt_dev->num = i;
debugf("added device %p %ld %s\n", bt_usb_devices[i] ,new_bt_dev->num,new_bt_dev->name);
break;
}
}
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
/* In the case we cannot us */
if (bt_usb_devices[i] != new_bt_dev) {
flowf("Device could not be added\n");
goto bail2;
}
new_bt_dev->dev = usb_dev;
/* TODO am i actually gonna use this? */
new_bt_dev->open_count = 0;
dev_count++;
return new_bt_dev;
bail2:
delete_sem(new_bt_dev->lock);
bail1:
delete_sem(new_bt_dev->cmd_complete);
bail0:
free(new_bt_dev);
exit:
return new_bt_dev;
}
/* remove a device from the list of connected devices */
static void
kill_device(bt_usb_dev* dev)
{
uint16 i;
debugf("remove_device(%p)\n", dev);
delete_sem(dev->lock);
delete_sem(dev->cmd_complete);
free(dev);
dev_count--;
}
bt_usb_dev*
fetch_device(bt_usb_dev* dev, hci_id hid)
{
int i;
debugf("(%p)\n", dev);
acquire_sem(dev_table_sem);
if (dev != NULL)
for (i = 0; i < MAX_BT_GENERIC_USB_DEVICES; i++) {
/* somehow the device is still around */
if (bt_usb_devices[i] == dev) {
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
return bt_usb_devices[i];
}
}
else
for (i = 0; i < MAX_BT_GENERIC_USB_DEVICES; i++) {
/* somehow the device is still around */
if (bt_usb_devices[i] != NULL && bt_usb_devices[i]->hdev == hid) {
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
return bt_usb_devices[i];
}
}
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
return NULL;
}
#if 0
#pragma mark -
#endif
static bt_hci_transport bt_usb_hooks =
{
NULL,
NULL,
NULL,
NULL,
H2,
"H2 Bluetooth Device"
};
/* called by USB Manager when device is added to the USB */
static status_t
device_added(const usb_device* dev, void** cookie)
{
const usb_interface_info* interface;
const usb_device_descriptor* desc;
const usb_configuration_info* config;
const usb_interface_info* uif;
const usb_endpoint_info* ep;
status_t err = B_ERROR;
bt_usb_dev* new_bt_dev = spawn_device(dev);
int e, i;
debugf("device_added(%ld, %p)\n", dev, new_bt_dev);
if (new_bt_dev == NULL) {
flowf("Couldn't allocate device record.\n");
err = ENOMEM;
goto bail_no_mem;
}
/* we only have 1 configuration number 0 */
config = usb->get_nth_configuration(dev, 0);
//dump_usb_configuration_info(config);
if (config == NULL) {
flowf("couldn't get default config.\n");
err = B_ERROR;
goto bail;
}
debugf("found %ld alt interfaces.\n", config->interface->alt_count);
/* set first interface */
interface = &config->interface->alt[0];
err = usb->set_alt_interface(new_bt_dev->dev, interface);
if (err != B_OK) {
debugf("set_alt_interface() returned %ld.\n", err);
goto bail;
}
/* call set_configuration() only after calling set_alt_interface()*/
err = usb->set_configuration(dev, config);
if (err != B_OK) {
debugf("set_configuration() returned %ld.\n", err);
goto bail;
}
/* Place to find out whats our concrete device and set up some special info to our driver */
/* TODO: if this code increases too much reconsider this implementation*/
desc = usb->get_device_descriptor(dev);
if ( desc->vendor_id == 0x0a5c && desc->product_id == 0x200a && desc->product_id == 0x2009) {
// Tecom Device VENDOR_ID 0x0a5c PRODUCT_ID 0x2035
new_bt_dev->driver_info = BT_WILL_NEED_A_RESET | BT_SCO_NOT_WORKING;
}
/*
else if ( desc->vendor_id == YOUR_VENDOR_HERE && desc->product_id == YOUR_PRODUCT_HERE ) {
YOUR_SPECIAL_FLAGS_HERE
}
*/
if (new_bt_dev->driver_info & BT_IGNORE_THIS_DEVICE){
err = ENODEV;
goto bail;
}
// security check
if (config->interface->active->descr->interface_number > 0){
debugf("Strange condition happened %d\n", config->interface->active->descr->interface_number);
err = B_ERROR;
goto bail;
}
debugf("Found %ld interfaces. Expected 3\n", config->interface_count);
/* Find endpoints that we need */
uif = config->interface->active;
for (e = 0; e < uif->descr->num_endpoints; e++) {
ep = &uif->endpoint[e];
switch (ep->descr->attributes & USB_ENDPOINT_ATTR_MASK)
{
case USB_ENDPOINT_ATTR_INTERRUPT:
if (ep->descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)
{
new_bt_dev->intr_in_ep = ep;
new_bt_dev->max_packet_size_intr_in = ep->descr->max_packet_size;
flowf("INT in\n");
} else
{
;
flowf("INT out\n");
}
break;
case USB_ENDPOINT_ATTR_BULK:
if (ep->descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN)
{
new_bt_dev->bulk_in_ep = ep;
new_bt_dev->max_packet_size_bulk_in = ep->descr->max_packet_size;;
flowf("BULK int\n");
} else
{
new_bt_dev->bulk_out_ep = ep;
new_bt_dev->max_packet_size_bulk_out = ep->descr->max_packet_size;;
flowf("BULK out\n");
}
break;
}
}
if (!new_bt_dev->bulk_in_ep || !new_bt_dev->bulk_out_ep || !new_bt_dev->intr_in_ep) {
flowf("Minimal # endpoints for BT not found\n");
goto bail;
}
// Look into the devices suported to understand this
if (new_bt_dev->driver_info & BT_DIGIANSWER)
new_bt_dev->ctrl_req = USB_TYPE_VENDOR;
else
new_bt_dev->ctrl_req = USB_TYPE_CLASS;
new_bt_dev->connected = true;
/* set the cookie that will be passed to other USB
hook functions (currently device_removed() is the only other) */
*cookie = new_bt_dev;
debugf("Ok %p\n",bt_usb_devices[0]);
return B_OK;
bail:
kill_device(new_bt_dev);
bail_no_mem:
*cookie = NULL;
done:
return err;
}
/* called by USB Manager when device is removed from the USB */
static status_t
device_removed(void* cookie)
{
int32 i;
void* item;
bt_usb_dev* bdev = (bt_usb_dev*) fetch_device(cookie, 0);
debugf("device_removed(%p)\n", bdev);
if (bdev == NULL) {
flowf("Weird condition...\n");
return B_ERROR;
}
// TODO: Consider some other place
// TX
for (i = 0; i < BT_DRIVER_TXCOVERAGE; i++) {
if (i = BT_COMMAND)
while ((item = list_remove_head_item(&bdev->nbuffersTx[i])) != NULL) {
snb_free(item);
}
else
while ((item = list_remove_head_item(&bdev->nbuffersTx[i])) != NULL) {
nb_destroy(item);
}
}
// RX
for (i = 0; i < BT_DRIVER_RXCOVERAGE; i++) {
nb_destroy(bdev->nbufferRx[i]);
}
snb_free(bdev->eventRx);
purge_room(&bdev->eventRoom);
purge_room(&bdev->aclRoom);
// TODO: Consider some other place
if (hci != NULL)
hci->UnregisterDriver(bdev->hdev);
bdev->connected = false;
/* TODO: maybe we still need this struct for close and free hooks */
kill_device(bdev);
return B_OK;
}
static usb_notify_hooks notify_hooks =
{
&device_added,
&device_removed
};
#if 0
#pragma mark -
#endif
/* implements the POSIX open() */
static status_t
device_open(const char *name, uint32 flags, void **cookie)
{
status_t err = ENODEV;
bt_usb_dev* bdev = NULL;
hci_id hdev;
int i;
flowf("device_open()\n");
acquire_sem(dev_table_sem);
for (i = 0; i < MAX_BT_GENERIC_USB_DEVICES; i++) {
if (bt_usb_devices[i] && !strcmp(name, bt_usb_devices[i]->name)) {
bdev = bt_usb_devices[i];
break;
}
}
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
if (bdev == NULL) {
flowf("Device not found in the open list!");
*cookie = NULL;
return B_ERROR;
}
acquire_sem(bdev->lock);
// Set HCI_RUNNING
if ( TEST_AND_SET(&bdev->state, RUNNING) ) {
flowf("dev already running! - reOpened device!\n");
return B_ERROR;
}
// TX structures
for (i = 0; i < BT_DRIVER_TXCOVERAGE; i++) {
list_init(&bdev->nbuffersTx[i]);
bdev->nbuffersPendingTx[i] = 0;
}
// RX structures
bdev->eventRx = NULL;
for (i = 0; i < BT_DRIVER_RXCOVERAGE; i++) {
bdev->nbufferRx[i] = NULL;
}
// dumping the USB frames
init_room(&bdev->eventRoom);
init_room(&bdev->aclRoom);
//Init_room(new_bt_dev->scoRoom);
list_init(&bdev->snetBufferRecycleTrash);
// Allocate set and register the HCI device
if (hci != NULL) {
// TODO: Fill the transport descriptor
hci->RegisterDriver(&bt_usb_hooks, &hdev, (void*)bdev);
if ( err != B_OK )
{
flowf("Impossible to register a hci device.\n");
hdev = bdev->num; /* XXX: Lets try to go on*/
}
}
else {
hdev = bdev->num;
}
bdev->hdev = hdev;
// H: set the special flags
// EVENTS
err = submit_rx_event(bdev);
if (err != B_OK)
goto unrun;
#if BT_DRIVER_SUPPORTS_ACL
// ACL
for (i = 0; i < MAX_ACL_IN_WINDOW; i++) {
err = submit_rx_acl(bdev);
if (err != B_OK && i == 0 )
goto unrun;
}
#endif
#if BT_DRIVER_SUPPORTS_SCO
// TODO: SCO / eSCO
#endif
*cookie = bdev;
release_sem(bdev->lock);
return B_OK;
unrun:
CLEAR_BIT(bdev->state, RUNNING); // Set the flaq in the HCI world
flowf("Queuing failed device stops running\n");
return err;
}
/* called when a client calls POSIX close() on the driver, but I/O
** requests may still be pending */
static status_t
device_close(void *cookie)
{
bt_usb_dev* bdev = (bt_usb_dev*)cookie;
if (bdev == NULL)
panic("bad cookie");
debugf("device_close() called on %s\n", DEVICE_PATH, bdev->hdev );
if (!TEST_AND_CLEAR(&bdev->state, RUNNING) ) {
flowf("Device wasnt running!!!\n");
}
flowf("Stopping device and cancelling queues...\n");
if ( bdev->intr_in_ep != NULL ) {
usb->cancel_queued_transfers(bdev->intr_in_ep->handle);
} else {
flowf("Cancelling impossible EVENTS\n");
}
if (bdev->bulk_in_ep!=NULL) {
usb->cancel_queued_transfers(bdev->bulk_in_ep->handle);
} else {
flowf("Cancelling impossible ACL in\n");
}
if (bdev->bulk_out_ep!=NULL) {
usb->cancel_queued_transfers(bdev->bulk_out_ep->handle);
} else {
flowf("Cancelling impossible ACL out\n");
}
// TODO: Kill if its not connected?
return B_OK;
}
/* called after device_close(), when all pending I/O requests have
* returned */
static status_t
device_free (void *cookie)
{
status_t err = B_OK;
bt_usb_dev* dev = (bt_usb_dev*)cookie;
debugf("device_free() called on \"%s %ld\"\n",DEVICE_PATH, dev->num);
if (--dev->open_count == 0) {
/* GotoLowPower */
// interesting .....
}
else {
/* The last client has closed, and the device is no longer
connected, so remove it from the list. */
}
// TODO: Kill if its not connected?
return err;
}
/* implements the POSIX ioctl() */
static status_t
device_control(void *cookie, uint32 msg, void *params, size_t size)
{
status_t err = B_ERROR;
bt_usb_dev* dev = (bt_usb_dev*)cookie;
snet_buffer* snbuf;
TOUCH(size);
debugf("ioctl() opcode %ld size %ld.\n", msg, size);
if (dev == NULL) {
flowf("Bad cookie\n");
return B_BAD_VALUE;
}
if (params == NULL) {
flowf("Invalid pointer control\n");
return B_BAD_VALUE;
}
acquire_sem(dev->lock);
switch (msg) {
case ISSUE_BT_COMMAND:
#ifdef BT_IOCTLS_PASS_SIZE
if (size == 0) {
flowf("Invalid size control\n");
err = B_BAD_VALUE;
break;
}
#else
size = (*((size_t*)params));
((size_t*)params)++;
#endif
// TODO: Reuse from some TXcompleted queue
snbuf = snb_create(size);
snb_put(snbuf, params, size);
err = send_command(dev->hdev, snbuf);
break;
case ISSUE_STATICS:
memcpy(params, &dev->stat, sizeof(bt_hci_statistics));
err = B_OK;
break;
default:
debugf("Invalid opcode %ld.\n", msg);
err = B_DEV_INVALID_IOCTL;
break;
}
release_sem(dev->lock);
return err;
}
/* implements the POSIX read() */
static status_t
device_read(void *cookie, off_t pos, void *buf, size_t *count)
{
debugf("Reading... pos = %ld || count = %ld\n", pos, *count);
*count = 0;
return B_OK;
}
/* implements the POSIX write() */
static status_t
device_write(void *cookie, off_t pos, const void *buf, size_t *count)
{
flowf("device_write()\n");
return B_ERROR;
}
#if 0
#pragma mark -
#endif
/* called each time the driver is loaded by the kernel */
status_t
init_driver(void)
{
int j;
flowf("init_driver()\n");
// HCI MODULE INITS
if (get_module(hci_name,(module_info**)&hci) != B_OK) {
debugf("cannot get module \"%s\"\n", hci_name);
#ifndef BT_SURVIVE_WITHOUT_HCI
return B_ERROR;
#endif
}
debugf("hci module at %p\n", hci);
// USB MODULE INITS
if (get_module(usb_name,(module_info**)&usb) != B_OK) {
debugf("cannot get module \"%s\"\n", usb_name);
goto err_release;
}
debugf("usb module at %p\n", usb);
if (get_module(NET_BUFFER_MODULE_NAME,(module_info**)&nb) != B_OK) {
debugf("cannot get module \"%s\"\n", NET_BUFFER_MODULE_NAME);
#ifndef BT_SURVIVE_WITHOUT_NET_BUFFERS
goto err_release;
#endif
}
debugf("nb module at %p\n", nb);
// GENERAL INITS
dev_table_sem = create_sem(1, ID "dev_table_lock");
if (dev_table_sem < 0) {
goto err;
}
for (j = 0; j < MAX_BT_GENERIC_USB_DEVICES; j++) {
bt_usb_devices[j] = NULL;
}
/* After here device_added and publish devices hooks are called
be carefull USB devs */
usb->register_driver(DEVICE_NAME, supported_devices, 1, NULL);
usb->install_notify(DEVICE_NAME, &notify_hooks);
return B_OK;
err: // Releasing
put_module(usb_name);
err_release:
put_module(hci_name);
return B_ERROR;
}
/* called just before the kernel unloads the driver */
void
uninit_driver(void)
{
int32 j;
flowf("uninit_driver()\n");
for (j = 0; j < MAX_BT_GENERIC_USB_DEVICES; j++) {
if (publish_names[j] != NULL)
free(publish_names[j]);
if (bt_usb_devices[j] != NULL) {
// if (connected_dev != NULL) {
// debugf("Device %p still exists.\n", connected_dev);
// }
kill_device(bt_usb_devices[j]);
bt_usb_devices[j] = NULL;
}
}
usb->uninstall_notify(DEVICE_NAME);
usb->register_driver(DEVICE_NAME, supported_devices, 1, NULL);
/* Releasing modules */
put_module(usb_name);
put_module(hci_name);
// TODO: netbuffers
delete_sem(dev_table_sem);
}
const char**
publish_devices(void)
{
int32 j;
int32 i = 0;
char* str;
flowf("publish_devices()\n");
for (j = 0; j < MAX_BT_GENERIC_USB_DEVICES; j++) {
if (publish_names[j]) {
free(publish_names[j]);
publish_names[j] = NULL;
}
}
acquire_sem(dev_table_sem);
for (j = 0; j < MAX_BT_GENERIC_USB_DEVICES; j++)
{
if (bt_usb_devices[j] != NULL && bt_usb_devices[j]->connected)
{
str = strdup(bt_usb_devices[j]->name);
if (str) {
publish_names[i++] = str;
debugf("publishing %s\n", bt_usb_devices[j]->name);
}
}
}
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
publish_names[i] = NULL;
debugf("published %ld devices\n", i);
// TODO: this method might make better memory use
// dev_names = (char**)malloc(sizeof (char*) * (dev_count+1));
// if (dev_names) {
// for (i = 0; i < MAX_NUM_DEVS; i++) {
// if ((dev != NULL) &&
// (dev_names[i] = (char*)malloc(strlen(DEVICE_PATH)+2/* num + \n */))) {
// sprintf(dev_names[i], "%s%ld", DEVICE_PATH, dev->num);
// debugf("publishing \"%s\"\n", dev_names[i]);
// }
// }
return (const char**)publish_names;
}
static device_hooks hooks = {
device_open,
device_close,
device_free,
device_control,
device_read,
device_write,
NULL,
NULL,
NULL,
NULL
};
device_hooks*
find_device(const char* name)
{
debugf("find_device(%s)\n", name);
return &hooks;
}
@@ -0,0 +1,107 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#ifndef _H2GENERIC_H_
#define _H2GENERIC_H_
#include <OS.h>
#include <USB.h>
#include <util/list.h>
#include <btHCI.h>
#include <net/net_buffer.h>
#include "h2cfg.h"
#include "snet_buffer.h"
/* USB definitions for the generic device*/
#define UDCLASS_WIRELESS 0xe0
#define UDSUBCLASS_RF 0x01
#define UDPROTO_BLUETOOTH 0x01
#define TRANSPORT_NAME "h2"
#define DEVICE_NAME "generic"
#define DEVICE_PATH "bus/bluetooth/" TRANSPORT_NAME "/" DEVICE_NAME
#define ID DEVICE_NAME ": " /* prefix for debug messages */
#define USB_TYPE_CLASS (0x01 << 5) /// Check if it is in some other header
#define USB_TYPE_VENDOR (0x02 << 5)
// Expecting nobody is gonna have 16 USB-BT dongles connected in their system
#define MAX_BT_GENERIC_USB_DEVICES 16
extern usb_module_info *usb;
extern bt_hci_module_info *hci;
extern struct net_buffer_module_info *nb;
#define MAX_COMMAND_WINDOW 1
#define MAX_ACL_OUT_WINDOW 4
#define MAX_ACL_IN_WINDOW 1
#define MAX_NUM_QUEUED_PACKETS 1
#define NUM_BUFFERS 1
typedef struct bt_usb_dev bt_usb_dev;
struct bt_usb_dev {
const usb_device* dev; /* opaque handle */
hci_id hdev; /* HCI device id*/
char name[B_OS_NAME_LENGTH];
bool connected; /* is the device plugged into the USB? */
int32 open_count; /* number of clients of the device */
int32 num; /* instance number of the device */
sem_id lock; /* synchronize access to the device */
sem_id cmd_complete; /* To synchronize completitions */
size_t actual_len; /* length of data returned by command */
status_t cmd_status; /* result of command */
uint8 ctrl_req;
uint8 driver_info;
uint32 state;
bt_hci_statistics stat;
const usb_endpoint_info *bulk_in_ep;
uint16 max_packet_size_bulk_in;
const usb_endpoint_info *bulk_out_ep;
uint16 max_packet_size_bulk_out;
const usb_endpoint_info *intr_in_ep;
uint16 max_packet_size_intr_in;
#ifdef BLUETOOTH_SUPPORTS_SCO
const usb_endpoint_info *iso_in_ep;
const usb_endpoint_info *iso_out_ep;
#endif
/* This so called rooms, are for dumping the USB RX frames
and try to reuse the allocations. see util submodule */
struct list eventRoom;
struct list aclRoom;
// Tx buffers: net_buffers for BT_ACL and snet_buffers for BT_COMMAND
// in the same array
struct list nbuffersTx[BT_DRIVER_TXCOVERAGE];
uint32 nbuffersPendingTx[BT_DRIVER_TXCOVERAGE];
// Rx buffer
net_buffer* nbufferRx[BT_DRIVER_RXCOVERAGE]; /* Wasting 1 pointer for BT_EVENT */
snet_buffer* eventRx; /* <- which we hold here */
// for who ever needs preallocated buffers
struct list snetBufferRecycleTrash;
};
bt_usb_dev* fetch_device(bt_usb_dev* dev, hci_id hid);
#endif
@@ -0,0 +1,402 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#include "h2generic.h"
#include "h2transactions.h"
#include "h2upper.h"
#include "h2util.h"
#include <btHCI.h>
#include <btHCI_event.h>
#include <btHCI_acl.h>
#include <ByteOrder.h>
#define BT_DEBUG_THIS_MODULE
#include "btDebug.h"
#if 0
#pragma mark --- RX Complete ---
#endif
status_t
assembly_rx(bt_usb_dev* bdev, bt_packet_t type, void *data, int count)
{
net_buffer* nbuf;
snet_buffer* snbuf;
size_t currentPacketLen;
size_t expectedPacketLen;
bdev->stat.bytesRX += count;
while (count) {
if (type == BT_EVENT)
snbuf = bdev->eventRx;
else
nbuf = bdev->nbufferRx[type];
debugf("count %d %p %p\n",count, nbuf, nb);
if ( (type != BT_EVENT && nbuf == NULL) ||
(type == BT_EVENT && snb_completed(snbuf)) ) {
/* new buffer incoming */
switch (type) {
case BT_EVENT:
if (count >= HCI_EVENT_HDR_SIZE) {
struct hci_event_header* headerPkt = data;
expectedPacketLen = HCI_EVENT_HDR_SIZE + headerPkt->elen;
snbuf = bdev->eventRx = snb_fetch(&bdev->snetBufferRecycleTrash, expectedPacketLen);
} else {
flowf("EVENT frame corrupted\n");
return -EILSEQ;
}
break;
case BT_ACL:
if (count >= HCI_ACL_HDR_SIZE) {
struct hci_acl_header* headerPkt = data;
expectedPacketLen = HCI_ACL_HDR_SIZE + B_LENDIAN_TO_HOST_INT16(headerPkt->alen);
/* Create the buffer */
bdev->nbufferRx[type] = nbuf = nb->create(expectedPacketLen);
nbuf->protocol = type;
} else {
flowf("ACL frame corrupted\n");
return -EILSEQ;
}
break;
case BT_SCO:
break;
default:
panic("unkown packet type in assembly");
break;
}
#if 0
if (nb == NULL) {
port_id port;
/* Coded for test purpose only we should panic here */
debugf("net_buffers are not ready post manually %d/%d\n",count,expectedPacketLen);
port = find_port(BT_USERLAND_PORT_NAME);
if (port != B_NAME_NOT_FOUND) {
(void)write_port(port, PACK_HEADER_PORT(bdev->num,type), data, count);
return B_OK;
}
panic("Algorithm just ready to arrive here");
return B_ERROR;
}
#endif
currentPacketLen = expectedPacketLen;
}
else {
/* Continuation */
if (type == BT_EVENT)
currentPacketLen = get_expected_size(nbuf) - nbuf->size;
else
currentPacketLen = snb_remaining_to_put(snbuf);
}
currentPacketLen = min(currentPacketLen, count);
if (type == BT_EVENT)
snb_put(snbuf, data, currentPacketLen);
else
nb->append(nbuf, data, currentPacketLen);
/* Complete frame? */
if (type == BT_EVENT && snb_completed(snbuf)) {
flowf("Frame goes up!\n");
snbuf = bdev->eventRx = NULL;
post_packet_up(bdev, type, snbuf);
}
if (type != BT_EVENT && (get_expected_size(nbuf) - nbuf->size) == 0 ) {
flowf("Frame goes up!\n");
bdev->nbufferRx[type] = nbuf = NULL;
post_packet_up(bdev, type, nbuf);
}
/* in case in the pipe there is info about the next buffer ... */
count -= currentPacketLen;
data += currentPacketLen;
}
}
void
event_complete(void* cookie, uint32 status, void* data, uint32 actual_len)
{
bt_usb_dev* bdev = cookie;
status_t err;
/* TODO: check are we running? */
if (status != B_OK || actual_len == 0)
goto resubmit;
if ( assembly_rx(cookie, BT_EVENT, data, actual_len) == B_OK ) {
bdev->stat.successfulTX++;
} else {
bdev->stat.errorRX++;
}
resubmit:
err = usb->queue_interrupt(bdev->intr_in_ep->handle,
data, bdev->max_packet_size_intr_in ,
event_complete, bdev);
if (err != B_OK ) {
reuse_room(&bdev->eventRoom, data);
bdev->stat.rejectedRX++;
debugf("RX event resubmittion failed %s\n",strerror(err));
}
else {
bdev->stat.acceptedRX++;
}
}
void
acl_rx_complete(void* cookie, uint32 status, void* data, uint32 actual_len)
{
bt_usb_dev* bdev = cookie;
status_t err;
/* TODO: check are we running? */
if (status != B_OK || actual_len == 0)
goto resubmit;
if ( assembly_rx(cookie, BT_ACL, data, actual_len) == B_OK ) {
bdev->stat.successfulRX++;
} else {
bdev->stat.errorRX++;
}
resubmit:
err = usb->queue_bulk(bdev->bulk_in_ep->handle, data,
max(HCI_MAX_FRAME_SIZE,bdev->max_packet_size_bulk_in),
acl_rx_complete, bdev);
if (err != B_OK ) {
reuse_room(&bdev->aclRoom, data);
bdev->stat.rejectedRX++;
debugf("RX acl resubmittion failed %s\n",strerror(err));
}
else {
bdev->stat.acceptedRX++;
}
}
#if 0
#pragma mark --- RX ---
#endif
status_t
submit_rx_event(bt_usb_dev* bdev)
{
status_t err;
size_t size = bdev->max_packet_size_intr_in;
void* buf = alloc_room(&bdev->eventRoom, size);
if (buf == NULL)
return ENOMEM;
err = usb->queue_interrupt(bdev->intr_in_ep->handle,
buf, size ,
event_complete, bdev);
if (err != B_OK ) {
reuse_room(&bdev->eventRoom, buf);
bdev->stat.rejectedRX++;
}
else {
bdev->stat.acceptedRX++;
debugf("Accepted RX Event\n",bdev->stat.acceptedRX);
}
return err;
}
status_t
submit_rx_acl(bt_usb_dev* bdev)
{
status_t err;
size_t size = max(HCI_MAX_FRAME_SIZE,bdev->max_packet_size_bulk_in);
void* buf = alloc_room(&bdev->aclRoom, size);
if (buf == NULL)
return ENOMEM;
err = usb->queue_bulk(bdev->bulk_in_ep->handle, buf, size ,
acl_rx_complete, bdev);
if (err != B_OK ) {
reuse_room(&bdev->aclRoom, buf);
bdev->stat.rejectedRX++;
}
else {
bdev->stat.acceptedRX++;
}
return B_ERROR;
}
status_t
submit_rx_sco(bt_usb_dev* bdev)
{
/* not yet implemented */
return B_ERROR;
}
#if 0
#pragma mark --- TX Complete ---
#endif
void
command_complete(void* cookie, uint32 status, void* data, uint32 actual_len)
{
snet_buffer* snbuf = (snet_buffer*) cookie;
bt_usb_dev* bdev = snb_cookie(snbuf);
status_t err;
debugf("%d %02x:%02x:%02x:\n", actual_len, ((uint8*)data)[0],((uint8*)data)[1],((uint8*)data)[2]);
if (status != B_OK) {
bdev->stat.successfulTX++;
bdev->stat.bytesTX += actual_len;
}
else {
bdev->stat.errorTX++;
/* the packet has been lost */
/* too late to requeue it? */
}
snb_park(&bdev->snetBufferRecycleTrash, snbuf);
#ifdef BT_RESCHEDULING_AFTER_COMPLETITIONS
// TODO: check just the empty queues?
schedTxProcessing(bdev);
#endif
}
void
aclTxComplete(void* cookie, uint32 status, void* data, uint32 actual_len)
{
net_buffer* nbuf = (net_buffer*) cookie;
bt_usb_dev* bdev = GET_DEVICE(nbuf);
status_t err;
if (status != B_OK) {
bdev->stat.successfulTX++;
bdev->stat.bytesTX += actual_len;
}
else {
bdev->stat.errorTX++;
/* the packet has been lost */
/* too late to requeue it? */
}
nb_destroy(nbuf);
#ifdef BT_RESCHEDULING_AFTER_COMPLETITIONS
schedTxProcessing(bdev);
#endif
}
#if 0
#pragma mark --- TX ---
#endif
status_t
submit_tx_command(bt_usb_dev* bdev, snet_buffer* snbuf)
{
status_t err;
uint8 bRequestType = bdev->ctrl_req;
uint8 bRequest = 0;
uint16 wIndex = 0;
uint16 value = 0;
uint16 wLength = B_HOST_TO_LENDIAN_INT16(snb_size(snbuf));
/* set cookie */
snb_set_cookie(snbuf, bdev);
err = usb->queue_request(bdev->dev, bRequestType, bRequest,
value, wIndex, wLength,
snb_get(snbuf), wLength //???
,command_complete, snbuf);
if (err != B_OK ) {
bdev->stat.rejectedTX++;
}
else {
bdev->stat.acceptedTX++;
}
return err;
}
status_t
submit_tx_acl(bt_usb_dev* bdev, net_buffer* nbuf)
{
status_t err;
/* set cookie */
SET_DEVICE(nbuf,bdev->hdev);
err = usb->queue_bulk(bdev->bulk_out_ep->handle,
nb_get_whole_buffer(nbuf), nbuf->size,
aclTxComplete, nbuf);
if (err != B_OK ) {
bdev->stat.rejectedTX++;
}
else {
bdev->stat.acceptedTX++;
}
return err;
}
status_t
submit_tx_sco(bt_usb_dev* bdev)
{
/* not yet implemented */
return B_ERROR;
}
@@ -0,0 +1,21 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#ifndef _H2TRANSACTION_H_
#define _H2TRANSACTION_H_
#include "h2generic.h"
status_t submit_rx_event(bt_usb_dev* bdev);
status_t submit_rx_acl(bt_usb_dev* bdev);
status_t submit_rx_sco(bt_usb_dev* bdev);
status_t submit_tx_command(bt_usb_dev* bdev, snet_buffer* snbuf);
status_t submit_tx_acl(bt_usb_dev* bdev, net_buffer* nbuf);
status_t submit_tx_sco(bt_usb_dev* bdev);
#endif
@@ -0,0 +1,201 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#include "h2upper.h"
#include "h2transactions.h"
#define BT_DEBUG_THIS_MODULE
#include "btDebug.h"
#include <btHCITransport.h>
#include <bluetooth.h>
#include "snet_buffer.h"
/* TODO: split for commands and comunication(ACL&SCO) */
void
sched_tx_processing(bt_usb_dev* bdev)
{
net_buffer* nbuf;
snet_buffer* snbuf;
status_t err;
flowf("sched\n")
if (!TEST_AND_SET(&bdev->state, PROCESSING)) {
/* We are not processing in another thread so... START!! */
do {
/* Do while this bit is on... so someone should set it before we stop the iterations*/
CLEAR_BIT(bdev->state, SENDING);
/* check Commands*/
#ifdef EMPTY_COMMAND_QUEUE
while (!list_is_empty(&bdev->nbuffersTx[BT_COMMAND]) ) {
#else
if (!list_is_empty(&bdev->nbuffersTx[BT_COMMAND]) ) {
#endif
snbuf = list_remove_head_item(&bdev->nbuffersTx[BT_COMMAND]);
err = submit_tx_command(bdev, snbuf);
if (err != B_OK) {
/* re-head it*/
list_insert_item_before(&bdev->nbuffersTx[BT_COMMAND],
list_get_first_item(&bdev->nbuffersTx[BT_COMMAND]), snbuf);
}
}
/* check Acl */
#define EMPTY_ACL_QUEUE
#ifdef EMPTY_ACL_QUEUE
while (!list_is_empty(&bdev->nbuffersTx[BT_ACL]) ) {
#else
if (!list_is_empty(&bdev->nbuffersTx[BT_ACL]) ) {
#endif
nbuf = list_remove_head_item(&bdev->nbuffersTx[BT_ACL]);
err = submit_tx_acl(bdev, nbuf);
if (err != B_OK) {
/* re-head it*/
list_insert_item_before(&bdev->nbuffersTx[BT_ACL],
list_get_first_item(&bdev->nbuffersTx[BT_ACL]), nbuf);
}
}
if (!list_is_empty(&bdev->nbuffersTx[BT_SCO]) ) {
/* TODO to be implemented */
}
} while (GET_BIT(bdev->state, SENDING));
CLEAR_BIT(bdev->state, PROCESSING);
} else {
/* We are processing so MARK that we need to still go on with that ... */
SET_BIT(bdev->state, SENDING);
}
}
status_t
post_packet_up(bt_usb_dev* bdev, bt_packet_t type, void* buf)
{
status_t err;
port_id port;
if (hci == NULL) {
err = B_ERROR;
// ERROR but we will try to send if its a event!
if (type != BT_EVENT) {
err = B_ERROR;
} else {
snet_buffer* snbuf = (snet_buffer*)buf;
flowf("HCI not present, Posting to userland\n");
port = find_port(BT_USERLAND_PORT_NAME);
if (port != B_NAME_NOT_FOUND) {
err = write_port_etc(port, PACK_HEADER_PORT(bdev->num,type),
snb_get(snbuf), snb_size(snbuf), B_TIMEOUT, 1*1000*1000);
if (err != B_OK)
debugf("Error posting userland %s\n",strerror(err));
}
else {
flowf("ERROR:bluetooth_server not found for posting\n");
err = B_NAME_NOT_FOUND;
}
/* No need to free the buffer at allocation is gonna be reused */
}
}
else {
// TODO: Upper layer comunication
/* Not freeing because is being used by upper layers*/
}
return err;
}
status_t
send_packet(hci_id hid, bt_packet_t type, net_buffer* nbuf)
{
bt_usb_dev* bdev = fetch_device(NULL, hid);
status_t err = B_OK;
if (bdev == NULL)
return B_ERROR;
// TODO: check if device is actually ready for this
// TODO: Lock Device
if (nbuf != NULL) {
if (type != nbuf->protocol) // a bit strict maybe?
panic("Upper layer has not filled correctly a packet");
switch (type) {
case BT_COMMAND:
case BT_ACL:
case BT_SCO:
list_add_item(&bdev->nbuffersTx[type],nbuf);
bdev->nbuffersPendingTx[type]++;
break;
default:
debugf("Unkown packet type for sending %d\n",type);
// TODO: free the net_buffer -> no, allow upper layer
// handle it with the given error
err = B_BAD_VALUE;
break;
}
} else {
flowf("tx sched provoked");
}
// TODO: check if device is actually ready for this
// TODO: unLock device
/* sched in All cases even if nbuf is null (hidden way to provoke re-scheduling)*/
sched_tx_processing(bdev);
return err;
}
status_t
send_command(hci_id hid, snet_buffer* snbuf)
{
bt_usb_dev* bdev = fetch_device(NULL, hid);
status_t err = B_OK;
if (bdev == NULL)
return B_ERROR;
// TODO: check if device is actually ready for this
// TODO: mutex?
if (snbuf != NULL) {
list_add_item(&bdev->nbuffersTx[BT_COMMAND],snbuf);
bdev->nbuffersPendingTx[BT_COMMAND]++;
} else {
err = B_BAD_VALUE;
flowf("tx sched provoked");
}
// TODO: check if device is actually ready for this
// TODO: mutex?
/* sched in All cases even if nbuf is null (hidden way to provoke re-scheduling)*/
sched_tx_processing(bdev);
return err;
}
@@ -0,0 +1,22 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#ifndef _H2UPPER_H_
#define _H2UPPER_H_
#include <util/list.h>
#include "h2generic.h"
#define PACK_HEADER_PORT(x,y) (x<<24|y<<16)
status_t post_packet_up(bt_usb_dev* bdev, bt_packet_t type, void* buf);
status_t send_packet(hci_id hid, bt_packet_t type, net_buffer* nbuf);
void sched_tx_processing(bt_usb_dev* bdev);
#endif
@@ -0,0 +1,147 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#include "h2upper.h"
#include "h2util.h"
#include "h2transactions.h"
#define BT_DEBUG_THIS_MODULE
#include "btDebug.h"
#include <btHCITransport.h>
#include <btHCI_acl.h>
#include <btHCI_command.h>
#include <btHCI_event.h>
void*
nb_get_whole_buffer(net_buffer* nbuf)
{
void* conPointer;
status_t err;
/* the job could be already done */
// !!! it could be trash from other upper protocols...
if (nbuf->COOKIEFIELD != NULL)
return (void*)nbuf->COOKIEFIELD;
if (nb == NULL)
goto fail;
err = nb->direct_access(nbuf, 0, nbuf->size, &conPointer);
if (err != B_OK) {
/* pity, we are gonna need a realocation */
nbuf->COOKIEFIELD = (uint32) malloc(nbuf->size);
if (nbuf->COOKIEFIELD == NULL)
goto fail;
err = nb->write(nbuf, 0, (void*) nbuf->COOKIEFIELD, nbuf->size);
if (err != B_OK)
goto free;
conPointer = (void*)nbuf->COOKIEFIELD;
}
return conPointer;
free:
free(nbuf->COOKIEFIELD);
fail:
return NULL;
}
void
nb_destroy(net_buffer* nbuf)
{
/* Free possible allocated */
if (nbuf->COOKIEFIELD != NULL)
free((void*)nbuf->COOKIEFIELD);
// TODO check for survivers...
if (nb != NULL)
nb->free(nbuf);
}
/* Check from the completition if the queue is empty */
size_t
get_expected_size(net_buffer* nbuf)
{
if (nbuf == NULL)
panic("Analizing NULL packet");
switch (nbuf->protocol) {
case BT_ACL: {
struct hci_acl_header* header = nb_get_whole_buffer(nbuf);
return header->alen;
}
case BT_COMMAND: {
struct hci_command_header* header = nb_get_whole_buffer(nbuf);
return header->clen;
}
case BT_EVENT: {
struct hci_event_header* header = nb_get_whole_buffer(nbuf);
return header->elen;
}
default:
break;
}
}
#if 0
#pragma mark - room util -
#endif
inline void
init_room(struct list* l)
{
list_init(l);
}
void*
alloc_room(struct list* l, size_t size)
{
void* item = list_get_first_item(l);
if (item == NULL)
item = (void*) malloc(size);
return item;
}
inline void
reuse_room(struct list* l, void* room)
{
list_add_item(l, room);
}
void
purge_room(struct list* l)
{
void* item;
while ((item = list_remove_head_item(l)) != NULL) {
free(item);
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
*
* All rights reserved. Distributed under the terms of the MIT License.
*
*/
#ifndef _H2UTIL_H_
#define _H2UTIL_H_
#include <util/list.h>
#include "h2generic.h"
/* net buffer utils for ACL, to be reviwed */
#define DEVICEFIELD type
#define SET_DEVICE(nbuf,hid) (nbuf->DEVICEFIELD=(nbuf->DEVICEFIELD&0xFFF0)|(hid&0xF))
#define GET_DEVICE(nbuf) fetch_device(NULL,(nbuf->DEVICEFIELD&0x0F))
#define COOKIEFIELD flags
void* nb_get_whole_buffer(net_buffer* nbuf);
void nb_destroy(net_buffer* nbuf);
size_t get_expected_size(net_buffer* nbuf);
/* Room utils */
inline void init_room(struct list* l);
void* alloc_room(struct list* l, size_t size);
inline void reuse_room(struct list* l, void* room);
void purge_room(struct list* l);
/* list utils */
#define list_purge(x) purge_room(x)
#endif