* Copyright 2007 Oliver Ruiz Dorantes, oliver.ruiz.dorantes_at_gmail.com
* Copyright 2008 Mika Lindqvist, monni1995_at_gmail.com
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "h2generic.h"
#include <kernel.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <KernelExport.h>
#include <ByteOrder.h>
#include <Drivers.h>
#include <btModules.h>
#include "snet_buffer.h"
#include "h2cfg.h"
#include "h2debug.h"
#include "h2transactions.h"
#include "h2util.h"
int32 api_version = B_CUR_DRIVER_API_VERSION;
static const char* usb_name = B_USB_MODULE_NAME;
static const char* hci_name = BT_HCI_MODULE_NAME;
static const char* btDevices_name = BT_HCI_MODULE_NAME;
usb_module_info* usb = NULL;
bt_hci_module_info* hci = NULL;
struct bt_hci_module_info* btDevices = NULL;
struct net_buffer_module_info* nb = NULL;
struct bluetooth_core_data_module_info* btCoreData = NULL;
static char* publish_names[MAX_BT_GENERIC_USB_DEVICES];
int32 dev_count = 0;
static bt_usb_dev* bt_usb_devices[MAX_BT_GENERIC_USB_DEVICES];
sem_id dev_table_sem = -1;
status_t submit_nbuffer(hci_id hid, net_buffer* nbuf);
usb_support_descriptor supported_devices[] = {
{ UDCLASS_WIRELESS, UDSUBCLASS_RF, UDPROTO_BLUETOOTH, 0, 0 },
{ 0, 0, 0, 0x0a5c, 0x200a },
{ 0, 0, 0, 0x0a5c, 0x2009 },
{ 0, 0, 0, 0x0e8d, 0x763f },
{ 0, 0, 0, 0x0a5c, 0x21e1 },
{ 0, 0, 0, 0x05ac, 0x8213 },
{ 0, 0, 0, 0x05ac, 0x8215 },
{ 0, 0, 0, 0x05ac, 0x8218 },
{ 0, 0, 0, 0x05ac, 0x821b },
{ 0, 0, 0, 0x05ac, 0x821f },
{ 0, 0, 0, 0x05ac, 0x821a },
{ 0, 0, 0, 0x05ac, 0x8281 },
{ 0, 0, 0, 0x057c, 0x3800 },
{ 0, 0, 0, 0x04bf, 0x030a },
{ 0, 0, 0, 0x044e, 0x3001 },
{ 0, 0, 0, 0x044e, 0x3002 },
{ 0, 0, 0, 0x0bdb, 0x1002 },
{ 0, 0, 0, 0x0c10, 0x0000 },
{ 0, 0, 0, 0x413c, 0x8197 },
};
static bt_usb_dev*
spawn_device(usb_device usb_dev)
{
CALLED();
int32 i;
status_t err = B_OK;
bt_usb_dev* new_bt_dev = NULL;
if (dev_count >= MAX_BT_GENERIC_USB_DEVICES) {
ERROR("%s: Device table full\n", __func__);
goto exit;
}
new_bt_dev = (bt_usb_dev*)malloc(sizeof(bt_usb_dev));
if (new_bt_dev == NULL) {
ERROR("%s: Unable to malloc new bt device\n", __func__);
goto exit;
}
memset(new_bt_dev, 0, sizeof(bt_usb_dev));
new_bt_dev->cmd_complete = create_sem(1,
BLUETOOTH_DEVICE_DEVFS_NAME "cmd_complete");
if (new_bt_dev->cmd_complete < 0) {
err = new_bt_dev->cmd_complete;
ERROR("%s: Unable to create bt device command semaphore: %" B_PRId32 "\n", __func__, err);
goto bail0;
}
new_bt_dev->lock = create_sem(1, BLUETOOTH_DEVICE_DEVFS_NAME "lock");
if (new_bt_dev->lock < 0) {
err = new_bt_dev->lock;
ERROR("%s: Unable to create bt device lock semaphore: %" B_PRId32 "\n", __func__, err);
goto bail1;
}
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/%" B_PRId32,
BLUETOOTH_DEVICE_PATH, i);
new_bt_dev->num = i;
TRACE("%s: added device %p %" B_PRId32 " %s\n", __func__,
bt_usb_devices[i], new_bt_dev->num, new_bt_dev->name);
break;
}
}
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
if (i >= MAX_BT_GENERIC_USB_DEVICES) {
ERROR("%s: Device could not be added\n", __func__);
goto bail2;
}
new_bt_dev->dev = usb_dev;
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);
new_bt_dev = NULL;
exit:
return new_bt_dev;
}
static void
kill_device(bt_usb_dev* bdev)
{
if (bdev != NULL) {
TRACE("%s: (%p)\n", __func__, bdev);
delete_sem(bdev->lock);
delete_sem(bdev->cmd_complete);
bt_usb_devices[bdev->num] = NULL;
free(bdev);
dev_count--;
}
}
bt_usb_dev*
fetch_device(bt_usb_dev* dev, hci_id hid)
{
int i;
acquire_sem(dev_table_sem);
if (dev != NULL) {
for (i = 0; i < MAX_BT_GENERIC_USB_DEVICES; i++) {
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++) {
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 status_t
device_added(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;
TRACE("%s: device_added(%p)\n", __func__, new_bt_dev);
if (new_bt_dev == NULL) {
ERROR("%s: Couldn't allocate device record.\n", __func__);
err = ENOMEM;
goto bail_no_mem;
}
config = usb->get_nth_configuration(dev, 0);
if (config == NULL) {
ERROR("%s: Couldn't get default USB config.\n", __func__);
err = B_ERROR;
goto bail;
}
TRACE("%s: found %" B_PRIuSIZE " alt interfaces.\n", __func__,
config->interface->alt_count);
interface = &config->interface->alt[0];
err = usb->set_alt_interface(new_bt_dev->dev, interface);
if (err != B_OK) {
ERROR("%s: set_alt_interface() error.\n", __func__);
goto bail;
}
err = usb->set_configuration(dev, config);
if (err != B_OK) {
ERROR("%s: set_configuration() error.\n", __func__);
goto bail;
}
desc = usb->get_device_descriptor(dev);
if (desc->vendor_id == 0x0a5c
&& (desc->product_id == 0x200a
|| desc->product_id == 0x2009
|| desc->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;
}
if (config->interface->active->descr->interface_number > 0) {
ERROR("%s: Strange condition happened %d\n", __func__,
config->interface->active->descr->interface_number);
err = B_ERROR;
goto bail;
}
TRACE("%s: Found %" B_PRIuSIZE " interfaces. Expected 3\n", __func__,
config->interface_count);
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;
TRACE("%s: INT in\n", __func__);
} else {
TRACE("%s: INT out\n", __func__);
}
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;
TRACE("%s: BULK int\n", __func__);
} else {
new_bt_dev->bulk_out_ep = ep;
new_bt_dev->max_packet_size_bulk_out
= ep->descr->max_packet_size;
TRACE("%s: BULK out\n", __func__);
}
break;
}
}
if (!new_bt_dev->bulk_in_ep || !new_bt_dev->bulk_out_ep
|| !new_bt_dev->intr_in_ep) {
ERROR("%s: Minimal # endpoints for BT not found\n", __func__);
goto bail;
}
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;
*cookie = new_bt_dev;
TRACE("%s: Ok %p\n", __func__, new_bt_dev);
return B_OK;
bail:
kill_device(new_bt_dev);
bail_no_mem:
*cookie = NULL;
return err;
}
static status_t
device_removed(void* cookie)
{
bt_usb_dev* bdev = fetch_device((bt_usb_dev*)cookie, 0);
TRACE("%s: device_removed(%p)\n", __func__, bdev);
if (bdev == NULL) {
ERROR("%s: Device not present in driver.\n", __func__);
return B_ERROR;
}
if (!TEST_AND_CLEAR(&bdev->state, RUNNING))
ERROR("%s: wasnt running?\n", __func__);
TRACE("%s: Cancelling queues...\n", __func__);
if (bdev->intr_in_ep != NULL)
usb->cancel_queued_transfers(bdev->intr_in_ep->handle);
if (bdev->bulk_in_ep != NULL)
usb->cancel_queued_transfers(bdev->bulk_in_ep->handle);
if (bdev->bulk_out_ep != NULL)
usb->cancel_queued_transfers(bdev->bulk_out_ep->handle);
bdev->connected = false;
return B_OK;
}
static bt_hci_transport_hooks bluetooth_hooks = {
NULL,
&submit_nbuffer,
&submit_nbuffer,
NULL,
NULL,
H2
};
static usb_notify_hooks notify_hooks = {
&device_added,
&device_removed
};
#if 0
#pragma mark -
#endif
status_t
submit_nbuffer(hci_id hid, net_buffer* nbuf)
{
bt_usb_dev* bdev = NULL;
bdev = fetch_device(NULL, hid);
TRACE("%s: index=%" B_PRId32 " nbuf=%p bdev=%p\n", __func__, hid,
nbuf, bdev);
if (bdev != NULL) {
switch (nbuf->protocol) {
case BT_COMMAND:
break;
case BT_ACL:
return submit_tx_acl(bdev, nbuf);
break;
default:
panic("submit_nbuffer: no protocol");
break;
}
}
return B_ERROR;
}
static status_t
device_open(const char* name, uint32 flags, void **cookie)
{
CALLED();
status_t err = ENODEV;
bt_usb_dev* bdev = NULL;
hci_id hdev;
int i;
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) {
ERROR("%s: Device not found in the open list!", __func__);
*cookie = NULL;
return B_ERROR;
}
if (TEST_AND_SET(&bdev->state, RUNNING)) {
ERROR("%s: dev already running! - reOpened device!\n", __func__);
return B_ERROR;
}
acquire_sem(bdev->lock);
for (i = 0; i < BT_DRIVER_TXCOVERAGE; i++) {
list_init(&bdev->nbuffersTx[i]);
bdev->nbuffersPendingTx[i] = 0;
}
bdev->eventRx = NULL;
for (i = 0; i < BT_DRIVER_RXCOVERAGE; i++) {
bdev->nbufferRx[i] = NULL;
}
init_room(&bdev->eventRoom);
init_room(&bdev->aclRoom);
list_init(&bdev->snetBufferRecycleTrash);
if (btDevices != NULL) {
bluetooth_device* ndev;
err = btDevices->RegisterDriver(&bluetooth_hooks, &ndev);
if (err == B_OK) {
bdev->hdev = hdev = ndev->index;
bdev->ndev = ndev;
} else {
hdev = bdev->num;
}
} else {
hdev = bdev->num;
}
bdev->hdev = hdev;
*cookie = bdev;
release_sem(bdev->lock);
return B_OK;
}
* requests may still be pending
*/
static status_t
device_close(void* cookie)
{
CALLED();
int32 i;
void* item;
bt_usb_dev* bdev = (bt_usb_dev*)cookie;
if (bdev == NULL)
panic("bad cookie");
if (bdev->connected == true) {
TRACE("%s: Cancelling queues...\n", __func__);
if (bdev->intr_in_ep != NULL)
usb->cancel_queued_transfers(bdev->intr_in_ep->handle);
if (bdev->bulk_in_ep!=NULL)
usb->cancel_queued_transfers(bdev->bulk_in_ep->handle);
if (bdev->bulk_out_ep!=NULL)
usb->cancel_queued_transfers(bdev->bulk_out_ep->handle);
}
for (i = 0; i < BT_DRIVER_TXCOVERAGE; i++) {
if (i == BT_COMMAND) {
while ((item = list_remove_head_item(&bdev->nbuffersTx[i])) != NULL)
snb_free((snet_buffer*)item);
} else {
while ((item = list_remove_head_item(&bdev->nbuffersTx[i])) != NULL)
nb_destroy((net_buffer*)item);
}
}
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);
if (btDevices != NULL)
btDevices->UnregisterDriver(bdev->hdev);
if (TEST_AND_CLEAR(&bdev->state, RUNNING)) {
ERROR("%s: %s not running?\n", __func__, bdev->name);
return B_ERROR;
}
return B_OK;
}
static status_t
device_free(void* cookie)
{
CALLED();
status_t err = B_OK;
bt_usb_dev* bdev = (bt_usb_dev*)cookie;
if (!bdev->connected)
kill_device(bdev);
return err;
}
static status_t
device_control(void* cookie, uint32 msg, void* params, size_t size)
{
status_t err = B_ERROR;
bt_usb_dev* bdev = (bt_usb_dev*)cookie;
snet_buffer* snbuf;
#if BT_DRIVER_SUPPORTS_ACL
int32 i;
#endif
TOUCH(size);
TRACE("%s: ioctl() opcode %" B_PRId32 " size %" B_PRIuSIZE ".\n", __func__,
msg, size);
if (bdev == NULL) {
TRACE("%s: Bad cookie\n", __func__);
return B_BAD_VALUE;
}
if (params == NULL || !IS_USER_ADDRESS(params)) {
TRACE("%s: Invalid pointer control\n", __func__);
return B_BAD_VALUE;
}
acquire_sem(bdev->lock);
switch (msg) {
case ISSUE_BT_COMMAND: {
if (size == 0) {
TRACE("%s: Invalid size control\n", __func__);
err = B_BAD_VALUE;
break;
}
void* _params = alloca(size);
if (user_memcpy(_params, params, size) != B_OK)
return B_BAD_ADDRESS;
snbuf = snb_fetch(&bdev->snetBufferRecycleTrash, size);
snb_put(snbuf, _params, size);
err = submit_tx_command(bdev, snbuf);
TRACE("%s: command launched\n", __func__);
break;
}
case BT_UP:
err = submit_rx_event(bdev);
if (err != B_OK) {
bdev->state = CLEAR_BIT(bdev->state, ANCILLYANT);
ERROR("%s: Queuing failed device stops running\n", __func__);
break;
}
#if BT_DRIVER_SUPPORTS_ACL
for (i = 0; i < MAX_ACL_IN_WINDOW; i++) {
err = submit_rx_acl(bdev);
if (err != B_OK && i == 0) {
bdev->state = CLEAR_BIT(bdev->state, ANCILLYANT);
ERROR("%s: Queuing failed device stops running\n",
__func__);
break;
}
}
#endif
bdev->state = SET_BIT(bdev->state, RUNNING);
#if BT_DRIVER_SUPPORTS_SCO
#endif
ERROR("%s: Device online\n", __func__);
break;
case GET_STATS:
err = user_memcpy(params, &bdev->stat, sizeof(bt_hci_statistics));
break;
case GET_HCI_ID:
err = user_memcpy(params, &bdev->hdev, sizeof(hci_id));
break;
default:
ERROR("%s: Invalid opcode.\n", __func__);
err = B_DEV_INVALID_IOCTL;
break;
}
release_sem(bdev->lock);
return err;
}
static status_t
device_read(void* cookie, off_t pos, void* buffer, size_t* count)
{
TRACE("%s: Reading... count = %" B_PRIuSIZE "\n", __func__, *count);
*count = 0;
return B_OK;
}
static status_t
device_write(void* cookie, off_t pos, const void* buffer, size_t* count)
{
CALLED();
return B_ERROR;
}
#if 0
#pragma mark -
#endif
static int
dump_driver(int argc, char** argv)
{
int i;
snet_buffer* item = NULL;
for (i = 0; i < MAX_BT_GENERIC_USB_DEVICES; i++) {
if (bt_usb_devices[i] != NULL) {
kprintf("%s : \n", bt_usb_devices[i]->name);
kprintf("\taclroom = %d\teventroom = %d\tcommand & events =%d\n",
snb_packets(&bt_usb_devices[i]->eventRoom),
snb_packets(&bt_usb_devices[i]->aclRoom),
snb_packets(&bt_usb_devices[i]->snetBufferRecycleTrash));
while ((item = (snet_buffer*)list_get_next_item(
&bt_usb_devices[i]->snetBufferRecycleTrash, item)) != NULL)
snb_dump(item);
}
}
return 0;
}
status_t
init_driver(void)
{
CALLED();
int j;
if (get_module(BT_CORE_DATA_MODULE_NAME,
(module_info**)&btCoreData) != B_OK) {
ERROR("%s: cannot get module '%s'\n", __func__,
BT_CORE_DATA_MODULE_NAME);
return B_ERROR;
}
if (get_module(btDevices_name, (module_info**)&btDevices) != B_OK) {
ERROR("%s: cannot get module '%s'\n", __func__, btDevices_name);
goto err_release3;
}
if (get_module(hci_name, (module_info**)&hci) != B_OK) {
ERROR("%s: cannot get module '%s'\n", __func__, hci_name);
#ifndef BT_SURVIVE_WITHOUT_HCI
goto err_release2;
#endif
}
if (get_module(usb_name, (module_info**)&usb) != B_OK) {
ERROR("%s: cannot get module '%s'\n", __func__, usb_name);
goto err_release1;
}
if (get_module(NET_BUFFER_MODULE_NAME, (module_info**)&nb) != B_OK) {
ERROR("%s: cannot get module '%s'\n", __func__,
NET_BUFFER_MODULE_NAME);
#ifndef BT_SURVIVE_WITHOUT_NET_BUFFERS
goto err_release;
#endif
}
dev_table_sem = create_sem(1, BLUETOOTH_DEVICE_DEVFS_NAME "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;
}
usb->register_driver(BLUETOOTH_DEVICE_DEVFS_NAME, supported_devices, 1, NULL);
usb->install_notify(BLUETOOTH_DEVICE_DEVFS_NAME, ¬ify_hooks);
add_debugger_command("bth2generic", &dump_driver,
"Lists H2 Transport device info");
return B_OK;
err:
put_module(NET_BUFFER_MODULE_NAME);
err_release:
put_module(usb_name);
err_release1:
put_module(hci_name);
#ifndef BT_SURVIVE_WITHOUT_HCI
err_release2:
#endif
put_module(btDevices_name);
err_release3:
put_module(BT_CORE_DATA_MODULE_NAME);
return B_ERROR;
}
void
uninit_driver(void)
{
CALLED();
int32 j;
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) {
ERROR("%s: %s still present?\n", __func__, bt_usb_devices[j]->name);
kill_device(bt_usb_devices[j]);
}
}
usb->uninstall_notify(BLUETOOTH_DEVICE_DEVFS_NAME);
remove_debugger_command("bth2generic", &dump_driver);
put_module(usb_name);
put_module(hci_name);
delete_sem(dev_table_sem);
}
const char**
publish_devices(void)
{
CALLED();
int32 j;
int32 i = 0;
char* str;
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;
TRACE("%s: publishing %s\n", __func__, bt_usb_devices[j]->name);
}
}
}
release_sem_etc(dev_table_sem, 1, B_DO_NOT_RESCHEDULE);
publish_names[i] = NULL;
TRACE("%s: published %" B_PRId32 " devices\n", __func__, 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)
{
CALLED();
return &hooks;
}