From cf9a08ae5aece88987bbeee8eb0dd0ebb5015815 Mon Sep 17 00:00:00 2001 From: Sebastian Andrzej Siewior Date: Sun, 23 Dec 2012 21:10:01 +0100 Subject: [PATCH] usb: gadget: convert source sink and loopback to new function interface This patch converts the f_sourcesink and f_loopback file to the USB-function module. Both functions shares a few common utility functions which are currently implemented in g_zero.c itself. This patch moves the common code into the sourcesink file and creates one module out of the the two functions (source sink and loop back). The g_zero gadget is function specific to source sink and loop back to set a few options. This Symbol dependency enforces a modul load right now. Signed-off-by: Sebastian Andrzej Siewior Signed-off-by: Felipe Balbi --- drivers/usb/gadget/Kconfig | 4 + drivers/usb/gadget/Makefile | 4 + drivers/usb/gadget/f_loopback.c | 75 +++++++++--- drivers/usb/gadget/f_sourcesink.c | 165 +++++++++++++++++-------- drivers/usb/gadget/g_zero.h | 31 ++++- drivers/usb/gadget/zero.c | 196 ++++++++++++++++++------------ 6 files changed, 321 insertions(+), 154 deletions(-) diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 14625fd2cecd..0ef5549b6544 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -500,6 +500,9 @@ config USB_LIBCOMPOSITE tristate depends on USB_GADGET +config USB_F_SS_LB + tristate + choice tristate "USB Gadget Drivers" default USB_ETH @@ -524,6 +527,7 @@ choice config USB_ZERO tristate "Gadget Zero (DEVELOPMENT)" select USB_LIBCOMPOSITE + select USB_F_SS_LB help Gadget Zero is a two-configuration device. It either sinks and sources bulk data; or it loops back a configurable number of diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index fef41f5d6e8d..e412befa7a35 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -74,3 +74,7 @@ obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o obj-$(CONFIG_USB_G_ACM_MS) += g_acm_ms.o obj-$(CONFIG_USB_GADGET_TARGET) += tcm_usb_gadget.o + +# USB Functions +f_ss_lb-y := f_loopback.o f_sourcesink.o +obj-$(CONFIG_USB_F_SS_LB) += f_ss_lb.o diff --git a/drivers/usb/gadget/f_loopback.c b/drivers/usb/gadget/f_loopback.c index 3d103a2f998f..4a3873a0f2d0 100644 --- a/drivers/usb/gadget/f_loopback.c +++ b/drivers/usb/gadget/f_loopback.c @@ -15,10 +15,11 @@ #include #include #include +#include +#include +#include #include "g_zero.h" -#include "gadget_chips.h" - /* * LOOPBACK FUNCTION ... a testing vehicle for USB peripherals, @@ -44,9 +45,8 @@ static inline struct f_loopback *func_to_loop(struct usb_function *f) return container_of(f, struct f_loopback, function); } -static unsigned qlen = 32; -module_param(qlen, uint, 0); -MODULE_PARM_DESC(qlenn, "depth of loopback queue"); +static unsigned qlen; +static unsigned buflen; /*-------------------------------------------------------------------------*/ @@ -171,8 +171,7 @@ static struct usb_gadget_strings *loopback_strings[] = { /*-------------------------------------------------------------------------*/ -static int __init -loopback_bind(struct usb_configuration *c, struct usb_function *f) +static int loopback_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_loopback *loop = func_to_loop(f); @@ -229,8 +228,7 @@ autoconf_fail: return 0; } -static void -loopback_unbind(struct usb_configuration *c, struct usb_function *f) +static void lb_free_func(struct usb_function *f) { usb_free_all_descriptors(f); kfree(func_to_loop(f)); @@ -372,25 +370,64 @@ static void loopback_disable(struct usb_function *f) disable_loopback(loop); } -/*-------------------------------------------------------------------------*/ - -static int __init loopback_bind_config(struct usb_configuration *c) +static struct usb_function *loopback_alloc(struct usb_function_instance *fi) { struct f_loopback *loop; - int status; + struct f_lb_opts *lb_opts; loop = kzalloc(sizeof *loop, GFP_KERNEL); if (!loop) - return -ENOMEM; + return ERR_PTR(-ENOMEM); + + lb_opts = container_of(fi, struct f_lb_opts, func_inst); + buflen = lb_opts->bulk_buflen; + qlen = lb_opts->qlen; + if (!qlen) + qlen = 32; loop->function.name = "loopback"; loop->function.bind = loopback_bind; - loop->function.unbind = loopback_unbind; loop->function.set_alt = loopback_set_alt; loop->function.disable = loopback_disable; + loop->function.strings = loopback_strings; - status = usb_add_function(c, &loop->function); - if (status) - kfree(loop); - return status; + loop->function.free_func = lb_free_func; + + return &loop->function; } + +static void lb_free_instance(struct usb_function_instance *fi) +{ + struct f_lb_opts *lb_opts; + + lb_opts = container_of(fi, struct f_lb_opts, func_inst); + kfree(lb_opts); +} + +static struct usb_function_instance *loopback_alloc_instance(void) +{ + struct f_lb_opts *lb_opts; + + lb_opts = kzalloc(sizeof(*lb_opts), GFP_KERNEL); + if (!lb_opts) + return ERR_PTR(-ENOMEM); + lb_opts->func_inst.free_func_inst = lb_free_instance; + return &lb_opts->func_inst; +} +DECLARE_USB_FUNCTION(Loopback, loopback_alloc_instance, loopback_alloc); + +int __init lb_modinit(void) +{ + int ret; + + ret = usb_function_register(&Loopbackusb_func); + if (ret) + return ret; + return ret; +} +void __exit lb_modexit(void) +{ + usb_function_unregister(&Loopbackusb_func); +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_sourcesink.c b/drivers/usb/gadget/f_sourcesink.c index f785cbd59688..41adf3ef96c2 100644 --- a/drivers/usb/gadget/f_sourcesink.c +++ b/drivers/usb/gadget/f_sourcesink.c @@ -16,11 +16,12 @@ #include #include #include +#include +#include #include "g_zero.h" #include "gadget_chips.h" - /* * SOURCE/SINK FUNCTION ... a primary testing vehicle for USB peripheral * controller drivers. @@ -62,24 +63,11 @@ static inline struct f_sourcesink *func_to_ss(struct usb_function *f) } static unsigned pattern; -module_param(pattern, uint, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(pattern, "0 = all zeroes, 1 = mod63, 2 = none"); - -static unsigned isoc_interval = 4; -module_param(isoc_interval, uint, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(isoc_interval, "1 - 16"); - -static unsigned isoc_maxpacket = 1024; -module_param(isoc_maxpacket, uint, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(isoc_maxpacket, "0 - 1023 (fs), 0 - 1024 (hs/ss)"); - +static unsigned isoc_interval; +static unsigned isoc_maxpacket; static unsigned isoc_mult; -module_param(isoc_mult, uint, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(isoc_mult, "0 - 2 (hs/ss only)"); - static unsigned isoc_maxburst; -module_param(isoc_maxburst, uint, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(isoc_maxburst, "0 - 15 (ss only)"); +static unsigned buflen; /*-------------------------------------------------------------------------*/ @@ -313,7 +301,57 @@ static struct usb_gadget_strings *sourcesink_strings[] = { /*-------------------------------------------------------------------------*/ -static int __init +struct usb_request *alloc_ep_req(struct usb_ep *ep, int len) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req) { + if (len) + req->length = len; + else + req->length = buflen; + req->buf = kmalloc(req->length, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + return req; +} + +void free_ep_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep) +{ + int value; + + if (ep->driver_data) { + value = usb_ep_disable(ep); + if (value < 0) + DBG(cdev, "disable %s --> %d\n", + ep->name, value); + ep->driver_data = NULL; + } +} + +void disable_endpoints(struct usb_composite_dev *cdev, + struct usb_ep *in, struct usb_ep *out, + struct usb_ep *iso_in, struct usb_ep *iso_out) +{ + disable_ep(cdev, in); + disable_ep(cdev, out); + if (iso_in) + disable_ep(cdev, iso_in); + if (iso_out) + disable_ep(cdev, iso_out); +} + +static int sourcesink_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; @@ -328,14 +366,6 @@ sourcesink_bind(struct usb_configuration *c, struct usb_function *f) source_sink_intf_alt0.bInterfaceNumber = id; source_sink_intf_alt1.bInterfaceNumber = id; - /* allocate string ID(s) */ - id = usb_string_id(cdev); - if (id < 0) - return id; - strings_sourcesink[0].id = id; - source_sink_intf_alt0.iInterface = id; - source_sink_intf_alt1.iInterface = id; - /* allocate bulk endpoints */ ss->in_ep = usb_ep_autoconfig(cdev->gadget, &fs_source_desc); if (!ss->in_ep) { @@ -457,14 +487,11 @@ no_iso: return 0; } -static struct usb_function *global_ss_func; - static void -sourcesink_unbind(struct usb_configuration *c, struct usb_function *f) +sourcesink_free_func(struct usb_function *f) { usb_free_all_descriptors(f); kfree(func_to_ss(f)); - global_ss_func = NULL; } /* optionally require specific source/sink data patterns */ @@ -767,6 +794,7 @@ static void sourcesink_disable(struct usb_function *f) } /*-------------------------------------------------------------------------*/ + static int sourcesink_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { @@ -839,41 +867,76 @@ unknown: return value; } -static int __init sourcesink_bind_config(struct usb_configuration *c) +static struct usb_function *source_sink_alloc_func( + struct usb_function_instance *fi) { - struct f_sourcesink *ss; - int status; + struct f_sourcesink *ss; + struct f_ss_opts *ss_opts; ss = kzalloc(sizeof(*ss), GFP_KERNEL); if (!ss) - return -ENOMEM; + return NULL; - global_ss_func = &ss->function; + ss_opts = container_of(fi, struct f_ss_opts, func_inst); + pattern = ss_opts->pattern; + isoc_interval = ss_opts->isoc_interval; + isoc_maxpacket = ss_opts->isoc_maxpacket; + isoc_mult = ss_opts->isoc_mult; + isoc_maxburst = ss_opts->isoc_maxburst; + buflen = ss_opts->bulk_buflen; ss->function.name = "source/sink"; ss->function.bind = sourcesink_bind; - ss->function.unbind = sourcesink_unbind; ss->function.set_alt = sourcesink_set_alt; ss->function.get_alt = sourcesink_get_alt; ss->function.disable = sourcesink_disable; ss->function.setup = sourcesink_setup; + ss->function.strings = sourcesink_strings; - status = usb_add_function(c, &ss->function); - if (status) - kfree(ss); - return status; + ss->function.free_func = sourcesink_free_func; + + return &ss->function; } -static int ss_config_setup(struct usb_configuration *c, - const struct usb_ctrlrequest *ctrl) +static void acm_free_instance(struct usb_function_instance *fi) { - if (!global_ss_func) - return -EOPNOTSUPP; - switch (ctrl->bRequest) { - case 0x5b: - case 0x5c: - return global_ss_func->setup(global_ss_func, ctrl); - default: - return -EOPNOTSUPP; - } + struct f_ss_opts *ss_opts; + + ss_opts = container_of(fi, struct f_ss_opts, func_inst); + kfree(ss_opts); } + +static struct usb_function_instance *source_sink_alloc_inst(void) +{ + struct f_ss_opts *ss_opts; + + ss_opts = kzalloc(sizeof(*ss_opts), GFP_KERNEL); + if (!ss_opts) + return ERR_PTR(-ENOMEM); + ss_opts->func_inst.free_func_inst = acm_free_instance; + return &ss_opts->func_inst; +} +DECLARE_USB_FUNCTION(SourceSink, source_sink_alloc_inst, + source_sink_alloc_func); + +static int __init sslb_modinit(void) +{ + int ret; + + ret = usb_function_register(&SourceSinkusb_func); + if (ret) + return ret; + ret = lb_modinit(); + if (ret) + usb_function_unregister(&SourceSinkusb_func); + return ret; +} +static void __exit sslb_modexit(void) +{ + usb_function_unregister(&SourceSinkusb_func); + lb_modexit(); +} +module_init(sslb_modinit); +module_exit(sslb_modexit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/g_zero.h b/drivers/usb/gadget/g_zero.h index 281239c4e544..ef3e8515272b 100644 --- a/drivers/usb/gadget/g_zero.h +++ b/drivers/usb/gadget/g_zero.h @@ -6,11 +6,34 @@ #ifndef __G_ZERO_H #define __G_ZERO_H -#include +struct usb_zero_options { + unsigned pattern; + unsigned isoc_interval; + unsigned isoc_maxpacket; + unsigned isoc_mult; + unsigned isoc_maxburst; + unsigned bulk_buflen; + unsigned qlen; +}; -/* global state */ -extern unsigned buflen; -extern const struct usb_descriptor_header *otg_desc[]; +struct f_ss_opts { + struct usb_function_instance func_inst; + unsigned pattern; + unsigned isoc_interval; + unsigned isoc_maxpacket; + unsigned isoc_mult; + unsigned isoc_maxburst; + unsigned bulk_buflen; +}; + +struct f_lb_opts { + struct usb_function_instance func_inst; + unsigned bulk_buflen; + unsigned qlen; +}; + +void lb_modexit(void); +int lb_modinit(void); /* common utilities */ struct usb_request *alloc_ep_req(struct usb_ep *ep, int len); diff --git a/drivers/usb/gadget/zero.c b/drivers/usb/gadget/zero.c index 8ba0bee4e6c0..a331ab13f1e5 100644 --- a/drivers/usb/gadget/zero.c +++ b/drivers/usb/gadget/zero.c @@ -10,7 +10,6 @@ * (at your option) any later version. */ - /* * Gadget Zero only needs two bulk endpoints, and is an example of how you * can write a hardware-agnostic gadget driver running inside a USB device. @@ -43,23 +42,11 @@ #include #include #include +#include +#include +#include #include "g_zero.h" -#include "gadget_chips.h" - - -/*-------------------------------------------------------------------------*/ - -/* - * Kbuild is not very cooperative with respect to linking separately - * compiled library objects into one module. So for now we won't use - * separate compilation ... ensuring init/exit sections work to shrink - * the runtime footprint, and giving us at least some parts of what - * a "gcc --combine ... part1.c part2.c part3.c ... " build would. - */ -#include "f_sourcesink.c" -#include "f_loopback.c" - /*-------------------------------------------------------------------------*/ USB_GADGET_COMPOSITE_OPTIONS(); @@ -67,9 +54,6 @@ USB_GADGET_COMPOSITE_OPTIONS(); static const char longname[] = "Gadget Zero"; -unsigned buflen = 4096; /* only used for bulk endpoints */ -module_param(buflen, uint, 0); - /* * Normally the "loopback" configuration is second (index 1) so * it's not the default. Here's where to change that order, to @@ -79,6 +63,13 @@ module_param(buflen, uint, 0); static bool loopdefault = 0; module_param(loopdefault, bool, S_IRUGO|S_IWUSR); +struct usb_zero_options gzero_options = { + .isoc_interval = 4, + .isoc_maxpacket = 1024, + .bulk_buflen = 4096, + .qlen = 32, +}; + /*-------------------------------------------------------------------------*/ /* Thanks to NetChip Technologies for donating this product ID. @@ -129,10 +120,12 @@ static struct usb_otg_descriptor otg_descriptor = { .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, }; -const struct usb_descriptor_header *otg_desc[] = { +static const struct usb_descriptor_header *otg_desc[] = { (struct usb_descriptor_header *) &otg_descriptor, NULL, }; +#else +#define otg_desc NULL #endif /* string IDs are assigned dynamically */ @@ -163,58 +156,6 @@ static struct usb_gadget_strings *dev_strings[] = { /*-------------------------------------------------------------------------*/ -struct usb_request *alloc_ep_req(struct usb_ep *ep, int len) -{ - struct usb_request *req; - - req = usb_ep_alloc_request(ep, GFP_ATOMIC); - if (req) { - if (len) - req->length = len; - else - req->length = buflen; - req->buf = kmalloc(req->length, GFP_ATOMIC); - if (!req->buf) { - usb_ep_free_request(ep, req); - req = NULL; - } - } - return req; -} - -void free_ep_req(struct usb_ep *ep, struct usb_request *req) -{ - kfree(req->buf); - usb_ep_free_request(ep, req); -} - -static void disable_ep(struct usb_composite_dev *cdev, struct usb_ep *ep) -{ - int value; - - if (ep->driver_data) { - value = usb_ep_disable(ep); - if (value < 0) - DBG(cdev, "disable %s --> %d\n", - ep->name, value); - ep->driver_data = NULL; - } -} - -void disable_endpoints(struct usb_composite_dev *cdev, - struct usb_ep *in, struct usb_ep *out, - struct usb_ep *iso_in, struct usb_ep *iso_out) -{ - disable_ep(cdev, in); - disable_ep(cdev, out); - if (iso_in) - disable_ep(cdev, iso_in); - if (iso_out) - disable_ep(cdev, iso_out); -} - -/*-------------------------------------------------------------------------*/ - static struct timer_list autoresume_timer; static void zero_autoresume(unsigned long _c) @@ -258,23 +199,63 @@ static void zero_resume(struct usb_composite_dev *cdev) static struct usb_configuration loopback_driver = { .label = "loopback", - .strings = loopback_strings, .bConfigurationValue = 2, .bmAttributes = USB_CONFIG_ATT_SELFPOWER, /* .iConfiguration = DYNAMIC */ }; +static struct usb_function *func_ss; +static struct usb_function_instance *func_inst_ss; + +static int ss_config_setup(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + switch (ctrl->bRequest) { + case 0x5b: + case 0x5c: + return func_ss->setup(func_ss, ctrl); + default: + return -EOPNOTSUPP; + } +} + static struct usb_configuration sourcesink_driver = { .label = "source/sink", - .strings = sourcesink_strings, .setup = ss_config_setup, .bConfigurationValue = 3, .bmAttributes = USB_CONFIG_ATT_SELFPOWER, /* .iConfiguration = DYNAMIC */ }; +module_param_named(buflen, gzero_options.bulk_buflen, uint, 0); +module_param_named(pattern, gzero_options.pattern, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(pattern, "0 = all zeroes, 1 = mod63, 2 = none"); + +module_param_named(isoc_interval, gzero_options.isoc_interval, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_interval, "1 - 16"); + +module_param_named(isoc_maxpacket, gzero_options.isoc_maxpacket, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_maxpacket, "0 - 1023 (fs), 0 - 1024 (hs/ss)"); + +module_param_named(isoc_mult, gzero_options.isoc_mult, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_mult, "0 - 2 (hs/ss only)"); + +module_param_named(isoc_maxburst, gzero_options.isoc_maxburst, uint, + S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(isoc_maxburst, "0 - 15 (ss only)"); + +static struct usb_function *func_lb; +static struct usb_function_instance *func_inst_lb; + +module_param_named(qlen, gzero_options.qlen, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(qlen, "depth of loopback queue"); + static int __init zero_bind(struct usb_composite_dev *cdev) { + struct f_ss_opts *ss_opts; + struct f_lb_opts *lb_opts; int status; /* Allocate string descriptor numbers ... note that string @@ -290,6 +271,36 @@ static int __init zero_bind(struct usb_composite_dev *cdev) setup_timer(&autoresume_timer, zero_autoresume, (unsigned long) cdev); + func_inst_ss = usb_get_function_instance("SourceSink"); + if (IS_ERR(func_inst_ss)) + return PTR_ERR(func_inst_ss); + + ss_opts = container_of(func_inst_ss, struct f_ss_opts, func_inst); + ss_opts->pattern = gzero_options.pattern; + ss_opts->isoc_interval = gzero_options.isoc_interval; + ss_opts->isoc_maxpacket = gzero_options.isoc_maxpacket; + ss_opts->isoc_mult = gzero_options.isoc_mult; + ss_opts->isoc_maxburst = gzero_options.isoc_maxpacket; + ss_opts->bulk_buflen = gzero_options.bulk_buflen; + + func_ss = usb_get_function(func_inst_ss); + if (IS_ERR(func_ss)) + goto err_put_func_inst_ss; + + func_inst_lb = usb_get_function_instance("Loopback"); + if (IS_ERR(func_inst_lb)) + goto err_put_func_ss; + + lb_opts = container_of(func_inst_lb, struct f_lb_opts, func_inst); + lb_opts->bulk_buflen = gzero_options.bulk_buflen; + lb_opts->qlen = gzero_options.qlen; + + func_lb = usb_get_function(func_inst_lb); + if (IS_ERR(func_lb)) { + status = PTR_ERR(func_lb); + goto err_put_func_inst_lb; + } + sourcesink_driver.iConfiguration = strings_dev[USB_GZERO_SS_DESC].id; loopback_driver.iConfiguration = strings_dev[USB_GZERO_LB_DESC].id; @@ -315,25 +326,50 @@ static int __init zero_bind(struct usb_composite_dev *cdev) * SH3 only allows one config... */ if (loopdefault) { - usb_add_config(cdev, &loopback_driver, loopback_bind_config); - usb_add_config(cdev, &sourcesink_driver, - sourcesink_bind_config); + usb_add_config_only(cdev, &loopback_driver); + usb_add_config_only(cdev, &sourcesink_driver); } else { - usb_add_config(cdev, &sourcesink_driver, - sourcesink_bind_config); - usb_add_config(cdev, &loopback_driver, loopback_bind_config); + usb_add_config_only(cdev, &sourcesink_driver); + usb_add_config_only(cdev, &loopback_driver); } + status = usb_add_function(&sourcesink_driver, func_ss); + if (status) + goto err_conf_flb; + usb_ep_autoconfig_reset(cdev->gadget); + status = usb_add_function(&loopback_driver, func_lb); + if (status) + goto err_conf_flb; + + usb_ep_autoconfig_reset(cdev->gadget); usb_composite_overwrite_options(cdev, &coverwrite); INFO(cdev, "%s, version: " DRIVER_VERSION "\n", longname); return 0; + +err_conf_flb: + usb_put_function(func_lb); + func_lb = NULL; +err_put_func_inst_lb: + usb_put_function_instance(func_inst_lb); + func_inst_lb = NULL; +err_put_func_ss: + usb_put_function(func_ss); + func_ss = NULL; +err_put_func_inst_ss: + usb_put_function_instance(func_inst_ss); + func_inst_ss = NULL; + return status; } static int zero_unbind(struct usb_composite_dev *cdev) { del_timer_sync(&autoresume_timer); + if (!IS_ERR_OR_NULL(func_ss)) + usb_put_function(func_ss); + if (!IS_ERR_OR_NULL(func_lb)) + usb_put_function(func_lb); return 0; }