blob: f6b60c9d6a65097a96465588bba3af3a914e691f [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 - 2021 DisplayLink (UK) Ltd.
*/
#include "hpal.h"
#include <linux/circ_buf.h>
#include <net/net_namespace.h>
#include "hcd.h"
#include "hpal_data.h"
#include "utils.h"
#define MAUSB_DELETE_MADEV_TIMEOUT_MS 3000
struct mss mss;
static int mausb_start_connection_timer(struct mausb_device *dev);
static int mausb_power_state_cb(struct notifier_block *nb, unsigned long action,
void *data);
static void mausb_signal_empty_mss(void);
static void mausb_remove_madev_from_list(u8 madev_addr);
static void mausb_execute_urb_dequeue(struct work_struct *dequeue_work);
static int mausb_start_heartbeat_timer(void);
static inline struct mausb_urb_ctx *__mausb_find_urb_in_tree(struct urb *urb)
{
struct rb_node *node = mhcd->mausb_urbs.rb_node;
while (node) {
struct mausb_urb_ctx *urb_ctx =
rb_entry(node, struct mausb_urb_ctx, rb_node);
if (urb < urb_ctx->urb)
node = urb_ctx->rb_node.rb_left;
else if (urb > urb_ctx->urb)
node = urb_ctx->rb_node.rb_right;
else
return urb_ctx;
}
return NULL;
}
struct mausb_urb_ctx *mausb_find_urb_in_tree(struct urb *urb)
{
unsigned long flags;
struct mausb_urb_ctx *urb_ctx;
spin_lock_irqsave(&mhcd->lock, flags);
urb_ctx = __mausb_find_urb_in_tree(urb);
spin_unlock_irqrestore(&mhcd->lock, flags);
return urb_ctx;
}
static int mausb_insert_urb_ctx_in_tree(struct mausb_urb_ctx *urb_ctx)
{
struct rb_node **new_node = &mhcd->mausb_urbs.rb_node;
struct rb_node *parent = NULL;
struct mausb_urb_ctx *current_urb = NULL;
while (*new_node) {
parent = *new_node;
current_urb = rb_entry(*new_node, struct mausb_urb_ctx,
rb_node);
if (urb_ctx->urb < current_urb->urb)
new_node = &((*new_node)->rb_left);
else if (urb_ctx->urb > current_urb->urb)
new_node = &((*new_node)->rb_right);
else
return -EEXIST;
}
rb_link_node(&urb_ctx->rb_node, parent, new_node);
rb_insert_color(&urb_ctx->rb_node, &mhcd->mausb_urbs);
return 0;
}
static void mausb_delete_urb_ctx_from_tree(struct mausb_urb_ctx *urb_ctx)
{
rb_erase(&urb_ctx->rb_node, &mhcd->mausb_urbs);
}
static struct mausb_urb_ctx *mausb_create_urb_ctx(struct urb *urb, int *status)
{
struct mausb_urb_ctx *urb_ctx = NULL;
if (!urb) {
dev_err(mausb_host_dev.this_device, "Urb is NULL");
*status = -EINVAL;
return NULL;
}
urb_ctx = kzalloc(sizeof(*urb_ctx), GFP_ATOMIC);
if (!urb_ctx) {
*status = -ENOMEM;
return NULL;
}
urb_ctx->urb = urb;
INIT_WORK(&urb_ctx->work, mausb_execute_urb_dequeue);
return urb_ctx;
}
int mausb_insert_urb_in_tree(struct urb *urb, bool link_urb_to_ep)
{
unsigned long flags;
int status = 0;
struct mausb_urb_ctx *urb_ctx = mausb_create_urb_ctx(urb, &status);
if (!urb_ctx)
return status;
spin_lock_irqsave(&mhcd->lock, flags);
if (link_urb_to_ep) {
status = usb_hcd_link_urb_to_ep(urb->hcpriv, urb);
if (status) {
spin_unlock_irqrestore(&mhcd->lock, flags);
dev_err(mausb_host_dev.this_device, "Error %d while linking urb to hcd_endpoint",
status);
kfree(urb_ctx);
return status;
}
}
if (mausb_insert_urb_ctx_in_tree(urb_ctx)) {
kfree(urb_ctx);
if (link_urb_to_ep)
usb_hcd_unlink_urb_from_ep(urb->hcpriv, urb);
spin_unlock_irqrestore(&mhcd->lock, flags);
dev_err(mausb_host_dev.this_device, "Urb_ctx insertion failed");
return -EEXIST;
}
mausb_init_data_iterator(&urb_ctx->iterator, urb->transfer_buffer,
urb->transfer_buffer_length, urb->sg,
(unsigned int)urb->num_sgs,
usb_urb_dir_in(urb));
spin_unlock_irqrestore(&mhcd->lock, flags);
return 0;
}
static bool mausb_return_urb_ctx_to_tree(struct mausb_urb_ctx *urb_ctx,
bool link_urb_to_ep)
{
unsigned long flags;
int status;
if (!urb_ctx)
return false;
spin_lock_irqsave(&mhcd->lock, flags);
if (link_urb_to_ep) {
status = usb_hcd_link_urb_to_ep(urb_ctx->urb->hcpriv,
urb_ctx->urb);
if (status) {
spin_unlock_irqrestore(&mhcd->lock, flags);
dev_err(mausb_host_dev.this_device, "Error %d while linking urb to hcd_endpoint",
status);
return false;
}
}
if (mausb_insert_urb_ctx_in_tree(urb_ctx)) {
if (link_urb_to_ep)
usb_hcd_unlink_urb_from_ep(urb_ctx->urb->hcpriv,
urb_ctx->urb);
spin_unlock_irqrestore(&mhcd->lock, flags);
dev_err(mausb_host_dev.this_device, "Urb_ctx insertion failed");
return false;
}
spin_unlock_irqrestore(&mhcd->lock, flags);
return true;
}
static void mausb_complete_urbs_from_tree(void)
{
struct mausb_urb_ctx *urb_ctx = NULL;
struct urb *current_urb = NULL;
struct rb_node *current_node = NULL;
unsigned long flags;
int status = 0;
int ret;
dev_dbg(mausb_host_dev.this_device, "Completing all urbs from tree");
spin_lock_irqsave(&mhcd->lock, flags);
while ((current_node = rb_first(&mhcd->mausb_urbs))) {
urb_ctx = rb_entry(current_node, struct mausb_urb_ctx, rb_node);
current_urb = urb_ctx->urb;
mausb_delete_urb_ctx_from_tree(urb_ctx);
mausb_uninit_data_iterator(&urb_ctx->iterator);
kfree(urb_ctx);
ret = usb_hcd_check_unlink_urb(current_urb->hcpriv,
current_urb, status);
if (ret == -EIDRM)
dev_warn(mausb_host_dev.this_device, "Urb=%p is already unlinked",
current_urb);
else
usb_hcd_unlink_urb_from_ep(current_urb->hcpriv,
current_urb);
spin_unlock_irqrestore(&mhcd->lock, flags);
/* Prepare urb for completion */
dev_dbg(mausb_host_dev.this_device, "Completing urb=%p",
current_urb);
current_urb->status = -EPROTO;
current_urb->actual_length = 0;
atomic_dec(&current_urb->use_count);
usb_hcd_giveback_urb(current_urb->hcpriv, current_urb,
current_urb->status);
spin_lock_irqsave(&mhcd->lock, flags);
}
spin_unlock_irqrestore(&mhcd->lock, flags);
dev_dbg(mausb_host_dev.this_device, "Completed all urbs from tree");
}
/*After this function call only valid thing to do with urb is to give it back*/
struct mausb_urb_ctx *mausb_unlink_and_delete_urb_from_tree(struct urb *urb,
int status)
{
struct mausb_urb_ctx *urb_ctx = NULL;
unsigned long flags;
int ret;
if (!urb) {
dev_warn(mausb_host_dev.this_device, "URB is NULL");
return NULL;
}
spin_lock_irqsave(&mhcd->lock, flags);
urb_ctx = __mausb_find_urb_in_tree(urb);
if (!urb_ctx) {
dev_warn(mausb_host_dev.this_device, "Urb=%p not in tree", urb);
spin_unlock_irqrestore(&mhcd->lock, flags);
return NULL;
}
ret = usb_hcd_check_unlink_urb(urb->hcpriv, urb, status);
if (ret == -EIDRM)
dev_warn(&urb->dev->dev, "Urb=%p is already unlinked", urb);
else
usb_hcd_unlink_urb_from_ep(urb->hcpriv, urb);
mausb_delete_urb_ctx_from_tree(urb_ctx);
spin_unlock_irqrestore(&mhcd->lock, flags);
dev_vdbg(&urb->dev->dev, "Urb=%p is removed from tree", urb);
return urb_ctx;
}
void mausb_release_event_resources(struct mausb_event *event)
{
struct ma_usb_hdr_common *receive_buffer = (struct ma_usb_hdr_common *)
event->data.recv_buf;
kfree(receive_buffer);
}
static void mausb_iterator_reset(struct mausb_device *dev,
struct mausb_event *event)
{
struct urb *urb = (struct urb *)event->data.urb;
struct mausb_urb_ctx *urb_ctx;
urb_ctx = mausb_find_urb_in_tree(urb);
if (urb_ctx)
mausb_reset_data_iterator(&urb_ctx->iterator);
}
static void mausb_iterator_seek(struct mausb_device *dev,
struct mausb_event *event)
{
struct urb *urb = (struct urb *)event->data.urb;
struct mausb_urb_ctx *urb_ctx;
urb_ctx = mausb_find_urb_in_tree(urb);
if (urb_ctx)
mausb_data_iterator_seek(&urb_ctx->iterator,
event->data.iterator_seek_delta);
}
void mausb_complete_urb(struct mausb_event *event)
{
struct urb *urb = (struct urb *)event->data.urb;
dev_vdbg(mausb_host_dev.this_device, "URB complete request, 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 void mausb_delete_ma_dev(struct mausb_device *dev,
struct mausb_event *event)
{
mausb_signal_event(dev, event, event->mgmt.delete_ma_dev.event_id);
}
static void mausb_process_user_finished(struct mausb_device *dev,
struct mausb_event *event)
{
complete(&dev->user_finished_event);
}
static int mausb_send_mgmt_msg(struct mausb_device *dev,
struct mausb_event *event)
{
struct mausb_kvec_data_wrapper wrapper;
struct kvec kvec;
struct ma_usb_hdr_common *hdr;
int status;
hdr = (struct ma_usb_hdr_common *)event->mgmt.mgmt_hdr.hdr;
dev_vdbg(mausb_host_dev.this_device, "Sending event=%d, type=%d",
event->type, hdr->type);
kvec.iov_base = hdr;
kvec.iov_len = hdr->length;
wrapper.kvec = &kvec;
wrapper.kvec_num = 1;
wrapper.length = hdr->length;
status = mausb_ip_send(dev->mgmt_channel, &wrapper);
if (status < 0) {
dev_err(mausb_host_dev.this_device, "Send failed. Disconnecting... status=%d",
status);
queue_work(dev->workq, &dev->socket_disconnect_work);
queue_work(dev->workq, &dev->hcd_disconnect_work);
}
return status;
}
static int mausb_get_first_free_port_number(u8 *port_number)
{
(*port_number) = 0;
while ((mhcd->connected_ports & (1 << *port_number)) != 0 &&
*port_number < NUMBER_OF_PORTS)
++(*port_number);
if (*port_number == NUMBER_OF_PORTS)
return -EINVAL;
mhcd->connected_ports |= (1 << *port_number);
return 0;
}
static inline void mausb_port_has_changed_event(struct mausb_device *dev,
struct mausb_event *event)
{
int status;
u8 port_number;
unsigned long flags;
spin_lock_irqsave(&mhcd->lock, flags);
status = mausb_get_first_free_port_number(&port_number);
if (status < 0) {
spin_unlock_irqrestore(&mhcd->lock, flags);
dev_err(mausb_host_dev.this_device, "There is no free port, schedule delete ma_dev");
queue_work(dev->workq, &dev->socket_disconnect_work);
return;
}
spin_unlock_irqrestore(&mhcd->lock, flags);
dev->dev_type = event->port_changed.dev_type;
dev->dev_speed = event->port_changed.dev_speed;
dev->lse = event->port_changed.lse;
dev->dev_connected = 1;
dev->port_number = port_number;
mausb_port_has_changed(event->port_changed.dev_type,
event->port_changed.dev_speed, dev);
if ((enum mausb_device_type)event->port_changed.dev_type == USB30HUB)
mausb_port_has_changed(USB20HUB, HIGH_SPEED, dev);
}
static void mausb_complete_timeout_event(struct mausb_device *dev,
struct mausb_event *event)
{
dev_vdbg(mausb_host_dev.this_device, "Event type=%d, event_id=%llu",
event->type, event->mgmt.mgmt_req_timedout.event_id);
mausb_signal_event(dev, event, event->mgmt.mgmt_req_timedout.event_id);
}
static void mausb_process_event(struct mausb_device *dev,
struct mausb_event *event)
{
dev_vdbg(mausb_host_dev.this_device, "Process event of type=%d",
event->type);
switch (event->type) {
case MAUSB_EVENT_TYPE_USB_DEV_HANDLE:
mausb_usbdevhandle_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_EP_HANDLE:
mausb_ephandle_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_EP_HANDLE_ACTIVATE:
mausb_epactivate_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_EP_HANDLE_INACTIVATE:
mausb_epinactivate_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_EP_HANDLE_RESET:
mausb_epreset_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_EP_HANDLE_DELETE:
mausb_epdelete_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_MODIFY_EP0:
mausb_modifyep0_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_SET_USB_DEV_ADDRESS:
mausb_setusbdevaddress_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_UPDATE_DEV:
mausb_updatedev_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_USB_DEV_RESET:
mausb_usbdevreset_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_CANCEL_TRANSFER:
mausb_canceltransfer_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_CLEAR_TRANSFERS:
mausb_cleartransfers_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_PORT_CHANGED:
mausb_port_has_changed_event(dev, event);
break;
case MAUSB_EVENT_TYPE_DEV_DISCONNECT:
mausb_devdisconnect_event_from_user(dev, event);
break;
case MAUSB_EVENT_TYPE_PING:
case MAUSB_EVENT_TYPE_SEND_MGMT_MSG:
mausb_send_mgmt_msg(dev, event);
break;
case MAUSB_EVENT_TYPE_SEND_DATA_MSG:
mausb_send_data_msg(dev, event);
break;
case MAUSB_EVENT_TYPE_RECEIVED_DATA_MSG:
mausb_receive_data_msg(dev, event);
break;
case MAUSB_EVENT_TYPE_URB_COMPLETE:
mausb_complete_urb(event);
break;
case MAUSB_EVENT_TYPE_SEND_ACK:
mausb_send_transfer_ack(dev, event);
mausb_release_event_resources(event);
break;
case MAUSB_EVENT_TYPE_ITERATOR_RESET:
mausb_iterator_reset(dev, event);
break;
case MAUSB_EVENT_TYPE_ITERATOR_SEEK:
mausb_iterator_seek(dev, event);
break;
case MAUSB_EVENT_TYPE_DELETE_MA_DEV:
mausb_delete_ma_dev(dev, event);
break;
case MAUSB_EVENT_TYPE_USER_FINISHED:
mausb_process_user_finished(dev, event);
break;
case MAUSB_EVENT_TYPE_RELEASE_EVENT_RESOURCES:
mausb_release_event_resources(event);
break;
case MAUSB_EVENT_TYPE_NONE:
mausb_release_event_resources(event);
break;
case MAUSB_EVENT_TYPE_MGMT_REQUEST_TIMED_OUT:
mausb_complete_timeout_event(dev, event);
break;
default:
break;
}
mausb_notify_completed_user_events(dev->ring_buffer);
}
static void mausb_hpal_kernel_work(struct work_struct *work)
{
struct mausb_device *dev = container_of(work, struct mausb_device,
work);
struct mausb_event *event;
int status;
u16 i;
u16 events;
u16 completed_events;
unsigned long flags;
struct mausb_ring_buffer *dev_mausb_ring = dev->ring_buffer;
spin_lock_irqsave(&dev->num_of_user_events_lock, flags);
events = dev->num_of_user_events;
completed_events = dev->num_of_completed_events;
dev->num_of_user_events = 0;
dev->num_of_completed_events = 0;
spin_unlock_irqrestore(&dev->num_of_user_events_lock, flags);
status = mausb_ring_buffer_move_tail(dev_mausb_ring, completed_events);
if (status < 0) {
dev_err(mausb_host_dev.this_device, "Dequeue failed, status=%d",
status);
kref_put(&dev->refcount, mausb_release_ma_dev_async);
return;
}
for (i = 0; i < events; ++i) {
event = mausb_ring_current_from_user(dev_mausb_ring);
mausb_ring_next_from_user(dev_mausb_ring);
mausb_process_event(dev, event);
}
}
static void mausb_socket_disconnect_event(struct work_struct *work)
{
struct mausb_device *dev = container_of(work, struct mausb_device,
socket_disconnect_work);
struct mausb_event event;
int status;
dev_info(mausb_host_dev.this_device, "Disconnect madev_addr=%d",
dev->madev_addr);
mausb_ip_disconnect(dev->ctrl_channel);
mausb_destroy_ip_ctx(dev->ctrl_channel);
dev->ctrl_channel = NULL;
mausb_ip_disconnect(dev->bulk_channel);
mausb_destroy_ip_ctx(dev->bulk_channel);
dev->bulk_channel = NULL;
mausb_ip_disconnect(dev->isoch_channel);
mausb_destroy_ip_ctx(dev->isoch_channel);
dev->isoch_channel = NULL;
if (dev->mgmt_channel) {
memset(&event, 0, sizeof(event));
event.type = MAUSB_EVENT_TYPE_NETWORK_DISCONNECTED;
event.data.device_id = dev->id;
status = mausb_enqueue_event_to_user(dev, &event);
dev_info(mausb_host_dev.this_device, "Network disconnected notification sent status=%d",
status);
dev_info(mausb_host_dev.this_device, "Releasing MAUSB device ref");
kref_put(&dev->refcount, mausb_release_ma_dev_async);
}
mausb_ip_disconnect(dev->mgmt_channel);
mausb_destroy_ip_ctx(dev->mgmt_channel);
dev->mgmt_channel = NULL;
memset(dev->channel_map, 0, sizeof(dev->channel_map));
}
static void mausb_disconnect_ma_dev(struct mausb_device *dev)
{
dev_info(mausb_host_dev.this_device, "Disconnecting MAUSB device madev_addr=%d",
dev->madev_addr);
if (!dev->dev_connected) {
dev_warn(mausb_host_dev.this_device, "MAUSB device is not connected");
kref_put(&dev->refcount, mausb_release_ma_dev_async);
return;
}
mausb_hcd_disconnect(dev->port_number, dev->dev_type, dev->dev_speed);
if (dev->dev_type == USB30HUB)
mausb_hcd_disconnect(dev->port_number, USB20HUB, HIGH_SPEED);
}
static void mausb_hcd_disconnect_event(struct work_struct *work)
{
struct mausb_device *ma_dev = container_of(work, struct mausb_device,
hcd_disconnect_work);
mausb_disconnect_ma_dev(ma_dev);
}
static void mausb_delete_madev(struct work_struct *work)
{
struct mausb_device *dev = container_of(work, struct mausb_device,
madev_delete_work);
struct mausb_event event;
struct completion completion;
struct completion *user_event;
struct mausb_completion mausb_completion;
long status;
unsigned long timeout = msecs_to_jiffies(MAUSB_DELETE_MADEV_TIMEOUT_MS);
dev_info(mausb_host_dev.this_device, "Deleting MAUSB device madev_addr=%d",
dev->madev_addr);
del_timer_sync(&dev->connection_timer);
/* Client IS responsive */
if (!atomic_read(&dev->unresponsive_client)) {
memset(&event, 0, sizeof(event));
event.type = MAUSB_EVENT_TYPE_DELETE_MA_DEV;
event.mgmt.delete_ma_dev.device_id = dev->id;
event.mgmt.delete_ma_dev.event_id = mausb_event_id(dev);
init_completion(&completion);
mausb_completion.completion_event = &completion;
mausb_completion.event_id = event.mgmt.delete_ma_dev.event_id;
mausb_completion.mausb_event = &event;
mausb_insert_event(dev, &mausb_completion);
status = mausb_enqueue_event_to_user(dev, &event);
if (status < 0) {
mausb_remove_event(dev, &mausb_completion);
dev_err(mausb_host_dev.this_device, "Ring buffer full, enqueue failed");
return;
}
dev_dbg(mausb_host_dev.this_device, "Deleting MAUSB device...");
status = wait_for_completion_interruptible_timeout(&completion,
timeout);
dev_dbg(mausb_host_dev.this_device, "Deleting MAUSB device event finished with %ld",
status);
mausb_remove_event(dev, &mausb_completion);
user_event = &dev->user_finished_event;
status = wait_for_completion_interruptible_timeout(user_event,
timeout);
dev_info(mausb_host_dev.this_device, "User event finished with %ld",
status);
}
flush_workqueue(dev->workq);
destroy_workqueue(dev->workq);
mausb_clear_hcd_madev(dev->port_number);
mausb_ring_buffer_cleanup(dev->ring_buffer);
mausb_ring_buffer_destroy(dev->ring_buffer);
mausb_remove_madev_from_list(dev->madev_addr);
put_net(dev->net_ns);
kfree(dev->ring_buffer);
kfree(dev);
mausb_signal_empty_mss();
dev_dbg(mausb_host_dev.this_device, "MAUSB device deleted.");
}
static void mausb_ping_work(struct work_struct *work)
{
struct mausb_device *dev = container_of(work, struct mausb_device,
ping_work);
if (mausb_start_connection_timer(dev) < 0) {
dev_err(mausb_host_dev.this_device, "Session timeout - disconnnecting device madev_addr=%d",
dev->madev_addr);
queue_work(dev->workq, &dev->socket_disconnect_work);
queue_work(dev->workq, &dev->hcd_disconnect_work);
return;
}
mausb_ping_event_to_user(dev);
}
static void mausb_heartbeat_work(struct work_struct *work)
{
struct mausb_device *dev = container_of(work, struct mausb_device,
heartbeat_work);
dev_err(mausb_host_dev.this_device, "App is unresponsive - disconnect device");
atomic_set(&dev->unresponsive_client, 1);
mausb_complete_urbs_from_tree();
queue_work(dev->workq, &dev->socket_disconnect_work);
queue_work(dev->workq, &dev->hcd_disconnect_work);
}
static void mausb_connection_timer_func(struct timer_list *timer)
{
struct mausb_device *dev = container_of(timer, struct mausb_device,
connection_timer);
queue_work(dev->workq, &dev->ping_work);
}
static void mausb_heartbeat_timer_func(struct timer_list *timer)
{
unsigned long flags;
struct mausb_device *dev = NULL;
if (mausb_start_heartbeat_timer() < 0) {
dev_err(mausb_host_dev.this_device, "App is unresponsive - disconnecting devices");
spin_lock_irqsave(&mss.lock, flags);
/* Reset connected clients */
mss.client_connected = false;
mss.missed_heartbeats = 0;
list_for_each_entry(dev, &mss.madev_list, list_entry) {
dev_vdbg(mausb_host_dev.this_device, "Enqueue heartbeat_work madev_addr=%x",
dev->madev_addr);
queue_work(dev->workq, &dev->heartbeat_work);
}
complete(&mss.client_stopped);
spin_unlock_irqrestore(&mss.lock, flags);
}
}
static struct mausb_device *
mausb_create_madev(struct mausb_device_address dev_addr, u8 madev_address,
int *status)
{
struct mausb_device *dev;
unsigned long flags;
char workq_name[16];
struct workqueue_struct *workq;
memset(workq_name, 0, sizeof(workq_name));
sprintf(workq_name, "%x", madev_address);
strcat(workq_name, "_madev_workq");
dev_vdbg(mausb_host_dev.this_device, "madev_workq_name = %s",
workq_name);
workq = alloc_ordered_workqueue(workq_name, WQ_MEM_RECLAIM);
if (!workq) {
*status = -ENOMEM;
return NULL;
}
spin_lock_irqsave(&mss.lock, flags);
if (mss.deinit_in_progress) {
spin_unlock_irqrestore(&mss.lock, flags);
dev_alert(mausb_host_dev.this_device, "Device creating failed - mss deinit in progress");
flush_workqueue(workq);
destroy_workqueue(workq);
*status = -ESHUTDOWN;
return NULL;
}
dev = mausb_get_dev_from_addr_unsafe(madev_address);
if (dev) {
spin_unlock_irqrestore(&mss.lock, flags);
dev_warn(mausb_host_dev.this_device, "MAUSB device already connected, madev_address=%x",
madev_address);
flush_workqueue(workq);
destroy_workqueue(workq);
*status = -EEXIST;
return NULL;
}
dev = kzalloc(sizeof(*dev), GFP_ATOMIC);
if (!dev) {
spin_unlock_irqrestore(&mss.lock, flags);
dev_alert(mausb_host_dev.this_device, "Could not allocate MAUSB device!");
flush_workqueue(workq);
destroy_workqueue(workq);
*status = -ENOMEM;
return NULL;
}
dev_dbg(mausb_host_dev.this_device, "Create MAUSB device.");
dev->workq = workq;
INIT_WORK(&dev->work, mausb_hpal_kernel_work);
INIT_WORK(&dev->socket_disconnect_work, mausb_socket_disconnect_event);
INIT_WORK(&dev->hcd_disconnect_work, mausb_hcd_disconnect_event);
INIT_WORK(&dev->madev_delete_work, mausb_delete_madev);
INIT_WORK(&dev->ping_work, mausb_ping_work);
INIT_WORK(&dev->heartbeat_work, mausb_heartbeat_work);
kref_init(&dev->refcount);
dev->event_id = 0;
spin_lock_init(&dev->event_id_lock);
INIT_LIST_HEAD(&dev->completion_events);
spin_lock_init(&dev->completion_events_lock);
spin_lock_init(&dev->num_of_user_events_lock);
spin_lock_init(&dev->connection_timer_lock);
init_completion(&dev->user_finished_event);
atomic_set(&dev->unresponsive_client, 0);
timer_setup(&dev->connection_timer, mausb_connection_timer_func, 0);
dev->dev_addr = dev_addr;
dev->madev_addr = madev_address;
dev->net_ns = get_net(current->nsproxy->net_ns);
if (!list_empty(&mss.available_ring_buffers)) {
dev->ring_buffer = container_of(mss.available_ring_buffers.next,
struct mausb_ring_buffer,
list_entry);
list_del(mss.available_ring_buffers.next);
} else {
dev_alert(mausb_host_dev.this_device, "Ring buffer for mausb device is not available!");
}
list_add_tail(&dev->list_entry, &mss.madev_list);
reinit_completion(&mss.empty);
spin_unlock_irqrestore(&mss.lock, flags);
return dev;
}
void mausb_release_ma_dev_async(struct kref *kref)
{
struct mausb_device *dev = container_of(kref, struct mausb_device,
refcount);
dev_info(mausb_host_dev.this_device, "Scheduling work for MAUSB device to be deleted");
schedule_work(&dev->madev_delete_work);
}
int mausb_initiate_dev_connection(struct mausb_device_address dev_addr,
u8 madev_address)
{
int error = 0;
struct mausb_device *dev;
unsigned long flags;
spin_lock_irqsave(&mss.lock, flags);
dev = mausb_get_dev_from_addr_unsafe(madev_address);
spin_unlock_irqrestore(&mss.lock, flags);
if (dev) {
dev_warn(mausb_host_dev.this_device, "MAUSB device already connected, madev_address=%x",
madev_address);
return -EEXIST;
}
dev = mausb_create_madev(dev_addr, madev_address, &error);
if (!dev)
return error;
dev_info(mausb_host_dev.this_device, "New MAUSB device created madev_addr=%d",
madev_address);
error = mausb_init_ip_ctx(&dev->mgmt_channel, dev->net_ns,
dev->dev_addr.ip.address,
dev->dev_addr.ip.port.management, dev,
mausb_ip_callback, MAUSB_MGMT_CHANNEL);
if (error) {
dev_err(mausb_host_dev.this_device, "Mgmt ip context init failed: error=%d",
error);
kref_put(&dev->refcount, mausb_release_ma_dev_async);
return error;
}
mausb_ip_connect_async(dev->mgmt_channel);
return 0;
}
void mausb_on_madev_connected(struct mausb_device *dev)
{
struct mausb_event mausb_event;
mausb_dev_reset_req_event(&mausb_event);
mausb_enqueue_event_to_user(dev, &mausb_event);
}
int mausb_enqueue_event_from_user(u8 madev_addr, u16 num_of_events,
u16 num_of_completed)
{
unsigned long flags;
struct mausb_device *dev;
spin_lock_irqsave(&mss.lock, flags);
dev = mausb_get_dev_from_addr_unsafe(madev_addr);
if (!dev) {
spin_unlock_irqrestore(&mss.lock, flags);
return -EINVAL;
}
spin_lock(&dev->num_of_user_events_lock);
dev->num_of_user_events += num_of_events;
dev->num_of_completed_events += num_of_completed;
spin_unlock(&dev->num_of_user_events_lock);
queue_work(dev->workq, &dev->work);
spin_unlock_irqrestore(&mss.lock, flags);
return 0;
}
int mausb_enqueue_event_to_user(struct mausb_device *dev,
struct mausb_event *event)
{
int status;
event->madev_addr = dev->madev_addr;
status = mausb_ring_buffer_put(dev->ring_buffer, event);
if (status < 0) {
dev_err(mausb_host_dev.this_device, "Ring buffer operation failed");
mausb_cleanup_ring_buffer_event(event);
return status;
}
mausb_notify_ring_events(dev->ring_buffer);
dev_vdbg(mausb_host_dev.this_device, "User-space notification sent.");
return 0;
}
int mausb_data_req_enqueue_event(struct mausb_device *dev, u16 ep_handle,
struct urb *request)
{
int status;
struct mausb_event mausb_event;
mausb_event.type = MAUSB_EVENT_TYPE_SEND_DATA_MSG;
mausb_event.status = 0;
mausb_event.data.transfer_type =
mausb_transfer_type_from_usb(&request->ep->desc);
mausb_event.data.device_id = dev->id;
mausb_event.data.ep_handle = ep_handle;
mausb_event.data.urb = (uintptr_t)request;
mausb_event.data.setup_packet =
(usb_endpoint_xfer_control(&request->ep->desc) &&
request->setup_packet);
mausb_event.data.transfer_size = request->transfer_buffer_length;
mausb_event.data.direction = (usb_urb_dir_in(request) ?
MAUSB_DATA_MSG_DIRECTION_IN :
MAUSB_DATA_MSG_DIRECTION_OUT);
mausb_event.data.transfer_size +=
((mausb_event.data.direction == MAUSB_DATA_MSG_DIRECTION_OUT &&
mausb_event.data.setup_packet) ?
MAUSB_CONTROL_SETUP_SIZE : 0);
mausb_event.data.rem_transfer_size = mausb_event.data.transfer_size;
mausb_event.data.transfer_flags = request->transfer_flags;
mausb_event.data.transfer_eot = false;
mausb_event.data.isoch_seg_num = (u32)request->number_of_packets;
mausb_event.data.recv_buf = 0;
mausb_event.data.payload_size =
(usb_endpoint_xfer_isoc(&request->ep->desc) &&
usb_endpoint_dir_out(&request->ep->desc)) ?
(request->iso_frame_desc[request->number_of_packets - 1]
.offset +
request->iso_frame_desc[request->number_of_packets - 1]
.length) : 0;
if (mausb_event.data.setup_packet) {
memcpy(mausb_event.data.hdr_ack, request->setup_packet,
MAUSB_CONTROL_SETUP_SIZE);
memcpy(shift_ptr(mausb_event.data.hdr_ack,
MAUSB_CONTROL_SETUP_SIZE),
&request->dev->route, sizeof(request->dev->route));
}
status = mausb_enqueue_event_to_user(dev, &mausb_event);
if (status < 0)
dev_err(&request->dev->dev, "Failed to enqueue event to user-space ep_handle=%#x, status=%d",
mausb_event.data.ep_handle, status);
return status;
}
void mausb_complete_request(struct urb *urb, u32 actual_length, int status)
{
mausb_hcd_urb_complete(urb, actual_length, status);
}
int mausb_signal_event(struct mausb_device *dev,
struct mausb_event *event, u64 event_id)
{
unsigned long flags;
struct mausb_completion *mausb_completion;
spin_lock_irqsave(&dev->completion_events_lock, flags);
list_for_each_entry(mausb_completion, &dev->completion_events,
list_entry) {
if (mausb_completion->event_id == event_id) {
memcpy(mausb_completion->mausb_event, event,
sizeof(*event));
complete(mausb_completion->completion_event);
spin_unlock_irqrestore(&dev->completion_events_lock,
flags);
return 0;
}
}
spin_unlock_irqrestore(&dev->completion_events_lock, flags);
return -ETIMEDOUT;
}
static int mausb_start_connection_timer(struct mausb_device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->connection_timer_lock, flags);
if (++dev->receive_failures_num > MAUSB_MAX_RECEIVE_FAILURES) {
dev_err(mausb_host_dev.this_device, "Missed more than %d ping responses",
MAUSB_MAX_RECEIVE_FAILURES);
spin_unlock_irqrestore(&dev->connection_timer_lock, flags);
return -ETIMEDOUT;
}
mod_timer(&dev->connection_timer, jiffies + msecs_to_jiffies(1000));
spin_unlock_irqrestore(&dev->connection_timer_lock, flags);
return 0;
}
void mausb_reset_connection_timer(struct mausb_device *dev)
{
unsigned long flags;
spin_lock_irqsave(&dev->connection_timer_lock, flags);
dev->receive_failures_num = 0;
mod_timer(&dev->connection_timer, jiffies + msecs_to_jiffies(1000));
spin_unlock_irqrestore(&dev->connection_timer_lock, flags);
}
static int mausb_start_heartbeat_timer(void)
{
unsigned long flags;
spin_lock_irqsave(&mss.lock, flags);
if (++mss.missed_heartbeats > MAUSB_MAX_MISSED_HEARTBEATS) {
dev_err(mausb_host_dev.this_device, "Missed more than %d heartbeats",
MAUSB_MAX_MISSED_HEARTBEATS);
spin_unlock_irqrestore(&mss.lock, flags);
return -ETIMEDOUT;
}
spin_unlock_irqrestore(&mss.lock, flags);
mod_timer(&mss.heartbeat_timer,
jiffies + msecs_to_jiffies(MAUSB_HEARTBEAT_TIMEOUT_MS));
return 0;
}
void mausb_reset_heartbeat_cnt(void)
{
unsigned long flags;
spin_lock_irqsave(&mss.lock, flags);
mss.missed_heartbeats = 0;
spin_unlock_irqrestore(&mss.lock, flags);
}
static void mausb_execute_urb_dequeue(struct work_struct *dequeue_work)
{
struct mausb_urb_ctx *urb_ctx =
container_of(dequeue_work, struct mausb_urb_ctx, work);
struct urb *urb = urb_ctx->urb;
struct mausb_endpoint_ctx *ep_ctx;
struct mausb_device *ma_dev;
struct mausb_event mausb_event;
int status = 0;
ep_ctx = urb->ep->hcpriv;
ma_dev = ep_ctx->ma_dev;
if (atomic_read(&ma_dev->unresponsive_client)) {
dev_err(mausb_host_dev.this_device, "Client is not responsive anymore - finish urb immediately urb=%p, ep_handle=%#x, dev_handle=%#x",
urb, ep_ctx->ep_handle, ep_ctx->dev_handle);
goto complete_urb;
}
dev_vdbg(mausb_host_dev.this_device, "urb=%p, ep_handle=%#x, dev_handle=%#x",
urb, ep_ctx->ep_handle, ep_ctx->dev_handle);
if (!usb_endpoint_xfer_isoc(&urb->ep->desc)) {
status = mausb_canceltransfer_event_to_user(ep_ctx->ma_dev,
ep_ctx->dev_handle,
ep_ctx->ep_handle,
(uintptr_t)urb);
if (status < 0)
goto complete_urb;
}
memset(&mausb_event, 0, sizeof(mausb_event));
mausb_event.type = MAUSB_EVENT_TYPE_DELETE_DATA_TRANSFER;
mausb_event.status = 0;
mausb_event.data.transfer_type =
mausb_transfer_type_from_usb(&urb->ep->desc);
mausb_event.data.device_id = ma_dev->id;
mausb_event.data.ep_handle = ep_ctx->ep_handle;
mausb_event.data.urb = (uintptr_t)urb;
mausb_event.data.direction = (usb_urb_dir_in(urb) ?
MAUSB_DATA_MSG_DIRECTION_IN :
MAUSB_DATA_MSG_DIRECTION_OUT);
status = mausb_enqueue_event_to_user(ep_ctx->ma_dev, &mausb_event);
if (status < 0) {
dev_alert(mausb_host_dev.this_device, "Failed to enqueue event to user-space ep_handle=%#x, status=%d",
mausb_event.data.ep_handle, status);
goto complete_urb;
}
if (!mausb_return_urb_ctx_to_tree(urb_ctx, false)) {
dev_alert(mausb_host_dev.this_device, "Failed to insert in tree urb=%p ep_handle=%#x, status=%d",
urb, mausb_event.data.ep_handle, status);
goto complete_urb;
}
return;
complete_urb:
/* Deallocate urb_ctx */
mausb_uninit_data_iterator(&urb_ctx->iterator);
kfree(urb_ctx);
urb->status = -EPROTO;
urb->actual_length = 0;
atomic_dec(&urb->use_count);
usb_hcd_giveback_urb(urb->hcpriv, urb, urb->status);
}
void mausb_initialize_mss(void)
{
spin_lock_init(&mss.lock);
INIT_LIST_HEAD(&mss.madev_list);
INIT_LIST_HEAD(&mss.available_ring_buffers);
init_completion(&mss.empty);
complete(&mss.empty);
init_completion(&mss.rings_events.mausb_ring_has_events);
atomic_set(&mss.rings_events.mausb_stop_reading_ring_events, 0);
mss.deinit_in_progress = false;
mss.ring_buffer_id = 0;
mss.client_connected = false;
mss.missed_heartbeats = 0;
init_completion(&mss.client_stopped);
atomic_set(&mss.num_of_transitions_to_sleep, 0);
timer_setup(&mss.heartbeat_timer, mausb_heartbeat_timer_func, 0);
}
void mausb_deinitialize_mss(void)
{
struct mausb_device *dev = NULL;
unsigned long flags;
unsigned long timeout =
msecs_to_jiffies(MAUSB_CLIENT_STOPPED_TIMEOUT_MS);
spin_lock_irqsave(&mss.lock, flags);
mss.deinit_in_progress = true;
list_for_each_entry(dev, &mss.madev_list, list_entry) {
dev_dbg(mausb_host_dev.this_device, "Enqueue mausb_hcd_disconnect_work madev_addr=%x",
dev->madev_addr);
queue_work(dev->workq, &dev->hcd_disconnect_work);
}
spin_unlock_irqrestore(&mss.lock, flags);
wait_for_completion(&mss.empty);
dev_dbg(mausb_host_dev.this_device, "Waiting for completion on disconnect_event ended");
mausb_stop_ring_events();
timeout = wait_for_completion_timeout(&mss.client_stopped, timeout);
if (timeout > 0)
dev_dbg(mausb_host_dev.this_device, "Remaining time after waiting for stopping client %ld",
timeout);
}
int mausb_register_power_state_listener(void)
{
dev_dbg(mausb_host_dev.this_device, "Registering power states listener");
mhcd->power_state_listener.notifier_call = mausb_power_state_cb;
return register_pm_notifier(&mhcd->power_state_listener);
}
void mausb_unregister_power_state_listener(void)
{
dev_dbg(mausb_host_dev.this_device, "Un-registering power states listener");
unregister_pm_notifier(&mhcd->power_state_listener);
}
static int mausb_power_state_cb(struct notifier_block *nb, unsigned long action,
void *data)
{
unsigned long flags;
struct mausb_device *dev = NULL;
dev_info(mausb_host_dev.this_device, "Power state callback action = %ld",
action);
if (action == PM_SUSPEND_PREPARE || action == PM_HIBERNATION_PREPARE) {
/* Stop heartbeat timer */
del_timer_sync(&mss.heartbeat_timer);
dev_info(mausb_host_dev.this_device, "Saving state before sleep");
spin_lock_irqsave(&mss.lock, flags);
if (!list_empty(&mss.madev_list))
atomic_inc(&mss.num_of_transitions_to_sleep);
list_for_each_entry(dev, &mss.madev_list, list_entry) {
dev_info(mausb_host_dev.this_device, "Enqueue heartbeat_work madev_addr=%x",
dev->madev_addr);
queue_work(dev->workq, &dev->heartbeat_work);
}
spin_unlock_irqrestore(&mss.lock, flags);
} else if (action == PM_POST_SUSPEND || action == PM_POST_HIBERNATION) {
mausb_reset_heartbeat_cnt();
/* Start hearbeat timer */
mod_timer(&mss.heartbeat_timer, jiffies +
msecs_to_jiffies(MAUSB_HEARTBEAT_TIMEOUT_MS));
}
return NOTIFY_OK;
}
static void mausb_populate_standard_ep_descriptor(struct usb_ep_desc *std_desc,
struct usb_endpoint_descriptor
*usb_std_desc)
{
std_desc->bLength = usb_std_desc->bLength;
std_desc->bDescriptorType = usb_std_desc->bDescriptorType;
std_desc->bEndpointAddress = usb_std_desc->bEndpointAddress;
std_desc->bmAttributes = usb_std_desc->bmAttributes;
std_desc->wMaxPacketSize = usb_std_desc->wMaxPacketSize;
std_desc->bInterval = usb_std_desc->bInterval;
}
static void
mausb_populate_superspeed_ep_descriptor(struct usb_ss_ep_comp_desc *ss_desc,
struct usb_ss_ep_comp_descriptor*
usb_ss_desc)
{
ss_desc->bLength = usb_ss_desc->bLength;
ss_desc->bDescriptorType = usb_ss_desc->bDescriptorType;
ss_desc->bMaxBurst = usb_ss_desc->bMaxBurst;
ss_desc->bmAttributes = usb_ss_desc->bmAttributes;
ss_desc->wBytesPerInterval = usb_ss_desc->wBytesPerInterval;
}
void
mausb_init_standard_ep_descriptor(struct ma_usb_ephandlereq_desc_std *std_desc,
struct usb_endpoint_descriptor *usb_std_desc)
{
mausb_populate_standard_ep_descriptor(&std_desc->usb20, usb_std_desc);
}
void
mausb_init_superspeed_ep_descriptor(struct ma_usb_ephandlereq_desc_ss *ss_desc,
struct usb_endpoint_descriptor *
usb_std_desc,
struct usb_ss_ep_comp_descriptor *
usb_ss_desc)
{
mausb_populate_standard_ep_descriptor(&ss_desc->usb20, usb_std_desc);
mausb_populate_superspeed_ep_descriptor(&ss_desc->usb31, usb_ss_desc);
}
struct mausb_device *mausb_get_dev_from_addr_unsafe(u8 madev_addr)
{
struct mausb_device *dev = NULL;
list_for_each_entry(dev, &mss.madev_list, list_entry) {
if (dev->madev_addr == madev_addr)
return dev;
}
return NULL;
}
static void mausb_remove_madev_from_list(u8 madev_addr)
{
unsigned long flags;
struct mausb_device *ma_dev = NULL;
struct mausb_device *tmp = NULL;
spin_lock_irqsave(&mss.lock, flags);
list_for_each_entry_safe(ma_dev, tmp, &mss.madev_list, list_entry) {
if (ma_dev->madev_addr == madev_addr) {
list_del(&ma_dev->list_entry);
break;
}
}
if (list_empty(&mss.madev_list))
reinit_completion(&mss.rings_events.mausb_ring_has_events);
spin_unlock_irqrestore(&mss.lock, flags);
}
static void mausb_signal_empty_mss(void)
{
unsigned long flags;
spin_lock_irqsave(&mss.lock, flags);
if (list_empty(&mss.madev_list))
complete(&mss.empty);
spin_unlock_irqrestore(&mss.lock, flags);
}
static inline
struct mausb_ip_ctx *mausb_get_data_channel(struct mausb_device *ma_dev,
enum mausb_channel channel)
{
if (channel >= MAUSB_CHANNEL_MAP_LENGTH)
return NULL;
return ma_dev->channel_map[channel];
}
int mausb_send_data(struct mausb_device *dev, enum mausb_channel channel_num,
struct mausb_kvec_data_wrapper *data)
{
struct mausb_ip_ctx *channel = mausb_get_data_channel(dev, channel_num);
int status = 0;
if (!channel)
return -ECHRNG;
status = mausb_ip_send(channel, data);
if (status < 0) {
dev_err(mausb_host_dev.this_device, "Send failed. Disconnecting... status=%d",
status);
queue_work(dev->workq, &dev->socket_disconnect_work);
queue_work(dev->workq, &dev->hcd_disconnect_work);
}
return status;
}
int mausb_send_transfer_ack(struct mausb_device *dev, struct mausb_event *event)
{
struct ma_usb_hdr_common *ack_hdr;
struct kvec kvec;
struct mausb_kvec_data_wrapper data_to_send;
enum mausb_channel channel;
ack_hdr = (struct ma_usb_hdr_common *)(&event->data.hdr_ack);
data_to_send.kvec = &kvec;
data_to_send.kvec->iov_base = ack_hdr;
data_to_send.kvec->iov_len = ack_hdr->length;
data_to_send.kvec_num = 1;
data_to_send.length = ack_hdr->length;
channel = mausb_transfer_type_to_channel(event->data.transfer_type);
return mausb_send_data(dev, channel, &data_to_send);
}
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) {
dev_err(mausb_host_dev.this_device, "Event %d failed with status %d",
event->type, event->status);
mausb_complete_urb(event);
return event->status;
}
urb_ctx = mausb_find_urb_in_tree((struct urb *)event->data.urb);
if (!urb_ctx) {
/* Transfer will be deleted from dequeue task */
dev_warn(mausb_host_dev.this_device, "Urb is already cancelled for event=%d",
event->type);
return status;
}
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)
{
int status = 0;
struct mausb_urb_ctx *urb_ctx;
dev_vdbg(mausb_host_dev.this_device, "Direction=%d",
event->data.direction);
if (!mausb_isoch_data_event(event)) {
status = mausb_send_transfer_ack(dev, event);
if (status < 0) {
dev_warn(mausb_host_dev.this_device, "Sending acknowledgment failed");
goto cleanup;
}
}
urb_ctx = mausb_find_urb_in_tree((struct urb *)event->data.urb);
if (!urb_ctx) {
/* Transfer will be deleted from dequeue task */
dev_warn(mausb_host_dev.this_device, "Urb is already cancelled");
goto cleanup;
}
if (mausb_isoch_data_event(event)) {
if (event->data.direction == MAUSB_DATA_MSG_DIRECTION_IN)
mausb_receive_isoch_in_data(dev, event, urb_ctx);
else
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:
mausb_release_event_resources(event);
return status;
}
int mausb_add_data_chunk(void *buffer, u32 buffer_size,
struct list_head *chunks_list)
{
struct mausb_payload_chunk *data_chunk;
data_chunk = kzalloc(sizeof(*data_chunk), GFP_KERNEL);
if (!data_chunk)
return -ENOMEM;
/* Initialize data chunk for MAUSB header and add it to chunks list */
INIT_LIST_HEAD(&data_chunk->list_entry);
data_chunk->kvec.iov_base = buffer;
data_chunk->kvec.iov_len = buffer_size;
list_add_tail(&data_chunk->list_entry, chunks_list);
return 0;
}
int mausb_init_data_wrapper(struct mausb_kvec_data_wrapper *data,
struct list_head *chunks_list,
u32 num_of_data_chunks)
{
struct mausb_payload_chunk *data_chunk = NULL;
struct mausb_payload_chunk *tmp = NULL;
u32 current_kvec = 0;
data->length = 0;
data->kvec = kcalloc(num_of_data_chunks, sizeof(struct kvec),
GFP_KERNEL);
if (!data->kvec)
return -ENOMEM;
list_for_each_entry_safe(data_chunk, tmp, chunks_list, list_entry) {
data->kvec[current_kvec].iov_base =
data_chunk->kvec.iov_base;
data->kvec[current_kvec].iov_len =
data_chunk->kvec.iov_len;
++data->kvec_num;
data->length += data_chunk->kvec.iov_len;
++current_kvec;
}
return 0;
}
void mausb_cleanup_chunks_list(struct list_head *chunks_list)
{
struct mausb_payload_chunk *data_chunk = NULL;
struct mausb_payload_chunk *tmp = NULL;
list_for_each_entry_safe(data_chunk, tmp, chunks_list, list_entry) {
list_del(&data_chunk->list_entry);
kfree(data_chunk);
}
}
static void mausb_init_ip_ctx_helper(struct mausb_device *dev,
struct mausb_ip_ctx **ip_ctx,
u16 port,
enum mausb_channel channel)
{
int status = mausb_init_ip_ctx(ip_ctx, dev->net_ns,
dev->dev_addr.ip.address, port, dev,
mausb_ip_callback, channel);
if (status < 0) {
dev_err(mausb_host_dev.this_device, "Init ip context failed with error=%d",
status);
queue_work(dev->workq, &dev->socket_disconnect_work);
return;
}
dev->channel_map[channel] = *ip_ctx;
mausb_ip_connect_async(*ip_ctx);
}
static void mausb_connect_callback(struct mausb_device *dev,
enum mausb_channel channel, int status)
{
struct mausb_device_address *dev_addr = &dev->dev_addr;
dev_info(mausb_host_dev.this_device, "Connect callback for channel=%d with status=%d",
channel, status);
if (status < 0) {
queue_work(dev->workq, &dev->socket_disconnect_work);
return;
}
if (channel == MAUSB_MGMT_CHANNEL) {
if (dev_addr->ip.port.control == 0) {
dev->channel_map[MAUSB_CTRL_CHANNEL] =
dev->mgmt_channel;
channel = MAUSB_CTRL_CHANNEL;
} else {
mausb_init_ip_ctx_helper(dev, &dev->ctrl_channel,
dev_addr->ip.port.control,
MAUSB_CTRL_CHANNEL);
return;
}
}
if (channel == MAUSB_CTRL_CHANNEL) {
if (dev_addr->ip.port.bulk == 0) {
dev->channel_map[MAUSB_BULK_CHANNEL] =
dev->channel_map[MAUSB_CTRL_CHANNEL];
channel = MAUSB_BULK_CHANNEL;
} else {
mausb_init_ip_ctx_helper(dev, &dev->bulk_channel,
dev_addr->ip.port.bulk,
MAUSB_BULK_CHANNEL);
return;
}
}
if (channel == MAUSB_BULK_CHANNEL) {
if (dev_addr->ip.port.isochronous == 0) {
/* if there is no isoch port use tcp for it */
dev->channel_map[MAUSB_ISOCH_CHANNEL] =
dev->channel_map[MAUSB_BULK_CHANNEL];
channel = MAUSB_ISOCH_CHANNEL;
} else {
mausb_init_ip_ctx_helper(dev, &dev->isoch_channel,
dev_addr->ip.port.isochronous,
MAUSB_ISOCH_CHANNEL);
return;
}
}
if (channel == MAUSB_ISOCH_CHANNEL) {
dev->channel_map[MAUSB_INTR_CHANNEL] =
dev->channel_map[MAUSB_CTRL_CHANNEL];
mausb_on_madev_connected(dev);
}
}
static void mausb_handle_connect_event(struct mausb_device *dev,
enum mausb_channel channel, int status,
void *data)
{
mausb_connect_callback(dev, channel, status);
}
static void mausb_handle_receive_event(struct mausb_device *dev,
enum mausb_channel channel, int status,
void *data)
{
struct mausb_event event;
event.madev_addr = dev->madev_addr;
if (status <= 0) {
dev_err(mausb_host_dev.this_device, "Receive event error status=%d",
status);
queue_work(dev->workq, &dev->socket_disconnect_work);
queue_work(dev->workq, &dev->hcd_disconnect_work);
return;
}
mausb_reset_connection_timer(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);
if (status < 0)
dev_err(mausb_host_dev.this_device, "Failed to enqueue, status=%d",
status);
}
void mausb_ip_callback(void *ctx, enum mausb_channel channel,
enum mausb_link_action action, int status, void *data)
{
struct mausb_device *dev = (struct mausb_device *)ctx;
switch (action) {
case MAUSB_LINK_CONNECT:
mausb_handle_connect_event(dev, channel, status, data);
break;
case MAUSB_LINK_SEND:
/*
* Currently there is nothing to do, as send operation is
* synchronous
*/
break;
case MAUSB_LINK_RECV:
mausb_handle_receive_event(dev, channel, status, data);
break;
case MAUSB_LINK_DISCONNECT:
/*
* Currently there is nothing to do, as disconnect operation is
* synchronous
*/
break;
default:
dev_warn(mausb_host_dev.this_device, "Unknown network action");
}
}
static int mausb_read_virtual_buffer(struct mausb_data_iter *iterator,
u32 byte_num,
struct list_head *data_chunks_list,
u32 *data_chunks_num)
{
u32 rem_data = 0;
u32 bytes_to_read = 0;
struct mausb_payload_chunk *data_chunk = NULL;
(*data_chunks_num) = 0;
if (!data_chunks_list)
return -EINVAL;
INIT_LIST_HEAD(data_chunks_list);
rem_data = iterator->length - iterator->offset;
bytes_to_read = min(byte_num, rem_data);
if (bytes_to_read == 0)
return 0;
data_chunk = kzalloc(sizeof(*data_chunk), GFP_KERNEL);
if (!data_chunk)
return -ENOMEM;
++(*data_chunks_num);
data_chunk->kvec.iov_base = (u8 *)(iterator->buffer) + iterator->offset;
data_chunk->kvec.iov_len = bytes_to_read;
iterator->offset += bytes_to_read;
list_add_tail(&data_chunk->list_entry, data_chunks_list);
return 0;
}
static int mausb_read_scatterlist_buffer(struct mausb_data_iter *iterator,
u32 byte_num,
struct list_head *data_chunks_list,
u32 *data_chunks_num)
{
u32 current_sg_read_num;
struct mausb_payload_chunk *data_chunk = NULL;
(*data_chunks_num) = 0;
if (!data_chunks_list)
return -EINVAL;
INIT_LIST_HEAD(data_chunks_list);
while (byte_num) {
if (iterator->sg_iter.consumed == iterator->sg_iter.length) {
if (!sg_miter_next(&iterator->sg_iter))
break;
iterator->sg_iter.consumed = 0;
}
data_chunk = kzalloc(sizeof(*data_chunk), GFP_KERNEL);
if (!data_chunk) {
sg_miter_stop(&iterator->sg_iter);
return -ENOMEM;
}
current_sg_read_num = min((size_t)byte_num,
iterator->sg_iter.length -
iterator->sg_iter.consumed);
data_chunk->kvec.iov_base = (u8 *)iterator->sg_iter.addr +
iterator->sg_iter.consumed;
data_chunk->kvec.iov_len = current_sg_read_num;
++(*data_chunks_num);
list_add_tail(&data_chunk->list_entry, data_chunks_list);
byte_num -= current_sg_read_num;
iterator->sg_iter.consumed += current_sg_read_num;
data_chunk = NULL;
}
return 0;
}
static u32 mausb_write_virtual_buffer(struct mausb_data_iter *iterator,
void *buffer, u32 size)
{
u32 rem_space = 0;
u32 write_count = 0;
if (!buffer || !size)
return write_count;
rem_space = iterator->length - iterator->offset;
write_count = min(size, rem_space);
if (write_count > 0) {
void *location = shift_ptr(iterator->buffer, iterator->offset);
memcpy(location, buffer, write_count);
iterator->offset += write_count;
}
return write_count;
}
static u32 mausb_write_scatterlist_buffer(struct mausb_data_iter *iterator,
void *buffer, u32 size)
{
u32 current_sg_rem_space;
u32 count = 0;
u32 total_count = 0;
void *location = NULL;
if (!buffer || !size)
return count;
while (size) {
if (iterator->sg_iter.consumed >= iterator->sg_iter.length) {
if (!sg_miter_next(&iterator->sg_iter))
break;
iterator->sg_iter.consumed = 0;
}
current_sg_rem_space = (u32)(iterator->sg_iter.length -
iterator->sg_iter.consumed);
count = min(size, current_sg_rem_space);
total_count += count;
location = shift_ptr(iterator->sg_iter.addr,
iterator->sg_iter.consumed);
memcpy(location, buffer, count);
buffer = shift_ptr(buffer, count);
size -= count;
iterator->sg_iter.consumed += count;
}
return total_count;
}
int mausb_data_iterator_read(struct mausb_data_iter *iterator,
u32 byte_num,
struct list_head *data_chunks_list,
u32 *data_chunks_num)
{
if (iterator->buffer)
return mausb_read_virtual_buffer(iterator, byte_num,
data_chunks_list,
data_chunks_num);
else
return mausb_read_scatterlist_buffer(iterator, byte_num,
data_chunks_list,
data_chunks_num);
}
u32 mausb_data_iterator_write(struct mausb_data_iter *iterator, void *buffer,
u32 length)
{
if (iterator->buffer)
return mausb_write_virtual_buffer(iterator, buffer, length);
else
return mausb_write_scatterlist_buffer(iterator, buffer, length);
}
static inline void mausb_seek_virtual_buffer(struct mausb_data_iter *iterator,
u32 seek_delta)
{
iterator->offset += min(seek_delta, iterator->length -
iterator->offset);
}
static void mausb_seek_scatterlist_buffer(struct mausb_data_iter *iterator,
u32 seek_delta)
{
u32 rem_bytes_in_current_scatter;
while (seek_delta) {
rem_bytes_in_current_scatter = (u32)(iterator->sg_iter.length -
iterator->sg_iter.consumed);
if (rem_bytes_in_current_scatter <= seek_delta) {
iterator->sg_iter.consumed +=
rem_bytes_in_current_scatter;
seek_delta -= rem_bytes_in_current_scatter;
if (!sg_miter_next(&iterator->sg_iter))
break;
iterator->sg_iter.consumed = 0;
} else {
iterator->sg_iter.consumed += seek_delta;
break;
}
}
}
void mausb_data_iterator_seek(struct mausb_data_iter *iterator,
u32 seek_delta)
{
if (iterator->buffer)
mausb_seek_virtual_buffer(iterator, seek_delta);
else
mausb_seek_scatterlist_buffer(iterator, seek_delta);
}
static void mausb_calculate_buffer_length(struct mausb_data_iter *iterator)
{
/* Calculate buffer length */
if (iterator->buffer_len > 0) {
/* Transfer_buffer_length is populated */
iterator->length = iterator->buffer_len;
} else if (iterator->sg && iterator->num_sgs != 0) {
/* Transfer_buffer_length is not populated */
sg_miter_start(&iterator->sg_iter, iterator->sg,
iterator->num_sgs, iterator->flags);
while (sg_miter_next(&iterator->sg_iter))
iterator->length += (u32)iterator->sg_iter.length;
sg_miter_stop(&iterator->sg_iter);
} else {
iterator->length = 0;
}
}
void mausb_init_data_iterator(struct mausb_data_iter *iterator, void *buffer,
u32 buffer_len, struct scatterlist *sg,
unsigned int num_sgs, bool direction)
{
iterator->offset = 0;
iterator->buffer = buffer;
iterator->buffer_len = buffer_len;
iterator->length = 0;
iterator->sg = sg;
iterator->num_sgs = num_sgs;
iterator->sg_started = false;
mausb_calculate_buffer_length(iterator);
if (!buffer && sg && num_sgs != 0) {
/* Scatterlist provided */
iterator->flags = direction ? SG_MITER_TO_SG : SG_MITER_FROM_SG;
sg_miter_start(&iterator->sg_iter, sg, num_sgs,
iterator->flags);
iterator->sg_started = true;
}
}
void mausb_uninit_data_iterator(struct mausb_data_iter *iterator)
{
iterator->offset = 0;
iterator->length = 0;
iterator->buffer = NULL;
iterator->buffer_len = 0;
if (iterator->sg_started)
sg_miter_stop(&iterator->sg_iter);
iterator->sg_started = false;
}
void mausb_reset_data_iterator(struct mausb_data_iter *iterator)
{
iterator->offset = 0;
if (iterator->sg_started) {
sg_miter_stop(&iterator->sg_iter);
iterator->sg_started = false;
}
if (!iterator->buffer && iterator->sg && iterator->num_sgs != 0) {
sg_miter_start(&iterator->sg_iter, iterator->sg,
iterator->num_sgs, iterator->flags);
iterator->sg_started = true;
}
}
u32 mausb_data_iterator_length(struct mausb_data_iter *iterator)
{
return iterator->length;
}
static int mausb_ring_buffer_get(struct mausb_ring_buffer *ring,
struct mausb_event *event)
{
unsigned long flags;
spin_lock_irqsave(&ring->lock, flags);
if (CIRC_CNT(ring->head, ring->tail, MAUSB_RING_BUFFER_SIZE) < 1) {
spin_unlock_irqrestore(&ring->lock, flags);
return -ENOSPC;
}
memcpy(event, ring->to_user_buffer + ring->tail, sizeof(*event));
dev_vdbg(mausb_host_dev.this_device, "HEAD=%d, TAIL=%d", ring->head,
ring->tail);
ring->tail = (ring->tail + 1) & (MAUSB_RING_BUFFER_SIZE - 1);
dev_vdbg(mausb_host_dev.this_device, "HEAD=%d, TAIL=%d", ring->head,
ring->tail);
spin_unlock_irqrestore(&ring->lock, flags);
return 0;
}
int mausb_ring_buffer_init(struct mausb_ring_buffer *ring)
{
unsigned int page_order =
mausb_get_page_order(2 * MAUSB_RING_BUFFER_SIZE,
sizeof(struct mausb_event));
ring->to_user_buffer =
(struct mausb_event *)__get_free_pages(GFP_KERNEL, page_order);
if (!ring->to_user_buffer)
return -ENOMEM;
ring->from_user_buffer = ring->to_user_buffer + MAUSB_RING_BUFFER_SIZE;
ring->head = 0;
ring->tail = 0;
ring->current_from_user = 0;
ring->buffer_full = false;
spin_lock_init(&ring->lock);
return 0;
}
int mausb_ring_buffer_put(struct mausb_ring_buffer *ring,
struct mausb_event *event)
{
unsigned long flags;
spin_lock_irqsave(&ring->lock, flags);
if (ring->buffer_full) {
dev_err(mausb_host_dev.this_device, "Ring buffer is full");
spin_unlock_irqrestore(&ring->lock, flags);
return -ENOSPC;
}
if (CIRC_SPACE(ring->head, ring->tail, MAUSB_RING_BUFFER_SIZE) < 2) {
dev_err(mausb_host_dev.this_device, "Ring buffer capacity exceeded, disconnecting device");
ring->buffer_full = true;
mausb_disconect_event_unsafe(ring, event->madev_addr);
ring->head = (ring->head + 1) & (MAUSB_RING_BUFFER_SIZE - 1);
spin_unlock_irqrestore(&ring->lock, flags);
return -ENOSPC;
}
memcpy(ring->to_user_buffer + ring->head, event, sizeof(*event));
dev_vdbg(mausb_host_dev.this_device, "HEAD=%d, TAIL=%d",
ring->head, ring->tail);
ring->head = (ring->head + 1) & (MAUSB_RING_BUFFER_SIZE - 1);
dev_vdbg(mausb_host_dev.this_device, "HEAD=%d, TAIL=%d",
ring->head, ring->tail);
spin_unlock_irqrestore(&ring->lock, flags);
return 0;
}
int mausb_ring_buffer_move_tail(struct mausb_ring_buffer *ring, u32 count)
{
unsigned long flags;
spin_lock_irqsave(&ring->lock, flags);
if (CIRC_CNT(ring->head, ring->tail, MAUSB_RING_BUFFER_SIZE) < count) {
spin_unlock_irqrestore(&ring->lock, flags);
return -ENOSPC;
}
dev_vdbg(mausb_host_dev.this_device, "old HEAD=%d, TAIL=%d",
ring->head, ring->tail);
ring->tail = (ring->tail + (int)count) & (MAUSB_RING_BUFFER_SIZE - 1);
dev_vdbg(mausb_host_dev.this_device, "new HEAD=%d, TAIL=%d",
ring->head, ring->tail);
spin_unlock_irqrestore(&ring->lock, flags);
return 0;
}
void mausb_ring_buffer_cleanup(struct mausb_ring_buffer *ring)
{
struct mausb_event event;
while (mausb_ring_buffer_get(ring, &event) == 0)
mausb_cleanup_ring_buffer_event(&event);
}
void mausb_ring_buffer_destroy(struct mausb_ring_buffer *ring)
{
unsigned int page_order =
mausb_get_page_order(2 * MAUSB_RING_BUFFER_SIZE,
sizeof(struct mausb_event));
if (ring && ring->to_user_buffer)
free_pages((unsigned long)ring->to_user_buffer, page_order);
}
void mausb_cleanup_ring_buffer_event(struct mausb_event *event)
{
dev_dbg(mausb_host_dev.this_device, "Cleanup ring buffer event=%d",
event->type);
switch (event->type) {
case MAUSB_EVENT_TYPE_SEND_DATA_MSG:
mausb_cleanup_send_data_msg_event(event);
break;
case MAUSB_EVENT_TYPE_RECEIVED_DATA_MSG:
mausb_cleanup_received_data_msg_event(event);
break;
case MAUSB_EVENT_TYPE_DELETE_DATA_TRANSFER:
mausb_cleanup_delete_data_transfer_event(event);
break;
case MAUSB_EVENT_TYPE_NONE:
break;
default:
dev_warn(mausb_host_dev.this_device, "Unknown event type");
break;
}
}
void mausb_disconect_event_unsafe(struct mausb_ring_buffer *ring,
uint8_t madev_addr)
{
struct mausb_event disconnect_event;
struct mausb_device *dev = mausb_get_dev_from_addr_unsafe(madev_addr);
if (!dev) {
dev_err(mausb_host_dev.this_device, "Device not found, madev_addr=%#x",
madev_addr);
return;
}
disconnect_event.type = MAUSB_EVENT_TYPE_DEV_DISCONNECT;
disconnect_event.status = -EINVAL;
disconnect_event.madev_addr = madev_addr;
memcpy(ring->to_user_buffer + ring->head, &disconnect_event,
sizeof(disconnect_event));
dev_dbg(mausb_host_dev.this_device, "Adding hcd_disconnect_work to dev workq, madev_addr=%#x",
madev_addr);
queue_work(dev->workq, &dev->hcd_disconnect_work);
}