| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2019 - 2021 DisplayLink (UK) Ltd. |
| */ |
| #include "hpal_data.h" |
| |
| #include <linux/slab.h> |
| #include <linux/uio.h> |
| |
| #include "hcd.h" |
| #include "hpal.h" |
| #include "hpal_events.h" |
| #include "utils.h" |
| |
| int mausb_send_in_data_msg(struct mausb_device *dev, struct mausb_event *event) |
| { |
| struct mausb_kvec_data_wrapper data_to_send; |
| struct kvec kvec[2]; |
| struct urb *urb = (struct urb *)(event->data.urb); |
| bool setup_packet = (usb_endpoint_xfer_control(&urb->ep->desc) && |
| urb->setup_packet); |
| u32 kvec_num = setup_packet ? 2 : 1; |
| enum mausb_channel channel; |
| |
| data_to_send.kvec_num = kvec_num; |
| data_to_send.length = MAUSB_TRANSFER_HDR_SIZE + |
| (setup_packet ? MAUSB_CONTROL_SETUP_SIZE : 0); |
| |
| /* Prepare transfer header kvec */ |
| kvec[0].iov_base = event->data.hdr; |
| kvec[0].iov_len = MAUSB_TRANSFER_HDR_SIZE; |
| |
| /* Prepare setup packet kvec */ |
| if (setup_packet) { |
| kvec[1].iov_base = urb->setup_packet; |
| kvec[1].iov_len = MAUSB_CONTROL_SETUP_SIZE; |
| } |
| data_to_send.kvec = kvec; |
| |
| channel = mausb_transfer_type_to_channel(event->data.transfer_type); |
| return mausb_send_data(dev, channel, &data_to_send); |
| } |
| |
| void mausb_receive_in_data(struct mausb_event *event, |
| struct mausb_urb_ctx *urb_ctx) |
| { |
| struct urb *urb = urb_ctx->urb; |
| struct mausb_data_iter *iterator = &urb_ctx->iterator; |
| struct ma_usb_hdr_common *common_hdr = |
| (struct ma_usb_hdr_common *)event->data.recv_buf; |
| void *buffer; |
| u32 payload_size = common_hdr->length - MAUSB_TRANSFER_HDR_SIZE; |
| u32 data_written = 0; |
| |
| buffer = shift_ptr(common_hdr, MAUSB_TRANSFER_HDR_SIZE); |
| data_written = mausb_data_iterator_write(iterator, buffer, |
| payload_size); |
| |
| dev_vdbg(mausb_host_dev.this_device, "data_written=%d, payload_size=%d", |
| data_written, payload_size); |
| |
| event->data.rem_transfer_size -= data_written; |
| |
| if (event->data.transfer_eot) { |
| dev_vdbg(mausb_host_dev.this_device, "transfer_size=%d, rem_transfer_size=%d, status=%d", |
| event->data.transfer_size, |
| event->data.rem_transfer_size, event->status); |
| mausb_complete_request(urb, event->data.transfer_size - |
| event->data.rem_transfer_size, |
| event->status); |
| } |
| } |
| |
| static int |
| mausb_init_data_out_header_chunk(struct ma_usb_hdr_common *common_hdr, |
| struct list_head *chunks_list, |
| u32 *num_of_data_chunks) |
| { |
| int status = mausb_add_data_chunk(common_hdr, MAUSB_TRANSFER_HDR_SIZE, |
| chunks_list); |
| if (!status) |
| ++(*num_of_data_chunks); |
| |
| return status; |
| } |
| |
| static int mausb_init_control_data_chunk(struct mausb_event *event, |
| struct list_head *chunks_list, |
| u32 *num_of_data_chunks) |
| { |
| int status; |
| void *buffer = ((struct urb *)event->data.urb)->setup_packet; |
| |
| if (!event->data.first_control_packet) |
| return 0; |
| |
| status = mausb_add_data_chunk(buffer, MAUSB_CONTROL_SETUP_SIZE, |
| chunks_list); |
| if (!status) |
| ++(*num_of_data_chunks); |
| |
| return status; |
| } |
| |
| static int |
| mausb_prepare_transfer_packet(struct mausb_kvec_data_wrapper *wrapper, |
| struct mausb_event *event, |
| struct mausb_data_iter *iterator) |
| { |
| u32 num_of_data_chunks = 0; |
| u32 num_of_payload_data_chunks = 0; |
| u32 payload_data_size = 0; |
| int status = 0; |
| struct list_head chunks_list; |
| struct list_head payload_data_chunks; |
| struct ma_usb_hdr_common *data_hdr = (struct ma_usb_hdr_common *) |
| event->data.hdr; |
| |
| INIT_LIST_HEAD(&chunks_list); |
| |
| /* Initialize data chunk for MAUSB header and add it to chunks list */ |
| if (mausb_init_data_out_header_chunk(data_hdr, &chunks_list, |
| &num_of_data_chunks) < 0) { |
| status = -ENOMEM; |
| goto cleanup_data_chunks; |
| } |
| |
| /* |
| * Initialize data chunk for MAUSB control setup packet and |
| * add it to chunks list |
| */ |
| if (mausb_init_control_data_chunk(event, &chunks_list, |
| &num_of_data_chunks) < 0) { |
| status = -ENOMEM; |
| goto cleanup_data_chunks; |
| } |
| |
| /* Get data chunks for data payload to send */ |
| INIT_LIST_HEAD(&payload_data_chunks); |
| payload_data_size = |
| ((struct ma_usb_hdr_common *)event->data.hdr)->length - |
| MAUSB_TRANSFER_HDR_SIZE - |
| (event->data.first_control_packet ? |
| MAUSB_CONTROL_SETUP_SIZE : 0); |
| |
| if (mausb_data_iterator_read(iterator, payload_data_size, |
| &payload_data_chunks, |
| &num_of_payload_data_chunks) < 0) { |
| status = -ENOMEM; |
| goto cleanup_data_chunks; |
| } |
| |
| list_splice_tail(&payload_data_chunks, &chunks_list); |
| num_of_data_chunks += num_of_payload_data_chunks; |
| |
| /* Map all data chunks to data wrapper */ |
| if (mausb_init_data_wrapper(wrapper, &chunks_list, |
| num_of_data_chunks) < 0) { |
| status = -ENOMEM; |
| goto cleanup_data_chunks; |
| } |
| |
| cleanup_data_chunks: /* Cleanup all allocated data chunks */ |
| mausb_cleanup_chunks_list(&chunks_list); |
| return status; |
| } |
| |
| int mausb_send_out_data_msg(struct mausb_device *dev, struct mausb_event *event, |
| struct mausb_urb_ctx *urb_ctx) |
| { |
| int status; |
| struct mausb_kvec_data_wrapper data; |
| enum mausb_channel channel; |
| |
| status = mausb_prepare_transfer_packet(&data, event, |
| &urb_ctx->iterator); |
| if (status < 0) { |
| dev_err(mausb_host_dev.this_device, "Failed to prepare transfer packet"); |
| return status; |
| } |
| |
| channel = mausb_transfer_type_to_channel(event->data.transfer_type); |
| status = mausb_send_data(dev, channel, &data); |
| |
| kfree(data.kvec); |
| |
| return status; |
| } |
| |
| void mausb_receive_out_data(struct mausb_event *event, |
| struct mausb_urb_ctx *urb_ctx) |
| { |
| struct urb *urb = urb_ctx->urb; |
| |
| dev_vdbg(mausb_host_dev.this_device, "transfer_size=%d, rem_transfer_size=%d, status=%d", |
| event->data.transfer_size, event->data.rem_transfer_size, |
| event->status); |
| |
| if (event->data.transfer_eot) { |
| mausb_complete_request(urb, urb->transfer_buffer_length - |
| event->data.rem_transfer_size, |
| event->status); |
| } |
| } |
| |
| static inline u32 |
| __mausb_isoch_prepare_read_size_block(struct ma_usb_hdr_isochreadsizeblock_std * |
| isoch_readsize_block, struct urb *urb) |
| { |
| u32 i; |
| u32 number_of_packets = (u32)urb->number_of_packets; |
| |
| if (number_of_packets == 0) |
| return 0; |
| |
| isoch_readsize_block->service_intervals = number_of_packets; |
| isoch_readsize_block->max_segment_length = |
| (u32)urb->iso_frame_desc[0].length; |
| |
| for (i = 0; i < number_of_packets; ++i) { |
| urb->iso_frame_desc[i].status = 0; |
| urb->iso_frame_desc[i].actual_length = 0; |
| } |
| |
| return sizeof(struct ma_usb_hdr_isochreadsizeblock_std); |
| } |
| |
| int mausb_send_isoch_in_msg(struct mausb_device *dev, struct mausb_event *event) |
| { |
| u32 read_size_block_length = 0; |
| struct mausb_kvec_data_wrapper data_to_send; |
| struct kvec kvec[MAUSB_ISOCH_IN_KVEC_NUM]; |
| struct ma_usb_hdr_isochtransfer_optional opt_isoch_hdr; |
| struct ma_usb_hdr_isochreadsizeblock_std isoch_readsize_block; |
| struct ma_usb_hdr_common *hdr = |
| (struct ma_usb_hdr_common *)event->data.hdr; |
| struct urb *urb = (struct urb *)event->data.urb; |
| enum mausb_channel channel; |
| |
| data_to_send.kvec_num = 0; |
| data_to_send.length = 0; |
| |
| /* Prepare transfer header kvec */ |
| kvec[0].iov_base = event->data.hdr; |
| kvec[0].iov_len = MAUSB_TRANSFER_HDR_SIZE; |
| data_to_send.length += (u32)kvec[0].iov_len; |
| data_to_send.kvec_num++; |
| |
| /* Prepare optional header kvec */ |
| opt_isoch_hdr.timestamp = MA_USB_TRANSFER_RESERVED; |
| opt_isoch_hdr.mtd = MA_USB_TRANSFER_RESERVED; |
| |
| kvec[1].iov_base = &opt_isoch_hdr; |
| kvec[1].iov_len = sizeof(struct ma_usb_hdr_isochtransfer_optional); |
| data_to_send.length += (u32)kvec[1].iov_len; |
| data_to_send.kvec_num++; |
| |
| /* Prepare read size blocks */ |
| read_size_block_length = |
| __mausb_isoch_prepare_read_size_block(&isoch_readsize_block, |
| urb); |
| if (read_size_block_length > 0) { |
| kvec[2].iov_base = &isoch_readsize_block; |
| kvec[2].iov_len = read_size_block_length; |
| data_to_send.length += (u32)kvec[2].iov_len; |
| data_to_send.kvec_num++; |
| } |
| |
| hdr->length = (u16)data_to_send.length; |
| data_to_send.kvec = kvec; |
| |
| channel = mausb_transfer_type_to_channel(event->data.transfer_type); |
| return mausb_send_data(dev, channel, &data_to_send); |
| } |
| |
| static void __mausb_process_in_isoch_short_resp(struct mausb_event *event, |
| struct ma_usb_hdr_common *hdr, |
| struct mausb_urb_ctx *urb_ctx) |
| { |
| u8 opt_hdr_shift = (hdr->flags & MA_USB_HDR_FLAGS_TIMESTAMP) ? |
| sizeof(struct ma_usb_hdr_isochtransfer_optional) : 0; |
| struct ma_usb_hdr_isochdatablock_short *data_block_hdr = |
| (struct ma_usb_hdr_isochdatablock_short *) |
| shift_ptr(mausb_hdr_isochtransfer_optional_hdr(hdr), |
| opt_hdr_shift); |
| u8 *isoch_data = shift_ptr(data_block_hdr, hdr->data.headers * |
| sizeof(*data_block_hdr)); |
| u8 *end_of_packet = shift_ptr(hdr, hdr->length); |
| struct urb *urb = urb_ctx->urb; |
| int i; |
| |
| if (isoch_data >= end_of_packet) { |
| dev_err(mausb_host_dev.this_device, "Bad header data. Data start pointer after end of packet: ep_handle=%#x", |
| event->data.ep_handle); |
| return; |
| } |
| |
| for (i = 0; i < hdr->data.headers; ++i) { |
| u16 seg_num = data_block_hdr[i].segment_number; |
| u16 seg_size = data_block_hdr[i].block_length; |
| |
| if (seg_num >= urb->number_of_packets) { |
| dev_err(mausb_host_dev.this_device, "Too many segments: ep_handle=%#x, seg_num=%d, urb.number_of_packets=%d", |
| event->data.ep_handle, seg_num, |
| urb->number_of_packets); |
| break; |
| } |
| |
| if (seg_size > urb->iso_frame_desc[seg_num].length) { |
| dev_err(mausb_host_dev.this_device, "Block to long for segment: ep_handle=%#x", |
| event->data.ep_handle); |
| break; |
| } |
| |
| if (shift_ptr(isoch_data, seg_size) > end_of_packet) { |
| dev_err(mausb_host_dev.this_device, "End of segment after enf of packet: ep_handle=%#x", |
| event->data.ep_handle); |
| break; |
| } |
| |
| mausb_reset_data_iterator(&urb_ctx->iterator); |
| mausb_data_iterator_seek(&urb_ctx->iterator, |
| urb->iso_frame_desc[seg_num].offset); |
| mausb_data_iterator_write(&urb_ctx->iterator, isoch_data, |
| seg_size); |
| |
| isoch_data = shift_ptr(isoch_data, seg_size); |
| |
| urb->iso_frame_desc[seg_num].actual_length = seg_size; |
| urb->iso_frame_desc[seg_num].status = 0; |
| } |
| } |
| |
| static void __mausb_process_in_isoch_std_resp(struct mausb_event *event, |
| struct ma_usb_hdr_common *hdr, |
| struct mausb_urb_ctx *urb_ctx) |
| { |
| u8 opt_hdr_shift = (hdr->flags & MA_USB_HDR_FLAGS_TIMESTAMP) ? |
| sizeof(struct ma_usb_hdr_isochtransfer_optional) : 0; |
| struct ma_usb_hdr_isochdatablock_std *data_block_hdr = |
| (struct ma_usb_hdr_isochdatablock_std *) |
| shift_ptr(mausb_hdr_isochtransfer_optional_hdr(hdr), |
| opt_hdr_shift); |
| u8 *isoch_data = |
| shift_ptr(data_block_hdr, hdr->data.headers * |
| sizeof(struct ma_usb_hdr_isochdatablock_std)); |
| u8 *end_of_packet = shift_ptr(hdr, hdr->length); |
| struct urb *urb = (struct urb *)event->data.urb; |
| int i; |
| |
| if (isoch_data >= end_of_packet) { |
| dev_err(mausb_host_dev.this_device, "Bad header data. Data start pointer after end of packet: ep_handle=%#x", |
| event->data.ep_handle); |
| return; |
| } |
| |
| for (i = 0; i < hdr->data.headers; ++i) { |
| u16 seg_num = data_block_hdr[i].segment_number; |
| u16 seg_len = data_block_hdr[i].segment_length; |
| u16 block_len = data_block_hdr[i].block_length; |
| |
| if (seg_num >= urb->number_of_packets) { |
| dev_err(mausb_host_dev.this_device, "Too many segments: ep_handle=%#x, seg_num=%d, number_of_packets=%d", |
| event->data.ep_handle, seg_num, |
| urb->number_of_packets); |
| break; |
| } |
| |
| if (block_len > urb->iso_frame_desc[seg_num].length - |
| urb->iso_frame_desc[seg_num].actual_length) { |
| dev_err(mausb_host_dev.this_device, "Block too long for segment: ep_handle=%#x", |
| event->data.ep_handle); |
| break; |
| } |
| |
| if (shift_ptr(isoch_data, block_len) > |
| end_of_packet) { |
| dev_err(mausb_host_dev.this_device, "End of fragment after end of packet: ep_handle=%#x", |
| event->data.ep_handle); |
| break; |
| } |
| |
| mausb_reset_data_iterator(&urb_ctx->iterator); |
| mausb_data_iterator_seek(&urb_ctx->iterator, |
| urb->iso_frame_desc[seg_num].offset + |
| data_block_hdr[i].fragment_offset); |
| mausb_data_iterator_write(&urb_ctx->iterator, |
| isoch_data, block_len); |
| isoch_data = shift_ptr(isoch_data, block_len); |
| |
| urb->iso_frame_desc[seg_num].actual_length += block_len; |
| |
| if (urb->iso_frame_desc[seg_num].actual_length == seg_len) |
| urb->iso_frame_desc[seg_num].status = 0; |
| } |
| } |
| |
| void mausb_receive_isoch_in_data(struct mausb_device *dev, |
| struct mausb_event *event, |
| struct mausb_urb_ctx *urb_ctx) |
| { |
| struct ma_usb_hdr_common *common_hdr = |
| (struct ma_usb_hdr_common *)event->data.recv_buf; |
| struct ma_usb_hdr_transfer *transfer_hdr = |
| mausb_get_data_transfer_hdr(common_hdr); |
| |
| if (!(common_hdr->data.i_flags & MA_USB_DATA_IFLAGS_FMT_MASK)) { |
| /* Short ISO headers response */ |
| __mausb_process_in_isoch_short_resp(event, common_hdr, urb_ctx); |
| } else if ((common_hdr->data.i_flags & MA_USB_DATA_IFLAGS_FMT_MASK) & |
| MA_USB_DATA_IFLAGS_HDR_FMT_STD) { |
| /* Standard ISO headers response */ |
| __mausb_process_in_isoch_std_resp(event, common_hdr, urb_ctx); |
| } else if ((common_hdr->data.i_flags & MA_USB_DATA_IFLAGS_FMT_MASK) & |
| MA_USB_DATA_IFLAGS_HDR_FMT_LONG) { |
| /* Long ISO headers response */ |
| dev_warn(mausb_host_dev.this_device, "Long isoc headers in response: ep_handle=%#x, req_id=%#x", |
| event->data.ep_handle, transfer_hdr->req_id); |
| } else { |
| /* Error */ |
| dev_err(mausb_host_dev.this_device, "Isoc header error in response: ep_handle=%#x, req_id=%#x", |
| event->data.ep_handle, transfer_hdr->req_id); |
| } |
| } |
| |
| static inline u32 |
| __mausb_calculate_isoch_common_header_size(u32 num_of_segments) |
| { |
| return MAUSB_ISOCH_TRANSFER_HDR_SIZE + |
| MAUSB_ISOCH_STANDARD_FORMAT_SIZE * num_of_segments; |
| } |
| |
| static struct ma_usb_hdr_common * |
| __mausb_create_isoch_out_transfer_packet(struct mausb_event *event, |
| struct mausb_urb_ctx *urb_ctx, |
| u16 payload_size, u32 seq_n, |
| u32 start_of_segments, |
| u32 number_of_segments) |
| { |
| struct ma_usb_hdr_common *hdr; |
| struct ma_usb_hdr_isochtransfer *hdr_isochtransfer; |
| struct ma_usb_hdr_isochdatablock_std *isoc_header_std; |
| struct ma_usb_hdr_isochtransfer_optional *hdr_opt_isochtransfer; |
| struct urb *urb = (struct urb *)event->data.urb; |
| void *isoc_headers = NULL; |
| u32 length; |
| u16 i; |
| unsigned long block_length; |
| u32 number_of_packets = (u32)event->data.isoch_seg_num; |
| u32 size_of_request = |
| __mausb_calculate_isoch_common_header_size(number_of_segments); |
| |
| hdr = kzalloc(size_of_request, GFP_KERNEL); |
| if (!hdr) |
| return NULL; |
| |
| hdr->version = MA_USB_HDR_VERSION_1_0; |
| hdr->ssid = event->data.mausb_ssid; |
| hdr->flags = MA_USB_HDR_FLAGS_HOST; |
| hdr->dev_addr = event->data.mausb_address; |
| hdr->handle.epv = event->data.ep_handle; |
| hdr->data.status = MA_USB_HDR_STATUS_NO_ERROR; |
| hdr->data.eps = MAUSB_TRANSFER_RESERVED; |
| hdr->data.t_flags = (u8)(usb_endpoint_type(&urb->ep->desc) << 3); |
| |
| isoc_headers = shift_ptr(hdr, MAUSB_ISOCH_TRANSFER_HDR_SIZE); |
| |
| for (i = (u16)start_of_segments; |
| i < number_of_segments + start_of_segments; ++i) { |
| block_length = i < number_of_packets - 1 ? |
| urb->iso_frame_desc[i + 1].offset - |
| urb->iso_frame_desc[i].offset : |
| mausb_data_iterator_length(&urb_ctx->iterator) - |
| urb->iso_frame_desc[i].offset; |
| |
| urb->iso_frame_desc[i].status = MA_USB_HDR_STATUS_UNSUCCESSFUL; |
| isoc_header_std = (struct ma_usb_hdr_isochdatablock_std *) |
| shift_ptr(isoc_headers, |
| (u64)MAUSB_ISOCH_STANDARD_FORMAT_SIZE * |
| (i - start_of_segments)); |
| isoc_header_std->block_length = (u16)block_length; |
| isoc_header_std->segment_number = i; |
| isoc_header_std->s_flags = 0; |
| isoc_header_std->segment_length = (u16)block_length; |
| isoc_header_std->fragment_offset = 0; |
| } |
| |
| length = __mausb_calculate_isoch_common_header_size(number_of_segments); |
| |
| hdr->flags |= MA_USB_HDR_FLAGS_TIMESTAMP; |
| hdr->type = (u8)MA_USB_HDR_TYPE_DATA_REQ(ISOCHTRANSFER); |
| hdr->data.headers = (u16)number_of_segments; |
| hdr->data.i_flags = MA_USB_DATA_IFLAGS_HDR_FMT_STD | |
| MA_USB_DATA_IFLAGS_ASAP; |
| hdr_opt_isochtransfer = mausb_hdr_isochtransfer_optional_hdr(hdr); |
| hdr_isochtransfer = mausb_get_isochtransfer_hdr(hdr); |
| hdr_isochtransfer->req_id = event->data.req_id; |
| hdr_isochtransfer->seq_n = seq_n; |
| hdr_isochtransfer->segments = number_of_packets; |
| |
| hdr_isochtransfer->presentation_time = MA_USB_TRANSFER_RESERVED; |
| |
| hdr_opt_isochtransfer->timestamp = MA_USB_TRANSFER_RESERVED; |
| hdr_opt_isochtransfer->mtd = MA_USB_TRANSFER_RESERVED; |
| |
| hdr->length = (u16)length + payload_size; |
| |
| return hdr; |
| } |
| |
| static int |
| mausb_init_isoch_out_header_chunk(struct ma_usb_hdr_common *common_hdr, |
| struct list_head *chunks_list, |
| u32 *num_of_data_chunks, |
| u32 num_of_packets) |
| { |
| u32 header_size = |
| __mausb_calculate_isoch_common_header_size(num_of_packets); |
| int status = mausb_add_data_chunk(common_hdr, header_size, chunks_list); |
| |
| if (!status) |
| ++(*num_of_data_chunks); |
| |
| return status; |
| } |
| |
| static |
| int mausb_prepare_isoch_out_transfer_packet(struct ma_usb_hdr_common *hdr, |
| struct mausb_event *event, |
| struct mausb_urb_ctx *urb_ctx, |
| struct mausb_kvec_data_wrapper * |
| result_data_wrapper) |
| { |
| u32 num_of_data_chunks = 0; |
| u32 num_of_payload_data_chunks = 0; |
| u32 segment_number = event->data.isoch_seg_num; |
| u32 payload_data_size; |
| struct list_head chunks_list; |
| struct list_head payload_data_chunks; |
| int status = 0; |
| |
| INIT_LIST_HEAD(&chunks_list); |
| |
| /* Initialize data chunk for MAUSB header and add it to chunks list */ |
| if (mausb_init_isoch_out_header_chunk(hdr, &chunks_list, |
| &num_of_data_chunks, |
| segment_number) < 0) { |
| status = -ENOMEM; |
| goto cleanup_data_chunks; |
| } |
| |
| /* Get data chunks for data payload to send */ |
| INIT_LIST_HEAD(&payload_data_chunks); |
| payload_data_size = hdr->length - |
| __mausb_calculate_isoch_common_header_size(segment_number); |
| |
| if (mausb_data_iterator_read(&urb_ctx->iterator, payload_data_size, |
| &payload_data_chunks, |
| &num_of_payload_data_chunks) < 0) { |
| dev_err(mausb_host_dev.this_device, "Data iterator read failed"); |
| status = -ENOMEM; |
| goto cleanup_data_chunks; |
| } |
| |
| list_splice_tail(&payload_data_chunks, &chunks_list); |
| num_of_data_chunks += num_of_payload_data_chunks; |
| |
| /* Map all data chunks to data wrapper */ |
| if (mausb_init_data_wrapper(result_data_wrapper, &chunks_list, |
| num_of_data_chunks) < 0) { |
| dev_err(mausb_host_dev.this_device, "Data wrapper init failed"); |
| status = -ENOMEM; |
| goto cleanup_data_chunks; |
| } |
| |
| cleanup_data_chunks: |
| mausb_cleanup_chunks_list(&chunks_list); |
| return status; |
| } |
| |
| static int mausb_create_and_send_isoch_transfer_req(struct mausb_device *dev, |
| struct mausb_event *event, |
| struct mausb_urb_ctx |
| *urb_ctx, u32 *seq_n, |
| u32 payload_size, |
| u32 start_of_segments, |
| u32 number_of_segments) |
| { |
| struct ma_usb_hdr_common *hdr; |
| struct mausb_kvec_data_wrapper data_to_send; |
| int status; |
| enum mausb_channel channel; |
| |
| hdr = __mausb_create_isoch_out_transfer_packet(event, urb_ctx, |
| (u16)payload_size, |
| *seq_n, |
| start_of_segments, |
| number_of_segments); |
| if (!hdr) { |
| dev_alert(mausb_host_dev.this_device, "Isoch transfer packet alloc failed"); |
| return -ENOMEM; |
| } |
| *seq_n = (*seq_n + 1) % (MA_USB_TRANSFER_SEQN_MAX + 1); |
| |
| status = mausb_prepare_isoch_out_transfer_packet(hdr, event, urb_ctx, |
| &data_to_send); |
| if (status < 0) { |
| dev_alert(mausb_host_dev.this_device, "Failed to prepare transfer packet"); |
| kfree(hdr); |
| return status; |
| } |
| |
| channel = mausb_transfer_type_to_channel(event->data.transfer_type); |
| status = mausb_send_data(dev, channel, &data_to_send); |
| |
| kfree(hdr); |
| kfree(data_to_send.kvec); |
| |
| return status; |
| } |
| |
| static inline int __mausb_send_isoch_out_packet(struct mausb_device *dev, |
| struct mausb_event *event, |
| struct mausb_urb_ctx *urb_ctx, |
| u32 *seq_n, |
| u32 *starting_segments, |
| u32 *rem_transfer_buf, |
| u32 *payload_size, u32 index) |
| { |
| int status = mausb_create_and_send_isoch_transfer_req(dev, event, |
| urb_ctx, seq_n, *payload_size, |
| *starting_segments, |
| index - *starting_segments); |
| if (status < 0) { |
| dev_err(mausb_host_dev.this_device, "ISOCH transfer request create and send failed"); |
| return status; |
| } |
| *starting_segments = index; |
| *rem_transfer_buf = MAX_ISOCH_ASAP_PACKET_SIZE; |
| *payload_size = 0; |
| |
| return 0; |
| } |
| |
| int mausb_send_isoch_out_msg(struct mausb_device *ma_dev, |
| struct mausb_event *mausb_event, |
| struct mausb_urb_ctx *urb_ctx) |
| { |
| u32 starting_segments = 0; |
| u32 rem_transfer_buf = MAX_ISOCH_ASAP_PACKET_SIZE; |
| struct urb *urb = (struct urb *)mausb_event->data.urb; |
| u32 number_of_packets = (u32)urb->number_of_packets; |
| u32 payload_size = 0; |
| u32 chunk_size; |
| u32 seq_n = 0; |
| int status; |
| u32 i; |
| |
| for (i = 0; i < number_of_packets; ++i) { |
| if (i < number_of_packets - 1) |
| chunk_size = urb->iso_frame_desc[i + 1].offset - |
| urb->iso_frame_desc[i].offset; |
| else |
| chunk_size = |
| mausb_data_iterator_length(&urb_ctx->iterator) - |
| urb->iso_frame_desc[i].offset; |
| |
| if (chunk_size + MAUSB_ISOCH_STANDARD_FORMAT_SIZE > |
| rem_transfer_buf) { |
| if (payload_size == 0) { |
| dev_warn(mausb_host_dev.this_device, "Fragmented"); |
| } else { |
| status = __mausb_send_isoch_out_packet |
| (ma_dev, mausb_event, urb_ctx, |
| &seq_n, &starting_segments, |
| &rem_transfer_buf, |
| &payload_size, i); |
| if (status < 0) |
| return status; |
| i--; |
| continue; |
| } |
| } else { |
| rem_transfer_buf -= |
| chunk_size + MAUSB_ISOCH_STANDARD_FORMAT_SIZE; |
| payload_size += chunk_size; |
| } |
| |
| if (i == number_of_packets - 1 || rem_transfer_buf == 0) { |
| status = __mausb_send_isoch_out_packet |
| (ma_dev, mausb_event, urb_ctx, &seq_n, |
| &starting_segments, &rem_transfer_buf, |
| &payload_size, i + 1); |
| if (status < 0) |
| return status; |
| } |
| } |
| return 0; |
| } |
| |
| void mausb_receive_isoch_out(struct mausb_event *event) |
| { |
| struct urb *urb = (struct urb *)event->data.urb; |
| u16 i; |
| |
| dev_vdbg(mausb_host_dev.this_device, "transfer_size=%d, rem_transfer_size=%d, status=%d", |
| event->data.transfer_size, event->data.rem_transfer_size, |
| event->status); |
| |
| for (i = 0; i < urb->number_of_packets; ++i) |
| urb->iso_frame_desc[i].status = event->status; |
| |
| mausb_complete_request(urb, event->data.payload_size, event->status); |
| } |