[RFC v3 09/11] ext4: add forcealign support to ext4_map_blocks
From: Ojaswin Mujoo
Date: Mon Mar 24 2025 - 03:40:01 EST
Introduce EXT4_GET_BLOCKS_FORCEALIGN that works with EXT4_GET_BLOCKS_EXTSIZE
and guarantees that the extent returned by allocator is physically as
well as logically aligned to the extsize hint set on the inode.
This feature will be used to guranatee aligned allocations for HW accelerated
atomic writes
Signed-off-by: Ojaswin Mujoo <ojaswin@xxxxxxxxxxxxx>
---
fs/ext4/ext4.h | 1 +
fs/ext4/extents.c | 24 ++++++++++++++++++++++--
fs/ext4/inode.c | 43 ++++++++++++++++++++++++++++++++++++++++---
3 files changed, 63 insertions(+), 5 deletions(-)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 9b9d7a354736..a7429797c1d2 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -730,6 +730,7 @@ enum {
/* Caller is in the atomic contex, find extent if it has been cached */
#define EXT4_GET_BLOCKS_CACHED_NOWAIT 0x0800
#define EXT4_GET_BLOCKS_EXTSIZE 0x1000
+#define EXT4_GET_BLOCKS_FORCEALIGN 0x2000
/*
* The bit position of these flags must not overlap with any of the
diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index a86cc3e76f14..25c1368b49bb 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -4414,13 +4414,27 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
ar.flags |= EXT4_MB_USE_RESERVED;
if (flags & EXT4_GET_BLOCKS_EXTSIZE)
ar.flags |= EXT4_MB_HINT_ALIGNED;
+ if (flags & EXT4_GET_BLOCKS_FORCEALIGN) {
+ if (WARN_ON(ar.logical != map->m_lblk || ar.len != map->m_len ||
+ !(flags & EXT4_GET_BLOCKS_EXTSIZE))) {
+ /*
+ * This should ideally not happen but if does then error
+ * out
+ */
+ err = -ENOSPC;
+ goto out;
+ }
+ ar.flags |= EXT4_MB_FORCE_ALIGN;
+ }
newblock = ext4_mb_new_blocks(handle, &ar, &err);
if (!newblock)
goto out;
allocated_clusters = ar.len;
ar.len = EXT4_C2B(sbi, ar.len) - offset;
- ext_debug(inode, "allocate new block: goal %llu, found %llu/%u, requested %u\n",
- ar.goal, newblock, ar.len, allocated);
+ ext_debug(
+ inode,
+ "allocate new block: goal %llu, found %llu/%u, requested %u\n",
+ ar.goal, newblock, ar.len, allocated);
if (ar.len > allocated)
ar.len = allocated;
@@ -4435,6 +4449,12 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
map->m_flags |= EXT4_MAP_UNWRITTEN;
}
+ if ((flags & EXT4_GET_BLOCKS_FORCEALIGN) &&
+ (ar.len != map->m_len || pblk % map->m_len)) {
+ err = -ENOSPC;
+ goto insert_error;
+ }
+
if ((flags & EXT4_GET_BLOCKS_EXTSIZE) &&
(flags & EXT4_GET_BLOCKS_PRE_IO)) {
/*
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index e41c97584f35..93ab76cb4818 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -753,6 +753,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
__u32 extsize = ext4_inode_get_extsize(EXT4_I(inode));
bool should_extsize = false;
+ bool should_forcealign = false;
#ifdef ES_AGGRESSIVE_TEST
struct ext4_map_blocks test_map;
@@ -793,6 +794,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
* ext4_extents.h here?
*/
int max_unwrit_len = ((1UL << 15) - 1);
+ should_forcealign = (flags & EXT4_GET_BLOCKS_FORCEALIGN);
align = orig_map->m_lblk % extsize;
len = orig_map->m_len + align;
@@ -802,7 +804,11 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
max_t(unsigned int, roundup_pow_of_two(len), extsize);
/* Fallback to normal allocation if we go beyond max len */
- if (extsize_map.m_len >= max_unwrit_len) {
+ if (WARN_ON(extsize_map.m_len >= max_unwrit_len)) {
+ if (should_forcealign)
+ /* forcealign has no fallback */
+ return -EINVAL;
+
flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
goto set_map;
}
@@ -814,8 +820,10 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
* dioread nolock to achieve this. Hence the caller has to pass
* CREATE_UNWRIT with EXTSIZE
*/
- if (!(flags | EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT)) {
- WARN_ON(true);
+ if (WARN_ON(!(flags | EXT4_GET_BLOCKS_CREATE_UNWRIT_EXT))) {
+ if (should_forcealign)
+ /* forcealign has no fallback */
+ return -EINVAL;
/* Fallback to non extsize allocation */
flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
@@ -905,6 +913,29 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
if (retval >= 0 && flags & EXT4_GET_BLOCKS_EXTSIZE) {
bool orig_in_range =
in_range(orig_mlblk, (__u64)map->m_lblk, map->m_len);
+
+ if (should_forcealign) {
+ /*
+ * For forcealign, irrespective of it it's a hole or not,
+ * the mapping we got should be exactly equal to the
+ * extsize mapping we requested since allocation and
+ * deallocation both respect extsize. If
+ * not, something has gone terribly wrong.
+ */
+ if (WARN_ON((map->m_lblk != extsize_mlblk) ||
+ (map->m_len != extsize_mlen))) {
+ ext4_error_adjust_map(map, orig_map);
+ ext4_warning(
+ inode->i_sb,
+ "%s: Unaligned blocks found! Disable forcealign and try again."
+ "requested:(%u, %u) extsize:(%u, %u) got:(%u, %u)\n",
+ __func__, orig_mlblk, orig_mlen,
+ extsize_mlblk, extsize_mlen,
+ map->m_lblk, map->m_len);
+ return -EUCLEAN;
+ }
+ }
+
/*
* Special case: if the extsize range is mapped already and
* covers the original start, we return it.
@@ -925,6 +956,7 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
if (map->m_lblk != extsize_mlblk ||
map->m_len != extsize_mlen) {
+ WARN_ON(should_forcealign);
flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
goto set_map;
}
@@ -1011,6 +1043,11 @@ int ext4_map_blocks(handle_t *handle, struct inode *inode,
* not used. Can we avoid that?
*/
if (!in_range(orig_mlblk, (__u64)map->m_lblk, map->m_len)) {
+ if (WARN_ON(should_forcealign)) {
+ /* this should never happen */
+ ext4_error_adjust_map(map, orig_map);
+ return -ENOSPC;
+ }
flags = orig_flags & ~EXT4_GET_BLOCKS_EXTSIZE;
goto set_map;
}
--
2.48.1