| /* | |
| * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> | |
| * Copyright (C) 2015-2016 Samsung Electronics | |
| * Igor Kotrasinski <i.kotrasinsk@samsung.com> | |
| * Krzysztof Opasiak <k.opasiak@samsung.com> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * 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. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| */ | |
| #include <linux/device.h> | |
| #include <linux/kernel.h> | |
| #include <linux/list.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/usb.h> | |
| #include <linux/usb/gadget.h> | |
| #include <linux/usb/hcd.h> | |
| #include <linux/kthread.h> | |
| #include <linux/file.h> | |
| #include <linux/byteorder/generic.h> | |
| #include "usbip_common.h" | |
| #include "vudc.h" | |
| #define VIRTUAL_ENDPOINTS (1 /* ep0 */ + 15 /* in eps */ + 15 /* out eps */) | |
| /* urb-related structures alloc / free */ | |
| static void free_urb(struct urb *urb) | |
| { | |
| if (!urb) | |
| return; | |
| kfree(urb->setup_packet); | |
| urb->setup_packet = NULL; | |
| kfree(urb->transfer_buffer); | |
| urb->transfer_buffer = NULL; | |
| usb_free_urb(urb); | |
| } | |
| struct urbp *alloc_urbp(void) | |
| { | |
| struct urbp *urb_p; | |
| urb_p = kzalloc(sizeof(*urb_p), GFP_KERNEL); | |
| if (!urb_p) | |
| return urb_p; | |
| urb_p->urb = NULL; | |
| urb_p->ep = NULL; | |
| INIT_LIST_HEAD(&urb_p->urb_entry); | |
| return urb_p; | |
| } | |
| static void free_urbp(struct urbp *urb_p) | |
| { | |
| kfree(urb_p); | |
| } | |
| void free_urbp_and_urb(struct urbp *urb_p) | |
| { | |
| if (!urb_p) | |
| return; | |
| free_urb(urb_p->urb); | |
| free_urbp(urb_p); | |
| } | |
| /* utilities ; almost verbatim from dummy_hcd.c */ | |
| /* called with spinlock held */ | |
| static void nuke(struct vudc *udc, struct vep *ep) | |
| { | |
| struct vrequest *req; | |
| while (!list_empty(&ep->req_queue)) { | |
| req = list_first_entry(&ep->req_queue, struct vrequest, | |
| req_entry); | |
| list_del_init(&req->req_entry); | |
| req->req.status = -ESHUTDOWN; | |
| spin_unlock(&udc->lock); | |
| usb_gadget_giveback_request(&ep->ep, &req->req); | |
| spin_lock(&udc->lock); | |
| } | |
| } | |
| /* caller must hold lock */ | |
| static void stop_activity(struct vudc *udc) | |
| { | |
| int i; | |
| struct urbp *urb_p, *tmp; | |
| udc->address = 0; | |
| for (i = 0; i < VIRTUAL_ENDPOINTS; i++) | |
| nuke(udc, &udc->ep[i]); | |
| list_for_each_entry_safe(urb_p, tmp, &udc->urb_queue, urb_entry) { | |
| list_del(&urb_p->urb_entry); | |
| free_urbp_and_urb(urb_p); | |
| } | |
| } | |
| struct vep *vudc_find_endpoint(struct vudc *udc, u8 address) | |
| { | |
| int i; | |
| if ((address & ~USB_DIR_IN) == 0) | |
| return &udc->ep[0]; | |
| for (i = 1; i < VIRTUAL_ENDPOINTS; i++) { | |
| struct vep *ep = &udc->ep[i]; | |
| if (!ep->desc) | |
| continue; | |
| if (ep->desc->bEndpointAddress == address) | |
| return ep; | |
| } | |
| return NULL; | |
| } | |
| /* gadget ops */ | |
| /* FIXME - this will probably misbehave when suspend/resume is added */ | |
| static int vgadget_get_frame(struct usb_gadget *_gadget) | |
| { | |
| struct timeval now; | |
| struct vudc *udc = usb_gadget_to_vudc(_gadget); | |
| do_gettimeofday(&now); | |
| return ((now.tv_sec - udc->start_time.tv_sec) * 1000 + | |
| (now.tv_usec - udc->start_time.tv_usec) / 1000) | |
| % 0x7FF; | |
| } | |
| static int vgadget_set_selfpowered(struct usb_gadget *_gadget, int value) | |
| { | |
| struct vudc *udc = usb_gadget_to_vudc(_gadget); | |
| if (value) | |
| udc->devstatus |= (1 << USB_DEVICE_SELF_POWERED); | |
| else | |
| udc->devstatus &= ~(1 << USB_DEVICE_SELF_POWERED); | |
| return 0; | |
| } | |
| static int vgadget_pullup(struct usb_gadget *_gadget, int value) | |
| { | |
| struct vudc *udc = usb_gadget_to_vudc(_gadget); | |
| unsigned long flags; | |
| int ret; | |
| spin_lock_irqsave(&udc->lock, flags); | |
| value = !!value; | |
| if (value == udc->pullup) | |
| goto unlock; | |
| udc->pullup = value; | |
| if (value) { | |
| udc->gadget.speed = min_t(u8, USB_SPEED_HIGH, | |
| udc->driver->max_speed); | |
| udc->ep[0].ep.maxpacket = 64; | |
| /* | |
| * This is the first place where we can ask our | |
| * gadget driver for descriptors. | |
| */ | |
| ret = get_gadget_descs(udc); | |
| if (ret) { | |
| dev_err(&udc->gadget.dev, "Unable go get desc: %d", ret); | |
| goto unlock; | |
| } | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| usbip_start_eh(&udc->ud); | |
| } else { | |
| /* Invalidate descriptors */ | |
| udc->desc_cached = 0; | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| usbip_event_add(&udc->ud, VUDC_EVENT_REMOVED); | |
| usbip_stop_eh(&udc->ud); /* Wait for eh completion */ | |
| } | |
| return 0; | |
| unlock: | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| return 0; | |
| } | |
| static int vgadget_udc_start(struct usb_gadget *g, | |
| struct usb_gadget_driver *driver) | |
| { | |
| struct vudc *udc = usb_gadget_to_vudc(g); | |
| unsigned long flags; | |
| spin_lock_irqsave(&udc->lock, flags); | |
| udc->driver = driver; | |
| udc->pullup = udc->connected = udc->desc_cached = 0; | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| return 0; | |
| } | |
| static int vgadget_udc_stop(struct usb_gadget *g) | |
| { | |
| struct vudc *udc = usb_gadget_to_vudc(g); | |
| unsigned long flags; | |
| spin_lock_irqsave(&udc->lock, flags); | |
| udc->driver = NULL; | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| return 0; | |
| } | |
| static const struct usb_gadget_ops vgadget_ops = { | |
| .get_frame = vgadget_get_frame, | |
| .set_selfpowered = vgadget_set_selfpowered, | |
| .pullup = vgadget_pullup, | |
| .udc_start = vgadget_udc_start, | |
| .udc_stop = vgadget_udc_stop, | |
| }; | |
| /* endpoint ops */ | |
| static int vep_enable(struct usb_ep *_ep, | |
| const struct usb_endpoint_descriptor *desc) | |
| { | |
| struct vep *ep; | |
| struct vudc *udc; | |
| unsigned int maxp; | |
| unsigned long flags; | |
| ep = to_vep(_ep); | |
| udc = ep_to_vudc(ep); | |
| if (!_ep || !desc || ep->desc || _ep->caps.type_control | |
| || desc->bDescriptorType != USB_DT_ENDPOINT) | |
| return -EINVAL; | |
| if (!udc->driver) | |
| return -ESHUTDOWN; | |
| spin_lock_irqsave(&udc->lock, flags); | |
| maxp = usb_endpoint_maxp(desc); | |
| _ep->maxpacket = maxp; | |
| ep->desc = desc; | |
| ep->type = usb_endpoint_type(desc); | |
| ep->halted = ep->wedged = 0; | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| return 0; | |
| } | |
| static int vep_disable(struct usb_ep *_ep) | |
| { | |
| struct vep *ep; | |
| struct vudc *udc; | |
| unsigned long flags; | |
| ep = to_vep(_ep); | |
| udc = ep_to_vudc(ep); | |
| if (!_ep || !ep->desc || _ep->caps.type_control) | |
| return -EINVAL; | |
| spin_lock_irqsave(&udc->lock, flags); | |
| ep->desc = NULL; | |
| nuke(udc, ep); | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| return 0; | |
| } | |
| static struct usb_request *vep_alloc_request(struct usb_ep *_ep, | |
| gfp_t mem_flags) | |
| { | |
| struct vep *ep; | |
| struct vrequest *req; | |
| if (!_ep) | |
| return NULL; | |
| ep = to_vep(_ep); | |
| req = kzalloc(sizeof(*req), mem_flags); | |
| if (!req) | |
| return NULL; | |
| INIT_LIST_HEAD(&req->req_entry); | |
| return &req->req; | |
| } | |
| static void vep_free_request(struct usb_ep *_ep, struct usb_request *_req) | |
| { | |
| struct vrequest *req; | |
| if (WARN_ON(!_ep || !_req)) | |
| return; | |
| req = to_vrequest(_req); | |
| kfree(req); | |
| } | |
| static int vep_queue(struct usb_ep *_ep, struct usb_request *_req, | |
| gfp_t mem_flags) | |
| { | |
| struct vep *ep; | |
| struct vrequest *req; | |
| struct vudc *udc; | |
| unsigned long flags; | |
| if (!_ep || !_req) | |
| return -EINVAL; | |
| ep = to_vep(_ep); | |
| req = to_vrequest(_req); | |
| udc = ep_to_vudc(ep); | |
| spin_lock_irqsave(&udc->lock, flags); | |
| _req->actual = 0; | |
| _req->status = -EINPROGRESS; | |
| list_add_tail(&req->req_entry, &ep->req_queue); | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| return 0; | |
| } | |
| static int vep_dequeue(struct usb_ep *_ep, struct usb_request *_req) | |
| { | |
| struct vep *ep; | |
| struct vrequest *req; | |
| struct vudc *udc; | |
| struct vrequest *lst; | |
| unsigned long flags; | |
| int ret = -EINVAL; | |
| if (!_ep || !_req) | |
| return ret; | |
| ep = to_vep(_ep); | |
| req = to_vrequest(_req); | |
| udc = req->udc; | |
| if (!udc->driver) | |
| return -ESHUTDOWN; | |
| spin_lock_irqsave(&udc->lock, flags); | |
| list_for_each_entry(lst, &ep->req_queue, req_entry) { | |
| if (&lst->req == _req) { | |
| list_del_init(&lst->req_entry); | |
| _req->status = -ECONNRESET; | |
| ret = 0; | |
| break; | |
| } | |
| } | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| if (ret == 0) | |
| usb_gadget_giveback_request(_ep, _req); | |
| return ret; | |
| } | |
| static int | |
| vep_set_halt_and_wedge(struct usb_ep *_ep, int value, int wedged) | |
| { | |
| struct vep *ep; | |
| struct vudc *udc; | |
| unsigned long flags; | |
| int ret = 0; | |
| ep = to_vep(_ep); | |
| if (!_ep) | |
| return -EINVAL; | |
| udc = ep_to_vudc(ep); | |
| if (!udc->driver) | |
| return -ESHUTDOWN; | |
| spin_lock_irqsave(&udc->lock, flags); | |
| if (!value) | |
| ep->halted = ep->wedged = 0; | |
| else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && | |
| !list_empty(&ep->req_queue)) | |
| ret = -EAGAIN; | |
| else { | |
| ep->halted = 1; | |
| if (wedged) | |
| ep->wedged = 1; | |
| } | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| return ret; | |
| } | |
| static int | |
| vep_set_halt(struct usb_ep *_ep, int value) | |
| { | |
| return vep_set_halt_and_wedge(_ep, value, 0); | |
| } | |
| static int vep_set_wedge(struct usb_ep *_ep) | |
| { | |
| return vep_set_halt_and_wedge(_ep, 1, 1); | |
| } | |
| static const struct usb_ep_ops vep_ops = { | |
| .enable = vep_enable, | |
| .disable = vep_disable, | |
| .alloc_request = vep_alloc_request, | |
| .free_request = vep_free_request, | |
| .queue = vep_queue, | |
| .dequeue = vep_dequeue, | |
| .set_halt = vep_set_halt, | |
| .set_wedge = vep_set_wedge, | |
| }; | |
| /* shutdown / reset / error handlers */ | |
| static void vudc_shutdown(struct usbip_device *ud) | |
| { | |
| struct vudc *udc = container_of(ud, struct vudc, ud); | |
| int call_disconnect = 0; | |
| unsigned long flags; | |
| dev_dbg(&udc->pdev->dev, "device shutdown"); | |
| if (ud->tcp_socket) | |
| kernel_sock_shutdown(ud->tcp_socket, SHUT_RDWR); | |
| if (ud->tcp_rx) { | |
| kthread_stop_put(ud->tcp_rx); | |
| ud->tcp_rx = NULL; | |
| } | |
| if (ud->tcp_tx) { | |
| kthread_stop_put(ud->tcp_tx); | |
| ud->tcp_tx = NULL; | |
| } | |
| if (ud->tcp_socket) { | |
| sockfd_put(ud->tcp_socket); | |
| ud->tcp_socket = NULL; | |
| } | |
| spin_lock_irqsave(&udc->lock, flags); | |
| stop_activity(udc); | |
| if (udc->connected && udc->driver->disconnect) | |
| call_disconnect = 1; | |
| udc->connected = 0; | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| if (call_disconnect) | |
| udc->driver->disconnect(&udc->gadget); | |
| } | |
| static void vudc_device_reset(struct usbip_device *ud) | |
| { | |
| struct vudc *udc = container_of(ud, struct vudc, ud); | |
| unsigned long flags; | |
| dev_dbg(&udc->pdev->dev, "device reset"); | |
| spin_lock_irqsave(&udc->lock, flags); | |
| stop_activity(udc); | |
| spin_unlock_irqrestore(&udc->lock, flags); | |
| if (udc->driver) | |
| usb_gadget_udc_reset(&udc->gadget, udc->driver); | |
| spin_lock_irqsave(&ud->lock, flags); | |
| ud->status = SDEV_ST_AVAILABLE; | |
| spin_unlock_irqrestore(&ud->lock, flags); | |
| } | |
| static void vudc_device_unusable(struct usbip_device *ud) | |
| { | |
| unsigned long flags; | |
| spin_lock_irqsave(&ud->lock, flags); | |
| ud->status = SDEV_ST_ERROR; | |
| spin_unlock_irqrestore(&ud->lock, flags); | |
| } | |
| /* device setup / cleanup */ | |
| struct vudc_device *alloc_vudc_device(int devid) | |
| { | |
| struct vudc_device *udc_dev = NULL; | |
| udc_dev = kzalloc(sizeof(*udc_dev), GFP_KERNEL); | |
| if (!udc_dev) | |
| goto out; | |
| INIT_LIST_HEAD(&udc_dev->dev_entry); | |
| udc_dev->pdev = platform_device_alloc(GADGET_NAME, devid); | |
| if (!udc_dev->pdev) { | |
| kfree(udc_dev); | |
| udc_dev = NULL; | |
| } | |
| out: | |
| return udc_dev; | |
| } | |
| void put_vudc_device(struct vudc_device *udc_dev) | |
| { | |
| platform_device_put(udc_dev->pdev); | |
| kfree(udc_dev); | |
| } | |
| static int init_vudc_hw(struct vudc *udc) | |
| { | |
| int i; | |
| struct usbip_device *ud = &udc->ud; | |
| struct vep *ep; | |
| udc->ep = kcalloc(VIRTUAL_ENDPOINTS, sizeof(*udc->ep), GFP_KERNEL); | |
| if (!udc->ep) | |
| goto nomem_ep; | |
| INIT_LIST_HEAD(&udc->gadget.ep_list); | |
| /* create ep0 and 15 in, 15 out general purpose eps */ | |
| for (i = 0; i < VIRTUAL_ENDPOINTS; ++i) { | |
| int is_out = i % 2; | |
| int num = (i + 1) / 2; | |
| ep = &udc->ep[i]; | |
| sprintf(ep->name, "ep%d%s", num, | |
| i ? (is_out ? "out" : "in") : ""); | |
| ep->ep.name = ep->name; | |
| ep->ep.ops = &vep_ops; | |
| usb_ep_set_maxpacket_limit(&ep->ep, ~0); | |
| ep->ep.max_streams = 16; | |
| ep->gadget = &udc->gadget; | |
| INIT_LIST_HEAD(&ep->req_queue); | |
| if (i == 0) { | |
| /* ep0 */ | |
| ep->ep.caps.type_control = true; | |
| ep->ep.caps.dir_out = true; | |
| ep->ep.caps.dir_in = true; | |
| udc->gadget.ep0 = &ep->ep; | |
| } else { | |
| /* All other eps */ | |
| ep->ep.caps.type_iso = true; | |
| ep->ep.caps.type_int = true; | |
| ep->ep.caps.type_bulk = true; | |
| if (is_out) | |
| ep->ep.caps.dir_out = true; | |
| else | |
| ep->ep.caps.dir_in = true; | |
| list_add_tail(&ep->ep.ep_list, &udc->gadget.ep_list); | |
| } | |
| } | |
| spin_lock_init(&udc->lock); | |
| spin_lock_init(&udc->lock_tx); | |
| INIT_LIST_HEAD(&udc->urb_queue); | |
| INIT_LIST_HEAD(&udc->tx_queue); | |
| init_waitqueue_head(&udc->tx_waitq); | |
| spin_lock_init(&ud->lock); | |
| ud->status = SDEV_ST_AVAILABLE; | |
| ud->side = USBIP_VUDC; | |
| ud->eh_ops.shutdown = vudc_shutdown; | |
| ud->eh_ops.reset = vudc_device_reset; | |
| ud->eh_ops.unusable = vudc_device_unusable; | |
| v_init_timer(udc); | |
| return 0; | |
| nomem_ep: | |
| return -ENOMEM; | |
| } | |
| static void cleanup_vudc_hw(struct vudc *udc) | |
| { | |
| kfree(udc->ep); | |
| } | |
| /* platform driver ops */ | |
| int vudc_probe(struct platform_device *pdev) | |
| { | |
| struct vudc *udc; | |
| int ret = -ENOMEM; | |
| udc = kzalloc(sizeof(*udc), GFP_KERNEL); | |
| if (!udc) | |
| goto out; | |
| udc->gadget.name = GADGET_NAME; | |
| udc->gadget.ops = &vgadget_ops; | |
| udc->gadget.max_speed = USB_SPEED_HIGH; | |
| udc->gadget.dev.parent = &pdev->dev; | |
| udc->pdev = pdev; | |
| ret = init_vudc_hw(udc); | |
| if (ret) | |
| goto err_init_vudc_hw; | |
| ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget); | |
| if (ret < 0) | |
| goto err_add_udc; | |
| ret = sysfs_create_group(&pdev->dev.kobj, &vudc_attr_group); | |
| if (ret) { | |
| dev_err(&udc->pdev->dev, "create sysfs files\n"); | |
| goto err_sysfs; | |
| } | |
| platform_set_drvdata(pdev, udc); | |
| return ret; | |
| err_sysfs: | |
| usb_del_gadget_udc(&udc->gadget); | |
| err_add_udc: | |
| cleanup_vudc_hw(udc); | |
| err_init_vudc_hw: | |
| kfree(udc); | |
| out: | |
| return ret; | |
| } | |
| int vudc_remove(struct platform_device *pdev) | |
| { | |
| struct vudc *udc = platform_get_drvdata(pdev); | |
| sysfs_remove_group(&pdev->dev.kobj, &vudc_attr_group); | |
| usb_del_gadget_udc(&udc->gadget); | |
| cleanup_vudc_hw(udc); | |
| kfree(udc); | |
| return 0; | |
| } |