Added implementation of MA USB data and isoch packets processing logic, both for IN and OUT directions. Signed-off-by: Vladimir Stankovic <vladimir.stankovic@xxxxxxxxxxxxxxx> --- drivers/usb/mausb_host/Makefile | 1 + drivers/usb/mausb_host/hpal.c | 32 +- drivers/usb/mausb_host/hpal_data.c | 719 +++++++++++++++++++++++++++++ drivers/usb/mausb_host/hpal_data.h | 34 ++ 4 files changed, 784 insertions(+), 2 deletions(-) create mode 100644 drivers/usb/mausb_host/hpal_data.c create mode 100644 drivers/usb/mausb_host/hpal_data.h diff --git a/drivers/usb/mausb_host/Makefile b/drivers/usb/mausb_host/Makefile index a9ae92d60f06..0dff6c9febf8 100644 --- a/drivers/usb/mausb_host/Makefile +++ b/drivers/usb/mausb_host/Makefile @@ -12,6 +12,7 @@ mausb_host-y += ip_link.o mausb_host-y += hcd.o mausb_host-y += hpal.o mausb_host-y += hpal_events.o +mausb_host-y += hpal_data.o ccflags-y += -I$(srctree)/$(src) ccflags-y += -g diff --git a/drivers/usb/mausb_host/hpal.c b/drivers/usb/mausb_host/hpal.c index 29e3b7bc3d75..4b86e2e9989a 100644 --- a/drivers/usb/mausb_host/hpal.c +++ b/drivers/usb/mausb_host/hpal.c @@ -11,6 +11,7 @@ #include <linux/uio.h> #include "hcd.h" +#include "hpal_data.h" #include "hpal_events.h" #include "utils.h" @@ -1386,6 +1387,7 @@ int mausb_send_transfer_ack(struct mausb_device *dev, struct mausb_event *event) int mausb_send_data_msg(struct mausb_device *dev, struct mausb_event *event) { struct mausb_urb_ctx *urb_ctx; + int status = 0; if (event->status != 0) { mausb_pr_err("Event %d failed with status %d", @@ -1400,9 +1402,22 @@ int mausb_send_data_msg(struct mausb_device *dev, struct mausb_event *event) /* Transfer will be deleted from dequeue task */ mausb_pr_warn("Urb is already cancelled for event=%d", event->type); + return status; } - return 0; + if (mausb_isoch_data_event(event)) { + if (event->data.direction == MAUSB_DATA_MSG_DIRECTION_IN) + status = mausb_send_isoch_in_msg(dev, event); + else + status = mausb_send_isoch_out_msg(dev, event, urb_ctx); + } else { + if (event->data.direction == MAUSB_DATA_MSG_DIRECTION_IN) + status = mausb_send_in_data_msg(dev, event); + else + status = mausb_send_out_data_msg(dev, event, urb_ctx); + } + + return status; } int mausb_receive_data_msg(struct mausb_device *dev, struct mausb_event *event) @@ -1425,6 +1440,20 @@ int mausb_receive_data_msg(struct mausb_device *dev, struct mausb_event *event) if (!urb_ctx) { /* Transfer will be deleted from dequeue task */ mausb_pr_warn("Urb is already cancelled"); + goto cleanup; + } + + if (mausb_isoch_data_event(event)) { + if (event->data.direction == MAUSB_DATA_MSG_DIRECTION_IN) + status = mausb_receive_isoch_in_data(dev, event, + urb_ctx); + else + status = mausb_receive_isoch_out(event); + } else { + if (event->data.direction == MAUSB_DATA_MSG_DIRECTION_IN) + mausb_receive_in_data(event, urb_ctx); + else + mausb_receive_out_data(event, urb_ctx); } cleanup: @@ -1592,7 +1621,6 @@ static void mausb_handle_receive_event(struct mausb_device *dev, status = mausb_msg_received_event(&event, (struct ma_usb_hdr_common *)data, channel); - if (status == 0) status = mausb_enqueue_event_to_user(dev, &event); diff --git a/drivers/usb/mausb_host/hpal_data.c b/drivers/usb/mausb_host/hpal_data.c new file mode 100644 index 000000000000..bf076418e596 --- /dev/null +++ b/drivers/usb/mausb_host/hpal_data.c @@ -0,0 +1,719 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 - 2020 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); + + mausb_pr_debug("data_written=%d, payload_size=%d", data_written, + payload_size); + event->data.rem_transfer_size -= data_written; + + if (event->data.transfer_eot) { + mausb_pr_debug("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) { + mausb_pr_err("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; + + mausb_pr_debug("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) { + mausb_pr_err("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) { + mausb_pr_err("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) { + mausb_pr_err("Block to long for segment: ep_handle=%#x", + event->data.ep_handle); + break; + } + + if (shift_ptr(isoch_data, seg_size) > end_of_packet) { + mausb_pr_err("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) { + mausb_pr_err("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) { + mausb_pr_err("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) { + mausb_pr_err("Block too long for segment: ep_handle=%#x", + event->data.ep_handle); + break; + } + + if (shift_ptr(isoch_data, block_len) > + end_of_packet) { + mausb_pr_err("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; + } +} + +int 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 */ + mausb_pr_warn("Long isoc headers in response: ep_handle=%#x, req_id=%#x", + event->data.ep_handle, transfer_hdr->req_id); + } else { + /* Error */ + mausb_pr_err("Isoc header error in response: ep_handle=%#x, req_id=%#x", + event->data.ep_handle, transfer_hdr->req_id); + } + + return 0; +} + +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) { + mausb_pr_err("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) { + mausb_pr_err("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) { + mausb_pr_alert("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) { + mausb_pr_alert("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) { + mausb_pr_err("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) { + mausb_pr_warn("Fragmentation"); + } 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; +} + +int mausb_receive_isoch_out(struct mausb_event *event) +{ + struct urb *urb = (struct urb *)event->data.urb; + int status = 0; + u16 i; + + mausb_pr_debug("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); + + return status; +} diff --git a/drivers/usb/mausb_host/hpal_data.h b/drivers/usb/mausb_host/hpal_data.h new file mode 100644 index 000000000000..8d9650e5fb75 --- /dev/null +++ b/drivers/usb/mausb_host/hpal_data.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 - 2020 DisplayLink (UK) Ltd. + */ +#ifndef __MAUSB_HPAL_DATA_H__ +#define __MAUSB_HPAL_DATA_H__ + +#include <linux/types.h> + +#include "hpal_events.h" + +int mausb_send_in_data_msg(struct mausb_device *dev, struct mausb_event *event); +void mausb_receive_in_data(struct mausb_event *event, + struct mausb_urb_ctx *urb_ctx); + +int mausb_send_out_data_msg(struct mausb_device *dev, struct mausb_event *event, + struct mausb_urb_ctx *urb_ctx); +void mausb_receive_out_data(struct mausb_event *event, + struct mausb_urb_ctx *urb_ctx); + +#define MAUSB_ISOCH_IN_KVEC_NUM 3 + +int mausb_send_isoch_in_msg(struct mausb_device *dev, + struct mausb_event *event); +int mausb_receive_isoch_in_data(struct mausb_device *dev, + struct mausb_event *event, + struct mausb_urb_ctx *urb_ctx); + +int mausb_send_isoch_out_msg(struct mausb_device *ma_dev, + struct mausb_event *mausb_event, + struct mausb_urb_ctx *urb_ctx); +int mausb_receive_isoch_out(struct mausb_event *event); + +#endif /* __MAUSB_HPAL_DATA_H__ */ -- 2.17.1