summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-07-27 09:07:51 -0600
committerTom Rini <[email protected]>2026-07-27 10:52:25 -0600
commit7ffa20af30dd0a39c2c15eb81b03ba42fab1ea09 (patch)
tree2ddf8237b605f3e26b43e8cff5c9a5d933af6a61
parent5c5f1e1de1852f3bc982aa19435e581e39dc775d (diff)
parent0ccf0193a58120f7a898a4379434b481be41b9f6 (diff)
Merge patch series "fs: regression-safe load <iface> for null_dev_desc_ok fstypes"
Vincent Jardin <[email protected]> says: 3 commits providing documentation of impacts and testing the dispatch for null_dev_desc_ok fstypes (semihosting, ubifs, sandbox) in the generic `load <iface> ...` command. The test does not cover ubifs, I could not make it work with qemu. Since the code logic is there and testing with semihost is done, it should cover the needed cases. Link: https://lore.kernel.org/r/[email protected]
-rw-r--r--doc/usage/cmd/load.rst33
-rw-r--r--fs/fs.c42
-rw-r--r--test/py/tests/test_load_sandbox.py51
-rw-r--r--test/py/tests/test_semihosting/conftest.py4
-rw-r--r--test/py/tests/test_semihosting/test_load_semihosting.py38
5 files changed, 166 insertions, 2 deletions
diff --git a/doc/usage/cmd/load.rst b/doc/usage/cmd/load.rst
index bfa45c6f36c..bf2fffbed21 100644
--- a/doc/usage/cmd/load.rst
+++ b/doc/usage/cmd/load.rst
@@ -63,6 +63,39 @@ Example
16 bytes read in 1 ms (15.6 KiB/s)
=>
+Null-block-device interfaces
+----------------------------
+
+A few ``<interface>`` values have no underlying block device. Their
+filesystem implementations directly call a back-end (JTAG
+debugger, UBI volume, host running U-Boot under sandbox, ...) and
+ignore the ``<dev[:part]>`` field, which may be given as ``-``. So
+``load <iface> - <addr> <filename>`` works.
+
+semihosting
+ Read files from the host filesystem of an attached JTAG debugger
+ using the ARM semihosting protocol. Useful with OpenOCD.
+ Built when ``CONFIG_SEMIHOSTING=y``.
+
+ubifs
+ Read files from a UBIFS volume that has already been attached
+ and mounted with the ``ubi part`` + ``ubifsmount`` commands.
+ Built when ``CONFIG_CMD_UBIFS=y``.
+
+sandbox
+ Read files from the host filesystem the sandbox binary is
+ running under. Available on sandbox builds.
+
+The ``<dev[:part]>`` argument is conventionally written as ``-`` for
+these interfaces, to make it visible at the call site that the field
+is unused. The filesystem layer never looks at it.
+
+Example::
+
+ => load semihosting - ${kernel_addr_r} kernel.itb
+ 9437184 bytes read in 412 ms (21.8 MiB/s)
+ =>
+
Configuration
-------------
diff --git a/fs/fs.c b/fs/fs.c
index 1ebda6f4ee2..2824c7defa2 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -461,11 +461,53 @@ const char *fs_get_type_name(void)
return fs_get_info(fs_type)->name;
}
+/*
+ * Some fstypes (semihosting, ubifs) have no underlying block device
+ * and ignore the block_desc argument of their probe hook. The legacy
+ * commands (ubifsload, semihosting via env macros) just pass NULL;
+ * for "load <iface> ..." to behave the same, the dispatcher opts
+ * those fstypes in by name here, before any block-device lookup is
+ * attempted.
+ *
+ * Returns the matching fstype_info if @ifname names a fstype that
+ * opts into null_dev_desc_ok dispatch and the caller's @fstype filter
+ * permits it. Returns NULL otherwise.
+ */
+static struct fstype_info *fs_lookup_null_dev_info(const char *ifname,
+ int fstype)
+{
+ struct fstype_info *info;
+ int i;
+
+ for (i = 0, info = fstypes; i < ARRAY_SIZE(fstypes); i++, info++) {
+ if (fstype != FS_TYPE_ANY && info->fstype != FS_TYPE_ANY &&
+ fstype != info->fstype)
+ continue;
+ if (!info->null_dev_desc_ok || !info->name)
+ continue;
+ if (!strcmp(info->name, ifname))
+ return info;
+ }
+
+ return NULL;
+}
+
int fs_set_blk_dev(const char *ifname, const char *dev_part_str, int fstype)
{
struct fstype_info *info;
int part, i;
+ info = fs_lookup_null_dev_info(ifname, fstype);
+ if (info) {
+ fs_dev_desc = NULL;
+ memset(&fs_partition, 0, sizeof(fs_partition));
+ if (!info->probe(NULL, &fs_partition)) {
+ fs_type = info->fstype;
+ fs_dev_part = 0;
+ return 0;
+ }
+ }
+
part = part_get_info_by_dev_and_name_or_num(ifname, dev_part_str, &fs_dev_desc,
&fs_partition, 1);
if (part < 0)
diff --git a/test/py/tests/test_load_sandbox.py b/test/py/tests/test_load_sandbox.py
new file mode 100644
index 00000000000..8d28a630e76
--- /dev/null
+++ b/test/py/tests/test_load_sandbox.py
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2026 Free Mobile - Vincent Jardin
+
+"""Regression test for `load sandbox - <addr> <file>`.
+
+Exercises the null_dev_desc_ok dispatch added in
+"fs: dispatch null_dev_desc_ok filesystems before block lookup".
+
+It is the counterpart of test_load_semihosting.py
+"""
+
+import os
+import pytest
+
+
[email protected](scope='session')
+def sandbox_fixture(u_boot_config):
+ """Host-staged fixture file read by `load sandbox`."""
+ path = os.path.join(u_boot_config.persistent_data_dir,
+ 'sandbox-fstype.txt')
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write('Das U-Boot\n') # 11 bytes, same as test_hostfs.py / semihosting
+ yield path
+ os.remove(path)
+
+
+def test_sandbox_load(ubman, sandbox_fixture):
+ """Run `load sandbox - <addr> <file>` and check the bytes."""
+ response = ubman.run_command(
+ f'load sandbox - $loadaddr {sandbox_fixture}')
+
+ # Fixture is "Das U-Boot\n" (11 bytes).
+ assert '11 bytes read' in response
+
+ # crc32("Das U-Boot\n") -- identical to the semihosting / hostfs checks.
+ response = ubman.run_command('crc32 $loadaddr $filesize')
+ assert '==> 60cfccfc' in response
+
+
+def test_sandbox_load_offset(ubman, sandbox_fixture):
+ """Run the [bytes] [pos] variant through the same dispatch."""
+ response = ubman.run_command(
+ f'load sandbox - $loadaddr {sandbox_fixture} 4 6')
+ # bytes=4 pos=6 over "Das U-Boot\n" -> "Boot".
+ assert '4 bytes read' in response
+
+ # crc32("Boot")
+ response = ubman.run_command('crc32 $loadaddr $filesize')
+ assert '==> e6df01fa' in response
diff --git a/test/py/tests/test_semihosting/conftest.py b/test/py/tests/test_semihosting/conftest.py
index b00d8f4ea9c..6b7f3f3c2d9 100644
--- a/test/py/tests/test_semihosting/conftest.py
+++ b/test/py/tests/test_semihosting/conftest.py
@@ -6,9 +6,9 @@
import os
import pytest
[email protected](scope='session')
[email protected](scope='function')
def semihosting_data(u_boot_config):
- """Set up a file system to be used in semihosting tests
+ """Set up a new file for each semihosting test
Args:
u_boot_config -- U-Boot configuration.
diff --git a/test/py/tests/test_semihosting/test_load_semihosting.py b/test/py/tests/test_semihosting/test_load_semihosting.py
new file mode 100644
index 00000000000..7c2eb72c69a
--- /dev/null
+++ b/test/py/tests/test_semihosting/test_load_semihosting.py
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright 2026 Free Mobile - Vincent Jardin
+
+"""Regression test for `load semihosting - <addr> <file>`.
+
+Companion to test_hostfs.py: same fixture, same crc32, different
+fstype routing:
+see the doc/usage/cmd/load.rst "Null-block-device interfaces" section.
+"""
+
+import pytest
+
+
[email protected]('semihosting')
+def test_semihosting_load(ubman, semihosting_data):
+ """Run `load semihosting - <addr> <file>` and check the bytes."""
+ response = ubman.run_command(
+ f'load semihosting - $loadaddr {semihosting_data}')
+
+ # Fixture is "Das U-Boot\n" (11 bytes).
+ assert '11 bytes read' in response
+
+ # crc32("Das U-Boot\n")
+ response = ubman.run_command('crc32 $loadaddr $filesize')
+ assert '==> 60cfccfc' in response
+
+
[email protected]('semihosting')
+def test_semihosting_load_offset(ubman, semihosting_data):
+ """Run the [bytes] [pos] variant through the same dispatch."""
+ response = ubman.run_command(
+ f'load semihosting - $loadaddr {semihosting_data} 4 6')
+ # bytes=4 pos=6 over "Das U-Boot\n" -> "Boot".
+ assert '4 bytes read' in response
+
+ # crc32("Boot")
+ response = ubman.run_command('crc32 $loadaddr $filesize')
+ assert '==> e6df01fa' in response