/* * 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 #include #include #include #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");