blob: fd71df9674940b05ab628decf61e1f41cfdf8e82 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2018, The Linux Foundation. All rights reserved. */
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/usb.h>
#include "qrtr.h"
#define QRTR_VENDOR_ID 0x05c6
#define MAX_DATA_SIZE 16384
enum qrtr_usb_rx_state {
QRTR_USB_RX_SUSPEND = 0,
QRTR_USB_RX_RUN,
QRTR_USB_RX_STOP
};
struct qrtr_usb_dev {
struct qrtr_endpoint ep;
struct usb_device *udev;
struct usb_interface *iface;
struct usb_anchor submitted;
struct completion in_compl;
struct urb *in_urb;
struct task_struct *rx_thread;
enum qrtr_usb_rx_state thread_state;
wait_queue_head_t qrtr_wq;
unsigned int in_pipe;
unsigned int out_pipe;
};
static void qcom_usb_qrtr_txn_cb(struct urb *urb)
{
struct completion *compl = urb->context;
complete(compl);
}
/* from usb to qrtr */
static int qcom_usb_qrtr_rx_thread_fn(void *data)
{
struct qrtr_usb_dev *qdev = data;
struct urb *in_urb = qdev->in_urb;
void *buf;
int rc = 0;
buf = kmalloc(MAX_DATA_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
usb_anchor_urb(in_urb, &qdev->submitted);
usb_fill_bulk_urb(in_urb, qdev->udev, qdev->in_pipe,
buf, MAX_DATA_SIZE, qcom_usb_qrtr_txn_cb,
&qdev->in_compl);
while (!kthread_should_stop()) {
if (qdev->thread_state == QRTR_USB_RX_SUSPEND ||
qdev->thread_state == QRTR_USB_RX_STOP) {
dev_dbg(&qdev->udev->dev,
"pausing or stopping thread, state=%d\n",
qdev->thread_state);
wait_event_interruptible(qdev->qrtr_wq,
qdev->thread_state ==
QRTR_USB_RX_RUN ||
kthread_should_stop());
continue;
}
rc = usb_autopm_get_interface(qdev->iface);
if (rc) {
dev_err(&qdev->udev->dev,
"failed to get autopm, rc=%d\n", rc);
continue;
}
reinit_completion(&qdev->in_compl);
rc = usb_submit_urb(in_urb, GFP_KERNEL);
if (rc) {
usb_autopm_put_interface(qdev->iface);
dev_dbg(&qdev->udev->dev,
"could not submit in urb, rc=%d\n", rc);
/* Give up if the device is disconnected */
if (rc == -ENODEV)
break;
continue;
}
wait_for_completion(&qdev->in_compl);
if (in_urb->status) {
usb_autopm_put_interface(qdev->iface);
dev_dbg(&qdev->udev->dev, "URB status %d",
in_urb->status);
continue;
}
usb_autopm_put_interface(qdev->iface);
dev_dbg(&qdev->udev->dev, "received message with len=%d\n",
in_urb->actual_length);
rc = qrtr_endpoint_post(&qdev->ep, buf, in_urb->actual_length);
if (rc == -EINVAL)
dev_err(&qdev->udev->dev,
"invalid ipcrouter packet\n");
}
kfree(buf);
wait_event_interruptible(qdev->qrtr_wq, kthread_should_stop());
dev_dbg(&qdev->udev->dev, "leaving rx_thread\n");
return rc;
}
/* from qrtr to usb */
static int qcom_usb_qrtr_send(struct qrtr_endpoint *ep, struct sk_buff *skb)
{
struct qrtr_usb_dev *qdev = container_of(ep, struct qrtr_usb_dev, ep);
struct urb *out_urb = NULL;
struct completion compl;
int rc;
rc = skb_linearize(skb);
if (rc)
goto exit_free_skb;
out_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!out_urb) {
rc = -ENOMEM;
goto exit_free_skb;
}
rc = usb_autopm_get_interface(qdev->iface);
if (rc)
goto exit_free_urb;
init_completion(&compl);
usb_fill_bulk_urb(out_urb, qdev->udev, qdev->out_pipe,
skb->data, skb->len, qcom_usb_qrtr_txn_cb, &compl);
usb_anchor_urb(out_urb, &qdev->submitted);
rc = usb_submit_urb(out_urb, GFP_KERNEL);
if (rc) {
dev_err(&qdev->udev->dev, "could not submit out urb\n");
usb_unanchor_urb(out_urb);
goto exit_autopm_put_intf;
}
wait_for_completion(&compl);
rc = out_urb->status;
dev_dbg(&qdev->udev->dev, "sent message with len=%d, rc=%d\n",
skb->len, rc);
exit_autopm_put_intf:
usb_autopm_put_interface(qdev->iface);
exit_free_urb:
usb_free_urb(out_urb);
exit_free_skb:
if (rc)
kfree_skb(skb);
else
consume_skb(skb);
return rc;
}
static int qcom_usb_qrtr_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
struct qrtr_usb_dev *qdev;
struct usb_device *udev;
struct usb_host_interface *intf_desc;
struct usb_endpoint_descriptor *ep_desc;
int rc, i;
udev = usb_get_dev(interface_to_usbdev(interface));
if (!udev)
return -ENODEV;
qdev = devm_kzalloc(&udev->dev, sizeof(*qdev), GFP_KERNEL);
if (!qdev)
return -ENOMEM;
qdev->udev = udev;
qdev->iface = interface;
qdev->ep.xmit = qcom_usb_qrtr_send;
intf_desc = interface->cur_altsetting;
for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
ep_desc = &intf_desc->endpoint[i].desc;
if (!qdev->in_pipe && usb_endpoint_is_bulk_in(ep_desc))
qdev->in_pipe =
usb_rcvbulkpipe(qdev->udev, ep_desc->bEndpointAddress);
if (!qdev->out_pipe && usb_endpoint_is_bulk_out(ep_desc))
qdev->out_pipe =
usb_sndbulkpipe(qdev->udev, ep_desc->bEndpointAddress);
}
if (!qdev->in_pipe || !qdev->out_pipe) {
dev_err(&qdev->udev->dev, "could not find endpoints\n");
return -ENODEV;
}
qdev->in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!qdev->in_urb) {
dev_err(&qdev->udev->dev, "could not allocate in urb\n");
return -ENOMEM;
}
init_usb_anchor(&qdev->submitted);
rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO);
if (rc)
return rc;
usb_set_intfdata(interface, qdev);
init_waitqueue_head(&qdev->qrtr_wq);
init_completion(&qdev->in_compl);
qdev->thread_state = QRTR_USB_RX_RUN;
qdev->rx_thread = kthread_run(qcom_usb_qrtr_rx_thread_fn, qdev,
"qrtr-usb-rx");
if (IS_ERR(qdev->rx_thread)) {
dev_err(&qdev->udev->dev, "could not create rx_thread\n");
qrtr_endpoint_unregister(&qdev->ep);
return PTR_ERR(qdev->rx_thread);
}
dev_dbg(&qdev->udev->dev, "QTI USB QRTR driver probed\n");
return 0;
}
static int qcom_usb_qrtr_suspend(struct usb_interface *intf,
pm_message_t message)
{
struct qrtr_usb_dev *qdev = usb_get_intfdata(intf);
/* Suspend the thread */
qdev->thread_state = QRTR_USB_RX_SUSPEND;
usb_kill_anchored_urbs(&qdev->submitted);
return 0;
}
static int qcom_usb_qrtr_resume(struct usb_interface *intf)
{
struct qrtr_usb_dev *qdev = usb_get_intfdata(intf);
qdev->thread_state = QRTR_USB_RX_RUN;
wake_up(&qdev->qrtr_wq);
return 0;
}
static int qcom_usb_qrtr_reset_resume(struct usb_interface *intf)
{
struct qrtr_usb_dev *qdev = usb_get_intfdata(intf);
int rc = 0;
qrtr_endpoint_unregister(&qdev->ep);
rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO);
if (rc)
return rc;
qdev->thread_state = QRTR_USB_RX_RUN;
wake_up(&qdev->qrtr_wq);
return rc;
}
static void qcom_usb_qrtr_disconnect(struct usb_interface *interface)
{
struct qrtr_usb_dev *qdev = usb_get_intfdata(interface);
qdev->thread_state = QRTR_USB_RX_STOP;
usb_kill_anchored_urbs(&qdev->submitted);
kthread_stop(qdev->rx_thread);
usb_free_urb(qdev->in_urb);
qrtr_endpoint_unregister(&qdev->ep);
usb_set_intfdata(interface, NULL);
usb_put_dev(qdev->udev);
}
static const struct usb_device_id qcom_usb_qrtr_ids[] = {
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x90ef, 3) },
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x90f0, 3) },
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x90f3, 2) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, qcom_usb_qrtr_ids);
static struct usb_driver qcom_usb_qrtr_driver = {
.name = "qcom_usb_qrtr",
.probe = qcom_usb_qrtr_probe,
.disconnect = qcom_usb_qrtr_disconnect,
.suspend = qcom_usb_qrtr_suspend,
.resume = qcom_usb_qrtr_resume,
.reset_resume = qcom_usb_qrtr_reset_resume,
.id_table = qcom_usb_qrtr_ids,
.supports_autosuspend = 1,
};
module_usb_driver(qcom_usb_qrtr_driver);
MODULE_DESCRIPTION("QTI IPC-Router USB interface driver");
MODULE_LICENSE("GPL v2");