diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2020-09-25 08:06:22 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2020-09-25 08:06:22 +0200 |
commit | faddac631a0e521780854ecf9a53f96481a32716 (patch) | |
tree | 292c866315fd161df41d90f4bbde1572b14415d1 | |
parent | 1ab2e649dc0ccef7ee990b999e1d8f8c32e7cd24 (diff) | |
parent | 0d4d157cf0d42206491b7a1b417c9904a9abd553 (diff) | |
download | barebox-faddac631a0e521780854ecf9a53f96481a32716.tar.gz barebox-faddac631a0e521780854ecf9a53f96481a32716.tar.xz |
Merge branch 'for-next/net' into master
37 files changed, 1797 insertions, 92 deletions
diff --git a/Documentation/devel/background-execution.rst b/Documentation/devel/background-execution.rst new file mode 100644 index 0000000000..eadad9d898 --- /dev/null +++ b/Documentation/devel/background-execution.rst @@ -0,0 +1,125 @@ +Background execution in barebox +=============================== + +barebox is single-threaded and doesn't support interrupts. Nevertheless it is +sometimes desired to execute code "in the background", like for example polling +for completion of transfers or to regularly blink a heartbeat LED. For these +scenarios barebox offers the techniques described below. + +Pollers +------- + +Pollers are a way in barebox to frequently execute code in the background. +barebox is single-threaded, so a poller is not executed as a separate thread, +but instead pollers are executed whenever ``is_timeout()`` is called. This has +a few implications. First of all, pollers are not executed when +``is_timeout()`` is not called. For this and other reasons, loops polling for +hardware events should always use a timeout, which is best implemented with +``is_timeout()``. Another thing to remember is that pollers can be executed +anywhere in the middle of other device accesses whenever ``is_timeout()`` is +called. Care must be taken that a poller doesn't access the very same device +again itself. See "slices" below on how devices can safely be accessed from +pollers. Some operations are entirely forbidden from pollers. For example it is +forbidden to access the virtual filesystem, as this could trigger arbitrary +device accesses. The macro ``assert_command_context()`` is added to those +places to make sure they are not called in the wrong context. The poller +interface is declared in ``include/poller.h``. ``poller_register()`` is used +to register a poller. The ``struct poller_struct`` passed to +``poller_register()`` needs to have the ``poller_struct.func()`` member +populated with the function that shall be called. From the moment a poller is +registered ``poller_struct.func()`` will be called regularly as long as someone +calls ``is_timeout()``. A special poller can be registered with +``poller_async_register()``. A poller registered this way won't be called right +away, instead running it can be triggered by calling ``poller_call_async()``. +This will execute the poller after the ``@delay_ns`` argument. +``poller_call_async()`` may also be called from with the poller, so with this +it's possible to run a poller regularly with configurable delays. + +Pollers are limited in the things they can do. Poller code must always be +prepared for the case that the resources it accesses are currently busy and +handle this gracefully by trying again later. Most places in barebox either do +not test for resources being busy or return with an error if they are busy. +Calling into the filesystem potentially uses arbitrary other devices, so +doing this from pollers is forbidden. For the same reason it is forbidden +to execute barebox commands from pollers. + +Workqueues +---------- + +Sometimes code wants to access files from poller context or even wants to +execute barebox commands from poller context. The fastboot implementation is an +example for such a case. The fastboot commands come in via USB or network and +the completion and packet receive handlers are executed in poller context. Here +workqueues come into play. They allow to queue work items from poller context. +These work items are executed later at the point where the shell fetches its +next command. At this point we implicitly know that it's allowed to execute +commands or to access the filesystem, because otherwise the shell could not do +it. This means that execution of the next shell command will be delayed until +all work items are being done. Likewise the work items will only be executed +when the current shell command has finished. + +The workqueue interface is declared in ``include/work.h``. + +``wq_register()`` is the first step to use the workqueue interface. +``wq_register()`` registers a workqueue to which work items can be attached. +Before registering a workqueue a ``struct work_queue`` has to be populated with +two function callbacks. ``work_queue.fn()`` is the callback that actually does +the work once it is scheduled. ``work_queue.cancel()`` is a callback which is +executed when the pending work shall be cancelled. This is primarily for +freeing the memory associated to a work item. ``wq_queue_work()`` is for +actually queueing a work item on a work queue. This can be called from poller +code. Usually a work item is allocated by the poller and then freed either in +``work_queue.fn()`` or in ``work_queue.cancel()``. + +Slices +------ + +Slices are a way to check if a device is currently busy and thus may not be +called into currently. Pollers wanting to access a device must call +``slice_busy()`` on the slice provided by the device before calling into it. +When the slice is acquired (which can only happen inside a poller) the poller +can't continue at this moment and must try again next time it is executed. +Drivers whose devices provide a slice must call ``slice_acquire()`` before +accessing the device and ``slice_release()`` afterwards. Slices can have +dependencies to other slices, for example a USB network controller uses the +corresponding USB host controller. A dependency can be expressed with +``slice_depends_on()``. With this the USB network controller can add a +dependency from the network device it provides itself to the USB host +controller it depends on. With this ``slice_busy()`` on the network device +will return ``true`` when the USB host controller is busy. + +The usual pattern for using slices is that the device driver for a device +calls ``slice_acquire()`` when entering the driver and ``slice_release()`` +before leaving the driver. The driver also provides a function returning +the slice for a device, for example the ethernet support code provides +``struct slice *eth_device_slice(struct eth_device *edev)``. Poller code +which wants to use the ethernet device checks for the availability doing +``slice_busy(eth_device_slice(edev))`` before accessing the ethernet +device. When the slice is not busy the poller can continue with accessing +that device. Otherwise the poller must return and try again next time it +is called. + +Limitations +----------- + +What barebox does is best described as cooperative multitasking. As pollers +can't be interrupted, it will directly affect the user experience when they +take too long. When barebox reacts sluggishly to key presses, then probably +pollers take too long to execute. A first test if this is the case can +be done by executing ``poller -t`` on the command line. This command will print +how many times we can execute all registered pollers in one second. When this +number is too low then pollers are guilty responsible. Workqueues help to run +schedule/execute longer running code, but during the time while workqueues are +executed nothing else happens. This means that when fastboot flashes an image +in a workqueue then barebox won't react to any key presses on the command line. +The usage of the interfaces described in this document is not yet very +widespread in barebox. The interfaces are used in the places where we need +them, but there are other places which do not use them but should. For example +using a LED driven by a I2C GPIO exander used as hearbeat LED won't work +properly currently. Consider the I2C driver accesses an unrelated I2C device, +like an EEPROM. After having initiated the transfer the driver polls for the +transfer being completed using a ``is_timeout()`` loop. The call to +``is_timeout()`` then calls into the registered pollers from which one accesses +the same I2C bus whose driver is just polling for completion of another +transfer. With this the I2C driver is in an undefined state and will likely not +work anymore. diff --git a/Documentation/devel/devel.rst b/Documentation/devel/devel.rst new file mode 100644 index 0000000000..f559512ca3 --- /dev/null +++ b/Documentation/devel/devel.rst @@ -0,0 +1,14 @@ +.. highlight:: console + +barebox programming +=================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + background-execution + +* :ref:`search` +* :ref:`genindex` diff --git a/Documentation/index.rst b/Documentation/index.rst index b7dae2396b..836dc41af2 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -19,6 +19,7 @@ Contents: boards glossary devicetree/* + devel/devel.rst * :ref:`search` * :ref:`genindex` diff --git a/Documentation/user/usb.rst b/Documentation/user/usb.rst index 4c1b2925f2..ca5f8138de 100644 --- a/Documentation/user/usb.rst +++ b/Documentation/user/usb.rst @@ -266,7 +266,7 @@ USB Gadget autostart Options See :ref:`command_usbgadget` -a. (Default 0). ``global.usbgadget.dfu_function`` Function description for DFU. See :ref:`command_usbgadget` -D [desc]. -``global.usbgadget.fastboot_function`` +``global.fastboot.partitions`` Function description for fastboot. See :ref:`command_usbgadget` -A [desc]. -``global.usbgadget.fastboot_bbu`` +``global.fastboot.bbu`` Export barebox update handlers. See :ref:`command_usbgadget` -b. (Default 0). diff --git a/commands/Kconfig b/commands/Kconfig index 1399f04d8b..9114d3cb31 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -253,6 +253,14 @@ config CMD_POLLER is_timeout() or one of the various delay functions. The poller command prints informations about registered pollers. +config CMD_SLICE + tristate + prompt "slice" + help + slices are a way to protect resources from being accessed by pollers. The slice + command can be used to print informations about slices and also to manipulate + them on the command line for debugging purposes. + # end Information commands endmenu diff --git a/commands/usbgadget.c b/commands/usbgadget.c index 193da86dee..9133402f52 100644 --- a/commands/usbgadget.c +++ b/commands/usbgadget.c @@ -57,7 +57,7 @@ BAREBOX_CMD_HELP_TEXT("") BAREBOX_CMD_HELP_TEXT("Options:") BAREBOX_CMD_HELP_OPT ("-a\t", "Create CDC ACM function") BAREBOX_CMD_HELP_OPT ("-A <desc>", "Create Android Fastboot function. If 'desc' is not provided, " - "try to use 'global.usbgadget.fastboot_function' variable.") + "try to use 'global.fastboot.partitions' variable.") BAREBOX_CMD_HELP_OPT ("-b\t", "include registered barebox update handlers (fastboot specific)") BAREBOX_CMD_HELP_OPT ("-D <desc>", "Create DFU function. If 'desc' is not provided, " "try to use 'global.usbgadget.dfu_function' variable.") diff --git a/common/Makefile b/common/Makefile index faf0415ef3..c3ae3ca1b9 100644 --- a/common/Makefile +++ b/common/Makefile @@ -11,6 +11,8 @@ obj-y += bootsource.o obj-$(CONFIG_ELF) += elf.o obj-y += restart.o obj-y += poweroff.o +obj-y += slice.o +obj-y += workqueue.o obj-$(CONFIG_MACHINE_ID) += machine_id.o obj-$(CONFIG_AUTO_COMPLETE) += complete.o obj-y += version.o diff --git a/common/fastboot.c b/common/fastboot.c index 86e7997a0b..bcfadfad3d 100644 --- a/common/fastboot.c +++ b/common/fastboot.c @@ -46,6 +46,8 @@ #define FASTBOOT_VERSION "0.4" static unsigned int fastboot_max_download_size = SZ_8M; +static int fastboot_bbu; +static char *fastboot_partitions; struct fb_variable { char *name; @@ -169,6 +171,8 @@ int fastboot_generic_init(struct fastboot *fb, bool export_bbu) struct file_list_entry *fentry; struct fb_variable *var; + INIT_LIST_HEAD(&fb->variables); + var = fb_addvar(fb, "version"); fb_setvar(var, "0.4"); var = fb_addvar(fb, "bootloader-version"); @@ -241,6 +245,7 @@ static char *fastboot_msg[] = { [FASTBOOT_MSG_FAIL] = "FAIL", [FASTBOOT_MSG_INFO] = "INFO", [FASTBOOT_MSG_DATA] = "DATA", + [FASTBOOT_MSG_NONE] = "", }; int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type, @@ -269,6 +274,7 @@ int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type, case FASTBOOT_MSG_INFO: pr_info("%pV\n", &vaf); break; + case FASTBOOT_MSG_NONE: case FASTBOOT_MSG_DATA: break; } @@ -283,6 +289,7 @@ int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type, static void cb_reboot(struct fastboot *fb, const char *cmd) { + fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, ""); restart_machine(); } @@ -335,6 +342,7 @@ int fastboot_handle_download_data(struct fastboot *fb, const void *buffer, void fastboot_download_finished(struct fastboot *fb) { close(fb->download_fd); + fb->download_fd = 0; printf("\n"); @@ -344,6 +352,18 @@ void fastboot_download_finished(struct fastboot *fb) fastboot_tx_print(fb, FASTBOOT_MSG_OKAY, ""); } +void fastboot_abort(struct fastboot *fb) +{ + if (fb->download_fd > 0) { + close(fb->download_fd); + fb->download_fd = 0; + } + + fb->active = false; + + unlink(fb->tempname); +} + static void cb_download(struct fastboot *fb, const char *cmd) { fb->download_size = simple_strtoul(cmd, NULL, 16); @@ -354,6 +374,11 @@ static void cb_download(struct fastboot *fb, const char *cmd) init_progression_bar(fb->download_size); + if (fb->download_fd > 0) { + pr_err("%s called and %s is still opened\n", __func__, fb->tempname); + close(fb->download_fd); + } + fb->download_fd = open(fb->tempname, O_WRONLY | O_CREAT | O_TRUNC); if (fb->download_fd < 0) { fastboot_tx_print(fb, FASTBOOT_MSG_FAIL, "internal error"); @@ -903,17 +928,43 @@ void fastboot_exec_cmd(struct fastboot *fb, const char *cmdbuf) ARRAY_SIZE(cmd_dispatch_info)); } +bool get_fastboot_bbu(void) +{ + return fastboot_bbu; +} + +const char *get_fastboot_partitions(void) +{ + return fastboot_partitions; +} + static int fastboot_globalvars_init(void) { if (IS_ENABLED(CONFIG_FASTBOOT_SPARSE)) - globalvar_add_simple_int("usbgadget.fastboot_max_download_size", + globalvar_add_simple_int("fastboot.max_download_size", &fastboot_max_download_size, "%u"); + globalvar_add_simple_bool("fastboot.bbu", &fastboot_bbu); + globalvar_add_simple_string("fastboot.partitions", + &fastboot_partitions); + + globalvar_alias_deprecated("usbgadget.fastboot_function", + "fastboot.partitions"); + globalvar_alias_deprecated("usbgadget.fastboot_bbu", + "fastboot.bbu"); + globalvar_alias_deprecated("usbgadget.fastboot_max_download_size", + "fastboot.max_download_size"); return 0; } device_initcall(fastboot_globalvars_init); -BAREBOX_MAGICVAR_NAMED(global_usbgadget_fastboot_max_download_size, - global.usbgadget.fastboot_max_download_size, +BAREBOX_MAGICVAR_NAMED(global_fastboot_max_download_size, + global.fastboot.max_download_size, "Fastboot maximum download size"); +BAREBOX_MAGICVAR_NAMED(global_fastboot_partitions, + global.fastboot.partitions, + "Partitions exported for update via fastboot"); +BAREBOX_MAGICVAR_NAMED(global_fastboot_bbu, + global.fastboot.bbu, + "Export barebox update handlers via fastboot"); diff --git a/common/globalvar.c b/common/globalvar.c index eefee73e7a..5bde86aad0 100644 --- a/common/globalvar.c +++ b/common/globalvar.c @@ -293,6 +293,53 @@ int nvvar_remove(const char *name) return ret; } +struct globalvar_deprecated { + char *newname; + char *oldname; + struct list_head list; +}; + +static LIST_HEAD(globalvar_deprecated_list); + +/* + * globalvar_alias_deprecated - add an alias + * + * @oldname: The old name for the variable + * @newname: The new name for the variable + * + * This function is a helper for globalvars that are renamed from one + * release to another. when a variable @oldname is found in the persistent + * environment a warning is issued and its value is written to @newname. + * + * Note that when both @oldname and @newname contain values then the values + * existing in @newname are overwritten. + */ +void globalvar_alias_deprecated(const char *oldname, const char *newname) +{ + struct globalvar_deprecated *gd; + + gd = xzalloc(sizeof(*gd)); + gd->newname = xstrdup(newname); + gd->oldname = xstrdup(oldname); + list_add_tail(&gd->list, &globalvar_deprecated_list); +} + +static const char *globalvar_new_name(const char *oldname) +{ + struct globalvar_deprecated *gd; + + list_for_each_entry(gd, &globalvar_deprecated_list, list) { + if (!strcmp(oldname, gd->oldname)) { + pr_warn("nv.%s is deprecated, converting to nv.%s\n", oldname, + gd->newname); + nv_dirty = 1; + return gd->newname; + } + } + + return oldname; +} + int nvvar_load(void) { char *val; @@ -308,6 +355,8 @@ int nvvar_load(void) return -ENOENT; while ((d = readdir(dir))) { + const char *n; + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; @@ -316,10 +365,11 @@ int nvvar_load(void) pr_debug("%s: Setting \"%s\" to \"%s\"\n", __func__, d->d_name, val); - ret = __nvvar_add(d->d_name, val); + n = globalvar_new_name(d->d_name); + ret = __nvvar_add(n, val); if (ret) pr_err("failed to create nv variable %s: %s\n", - d->d_name, strerror(-ret)); + n, strerror(-ret)); } closedir(dir); diff --git a/common/hush.c b/common/hush.c index c24b2c7cd2..ec0d5129b7 100644 --- a/common/hush.c +++ b/common/hush.c @@ -121,6 +121,7 @@ #include <libbb.h> #include <password.h> #include <glob.h> +#include <slice.h> #include <getopt.h> #include <libfile.h> #include <libbb.h> @@ -460,7 +461,12 @@ static void get_user_input(struct in_str *i) else prompt = CONFIG_PROMPT_HUSH_PS2; + command_slice_release(); + n = readline(prompt, console_buffer, CONFIG_CBSIZE); + + command_slice_acquire(); + if (n == -1 ) { i->interrupt = 1; n = 0; diff --git a/common/poller.c b/common/poller.c index 95f828b439..50c518697b 100644 --- a/common/poller.c +++ b/common/poller.c @@ -12,9 +12,11 @@ #include <param.h> #include <poller.h> #include <clock.h> +#include <work.h> +#include <slice.h> static LIST_HEAD(poller_list); -static int poller_active; +int poller_active; int poller_register(struct poller_struct *poller, const char *name) { @@ -110,15 +112,22 @@ int poller_async_unregister(struct poller_async *pa) void poller_call(void) { struct poller_struct *poller, *tmp; + bool run_workqueues = !slice_acquired(&command_slice); if (poller_active) return; + command_slice_acquire(); + + if (run_workqueues) + wq_do_all_works(); + poller_active = 1; list_for_each_entry_safe(poller, tmp, &poller_list, list) poller->func(poller); + command_slice_release(); poller_active = 0; } diff --git a/common/ratp/ratp.c b/common/ratp/ratp.c index 9ca299eef2..424c9406d2 100644 --- a/common/ratp/ratp.c +++ b/common/ratp/ratp.c @@ -22,6 +22,7 @@ #include <environment.h> #include <kfifo.h> #include <poller.h> +#include <work.h> #include <linux/sizes.h> #include <ratp_bb.h> #include <fs.h> @@ -49,9 +50,11 @@ struct ratp_ctx { struct ratp_bb_pkt *fs_rx; struct poller_struct poller; + struct work_queue wq; bool console_registered; bool poller_registered; + bool wq_registered; }; static int compare_ratp_command(struct list_head *a, struct list_head *b) @@ -193,9 +196,14 @@ static int ratp_bb_send_command_return(struct ratp_ctx *ctx, uint32_t errno) return ret; } -static char *ratp_command; static struct ratp_ctx *ratp_ctx; +struct ratp_work { + struct work_struct work; + struct ratp_ctx *ctx; + char *command; +}; + static int ratp_bb_dispatch(struct ratp_ctx *ctx, const void *buf, int len) { const struct ratp_bb *rbb = buf; @@ -204,6 +212,7 @@ static int ratp_bb_dispatch(struct ratp_ctx *ctx, const void *buf, int len) int ret = 0; uint16_t type = be16_to_cpu(rbb->type); struct ratp_command *cmd; + struct ratp_work *rw; /* See if there's a command registered to this type */ cmd = find_ratp_request(type); @@ -221,12 +230,16 @@ static int ratp_bb_dispatch(struct ratp_ctx *ctx, const void *buf, int len) switch (type) { case BB_RATP_TYPE_COMMAND: - if (!IS_ENABLED(CONFIG_CONSOLE_RATP) || ratp_command) + if (!IS_ENABLED(CONFIG_CONSOLE_RATP)) return 0; - ratp_command = xstrndup((const char *)rbb->data, dlen); - ratp_ctx = ctx; - pr_debug("got command: %s\n", ratp_command); + rw = xzalloc(sizeof(*rw)); + rw->ctx = ctx; + rw->command = xstrndup((const char *)rbb->data, dlen); + + wq_queue_work(&ctx->wq, &rw->work); + + pr_debug("got command: %s\n", rw->command); break; @@ -297,21 +310,20 @@ static void ratp_console_putc(struct console_device *cdev, char c) kfifo_putc(ctx->console_transmit_fifo, c); } -void barebox_ratp_command_run(void) +static void ratp_command_run(struct work_struct *w) { + struct ratp_work *rw = container_of(w, struct ratp_work, work); + struct ratp_ctx *ctx = rw->ctx; int ret; - if (!ratp_command) - return; - - pr_debug("running command: %s\n", ratp_command); + pr_debug("running command: %s\n", rw->command); - ret = run_command(ratp_command); + ret = run_command(rw->command); - free(ratp_command); - ratp_command = NULL; + free(rw->command); + free(rw); - ratp_bb_send_command_return(ratp_ctx, ret); + ratp_bb_send_command_return(ctx, ret); } static const char *ratpfs_mount_path; @@ -336,6 +348,9 @@ static void ratp_unregister(struct ratp_ctx *ctx) if (ctx->poller_registered) poller_unregister(&ctx->poller); + if (ctx->wq_registered) + wq_unregister(&ctx->wq); + ratp_close(&ctx->ratp); console_set_active(ctx->cdev, ctx->old_active); @@ -368,6 +383,7 @@ static void ratp_poller(struct poller_struct *poller) ret = ratp_poll(&ctx->ratp); if (ret == -EINTR) goto out; + if (ratp_closed(&ctx->ratp)) goto out; @@ -423,6 +439,14 @@ int barebox_ratp_fs_call(struct ratp_bb_pkt *tx, struct ratp_bb_pkt **rx) return 0; } +static void ratp_work_cancel(struct work_struct *w) +{ + struct ratp_work *rw = container_of(w, struct ratp_work, work); + + free(rw->command); + free(rw); +} + int barebox_ratp(struct console_device *cdev) { int ret; @@ -466,6 +490,11 @@ int barebox_ratp(struct console_device *cdev) if (ret < 0) goto out; + ctx->wq.fn = ratp_command_run; + ctx->wq.cancel = ratp_work_cancel; + wq_register(&ctx->wq); + ctx->wq_registered = true; + ret = poller_register(&ctx->poller, "ratp"); if (ret) goto out; diff --git a/common/slice.c b/common/slice.c new file mode 100644 index 0000000000..9d7e0d16cf --- /dev/null +++ b/common/slice.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define pr_fmt(fmt) "slice: " fmt + +#include <common.h> +#include <init.h> +#include <slice.h> + +/* + * slices, the barebox idea of locking + * + * barebox has pollers which execute code in the background whenever one of the + * delay functions (udelay, mdelay, ...) or is_timeout() are called. This + * introduces resource problems when some device triggers a poller by calling + * a delay function and then the poller code calls into the same device again. + * + * As an example consider a I2C GPIO expander which drives a LED which shall + * be used as a heartbeat LED: + * + * poller -> LED on/off -> GPIO high/low -> I2C transfer + * + * The I2C controller has a timeout loop using is_timeout() and thus can trigger + * a poller run. With this the following can happen during an unrelated I2C + * transfer: + * + * I2C transfer -> is_timeout() -> poller -> LED on/off -> GPIO high/low -> I2C transfer + * + * We end up with issuing an I2C transfer during another I2C transfer and + * things go downhill. + * + * Due to the lack of interrupts we can't do real locking in barebox. We use + * a mechanism called slices instead. A slice describes a resource to which + * other slices can be attached. Whenever a slice is needed it must be acquired. + * Acquiring a slice never fails, it just increases the acquired counter of + * the slice and its dependent slices. when a slice shall be used inside a + * poller it must first be tested if the slice is already in use. If it is, + * we can't do the operation on the slice now and must return and hope that + * we have more luck in the next poller call. + * + * slices can be attached other slices as dependencies. In the example above + * LED driver would add a dependency to the GPIO controller and the GPIO driver + * would add a dependency to the I2C bus: + * + * GPIO driver probe: + * + * slice_depends_on(&gpio->slice, i2c_device_slice(i2cdev)); + * + * LED driver probe: + * + * slice_depends_on(&led->slice, gpio_slice(gpio)); + * + * The GPIO code would call slice_acquire(&gpio->slice) before doing any + * operation on the GPIO chip providing this GPIO, likewise the I2C core + * would call slice_acquire(&i2cbus->slice) before doing an operation on + * this I2C bus. + * + * The heartbeat poller code would call slice_acquired(led_slice(led)) and + * only continue when the slice is not acquired. + */ + +/* + * slices are not required to have a device and thus a name, but it really + * helps debugging when it has one. + */ +static const char *slice_name(struct slice *slice) +{ + return slice->name; +} + +static void __slice_acquire(struct slice *slice, int add) +{ + slice->acquired += add; + + if (slice->acquired < 0) { + pr_err("Slice %s acquired count drops below zero\n", + slice_name(slice)); + dump_stack(); + slice->acquired = 0; + return; + } +} + +/** + * slice_acquire: acquire a slice + * @slice: The slice to acquire + * + * This acquires a slice and its dependencies + */ +void slice_acquire(struct slice *slice) +{ + __slice_acquire(slice, 1); +} + +/** + * slice_release: release a slice + * @slice: The slice to release + * + * This releases a slice and its dependencies + */ +void slice_release(struct slice *slice) +{ + __slice_acquire(slice, -1); +} + +/** + * slice_acquired: test if a slice is acquired + * @slice: The slice to test + * + * This tests if a slice is acquired. Returns true if it is, false otherwise + */ +bool slice_acquired(struct slice *slice) +{ + struct slice_entry *se; + bool ret = false; + + if (slice->acquired > 0) + return true; + + if (slice->acquired < 0) { + pr_err("Recursive dependency detected in slice %s\n", + slice_name(slice)); + panic("Cannot continue"); + } + + slice->acquired = -1; + + list_for_each_entry(se, &slice->deps, list) + if (slice_acquired(se->slice)) { + ret = true; + break; + } + + slice->acquired = 0; + + return ret; +} + +static void __slice_debug_acquired(struct slice *slice, int level) +{ + struct slice_entry *se; + + pr_debug("%*s%s: %d\n", level * 4, "", + slice_name(slice), + slice->acquired); + + list_for_each_entry(se, &slice->deps, list) + __slice_debug_acquired(se->slice, level + 1); +} + +/** + * slice_debug_acquired: print the acquired state of a slice + * + * @slice: The slice to print + * + * This prints the acquired state of a slice and its dependencies. + */ +void slice_debug_acquired(struct slice *slice) +{ + if (!slice_acquired(slice)) + return; + + __slice_debug_acquired(slice, 0); +} + +/** + * slice_depends_on: Add a dependency to a slice + * + * @slice: The slice to add the dependency to + * @dep: The slice @slice depends on + * + * This makes @slice dependent on @dep. In other words, acquiring @slice + * will lead to also acquiring @dep. + */ +void slice_depends_on(struct slice *slice, struct slice *dep) +{ + struct slice_entry *se = xzalloc(sizeof(*se)); + + pr_debug("Adding dependency %s (%d) to slice %s (%d)\n", + slice_name(dep), dep->acquired, + slice_name(slice), slice->acquired); + + se->slice = dep; + + list_add_tail(&se->list, &slice->deps); +} + +static LIST_HEAD(slices); + +/** + * slice_init - initialize a slice before usage + * @slice: The slice to initialize + * @name: The name of the slice + * + * This initializes a slice before usage. @name should be a meaningful name, when + * a device context is available to the caller it is recommended to pass its + * dev_name() as name argument. + */ +void slice_init(struct slice *slice, const char *name) +{ + INIT_LIST_HEAD(&slice->deps); + slice->name = xstrdup(name); + list_add_tail(&slice->list, &slices); +} + +/** + * slice_exit: Remove a slice + * @slice: The slice to remove the dependency from + */ +void slice_exit(struct slice *slice) +{ + struct slice *s; + struct slice_entry *se, *tmp; + + list_del(&slice->list); + + /* remove our dependencies */ + list_for_each_entry_safe(se, tmp, &slice->deps, list) { + list_del(&se->list); + free(se); + } + + /* remove this slice from other slices dependencies */ + list_for_each_entry(s, &slices, list) { + list_for_each_entry_safe(se, tmp, &s->deps, list) { + if (se->slice == slice) { + list_del(&se->list); + free(se); + } + } + } + + free(slice->name); +} + +struct slice command_slice; + +/** + * command_slice_acquire - acquire the command slice + * + * The command slice is acquired by default. It is only released + * at certain points we know it's safe to execute code in the + * background, like when the shell is waiting for input. + */ +void command_slice_acquire(void) +{ + slice_acquire(&command_slice); +} + +/** + * command_slice_release - release the command slice + */ +void command_slice_release(void) +{ + slice_release(&command_slice); +} + +static int command_slice_init(void) +{ + slice_init(&command_slice, "command"); + slice_acquire(&command_slice); + return 0; +} + +pure_initcall(command_slice_init); + +#if defined CONFIG_CMD_SLICE + +#include <command.h> +#include <getopt.h> + +static void slice_info(void) +{ + struct slice *slice; + struct slice_entry *se; + + list_for_each_entry(slice, &slices, list) { + printf("slice %s: acquired=%d\n", + slice_name(slice), slice->acquired); + list_for_each_entry(se, &slice->deps, list) + printf(" dep: %s\n", slice_name(se->slice)); + } +} + +static int __slice_acquire_name(const char *name, int add) +{ + struct slice *slice; + + list_for_each_entry(slice, &slices, list) { + if (!strcmp(slice->name, name)) { + __slice_acquire(slice, add); + return 0; + } + } + + printf("No such slice: %s\n", name); + + return 1; +} + +BAREBOX_CMD_HELP_START(slice) +BAREBOX_CMD_HELP_TEXT("slice debugging command") +BAREBOX_CMD_HELP_TEXT("") +BAREBOX_CMD_HELP_TEXT("Options:") +BAREBOX_CMD_HELP_OPT ("-i", "Print information about slices") +BAREBOX_CMD_HELP_OPT ("-a <devname>", "acquire a slice") +BAREBOX_CMD_HELP_OPT ("-r <devname>", "release a slice") +BAREBOX_CMD_HELP_END + +static int do_slice(int argc, char *argv[]) +{ + int opt; + + while ((opt = getopt(argc, argv, "a:r:i")) > 0) { + switch (opt) { + case 'a': + return __slice_acquire_name(optarg, 1); + case 'r': + return __slice_acquire_name(optarg, -1); + case 'i': + slice_info(); + return 0; + } + } + + return COMMAND_ERROR_USAGE; +} + +BAREBOX_CMD_START(slice) + .cmd = do_slice, + BAREBOX_CMD_DESC("debug slices") + BAREBOX_CMD_OPTS("[-ari]") + BAREBOX_CMD_GROUP(CMD_GRP_MISC) + BAREBOX_CMD_HELP(cmd_slice_help) +BAREBOX_CMD_END +#endif diff --git a/common/startup.c b/common/startup.c index 6cb0588ae6..ea7ce6b8da 100644 --- a/common/startup.c +++ b/common/startup.c @@ -34,6 +34,7 @@ #include <debug_ll.h> #include <fs.h> #include <errno.h> +#include <slice.h> #include <linux/stat.h> #include <envfs.h> #include <magicvar.h> @@ -272,8 +273,10 @@ enum autoboot_state do_autoboot_countdown(void) break; } + command_slice_release(); ret = console_countdown(global_autoboot_timeout, flags, abortkeys, &outkey); + command_slice_acquire(); if (ret == 0) autoboot_state = AUTOBOOT_BOOT; diff --git a/common/usbgadget.c b/common/usbgadget.c index a8f104cf1c..b4f4ba04ca 100644 --- a/common/usbgadget.c +++ b/common/usbgadget.c @@ -30,8 +30,6 @@ static int autostart; static int acm; static char *dfu_function; -static char *fastboot_function; -static int fastboot_bbu; static struct file_list *parse(const char *files) { @@ -51,13 +49,14 @@ int usbgadget_register(bool dfu, const char *dfu_opts, int ret; struct device_d *dev; struct f_multi_opts *opts; + const char *fastboot_partitions = get_fastboot_partitions(); if (dfu && !dfu_opts && dfu_function && *dfu_function) dfu_opts = dfu_function; - if (fastboot && !fastboot_opts && - fastboot_function && *fastboot_function) - fastboot_opts = fastboot_function; + if (IS_ENABLED(CONFIG_FASTBOOT_BASE) && fastboot && !fastboot_opts && + fastboot_partitions && *fastboot_partitions) + fastboot_opts = fastboot_partitions; if (!dfu_opts && !fastboot_opts && !acm) return COMMAND_ERROR_USAGE; @@ -104,6 +103,8 @@ int usbgadget_register(bool dfu, const char *dfu_opts, static int usbgadget_autostart(void) { + bool fastboot_bbu = get_fastboot_bbu(); + if (!IS_ENABLED(CONFIG_USB_GADGET_AUTOSTART) || !autostart) return 0; @@ -116,12 +117,8 @@ static int usbgadget_globalvars_init(void) if (IS_ENABLED(CONFIG_USB_GADGET_AUTOSTART)) { globalvar_add_simple_bool("usbgadget.autostart", &autostart); globalvar_add_simple_bool("usbgadget.acm", &acm); - globalvar_add_simple_bool("usbgadget.fastboot_bbu", - &fastboot_bbu); } globalvar_add_simple_string("usbgadget.dfu_function", &dfu_function); - globalvar_add_simple_string("usbgadget.fastboot_function", - &fastboot_function); return 0; } @@ -136,9 +133,3 @@ BAREBOX_MAGICVAR_NAMED(global_usbgadget_acm, BAREBOX_MAGICVAR_NAMED(global_usbgadget_dfu_function, global.usbgadget.dfu_function, "usbgadget: Create DFU function"); -BAREBOX_MAGICVAR_NAMED(global_usbgadget_fastboot_function, - global.usbgadget.fastboot_function, - "usbgadget: Create Android Fastboot function"); -BAREBOX_MAGICVAR_NAMED(global_usbgadget_fastboot_bbu, - global.usbgadget.fastboot_bbu, - "usbgadget: export barebox update handlers via fastboot"); diff --git a/common/workqueue.c b/common/workqueue.c new file mode 100644 index 0000000000..dc6941dec1 --- /dev/null +++ b/common/workqueue.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <common.h> +#include <work.h> + +static void wq_do_pending_work(struct work_queue *wq) +{ + struct work_struct *work, *tmp; + + list_for_each_entry_safe(work, tmp, &wq->work, list) { + if (work->delayed && get_time_ns() < work->timeout) + continue; + + list_del(&work->list); + wq->fn(work); + } +} + +void wq_cancel_work(struct work_queue *wq) +{ + struct work_struct *work, *tmp; + + list_for_each_entry_safe(work, tmp, &wq->work, list) { + list_del(&work->list); + wq->cancel(work); + } +} + +static LIST_HEAD(work_queues); + +/** + * wq_do_all_works - do all pending work + * + * This calls all pending work functions + */ +void wq_do_all_works(void) +{ + struct work_queue *wq; + + list_for_each_entry(wq, &work_queues, list) + wq_do_pending_work(wq); +} + +/** + * wq_register - register a new work queue + * @wq: The work queue + */ +void wq_register(struct work_queue *wq) +{ + INIT_LIST_HEAD(&wq->work); + list_add_tail(&wq->list, &work_queues); +} + +/** + * wq_unregister - unregister a work queue + * @wq: The work queue + */ +void wq_unregister(struct work_queue *wq) +{ + wq_cancel_work(wq); + list_del(&wq->list); +} diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c index 3480e2ffb4..99d23ffedf 100644 --- a/drivers/net/phy/mdio_bus.c +++ b/drivers/net/phy/mdio_bus.c @@ -239,6 +239,8 @@ int mdiobus_register(struct mii_bus *bus) return -EINVAL; } + slice_init(&bus->slice, dev_name(&bus->dev)); + if (bus->reset) bus->reset(bus); @@ -272,6 +274,8 @@ void mdiobus_unregister(struct mii_bus *bus) bus->phy_map[i] = NULL; } + slice_exit(&bus->slice); + list_del(&bus->list); } EXPORT_SYMBOL(mdiobus_unregister); @@ -357,6 +361,45 @@ static int mdio_bus_match(struct device_d *dev, struct driver_d *drv) return 1; } +/** + * mdiobus_read - Convenience function for reading a given MII mgmt register + * @bus: the mii_bus struct + * @addr: the phy address + * @regnum: register number to read + */ +int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum) +{ + int ret; + + slice_acquire(&bus->slice); + + ret = bus->read(bus, addr, regnum); + + slice_release(&bus->slice); + + return ret; +} + +/** + * mdiobus_write - Convenience function for writing a given MII mgmt register + * @bus: the mii_bus struct + * @addr: the phy address + * @regnum: register number to write + * @val: value to write to @regnum + */ +int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val) +{ + int ret; + + slice_acquire(&bus->slice); + + ret = bus->write(bus, addr, regnum, val); + + slice_release(&bus->slice); + + return ret; +} + static ssize_t phydev_read(struct cdev *cdev, void *_buf, size_t count, loff_t offset, ulong flags) { int i = count; diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c index 7397034586..cead95e7f6 100644 --- a/drivers/net/usb/usbnet.c +++ b/drivers/net/usb/usbnet.c @@ -124,7 +124,7 @@ static int usbnet_recv(struct eth_device *edev) len = dev->rx_urb_size; - ret = usb_bulk_msg(dev->udev, dev->in, dev->rx_buf, len, &alen, 100); + ret = usb_bulk_msg(dev->udev, dev->in, dev->rx_buf, len, &alen, 2); if (ret) return ret; @@ -233,6 +233,9 @@ int usbnet_probe(struct usb_device *usbdev, const struct usb_device_id *prod) eth_register(edev); + slice_depends_on(eth_device_slice(edev), usb_device_slice(usbdev)); + slice_depends_on(mdiobus_slice(&undev->miibus), usb_device_slice(usbdev)); + return 0; out1: dev_dbg(&edev->dev, "err: %d\n", status); diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 30c251f405..c068c64c6b 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -75,28 +75,30 @@ static int host_busnum = 1; static inline int usb_host_acquire(struct usb_host *host) { - if (host->sem) + if (slice_acquired(&host->slice)) return -EAGAIN; - host->sem++; + + slice_acquire(&host->slice); + return 0; } static inline void usb_host_release(struct usb_host *host) { - if (host->sem > 0) - host->sem--; + slice_release(&host->slice); } int usb_register_host(struct usb_host *host) { list_add_tail(&host->list, &host_list); host->busnum = host_busnum++; - host->sem = 0; + slice_init(&host->slice, dev_name(host->hw_dev)); return 0; } void usb_unregister_host(struct usb_host *host) { + slice_exit(&host->slice); list_del(&host->list); } diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c index f8a9c32530..da3e0c7a8f 100644 --- a/drivers/usb/gadget/f_fastboot.c +++ b/drivers/usb/gadget/f_fastboot.c @@ -21,6 +21,7 @@ #define pr_fmt(fmt) "fastboot: " fmt #include <dma.h> +#include <work.h> #include <unistd.h> #include <progress.h> #include <fastboot.h> @@ -39,6 +40,7 @@ struct f_fastboot { /* IN/OUT EP's and corresponding requests */ struct usb_ep *in_ep, *out_ep; struct usb_request *in_req, *out_req; + struct work_queue wq; }; static inline struct f_fastboot *func_to_fastboot(struct usb_function *f) @@ -136,6 +138,32 @@ static void fastboot_complete(struct usb_ep *ep, struct usb_request *req) { } +struct fastboot_work { + struct work_struct work; + struct f_fastboot *f_fb; + char command[FASTBOOT_MAX_CMD_LEN + 1]; +}; + +static void fastboot_do_work(struct work_struct *w) +{ + struct fastboot_work *fw = container_of(w, struct fastboot_work, work); + struct f_fastboot *f_fb = fw->f_fb; + + fastboot_exec_cmd(&f_fb->fastboot, fw->command); + + memset(f_fb->out_req->buf, 0, EP_BUFFER_SIZE); + usb_ep_queue(f_fb->out_ep, f_fb->out_req); + + free(fw); +} + +static void fastboot_work_cancel(struct work_struct *w) +{ + struct fastboot_work *fw = container_of(w, struct fastboot_work, work); + + free(fw); +} + static struct usb_request *fastboot_alloc_request(struct usb_ep *ep) { struct usb_request *req; @@ -172,6 +200,11 @@ static int fastboot_bind(struct usb_configuration *c, struct usb_function *f) f_fb->fastboot.cmd_exec = opts->common.cmd_exec; f_fb->fastboot.cmd_flash = opts->common.cmd_flash; + f_fb->wq.fn = fastboot_do_work; + f_fb->wq.cancel = fastboot_work_cancel; + + wq_register(&f_fb->wq); + ret = fastboot_generic_init(&f_fb->fastboot, opts->common.export_bbu); if (ret) return ret; @@ -246,6 +279,8 @@ static void fastboot_unbind(struct usb_configuration *c, struct usb_function *f) usb_ep_free_request(f_fb->out_ep, f_fb->out_req); f_fb->out_req = NULL; + wq_unregister(&f_fb->wq); + fastboot_generic_free(&f_fb->fastboot); } @@ -313,8 +348,6 @@ static struct usb_function *fastboot_alloc_func(struct usb_function_instance *fi f_fb = xzalloc(sizeof(*f_fb)); - INIT_LIST_HEAD(&f_fb->fastboot.variables); - f_fb->func.name = "fastboot"; f_fb->func.strings = fastboot_strings; f_fb->func.bind = fastboot_bind; @@ -430,16 +463,19 @@ static void fastboot_start_download_usb(struct fastboot *fb) static void rx_handler_command(struct usb_ep *ep, struct usb_request *req) { - char *cmdbuf = req->buf; struct f_fastboot *f_fb = req->context; + struct fastboot_work *w; + int len; if (req->status != 0) return; - *(cmdbuf + req->actual) = 0; - fastboot_exec_cmd(&f_fb->fastboot, cmdbuf); - *cmdbuf = '\0'; - req->actual = 0; - memset(req->buf, 0, EP_BUFFER_SIZE); - usb_ep_queue(ep, req); + w = xzalloc(sizeof(*w)); + w->f_fb = f_fb; + + len = min_t(unsigned int, req->actual, FASTBOOT_MAX_CMD_LEN); + + memcpy(w->command, req->buf, len); + + wq_queue_work(&f_fb->wq, &w->work); } @@ -31,6 +31,7 @@ #include <environment.h> #include <libgen.h> #include <block.h> +#include <slice.h> #include <libfile.h> #include <parseopt.h> #include <linux/namei.h> @@ -74,6 +75,8 @@ static FILE *files; static struct dentry *d_root; static struct vfsmount *mnt_root; +static struct fs_driver_d *ramfs_driver; + static int init_fs(void) { cwd = xzalloc(PATH_MAX); @@ -257,6 +260,9 @@ static ssize_t __read(FILE *f, void *buf, size_t count) fsdrv = f->fsdev->driver; + if (fsdrv != ramfs_driver) + assert_command_context(); + if (f->size != FILE_SIZE_STREAM && f->pos + count > f->size) count = f->size - f->pos; @@ -315,6 +321,10 @@ static ssize_t __write(FILE *f, const void *buf, size_t count) } fsdrv = f->fsdev->driver; + + if (fsdrv != ramfs_driver) + assert_command_context(); + if (f->size != FILE_SIZE_STREAM && f->pos + count > f->size) { ret = fsdrv->truncate(&f->fsdev->dev, f, f->pos + count); if (ret) { @@ -402,6 +412,9 @@ loff_t lseek(int fd, loff_t offset, int whence) fsdrv = f->fsdev->driver; + if (fsdrv != ramfs_driver) + assert_command_context(); + ret = -EINVAL; switch (whence) { @@ -457,6 +470,10 @@ int erase(int fd, loff_t count, loff_t offset) return -EINVAL; fsdrv = f->fsdev->driver; + + if (fsdrv != ramfs_driver) + assert_command_context(); + if (fsdrv->erase) ret = fsdrv->erase(&f->fsdev->dev, f, count, offset); else @@ -483,6 +500,10 @@ int protect(int fd, size_t count, loff_t offset, int prot) count = f->size - offset; fsdrv = f->fsdev->driver; + + if (fsdrv != ramfs_driver) + assert_command_context(); + if (fsdrv->protect) ret = fsdrv->protect(&f->fsdev->dev, f, count, offset, prot); else @@ -509,6 +530,10 @@ int discard_range(int fd, loff_t count, loff_t offset) count = f->size - offset; fsdrv = f->fsdev->driver; + + if (fsdrv != ramfs_driver) + assert_command_context(); + if (fsdrv->discard_range) ret = fsdrv->discard_range(&f->fsdev->dev, f, count, offset); else @@ -547,6 +572,9 @@ void *memmap(int fd, int flags) fsdrv = f->fsdev->driver; + if (fsdrv != ramfs_driver) + assert_command_context(); + if (fsdrv->memmap) ret = fsdrv->memmap(&f->fsdev->dev, f, &retp, flags); else @@ -570,6 +598,9 @@ int close(int fd) fsdrv = f->fsdev->driver; + if (fsdrv != ramfs_driver) + assert_command_context(); + if (fsdrv->close) ret = fsdrv->close(&f->fsdev->dev, f); @@ -698,6 +729,9 @@ int register_fs_driver(struct fs_driver_d *fsdrv) fsdrv->drv.bus = &fs_bus; register_driver(&fsdrv->drv); + if (!strcmp(fsdrv->drv.name, "ramfs")) + ramfs_driver = fsdrv; + return 0; } EXPORT_SYMBOL(register_fs_driver); diff --git a/include/asm-generic/bug.h b/include/asm-generic/bug.h index 8583d2425f..82b78261fc 100644 --- a/include/asm-generic/bug.h +++ b/include/asm-generic/bug.h @@ -34,4 +34,17 @@ unlikely(__ret_warn_on); \ }) #endif + +#define WARN_ONCE(condition, format...) ({ \ + static int __warned; \ + int __ret_warn_once = !!(condition); \ + \ + if (unlikely(__ret_warn_once)) { \ + if (WARN(!__warned, format)) { \ + __warned = 1; \ + dump_stack(); \ + } \ + } \ + unlikely(__ret_warn_once); \ +}) #endif diff --git a/include/fastboot.h b/include/fastboot.h index d33f9d1851..2eab2dfe6a 100644 --- a/include/fastboot.h +++ b/include/fastboot.h @@ -5,6 +5,8 @@ #include <file-list.h> #include <net.h> +#define FASTBOOT_MAX_CMD_LEN 64 + /* * Return codes for the exec_cmd callback above: * @@ -51,8 +53,24 @@ enum fastboot_msg_type { FASTBOOT_MSG_FAIL, FASTBOOT_MSG_INFO, FASTBOOT_MSG_DATA, + FASTBOOT_MSG_NONE, }; +#ifdef CONFIG_FASTBOOT_BASE +bool get_fastboot_bbu(void); +const char *get_fastboot_partitions(void); +#else +static inline int get_fastboot_bbu(void) +{ + return false; +} + +static inline const char *get_fastboot_partitions(void) +{ + return NULL; +} +#endif + int fastboot_generic_init(struct fastboot *fb, bool export_bbu); void fastboot_generic_close(struct fastboot *fb); void fastboot_generic_free(struct fastboot *fb); @@ -63,4 +81,6 @@ int fastboot_tx_print(struct fastboot *fb, enum fastboot_msg_type type, void fastboot_start_download_generic(struct fastboot *fb); void fastboot_download_finished(struct fastboot *fb); void fastboot_exec_cmd(struct fastboot *fb, const char *cmdbuf); +void fastboot_abort(struct fastboot *fb); + #endif diff --git a/include/fastboot_net.h b/include/fastboot_net.h new file mode 100644 index 0000000000..e4b9d98091 --- /dev/null +++ b/include/fastboot_net.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +#ifndef __FASTBOOT_NET__ +#define __FASTBOOT_NET__ + +#include <fastboot.h> + +struct fastboot_net; + +struct fastboot_net *fastboot_net_init(struct fastboot_opts *opts); +void fastboot_net_free(struct fastboot_net *fbn); + +#endif diff --git a/include/globalvar.h b/include/globalvar.h index face53d33f..84bee9102c 100644 --- a/include/globalvar.h +++ b/include/globalvar.h @@ -36,6 +36,7 @@ int nvvar_remove(const char *name); void globalvar_print(void); void dev_param_init_from_nv(struct device_d *dev, const char *name); +void globalvar_alias_deprecated(const char *newname, const char *oldname); #else static inline int globalvar_add_simple(const char *name, const char *value) @@ -118,6 +119,10 @@ static inline void dev_param_init_from_nv(struct device_d *dev, const char *name { } +static inline void globalvar_alias_deprecated(const char *newname, const char *oldname) +{ +} + #endif void nv_var_set_clean(void); diff --git a/include/linux/phy.h b/include/linux/phy.h index eec1332c9d..cdcb7c24f2 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -16,6 +16,7 @@ #define __PHY_H #include <driver.h> +#include <slice.h> #include <linux/list.h> #include <linux/ethtool.h> #include <linux/mii.h> @@ -116,9 +117,16 @@ struct mii_bus { struct list_head list; bool is_multiplexed; + + struct slice slice; }; #define to_mii_bus(d) container_of(d, struct mii_bus, dev) +static inline struct slice *mdiobus_slice(struct mii_bus *bus) +{ + return &bus->slice; +} + int mdiobus_register(struct mii_bus *bus); void mdiobus_unregister(struct mii_bus *bus); struct phy_device *mdiobus_scan(struct mii_bus *bus, int addr); @@ -134,28 +142,8 @@ struct mii_bus *mdiobus_get_bus(int busnum); struct mii_bus *of_mdio_find_bus(struct device_node *mdio_bus_np); -/** - * mdiobus_read - Convenience function for reading a given MII mgmt register - * @bus: the mii_bus struct - * @addr: the phy address - * @regnum: register number to read - */ -static inline int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum) -{ - return bus->read(bus, addr, regnum); -} - -/** - * mdiobus_write - Convenience function for writing a given MII mgmt register - * @bus: the mii_bus struct - * @addr: the phy address - * @regnum: register number to write - * @val: value to write to @regnum - */ -static inline int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val) -{ - return bus->write(bus, addr, regnum, val); -} +int mdiobus_read(struct mii_bus *bus, int addr, u32 regnum); +int mdiobus_write(struct mii_bus *bus, int addr, u32 regnum, u16 val); /* phy_device: An instance of a PHY * @@ -397,11 +385,15 @@ int phy_register_fixup(const char *bus_id, u32 phy_uid, u32 phy_uid_mask, int phy_register_fixup_for_id(const char *bus_id, int (*run)(struct phy_device *)); int phy_scan_fixups(struct phy_device *phydev); - int phy_read_mmd_indirect(struct phy_device *phydev, int prtad, int devad); void phy_write_mmd_indirect(struct phy_device *phydev, int prtad, int devad, u16 data); +static inline bool phy_acquired(struct phy_device *phydev) +{ + return phydev && phydev->bus && slice_acquired(&phydev->bus->slice); +} + #ifdef CONFIG_PHYLIB int phy_register_fixup_for_uid(u32 phy_uid, u32 phy_uid_mask, int (*run)(struct phy_device *)); diff --git a/include/net.h b/include/net.h index 54db8a179a..aad28e4f4c 100644 --- a/include/net.h +++ b/include/net.h @@ -19,6 +19,7 @@ #include <stdlib.h> #include <clock.h> #include <led.h> +#include <slice.h> #include <xfuncs.h> #include <linux/phy.h> #include <linux/string.h> /* memcpy */ @@ -63,6 +64,10 @@ struct eth_device { char *bootarg; char *linuxdevname; + struct slice slice; + + struct list_head send_queue; + bool ifup; #define ETH_MODE_DHCP 0 #define ETH_MODE_STATIC 1 @@ -72,6 +77,11 @@ struct eth_device { #define dev_to_edev(d) container_of(d, struct eth_device, dev) +static inline struct slice *eth_device_slice(struct eth_device *edev) +{ + return &edev->slice; +} + static inline const char *eth_name(struct eth_device *edev) { return edev->devname; diff --git a/include/ratp_bb.h b/include/ratp_bb.h index a4f28c642c..b710f99bf9 100644 --- a/include/ratp_bb.h +++ b/include/ratp_bb.h @@ -41,7 +41,6 @@ struct ratp_bb_pkt { }; int barebox_ratp(struct console_device *cdev); -void barebox_ratp_command_run(void); int barebox_ratp_fs_call(struct ratp_bb_pkt *tx, struct ratp_bb_pkt **rx); int barebox_ratp_fs_mount(const char *path); diff --git a/include/slice.h b/include/slice.h new file mode 100644 index 0000000000..b2d65b80cd --- /dev/null +++ b/include/slice.h @@ -0,0 +1,46 @@ +#ifndef __SLICE_H +#define __SLICE_H + +enum slice_action { + SLICE_ACQUIRE = 1, + SLICE_RELEASE = -1, + SLICE_TEST = 0, +}; + +struct slice { + int acquired; + struct list_head deps; + char *name; + struct list_head list; +}; + +struct slice_entry { + struct slice *slice; + struct list_head list; +}; + +void slice_acquire(struct slice *slice); +void slice_release(struct slice *slice); +bool slice_acquired(struct slice *slice); +void slice_depends_on(struct slice *slice, struct slice *dep); +void slice_init(struct slice *slice, const char *name); +void slice_exit(struct slice *slice); + +void slice_debug_acquired(struct slice *slice); + +extern struct slice command_slice; + +void command_slice_acquire(void); +void command_slice_release(void); + +extern int poller_active; + +#ifdef CONFIG_POLLER +#define assert_command_context() ({ \ + WARN_ONCE(poller_active, "%s called in poller\n", __func__); \ +}) +#else +#define assert_command_context() do { } while (0) +#endif + +#endif /* __SLICE_H */ diff --git a/include/usb/usb.h b/include/usb/usb.h index c2085eae87..39f4750916 100644 --- a/include/usb/usb.h +++ b/include/usb/usb.h @@ -20,6 +20,7 @@ #define _USB_H_ #include <driver.h> +#include <slice.h> #include <usb/ch9.h> #include <usb/ch11.h> #include <usb/usb_defs.h> @@ -163,13 +164,18 @@ struct usb_host { struct device_d *hw_dev; int busnum; struct usb_device *root_dev; - int sem; struct usb_phy *usbphy; + struct slice slice; }; int usb_register_host(struct usb_host *); void usb_unregister_host(struct usb_host *host); +static inline struct slice *usb_device_slice(struct usb_device *udev) +{ + return &udev->host->slice; +} + int usb_host_detect(struct usb_host *host); int usb_set_protocol(struct usb_device *dev, int ifnum, int protocol); diff --git a/include/work.h b/include/work.h new file mode 100644 index 0000000000..0785bb3a88 --- /dev/null +++ b/include/work.h @@ -0,0 +1,42 @@ +#ifndef __WORK_H +#define __WORK_H + +#include <linux/list.h> +#include <clock.h> + +struct work_struct { + struct list_head list; + uint64_t timeout; + bool delayed; +}; + +struct work_queue { + void (*fn)(struct work_struct *work); + void (*cancel)(struct work_struct *work); + + struct list_head list; + struct list_head work; +}; + +static inline void wq_queue_work(struct work_queue *wq, struct work_struct *work) +{ + work->delayed = false; + list_add_tail(&work->list, &wq->work); +} + +static inline void wq_queue_delayed_work(struct work_queue *wq, + struct work_struct *work, + uint64_t delay_ns) +{ + work->timeout = get_time_ns() + delay_ns; + work->delayed = true; + list_add_tail(&work->list, &wq->work); +} + +void wq_register(struct work_queue *wq); +void wq_unregister(struct work_queue *wq); + +void wq_do_all_works(void); +void wq_cancel_work(struct work_queue *wq); + +#endif /* __WORK_H */ diff --git a/lib/readline.c b/lib/readline.c index 3d16c1838c..e5370f9c7b 100644 --- a/lib/readline.c +++ b/lib/readline.c @@ -3,7 +3,6 @@ #include <init.h> #include <libbb.h> #include <poller.h> -#include <ratp_bb.h> #include <xfuncs.h> #include <complete.h> #include <linux/ctype.h> @@ -200,11 +199,8 @@ int readline(const char *prompt, char *buf, int len) puts (prompt); while (1) { - while (!tstc()) { + while (!tstc()) poller_call(); - if (IS_ENABLED(CONFIG_CONSOLE_RATP)) - barebox_ratp_command_run(); - } ichar = read_key(); diff --git a/net/Kconfig b/net/Kconfig index 12b6bdb56d..1549c9af6b 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -1,5 +1,6 @@ menuconfig NET bool "Networking Support" + select POLLER if NET @@ -31,4 +32,14 @@ config NET_SNTP bool prompt "sntp support" +config NET_FASTBOOT + bool + select BANNER + select FILE_LIST + select FASTBOOT_BASE + prompt "Android Fastboot support" + help + This option adds support for the UDP variant of the Fastboot + protocol. + endif diff --git a/net/Makefile b/net/Makefile index eb8d439150..962b2dec58 100644 --- a/net/Makefile +++ b/net/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_CMD_PING) += ping.o obj-$(CONFIG_NET_RESOLV)+= dns.o obj-$(CONFIG_NET_NETCONSOLE) += netconsole.o obj-$(CONFIG_NET_IFUP) += ifup.o +obj-$(CONFIG_NET_FASTBOOT) += fastboot.o @@ -22,6 +22,7 @@ #include <init.h> #include <dhcp.h> #include <net.h> +#include <dma.h> #include <of.h> #include <linux/phy.h> #include <errno.h> @@ -208,6 +209,36 @@ static int eth_carrier_check(struct eth_device *edev, int force) return edev->phydev->link ? 0 : -ENETDOWN; } +struct eth_q { + struct eth_device *edev; + int length; + struct list_head list; + void *data; +}; + +static int eth_queue(struct eth_device *edev, void *packet, int length) +{ + struct eth_q *q; + + q = xzalloc(sizeof(*q)); + if (!q) + return -ENOMEM; + + q->data = dma_alloc(length); + if (!q->data) { + free(q); + return -ENOMEM; + } + + q->length = length; + q->edev = edev; + + memcpy(q->data, packet, length); + list_add_tail(&q->list, &edev->send_queue); + + return 0; +} + int eth_send(struct eth_device *edev, void *packet, int length) { int ret; @@ -215,24 +246,51 @@ int eth_send(struct eth_device *edev, void *packet, int length) if (!edev->active) return -ENETDOWN; + if (slice_acquired(eth_device_slice(edev))) + return eth_queue(edev, packet, length); + ret = eth_carrier_check(edev, 0); if (ret) return ret; + slice_acquire(eth_device_slice(edev)); + led_trigger_network(LED_TRIGGER_NET_TX); - return edev->send(edev, packet, length); + ret = edev->send(edev, packet, length); + + slice_release(eth_device_slice(edev)); + + return ret; } -static int __eth_rx(struct eth_device *edev) +static void eth_do_work(struct eth_device *edev) { + struct eth_q *q, *tmp; int ret; - ret = eth_carrier_check(edev, 0); - if (ret) - return ret; + if (!phy_acquired(edev->phydev)) { + ret = eth_carrier_check(edev, 0); + if (ret) + return; + } + + if (slice_acquired(eth_device_slice(edev))) + return; + + slice_acquire(eth_device_slice(edev)); + + edev->recv(edev); - return edev->recv(edev); + list_for_each_entry_safe(q, tmp, &edev->send_queue, list) { + led_trigger_network(LED_TRIGGER_NET_TX); + edev->send(edev, q->data, q->length); + list_del(&q->list); + free(q->data); + free(q); + } + + slice_release(eth_device_slice(edev)); } int eth_rx(void) @@ -241,7 +299,7 @@ int eth_rx(void) for_each_netdev(edev) { if (edev->active) - __eth_rx(edev); + eth_do_work(edev); } return 0; @@ -349,10 +407,14 @@ int eth_register(struct eth_device *edev) edev->dev.id = DEVICE_ID_DYNAMIC; } + INIT_LIST_HEAD(&edev->send_queue); + ret = register_device(&edev->dev); if (ret) return ret; + slice_init(&edev->slice, dev_name(dev)); + edev->devname = xstrdup(dev_name(&edev->dev)); dev_add_param_ip(dev, "ipaddr", NULL, NULL, &edev->ipaddr, edev); @@ -422,15 +484,27 @@ void eth_close(struct eth_device *edev) void eth_unregister(struct eth_device *edev) { + struct eth_q *q, *tmp; + if (edev->active) edev->halt(edev); + list_for_each_entry_safe(q, tmp, &edev->send_queue, list) { + if (q->edev != edev) + continue; + + list_del(&q->list); + free(q->data); + free(q); + } + if (IS_ENABLED(CONFIG_OFDEVICE)) free(edev->nodepath); free(edev->devname); unregister_device(&edev->dev); + slice_exit(&edev->slice); list_del(&edev->list); } diff --git a/net/fastboot.c b/net/fastboot.c new file mode 100644 index 0000000000..a664fc8163 --- /dev/null +++ b/net/fastboot.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Copyright 2020 Edmund Henniges <eh@emlix.com> + * Copyright 2020 Daniel Glöckner <dg@emlix.com> + * Ported from U-Boot to Barebox + */ + +#define pr_fmt(fmt) "net fastboot: " fmt + +#include <common.h> +#include <net.h> +#include <fastboot.h> +#include <fastboot_net.h> +#include <environment.h> +#include <progress.h> +#include <unistd.h> +#include <init.h> +#include <work.h> +#include <globalvar.h> +#include <magicvar.h> + +#define FASTBOOT_PORT 5554 +#define MAX_MTU 1500 +#define PACKET_SIZE (min(PKTSIZE, MAX_MTU + ETHER_HDR_SIZE) \ + - (net_eth_to_udp_payload(0) - (char *)0)) + +enum { + FASTBOOT_ERROR = 0, + FASTBOOT_QUERY = 1, + FASTBOOT_INIT = 2, + FASTBOOT_FASTBOOT = 3, +}; + +enum may_send { + MAY_NOT_SEND, + MAY_SEND_MESSAGE, + MAY_SEND_ACK, +}; + +struct __packed fastboot_header { + u8 id; + u8 flags; + u16 seq; +}; + +#define FASTBOOT_MAX_MSG_LEN 64 + +struct fastboot_net { + struct fastboot fastboot; + + struct net_connection *net_con; + struct fastboot_header response_header; + struct poller_struct poller; + struct work_queue wq; + u64 host_waits_since; + u64 last_download_pkt; + bool sequence_number_seen; + bool active_download; + bool reinit; + bool send_keep_alive; + enum may_send may_send; + + IPaddr_t host_addr; + u16 host_port; + u8 host_mac[ETH_ALEN]; + u16 sequence_number; + u16 last_payload_len; + void *last_payload; +}; + +static const ushort udp_version = 1; + +static bool is_current_connection(struct fastboot_net *fbn) +{ + return fbn->host_addr == net_read_ip(&fbn->net_con->ip->daddr) && + fbn->host_port == fbn->net_con->udp->uh_dport; +} + +static void fastboot_net_abort(struct fastboot_net *fbn) +{ + fbn->reinit = true; + + fastboot_abort(&fbn->fastboot); + + fbn->active_download = false; + + poller_unregister(&fbn->poller); + + /* + * If the host sends a data packet at a time when an empty packet was + * expected, fastboot_abort is called and an error message is sent. + * We don't want to execute the contents of the bad packet afterwards. + * Clearing command also tells our keep-alive poller to stop sending + * messages. + */ + wq_cancel_work(&fbn->wq); + + free(fbn->last_payload); + fbn->last_payload = NULL; +} + +static void fastboot_net_save_payload(struct fastboot_net *fbn, void *packet, + int len) +{ + /* Save packet for retransmitting */ + + fbn->last_payload_len = len; + free(fbn->last_payload); + fbn->last_payload = memdup(packet, len); +} + +static void fastboot_send(struct fastboot_net *fbn, + struct fastboot_header header, + const char *error_msg) +{ + short tmp; + uchar *packet = net_udp_get_payload(fbn->net_con); + uchar *packet_base = packet; + bool current_session = false; + + if (fbn->sequence_number == ntohs(header.seq) && + is_current_connection(fbn)) + current_session = true; + + if (error_msg) + header.id = FASTBOOT_ERROR; + + /* send header */ + memcpy(packet, &header, sizeof(header)); + packet += sizeof(header); + + switch (header.id) { + case FASTBOOT_QUERY: + /* send sequence number */ + tmp = htons(fbn->sequence_number); + memcpy(packet, &tmp, sizeof(tmp)); + packet += sizeof(tmp); + break; + case FASTBOOT_INIT: + /* send udp version and packet size */ + tmp = htons(udp_version); + memcpy(packet, &tmp, sizeof(tmp)); + packet += sizeof(tmp); + tmp = htons(PACKET_SIZE); + memcpy(packet, &tmp, sizeof(tmp)); + packet += sizeof(tmp); + break; + case FASTBOOT_ERROR: + pr_err("%s\n", error_msg); + + /* send error message */ + tmp = strlen(error_msg); + memcpy(packet, error_msg, tmp); + packet += tmp; + + if (current_session) + fastboot_net_abort(fbn); + + break; + } + + if (current_session && header.id != FASTBOOT_QUERY) { + fbn->sequence_number++; + fbn->sequence_number_seen = false; + fastboot_net_save_payload(fbn, packet_base, packet - packet_base); + } + net_udp_send(fbn->net_con, packet - packet_base); +} + +static int fastboot_net_wait_may_send(struct fastboot_net *fbn) +{ + uint64_t start = get_time_ns(); + + while (!is_timeout(start, 2 * SECOND)) { + if (fbn->may_send != MAY_NOT_SEND) + return 0; + } + + return -ETIMEDOUT; +} + +static int fastboot_write_net(struct fastboot *fb, const char *buf, + unsigned int n) +{ + struct fastboot_net *fbn = container_of(fb, struct fastboot_net, + fastboot); + struct fastboot_header response_header; + uchar *packet; + uchar *packet_base; + int ret; + + if (fbn->reinit) + return 0; + + /* + * This function is either called in command context, in which + * case we may wait, or from the keepalive poller which explicitly + * only calls us when we don't have to wait here. + */ + ret = fastboot_net_wait_may_send(fbn); + if (ret) { + fastboot_net_abort(fbn); + return ret; + } + + if (n && fbn->may_send == MAY_SEND_ACK) { + fastboot_send(fbn, fbn->response_header, + "Have message but only ACK allowed"); + return -EPROTO; + } else if (!n && fbn->may_send == MAY_SEND_MESSAGE) { + fastboot_send(fbn, fbn->response_header, + "Want to send ACK but message expected"); + return -EPROTO; + } + + response_header = fbn->response_header; + response_header.flags = 0; + response_header.seq = htons(fbn->sequence_number); + ++fbn->sequence_number; + fbn->sequence_number_seen = false; + + packet = net_udp_get_payload(fbn->net_con); + packet_base = packet; + + /* Write headers */ + memcpy(packet, &response_header, sizeof(response_header)); + packet += sizeof(response_header); + /* Write response */ + memcpy(packet, buf, n); + packet += n; + + fastboot_net_save_payload(fbn, packet_base, packet - packet_base); + + memcpy(fbn->net_con->et->et_dest, fbn->host_mac, ETH_ALEN); + net_write_ip(&fbn->net_con->ip->daddr, fbn->host_addr); + fbn->net_con->udp->uh_dport = fbn->host_port; + + fbn->may_send = MAY_NOT_SEND; + + net_udp_send(fbn->net_con, fbn->last_payload_len); + + return 0; +} + +static void fastboot_start_download_net(struct fastboot *fb) +{ + struct fastboot_net *fbn = container_of(fb, struct fastboot_net, + fastboot); + + fastboot_start_download_generic(fb); + fbn->active_download = true; + fbn->last_download_pkt = get_time_ns(); +} + +/* must send exactly one packet on all code paths */ +static void fastboot_data_download(struct fastboot_net *fbn, + const void *fastboot_data, + unsigned int fastboot_data_len) +{ + int ret; + + if (fastboot_data_len == 0 || + (fbn->fastboot.download_bytes + fastboot_data_len) > + fbn->fastboot.download_size) { + fastboot_send(fbn, fbn->response_header, + "Received invalid data length"); + return; + } + + ret = fastboot_handle_download_data(&fbn->fastboot, fastboot_data, + fastboot_data_len); + if (ret < 0) { + fastboot_send(fbn, fbn->response_header, strerror(-ret)); + return; + } + + fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_NONE, ""); +} + +struct fastboot_work { + struct work_struct work; + struct fastboot_net *fbn; + bool download_finished; + char command[FASTBOOT_MAX_CMD_LEN + 1]; +}; + +static void fastboot_handle_type_fastboot(struct fastboot_net *fbn, + struct fastboot_header header, + char *fastboot_data, + unsigned int fastboot_data_len) +{ + struct fastboot_work *w; + + fbn->response_header = header; + fbn->host_waits_since = get_time_ns(); + fbn->may_send = fastboot_data_len ? MAY_SEND_ACK : MAY_SEND_MESSAGE; + + if (fbn->active_download) { + fbn->last_download_pkt = get_time_ns(); + + if (!fastboot_data_len && fbn->fastboot.download_bytes + == fbn->fastboot.download_size) { + + fbn->active_download = false; + + w = xzalloc(sizeof(*w)); + w->fbn = fbn; + w->download_finished = true; + + wq_queue_work(&fbn->wq, &w->work); + } else { + fastboot_data_download(fbn, fastboot_data, + fastboot_data_len); + } + return; + } + + if (fastboot_data_len > FASTBOOT_MAX_CMD_LEN) { + fastboot_send(fbn, header, "command too long"); + return; + } + + if (!list_empty(&fbn->wq.work)) + return; + + if (fastboot_data_len) { + w = xzalloc(sizeof(*w)); + w->fbn = fbn; + memcpy(w->command, fastboot_data, fastboot_data_len); + w->command[fastboot_data_len] = 0; + + wq_queue_work(&fbn->wq, &w->work); + } +} + +static void fastboot_check_retransmit(struct fastboot_net *fbn, + struct fastboot_header header) +{ + if (ntohs(header.seq) == fbn->sequence_number - 1 && + is_current_connection(fbn)) { + /* Retransmit last sent packet */ + memcpy(net_udp_get_payload(fbn->net_con), + fbn->last_payload, fbn->last_payload_len); + net_udp_send(fbn->net_con, fbn->last_payload_len); + } +} + +static void fastboot_handler(void *ctx, char *packet, unsigned int raw_len) +{ + unsigned int len = net_eth_to_udplen(packet); + struct ethernet *eth_header = (struct ethernet *)packet; + struct iphdr *ip_header = net_eth_to_iphdr(packet); + struct udphdr *udp_header = net_eth_to_udphdr(packet); + char *payload = net_eth_to_udp_payload(packet); + struct fastboot_net *fbn = ctx; + struct fastboot_header header; + char *fastboot_data = payload + sizeof(header); + u16 tot_len = ntohs(ip_header->tot_len); + int ret; + + /* catch bogus tot_len values */ + if ((char *)ip_header - packet + tot_len > raw_len) + return; + + /* catch packets split into fragments that are too small to reply */ + if (fastboot_data - (char *)ip_header > tot_len) + return; + + /* catch packets too small to be valid */ + if (len < sizeof(struct fastboot_header)) + return; + + memcpy(&header, payload, sizeof(header)); + header.flags = 0; + len -= sizeof(header); + + /* catch remaining fragmented packets */ + if (fastboot_data - (char *)ip_header + len > tot_len) { + fastboot_send(fbn, header, + "can't reassemble fragmented frames"); + return; + } + /* catch too large packets */ + if (len > PACKET_SIZE) { + fastboot_send(fbn, header, "packet too large"); + return; + } + + memcpy(fbn->net_con->et->et_dest, eth_header->et_src, ETH_ALEN); + net_copy_ip(&fbn->net_con->ip->daddr, &ip_header->saddr); + fbn->net_con->udp->uh_dport = udp_header->uh_sport; + + switch (header.id) { + case FASTBOOT_QUERY: + fastboot_send(fbn, header, NULL); + break; + case FASTBOOT_INIT: + if (ntohs(header.seq) != fbn->sequence_number) { + fastboot_check_retransmit(fbn, header); + break; + } + fbn->host_addr = net_read_ip(&ip_header->saddr); + fbn->host_port = udp_header->uh_sport; + memcpy(fbn->host_mac, eth_header->et_src, ETH_ALEN); + fastboot_net_abort(fbn); + /* poller just unregistered in fastboot_net_abort() */ + ret = poller_register(&fbn->poller, "fastboot"); + if (ret) { + pr_err("Cannot register poller: %s\n", strerror(-ret)); + return; + } + fastboot_send(fbn, header, NULL); + break; + case FASTBOOT_FASTBOOT: + if (!is_current_connection(fbn)) + break; + memcpy(fbn->host_mac, eth_header->et_src, ETH_ALEN); + + if (ntohs(header.seq) != fbn->sequence_number) { + fastboot_check_retransmit(fbn, header); + } else if (!fbn->sequence_number_seen) { + fbn->sequence_number_seen = true; + fastboot_handle_type_fastboot(fbn, header, + fastboot_data, len); + } + break; + default: + fastboot_send(fbn, header, "unknown packet type"); + break; + } +} + +static void fastboot_do_work(struct work_struct *w) +{ + struct fastboot_work *fw = container_of(w, struct fastboot_work, work); + struct fastboot_net *fbn = fw->fbn; + + if (fw->download_finished) { + fastboot_download_finished(&fbn->fastboot); + goto out; + } + + fbn->reinit = false; + fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_NONE, ""); + + fbn->send_keep_alive = true; + + fastboot_exec_cmd(&fbn->fastboot, fw->command); + fbn->send_keep_alive = false; +out: + free(fw); +} + +static void fastboot_work_cancel(struct work_struct *w) +{ + struct fastboot_work *fw = container_of(w, struct fastboot_work, work); + + free(fw); +} + +static void fastboot_poll(struct poller_struct *poller) +{ + struct fastboot_net *fbn = container_of(poller, struct fastboot_net, + poller); + + if (fbn->active_download) { + net_poll(); + if (is_timeout(fbn->last_download_pkt, 5 * SECOND)) { + pr_err("No progress for 5s, aborting\n"); + fastboot_net_abort(fbn); + return; + } + } + + if (!fbn->send_keep_alive) + return; + + if (!is_timeout(fbn->host_waits_since, 30ULL * SECOND)) + return; + + if (fbn->may_send != MAY_SEND_MESSAGE) + return; + + fastboot_tx_print(&fbn->fastboot, FASTBOOT_MSG_INFO, "still busy"); +} + +void fastboot_net_free(struct fastboot_net *fbn) +{ + fastboot_generic_close(&fbn->fastboot); + net_unregister(fbn->net_con); + fastboot_generic_free(&fbn->fastboot); + wq_unregister(&fbn->wq); + free(fbn->last_payload); + free(fbn); +} + +struct fastboot_net *fastboot_net_init(struct fastboot_opts *opts) +{ + struct fastboot_net *fbn; + const char *partitions = get_fastboot_partitions(); + bool bbu = get_fastboot_bbu(); + int ret; + + fbn = xzalloc(sizeof(*fbn)); + fbn->fastboot.write = fastboot_write_net; + fbn->fastboot.start_download = fastboot_start_download_net; + + if (opts) { + fbn->fastboot.files = opts->files; + fbn->fastboot.cmd_exec = opts->cmd_exec; + fbn->fastboot.cmd_flash = opts->cmd_flash; + ret = fastboot_generic_init(&fbn->fastboot, opts->export_bbu); + } else { + fbn->fastboot.files = file_list_parse(partitions ? + partitions : ""); + ret = fastboot_generic_init(&fbn->fastboot, bbu); + } + if (ret) + goto fail_generic_init; + + fbn->net_con = net_udp_new(IP_BROADCAST, FASTBOOT_PORT, + fastboot_handler, fbn); + if (IS_ERR(fbn->net_con)) { + ret = PTR_ERR(fbn->net_con); + goto fail_net_con; + } + net_udp_bind(fbn->net_con, FASTBOOT_PORT); + + eth_open(fbn->net_con->edev); + + fbn->poller.func = fastboot_poll; + + fbn->wq.fn = fastboot_do_work; + fbn->wq.cancel = fastboot_work_cancel; + + wq_register(&fbn->wq); + + return fbn; + +fail_net_con: + fastboot_generic_free(&fbn->fastboot); +fail_generic_init: + free(fbn); + return ERR_PTR(ret); +} + +static struct fastboot_net *fastboot_net_obj; +static int fastboot_net_autostart; + +static int fastboot_on_boot(void) +{ + struct fastboot_net *fbn; + + globalvar_add_simple_bool("fastboot.net.autostart", + &fastboot_net_autostart); + + if (!fastboot_net_autostart) + return 0; + + ifup_all(0); + fbn = fastboot_net_init(NULL); + + if (IS_ERR(fbn)) + return PTR_ERR(fbn); + + fastboot_net_obj = fbn; + return 0; +} + +static void fastboot_net_exit(void) +{ + if (fastboot_net_obj) + fastboot_net_free(fastboot_net_obj); +} + +postenvironment_initcall(fastboot_on_boot); +predevshutdown_exitcall(fastboot_net_exit); + +BAREBOX_MAGICVAR_NAMED(global_fastboot_net_autostart, + global.fastboot.net.autostart, + "If true, automatically start fastboot over UDP during startup"); @@ -251,9 +251,59 @@ static int arp_request(struct eth_device *edev, IPaddr_t dest, unsigned char *et void net_poll(void) { + static bool in_net_poll; + + if (in_net_poll) + return; + + in_net_poll = true; + eth_rx(); + + in_net_poll = false; } +static void __net_poll(struct poller_struct *poller) +{ + static uint64_t last; + + /* + * USB network controllers take a long time in the receive path, + * so limit the polling rate to once per 10ms. This is due to + * deficiencies in the barebox USB stack: We can't queue URBs and + * receive a callback when they are done. Instead, we always + * synchronously queue an URB and wait for its completion. In case + * of USB network adapters the only way to detect if packets have + * been received is to queue a RX URB and see if it completes (in + * which case we have received data) or if it timeouts (no data + * available). The timeout can't be arbitrarily small, 2ms is the + * smallest we can do with the 1ms USB frame size. + * + * Given that we do a mixture of polling-as-fast-as-possible when + * we are waiting for network traffic (tftp, nfs and other users + * actively calling net_poll()) and doing a low frequency polling + * here to still get packets when no user is actively waiting for + * incoming packets. This is used to receive incoming ping packets + * and to get fastboot over ethernet going. + */ + if (!is_timeout(last, 10 * MSECOND)) + return; + + net_poll(); + + last = get_time_ns(); +} + +static struct poller_struct net_poller = { + .func = __net_poll, +}; + +static int init_net_poll(void) +{ + return poller_register(&net_poller, "net"); +} +device_initcall(init_net_poll); + static uint16_t net_udp_new_localport(void) { static uint16_t localport; @@ -562,12 +612,54 @@ static int net_handle_udp(unsigned char *pkt, int len) return -EINVAL; } -static int net_handle_icmp(unsigned char *pkt, int len) +static int ping_reply(struct eth_device *edev, unsigned char *pkt, int len) +{ + struct ethernet *et = (struct ethernet *)pkt; + struct icmphdr *icmp; + struct iphdr *ip = (struct iphdr *)(pkt + ETHER_HDR_SIZE); + unsigned char *packet; + int ret; + + memcpy(et->et_dest, et->et_src, 6); + memcpy(et->et_src, edev->ethaddr, 6); + et->et_protlen = htons(PROT_IP); + + icmp = net_eth_to_icmphdr(pkt); + + icmp->type = ICMP_ECHO_REPLY; + icmp->checksum = 0; + icmp->checksum = ~net_checksum((unsigned char *)icmp, + len - sizeof(struct iphdr) - ETHER_HDR_SIZE); + ip->check = 0; + ip->frag_off = 0; + net_copy_ip((void *)&ip->daddr, &ip->saddr); + net_copy_ip((void *)&ip->saddr, &edev->ipaddr); + ip->check = ~net_checksum((unsigned char *)ip, sizeof(struct iphdr)); + + packet = net_alloc_packet(); + if (!packet) + return 0; + + memcpy(packet, pkt, ETHER_HDR_SIZE + len); + + ret = eth_send(edev, packet, ETHER_HDR_SIZE + len); + + free(packet); + + return 0; +} + +static int net_handle_icmp(struct eth_device *edev, unsigned char *pkt, int len) { struct net_connection *con; + struct icmphdr *icmp; pr_debug("%s\n", __func__); + icmp = net_eth_to_icmphdr(pkt); + if (icmp->type == ICMP_ECHO_REQUEST) + ping_reply(edev, pkt, len); + list_for_each_entry(con, &connection_list, list) { if (con->proto == IPPROTO_ICMP) { con->handler(con->priv, pkt, len); @@ -604,7 +696,7 @@ static int net_handle_ip(struct eth_device *edev, unsigned char *pkt, int len) switch (ip->protocol) { case IPPROTO_ICMP: - return net_handle_icmp(pkt, len); + return net_handle_icmp(edev, pkt, len); case IPPROTO_UDP: return net_handle_udp(pkt, len); } |