/* * Copyright (c) 2011 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 libusbdev * @{ */ /** @file * Input and output functions (reads and writes) on endpoint pipes. * * Note on synchronousness of the operations: there is ABSOLUTELY NO * guarantee that a call to particular function will not trigger a fibril * switch. * * Note about the implementation: the transfer requests are always divided * into two functions. * The outer one does checking of input parameters (e.g. that session was * already started, buffers are not NULL etc), while the inner one * (with _no_checks suffix) does the actual IPC (it checks for IPC errors, * obviously). */ #include #include #include #include #include #include #include #include "pipepriv.h" /** Request an in transfer, no checking of input parameters. * * @param[in] pipe Pipe used for the transfer. * @param[out] buffer Buffer where to store the data. * @param[in] size Size of the buffer (in bytes). * @param[out] size_transfered Number of bytes that were actually transfered. * @return Error code. */ static int usb_pipe_read_no_checks(usb_pipe_t *pipe, void *buffer, size_t size, size_t *size_transfered) { /* Only interrupt and bulk transfers are supported */ if (pipe->transfer_type != USB_TRANSFER_INTERRUPT && pipe->transfer_type != USB_TRANSFER_BULK) return ENOTSUP; const usb_target_t target = {{ .address = pipe->wire->address, .endpoint = pipe->endpoint_no }}; /* Ensure serialization over the phone. */ pipe_start_transaction(pipe); async_exch_t *exch = async_exchange_begin(pipe->hc_sess); /* * Make call identifying target USB device and type of transfer. */ aid_t opening_request = async_send_2(exch, DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USBHC_READ, target.packed, NULL); if (opening_request == 0) { async_exchange_end(exch); pipe_end_transaction(pipe); return ENOMEM; } /* * Retrieve the data. */ ipc_call_t data_request_call; aid_t data_request = async_data_read(exch, buffer, size, &data_request_call); /* * Since now on, someone else might access the backing phone * without breaking the transfer IPC protocol. */ async_exchange_end(exch); pipe_end_transaction(pipe); if (data_request == 0) { /* * FIXME: * How to let the other side know that we want to abort? */ async_wait_for(opening_request, NULL); return ENOMEM; } /* * Wait for the answer. */ sysarg_t data_request_rc; sysarg_t opening_request_rc; async_wait_for(data_request, &data_request_rc); async_wait_for(opening_request, &opening_request_rc); if (data_request_rc != EOK) { /* Prefer the return code of the opening request. */ if (opening_request_rc != EOK) { return (int) opening_request_rc; } else { return (int) data_request_rc; } } if (opening_request_rc != EOK) { return (int) opening_request_rc; } *size_transfered = IPC_GET_ARG2(data_request_call); return EOK; } /** Request a read (in) transfer on an endpoint pipe. * * @param[in] pipe Pipe used for the transfer. * @param[out] buffer Buffer where to store the data. * @param[in] size Size of the buffer (in bytes). * @param[out] size_transfered Number of bytes that were actually transfered. * @return Error code. */ int usb_pipe_read(usb_pipe_t *pipe, void *buffer, size_t size, size_t *size_transfered) { assert(pipe); if (buffer == NULL) { return EINVAL; } if (size == 0) { return EINVAL; } if (pipe->direction != USB_DIRECTION_IN) { return EBADF; } if (pipe->transfer_type == USB_TRANSFER_CONTROL) { return EBADF; } int rc; rc = pipe_add_ref(pipe, false); if (rc != EOK) { return rc; } size_t act_size = 0; rc = usb_pipe_read_no_checks(pipe, buffer, size, &act_size); pipe_drop_ref(pipe); if (rc != EOK) { return rc; } if (size_transfered != NULL) { *size_transfered = act_size; } return EOK; } /** Request an out transfer, no checking of input parameters. * * @param[in] pipe Pipe used for the transfer. * @param[in] buffer Buffer with data to transfer. * @param[in] size Size of the buffer (in bytes). * @return Error code. */ static int usb_pipe_write_no_check(usb_pipe_t *pipe, void *buffer, size_t size) { /* Only interrupt and bulk transfers are supported */ if (pipe->transfer_type != USB_TRANSFER_INTERRUPT && pipe->transfer_type != USB_TRANSFER_BULK) return ENOTSUP; const usb_target_t target = {{ .address = pipe->wire->address, .endpoint = pipe->endpoint_no }}; /* Ensure serialization over the phone. */ pipe_start_transaction(pipe); async_exch_t *exch = async_exchange_begin(pipe->hc_sess); /* * Make call identifying target USB device and type of transfer. */ aid_t opening_request = async_send_3(exch, DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USBHC_WRITE, target.packed, size, NULL); if (opening_request == 0) { async_exchange_end(exch); pipe_end_transaction(pipe); return ENOMEM; } /* * Send the data. */ int rc = async_data_write_start(exch, buffer, size); /* * Since now on, someone else might access the backing phone * without breaking the transfer IPC protocol. */ async_exchange_end(exch); pipe_end_transaction(pipe); if (rc != EOK) { async_wait_for(opening_request, NULL); return rc; } /* * Wait for the answer. */ sysarg_t opening_request_rc; async_wait_for(opening_request, &opening_request_rc); return (int) opening_request_rc; } /** Request a write (out) transfer on an endpoint pipe. * * @param[in] pipe Pipe used for the transfer. * @param[in] buffer Buffer with data to transfer. * @param[in] size Size of the buffer (in bytes). * @return Error code. */ int usb_pipe_write(usb_pipe_t *pipe, void *buffer, size_t size) { assert(pipe); if (buffer == NULL) { return EINVAL; } if (size == 0) { return EINVAL; } if (pipe->direction != USB_DIRECTION_OUT) { return EBADF; } if (pipe->transfer_type == USB_TRANSFER_CONTROL) { return EBADF; } int rc; rc = pipe_add_ref(pipe, false); if (rc != EOK) { return rc; } rc = usb_pipe_write_no_check(pipe, buffer, size); pipe_drop_ref(pipe); return rc; } /** Try to clear endpoint halt of default control pipe. * * @param pipe Pipe for control endpoint zero. */ static void clear_self_endpoint_halt(usb_pipe_t *pipe) { assert(pipe != NULL); if (!pipe->auto_reset_halt || (pipe->endpoint_no != 0)) { return; } /* Prevent indefinite recursion. */ pipe->auto_reset_halt = false; usb_request_clear_endpoint_halt(pipe, 0); pipe->auto_reset_halt = true; } /** Request a control read transfer, no checking of input parameters. * * @param[in] pipe Pipe used for the transfer. * @param[in] setup_buffer Buffer with the setup packet. * @param[in] setup_buffer_size Size of the setup packet (in bytes). * @param[out] data_buffer Buffer for incoming data. * @param[in] data_buffer_size Size of the buffer for incoming data (in bytes). * @param[out] data_transfered_size Number of bytes that were actually * transfered during the DATA stage. * @return Error code. */ static int usb_pipe_control_read_no_check(usb_pipe_t *pipe, const void *setup_buffer, size_t setup_buffer_size, void *data_buffer, size_t data_buffer_size, size_t *data_transfered_size) { /* Ensure serialization over the phone. */ pipe_start_transaction(pipe); const usb_target_t target = {{ .address = pipe->wire->address, .endpoint = pipe->endpoint_no }}; assert(setup_buffer_size == 8); uint64_t setup_packet; memcpy(&setup_packet, setup_buffer, 8); /* * Make call identifying target USB device and control transfer type. */ async_exch_t *exch = async_exchange_begin(pipe->hc_sess); aid_t opening_request = async_send_4(exch, DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USBHC_READ, target.packed, (setup_packet & UINT32_MAX), (setup_packet >> 32), NULL); if (opening_request == 0) { async_exchange_end(exch); return ENOMEM; } /* * Retrieve the data. */ ipc_call_t data_request_call; aid_t data_request = async_data_read(exch, data_buffer, data_buffer_size, &data_request_call); /* * Since now on, someone else might access the backing phone * without breaking the transfer IPC protocol. */ async_exchange_end(exch); pipe_end_transaction(pipe); if (data_request == 0) { async_wait_for(opening_request, NULL); return ENOMEM; } /* * Wait for the answer. */ sysarg_t data_request_rc; sysarg_t opening_request_rc; async_wait_for(data_request, &data_request_rc); async_wait_for(opening_request, &opening_request_rc); if (data_request_rc != EOK) { /* Prefer the return code of the opening request. */ if (opening_request_rc != EOK) { return (int) opening_request_rc; } else { return (int) data_request_rc; } } if (opening_request_rc != EOK) { return (int) opening_request_rc; } *data_transfered_size = IPC_GET_ARG2(data_request_call); return EOK; } /** Request a control read transfer on an endpoint pipe. * * This function encapsulates all three stages of a control transfer. * * @param[in] pipe Pipe used for the transfer. * @param[in] setup_buffer Buffer with the setup packet. * @param[in] setup_buffer_size Size of the setup packet (in bytes). * @param[out] data_buffer Buffer for incoming data. * @param[in] data_buffer_size Size of the buffer for incoming data (in bytes). * @param[out] data_transfered_size Number of bytes that were actually * transfered during the DATA stage. * @return Error code. */ int usb_pipe_control_read(usb_pipe_t *pipe, const void *setup_buffer, size_t setup_buffer_size, void *data_buffer, size_t data_buffer_size, size_t *data_transfered_size) { assert(pipe); if ((setup_buffer == NULL) || (setup_buffer_size == 0)) { return EINVAL; } if ((data_buffer == NULL) || (data_buffer_size == 0)) { return EINVAL; } if ((pipe->direction != USB_DIRECTION_BOTH) || (pipe->transfer_type != USB_TRANSFER_CONTROL)) { return EBADF; } int rc; rc = pipe_add_ref(pipe, false); if (rc != EOK) { return rc; } size_t act_size = 0; rc = usb_pipe_control_read_no_check(pipe, setup_buffer, setup_buffer_size, data_buffer, data_buffer_size, &act_size); if (rc == ESTALL) { clear_self_endpoint_halt(pipe); } pipe_drop_ref(pipe); if (rc != EOK) { return rc; } if (data_transfered_size != NULL) { *data_transfered_size = act_size; } return EOK; } /** Request a control write transfer, no checking of input parameters. * * @param[in] pipe Pipe used for the transfer. * @param[in] setup_buffer Buffer with the setup packet. * @param[in] setup_buffer_size Size of the setup packet (in bytes). * @param[in] data_buffer Buffer with data to be sent. * @param[in] data_buffer_size Size of the buffer with outgoing data (in bytes). * @return Error code. */ static int usb_pipe_control_write_no_check(usb_pipe_t *pipe, const void *setup_buffer, size_t setup_buffer_size, const void *data_buffer, size_t data_buffer_size) { /* Ensure serialization over the phone. */ pipe_start_transaction(pipe); const usb_target_t target = {{ .address = pipe->wire->address, .endpoint = pipe->endpoint_no }}; assert(setup_buffer_size == 8); uint64_t setup_packet; memcpy(&setup_packet, setup_buffer, 8); /* * Make call identifying target USB device and control transfer type. */ async_exch_t *exch = async_exchange_begin(pipe->hc_sess); aid_t opening_request = async_send_5(exch, DEV_IFACE_ID(USBHC_DEV_IFACE), IPC_M_USBHC_WRITE, target.packed, data_buffer_size, (setup_packet & UINT32_MAX), (setup_packet >> 32), NULL); if (opening_request == 0) { async_exchange_end(exch); pipe_end_transaction(pipe); return ENOMEM; } /* * Send the data (if any). */ if (data_buffer_size > 0) { int rc = async_data_write_start(exch, data_buffer, data_buffer_size); /* All data sent, pipe can be released. */ async_exchange_end(exch); pipe_end_transaction(pipe); if (rc != EOK) { async_wait_for(opening_request, NULL); return rc; } } else { /* No data to send, we can release the pipe for others. */ async_exchange_end(exch); pipe_end_transaction(pipe); } /* * Wait for the answer. */ sysarg_t opening_request_rc; async_wait_for(opening_request, &opening_request_rc); return (int) opening_request_rc; } /** Request a control write transfer on an endpoint pipe. * * This function encapsulates all three stages of a control transfer. * * @param[in] pipe Pipe used for the transfer. * @param[in] setup_buffer Buffer with the setup packet. * @param[in] setup_buffer_size Size of the setup packet (in bytes). * @param[in] data_buffer Buffer with data to be sent. * @param[in] data_buffer_size Size of the buffer with outgoing data (in bytes). * @return Error code. */ int usb_pipe_control_write(usb_pipe_t *pipe, const void *setup_buffer, size_t setup_buffer_size, const void *data_buffer, size_t data_buffer_size) { assert(pipe); if ((setup_buffer == NULL) || (setup_buffer_size == 0)) { return EINVAL; } if ((data_buffer == NULL) && (data_buffer_size > 0)) { return EINVAL; } if ((data_buffer != NULL) && (data_buffer_size == 0)) { return EINVAL; } if ((pipe->direction != USB_DIRECTION_BOTH) || (pipe->transfer_type != USB_TRANSFER_CONTROL)) { return EBADF; } int rc; rc = pipe_add_ref(pipe, false); if (rc != EOK) { return rc; } rc = usb_pipe_control_write_no_check(pipe, setup_buffer, setup_buffer_size, data_buffer, data_buffer_size); if (rc == ESTALL) { clear_self_endpoint_halt(pipe); } pipe_drop_ref(pipe); return rc; } /** * @} */