diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index 6d26c5c6ac48..56f8e0fc3963 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -539,6 +539,8 @@ Flags bitfields such as page flags, gfp_flags
 ::
 
 	%pGp	referenced|uptodate|lru|active|private
+	%pGpb	node=0|zone=2|referenced|uptodate|lru|active|private
+	%pGpl	referenced|uptodate|lru|active|private|node=0|zone=2
 	%pGg	GFP_USER|GFP_DMA32|GFP_NOWARN
 	%pGv	read|exec|mayread|maywrite|mayexec|denywrite
 
diff --git a/lib/test_printf.c b/lib/test_printf.c
index 7ac87f18a10f..af2945bb730a 100644
--- a/lib/test_printf.c
+++ b/lib/test_printf.c
@@ -569,24 +569,130 @@ netdev_features(void)
 {
 }
 
+static void  __init
+page_flags_build_info(char *cmp_buf, int prefix, int sec, int node, int zone,
+		      int last_cpupid, int kasan_tag, unsigned long *flags)
+{
+	unsigned long size = prefix;
+
+#ifdef SECTION_IN_PAGE_FLAGS
+	*flags |= (sec & SECTIONS_MASK) << SECTIONS_PGSHIFT;
+	snprintf(cmp_buf + size, BUF_SIZE - size, "section=%#x|", sec);
+	size = strlen(cmp_buf);
+#endif
+
+	*flags |= ((node & NODES_MASK) << NODES_PGSHIFT) |
+		  ((zone & ZONES_MASK) << ZONES_PGSHIFT);
+	snprintf(cmp_buf + size, BUF_SIZE - size, "node=%d|zone=%d", node, zone);
+	size = strlen(cmp_buf);
+
+#ifndef LAST_CPUPID_NOT_IN_PAGE_FLAGS
+	*flags |= (last_cpupid & LAST_CPUPID_MASK) << LAST_CPUPID_PGSHIFT;
+	snprintf(cmp_buf + size, BUF_SIZE - size, "|lastcpupid=%#x", last_cpupid);
+	size = strlen(cmp_buf);
+#endif
+
+#if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS)
+	*flags |= (tag & KASAN_TAG_MASK) << KASAN_TAG_PGSHIFT;
+	snprintf(cmp_buf + size, BUF_SIZE - size, "|kasantag=%#x", tag);
+	size = strlen(cmp_buf);
+#endif
+}
+
 static void __init
-flags(void)
+page_flags_build_names(char *cmp_buf, int prefix, const char *expect,
+		       unsigned long flags, unsigned long *page_flags)
 {
+	*page_flags |= flags;
+	snprintf(cmp_buf + prefix, BUF_SIZE - prefix, "%s", expect);
+}
+
+static void __init
+__page_flags_test(const char *expect, unsigned long flags)
+{
+	test(expect, "%pGp", &flags);
+}
+
+static void __init
+page_flags_test_be(char *cmp_buf, int sec, int node, int zone,
+		   int last_cpupid, int kasan_tag, const char *name,
+		   unsigned long flags)
+{
+	unsigned long page_flags = 0;
+	int size;
+
+	page_flags_build_info(cmp_buf, 0, sec, node, zone, last_cpupid,
+			      kasan_tag, &page_flags);
+
+	if (*name) {
+		size = strlen(cmp_buf);
+		if (size < BUF_SIZE - 2) {
+			*(cmp_buf + size) = '|';
+			*(cmp_buf + size + 1) = '\0';
+		}
+		page_flags_build_names(cmp_buf, strlen(cmp_buf), name, flags, &page_flags);
+	}
+
+	test(cmp_buf, "%pGpb", &page_flags);
+}
+
+static void __init
+page_flags_test_le(char *cmp_buf, int sec, int node, int zone,
+		   int last_cpupid, int kasan_tag, const char *name,
+		   unsigned long flags)
+{
+	unsigned long page_flags = 0;
+	int size = 0;
+
+	if (*name) {
+		page_flags_build_names(cmp_buf, 0, name, flags, &page_flags);
+		size = strlen(cmp_buf);
+		if (size < BUF_SIZE - 2) {
+			*(cmp_buf + size) = '|';
+			*(cmp_buf + size + 1) = '\0';
+		}
+		size = strlen(cmp_buf);
+	}
+
+	page_flags_build_info(cmp_buf, size, sec, node, zone, last_cpupid,
+			      kasan_tag, &page_flags);
+
+	test(cmp_buf, "%pGpl", &page_flags);
+}
+
+static void __init
+page_flags_test(char *cmp_buf)
+{
+	char *name = "uptodate|dirty|lru|active|swapbacked";
 	unsigned long flags;
-	gfp_t gfp;
-	char *cmp_buffer;
 
 	flags = 0;
-	test("", "%pGp", &flags);
+	__page_flags_test("", flags);
+	page_flags_test_be(cmp_buf, 0, 0, 0, 0, 0, "", flags);
+	page_flags_test_le(cmp_buf, 0, 0, 0, 0, 0, "", flags);
 
-	/* Page flags should filter the zone id */
 	flags = 1UL << NR_PAGEFLAGS;
-	test("", "%pGp", &flags);
+	__page_flags_test("", flags);
 
 	flags |= 1UL << PG_uptodate | 1UL << PG_dirty | 1UL << PG_lru
-		| 1UL << PG_active | 1UL << PG_swapbacked;
-	test("uptodate|dirty|lru|active|swapbacked", "%pGp", &flags);
+		 | 1UL << PG_active | 1UL << PG_swapbacked;
+	__page_flags_test(name, flags);
+	page_flags_test_be(cmp_buf, 1, 1, 1, 0x1fffff, 1, name, flags);
+	page_flags_test_le(cmp_buf, 1, 1, 1, 0x1fffff, 1, name, flags);
+}
+
+static void __init
+flags(void)
+{
+	unsigned long flags;
+	gfp_t gfp;
+	char *cmp_buffer;
 
+	cmp_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
+	if (!cmp_buffer)
+		return;
+
+	page_flags_test(cmp_buffer);
 
 	flags = VM_READ | VM_EXEC | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC
 			| VM_DENYWRITE;
@@ -601,10 +707,6 @@ flags(void)
 	gfp = __GFP_ATOMIC;
 	test("__GFP_ATOMIC", "%pGg", &gfp);
 
-	cmp_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
-	if (!cmp_buffer)
-		return;
-
 	/* Any flags not translated by the table should remain numeric */
 	gfp = ~__GFP_BITS_MASK;
 	snprintf(cmp_buffer, BUF_SIZE, "%#lx", (unsigned long) gfp);
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 14c9a6af1b23..c912cc9bddb0 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -1916,6 +1916,115 @@ char *format_flags(char *buf, char *end, unsigned long flags,
 	return buf;
 }
 
+struct page_flags_layout {
+	int width;
+	int shift;
+	int mask;
+	const struct printf_spec *spec;
+	const char *name;
+};
+
+static const struct page_flags_layout pfl[] = {
+	{SECTIONS_WIDTH, SECTIONS_PGSHIFT, SECTIONS_MASK,
+	 &default_dec_spec, "section"},
+	{NODES_WIDTH, NODES_PGSHIFT, NODES_MASK,
+	 &default_dec_spec, "node"},
+	{ZONES_WIDTH, ZONES_PGSHIFT, ZONES_MASK,
+	 &default_dec_spec, "zone"},
+	{LAST_CPUPID_WIDTH, LAST_CPUPID_PGSHIFT, LAST_CPUPID_MASK,
+	 &default_flag_spec, "lastcpupid"},
+	{KASAN_TAG_WIDTH, KASAN_TAG_PGSHIFT, KASAN_TAG_MASK,
+	 &default_flag_spec, "kasantag"},
+};
+
+static
+char *__format_page_flags(char *buf, char *end, unsigned long flags)
+{
+	DECLARE_BITMAP(mask, ARRAY_SIZE(pfl));
+	unsigned long last;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pfl); i++)
+		__assign_bit(i, mask, pfl[i].width);
+
+	last = find_last_bit(mask, ARRAY_SIZE(pfl));
+
+	for_each_set_bit(i, mask, ARRAY_SIZE(pfl)) {
+		/* Format: Flag Name + '=' (equals sign) + Number + '|' (separator) */
+		buf = string(buf, end, pfl[i].name, *pfl[i].spec);
+
+		if (buf < end)
+			*buf = '=';
+		buf++;
+		buf = number(buf, end, (flags >> pfl[i].shift) & pfl[i].mask,
+			     *pfl[i].spec);
+
+		/* No separator for the last entry */
+		if (i != last) {
+			if (buf < end)
+				*buf = '|';
+			buf++;
+		}
+	}
+
+	return buf;
+}
+
+static
+char *format_page_flags_be(char *buf, char *end, unsigned long flags)
+{
+	unsigned long mask = BIT(NR_PAGEFLAGS) - 1;
+
+	buf = __format_page_flags(buf, end, flags);
+
+	if (flags & mask) {
+		if (buf < end)
+			*buf = '|';
+		buf++;
+	}
+
+	return format_flags(buf, end, flags & mask, pageflag_names);
+}
+
+static
+char *format_page_flags_le(char *buf, char *end, unsigned long flags)
+{
+	unsigned long mask = BIT(NR_PAGEFLAGS) - 1;
+
+	buf = format_flags(buf, end, flags & mask, pageflag_names);
+
+	if (flags & mask) {
+		if (buf < end)
+			*buf = '|';
+		buf++;
+	}
+
+	return __format_page_flags(buf, end, flags & ~mask);
+}
+
+static
+char *format_page_flags(char *buf, char *end, void *flags_ptr,
+			struct printf_spec spec, const char *fmt)
+{
+	unsigned long flags = *(unsigned long *)flags_ptr;
+	unsigned long mask = BIT(NR_PAGEFLAGS) - 1;
+
+	if (strlen(fmt) == 2) {
+		flags &= mask;
+		return format_flags(buf, end, flags, pageflag_names);
+	}
+
+	switch (fmt[2]) {
+	case 'b':
+		return format_page_flags_be(buf, end, flags);
+	case 'l':
+		return format_page_flags_le(buf, end, flags);
+	default:
+		flags &= mask;
+		return format_flags(buf, end, flags, pageflag_names);
+	}
+}
+
 static noinline_for_stack
 char *flags_string(char *buf, char *end, void *flags_ptr,
 		   struct printf_spec spec, const char *fmt)
@@ -1928,11 +2037,7 @@ char *flags_string(char *buf, char *end, void *flags_ptr,
 
 	switch (fmt[1]) {
 	case 'p':
-		flags = *(unsigned long *)flags_ptr;
-		/* Remove zone id */
-		flags &= (1UL << NR_PAGEFLAGS) - 1;
-		names = pageflag_names;
-		break;
+		return format_page_flags(buf, end, flags_ptr, spec, fmt);
 	case 'v':
 		flags = *(unsigned long *)flags_ptr;
 		names = vmaflag_names;
