[PATCH 21/26] test: spl: Add functions to create filesystems
Sean Anderson
seanga2 at gmail.com
Thu Oct 12 06:11:30 CEST 2023
On 10/11/23 23:41, Simon Glass wrote:
> Hi Sean,
>
> On Wed, 11 Oct 2023 at 18:56, Sean Anderson <seanga2 at gmail.com> wrote:
>>
>> Add some functions for creating fat/ext2 filesystems with a single file and
>> a test for them. Filesystems require block devices, and it is easiest to
>> just use MMC for this. To get an MMC, we must also pull in the test device
>> tree.
>>
>> Signed-off-by: Sean Anderson <seanga2 at gmail.com>
>> ---
>>
>> arch/sandbox/cpu/start.c | 9 +-
>> configs/sandbox_noinst_defconfig | 7 +
>> include/ext4fs.h | 1 +
>> include/ext_common.h | 14 ++
>> include/test/spl.h | 3 +
>> test/image/Kconfig | 11 ++
>> test/image/Makefile | 1 +
>> test/image/spl_load.c | 1 +
>> test/image/spl_load_fs.c | 305 +++++++++++++++++++++++++++++++
>> 9 files changed, 350 insertions(+), 2 deletions(-)
>> create mode 100644 test/image/spl_load_fs.c
>
> Reviewed-by: Simon Glass <sjg at chromium.org>
>
> Wow there is a lot going on in this patch. It might be good to split it.
OK.
>>
>> diff --git a/arch/sandbox/cpu/start.c b/arch/sandbox/cpu/start.c
>> index 2c8a72590b5..f5728e6e7ee 100644
>> --- a/arch/sandbox/cpu/start.c
>> +++ b/arch/sandbox/cpu/start.c
>> @@ -13,6 +13,7 @@
>> #include <log.h>
>> #include <os.h>
>> #include <sort.h>
>> +#include <spl.h>
>> #include <asm/getopt.h>
>> #include <asm/global_data.h>
>> #include <asm/io.h>
>> @@ -202,10 +203,14 @@ static int sandbox_cmdline_cb_test_fdt(struct sandbox_state *state,
>> {
>> char buf[256];
>> char *fname;
>> + char *relname;
>> int len;
>>
>> - len = state_get_rel_filename("arch/sandbox/dts/test.dtb", buf,
>> - sizeof(buf));
>> + if (spl_phase() < PHASE_BOARD_F)
>
> I think <= PHASE_SPL is better since you care about whether it is in
> SPL or not. I did send a patch to check for SPL, but I don't think it
> is merged yet.
Fine by me.
>> + relname = "../arch/sandbox/dts/test.dtb";
>> + else
>> + relname = "arch/sandbox/dts/test.dtb";
>> + len = state_get_rel_filename(relname, buf, sizeof(buf));
>> if (len < 0)
>> return len;
>>
>> diff --git a/configs/sandbox_noinst_defconfig b/configs/sandbox_noinst_defconfig
>> index 908155be8a3..0a542cfb6aa 100644
>> --- a/configs/sandbox_noinst_defconfig
>> +++ b/configs/sandbox_noinst_defconfig
>> @@ -6,10 +6,12 @@ CONFIG_NR_DRAM_BANKS=1
>> CONFIG_ENV_SIZE=0x2000
>> CONFIG_DEFAULT_DEVICE_TREE="sandbox"
>> CONFIG_DM_RESET=y
>> +CONFIG_SPL_MMC=y
>> CONFIG_SPL_SERIAL=y
>> CONFIG_SPL_DRIVERS_MISC=y
>> CONFIG_SPL_SYS_MALLOC_F_LEN=0x8000
>> CONFIG_SPL=y
>> +CONFIG_SPL_FS_FAT=y
>> CONFIG_SYS_LOAD_ADDR=0x0
>> CONFIG_PCI=y
>> CONFIG_SANDBOX_SPL=y
>> @@ -39,7 +41,9 @@ CONFIG_SPL_HAS_CUSTOM_MALLOC_START=y
>> CONFIG_SPL_CUSTOM_SYS_MALLOC_ADDR=0xa000000
>> CONFIG_SPL_SYS_MALLOC_SIZE=0x4000000
>> CONFIG_SPL_ENV_SUPPORT=y
>> +CONFIG_SPL_FS_EXT4=y
>> CONFIG_SPL_I2C=y
>> +CONFIG_SPL_MMC_WRITE=y
>> CONFIG_SPL_RTC=y
>> CONFIG_CMD_CPU=y
>> CONFIG_CMD_LICENSE=y
>> @@ -97,6 +101,7 @@ CONFIG_AMIGA_PARTITION=y
>> CONFIG_OF_CONTROL=y
>> CONFIG_SPL_OF_CONTROL=y
>> CONFIG_SPL_OF_PLATDATA=y
>> +CONFIG_SPL_OF_REAL=y
>> CONFIG_ENV_IS_NOWHERE=y
>> CONFIG_ENV_IS_IN_EXT4=y
>> CONFIG_ENV_EXT4_INTERFACE="host"
>> @@ -159,6 +164,7 @@ CONFIG_CROS_EC_SPI=y
>> CONFIG_P2SB=y
>> CONFIG_PWRSEQ=y
>> CONFIG_SPL_PWRSEQ=y
>> +CONFIG_FS_LOADER=y
>> CONFIG_MMC_SANDBOX=y
>> CONFIG_SPI_FLASH_SANDBOX=y
>> CONFIG_SPI_FLASH_ATMEL=y
>> @@ -220,6 +226,7 @@ CONFIG_SYSRESET=y
>> CONFIG_SPL_SYSRESET=y
>> CONFIG_DM_THERMAL=y
>> CONFIG_TIMER=y
>> +CONFIG_SPL_TIMER=y
>> CONFIG_TIMER_EARLY=y
>> CONFIG_SANDBOX_TIMER=y
>> CONFIG_USB=y
>
> I think these would be better in their own patch
Surprisingly, MMC_WRITE actually depends on SPL_TIMER. I didn't check on the exact
dependency, but presumably there is some poll/timeout loop in the MMC write code.
>> diff --git a/include/ext4fs.h b/include/ext4fs.h
>> index cb5d9cc0a5c..dd66d27f776 100644
>> --- a/include/ext4fs.h
>> +++ b/include/ext4fs.h
>> @@ -31,6 +31,7 @@
>> struct disk_partition;
>>
>> #define EXT4_INDEX_FL 0x00001000 /* Inode uses hash tree index */
>> +#define EXT4_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/
>> #define EXT4_EXTENTS_FL 0x00080000 /* Inode uses extents */
>> #define EXT4_EXT_MAGIC 0xf30a
>> #define EXT4_FEATURE_RO_COMPAT_GDT_CSUM 0x0010
>> diff --git a/include/ext_common.h b/include/ext_common.h
>> index 30a0c248414..b09bbde116a 100644
>> --- a/include/ext_common.h
>> +++ b/include/ext_common.h
>> @@ -35,6 +35,16 @@ struct cmd_tbl;
>> #define EXT2_PATH_MAX 4096
>> /* Maximum nesting of symlinks, used to prevent a loop. */
>> #define EXT2_MAX_SYMLINKCNT 8
>> +/* Maximum file name length */
>> +#define EXT2_NAME_LEN 255
>> +
>> +/*
>> + * Revision levels
>> + */
>> +#define EXT2_GOOD_OLD_REV 0 /* The good old (original) format */
>> +#define EXT2_DYNAMIC_REV 1 /* V2 format w/ dynamic inode sizes */
>> +
>> +#define EXT2_GOOD_OLD_INODE_SIZE 128
>>
>> /* Filetype used in directory entry. */
>> #define FILETYPE_UNKNOWN 0
>> @@ -48,6 +58,10 @@ struct cmd_tbl;
>> #define FILETYPE_INO_DIRECTORY 0040000
>> #define FILETYPE_INO_SYMLINK 0120000
>> #define EXT2_ROOT_INO 2 /* Root inode */
>> +#define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */
>> +
>> +/* First non-reserved inode for old ext2 filesystems */
>> +#define EXT2_GOOD_OLD_FIRST_INO 11
>>
>> /* The size of an ext2 block in bytes. */
>> #define EXT2_BLOCK_SIZE(data) (1 << LOG2_BLOCK_SIZE(data))
>
> Again I think these ext2 changes would be better in their own patch
OK.
FWIW These are really only used by this patch.
--Sean
>> diff --git a/include/test/spl.h b/include/test/spl.h
>> index a2f8d77b88f..7ae32a1020b 100644
>> --- a/include/test/spl.h
>> +++ b/include/test/spl.h
>> @@ -114,4 +114,7 @@ SPL_TEST(func##_##type, flags)
>> /* More than a couple blocks, and will not be aligned to anything */
>> #define SPL_TEST_DATA_SIZE 4099
>>
>> +/* Flags necessary for accessing DM devices */
>> +#define DM_FLAGS (UT_TESTF_DM | UT_TESTF_SCAN_FDT)
>> +
>> #endif /* TEST_SPL_H */
>> diff --git a/test/image/Kconfig b/test/image/Kconfig
>> index 70ffe0ff276..963c86cc290 100644
>> --- a/test/image/Kconfig
>> +++ b/test/image/Kconfig
>> @@ -10,6 +10,17 @@ config SPL_UT_LOAD
>>
>> if SPL_UT_LOAD
>>
>> +config SPL_UT_LOAD_FS
>> + bool "Unit tests for filesystems"
>> + depends on SANDBOX && SPL_OF_REAL
>> + depends on FS_LOADER
>> + depends on SPL_FS_FAT
>> + depends on SPL_FS_EXT4
>> + depends on SPL_MMC_WRITE
>> + default y
>> + help
>> + Test filesystems in SPL.
>> +
>> config SPL_UT_LOAD_OS
>> bool "Test loading from the host OS"
>> depends on SANDBOX && SPL_LOAD_FIT
>> diff --git a/test/image/Makefile b/test/image/Makefile
>> index 1f62d54453c..9427e69bd3b 100644
>> --- a/test/image/Makefile
>> +++ b/test/image/Makefile
>> @@ -3,4 +3,5 @@
>> # Copyright 2021 Google LLC
>>
>> obj-$(CONFIG_SPL_UT_LOAD) += spl_load.o
>> +obj-$(CONFIG_SPL_UT_LOAD_FS) += spl_load_fs.o
>> obj-$(CONFIG_SPL_UT_LOAD_OS) += spl_load_os.o
>> diff --git a/test/image/spl_load.c b/test/image/spl_load.c
>> index ca3777cab37..4338da417ce 100644
>> --- a/test/image/spl_load.c
>> +++ b/test/image/spl_load.c
>> @@ -4,6 +4,7 @@
>> */
>>
>> #include <common.h>
>> +#include <dm.h>
>> #include <image.h>
>> #include <imx_container.h>
>> #include <mapmem.h>
>> diff --git a/test/image/spl_load_fs.c b/test/image/spl_load_fs.c
>> new file mode 100644
>> index 00000000000..8cd90b73518
>> --- /dev/null
>> +++ b/test/image/spl_load_fs.c
>> @@ -0,0 +1,305 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright (C) 2023 Sean Anderson <seanga2 at gmail.com>
>> + */
>> +
>> +#include <common.h>
>> +#include <blk.h>
>> +#include <ext_common.h>
>> +#include <ext4fs.h>
>> +#include <fat.h>
>> +#include <fs.h>
>> +#include <memalign.h>
>> +#include <asm/io.h>
>> +#include <linux/stat.h>
>> +#include <test/spl.h>
>> +#include <test/ut.h>
>> +
>> +/**
>> + * create_ext2() - Create an "ext2" filesystem with a single file
>> + * @dst: The location of the new filesystem; MUST be zeroed
>> + * @size: The size of the file
>> + * @filename: The name of the file
>> + * @data_offset: Filled with the offset of the file data from @dst
>> + *
>> + * Budget mke2fs. We use 1k blocks (to reduce overhead) with a single block
>> + * group, which limits us to 8M of data. Almost every feature which increases
>> + * complexity (checksums, hash tree directories, etc.) is disabled. We do cheat
>> + * a little and use extents from ext4 to save having to deal with indirects, but
>> + * U-Boot doesn't care.
>> + *
>> + * If @dst is %NULL, nothing is copied.
>> + *
>> + * Return: The size of the filesystem in bytes
>> + */
>> +static size_t create_ext2(void *dst, size_t size, const char *filename,
>> + size_t *data_offset)
>> +{
>> + u32 super_block = 1;
>> + u32 group_block = 2;
>> + u32 block_bitmap_block = 3;
>> + u32 inode_bitmap_block = 4;
>> + u32 inode_table_block = 5;
>> + u32 root_block = 6;
>> + u32 file_block = 7;
>> +
>> + u32 root_ino = EXT2_ROOT_INO;
>> + u32 file_ino = EXT2_BOOT_LOADER_INO;
>> +
>> + u32 block_size = EXT2_MIN_BLOCK_SIZE;
>> + u32 inode_size = sizeof(struct ext2_inode);
>> +
>> + u32 file_blocks = (size + block_size - 1) / block_size;
>> + u32 blocks = file_block + file_blocks;
>> + u32 inodes = block_size / inode_size;
>> + u32 filename_len = strlen(filename);
>> + u32 dirent_len = ALIGN(filename_len, sizeof(struct ext2_dirent)) +
>> + sizeof(struct ext2_dirent);
>> +
>> + struct ext2_sblock *sblock = dst + super_block * block_size;
>> + struct ext2_block_group *bg = dst + group_block * block_size;
>> + struct ext2_inode *inode_table = dst + inode_table_block * block_size;
>> + struct ext2_inode *root_inode = &inode_table[root_ino - 1];
>> + struct ext2_inode *file_inode = &inode_table[file_ino - 1];
>> + struct ext4_extent_header *ext_block = (void *)&file_inode->b;
>> + struct ext4_extent *extent = (void *)(ext_block + 1);
>> + struct ext2_dirent *dot = dst + root_block * block_size;
>> + struct ext2_dirent *dotdot = dot + 2;
>> + struct ext2_dirent *dirent = dotdot + 2;
>> + struct ext2_dirent *last = ((void *)dirent) + dirent_len;
>> +
>> + /* Make sure we fit in one block group */
>> + if (blocks > block_size * 8)
>> + return 0;
>> +
>> + if (filename_len > EXT2_NAME_LEN)
>> + return 0;
>> +
>> + if (data_offset)
>> + *data_offset = file_block * block_size;
>> +
>> + if (!dst)
>> + goto out;
>> +
>> + sblock->total_inodes = cpu_to_le32(inodes);
>> + sblock->total_blocks = cpu_to_le32(blocks);
>> + sblock->first_data_block = cpu_to_le32(super_block);
>> + sblock->blocks_per_group = cpu_to_le32(blocks);
>> + sblock->fragments_per_group = cpu_to_le32(blocks);
>> + sblock->inodes_per_group = cpu_to_le32(inodes);
>> + sblock->magic = cpu_to_le16(EXT2_MAGIC);
>> + /* Done mostly so we can pretend to be (in)compatible */
>> + sblock->revision_level = cpu_to_le32(EXT2_DYNAMIC_REV);
>> + /* Not really accurate but it doesn't matter */
>> + sblock->first_inode = cpu_to_le32(EXT2_GOOD_OLD_FIRST_INO);
>> + sblock->inode_size = cpu_to_le32(inode_size);
>> + sblock->feature_incompat = cpu_to_le32(EXT4_FEATURE_INCOMPAT_EXTENTS);
>> +
>> + bg->block_id = cpu_to_le32(block_bitmap_block);
>> + bg->inode_id = cpu_to_le32(inode_bitmap_block);
>> + bg->inode_table_id = cpu_to_le32(inode_table_block);
>> +
>> + /*
>> + * All blocks/inodes are in-use. I don't want to have to deal with
>> + * endianness, so just fill everything in.
>> + */
>> + memset(dst + block_bitmap_block * block_size, 0xff, block_size * 2);
>> +
>> + root_inode->mode = cpu_to_le16(S_IFDIR | 0755);
>> + root_inode->size = cpu_to_le32(block_size);
>> + root_inode->nlinks = cpu_to_le16(3);
>> + root_inode->blockcnt = cpu_to_le32(1);
>> + root_inode->flags = cpu_to_le32(EXT4_TOPDIR_FL);
>> + root_inode->b.blocks.dir_blocks[0] = root_block;
>> +
>> + file_inode->mode = cpu_to_le16(S_IFREG | 0644);
>> + file_inode->size = cpu_to_le32(size);
>> + file_inode->nlinks = cpu_to_le16(1);
>> + file_inode->blockcnt = cpu_to_le32(file_blocks);
>> + file_inode->flags = cpu_to_le32(EXT4_EXTENTS_FL);
>> + ext_block->eh_magic = cpu_to_le16(EXT4_EXT_MAGIC);
>> + ext_block->eh_entries = cpu_to_le16(1);
>> + ext_block->eh_max = cpu_to_le16(sizeof(file_inode->b) /
>> + sizeof(*ext_block) - 1);
>> + extent->ee_len = cpu_to_le16(file_blocks);
>> + extent->ee_start_lo = cpu_to_le16(file_block);
>> +
>> + /* I'm not sure we need these, but it can't hurt */
>> + dot->inode = cpu_to_le32(root_ino);
>> + dot->direntlen = cpu_to_le16(2 * sizeof(*dot));
>> + dot->namelen = 1;
>> + dot->filetype = FILETYPE_DIRECTORY;
>> + memcpy(dot + 1, ".", dot->namelen);
>> +
>> + dotdot->inode = cpu_to_le32(root_ino);
>> + dotdot->direntlen = cpu_to_le16(2 * sizeof(*dotdot));
>> + dotdot->namelen = 2;
>> + dotdot->filetype = FILETYPE_DIRECTORY;
>> + memcpy(dotdot + 1, "..", dotdot->namelen);
>> +
>> + dirent->inode = cpu_to_le32(file_ino);
>> + dirent->direntlen = cpu_to_le16(dirent_len);
>> + dirent->namelen = filename_len;
>> + dirent->filetype = FILETYPE_REG;
>> + memcpy(dirent + 1, filename, filename_len);
>> +
>> + last->direntlen = block_size - dirent_len;
>> +
>> +out:
>> + return (size_t)blocks * block_size;
>> +}
>> +
>> +/**
>> + * create_fat() - Create a FAT32 filesystem with a single file
>> + * @dst: The location of the new filesystem; MUST be zeroed
>> + * @size: The size of the file
>> + * @filename: The name of the file
>> + * @data_offset: Filled with the offset of the file data from @dst
>> + *
>> + * Budget mkfs.fat. We use FAT32 (so I don't have to deal with FAT12) with no
>> + * info sector, and a single one-sector FAT. This limits us to 64k of data
>> + * (enough for anyone). The filename must fit in 8.3.
>> + *
>> + * If @dst is %NULL, nothing is copied.
>> + *
>> + * Return: The size of the filesystem in bytes
>> + */
>> +static size_t create_fat(void *dst, size_t size, const char *filename,
>> + size_t *data_offset)
>> +{
>> + u16 boot_sector = 0;
>> + u16 fat_sector = 1;
>> + u32 root_sector = 2;
>> + u32 file_sector = 3;
>> +
>> + u16 sector_size = 512;
>> + u32 file_sectors = (size + sector_size - 1) / sector_size;
>> + u32 sectors = file_sector + file_sectors;
>> +
>> + char *ext;
>> + size_t filename_len, ext_len;
>> + int i;
>> +
>> + struct boot_sector *bs = dst + boot_sector * sector_size;
>> + struct volume_info *vi = (void *)(bs + 1);
>> + __le32 *fat = dst + fat_sector * sector_size;
>> + struct dir_entry *dirent = dst + root_sector * sector_size;
>> +
>> + /* Make sure we fit in the FAT */
>> + if (sectors > sector_size / sizeof(u32))
>> + return 0;
>> +
>> + ext = strchr(filename, '.');
>> + if (ext) {
>> + filename_len = ext - filename;
>> + ext++;
>> + ext_len = strlen(ext);
>> + } else {
>> + filename_len = strlen(filename);
>> + ext_len = 0;
>> + }
>> +
>> + if (filename_len > 8 || ext_len > 3)
>> + return 0;
>> +
>> + if (data_offset)
>> + *data_offset = file_sector * sector_size;
>> +
>> + if (!dst)
>> + goto out;
>> +
>> + bs->sector_size[0] = sector_size & 0xff;
>> + bs->sector_size[1] = sector_size >> 8;
>> + bs->cluster_size = 1;
>> + bs->reserved = cpu_to_le16(fat_sector);
>> + bs->fats = 1;
>> + bs->media = 0xf8;
>> + bs->total_sect = cpu_to_le32(sectors);
>> + bs->fat32_length = cpu_to_le32(1);
>> + bs->root_cluster = cpu_to_le32(root_sector);
>> +
>> + vi->ext_boot_sign = 0x29;
>> + memcpy(vi->fs_type, FAT32_SIGN, sizeof(vi->fs_type));
>> +
>> + memcpy(dst + 0x1fe, "\x55\xAA", 2);
>> +
>> + fat[0] = cpu_to_le32(0x0ffffff8);
>> + fat[1] = cpu_to_le32(0x0fffffff);
>> + fat[2] = cpu_to_le32(0x0ffffff8);
>> + for (i = file_sector; file_sectors > 1; file_sectors--, i++)
>> + fat[i] = cpu_to_le32(i + 1);
>> + fat[i] = cpu_to_le32(0x0ffffff8);
>> +
>> + for (i = 0; i < sizeof(dirent->nameext.name); i++) {
>> + if (i < filename_len)
>> + dirent->nameext.name[i] = toupper(filename[i]);
>> + else
>> + dirent->nameext.name[i] = ' ';
>> + }
>> +
>> + for (i = 0; i < sizeof(dirent->nameext.ext); i++) {
>> + if (i < ext_len)
>> + dirent->nameext.ext[i] = toupper(ext[i]);
>> + else
>> + dirent->nameext.ext[i] = ' ';
>> + }
>> +
>> + dirent->start = cpu_to_le16(file_sector);
>> + dirent->size = cpu_to_le32(size);
>> +
>> +out:
>> + return sectors * sector_size;
>> +}
>> +
>> +typedef size_t (*create_fs_t)(void *, size_t, const char *, size_t *);
>> +
>> +static int spl_test_fs(struct unit_test_state *uts, const char *test_name,
>> + create_fs_t create)
>> +{
>> + const char *filename = CONFIG_SPL_FS_LOAD_PAYLOAD_NAME;
>> + struct blk_desc *dev_desc;
>> + char *data_write, *data_read;
>> + void *fs;
>> + size_t fs_size, fs_data, fs_blocks, data_size = SPL_TEST_DATA_SIZE;
>> + loff_t actread;
>> +
>> + fs_size = create(NULL, data_size, filename, &fs_data);
>> + ut_assert(fs_size);
>> + fs = calloc(fs_size, 1);
>> + ut_assertnonnull(fs);
>> +
>> + data_write = fs + fs_data;
>> + generate_data(data_write, data_size, test_name);
>> + ut_asserteq(fs_size, create(fs, data_size, filename, NULL));
>> +
>> + dev_desc = blk_get_devnum_by_uclass_id(UCLASS_MMC, 0);
>> + ut_assertnonnull(dev_desc);
>> + ut_asserteq(512, dev_desc->blksz);
>> + fs_blocks = fs_size / dev_desc->blksz;
>> + ut_asserteq(fs_blocks, blk_dwrite(dev_desc, 0, fs_blocks, fs));
>> +
>> + /* We have to use malloc so we can call virt_to_phys */
>> + data_read = malloc_cache_aligned(data_size);
>> + ut_assertnonnull(data_read);
>> + ut_assertok(fs_set_blk_dev_with_part(dev_desc, 0));
>> + ut_assertok(fs_read("/" CONFIG_SPL_FS_LOAD_PAYLOAD_NAME,
>> + virt_to_phys(data_read), 0, data_size, &actread));
>> + ut_asserteq(data_size, actread);
>> + ut_asserteq_mem(data_write, data_read, data_size);
>> +
>> + free(data_read);
>> + free(fs);
>> + return 0;
>> +}
>> +
>> +static int spl_test_ext(struct unit_test_state *uts)
>> +{
>> + return spl_test_fs(uts, __func__, create_ext2);
>> +}
>> +SPL_TEST(spl_test_ext, DM_FLAGS);
>> +
>> +static int spl_test_fat(struct unit_test_state *uts)
>> +{
>> + return spl_test_fs(uts, __func__, create_fat);
>> +}
>> +SPL_TEST(spl_test_fat, DM_FLAGS);
>> --
>> 2.37.1
>>
>
> Regards,
> Simon
More information about the U-Boot
mailing list