At present it is not possible to read from some CDROM drives since the FAT sector size does not match the media's block size. Add a conversion option for this, so that reading is possible.
This does increase SPL size for read-only FAT support by 25 bytes but all but 6 are covered by the previous patch. We could reduce the overhead of this feature to 0 bytes by making the code uglier (using a static variable). Signed-off-by: Simon Glass <s...@chromium.org> --- (no changes since v2) Changes in v2: - Use log_warning() for the warning fs/fat/Kconfig | 13 ++++++ fs/fat/fat.c | 107 ++++++++++++++++++++++++++++++++++++++++----- fs/fat/fat_write.c | 8 ++-- 3 files changed, 114 insertions(+), 14 deletions(-) diff --git a/fs/fat/Kconfig b/fs/fat/Kconfig index 9bb11eac9f7..b0aa888c6cc 100644 --- a/fs/fat/Kconfig +++ b/fs/fat/Kconfig @@ -22,3 +22,16 @@ 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 FAT_BLK_XLATE + bool "Enable FAT filesystem on a device with a larger block size" + depends on FS_FAT + help + This provides a simple translation mechanism for reading FAT + filesystems which don't use the same sector size as the underlying + media. For example, the FAT filesystem may use 512 bytes but the + media uses 2048, e.g. on a CDROM drive. + + This only supports the case where the FAT filesystem's sector size is + smaller than the media's block size. It does not support creating or + writing files. diff --git a/fs/fat/fat.c b/fs/fat/fat.c index d1476aa433d..686b321163f 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -45,13 +45,93 @@ static struct disk_partition cur_part_info; #define DOS_FS_TYPE_OFFSET 0x36 #define DOS_FS32_TYPE_OFFSET 0x52 -static int disk_read(__u32 block, __u32 nr_blocks, void *buf) +/** + * disk_read_conv() - Read blocks and break them into smaller ones + * + * This is used when the FAT filesystem is hosted on a block device with a + * block size greated than 512 bytes, e.g. the 2048 bytes of a CDROM drive. It + * reads the blocks into a buffer and pulls out what is requested by the calling + * function. + * + * It uses an internal 2KB buffer on the stack. + * + * @mydata: Filesystem information + * @block: Block number to read, in terms of mydata->sect_size + * @nr_blocks: Number of blocks to read, in terms of mydata->sect_size + * @buf: Buffer for data + */ +static int disk_read_conv(fsdata *mydata, __u32 block, __u32 nr_blocks, + void *buf) +{ + uint factor, whole, remain, upto; + ulong base, index; + uint to_copy; + u8 tbuf[2048]; + int ret; + + log_debug("mydata %x, cur_dev %lx, block %x, nr_block %x\n", + mydata->sect_size, cur_dev->blksz, block, nr_blocks); + if (mydata->sect_size > cur_dev->blksz || + cur_dev->blksz > sizeof(tbuf)) { + log_err("Block size %lx not supported\n", cur_dev->blksz); + return -EIO; + } + factor = cur_dev->blksz / mydata->sect_size; + + /* get the first partial block */ + base = cur_part_info.start + block / factor; + index = block % factor; + log_debug("cur_part_info.start %llx, block %x, base %lx, index %lx\n", + (unsigned long long)cur_part_info.start, block, base, index); + ret = blk_dread(cur_dev, base, 1, tbuf); + if (ret != 1) + return -EIO; + + to_copy = min((ulong)nr_blocks, factor - index); + log_debug("to_copy %x\n", to_copy); + memcpy(buf, tbuf + index * mydata->sect_size, + to_copy * mydata->sect_size); + upto = to_copy; + + /* load any whole blocks */ + remain = nr_blocks - upto; + whole = remain / factor; + log_debug("factor %x, whole %x, remain %x\n", factor, whole, remain); + if (whole) { + ret = blk_dread(cur_dev, base + 1, whole, + buf + upto * mydata->sect_size); + if (ret != whole) + return -EIO; + upto += whole * factor; + remain = nr_blocks - upto; + } + + /* load any blocks at the end */ + log_debug("end: remain %x\n", remain); + if (remain) { + ret = blk_dread(cur_dev, base + 1 + whole, 1, tbuf); + if (ret != 1) + return -EIO; + memcpy(buf + upto * mydata->sect_size, tbuf, + remain * mydata->sect_size); + upto += remain; + } + + return upto; +} + +static int disk_read(fsdata *mydata, __u32 block, __u32 nr_blocks, void *buf) { ulong ret; if (!cur_dev) return -1; + /* support converting from a larger block size */ + if (IS_ENABLED(CONFIG_FAT_BLK_XLATE) && mydata && + mydata->sect_size != cur_dev->blksz) + return disk_read_conv(mydata, block, nr_blocks, buf); + ret = blk_dread(cur_dev, cur_part_info.start + block, nr_blocks, buf); if (ret != nr_blocks) @@ -68,7 +148,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 (disk_read(NULL, 0, 1, buffer) != 1) { cur_dev = NULL; return -1; } @@ -213,7 +293,7 @@ static __u32 get_fatent(fsdata *mydata, __u32 entry) if (flush_dirty_fat_buffer(mydata) < 0) return -1; - if (disk_read(startblock, getsize, bufptr) < 0) { + if (disk_read(mydata, startblock, getsize, bufptr) < 0) { debug("Error reading FAT blocks\n"); return ret; } @@ -267,7 +347,7 @@ get_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer, unsigned long size) debug("FAT: Misaligned buffer address (%p)\n", buffer); while (size >= mydata->sect_size) { - ret = disk_read(startsect++, 1, tmpbuf); + ret = disk_read(mydata, startsect++, 1, tmpbuf); if (ret != 1) { debug("Error reading data (got %d)\n", ret); return -1; @@ -281,7 +361,7 @@ get_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer, unsigned long size) __u32 bytes_read; __u32 sect_count = size / mydata->sect_size; - ret = disk_read(startsect, sect_count, buffer); + ret = disk_read(mydata, startsect, sect_count, buffer); if (ret != sect_count) { debug("Error reading data (got %d)\n", ret); return -1; @@ -294,7 +374,7 @@ get_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer, unsigned long size) if (size) { ALLOC_CACHE_ALIGN_BUFFER(__u8, tmpbuf, mydata->sect_size); - ret = disk_read(startsect, 1, tmpbuf); + ret = disk_read(mydata, startsect, 1, tmpbuf); if (ret != 1) { debug("Error reading data (got %d)\n", ret); return -1; @@ -506,7 +586,7 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize) return -1; } - if (disk_read(0, 1, block) < 0) { + if (disk_read(NULL, 0, 1, block) < 0) { debug("Error: reading block\n"); goto fail; } @@ -588,9 +668,14 @@ static int get_fs_info(fsdata *mydata) mydata->sect_size = (bs.sector_size[1] << 8) + bs.sector_size[0]; 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_FAT_BLK_XLATE)) { + log_warning("FAT sector size mismatch (fs=%u, dev=%lu): translating for read-only\n", + mydata->sect_size, cur_part_info.blksz); + } else { + log_err("FAT sector size mismatch (fs=%u, dev=%lu), see CONFIG_FAT_BLK_XLATE\n", + mydata->sect_size, cur_part_info.blksz); + return -1; + } } if (mydata->clust_size == 0) { log_err("FAT cluster size not set\n"); @@ -848,7 +933,7 @@ void *fat_next_cluster(fat_itr *itr, unsigned int *nbytes) * dent at a time and iteratively constructing the vfat long * name. */ - ret = disk_read(sect, read_size, itr->block); + ret = disk_read(itr->fsdata, sect, read_size, itr->block); if (ret < 0) { debug("Error: reading block\n"); return NULL; diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c index e2a9913f807..95f7a60caa2 100644 --- a/fs/fat/fat_write.c +++ b/fs/fat/fat_write.c @@ -477,7 +477,7 @@ static int set_fatent_value(fsdata *mydata, __u32 entry, __u32 entry_value) startblock += mydata->fat_sect; - if (disk_read(startblock, getsize, bufptr) < 0) { + if (disk_read(NULL, startblock, getsize, bufptr) < 0) { debug("Error reading FAT blocks\n"); return -1; } @@ -712,7 +712,8 @@ get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer, /* partial write at beginning */ if (pos) { wsize = min(bytesperclust - pos, size); - ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster); + ret = disk_read(NULL, startsect, mydata->clust_size, + tmpbuf_cluster); if (ret != mydata->clust_size) { debug("Error reading data (got %d)\n", ret); return -1; @@ -778,7 +779,8 @@ get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer, /* partial write at end */ if (size) { wsize = size; - ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster); + ret = disk_read(NULL, startsect, mydata->clust_size, + tmpbuf_cluster); if (ret != mydata->clust_size) { debug("Error reading data (got %d)\n", ret); return -1; -- 2.40.1.521.gf1e218fcd8-goog