| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * NTFS block device I/O. | |
| * | |
| * Copyright (c) 2026 LG Electronics Co., Ltd. | |
| */ | |
| #include <linux/blkdev.h> | |
| #include "ntfs.h" | |
| /* | |
| * ntfs_bdev_read - Read data directly from block device using bio | |
| * @bdev: block device to read from | |
| * @data: destination buffer | |
| * @start: starting byte offset on the block device | |
| * @size: number of bytes to read | |
| * | |
| * Reads @size bytes starting from byte offset @start directly from the block | |
| * device using one or more BIOs. This function bypasses the page cache | |
| * completely and performs synchronous I/O with REQ_META | REQ_SYNC flags set. | |
| * | |
| * The @start offset must be sector-aligned (512 bytes). If it is not aligned, | |
| * the function will return -EINVAL. | |
| * | |
| * If the destination buffer @data is not a vmalloc address, it falls back | |
| * to the more efficient bdev_rw_virt() helper. | |
| * | |
| * Return: 0 on success, negative error code on failure. | |
| */ | |
| int ntfs_bdev_read(struct block_device *bdev, char *data, loff_t start, size_t size) | |
| { | |
| unsigned int done = 0, added; | |
| int error; | |
| struct bio *bio; | |
| enum req_op op; | |
| sector_t sector = start >> SECTOR_SHIFT; | |
| if (start & (SECTOR_SIZE - 1)) | |
| return -EINVAL; | |
| op = REQ_OP_READ | REQ_META | REQ_SYNC; | |
| if (!is_vmalloc_addr(data)) | |
| return bdev_rw_virt(bdev, sector, data, size, op); | |
| bio = bio_alloc(bdev, | |
| bio_max_segs(DIV_ROUND_UP(size, PAGE_SIZE)), | |
| op, GFP_KERNEL); | |
| bio->bi_iter.bi_sector = sector; | |
| do { | |
| added = bio_add_vmalloc_chunk(bio, data + done, size - done); | |
| if (!added) { | |
| struct bio *prev = bio; | |
| bio = bio_alloc(prev->bi_bdev, | |
| bio_max_segs(DIV_ROUND_UP(size - done, PAGE_SIZE)), | |
| prev->bi_opf, GFP_KERNEL); | |
| bio->bi_iter.bi_sector = bio_end_sector(prev); | |
| bio_chain(prev, bio); | |
| submit_bio(prev); | |
| } | |
| done += added; | |
| } while (done < size); | |
| error = submit_bio_wait(bio); | |
| bio_put(bio); | |
| if (op == REQ_OP_READ) | |
| invalidate_kernel_vmap_range(data, size); | |
| return error; | |
| } | |
| /* | |
| * ntfs_bdev_write - Update block device contents via page cache | |
| * @sb: super block of the mounted NTFS filesystem | |
| * @buf: source buffer containing data to write | |
| * @start: starting byte offset on the block device | |
| * @size: number of bytes to write | |
| * | |
| * Writes @size bytes from @buf to the block device (sb->s_bdev) starting | |
| * at byte offset @start. The write is performed entirely through the page | |
| * cache of the block device's address space. | |
| */ | |
| int ntfs_bdev_write(struct super_block *sb, void *buf, loff_t start, size_t size) | |
| { | |
| pgoff_t idx, idx_end; | |
| loff_t offset, end = start + size; | |
| u32 from, to, buf_off = 0; | |
| struct folio *folio; | |
| idx = start >> PAGE_SHIFT; | |
| idx_end = end >> PAGE_SHIFT; | |
| from = start & ~PAGE_MASK; | |
| if (idx == idx_end) | |
| idx_end++; | |
| for (; idx < idx_end; idx++, from = 0) { | |
| u32 len; | |
| folio = read_mapping_folio(sb->s_bdev->bd_mapping, idx, NULL); | |
| if (IS_ERR(folio)) { | |
| ntfs_error(sb, "Unable to read %ld page", idx); | |
| return PTR_ERR(folio); | |
| } | |
| offset = (loff_t)idx << PAGE_SHIFT; | |
| to = min_t(u32, end - offset, PAGE_SIZE); | |
| len = to - from; | |
| memcpy_to_folio(folio, from, buf + buf_off, len); | |
| buf_off += len; | |
| folio_mark_uptodate(folio); | |
| folio_mark_dirty(folio); | |
| folio_put(folio); | |
| } | |
| return 0; | |
| } |