Added implementation of MA USB data&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 fd2a36a04ad6..a5fd033c002e 100644
--- a/drivers/usb/mausb_host/Makefile
+++ b/drivers/usb/mausb_host/Makefile
@@ -12,5 +12,6 @@ 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)
diff --git a/drivers/usb/mausb_host/hpal.c b/drivers/usb/mausb_host/hpal.c
index 1e7bbe3b230a..ecbb8e6d6f84 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"
@@ -1387,6 +1388,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",
@@ -1401,9 +1403,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)
@@ -1426,6 +1441,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:
@@ -1593,7 +1622,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