summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/devel/background-execution.rst125
-rw-r--r--Documentation/devel/devel.rst14
-rw-r--r--Documentation/index.rst1
-rw-r--r--Documentation/user/usb.rst4
-rw-r--r--commands/Kconfig8
-rw-r--r--commands/usbgadget.c2
-rw-r--r--common/Makefile2
-rw-r--r--common/fastboot.c57
-rw-r--r--common/globalvar.c54
-rw-r--r--common/hush.c6
-rw-r--r--common/poller.c11
-rw-r--r--common/ratp/ratp.c57
-rw-r--r--common/slice.c335
-rw-r--r--common/startup.c3
-rw-r--r--common/usbgadget.c21
-rw-r--r--common/workqueue.c61
-rw-r--r--drivers/net/phy/mdio_bus.c43
-rw-r--r--drivers/net/usb/usbnet.c5
-rw-r--r--drivers/usb/core/usb.c12
-rw-r--r--drivers/usb/gadget/f_fastboot.c54
-rw-r--r--fs/fs.c34
-rw-r--r--include/asm-generic/bug.h13
-rw-r--r--include/fastboot.h20
-rw-r--r--include/fastboot_net.h12
-rw-r--r--include/globalvar.h5
-rw-r--r--include/linux/phy.h38
-rw-r--r--include/net.h10
-rw-r--r--include/ratp_bb.h1
-rw-r--r--include/slice.h46
-rw-r--r--include/usb/usb.h8
-rw-r--r--include/work.h42
-rw-r--r--lib/readline.c6
-rw-r--r--net/Kconfig11
-rw-r--r--net/Makefile1
-rw-r--r--net/eth.c88
-rw-r--r--net/fastboot.c583
-rw-r--r--net/net.c96
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);
}
diff --git a/fs/fs.c b/fs/fs.c
index 5784e9c1f3..f41e4b9b72 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -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
diff --git a/net/eth.c b/net/eth.c
index e3d0d06efe..85110ef695 100644
--- a/net/eth.c
+++ b/net/eth.c
@@ -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");
diff --git a/net/net.c b/net/net.c
index 5fd6246ab2..cfe54ef904 100644
--- a/net/net.c
+++ b/net/net.c
@@ -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);
}