root/src/add-ons/kernel/drivers/bluetooth/h2/h2generic/h2transactions.cpp
/*
 * 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 "h2transactions.h"

#include <bluetooth/HCI/btHCI.h>
#include <bluetooth/HCI/btHCI_event.h>
#include <bluetooth/HCI/btHCI_acl.h>

#include <ByteOrder.h>
#include <kernel.h>
#include <string.h>

#include "h2debug.h"
#include "h2generic.h"
#include "h2upper.h"
#include "h2util.h"


//#define DUMP_BUFFERS

/* Forward declaration */

void acl_tx_complete(void* cookie, status_t status, void* data, size_t actual_len);
void acl_rx_complete(void* cookie, status_t status, void* data, size_t actual_len);
void command_complete(void* cookie, status_t status, void* data, size_t actual_len);
void event_complete(void* cookie, status_t status, void* data, size_t actual_len);


static status_t
assembly_rx(bt_usb_dev* bdev, bt_packet_t type, void* data, int count)
{
        bdev->stat.bytesRX += count;

        return btDevices->PostTransportPacket(bdev->hdev, type, data, count);

}


#if 0
#pragma mark --- RX Complete ---
#endif

void
event_complete(void* cookie, status_t status, void* data, size_t actual_len)
{
        bt_usb_dev* bdev = (bt_usb_dev*)cookie;
        // bt_usb_dev* bdev = fetch_device(cookie, 0); -> safer / slower option
        status_t error;

        TRACE("%s: cookie@%p status=%s len=%" B_PRIuSIZE "\n", __func__, cookie,
                strerror(status), actual_len);

        if (bdev == NULL)
                return;

        if (status == B_CANCELED || status == B_DEV_CRC_ERROR)
                return; // or not running anymore...

        if (status != B_OK || actual_len == 0)
                goto resubmit;

        if (assembly_rx(bdev, BT_EVENT, data, actual_len) == B_OK) {
                bdev->stat.successfulTX++;
        } else {
                bdev->stat.errorRX++;
        }

resubmit:

        error = usb->queue_interrupt(bdev->intr_in_ep->handle, data,
                max_c(HCI_MAX_EVENT_SIZE, bdev->max_packet_size_intr_in),
                event_complete, bdev);

        if (error != B_OK) {
                reuse_room(&bdev->eventRoom, data);
                bdev->stat.rejectedRX++;
                ERROR("%s: RX event resubmittion failed %s\n", __func__,
                        strerror(error));
        } else {
                bdev->stat.acceptedRX++;
        }
}


void
acl_rx_complete(void* cookie, status_t status, void* data, size_t actual_len)
{
        bt_usb_dev* bdev = (bt_usb_dev*)cookie;
        // bt_usb_dev* bdev = fetch_device(cookie, 0); -> safer / slower option
        status_t error;

        if (bdev == NULL)
                return;

        if (status == B_CANCELED || status == B_DEV_CRC_ERROR)
                return; // or not running anymore...

        if (status != B_OK || actual_len == 0)
                goto resubmit;

        if (assembly_rx(bdev, BT_ACL, data, actual_len) == B_OK) {
                bdev->stat.successfulRX++;
        } else {
                bdev->stat.errorRX++;
        }

resubmit:

        error = usb->queue_bulk(bdev->bulk_in_ep->handle, data,
                max_c(HCI_MAX_FRAME_SIZE, bdev->max_packet_size_bulk_in),
                acl_rx_complete, (void*) bdev);

        if (error != B_OK) {
                reuse_room(&bdev->aclRoom, data);
                bdev->stat.rejectedRX++;
                ERROR("%s: RX acl resubmittion failed %s\n", __func__, strerror(error));
        } else {
                bdev->stat.acceptedRX++;
        }
}


#if 0
#pragma mark --- RX ---
#endif

status_t
submit_rx_event(bt_usb_dev* bdev)
{
        size_t size = max_c(HCI_MAX_EVENT_SIZE, bdev->max_packet_size_intr_in);
        void* buf = alloc_room(&bdev->eventRoom, size);
        status_t status;

        if (buf == NULL)
                return ENOMEM;

        status = usb->queue_interrupt(bdev->intr_in_ep->handle, buf, size,
                event_complete, (void*)bdev);

        if (status != B_OK) {
                reuse_room(&bdev->eventRoom, buf); // reuse allocated one
                bdev->stat.rejectedRX++;
        } else {
                bdev->stat.acceptedRX++;
                TRACE("%s: Accepted RX Event %d\n", __func__, bdev->stat.acceptedRX);
        }

        return status;
}


status_t
submit_rx_acl(bt_usb_dev* bdev)
{
        size_t size = max_c(HCI_MAX_FRAME_SIZE, bdev->max_packet_size_bulk_in);
        void* buf = alloc_room(&bdev->aclRoom, size);
        status_t status;

        if (buf == NULL)
                return ENOMEM;

        status = usb->queue_bulk(bdev->bulk_in_ep->handle, buf, size,
                acl_rx_complete, bdev);

        if (status != B_OK) {
                reuse_room(&bdev->aclRoom, buf); // reuse allocated
                bdev->stat.rejectedRX++;
        } else {
                bdev->stat.acceptedRX++;
        }

        return status;
}


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, status_t status, void* data, size_t actual_len)
{
        snet_buffer* snbuf = (snet_buffer*)cookie;
        bt_usb_dev* bdev = (bt_usb_dev*)snb_cookie(snbuf);

        TRACE("%s: len = %" B_PRIuSIZE " @%p\n", __func__, actual_len, data);

        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
acl_tx_complete(void* cookie, status_t status, void* data, size_t actual_len)
{
        net_buffer* nbuf = (net_buffer*)cookie;
        bt_usb_dev* bdev = GET_DEVICE(nbuf);

        //debugf("fetched=%p type %lx %p\n", bdev, nbuf->type, data);

        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)
{
        uint8 bRequestType = bdev->ctrl_req;
        uint8 bRequest = 0;
        uint16 wIndex = 0;
        uint16 value = 0;
        uint16 wLength = B_HOST_TO_LENDIAN_INT16(snb_size(snbuf));
        status_t error;

        if (!GET_BIT(bdev->state, RUNNING)) {
                return B_DEV_NOT_READY;
        }

        // set cookie
        snb_set_cookie(snbuf, bdev);

        TRACE("%s: @%p\n", __func__, snb_get(snbuf));

        error = usb->queue_request(bdev->dev, bRequestType, bRequest,
                value, wIndex, wLength, snb_get(snbuf),
                command_complete, (void*) snbuf);

        if (error != B_OK) {
                bdev->stat.rejectedTX++;
        } else {
                bdev->stat.acceptedTX++;
        }

        return error;
}


status_t
submit_tx_acl(bt_usb_dev* bdev, net_buffer* nbuf)
{
        status_t error;

        // set cookie
        SET_DEVICE(nbuf, bdev->hdev);

        if (!GET_BIT(bdev->state, RUNNING)) {
                return B_DEV_NOT_READY;
        }
        /*
        debugf("### Outgoing ACL: len = %ld\n", nbuf->size);
        for (uint32 index = 0 ; index < nbuf->size; index++ ) {
                dprintf("%x:",((uint8*)nb_get_whole_buffer(nbuf))[index]);
        }
        */

        error = usb->queue_bulk(bdev->bulk_out_ep->handle, nb_get_whole_buffer(nbuf),
                nbuf->size, acl_tx_complete, (void*)nbuf);

        if (error != B_OK) {
                bdev->stat.rejectedTX++;
        } else {
                bdev->stat.acceptedTX++;
        }

        return error;
}


status_t
submit_tx_sco(bt_usb_dev* bdev)
{

        if (!GET_BIT(bdev->state, RUNNING)) {
                return B_DEV_NOT_READY;
        }

        // not yet implemented
        return B_ERROR;
}