HelenOS sources

root/uspace/lib/usbhost/src/endpoint.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. endpoint_init
  2. get_bus_ops
  3. endpoint_add_ref
  4. endpoint_destroy
  5. endpoint_del_ref
  6. endpoint_set_online
  7. endpoint_set_offline_locked
  8. endpoint_wait_timeout_locked
  9. endpoint_activate_locked
  10. endpoint_deactivate_locked
  11. endpoint_send_batch

/*
 * Copyright (c) 2011 Jan Vesely
 * Copyright (c) 2018 Ondrej Hlavaty
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup libusbhost
 * @{
 */
/** @file
 * @brief UHCI host controller driver structure
 */

#include <assert.h>
#include <mem.h>
#include <stdlib.h>
#include <str_error.h>
#include <usb/debug.h>
#include <usb/descriptor.h>
#include <usb/host/hcd.h>
#include <usb/host/utility.h>

#include "usb_transfer_batch.h"
#include "bus.h"

#include "endpoint.h"

/**
 * Initialize provided endpoint structure.
 */
void endpoint_init(endpoint_t *ep, device_t *dev, const usb_endpoint_descriptors_t *desc)
{
        memset(ep, 0, sizeof(endpoint_t));

        assert(dev);
        ep->device = dev;

        refcount_init(&ep->refcnt);
        fibril_condvar_initialize(&ep->avail);

        ep->endpoint = USB_ED_GET_EP(desc->endpoint);
        ep->direction = USB_ED_GET_DIR(desc->endpoint);
        ep->transfer_type = USB_ED_GET_TRANSFER_TYPE(desc->endpoint);
        ep->max_packet_size = USB_ED_GET_MPS(desc->endpoint);
        ep->packets_per_uframe = USB_ED_GET_ADD_OPPS(desc->endpoint) + 1;

        /** Direction both is our construct never present in descriptors */
        if (ep->transfer_type == USB_TRANSFER_CONTROL)
                ep->direction = USB_DIRECTION_BOTH;

        ep->max_transfer_size = ep->max_packet_size * ep->packets_per_uframe;
        ep->transfer_buffer_policy = DMA_POLICY_STRICT;
        ep->required_transfer_buffer_policy = DMA_POLICY_STRICT;
}

/**
 * Get the bus endpoint belongs to.
 */
static inline const bus_ops_t *get_bus_ops(endpoint_t *ep)
{
        return ep->device->bus->ops;
}

/**
 * Increase the reference count on endpoint.
 */
void endpoint_add_ref(endpoint_t *ep)
{
        refcount_up(&ep->refcnt);
}

/**
 * Call the desctruction callback. Default behavior is to free the memory directly.
 */
static inline void endpoint_destroy(endpoint_t *ep)
{
        const bus_ops_t *ops = get_bus_ops(ep);
        if (ops->endpoint_destroy) {
                ops->endpoint_destroy(ep);
        } else {
                assert(ep->active_batch == NULL);

                /* Assume mostly the eps will be allocated by malloc. */
                free(ep);
        }
}

/**
 * Decrease the reference count.
 */
void endpoint_del_ref(endpoint_t *ep)
{
        if (refcount_down(&ep->refcnt))
                endpoint_destroy(ep);
}

/**
 * Mark the endpoint as online. Supply a guard to be used for this endpoint
 * synchronization.
 */
void endpoint_set_online(endpoint_t *ep, fibril_mutex_t *guard)
{
        ep->guard = guard;
        ep->online = true;
}

/**
 * Mark the endpoint as offline. All other fibrils waiting to activate this
 * endpoint will be interrupted.
 */
void endpoint_set_offline_locked(endpoint_t *ep)
{
        assert(ep);
        assert(fibril_mutex_is_locked(ep->guard));

        ep->online = false;
        fibril_condvar_broadcast(&ep->avail);
}

/**
 * Wait until a transfer finishes. Can be used even when the endpoint is
 * offline (and is interrupted by the endpoint going offline).
 */
void endpoint_wait_timeout_locked(endpoint_t *ep, usec_t timeout)
{
        assert(ep);
        assert(fibril_mutex_is_locked(ep->guard));

        if (ep->active_batch == NULL)
                return;

        fibril_condvar_wait_timeout(&ep->avail, ep->guard, timeout);
}

/**
 * Mark the endpoint as active and block access for further fibrils. If the
 * endpoint is already active, it will block on ep->avail condvar.
 *
 * Call only under endpoint guard. After you activate the endpoint and release
 * the guard, you must assume that particular transfer is already
 * finished/aborted.
 *
 * Activation and deactivation is not done by the library to maximize
 * performance. The HC might want to prepare some memory buffers prior to
 * interfering with other world.
 *
 * @param batch Transfer batch this endpoint is blocked by.
 */
int endpoint_activate_locked(endpoint_t *ep, usb_transfer_batch_t *batch)
{
        assert(ep);
        assert(batch);
        assert(batch->ep == ep);
        assert(ep->guard);
        assert(fibril_mutex_is_locked(ep->guard));

        while (ep->online && ep->active_batch != NULL)
                fibril_condvar_wait(&ep->avail, ep->guard);

        if (!ep->online)
                return EINTR;

        assert(ep->active_batch == NULL);
        ep->active_batch = batch;
        return EOK;
}

/**
 * Mark the endpoint as inactive and allow access for further fibrils.
 */
void endpoint_deactivate_locked(endpoint_t *ep)
{
        assert(ep);
        assert(fibril_mutex_is_locked(ep->guard));

        ep->active_batch = NULL;
        fibril_condvar_signal(&ep->avail);
}

/**
 * Initiate a transfer on an endpoint. Creates a transfer batch, checks the
 * bandwidth requirements and schedules the batch.
 *
 * @param endpoint Endpoint for which to send the batch
 */
errno_t endpoint_send_batch(endpoint_t *ep, const transfer_request_t *req)
{
        assert(ep);
        assert(req);

        if (ep->transfer_type == USB_TRANSFER_CONTROL) {
                usb_log_debug("%s %d:%d %zu/%zuB, setup %#016" PRIx64, req->name,
                    req->target.address, req->target.endpoint,
                    req->size, ep->max_packet_size,
                    req->setup);
        } else {
                usb_log_debug("%s %d:%d %zu/%zuB", req->name,
                    req->target.address, req->target.endpoint,
                    req->size, ep->max_packet_size);
        }

        device_t *const device = ep->device;
        if (!device) {
                usb_log_warning("Endpoint detached");
                return EAGAIN;
        }

        const bus_ops_t *ops = device->bus->ops;
        if (!ops->batch_schedule) {
                usb_log_error("HCD does not implement scheduler.");
                return ENOTSUP;
        }

        size_t size = req->size;
        /*
         * Limit transfers with reserved bandwidth to the amount reserved.
         * OUT transfers are rejected, IN can be just trimmed in advance.
         */
        if (size > ep->max_transfer_size &&
            (ep->transfer_type == USB_TRANSFER_INTERRUPT ||
            ep->transfer_type == USB_TRANSFER_ISOCHRONOUS)) {
                if (req->dir == USB_DIRECTION_OUT)
                        return ENOSPC;
                else
                        size = ep->max_transfer_size;
        }

        /* Offline devices don't schedule transfers other than on EP0. */
        if (!device->online && ep->endpoint > 0)
                return EAGAIN;

        usb_transfer_batch_t *batch = usb_transfer_batch_create(ep);
        if (!batch) {
                usb_log_error("Failed to create transfer batch.");
                return ENOMEM;
        }

        batch->target = req->target;
        batch->setup.packed = req->setup;
        batch->dir = req->dir;
        batch->size = size;
        batch->offset = req->offset;
        batch->dma_buffer = req->buffer;

        dma_buffer_acquire(&batch->dma_buffer);

        if (batch->offset != 0) {
                usb_log_debug("A transfer with nonzero offset requested.");
                usb_transfer_batch_bounce(batch);
        }

        if (usb_transfer_batch_bounce_required(batch))
                usb_transfer_batch_bounce(batch);

        batch->on_complete = req->on_complete;
        batch->on_complete_data = req->arg;

        const int ret = ops->batch_schedule(batch);
        if (ret != EOK) {
                usb_log_warning("Batch %p failed to schedule: %s", batch, str_error(ret));
                usb_transfer_batch_destroy(batch);
        }

        return ret;
}

/**
 * @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub