[PATCH v2 08/11] zsmalloc: encode 1-based obj_idx so valid obj is never 0

From: Sergey Senozhatsky

Date: Tue Nov 10 2026 - 00:53:18 EST


The next patch embeds struct zs_handle into zram and zswap entries,
and a handle is considered allocated when its obj is not 0. But obj
can be 0: object 0 of a zspage whose first page is PFN 0 encodes to
0, and some platforms do give PFN 0 to the page allocator.

Store obj_idx + 1 in obj, so that a valid obj is never 0, and adjust
ZS_MIN_ALLOC_SIZE so that the largest index still fits. Unlike a
separate tag bit, this doesn't take any bits from obj. zsmalloc did
the same before handles moved to handle_cachep, see
commit 67296874eb1c ("staging: zsmalloc: Ensure handle is never 0 on
success").

Signed-off-by: Sergey Senozhatsky <senozhatsky@xxxxxxxxxxxx>
---
mm/zsmalloc.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/mm/zsmalloc.c b/mm/zsmalloc.c
index 3a6a7253fd01..aa436df0cc4d 100644
--- a/mm/zsmalloc.c
+++ b/mm/zsmalloc.c
@@ -61,7 +61,8 @@
* Object location (<PFN>, <obj_idx>) is encoded as
* a single (unsigned long) handle value.
*
- * Note that object index <obj_idx> starts from 0.
+ * Note that object index <obj_idx> starts from 0, and is encoded as
+ * <obj_idx> + 1 so that a valid encoded obj value is never 0.
*
* This is made more complicated by various memory models and PAE.
*/
@@ -169,9 +170,6 @@
static_assert(ZS_OBJ_IDX_BITS >= ZS_PAGES_PER_ZSPAGE_BITS,
"zsmalloc: ZS_MIN_ALLOC_SIZE would exceed ZS_MAX_ALLOC_SIZE");

-/* ZS_MIN_ALLOC_SIZE must be multiple of ZS_ALIGN */
-#define ZS_MIN_ALLOC_SIZE \
- MAX(32, (ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT >> ZS_OBJ_IDX_BITS))
/* each chunk includes extra space to keep handle */
#define ZS_MAX_ALLOC_SIZE PAGE_SIZE

@@ -189,6 +187,11 @@ static_assert(ZS_OBJ_IDX_BITS >= ZS_PAGES_PER_ZSPAGE_BITS,
* (reason above)
*/
#define ZS_SIZE_CLASS_DELTA (PAGE_SIZE >> CLASS_BITS)
+#define _ZS_MIN_ALLOC_RAW \
+ DIV_ROUND_UP(ZS_MAX_PAGES_PER_ZSPAGE << PAGE_SHIFT, ZS_OBJ_IDX_MASK)
+#define ZS_MIN_ALLOC_SIZE \
+ MIN(ZS_MAX_ALLOC_SIZE, \
+ MAX(32UL, ALIGN(_ZS_MIN_ALLOC_RAW, ZS_SIZE_CLASS_DELTA)))
#define ZS_SIZE_CLASSES (DIV_ROUND_UP(ZS_MAX_ALLOC_SIZE - ZS_MIN_ALLOC_SIZE, \
ZS_SIZE_CLASS_DELTA) + 1)

@@ -794,8 +797,10 @@ static struct zpdesc *get_next_zpdesc(struct zpdesc *zpdesc)
static void obj_to_location(unsigned long obj, struct zpdesc **zpdesc,
unsigned int *obj_idx)
{
+ VM_WARN_ON_ONCE(!(obj & ZS_OBJ_IDX_MASK));
+
*zpdesc = pfn_zpdesc(obj >> ZS_OBJ_PFN_SHIFT);
- *obj_idx = (obj & ZS_OBJ_IDX_MASK);
+ *obj_idx = (obj & ZS_OBJ_IDX_MASK) - 1;
}

static void obj_to_zpdesc(unsigned long obj, struct zpdesc **zpdesc)
@@ -814,9 +819,11 @@ static unsigned long location_to_obj(struct zpdesc *zpdesc, unsigned int obj_idx
{
unsigned long obj;

+ VM_WARN_ON_ONCE(obj_idx + 1 > ZS_OBJ_IDX_MASK);
+
obj = zpdesc_pfn(zpdesc) << ZS_OBJ_PFN_SHIFT;
obj |= (unsigned long)(class_idx & ZS_OBJ_CLASS_MASK) << ZS_OBJ_IDX_BITS;
- obj |= obj_idx & ZS_OBJ_IDX_MASK;
+ obj |= (obj_idx + 1) & ZS_OBJ_IDX_MASK;

return obj;
}
--
2.56.0.506.g506a7eabc5-goog