/* * Copyright (c) 2011 Jan Vesely * 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 * */ #include #include #include #include #include #include #include #include "hcd.h" /** Initialize hcd_t structure. * Initializes device and endpoint managers. Sets data and hook pointer to NULL. * * @param hcd hcd_t structure to initialize, non-null. * @param max_speed Maximum supported USB speed (full, high). * @param bandwidth Available bandwidth, passed to endpoint manager. * @param bw_count Bandwidth compute function, passed to endpoint manager. */ void hcd_init(hcd_t *hcd) { assert(hcd); hcd_set_implementation(hcd, NULL, NULL, NULL); } usb_address_t hcd_request_address(hcd_t *hcd, usb_speed_t speed) { assert(hcd); usb_address_t address = 0; const int ret = bus_request_address(hcd->bus, &address, false, speed); if (ret != EOK) return ret; return address; } /** Prepare generic usb_transfer_batch and schedule it. * @param hcd Host controller driver. * @param target address and endpoint number. * @param setup_data Data to use in setup stage (Control communication type) * @param in Callback for device to host communication. * @param out Callback for host to device communication. * @param arg Callback parameter. * @param name Communication identifier (for nicer output). * @return Error code. */ int hcd_send_batch(hcd_t *hcd, usb_target_t target, usb_direction_t direction, void *data, size_t size, uint64_t setup_data, usbhc_iface_transfer_in_callback_t in, usbhc_iface_transfer_out_callback_t out, void *arg, const char *name) { assert(hcd); endpoint_t *ep = bus_find_endpoint(hcd->bus, target, direction); if (ep == NULL) { usb_log_error("Endpoint(%d:%d) not registered for %s.\n", target.address, target.endpoint, name); return ENOENT; } usb_log_debug2("%s %d:%d %zu(%zu).\n", name, target.address, target.endpoint, size, ep->max_packet_size); const size_t bw = bus_count_bw(ep, size); /* Check if we have enough bandwidth reserved */ if (ep->bandwidth < bw) { usb_log_error("Endpoint(%d:%d) %s needs %zu bw " "but only %zu is reserved.\n", ep->target.address, ep->target.endpoint, name, bw, ep->bandwidth); return ENOSPC; } if (!hcd->ops.schedule) { usb_log_error("HCD does not implement scheduler.\n"); return ENOTSUP; } usb_transfer_batch_t *batch = usb_transfer_batch_create(ep); if (!batch) { usb_log_error("Failed to create transfer batch.\n"); return ENOMEM; } batch->buffer = data; batch->buffer_size = size; batch->setup.packed = setup_data; batch->dir = direction; /* Check for commands that reset toggle bit */ if (ep->transfer_type == USB_TRANSFER_CONTROL) batch->toggle_reset_mode = usb_request_get_toggle_reset_mode(&batch->setup.packet); usb_transfer_batch_set_old_handlers(batch, in, out, arg); const int ret = hcd->ops.schedule(hcd, batch); if (ret != EOK) { usb_log_warning("Batch %p failed to schedule: %s", batch, str_error(ret)); usb_transfer_batch_destroy(batch); } /* Drop our own reference to ep. */ endpoint_del_ref(ep); return ret; } typedef struct { fibril_mutex_t done_mtx; fibril_condvar_t done_cv; unsigned done; int ret; size_t size; } sync_data_t; static void transfer_in_cb(int ret, size_t size, void* data) { sync_data_t *d = data; assert(d); d->ret = ret; d->size = size; fibril_mutex_lock(&d->done_mtx); d->done = 1; fibril_condvar_broadcast(&d->done_cv); fibril_mutex_unlock(&d->done_mtx); } static void transfer_out_cb(int ret, void* data) { sync_data_t *d = data; assert(data); d->ret = ret; fibril_mutex_lock(&d->done_mtx); d->done = 1; fibril_condvar_broadcast(&d->done_cv); fibril_mutex_unlock(&d->done_mtx); } /** this is really ugly version of sync usb communication */ ssize_t hcd_send_batch_sync( hcd_t *hcd, usb_target_t target, usb_direction_t dir, void *data, size_t size, uint64_t setup_data, const char* name) { assert(hcd); sync_data_t sd = { .done = 0, .ret = EBUSY, .size = size }; fibril_mutex_initialize(&sd.done_mtx); fibril_condvar_initialize(&sd.done_cv); const int ret = hcd_send_batch(hcd, target, dir, data, size, setup_data, dir == USB_DIRECTION_IN ? transfer_in_cb : NULL, dir == USB_DIRECTION_OUT ? transfer_out_cb : NULL, &sd, name); if (ret != EOK) return ret; fibril_mutex_lock(&sd.done_mtx); while (!sd.done) fibril_condvar_wait(&sd.done_cv, &sd.done_mtx); fibril_mutex_unlock(&sd.done_mtx); if (sd.ret == EOK) return sd.size; return sd.ret; } /** * @} */