/* * Copyright (c) 2010 Vojtech Horky * 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 libusb * @{ */ /** @file * @brief Driver communication for local drivers (hubs). */ #include #include #include #include #include /** Find host controller when handled by current task. * * @param dev Device asking for connection. * @return Device structure corresponding to parent host controller. * @retval NULL Corresponding host controller not found. */ device_t *usb_hc_connect(device_t *dev) { /* * FIXME: this will not work when some hub on the path is * not driven by the same task. */ device_t *parent = dev; while (parent->parent != NULL) { parent = parent->parent; } if (dev == parent) { printf("FIXME in %s:%d encountered!\n", __FILE__, __LINE__); parent = NULL; } return parent; } /** Information about pending transaction on HC. */ typedef struct { /** Storage for actual number of bytes transferred. */ size_t *size_transferred; /** Target device. */ usb_hcd_attached_device_info_t *device; /** Target endpoint. */ usb_hc_endpoint_info_t *endpoint; /** Guard for the condition variable. */ fibril_mutex_t condvar_guard; /** Condition variable for waiting for done. */ fibril_condvar_t condvar; /** Flag whether the transfer is done. */ bool done; } transfer_info_t; /** Create new transfer info. * * @param device Attached device. * @param endpoint Endpoint. * @return Transfer info with pre-filled values. */ static transfer_info_t *transfer_info_create( usb_hcd_attached_device_info_t *device, usb_hc_endpoint_info_t *endpoint) { transfer_info_t *transfer = malloc(sizeof(transfer_info_t)); transfer->size_transferred = NULL; fibril_condvar_initialize(&transfer->condvar); fibril_mutex_initialize(&transfer->condvar_guard); transfer->done = false; transfer->device = device; transfer->endpoint = endpoint; return transfer; } /** Destroy transfer info. * * @param transfer Transfer to be destroyed. */ static void transfer_info_destroy(transfer_info_t *transfer) { free(transfer->device); free(transfer->endpoint); free(transfer); } /** Create info about attached device. * * @param address Device address. * @return Device info structure. */ static usb_hcd_attached_device_info_t *create_attached_device_info( usb_address_t address) { usb_hcd_attached_device_info_t *dev = malloc(sizeof(usb_hcd_attached_device_info_t)); dev->address = address; dev->endpoint_count = 0; dev->endpoints = NULL; list_initialize(&dev->link); return dev; } /** Create info about device endpoint. * * @param endpoint Endpoint number. * @param direction Endpoint data direction. * @param transfer_type Transfer type of the endpoint. * @return Endpoint info structure. */ static usb_hc_endpoint_info_t *create_endpoint_info(usb_endpoint_t endpoint, usb_direction_t direction, usb_transfer_type_t transfer_type) { usb_hc_endpoint_info_t *ep = malloc(sizeof(usb_hc_endpoint_info_t)); ep->data_toggle = 0; ep->direction = direction; ep->transfer_type = transfer_type; ep->endpoint = endpoint; return ep; } /** Marks given transfer as done. * * @param transfer Transfer to be completed. */ static void transfer_mark_complete(transfer_info_t *transfer) { fibril_mutex_lock(&transfer->condvar_guard); transfer->done = true; fibril_condvar_signal(&transfer->condvar); fibril_mutex_unlock(&transfer->condvar_guard); } /** Callback for OUT transfers. * * @param hc Host controller that processed the transfer. * @param outcome Transfer outcome. * @param arg Custom argument. */ static void callback_out(usb_hc_device_t *hc, usb_transaction_outcome_t outcome, void *arg) { transfer_info_t *transfer = (transfer_info_t *) arg; /* * For out transfers, marking them complete is enough. * No other processing is necessary. */ transfer_mark_complete(transfer); } /** Callback for IN transfers. * * @param hc Host controller that processed the transfer. * @param actual_size Number of actually transferred bytes. * @param outcome Transfer outcome. * @param arg Custom argument. */ static void callback_in(usb_hc_device_t *hc, size_t actual_size, usb_transaction_outcome_t outcome, void *arg) { transfer_info_t *transfer = (transfer_info_t *) arg; /* * Set the number of actually transferred bytes and * mark the transfer as complete. */ if (transfer->size_transferred != NULL) { *transfer->size_transferred = actual_size; } transfer_mark_complete(transfer); } static int async_transfer_out(usb_hc_device_t *hc, usb_target_t target, usb_transfer_type_t transfer_type, void *data, size_t size, usb_handle_t *handle) { if ((hc->transfer_ops == NULL) || (hc->transfer_ops->transfer_out == NULL)) { return ENOTSUP; } /* This creation of the device on the fly is just a workaround. */ transfer_info_t *transfer = transfer_info_create( create_attached_device_info(target.address), create_endpoint_info(target.endpoint, USB_DIRECTION_OUT, transfer_type)); int rc = hc->transfer_ops->transfer_out(hc, transfer->device, transfer->endpoint, data, size, callback_out, transfer); if (rc != EOK) { transfer_info_destroy(transfer); return rc; } *handle = (usb_handle_t)transfer; return EOK; } static int async_transfer_setup(usb_hc_device_t *hc, usb_target_t target, usb_transfer_type_t transfer_type, void *data, size_t size, usb_handle_t *handle) { if ((hc->transfer_ops == NULL) || (hc->transfer_ops->transfer_setup == NULL)) { return ENOTSUP; } /* This creation of the device on the fly is just a workaround. */ transfer_info_t *transfer = transfer_info_create( create_attached_device_info(target.address), create_endpoint_info(target.endpoint, USB_DIRECTION_OUT, transfer_type)); int rc = hc->transfer_ops->transfer_setup(hc, transfer->device, transfer->endpoint, data, size, callback_out, transfer); if (rc != EOK) { transfer_info_destroy(transfer); return rc; } *handle = (usb_handle_t)transfer; return EOK; } static int async_transfer_in(usb_hc_device_t *hc, usb_target_t target, usb_transfer_type_t transfer_type, void *buffer, size_t size, size_t *actual_size, usb_handle_t *handle) { if ((hc->transfer_ops == NULL) || (hc->transfer_ops->transfer_in == NULL)) { return ENOTSUP; } /* This creation of the device on the fly is just a workaround. */ transfer_info_t *transfer = transfer_info_create( create_attached_device_info(target.address), create_endpoint_info(target.endpoint, USB_DIRECTION_IN, transfer_type)); transfer->size_transferred = actual_size; int rc = hc->transfer_ops->transfer_in(hc, transfer->device, transfer->endpoint, buffer, size, callback_in, transfer); if (rc != EOK) { transfer_info_destroy(transfer); return rc; } *handle = (usb_handle_t)transfer; return EOK; } /** Issue interrupt OUT transfer to HC driven by current task. * * @param hc Host controller to handle the transfer. * @param target Target device address. * @param buffer Data buffer. * @param size Buffer size. * @param handle Transfer handle. * @return Error code. */ int usb_hc_async_interrupt_out(usb_hc_device_t *hc, usb_target_t target, void *buffer, size_t size, usb_handle_t *handle) { return async_transfer_out(hc, target, USB_TRANSFER_INTERRUPT, buffer, size, handle); } /** Issue interrupt IN transfer to HC driven by current task. * * @warning The @p buffer and @p actual_size parameters shall not be * touched until this transfer is waited for by usb_hc_async_wait_for(). * * @param hc Host controller to handle the transfer. * @param target Target device address. * @param buffer Data buffer. * @param size Buffer size. * @param actual_size Size of actually transferred data. * @param handle Transfer handle. * @return Error code. */ int usb_hc_async_interrupt_in(usb_hc_device_t *hc, usb_target_t target, void *buffer, size_t size, size_t *actual_size, usb_handle_t *handle) { return async_transfer_in(hc, target, USB_TRANSFER_INTERRUPT, buffer, size, actual_size, handle); } int usb_hc_async_control_write_setup(usb_hc_device_t *hc, usb_target_t target, void *data, size_t size, usb_handle_t *handle) { return async_transfer_setup(hc, target, USB_TRANSFER_CONTROL, data, size, handle); } int usb_hc_async_control_write_data(usb_hc_device_t *hc, usb_target_t target, void *data, size_t size, usb_handle_t *handle) { return async_transfer_out(hc, target, USB_TRANSFER_CONTROL, data, size, handle); } int usb_hc_async_control_write_status(usb_hc_device_t *hc, usb_target_t target, usb_handle_t *handle) { return async_transfer_in(hc, target, USB_TRANSFER_CONTROL, NULL, 0, NULL, handle); } int usb_hc_async_control_read_setup(usb_hc_device_t *hc, usb_target_t target, void *data, size_t size, usb_handle_t *handle) { return async_transfer_setup(hc, target, USB_TRANSFER_CONTROL, data, size, handle); } int usb_hc_async_control_read_data(usb_hc_device_t *hc, usb_target_t target, void *buffer, size_t size, size_t *actual_size, usb_handle_t *handle) { return async_transfer_in(hc, target, USB_TRANSFER_CONTROL, buffer, size, actual_size, handle); } int usb_hc_async_control_read_status(usb_hc_device_t *hc, usb_target_t target, usb_handle_t *handle) { return async_transfer_out(hc, target, USB_TRANSFER_CONTROL, NULL, 0, handle); } /** Wait for transfer to complete. * * @param handle Transfer handle. * @return Error code. */ int usb_hc_async_wait_for(usb_handle_t handle) { transfer_info_t *transfer = (transfer_info_t *) handle; if (transfer == NULL) { return ENOENT; } fibril_mutex_lock(&transfer->condvar_guard); while (!transfer->done) { fibril_condvar_wait(&transfer->condvar, &transfer->condvar_guard); } fibril_mutex_unlock(&transfer->condvar_guard); transfer_info_destroy(transfer); return EOK; } /** * @} */