git: 9bf3a5616c51 - stable/13 - LinuxKPI: add simplified version of page_frag_cache

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Wed, 18 Jan 2023 16:24:52 UTC
The branch stable/13 has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=9bf3a5616c51081b221cbb794f559a5aab07aabc

commit 9bf3a5616c51081b221cbb794f559a5aab07aabc
Author:     Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2022-12-03 00:33:34 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2023-01-18 13:25:11 +0000

    LinuxKPI: add simplified version of page_frag_cache
    
    For the moment and the currently only consumer (mt76) add a simplified
    version of the page_frag_cache.  We will only accept fragement sizes up
    to 1 PAGE_SIZE (KASSERT) and we will always return a full page.
    Should we add more consumers or small (or large) objects would become a
    problem we can always add a more elaborate version.
    
    Discussed with: markj
    Reviewed by:    markj (,hselasky commented as well)
    MFC after:      3 days
    Differential Revision: https://reviews.freebsd.org/D37595
    
    (cherry picked from commit 55038a6306a570c9f2df89f5ad076de0f7d98152)
    
    LinuxKPI: fix possible NULL dereference in linuxkpi_page_frag_alloc()
    
    Reported by:    Coverity via emaste
    Coverity ID:    1502345
    Sponsored by:   The FreeBSD Foundation
    
    (cherry picked from commit 51e94a4658d23016dd0ae67c189a89f53281cbcd)
---
 sys/compat/linuxkpi/common/include/linux/gfp.h | 29 ++++++++++++++++
 sys/compat/linuxkpi/common/src/linux_page.c    | 47 ++++++++++++++++++++++++++
 2 files changed, 76 insertions(+)

diff --git a/sys/compat/linuxkpi/common/include/linux/gfp.h b/sys/compat/linuxkpi/common/include/linux/gfp.h
index 6273fa969db8..7657e78b0950 100644
--- a/sys/compat/linuxkpi/common/include/linux/gfp.h
+++ b/sys/compat/linuxkpi/common/include/linux/gfp.h
@@ -80,6 +80,11 @@
 CTASSERT((__GFP_DMA32 & GFP_NATIVE_MASK) == 0);
 CTASSERT((__GFP_BITS_MASK & GFP_NATIVE_MASK) == GFP_NATIVE_MASK);
 
+struct page_frag_cache {
+	void *va;
+	int pagecnt_bias;
+};
+
 /*
  * Resolve a page into a virtual address:
  *
@@ -94,6 +99,9 @@ extern void *linux_page_address(struct page *);
  */
 extern vm_page_t linux_alloc_pages(gfp_t flags, unsigned int order);
 extern void linux_free_pages(vm_page_t page, unsigned int order);
+void *linuxkpi_page_frag_alloc(struct page_frag_cache *, size_t, gfp_t);
+void linuxkpi_page_frag_free(void *);
+void linuxkpi__page_frag_cache_drain(struct page *, size_t);
 
 static inline struct page *
 alloc_page(gfp_t flags)
@@ -175,6 +183,27 @@ free_page(uintptr_t addr)
 	linux_free_kmem(addr, 0);
 }
 
+static inline void *
+page_frag_alloc(struct page_frag_cache *pfc, size_t fragsz, gfp_t gfp)
+{
+
+	return (linuxkpi_page_frag_alloc(pfc, fragsz, gfp));
+}
+
+static inline void
+page_frag_free(void *addr)
+{
+
+	linuxkpi_page_frag_free(addr);
+}
+
+static inline void
+__page_frag_cache_drain(struct page *page, size_t count)
+{
+
+	linuxkpi__page_frag_cache_drain(page, count);
+}
+
 static inline bool
 gfpflags_allow_blocking(const gfp_t gfp_flags)
 {
diff --git a/sys/compat/linuxkpi/common/src/linux_page.c b/sys/compat/linuxkpi/common/src/linux_page.c
index df4a124cf3e2..5fa370147045 100644
--- a/sys/compat/linuxkpi/common/src/linux_page.c
+++ b/sys/compat/linuxkpi/common/src/linux_page.c
@@ -429,3 +429,50 @@ lkpi_arch_phys_wc_del(int reg)
 	free(mrdesc, M_LKMTRR);
 #endif
 }
+
+/*
+ * This is a highly simplified version of the Linux page_frag_cache.
+ * We only support up-to 1 single page as fragment size and we will
+ * always return a full page.  This may be wasteful on small objects
+ * but the only known consumer (mt76) is either asking for a half-page
+ * or a full page.  If this was to become a problem we can implement
+ * a more elaborate version.
+ */
+void *
+linuxkpi_page_frag_alloc(struct page_frag_cache *pfc,
+    size_t fragsz, gfp_t gfp)
+{
+	vm_page_t pages;
+
+	if (fragsz == 0)
+		return (NULL);
+
+	KASSERT(fragsz <= PAGE_SIZE, ("%s: fragsz %zu > PAGE_SIZE not yet "
+	    "supported", __func__, fragsz));
+
+	pages = alloc_pages(gfp, flsl(howmany(fragsz, PAGE_SIZE) - 1));
+	if (pages == NULL)
+		return (NULL);
+	pfc->va = linux_page_address(pages);
+
+	/* Passed in as "count" to __page_frag_cache_drain(). Unused by us. */
+	pfc->pagecnt_bias = 0;
+
+	return (pfc->va);
+}
+
+void
+linuxkpi_page_frag_free(void *addr)
+{
+	vm_page_t page;
+
+	page = PHYS_TO_VM_PAGE(vtophys(addr));
+	linux_free_pages(page, 0);
+}
+
+void
+linuxkpi__page_frag_cache_drain(struct page *page, size_t count __unused)
+{
+
+	linux_free_pages(page, 0);
+}