www.pudn.com > sdio-2.6.18-full.rar > sdio_bt_os.c


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@file: sdio_bt_os.c

@abstract: Linux implementation module for SDIO Bluetooth Function driver

#notes: includes module load and unload functions

@notice: Copyright (c), 2004-2006 Atheros Communications, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation;
 *
 *  Software distributed under the License is distributed on an "AS
 *  IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 *  implied. See the License for the specific language governing
 *  rights and limitations under the License.
 *
 *  Portions o this code were developed with information supplied from the
 *  SD Card Association Simplified Specifications. The following conditions and disclaimers may apply:
 *
 *   The following conditions apply to the release of the SD simplified specification (“Simplified
 *   Specification”) by the SD Card Association. The Simplified Specification is a subset of the complete
 *   SD Specification which is owned by the SD Card Association. This Simplified Specification is provided
 *   on a non-confidential basis subject to the disclaimers below. Any implementation of the Simplified
 *   Specification may require a license from the SD Card Association or other third parties.
 *   Disclaimers:
 *   The information contained in the Simplified Specification is presented only as a standard
 *   specification for SD Cards and SD Host/Ancillary products and is provided "AS-IS" without any
 *   representations or warranties of any kind. No responsibility is assumed by the SD Card Association for
 *   any damages, any infringements of patents or other right of the SD Card Association or any third
 *   parties, which may result from its use. No license is granted by implication, estoppel or otherwise
 *   under any patent or other rights of the SD Card Association or any third party. Nothing herein shall
 *   be construed as an obligation by the SD Card Association to disclose or distribute any technical
 *   information, know-how or other confidential information to any third party.
 *
 *
 *  The initial developers of the original code are Seung Yi and Paul Lever
 *
 *  sdio@atheros.com
 *
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
/* debug level for this module*/
#define DBG_DECLARE 3;
#include 
#include 
#include 
#include 
#include "sdio_bt.h"
#include 
#include 
#include 

#define DESCRIPTION "SDIO Bluetooth Function Driver"
#define AUTHOR "Atheros Communications, Inc."

/* debug print parameter */
module_param(debuglevel, int, 0644);
MODULE_PARM_DESC(debuglevel, "debuglevel 0-7, controls debug prints");

static INT blockfix = 0;
module_param(blockfix, int, 0644);
MODULE_PARM_DESC(blockfix, "HCI packet block fix");

static INT sdrequests = 8;
module_param(sdrequests, int, 0644);
MODULE_PARM_DESC(sdrequests, "HCI SDRequest list size");

static int bt_open(struct hci_dev *hdev);
static int bt_close(struct hci_dev *hdev);
static int bt_send_frame(struct sk_buff *skb);
static int bt_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg);
static int bt_flush(struct hci_dev *hdev);
static void bt_destruct(struct hci_dev *hdev);
static BOOL Probe(PSDFUNCTION pFunction, PSDDEVICE pDevice);
static void Remove(PSDFUNCTION pFunction, PSDDEVICE pDevice);
static void CleanupInstance(PBT_FUNCTION_CONTEXT  pFunctionContext,
                            PBT_HCI_INSTANCE      pHci);

/* devices we support, null terminated */
static SD_PNP_INFO Ids[] = {
   {.SDIO_FunctionClass = 0x02}, /* SDIO-BLUETOOTH SDIO standard interface code */
   {.SDIO_FunctionClass = 0x03},
    {}
};

/* driver data */
static BT_FUNCTION_CONTEXT FunctionContext = {
    .Function.Version = CT_SDIO_STACK_VERSION_CODE,
    .Function.pName    = "sdio_bluetooth",
    .Function.MaxDevices = 1,
    .Function.NumDevices = 0,
    .Function.pIds     = Ids,
    .Function.pProbe   = Probe,
    .Function.pRemove  = Remove,
    .Function.pSuspend = NULL,
    .Function.pResume  = NULL,
    .Function.pWake    = NULL,
    .Function.pContext = &FunctionContext,
};

void OSFreeSDRequest(PBT_HCI_INSTANCE pHci, PSDREQUEST pReq)
{
    spin_lock(&pHci->Config.RequestListLock);
    SDListAdd(&pHci->Config.RequestList, &pReq->SDList);
    spin_unlock(&pHci->Config.RequestListLock);
}

PSDREQUEST OSAllocSDRequest(PBT_HCI_INSTANCE pHci)
{
    PSDREQUEST pReq = NULL;
    PSDLIST    pItem;

    spin_lock(&pHci->Config.RequestListLock);
    do {
            /* check the list */
        pItem = SDListRemoveItemFromHead(&pHci->Config.RequestList);
        if (NULL == pItem) {
            break;
        }
        pReq = CONTAINING_STRUCT(pItem, SDREQUEST, SDList);
    } while (FALSE);

    spin_unlock(&pHci->Config.RequestListLock);

    return pReq;
}

/* allocate a receive packet  */
PSDBT_HCI_PACKET OSAllocHCIRcvPacket(PBT_HCI_INSTANCE pHci,
                                     UINT32           HCIPacketLength,
                                     UINT8            Type)
{
    PSDBT_HCI_PACKET pPacket;

        /* allocate a buffer */
    pPacket = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC);
    if (pPacket != NULL) {
        pPacket->dev = (PVOID)pHci->Config.pHciDev;
    }
    return pPacket;
}

void OSFreeHciRcvPacket(PBT_HCI_INSTANCE pHci, PSDBT_HCI_PACKET pPkt)
{
    kfree_skb(pPkt);
}

/*
 * Indicate that a HCI packet was received
*/
SDIO_STATUS OSIndicateHCIPacketReceived(PBT_HCI_INSTANCE pHci,
                                        PSDBT_HCI_PACKET pPacket,
                                        UINT32           HCIPacketLength,
                                        UINT8            ServiceID)
{
    UINT8   btType;

    switch (ServiceID) {
        case SDIO_BT_TYPE_A_HCI_ACL:
            btType = HCI_ACLDATA_PKT;
            break;
        case SDIO_BT_TYPE_A_HCI_SCO:
            btType = HCI_SCODATA_PKT;
            break;
        case SDIO_BT_TYPE_A_HCI_EVT:
            btType = HCI_EVENT_PKT;
            break;
        default:
            DBG_ASSERT(FALSE);
            return SDIO_STATUS_ERROR;
    }
        /* set the final type */
    pPacket->pkt_type = btType;
        /* adjust packet length to what the caller copied to the buffer */
    skb_put(pPacket,HCIPacketLength);
        /* pass receive packet up the stack */
    if (hci_recv_frame(pPacket) != 0) {
        DBG_PRINT(SDDBG_ERROR, ("SDIO Bluetooth Function: hci_recv_frame failed \n"));
    } else {
        DBG_PRINT(SDBT_DBG_RECEIVE, ("SDIO Bluetooth Function: Indicated RCV of type:%d, Length:%d \n",
                                     btType,HCIPacketLength));
    }
    return SDIO_STATUS_SUCCESS;
}

/*
 * bt_open - open a handle to the device
*/
static int bt_open(struct hci_dev *hdev)
{

    DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: bt_open - enter - x\n"));

    set_bit(HCI_RUNNING, &hdev->flags);
    set_bit(HCI_UP, &hdev->flags);
    set_bit(HCI_INIT, &hdev->flags);
    ((PBT_HCI_INSTANCE)hdev->driver_data)->Config.PktFlush = FALSE;

    return 0;
}

/*
 * bt_close - close handle to the device
*/
static int bt_close(struct hci_dev *hdev)
{
    DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: bt_close - enter\n"));

    clear_bit(HCI_RUNNING, &hdev->flags);
    return 0;
}


void OSIndicateHCIPacketTransmitDone(PBT_HCI_INSTANCE pHci,
                                     SDIO_STATUS      status)
{
    PSDBT_HCI_PACKET pPacket;

    pPacket = pHci->pCurrentTxPacket;
    DBG_ASSERT(pPacket != NULL);

    spin_lock(&pHci->Config.TxListLock);
    pHci->pCurrentTxPacket = NULL;
    if (!pHci->Config.PktFlush) {
            /* dequeue HCI packet */
        pHci->pCurrentTxPacket = __skb_dequeue(&pHci->Config.TxList);
        spin_unlock(&pHci->Config.TxListLock);
        if (pHci->pCurrentTxPacket != NULL) {
                /* start next */
            SendHciPacket(pHci);
        }
    } else {
        spin_unlock(&pHci->Config.TxListLock);
        DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth, cleanup in progress \n"));
    }
        /* free the one that completed */
    kfree_skb(pPacket);
}



/*
 * bt_send_frame - send data frames
*/
static int bt_send_frame(struct sk_buff *skb)
{
    SDIO_STATUS status;
    struct hci_dev *hdev = (struct hci_dev *) skb->dev;
    PBT_HCI_INSTANCE pHci;
    UINT8            serviceID;
    UINT8            *pTemp;

    if (!hdev) {
        DBG_PRINT(SDDBG_WARN, ("SDIO Bluetooth Function: bt_send_frame - no device\n"));
        return -ENODEV;
    }

    if (!test_bit(HCI_RUNNING, &hdev->flags)) {
        DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: bt_send_frame - not open\n"));
        return -EBUSY;
    }

    pHci = (PBT_HCI_INSTANCE)hdev->driver_data;

    if (pHci->Config.PktFlush) {
        DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: bt_send_frame - flush in progress..\n"));
        return -EBUSY;
    }

    switch (skb->pkt_type) {
        case HCI_COMMAND_PKT:
            serviceID = SDIO_BT_TYPE_A_HCI_CMD;
            hdev->stat.cmd_tx++;
            break;

        case HCI_ACLDATA_PKT:
            serviceID = SDIO_BT_TYPE_A_HCI_ACL;
            hdev->stat.acl_tx++;
            break;

        case HCI_SCODATA_PKT:
            serviceID = SDIO_BT_TYPE_A_HCI_SCO;
            hdev->stat.sco_tx++;
            break;
        default:
            kfree_skb(skb);
            return 0;
    }
    if (DBG_GET_DEBUG_LEVEL() >= SDBT_DBG_TRANSMIT) {
        SDLIB_PrintBuffer(SDBTHCI_GET_PKT_BUFFER(skb),SDBTHCI_GET_PKT_LENGTH(skb),
                "Linux BT HCI Packet Dump");
        if (skb->pkt_type == HCI_COMMAND_PKT) {
            pTemp = SDBTHCI_GET_PKT_BUFFER(skb);
            DBG_PRINT(SDBT_DBG_TRANSMIT, ("SDIO BT HCI Command: OCF:0x%4.4X, OGF:0x%2.2X \n",
                    (INT)pTemp[0] | (((INT)(pTemp[1] & 0x03)) << 8), pTemp[1] >> 2 ));
        }
    }

        /* BT HCI packets have 8 bytes of header space, push on 4 bytes for the header
         * this bumps up the "len" field */
    pTemp = (PUINT8)skb_push(skb, SDIO_BT_TRANSPORT_HEADER_LENGTH);
        /* set the header */
    SDIO_BT_SET_HEADER(pTemp,
                       serviceID,
                       SDBTHCI_GET_PKT_LENGTH(skb));


    DBG_PRINT(SDBT_DBG_TRANSMIT, ("SDIO Bluetooth Function: bt_send_frame (hci:0x%X) Packet:0x%X \n",
                                  (INT)pHci, (INT)skb));
    DBG_PRINT(SDBT_DBG_TRANSMIT, ("SDIO BT Send ServiceID:%d, Total Length:%d Bytes \n",
                                  serviceID,SDBTHCI_GET_PKT_LENGTH(skb)));

    spin_lock(&pHci->Config.TxListLock);
    status = SDIO_STATUS_PENDING;
    if (pHci->pCurrentTxPacket != NULL) {
            /* queue HCI packet */
        __skb_queue_tail(&pHci->Config.TxList,skb);
        spin_unlock(&pHci->Config.TxListLock);
        DBG_PRINT(SDBT_DBG_TRANSMIT, ("SDIO BT Send , Packet Queued \n"));
    } else {
        pHci->pCurrentTxPacket = skb;
        spin_unlock(&pHci->Config.TxListLock);
        status = SendHciPacket(pHci);
    }

    if (!SDIO_SUCCESS(status)) {
        return  SDIOErrorToOSError(status);
    }
    return 0;
}

/*
 * bt_ioctl - ioctl processing
*/
static int bt_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg)
{
    DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: bt_ioctl - enter\n"));
    return -ENOIOCTLCMD;
}

/*
 * bt_flush - flush outstandingbpackets
*/
static int bt_flush(struct hci_dev *hdev)
{
    PSDBT_HCI_PACKET pPkt;
    PBT_HCI_INSTANCE pHci;

    DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: bt_flush - enter\n"));

    pHci = (PBT_HCI_INSTANCE)hdev->driver_data;
    pHci->Config.PktFlush = TRUE;

    spin_lock(&pHci->Config.TxListLock);
        /* cleanup the queue */
    while (1) {
        pPkt = __skb_dequeue(&pHci->Config.TxList);
        if (pPkt != NULL) {
            kfree_skb(pPkt);
        } else {
            break;
        }

    }
    if (pHci->pCurrentTxPacket != NULL) {
        DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: Outstanding HCI packet:0x%X \n",
        (INT)pHci->pCurrentTxPacket));
    }
    spin_unlock(&pHci->Config.TxListLock);

    return 0;
}


/*
 * bt_destruct -
*/
static void bt_destruct(struct hci_dev *hdev)
{
    DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: bt_destruct - enter\n"));
    /* currently only supporting a single statically assigned device, nothing to do here */
}

/*
 * Probe - a device potentially for us
*/
static BOOL Probe(PSDFUNCTION pFunction, PSDDEVICE pDevice) {
    PBT_FUNCTION_CONTEXT pFunctionContext =
                                (PBT_FUNCTION_CONTEXT)pFunction->pContext;
    SYSTEM_STATUS err = 0;
    BOOL          okay = FALSE;
    struct hci_dev *pHciDev = NULL;
    PBT_HCI_INSTANCE pNewHci = NULL;
    INT i;
    PSDREQUEST pReq;

    DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: Probe - enter\n"));

    /* make sure this is a device we can handle */
    if ((pDevice->pId[0].SDIO_FunctionClass == 0x02) ||
        (pDevice->pId[0].SDIO_FunctionClass == 0x03)) {
        DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: Probe - card matched (0x%X/0x%X/0x%X)\n",
                                pDevice->pId[0].SDIO_ManufacturerID,
                                pDevice->pId[0].SDIO_ManufacturerCode,
                                pDevice->pId[0].SDIO_FunctionNo));
    } else {
        DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: Probe - not our card (0x%X/0x%X/0x%X)\n",
                                pDevice->pId[0].SDIO_ManufacturerID,
                                pDevice->pId[0].SDIO_ManufacturerCode,
                                pDevice->pId[0].SDIO_FunctionNo));
        return FALSE;
    }

    do {

        pNewHci = CreateHciInstance(pFunctionContext, pDevice);
        if (NULL == pNewHci) {
            break;
        }

        if (blockfix) {
            pNewHci->BlockTransferFix = TRUE;
        } else {
            pNewHci->BlockTransferFix = FALSE;
        }

        skb_queue_head_init(&pNewHci->Config.TxList);
        spin_lock_init(&pNewHci->Config.TxListLock);
        spin_lock_init(&pNewHci->Config.RequestListLock);
        SDLIST_INIT(&pNewHci->Config.RequestList);

            /* allocate bus requests for block transfers */
        for (i = 0; i < sdrequests; i++) {
            pReq = SDDeviceAllocRequest(pDevice);
            if (NULL == pReq) {
                break;
            }
                /* add it to our list */
            OSFreeSDRequest(pNewHci, pReq);
        }

            /* allocate a BT HCI struct for this device */
        pHciDev = hci_alloc_dev();
        if (NULL == pHciDev) {
            DBG_PRINT(SDDBG_ERROR, ("SDIO Bluetooth Function: Probe - failed to allocate bt struct.\n"));
            break;
        }
        SET_HCIDEV_DEV(pHciDev, SD_GET_OS_DEVICE(pDevice));

        pNewHci->Config.pHciDev = pHciDev;
             /* add this instance to our list */
        if (!SDIO_SUCCESS(AddHciInstance(pFunctionContext,pNewHci))) {
            DBG_PRINT(SDDBG_ERROR, ("SDIO Bluetooth : failed to add instance to list \n"));
            break;
        }

        pHciDev->type = HCI_VHCI; /* we don't really have a type assigned ????*/
        pHciDev->driver_data = pNewHci;
        pHciDev->open     = bt_open;
        pHciDev->close    = bt_close;
        pHciDev->send     = bt_send_frame;
        pHciDev->ioctl    = bt_ioctl;
        pHciDev->flush    = bt_flush;
        pHciDev->destruct = bt_destruct;
        pHciDev->owner = THIS_MODULE;

            /* mark that we are registered */
        pNewHci->Config.HciRegistered = TRUE;
        if ((err = hci_register_dev(pHciDev)) < 0) {
            DBG_PRINT(SDDBG_ERROR, ("SDIO Bluetooth Function: Probe - can't register with bluetooth %d\n",
                                    err));
            pNewHci->Config.HciRegistered = FALSE;
            break;
        }

        okay = TRUE;
    } while (FALSE);

    if (!okay) {
        if (pNewHci != NULL) {
            CleanupInstance(pFunctionContext, pNewHci);
        }
    }

    return okay;
}

static void CleanupInstance(PBT_FUNCTION_CONTEXT  pFunctionContext,
                            PBT_HCI_INSTANCE      pHci)
{
    int err;
    PSDREQUEST pReq;

    DBG_PRINT(SDDBG_TRACE, ("+SDIO Bluetooth CleanupInstance \n"));

    if (pHci->Config.pHciDev != NULL) {
        if (pHci->Config.HciRegistered) {
                /* first unregister */
            if ((err = hci_unregister_dev(pHci->Config.pHciDev)) < 0) {
                DBG_PRINT(SDDBG_ERROR, ("SDIO Bluetooth Function: Remove - can't unregister with bluetooth %d\n",
                                        err));
            } else {
                DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: Remove - HCI Instance:0x%X, unregistered\n",
                                       (INT)pHci));
            }

            if (pHci->pCurrentTxPacket != NULL) {
                /* TODO fix this with polling or an event */
                OSSleep(2000);
            }
            DBG_ASSERT(pHci->pCurrentTxPacket == NULL);
            KernelFree(pHci->Config.pHciDev);
        }
    }

        /* cleanup list */
    while (1) {
        pReq = OSAllocSDRequest(pHci);
        if (NULL == pReq) {
            break;
        }
        SDDeviceFreeRequest(pHci->pDevice,pReq);
    }
        /* remove this instance */
    DeleteHciInstance(pFunctionContext, pHci);
    DBG_PRINT(SDDBG_TRACE, ("-SDIO Bluetooth CleanupInstance \n"));
}

/*
 * Remove - our device is being removed
*/
static void Remove(PSDFUNCTION pFunction, PSDDEVICE pDevice)
{
    PBT_HCI_INSTANCE    pHci;
    PBT_FUNCTION_CONTEXT pFunctionContext =
                          (PBT_FUNCTION_CONTEXT)pFunction->pContext;

    DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: Remove - enter\n"));

    pHci =  FindHciInstance(pFunctionContext,pDevice);
    if (pHci != NULL) {
        DBG_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: Removing instance: 0x%X From Remove() \n",
                                (INT)pHci));
        CleanupInstance(pFunctionContext, pHci);
    } else {
        DBG_PRINT(SDDBG_ERROR, ("SDIO Bluetooth Function: could not find matching instance! \n"));
    }
}


/*
 * module init
*/
static int __init sdio_bt_init(void) {
    SDIO_STATUS status;

    REL_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: enter\n"));

    SDLIST_INIT(&FunctionContext.InstanceList);
    status = SemaphoreInitialize(&FunctionContext.InstanceSem, 1);
    if (!SDIO_SUCCESS(status)) {
        return SDIOErrorToOSError(status);
    }
    /* register with bus driver core */
    return SDIOErrorToOSError(SDIO_RegisterFunction(&FunctionContext.Function));
}

/*
 * module cleanup
*/
static void __exit sdio_bt_cleanup(void) {
    REL_PRINT(SDDBG_TRACE, ("SDIO Bluetooth Function: exit\n"));
    SDIO_UnregisterFunction(&FunctionContext.Function);
    SemaphoreDelete(&FunctionContext.InstanceSem);
}


MODULE_LICENSE("GPL");
MODULE_DESCRIPTION(DESCRIPTION);
MODULE_AUTHOR(AUTHOR);
module_init(sdio_bt_init);
module_exit(sdio_bt_cleanup);