[PATCH 21/26] test: spl: Add functions to create filesystems
Simon Glass
sjg at chromium.org
Thu Oct 12 05:41:15 CEST 2023
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.
>
> 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.
> + 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
> 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
> 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