From 6a1daf5adcefad3d08f97dd85e2917eac12e7202 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 13 Dec 2019 11:45:25 +0100 Subject: fs: Introduce discard_range() discard_range() is a way to tell the lower layers that we are no longer interested in a data range of a file, so that the lower layers can discard the underlying data if desired. This is mainly designed to bypass the deficiencies of our block layer. We cache the block data in chunks of multiple KiB (16 currently) if we fall into the block layer with write requests smaller than that we have to read/modify/write a chunk. With the help of discard_range() code writing files to a raw block device can now discard the range the file will be written to. The block layer then no longer has to read the chunks first that are inside the discard range. Signed-off-by: Sascha Hauer --- fs/fs.c | 25 +++++++++++++++++++++++++ include/fs.h | 3 +++ 2 files changed, 28 insertions(+) diff --git a/fs/fs.c b/fs/fs.c index 12faaebc27..e00bbcd223 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -497,6 +497,31 @@ int protect(int fd, size_t count, loff_t offset, int prot) } EXPORT_SYMBOL(protect); +int discard_range(int fd, loff_t count, loff_t offset) +{ + struct fs_driver_d *fsdrv; + FILE *f = fd_to_file(fd); + int ret; + + if (IS_ERR(f)) + return -errno; + if (offset >= f->size) + return 0; + if (count > f->size - offset) + count = f->size - offset; + + fsdrv = f->fsdev->driver; + if (fsdrv->discard_range) + ret = fsdrv->discard_range(&f->fsdev->dev, f, count, offset); + else + ret = -ENOSYS; + + if (ret) + errno = -ret; + + return ret; +} + int protect_file(const char *file, int prot) { int fd, ret; diff --git a/include/fs.h b/include/fs.h index 38debfc41b..d9684c82b1 100644 --- a/include/fs.h +++ b/include/fs.h @@ -60,6 +60,8 @@ struct fs_driver_d { loff_t offset); int (*protect)(struct device_d *dev, FILE *f, size_t count, loff_t offset, int prot); + int (*discard_range)(struct device_d *dev, FILE *f, loff_t count, + loff_t offset); int (*memmap)(struct device_d *dev, FILE *f, void **map, int flags); @@ -127,6 +129,7 @@ int umount_by_cdev(struct cdev *cdev); #define ERASE_SIZE_ALL ((loff_t) - 1) int erase(int fd, loff_t count, loff_t offset); int protect(int fd, size_t count, loff_t offset, int prot); +int discard_range(int fd, loff_t count, loff_t offset); int protect_file(const char *file, int prot); void *memmap(int fd, int flags); -- cgit v1.2.3 From a18f5ad97df6b3d0768c8b741e85da077f990cc7 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 13 Dec 2019 12:14:16 +0100 Subject: cdev: Add discard_range hook To pass though discard_range() to the underlying drivers add a discard_range hook to struct cdev_operations. Signed-off-by: Sascha Hauer --- fs/devfs.c | 21 +++++++++++++++++++++ include/driver.h | 1 + 2 files changed, 22 insertions(+) diff --git a/fs/devfs.c b/fs/devfs.c index d088c1a66c..e1893d1bd0 100644 --- a/fs/devfs.c +++ b/fs/devfs.c @@ -94,6 +94,26 @@ static int devfs_protect(struct device_d *_dev, FILE *f, size_t count, loff_t of return cdev->ops->protect(cdev, count, offset + cdev->offset, prot); } +static int devfs_discard_range(struct device_d *dev, FILE *f, loff_t count, + loff_t offset) +{ + struct cdev *cdev = f->priv; + + if (!cdev->ops->discard_range) + return -ENOSYS; + + if (cdev->flags & DEVFS_PARTITION_READONLY) + return -EPERM; + + if (offset >= cdev->size) + return 0; + + if (count + offset > cdev->size) + count = cdev->size - offset; + + return cdev->ops->discard_range(cdev, count, offset + cdev->offset); +} + static int devfs_memmap(struct device_d *_dev, FILE *f, void **map, int flags) { struct cdev *cdev = f->priv; @@ -329,6 +349,7 @@ static struct fs_driver_d devfs_driver = { .truncate = devfs_truncate, .erase = devfs_erase, .protect = devfs_protect, + .discard_range = devfs_discard_range, .memmap = devfs_memmap, .flags = FS_DRIVER_NO_DEV, .drv = { diff --git a/include/driver.h b/include/driver.h index 74be1b3e8e..c9ad95fd40 100644 --- a/include/driver.h +++ b/include/driver.h @@ -441,6 +441,7 @@ struct cdev_operations { int (*flush)(struct cdev*); int (*erase)(struct cdev*, loff_t count, loff_t offset); int (*protect)(struct cdev*, size_t count, loff_t offset, int prot); + int (*discard_range)(struct cdev*, loff_t count, loff_t offset); int (*memmap)(struct cdev*, void **map, int flags); int (*truncate)(struct cdev*, size_t size); }; -- cgit v1.2.3 From e488952b9d04fb0fc7dddd31ec639549d71c76b3 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 13 Dec 2019 12:16:51 +0100 Subject: block: Implement discard_range This implements the discard_range hook. When a range of data is discarded then we do not have to read it from the device and can pass a zeroed buffer instead. Signed-off-by: Sascha Hauer --- common/block.c | 21 +++++++++++++++++++++ include/block.h | 3 +++ 2 files changed, 24 insertions(+) diff --git a/common/block.c b/common/block.c index 97cf5dc4de..8b43c3c83a 100644 --- a/common/block.c +++ b/common/block.c @@ -161,6 +161,14 @@ static int block_cache(struct block_device *blk, int block) dev_dbg(blk->dev, "%s: %d to %d\n", __func__, chunk->block_start, chunk->num); + if (chunk->block_start * BLOCKSIZE(blk) >= blk->discard_start && + chunk->block_start * BLOCKSIZE(blk) + writebuffer_io_len(blk, chunk) + <= blk->discard_start + blk->discard_size) { + memset(chunk->data, 0, writebuffer_io_len(blk, chunk)); + list_add(&chunk->list, &blk->buffered_blocks); + return 0; + } + ret = blk->ops->read(blk, chunk->data, chunk->block_start, writebuffer_io_len(blk, chunk)); if (ret) { @@ -337,11 +345,23 @@ static int block_op_flush(struct cdev *cdev) { struct block_device *blk = cdev->priv; + blk->discard_start = blk->discard_size = 0; + return writebuffer_flush(blk); } static int block_op_close(struct cdev *cdev) __alias(block_op_flush); +static int block_op_discard_range(struct cdev *cdev, loff_t count, loff_t offset) +{ + struct block_device *blk = cdev->priv; + + blk->discard_start = offset; + blk->discard_size = count; + + return 0; +} + static struct cdev_operations block_ops = { .read = block_op_read, #ifdef CONFIG_BLOCK_WRITE @@ -349,6 +369,7 @@ static struct cdev_operations block_ops = { #endif .close = block_op_close, .flush = block_op_flush, + .discard_range = block_op_discard_range, }; int blockdevice_register(struct block_device *blk) diff --git a/include/block.h b/include/block.h index 91377679b0..d35c4ecdf4 100644 --- a/include/block.h +++ b/include/block.h @@ -23,6 +23,9 @@ struct block_device { int rdbufsize; int blkmask; + loff_t discard_start; + loff_t discard_size; + struct list_head buffered_blocks; struct list_head idle_blocks; -- cgit v1.2.3 From 9552f5150bb2654a0e52aad8c12973896f24f2ee Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 13 Dec 2019 12:20:15 +0100 Subject: copy_file: call discard_range on destination file discard the range in the output file we are going to overwrite anyway. Signed-off-by: Sascha Hauer --- lib/libfile.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/libfile.c b/lib/libfile.c index 5a1817e32a..dbeed12ccd 100644 --- a/lib/libfile.c +++ b/lib/libfile.c @@ -367,6 +367,8 @@ int copy_file(const char *src, const char *dst, int verbose) goto out; } + discard_range(dstfd, srcstat.st_size, 0); + if (verbose) { if (stat(src, &srcstat) < 0) srcstat.st_size = 0; -- cgit v1.2.3 From 0e8d4c71ab877e1498ab1f0f63be54430e24945a Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 27 Feb 2020 17:58:24 +0100 Subject: usb: gadget: fastboot: call discard_range for sparse files as well We can save some time by explicitly telling the block layer that a range is unused before overwriting it. This brought time my sample write from 445s to 376s. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/usb/gadget/f_fastboot.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c index 35c4b8cf4c..96aecacc27 100644 --- a/drivers/usb/gadget/f_fastboot.c +++ b/drivers/usb/gadget/f_fastboot.c @@ -957,6 +957,8 @@ static int fastboot_handle_sparse(struct f_fastboot *f_fb, if (ret) goto out; } else { + discard_range(fd, retlen, pos); + pos = lseek(fd, pos, SEEK_SET); if (pos == -1) { ret = -errno; -- cgit v1.2.3 From 73290bcfd105cd72e45c4843e8625033cefcc089 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Thu, 27 Feb 2020 17:58:25 +0100 Subject: Revert "block: Adjust cache sizes" On 12/13/19 2:12 PM, Sascha Hauer wrote: > On Tue, Dec 10, 2019 at 03:44:52PM +0100, Hubert Feurstein wrote: >> With v2015.06.0 the indicated progress of the copy command is very >> smooth. Calling "cp -v /dev/zero /dev/mmc3.root" takes about 80 >> seconds for 256MB. But with v2019.12.0 the progress is very bumpy and >> the copy takes about 280 seconds. >> >> I've tracked this down to this commit which destroys the performance: >> "block: Adjust cache sizes" (b6fef20c1215c6ef0004f6af4a9c4b77af51dc43) > > We could just revert this patch. I can't find any workload that gets > faster with b6fef20c1215. It's rather the other way round. Do this by reverting commit b6fef20c1215c6ef0004f6af4a9c4b77af51dc43. Reported-by: Hubert Feurstein Suggested-by: Sascha Hauer Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- common/block.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/block.c b/common/block.c index 8b43c3c83a..02be80d7cc 100644 --- a/common/block.c +++ b/common/block.c @@ -36,7 +36,7 @@ struct chunk { struct list_head list; }; -#define BUFSIZE (PAGE_SIZE * 4) +#define BUFSIZE (PAGE_SIZE * 16) static int writebuffer_io_len(struct block_device *blk, struct chunk *chunk) { @@ -391,7 +391,7 @@ int blockdevice_register(struct block_device *blk) dev_dbg(blk->dev, "rdbufsize: %d blockbits: %d blkmask: 0x%08x\n", blk->rdbufsize, blk->blockbits, blk->blkmask); - for (i = 0; i < 32; i++) { + for (i = 0; i < 8; i++) { struct chunk *chunk = xzalloc(sizeof(*chunk)); chunk->data = dma_alloc(BUFSIZE); chunk->num = i; -- cgit v1.2.3