summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/fat/Kconfig8
-rw-r--r--fs/fat/fat.c152
-rw-r--r--fs/fat/fat_write.c2
3 files changed, 157 insertions, 5 deletions
diff --git a/fs/fat/Kconfig b/fs/fat/Kconfig
index 19d52238713..9606fa48bbe 100644
--- a/fs/fat/Kconfig
+++ b/fs/fat/Kconfig
@@ -29,3 +29,11 @@ config FS_FAT_MAX_CLUSTSIZE
is the smallest amount of disk space that can be used to hold a
file. Unless you have an extremely tight memory memory constraints,
leave the default.
+
+config FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH
+ bool "Handle FAT sector size mismatch"
+ default n
+ depends on FS_FAT
+ help
+ Handle filesystems on media where the hardware block size and
+ the sector size in the FAT metadata do not match.
diff --git a/fs/fat/fat.c b/fs/fat/fat.c
index 9ce5df59f9b..85b511f75af 100644
--- a/fs/fat/fat.c
+++ b/fs/fat/fat.c
@@ -45,11 +45,146 @@ static void downcase(char *str, size_t len)
static struct blk_desc *cur_dev;
static struct disk_partition cur_part_info;
+static int fat_sect_size;
#define DOS_BOOT_MAGIC_OFFSET 0x1fe
#define DOS_FS_TYPE_OFFSET 0x36
#define DOS_FS32_TYPE_OFFSET 0x52
+#if IS_ENABLED(CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH)
+static inline __u32 sect_to_block(__u32 sect, __u32 *off)
+{
+ const ulong blksz = cur_part_info.blksz;
+
+ *off = 0;
+ if (fat_sect_size && fat_sect_size < blksz) {
+ int div = blksz / fat_sect_size;
+
+ *off = sect % div;
+ return sect / div;
+ } else if (fat_sect_size && (fat_sect_size > blksz)) {
+ return sect * (fat_sect_size / blksz);
+ }
+
+ return sect;
+}
+
+static int disk_rw(__u32 sect, __u32 nr_sect, void *buf, bool read)
+{
+ int ret;
+ __u8 *block = NULL;
+ __u32 rem, size, s, n;
+ const ulong blksz = cur_part_info.blksz;
+ const lbaint_t start = cur_part_info.start;
+
+ rem = nr_sect * fat_sect_size;
+ /*
+ * block N block N + 1 block N + 2
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | | | | |s|e|c|t|o|r| | |s|e|c|t|o|r| | |s|e|c|t|o|r| | | | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * . . . | | | | . . .
+ * ------+---------------+---------------+---------------+------
+ * |<--- FAT reads in sectors --->|
+ *
+ * | part 1 | part 2 | part 3 |
+ *
+ */
+
+ /* Do part 1 */
+ if (fat_sect_size) {
+ __u32 offset;
+
+ /* Read one block and overwrite the leading sectors */
+ block = malloc_cache_aligned(cur_dev->blksz);
+ if (!block) {
+ printf("Error: allocating block: %lu\n", cur_dev->blksz);
+ return -1;
+ }
+
+ s = sect_to_block(sect, &offset);
+ offset = offset * fat_sect_size;
+
+ ret = blk_dread(cur_dev, start + s, 1, block);
+ if (ret != 1) {
+ ret = -1;
+ goto exit;
+ }
+
+ if (rem > (blksz - offset))
+ size = blksz - offset;
+ else
+ size = rem;
+
+ if (read) {
+ memcpy(buf, block + offset, size);
+ } else {
+ memcpy(block + offset, buf, size);
+ ret = blk_dwrite(cur_dev, start + s, 1, block);
+ if (ret != 1) {
+ ret = -1;
+ goto exit;
+ }
+ }
+
+ rem -= size;
+ buf += size;
+ s++;
+ }
+
+ /* Do part 2, read/write directly to/from the given buffer */
+ if (rem > blksz) {
+ n = rem / blksz;
+
+ if (read)
+ ret = blk_dread(cur_dev, start + s, n, buf);
+ else
+ ret = blk_dwrite(cur_dev, start + s, n, buf);
+
+ if (ret != n) {
+ ret = -1;
+ goto exit;
+ }
+ buf += n * blksz;
+ rem = rem % blksz;
+ s += n;
+ }
+
+ /* Do part 3, read a block and copy the trailing sectors */
+ if (rem) {
+ ret = blk_dread(cur_dev, start + s, 1, block);
+ if (ret != 1) {
+ ret = -1;
+ goto exit;
+ }
+ if (read) {
+ memcpy(buf, block, rem);
+ } else {
+ memcpy(block, buf, rem);
+ ret = blk_dwrite(cur_dev, start + s, 1, block);
+ if (ret != 1) {
+ ret = -1;
+ goto exit;
+ }
+ }
+ }
+exit:
+ if (block)
+ free(block);
+
+ return (ret == -1) ? -1 : nr_sect;
+}
+
+static int disk_read(__u32 sect, __u32 nr_sect, void *buf)
+{
+ return disk_rw(sect, nr_sect, buf, true);
+}
+
+int disk_write(__u32 sect, __u32 nr_sect, void *buf)
+{
+ return disk_rw(sect, nr_sect, buf, false);
+}
+#else
static int disk_read(__u32 block, __u32 nr_blocks, void *buf)
{
ulong ret;
@@ -64,6 +199,7 @@ static int disk_read(__u32 block, __u32 nr_blocks, void *buf)
return ret;
}
+#endif /* CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH */
int fat_set_blk_dev(struct blk_desc *dev_desc, struct disk_partition *info)
{
@@ -73,7 +209,7 @@ int fat_set_blk_dev(struct blk_desc *dev_desc, struct disk_partition *info)
cur_part_info = *info;
/* Make sure it has a valid FAT header */
- if (disk_read(0, 1, buffer) != 1) {
+ if (blk_dread(cur_dev, cur_part_info.start, 1, buffer) != 1) {
cur_dev = NULL;
return -1;
}
@@ -581,7 +717,8 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize)
return -1;
}
- if (disk_read(0, 1, block) < 0) {
+ fat_sect_size = 0;
+ if (blk_dread(cur_dev, cur_part_info.start, 1, block) != 1) {
debug("Error: reading block\n");
ret = -1;
goto out_free;
@@ -651,11 +788,16 @@ static int get_fs_info(fsdata *mydata)
mydata->rootdir_sect = mydata->fat_sect + mydata->fatlength * bs.fats;
mydata->sect_size = get_unaligned_le16(bs.sector_size);
+ fat_sect_size = mydata->sect_size;
mydata->clust_size = bs.cluster_size;
if (mydata->sect_size != cur_part_info.blksz) {
- log_err("FAT sector size mismatch (fs=%u, dev=%lu)\n",
- mydata->sect_size, cur_part_info.blksz);
- return -1;
+ if (!IS_ENABLED(CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH)) {
+ log_err("FAT sector size mismatch (fs=%u, dev=%lu)\n",
+ mydata->sect_size, cur_part_info.blksz);
+ return -1;
+ }
+ log_info("FAT sector size mismatch (fs=%u, dev=%lu)\n",
+ mydata->sect_size, cur_part_info.blksz);
}
if (mydata->clust_size == 0) {
log_err("FAT cluster size not set\n");
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
index 0b924541187..02e006f7c9e 100644
--- a/fs/fat/fat_write.c
+++ b/fs/fat/fat_write.c
@@ -192,6 +192,7 @@ out:
}
static int total_sector;
+#if !IS_ENABLED(CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH)
static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
{
ulong ret;
@@ -211,6 +212,7 @@ static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
return ret;
}
+#endif /* CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH */
/*
* Write fat buffer into block device