From 04dd17822334871b23ea2862f7798fb0e0007777 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Sat, 11 May 2024 08:53:19 +0000 Subject: [PATCH] change otg to host mode --- kernel/drivers/usb/gadget/udc/core.c | 307 ++++++++++++++++++++++++++++++++++++-------------- 1 files changed, 219 insertions(+), 88 deletions(-) diff --git a/kernel/drivers/usb/gadget/udc/core.c b/kernel/drivers/usb/gadget/udc/core.c index 22b2b81..74bc551 100644 --- a/kernel/drivers/usb/gadget/udc/core.c +++ b/kernel/drivers/usb/gadget/udc/core.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -/** +/* * udc.c - Core UDC Framework * * Copyright (C) 2010 Texas Instruments @@ -23,12 +23,13 @@ /** * struct usb_udc - describes one usb device controller - * @driver - the gadget driver pointer. For use by the class code - * @dev - the child device to the actual controller - * @gadget - the gadget. For use by the class code - * @list - for use by the udc class driver - * @vbus - for udcs who care about vbus status, this value is real vbus status; + * @driver: the gadget driver pointer. For use by the class code + * @dev: the child device to the actual controller + * @gadget: the gadget. For use by the class code + * @list: for use by the udc class driver + * @vbus: for udcs who care about vbus status, this value is real vbus status; * for udcs who do not care about vbus status, this value is always true + * @started: the UDC's started state. True if the UDC had started. * * This represents the internal data structure which is used by the UDC-class * to hold information about udc driver and gadget together. @@ -39,6 +40,7 @@ struct device dev; struct list_head list; bool vbus; + bool started; }; static struct class *udc_class; @@ -85,9 +87,9 @@ * for interrupt transfers as well as bulk, but it likely couldn't be used * for iso transfers or for endpoint 14. some endpoints are fully * configurable, with more generic names like "ep-a". (remember that for - * USB, "in" means "towards the USB master".) + * USB, "in" means "towards the USB host".) * - * This routine must be called in process context. + * This routine may be called in an atomic (interrupt) context. * * returns zero, or a negative error code. */ @@ -132,7 +134,7 @@ * gadget drivers must call usb_ep_enable() again before queueing * requests to the endpoint. * - * This routine must be called in process context. + * This routine may be called in an atomic (interrupt) context. * * returns zero, or a negative error code. */ @@ -292,10 +294,10 @@ * @ep:the endpoint associated with the request * @req:the request being canceled * - * If the request is still active on the endpoint, it is dequeued and its - * completion routine is called (with status -ECONNRESET); else a negative - * error code is returned. This is guaranteed to happen before the call to - * usb_ep_dequeue() returns. + * If the request is still active on the endpoint, it is dequeued and + * eventually its completion routine is called (with status -ECONNRESET); + * else a negative error code is returned. This routine is asynchronous, + * that is, it may return before the completion routine runs. * * Note that some hardware can't clear out write fifos (to unlink the request * at the head of the queue) except as part of disconnecting from usb. Such @@ -507,43 +509,6 @@ EXPORT_SYMBOL_GPL(usb_gadget_wakeup); /** - * usb_gsi_ep_op - performs operation on GSI accelerated EP based on EP op code - * - * Operations such as EP configuration, TRB allocation, StartXfer etc. - * See gsi_ep_op for more details. - */ -int usb_gsi_ep_op(struct usb_ep *ep, - struct usb_gsi_request *req, enum gsi_ep_op op) -{ - if (ep && ep->ops && ep->ops->gsi_ep_op) - return ep->ops->gsi_ep_op(ep, req, op); - - return -EOPNOTSUPP; -} -EXPORT_SYMBOL_GPL(usb_gsi_ep_op); - -/** - * usb_gadget_func_wakeup - send a function remote wakeup up notification - * to the host connected to this gadget - * @gadget: controller used to wake up the host - * @interface_id: the interface which triggered the remote wakeup event - * - * Returns zero on success. Otherwise, negative error code is returned. - */ -int usb_gadget_func_wakeup(struct usb_gadget *gadget, - int interface_id) -{ - if (!gadget || (gadget->speed != USB_SPEED_SUPER)) - return -EOPNOTSUPP; - - if (!gadget->ops || !gadget->ops->func_wakeup) - return -EOPNOTSUPP; - - return gadget->ops->func_wakeup(gadget, interface_id); -} -EXPORT_SYMBOL_GPL(usb_gadget_func_wakeup); - -/** * usb_gadget_set_selfpowered - sets the device selfpowered feature. * @gadget:the device being declared as self-powered * @@ -738,6 +703,9 @@ * as a disconnect (when a VBUS session is active). Not all systems * support software pullup controls. * + * Following a successful disconnect, invoke the ->disconnect() callback + * for the current gadget driver so that UDC drivers don't need to. + * * Returns zero on success, else negative errno. */ int usb_gadget_disconnect(struct usb_gadget *gadget) @@ -748,6 +716,9 @@ ret = -EOPNOTSUPP; goto out; } + + if (!gadget->connected) + goto out; if (gadget->deactivated) { /* @@ -761,6 +732,9 @@ ret = gadget->ops->pullup(gadget, 0); if (!ret) gadget->connected = 0; + + if (gadget->udc->driver) + gadget->udc->driver->disconnect(gadget); out: trace_usb_gadget_disconnect(gadget, ret); @@ -787,7 +761,7 @@ if (!gadget || gadget->deactivated) goto out; - if (gadget->connected && !gadget->uvc_enabled) { + if (gadget->connected) { ret = usb_gadget_disconnect(gadget); if (ret) goto out; @@ -923,6 +897,9 @@ /** * usb_gadget_giveback_request - give the request back to the gadget layer + * @ep: the endpoint to be used with with the request + * @req: the request being given back + * * Context: in_interrupt() * * This is called by device controller drivers in order to return the @@ -1030,6 +1007,25 @@ } EXPORT_SYMBOL_GPL(usb_gadget_ep_match_desc); +/** + * usb_gadget_check_config - checks if the UDC can support the binded + * configuration + * @gadget: controller to check the USB configuration + * + * Ensure that a UDC is able to support the requested resources by a + * configuration, and that there are no resource limitations, such as + * internal memory allocated to all requested endpoints. + * + * Returns zero on success, else a negative errno. + */ +int usb_gadget_check_config(struct usb_gadget *gadget) +{ + if (gadget->ops->check_config) + return gadget->ops->check_config(gadget); + return 0; +} +EXPORT_SYMBOL_GPL(usb_gadget_check_config); + /* ------------------------------------------------------------------------- */ static void usb_gadget_state_work(struct work_struct *work) @@ -1051,12 +1047,16 @@ /* ------------------------------------------------------------------------- */ -static void usb_udc_connect_control(struct usb_udc *udc) +static int usb_udc_connect_control(struct usb_udc *udc) { + int ret; + if (udc->vbus) - usb_gadget_connect(udc->gadget); + ret = usb_gadget_connect(udc->gadget); else - usb_gadget_disconnect(udc->gadget); + ret = usb_gadget_disconnect(udc->gadget); + + return ret; } /** @@ -1111,13 +1111,23 @@ */ static inline int usb_gadget_udc_start(struct usb_udc *udc) { - return udc->gadget->ops->udc_start(udc->gadget, udc->driver); + int ret; + + if (udc->started) { + dev_err(&udc->dev, "UDC had already started\n"); + return -EBUSY; + } + + ret = udc->gadget->ops->udc_start(udc->gadget, udc->driver); + if (!ret) + udc->started = true; + + return ret; } /** * usb_gadget_udc_stop - tells usb device controller we don't need it anymore - * @gadget: The device we want to stop activity - * @driver: The driver to unbind from @gadget + * @udc: The UDC to be stopped * * This call is issued by the UDC Class driver after calling * gadget driver's unbind() method. @@ -1128,7 +1138,13 @@ */ static inline void usb_gadget_udc_stop(struct usb_udc *udc) { + if (!udc->started) { + dev_err(&udc->dev, "UDC had already stopped\n"); + return; + } + udc->gadget->ops->udc_stop(udc->gadget); + udc->started = false; } /** @@ -1144,12 +1160,65 @@ static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, enum usb_device_speed speed) { - if (udc->gadget->ops->udc_set_speed) { - enum usb_device_speed s; + struct usb_gadget *gadget = udc->gadget; + enum usb_device_speed s; - s = min(speed, udc->gadget->max_speed); - udc->gadget->ops->udc_set_speed(udc->gadget, s); - } + if (speed == USB_SPEED_UNKNOWN) + s = gadget->max_speed; + else + s = min(speed, gadget->max_speed); + + if (s == USB_SPEED_SUPER_PLUS && gadget->ops->udc_set_ssp_rate) + gadget->ops->udc_set_ssp_rate(gadget, gadget->max_ssp_rate); + else if (gadget->ops->udc_set_speed) + gadget->ops->udc_set_speed(gadget, s); +} + +/** + * usb_gadget_enable_async_callbacks - tell usb device controller to enable asynchronous callbacks + * @udc: The UDC which should enable async callbacks + * + * This routine is used when binding gadget drivers. It undoes the effect + * of usb_gadget_disable_async_callbacks(); the UDC driver should enable IRQs + * (if necessary) and resume issuing callbacks. + * + * This routine will always be called in process context. + */ +static inline void usb_gadget_enable_async_callbacks(struct usb_udc *udc) +{ + struct usb_gadget *gadget = udc->gadget; + + if (gadget->ops->udc_async_callbacks) + gadget->ops->udc_async_callbacks(gadget, true); +} + +/** + * usb_gadget_disable_async_callbacks - tell usb device controller to disable asynchronous callbacks + * @udc: The UDC which should disable async callbacks + * + * This routine is used when unbinding gadget drivers. It prevents a race: + * The UDC driver doesn't know when the gadget driver's ->unbind callback + * runs, so unless it is told to disable asynchronous callbacks, it might + * issue a callback (such as ->disconnect) after the unbind has completed. + * + * After this function runs, the UDC driver must suppress all ->suspend, + * ->resume, ->disconnect, ->reset, and ->setup callbacks to the gadget driver + * until async callbacks are again enabled. A simple-minded but effective + * way to accomplish this is to tell the UDC hardware not to generate any + * more IRQs. + * + * Request completion callbacks must still be issued. However, it's okay + * to defer them until the request is cancelled, since the pull-up will be + * turned off during the time period when async callbacks are disabled. + * + * This routine will always be called in process context. + */ +static inline void usb_gadget_disable_async_callbacks(struct usb_udc *udc) +{ + struct usb_gadget *gadget = udc->gadget; + + if (gadget->ops->udc_async_callbacks) + gadget->ops->udc_async_callbacks(gadget, false); } /** @@ -1194,21 +1263,18 @@ } /** - * usb_add_gadget_udc_release - adds a new gadget to the udc class driver list + * usb_initialize_gadget - initialize a gadget and its embedded struct device * @parent: the parent device to this udc. Usually the controller driver's * device. - * @gadget: the gadget to be added to the list. + * @gadget: the gadget to be initialized. * @release: a gadget release function. * * Returns zero on success, negative errno otherwise. * Calls the gadget release function in the latter case. */ -int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, +void usb_initialize_gadget(struct device *parent, struct usb_gadget *gadget, void (*release)(struct device *dev)) { - struct usb_udc *udc; - int ret = -ENOMEM; - dev_set_name(&gadget->dev, "gadget"); INIT_WORK(&gadget->work, usb_gadget_state_work); gadget->dev.parent = parent; @@ -1219,17 +1285,32 @@ gadget->dev.release = usb_udc_nop_release; device_initialize(&gadget->dev); +} +EXPORT_SYMBOL_GPL(usb_initialize_gadget); + +/** + * usb_add_gadget - adds a new gadget to the udc class driver list + * @gadget: the gadget to be added to the list. + * + * Returns zero on success, negative errno otherwise. + * Does not do a final usb_put_gadget() if an error occurs. + */ +int usb_add_gadget(struct usb_gadget *gadget) +{ + struct usb_udc *udc; + int ret = -ENOMEM; udc = kzalloc(sizeof(*udc), GFP_KERNEL); if (!udc) - goto err_put_gadget; + goto error; device_initialize(&udc->dev); udc->dev.release = usb_udc_release; udc->dev.class = udc_class; udc->dev.groups = usb_udc_attr_groups; - udc->dev.parent = parent; - ret = dev_set_name(&udc->dev, "%s", kobject_name(&parent->kobj)); + udc->dev.parent = gadget->dev.parent; + ret = dev_set_name(&udc->dev, "%s", + kobject_name(&gadget->dev.parent->kobj)); if (ret) goto err_put_udc; @@ -1239,6 +1320,8 @@ udc->gadget = gadget; gadget->udc = udc; + + udc->started = false; mutex_lock(&udc_lock); list_add_tail(&udc->list, &udc_list); @@ -1260,6 +1343,7 @@ return 0; err_del_udc: + flush_work(&gadget->work); device_del(&udc->dev); err_unlist_udc: @@ -1271,8 +1355,30 @@ err_put_udc: put_device(&udc->dev); - err_put_gadget: - put_device(&gadget->dev); + error: + return ret; +} +EXPORT_SYMBOL_GPL(usb_add_gadget); + +/** + * usb_add_gadget_udc_release - adds a new gadget to the udc class driver list + * @parent: the parent device to this udc. Usually the controller driver's + * device. + * @gadget: the gadget to be added to the list. + * @release: a gadget release function. + * + * Returns zero on success, negative errno otherwise. + * Calls the gadget release function in the latter case. + */ +int usb_add_gadget_udc_release(struct device *parent, struct usb_gadget *gadget, + void (*release)(struct device *dev)) +{ + int ret; + + usb_initialize_gadget(parent, gadget, release); + ret = usb_add_gadget(gadget); + if (ret) + usb_put_gadget(gadget); return ret; } EXPORT_SYMBOL_GPL(usb_add_gadget_udc_release); @@ -1329,23 +1435,25 @@ kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); usb_gadget_disconnect(udc->gadget); - udc->driver->disconnect(udc->gadget); + usb_gadget_disable_async_callbacks(udc); + if (udc->gadget->irq) + synchronize_irq(udc->gadget->irq); udc->driver->unbind(udc->gadget); usb_gadget_udc_stop(udc); udc->driver = NULL; - udc->dev.driver = NULL; udc->gadget->dev.driver = NULL; } /** - * usb_del_gadget_udc - deletes @udc from udc_list + * usb_del_gadget - deletes @udc from udc_list * @gadget: the gadget to be removed. * - * This, will call usb_gadget_unregister_driver() if + * This will call usb_gadget_unregister_driver() if * the @udc is still busy. + * It will not do a final usb_put_gadget(). */ -void usb_del_gadget_udc(struct usb_gadget *gadget) +void usb_del_gadget(struct usb_gadget *gadget) { struct usb_udc *udc = gadget->udc; @@ -1368,8 +1476,20 @@ kobject_uevent(&udc->dev.kobj, KOBJ_REMOVE); flush_work(&gadget->work); device_unregister(&udc->dev); - device_unregister(&gadget->dev); - memset(&gadget->dev, 0x00, sizeof(gadget->dev)); + device_del(&gadget->dev); +} +EXPORT_SYMBOL_GPL(usb_del_gadget); + +/** + * usb_del_gadget_udc - deletes @udc from udc_list + * @gadget: the gadget to be removed. + * + * Calls usb_del_gadget() and does a final usb_put_gadget(). + */ +void usb_del_gadget_udc(struct usb_gadget *gadget) +{ + usb_del_gadget(gadget); + usb_put_gadget(gadget); } EXPORT_SYMBOL_GPL(usb_del_gadget_udc); @@ -1383,7 +1503,6 @@ driver->function); udc->driver = driver; - udc->dev.driver = &driver->driver; udc->gadget->dev.driver = &driver->driver; usb_gadget_udc_set_speed(udc, driver->max_speed); @@ -1392,20 +1511,31 @@ if (ret) goto err1; ret = usb_gadget_udc_start(udc); - if (ret) { - driver->unbind(udc->gadget); - goto err1; - } - usb_udc_connect_control(udc); + if (ret) + goto err_start; + + usb_gadget_enable_async_callbacks(udc); + ret = usb_udc_connect_control(udc); + if (ret) + goto err_connect_control; kobject_uevent(&udc->dev.kobj, KOBJ_CHANGE); return 0; + +err_connect_control: + usb_gadget_disable_async_callbacks(udc); + if (udc->gadget->irq) + synchronize_irq(udc->gadget->irq); + usb_gadget_udc_stop(udc); + +err_start: + driver->unbind(udc->gadget); + err1: if (ret != -EISNAM) dev_err(&udc->dev, "failed to start %s: %d\n", udc->driver->function, ret); udc->driver = NULL; - udc->dev.driver = NULL; udc->gadget->dev.driver = NULL; return ret; } @@ -1447,6 +1577,8 @@ } mutex_unlock(&udc_lock); + if (ret) + pr_warn("udc-core: couldn't find an available UDC or it's busy\n"); return ret; found: ret = udc_bind_to_driver(udc, driver); @@ -1522,7 +1654,6 @@ usb_gadget_connect(udc->gadget); } else if (sysfs_streq(buf, "disconnect")) { usb_gadget_disconnect(udc->gadget); - udc->driver->disconnect(udc->gadget); usb_gadget_udc_stop(udc); } else { dev_err(dev, "unsupported command '%s'\n", buf); -- Gitblit v1.6.2