mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-28 23:51:33 +00:00
e2a9edc0ce
To quote the author:
This series fixes an issue where the FAT type (FAT12, FAT16) is not
correctly detected, e.g. when the BPB field BS_FilSysType contains the
valid value "FAT ".
This issue occures, for example, if a partition is formatted by
swupdate using its diskformat handler. swupdate uses the FAT library
from http://elm-chan.org/fsw/ff/ internally.
See https://groups.google.com/g/swupdate/c/7Yc3NupjXx8 for a
discussion in the swupdate mailing list.
Please refer to the commit messages for more details.
1. Added bootsector checks
Most tests from https://www.win.tue.nl/~aeb/linux/fs/fat/fat-2.html
are added in the commit 'fs: fat: add bootsector validity check'.
Only the tests VIII, IX and X are not implemented.
I also checked the Linux kernel code (v6.6) and did not find any
checks on 'vistart->fs_type'. This is the reason why is skipped them
here.
See section '2. Size comparisons' for the impact on the binary size.
2. Size comparisons
I executed bloat-o-meter from the Linux kernel for an arm64
target (config xilinx_zynqmp_mini_emmc0_defconfig):
Comparison of the binary spl/u-boot-spl between master (rev
e17d174773
) and this patch
series (including the added validity checks of the boot sector):
add/remove: 0/0 grow/shrink: 1/1 up/down: 100/-12 (88)
Function old new delta
read_bootsectandvi 308 408 +100
fat_itr_root 444 432 -12
Total: Before=67977, After=68065, chg +0.13%
When compare the size of the binary spl/u-boot-spl between master this
series without the the validity checks of the boot sector:
add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-24 (-24)
Function old new delta
read_bootsectandvi 308 296 -12
fat_itr_root 444 432 -12
Total: Before=67977, After=67953, chg -0.04%
So the size of the spl on this arm64 target increases by 88 bytes for
this series. When i remove the validity check the size decreases by 24 bytes.
442 lines
13 KiB
C
442 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2023 Sean Anderson <seanga2@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 <spl.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 ", 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)
|
|
{
|
|
spl_fat_force_reregister();
|
|
return spl_test_fs(uts, __func__, create_fat);
|
|
}
|
|
SPL_TEST(spl_test_fat, DM_FLAGS);
|
|
|
|
static bool spl_mmc_raw;
|
|
|
|
u32 spl_mmc_boot_mode(struct mmc *mmc, const u32 boot_device)
|
|
{
|
|
return spl_mmc_raw ? MMCSD_MODE_RAW : MMCSD_MODE_FS;
|
|
}
|
|
|
|
static int spl_test_mmc_fs(struct unit_test_state *uts, const char *test_name,
|
|
enum spl_test_image type, create_fs_t create_fs,
|
|
bool blk_mode)
|
|
{
|
|
const char *filename = CONFIG_SPL_FS_LOAD_PAYLOAD_NAME;
|
|
struct blk_desc *dev_desc;
|
|
size_t fs_size, fs_data, img_size, img_data,
|
|
plain_size = SPL_TEST_DATA_SIZE;
|
|
struct spl_image_info info_write = {
|
|
.name = test_name,
|
|
.size = type == LEGACY_LZMA ? lzma_compressed_size :
|
|
plain_size,
|
|
}, info_read = { };
|
|
struct disk_partition part = {
|
|
.start = 1,
|
|
.sys_ind = 0x83,
|
|
};
|
|
struct spl_image_loader *loader =
|
|
SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_MMC1, spl_mmc_load_image);
|
|
struct spl_boot_device bootdev = {
|
|
.boot_device = loader->boot_device,
|
|
};
|
|
void *fs;
|
|
char *data, *plain;
|
|
|
|
img_size = create_image(NULL, type, &info_write, &img_data);
|
|
ut_assert(img_size);
|
|
fs_size = create_fs(NULL, img_size, filename, &fs_data);
|
|
ut_assert(fs_size);
|
|
fs = calloc(fs_size, 1);
|
|
ut_assertnonnull(fs);
|
|
|
|
data = fs + fs_data + img_data;
|
|
if (type == LEGACY_LZMA) {
|
|
plain = malloc(plain_size);
|
|
ut_assertnonnull(plain);
|
|
generate_data(plain, plain_size, "lzma");
|
|
memcpy(data, lzma_compressed, lzma_compressed_size);
|
|
} else {
|
|
plain = data;
|
|
generate_data(plain, plain_size, test_name);
|
|
}
|
|
ut_asserteq(img_size, create_image(fs + fs_data, type, &info_write,
|
|
NULL));
|
|
ut_asserteq(fs_size, create_fs(fs, img_size, filename, NULL));
|
|
|
|
dev_desc = blk_get_devnum_by_uclass_id(UCLASS_MMC, 0);
|
|
ut_assertnonnull(dev_desc);
|
|
|
|
ut_asserteq(512, dev_desc->blksz);
|
|
part.size = fs_size / dev_desc->blksz;
|
|
ut_assertok(write_mbr_partitions(dev_desc, &part, 1, 0));
|
|
ut_asserteq(part.size, blk_dwrite(dev_desc, part.start, part.size, fs));
|
|
|
|
spl_mmc_raw = false;
|
|
if (blk_mode)
|
|
ut_assertok(spl_blk_load_image(&info_read, &bootdev, UCLASS_MMC,
|
|
0, 1));
|
|
else
|
|
ut_assertok(loader->load_image(&info_read, &bootdev));
|
|
if (check_image_info(uts, &info_write, &info_read))
|
|
return CMD_RET_FAILURE;
|
|
if (type == LEGACY_LZMA)
|
|
ut_asserteq(plain_size, info_read.size);
|
|
ut_asserteq_mem(plain, phys_to_virt(info_write.load_addr), plain_size);
|
|
|
|
if (type == LEGACY_LZMA)
|
|
free(plain);
|
|
free(fs);
|
|
return 0;
|
|
}
|
|
|
|
static int spl_test_blk(struct unit_test_state *uts, const char *test_name,
|
|
enum spl_test_image type)
|
|
{
|
|
spl_fat_force_reregister();
|
|
if (spl_test_mmc_fs(uts, test_name, type, create_fat, true))
|
|
return CMD_RET_FAILURE;
|
|
|
|
return spl_test_mmc_fs(uts, test_name, type, create_ext2, true);
|
|
}
|
|
SPL_IMG_TEST(spl_test_blk, LEGACY, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_blk, LEGACY_LZMA, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_blk, IMX8, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_blk, FIT_EXTERNAL, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_blk, FIT_INTERNAL, DM_FLAGS);
|
|
|
|
static int spl_test_mmc_write_image(struct unit_test_state *uts, void *img,
|
|
size_t img_size)
|
|
{
|
|
struct blk_desc *dev_desc;
|
|
size_t img_blocks;
|
|
|
|
dev_desc = blk_get_devnum_by_uclass_id(UCLASS_MMC, 0);
|
|
ut_assertnonnull(dev_desc);
|
|
|
|
img_blocks = DIV_ROUND_UP(img_size, dev_desc->blksz);
|
|
ut_asserteq(img_blocks, blk_dwrite(dev_desc,
|
|
CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR,
|
|
img_blocks, img));
|
|
|
|
spl_mmc_raw = true;
|
|
return 0;
|
|
}
|
|
|
|
static int spl_test_mmc(struct unit_test_state *uts, const char *test_name,
|
|
enum spl_test_image type)
|
|
{
|
|
spl_mmc_clear_cache();
|
|
spl_fat_force_reregister();
|
|
|
|
if (spl_test_mmc_fs(uts, test_name, type, create_ext2, false))
|
|
return CMD_RET_FAILURE;
|
|
|
|
if (spl_test_mmc_fs(uts, test_name, type, create_fat, false))
|
|
return CMD_RET_FAILURE;
|
|
|
|
return do_spl_test_load(uts, test_name, type,
|
|
SPL_LOAD_IMAGE_GET(0, BOOT_DEVICE_MMC1,
|
|
spl_mmc_load_image),
|
|
spl_test_mmc_write_image);
|
|
}
|
|
SPL_IMG_TEST(spl_test_mmc, LEGACY, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_mmc, LEGACY_LZMA, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_mmc, IMX8, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_mmc, FIT_EXTERNAL, DM_FLAGS);
|
|
SPL_IMG_TEST(spl_test_mmc, FIT_INTERNAL, DM_FLAGS);
|