You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
8.5 KiB
338 lines
8.5 KiB
/*
|
|
* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#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 struct qrtr_usb_dev *__qdev;
|
|
|
|
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 = usb_get_intf(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");
|
|
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");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
init_usb_anchor(&qdev->submitted);
|
|
|
|
rc = qrtr_endpoint_register(&qdev->ep, QRTR_EP_NID_AUTO, false);
|
|
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);
|
|
}
|
|
|
|
__qdev = qdev;
|
|
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);
|
|
int rc = 0;
|
|
|
|
qdev->thread_state = QRTR_USB_RX_RUN;
|
|
wake_up(&qdev->qrtr_wq);
|
|
|
|
return rc;
|
|
}
|
|
|
|
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, false);
|
|
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_intf(interface);
|
|
qdev->iface = NULL;
|
|
usb_put_dev(qdev->udev);
|
|
__qdev = NULL;
|
|
}
|
|
|
|
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) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x90fd, 1) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x9102, 1) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x9103, 1) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x9106, 1) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x9107, 1) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x910A, 4) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x910B, 3) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x910C, 3) },
|
|
{ USB_DEVICE_INTERFACE_NUMBER(QRTR_VENDOR_ID, 0x910D, 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");
|
|
|