root/src/add-ons/kernel/bus_managers/usb/Pipe.cpp
/*
 * Copyright 2004-2020, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Lotz <mmlr@mlotz.ch>
 *              Niels S. Reedijk
 */

#include "usb_private.h"


Pipe::Pipe(Object *parent)
        :       Object(parent),
                fDataToggle(false),
                fControllerCookie(NULL)
{
        // all other init is to be done in InitCommon()
}


Pipe::~Pipe()
{
        PutUSBID(false);
        Pipe::CancelQueuedTransfers(true);
        WaitForIdle();

        GetBusManager()->NotifyPipeChange(this, USB_CHANGE_DESTROYED);
}


void
Pipe::InitCommon(int8 deviceAddress, uint8 endpointAddress, usb_speed speed,
        pipeDirection direction, size_t maxPacketSize, uint8 interval,
        int8 hubAddress, uint8 hubPort)
{
        fDeviceAddress = deviceAddress;
        fEndpointAddress = endpointAddress;
        fSpeed = speed;
        fDirection = direction;
        fMaxPacketSize = maxPacketSize;
        fInterval = interval;
        fHubAddress = hubAddress;
        fHubPort = hubPort;

        fMaxBurst = 0;
        fBytesPerInterval = 0;

        GetBusManager()->NotifyPipeChange(this, USB_CHANGE_CREATED);
}


void
Pipe::InitSuperSpeed(uint8 maxBurst, uint16 bytesPerInterval)
{
        fMaxBurst = maxBurst;
        fBytesPerInterval = bytesPerInterval;
}


void
Pipe::SetHubInfo(int8 address, uint8 port)
{
        fHubAddress = address;
        fHubPort = port;
}


status_t
Pipe::SubmitTransfer(Transfer *transfer)
{
        if (USBID() == UINT32_MAX)
                return B_NO_INIT;

        // ToDo: keep track of all submited transfers to be able to cancel them
        return GetBusManager()->SubmitTransfer(transfer);
}


status_t
Pipe::CancelQueuedTransfers(bool force)
{
        return GetBusManager()->CancelQueuedTransfers(this, force);
}


status_t
Pipe::SetFeature(uint16 selector)
{
        TRACE("set feature %u\n", selector);
        return ((Device *)Parent())->DefaultPipe()->SendRequest(
                USB_REQTYPE_STANDARD | USB_REQTYPE_ENDPOINT_OUT,
                USB_REQUEST_SET_FEATURE,
                selector,
                fEndpointAddress | (fDirection == In ? USB_ENDPOINT_ADDR_DIR_IN
                        : USB_ENDPOINT_ADDR_DIR_OUT),
                0,
                NULL,
                0,
                NULL);
}


status_t
Pipe::ClearFeature(uint16 selector)
{
        // clearing a stalled condition resets the data toggle
        if (selector == USB_FEATURE_ENDPOINT_HALT)
                SetDataToggle(false);

        TRACE("clear feature %u\n", selector);
        return ((Device *)Parent())->DefaultPipe()->SendRequest(
                USB_REQTYPE_STANDARD | USB_REQTYPE_ENDPOINT_OUT,
                USB_REQUEST_CLEAR_FEATURE,
                selector,
                fEndpointAddress | (fDirection == In ? USB_ENDPOINT_ADDR_DIR_IN
                        : USB_ENDPOINT_ADDR_DIR_OUT),
                0,
                NULL,
                0,
                NULL);
}


status_t
Pipe::GetStatus(uint16 *status)
{
        TRACE("get status\n");
        return ((Device *)Parent())->DefaultPipe()->SendRequest(
                USB_REQTYPE_STANDARD | USB_REQTYPE_ENDPOINT_IN,
                USB_REQUEST_GET_STATUS,
                0,
                fEndpointAddress | (fDirection == In ? USB_ENDPOINT_ADDR_DIR_IN
                        : USB_ENDPOINT_ADDR_DIR_OUT),
                2,
                (void *)status,
                2,
                NULL);
}


//
// #pragma mark -
//


InterruptPipe::InterruptPipe(Object *parent)
        :       Pipe(parent)
{
}


status_t
InterruptPipe::QueueInterrupt(void *data, size_t dataLength,
        usb_callback_func callback, void *callbackCookie)
{
        if (dataLength > 0 && data == NULL)
                return B_BAD_VALUE;

        Transfer *transfer = new(std::nothrow) Transfer(this);
        if (!transfer)
                return B_NO_MEMORY;

        transfer->SetData((uint8 *)data, dataLength);
        transfer->SetCallback(callback, callbackCookie);

        status_t result = GetBusManager()->SubmitTransfer(transfer);
        if (result < B_OK)
                delete transfer;
        return result;
}


//
// #pragma mark -
//


BulkPipe::BulkPipe(Object *parent)
        :       Pipe(parent)
{
}


void
BulkPipe::InitCommon(int8 deviceAddress, uint8 endpointAddress,
        usb_speed speed, pipeDirection direction, size_t maxPacketSize,
        uint8 interval, int8 hubAddress, uint8 hubPort)
{
        // See comments in ControlPipe::InitCommon.
        switch (speed) {
                case USB_SPEED_HIGHSPEED:
                        maxPacketSize = 512;
                        break;
                case USB_SPEED_SUPERSPEED:
                case USB_SPEED_SUPERSPEEDPLUS:
                        maxPacketSize = 1024;
                        break;

                default:
                        break;
        }

        Pipe::InitCommon(deviceAddress, endpointAddress, speed, direction,
                maxPacketSize, interval, hubAddress, hubPort);
}


status_t
BulkPipe::QueueBulk(void *data, size_t dataLength, usb_callback_func callback,
        void *callbackCookie)
{
        if (dataLength > 0 && data == NULL)
                return B_BAD_VALUE;

        Transfer *transfer = new(std::nothrow) Transfer(this);
        if (!transfer)
                return B_NO_MEMORY;

        transfer->SetData((uint8 *)data, dataLength);
        transfer->SetCallback(callback, callbackCookie);

        status_t result = GetBusManager()->SubmitTransfer(transfer);
        if (result < B_OK)
                delete transfer;
        return result;
}


status_t
BulkPipe::QueueBulkV(iovec *vector, size_t vectorCount,
        usb_callback_func callback, void *callbackCookie)
{
        if (vectorCount > 0 && vector == NULL)
                return B_BAD_VALUE;

        Transfer *transfer = new(std::nothrow) Transfer(this);
        if (!transfer)
                return B_NO_MEMORY;

        transfer->SetVector(vector, vectorCount);
        transfer->SetCallback(callback, callbackCookie);

        status_t result = GetBusManager()->SubmitTransfer(transfer);
        if (result < B_OK)
                delete transfer;
        return result;
}


status_t
BulkPipe::QueueBulkV(physical_entry *vector, size_t vectorCount,
        usb_callback_func callback, void *callbackCookie)
{
        if (vectorCount > 0 && vector == NULL)
                return B_BAD_VALUE;

        Transfer *transfer = new(std::nothrow) Transfer(this);
        if (!transfer)
                return B_NO_MEMORY;

        transfer->SetVector(vector, vectorCount);
        transfer->SetCallback(callback, callbackCookie);

        status_t result = GetBusManager()->SubmitTransfer(transfer);
        if (result < B_OK)
                delete transfer;
        return result;
}


//
// #pragma mark -
//


IsochronousPipe::IsochronousPipe(Object *parent)
        :       Pipe(parent),
                fMaxQueuedPackets(0),
                fMaxBufferDuration(0),
                fSampleSize(0)
{
}


status_t
IsochronousPipe::QueueIsochronous(void *data, size_t dataLength,
        usb_iso_packet_descriptor *packetDesc, uint32 packetCount,
        uint32 *startingFrameNumber, uint32 flags, usb_callback_func callback,
        void *callbackCookie)
{
        if ((dataLength > 0 && data == NULL)
                || (packetCount > 0 && packetDesc == NULL))
                return B_BAD_VALUE;

        usb_isochronous_data *isochronousData
                = new(std::nothrow) usb_isochronous_data;

        if (!isochronousData)
                return B_NO_MEMORY;

        isochronousData->packet_descriptors = packetDesc;
        isochronousData->packet_count = packetCount;
        isochronousData->starting_frame_number = startingFrameNumber;
        isochronousData->flags = flags;

        for (uint32 i = 0; i < isochronousData->packet_count; i++) {
                isochronousData->packet_descriptors[i].actual_length = 0;
                isochronousData->packet_descriptors[i].status = B_NO_INIT;
        }

        Transfer *transfer = new(std::nothrow) Transfer(this);
        if (!transfer) {
                delete isochronousData;
                return B_NO_MEMORY;
        }

        transfer->SetData((uint8 *)data, dataLength);
        transfer->SetCallback(callback, callbackCookie);
        transfer->SetIsochronousData(isochronousData);

        status_t result = GetBusManager()->SubmitTransfer(transfer);
        if (result < B_OK)
                delete transfer;
        return result;
}


status_t
IsochronousPipe::SetPipePolicy(uint8 maxQueuedPackets,
        uint16 maxBufferDurationMS, uint16 sampleSize)
{
        if (maxQueuedPackets == fMaxQueuedPackets
                || maxBufferDurationMS == fMaxBufferDuration
                || sampleSize == fSampleSize)
                return B_OK;

        fMaxQueuedPackets = maxQueuedPackets;
        fMaxBufferDuration = maxBufferDurationMS;
        fSampleSize = sampleSize;

        GetBusManager()->NotifyPipeChange(this, USB_CHANGE_PIPE_POLICY_CHANGED);
        return B_OK;
}


status_t
IsochronousPipe::GetPipePolicy(uint8 *maxQueuedPackets,
        uint16 *maxBufferDurationMS, uint16 *sampleSize)
{
        if (maxQueuedPackets)
                *maxQueuedPackets = fMaxQueuedPackets;
        if (maxBufferDurationMS)
                *maxBufferDurationMS = fMaxBufferDuration;
        if (sampleSize)
                *sampleSize = fSampleSize;
        return B_OK;
}


//
// #pragma mark -
//


ControlPipe::ControlPipe(Object *parent)
        :       Pipe(parent),
                fNotifySem(-1)
{
        mutex_init(&fSendRequestLock, "control pipe send request");
}


ControlPipe::~ControlPipe()
{
        // We do this here in case a submitted request is still running.
        PutUSBID(false);
        ControlPipe::CancelQueuedTransfers(true);
        WaitForIdle();

        if (fNotifySem >= 0)
                delete_sem(fNotifySem);
        mutex_lock(&fSendRequestLock);
        mutex_destroy(&fSendRequestLock);
}


void
ControlPipe::InitCommon(int8 deviceAddress, uint8 endpointAddress,
        usb_speed speed, pipeDirection direction, size_t maxPacketSize,
        uint8 interval, int8 hubAddress, uint8 hubPort)
{
        // The USB 2.0 spec section 5.5.3 gives fixed max packet sizes for the
        // different speeds. The USB 3.1 specs defines some fixed max packet sizes,
        // including for control endpoints in 9.6.6. Some devices ignore these
        // values and use bogus ones, so we restrict them here.
        switch (speed) {
                case USB_SPEED_LOWSPEED:
                        maxPacketSize = 8;
                        break;
                case USB_SPEED_HIGHSPEED:
                        maxPacketSize = 64;
                        break;
                case USB_SPEED_SUPERSPEED:
                case USB_SPEED_SUPERSPEEDPLUS:
                        maxPacketSize = 512;
                        break;

                default:
                        break;
        }

        Pipe::InitCommon(deviceAddress, endpointAddress, speed, direction,
                maxPacketSize, interval, hubAddress, hubPort);
}


status_t
ControlPipe::SendRequest(uint8 requestType, uint8 request, uint16 value,
        uint16 index, uint16 length, void *data, size_t dataLength,
        size_t *actualLength)
{
        status_t result = mutex_lock(&fSendRequestLock);
        if (result != B_OK)
                return result;

        if (fNotifySem < 0) {
                fNotifySem = create_sem(0, "usb send request notify");
                if (fNotifySem < 0) {
                        mutex_unlock(&fSendRequestLock);
                        return B_NO_MORE_SEMS;
                }
        }

        result = QueueRequest(requestType, request, value, index, length, data,
                dataLength, SendRequestCallback, this);
        if (result < B_OK) {
                mutex_unlock(&fSendRequestLock);
                return result;
        }

        // The sem will be released unconditionally in the callback after the
        // result data was filled in. Use a 2 seconds timeout for control transfers.
        if (acquire_sem_etc(fNotifySem, 1, B_RELATIVE_TIMEOUT, 2000000) < B_OK) {
                TRACE_ERROR("timeout waiting for queued request to complete\n");

                CancelQueuedTransfers(false);

                // After the above cancel returns it is guaranteed that the callback
                // has been invoked. Therefore we can simply grab that released
                // semaphore again to clean up.
                acquire_sem(fNotifySem);

                if (actualLength)
                        *actualLength = 0;

                mutex_unlock(&fSendRequestLock);
                return B_TIMED_OUT;
        }

        if (actualLength)
                *actualLength = fActualLength;

        mutex_unlock(&fSendRequestLock);
        return fTransferStatus;
}


void
ControlPipe::SendRequestCallback(void *cookie, status_t status, void *data,
        size_t actualLength)
{
        ControlPipe *pipe = (ControlPipe *)cookie;
        pipe->fTransferStatus = status;
        pipe->fActualLength = actualLength;
        release_sem(pipe->fNotifySem);
}


status_t
ControlPipe::QueueRequest(uint8 requestType, uint8 request, uint16 value,
        uint16 index, uint16 length, void *data, size_t dataLength,
        usb_callback_func callback, void *callbackCookie)
{
        if (dataLength > 0 && data == NULL)
                return B_BAD_VALUE;

        if (USBID() == UINT32_MAX)
                return B_NO_INIT;

        usb_request_data *requestData = new(std::nothrow) usb_request_data;
        if (!requestData)
                return B_NO_MEMORY;

        requestData->RequestType = requestType;
        requestData->Request = request;
        requestData->Value = value;
        requestData->Index = index;
        requestData->Length = length;

        Transfer *transfer = new(std::nothrow) Transfer(this);
        if (!transfer) {
                delete requestData;
                return B_NO_MEMORY;
        }

        transfer->SetRequestData(requestData);
        transfer->SetData((uint8 *)data, dataLength);
        transfer->SetCallback(callback, callbackCookie);

        status_t result = GetBusManager()->SubmitTransfer(transfer);
        if (result < B_OK)
                delete transfer;
        return result;
}


status_t
ControlPipe::CancelQueuedTransfers(bool force)
{
        if (force && fNotifySem >= 0) {
                // There is likely a transfer currently running; we need to cancel it
                // manually, as callbacks are not invoked when force-cancelling.
                fTransferStatus = B_CANCELED;
                fActualLength = 0;
                release_sem_etc(fNotifySem, 1, B_RELEASE_IF_WAITING_ONLY);
        }

        return Pipe::CancelQueuedTransfers(force);
}