#include <linux/errno.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/page-flags.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <linux/string.h>
        req->base.err = err;
        state = &req->chain;
 
-       if (state->src)
+       if (state->flags & CRYPTO_ACOMP_REQ_SRC_VIRT)
                acomp_request_set_src_dma(req, state->src, slen);
-       if (state->dst)
+       else if (state->flags & CRYPTO_ACOMP_REQ_SRC_FOLIO)
+               acomp_request_set_src_folio(req, state->sfolio, state->soff, slen);
+       if (state->flags & CRYPTO_ACOMP_REQ_DST_VIRT)
                acomp_request_set_dst_dma(req, state->dst, dlen);
-       state->src = NULL;
-       state->dst = NULL;
+       else if (state->flags & CRYPTO_ACOMP_REQ_DST_FOLIO)
+               acomp_request_set_dst_folio(req, state->dfolio, state->doff, dlen);
 }
 
 static void acomp_virt_to_sg(struct acomp_req *req)
 {
        struct acomp_req_chain *state = &req->chain;
 
+       state->flags = req->base.flags & (CRYPTO_ACOMP_REQ_SRC_VIRT |
+                                         CRYPTO_ACOMP_REQ_DST_VIRT |
+                                         CRYPTO_ACOMP_REQ_SRC_FOLIO |
+                                         CRYPTO_ACOMP_REQ_DST_FOLIO);
+
        if (acomp_request_src_isvirt(req)) {
                unsigned int slen = req->slen;
                const u8 *svirt = req->svirt;
                state->src = svirt;
                sg_init_one(&state->ssg, svirt, slen);
                acomp_request_set_src_sg(req, &state->ssg, slen);
+       } else if (acomp_request_src_isfolio(req)) {
+               struct folio *folio = req->sfolio;
+               unsigned int slen = req->slen;
+               size_t off = req->soff;
+
+               state->sfolio = folio;
+               state->soff = off;
+               sg_init_table(&state->ssg, 1);
+               sg_set_page(&state->ssg, folio_page(folio, off / PAGE_SIZE),
+                           slen, off % PAGE_SIZE);
+               acomp_request_set_src_sg(req, &state->ssg, slen);
        }
 
        if (acomp_request_dst_isvirt(req)) {
                state->dst = dvirt;
                sg_init_one(&state->dsg, dvirt, dlen);
                acomp_request_set_dst_sg(req, &state->dsg, dlen);
+       } else if (acomp_request_dst_isfolio(req)) {
+               struct folio *folio = req->dfolio;
+               unsigned int dlen = req->dlen;
+               size_t off = req->doff;
+
+               state->dfolio = folio;
+               state->doff = off;
+               sg_init_table(&state->dsg, 1);
+               sg_set_page(&state->dsg, folio_page(folio, off / PAGE_SIZE),
+                           dlen, off % PAGE_SIZE);
+               acomp_request_set_src_sg(req, &state->dsg, dlen);
        }
 }
 
        int err;
 
        if (crypto_acomp_req_chain(tfm) ||
-           (!acomp_request_chained(req) && !acomp_request_isvirt(req)))
+           (!acomp_request_chained(req) && acomp_request_issg(req)))
                return op(req);
 
        if (acomp_is_async(tfm)) {
 
        unsigned int slen = req->slen;
        unsigned int dlen = req->dlen;
        struct page *spage, *dpage;
-       unsigned int soff, doff;
        unsigned int n;
        const u8 *src;
+       size_t soff;
+       size_t doff;
        u8 *dst;
        int ret;
 
        if (acomp_request_src_isvirt(req))
                src = req->svirt;
        else {
-               soff = req->src->offset;
-               spage = nth_page(sg_page(req->src), soff / PAGE_SIZE);
-               soff = offset_in_page(soff);
-
-               n = slen / PAGE_SIZE;
-               n += (offset_in_page(slen) + soff - 1) / PAGE_SIZE;
-               if (slen <= req->src->length &&
-                   (!PageHighMem(nth_page(spage, n)) ||
-                    size_add(soff, slen) <= PAGE_SIZE))
+               src = scratch->src;
+               do {
+                       if (acomp_request_src_isfolio(req)) {
+                               spage = folio_page(req->sfolio, 0);
+                               soff = req->soff;
+                       } else if (slen <= req->src->length) {
+                               spage = sg_page(req->src);
+                               soff = req->src->offset;
+                       } else
+                               break;
+
+                       spage = nth_page(spage, soff / PAGE_SIZE);
+                       soff = offset_in_page(soff);
+
+                       n = slen / PAGE_SIZE;
+                       n += (offset_in_page(slen) + soff - 1) / PAGE_SIZE;
+                       if (PageHighMem(nth_page(spage, n)) &&
+                           size_add(soff, slen) > PAGE_SIZE)
+                               break;
                        src = kmap_local_page(spage) + soff;
-               else
-                       src = scratch->src;
+               } while (0);
        }
 
        if (acomp_request_dst_isvirt(req))
                dst = req->dvirt;
        else {
-               doff = req->dst->offset;
-               dpage = nth_page(sg_page(req->dst), doff / PAGE_SIZE);
-               doff = offset_in_page(doff);
-
-               n = dlen / PAGE_SIZE;
-               n += (offset_in_page(dlen) + doff - 1) / PAGE_SIZE;
-               if (dlen <= req->dst->length &&
-                   (!PageHighMem(nth_page(dpage, n)) ||
-                    size_add(doff, dlen) <= PAGE_SIZE))
+               unsigned int max = SCOMP_SCRATCH_SIZE;
+
+               dst = scratch->dst;
+               do {
+                       if (acomp_request_dst_isfolio(req)) {
+                               dpage = folio_page(req->dfolio, 0);
+                               doff = req->doff;
+                       } else if (dlen <= req->dst->length) {
+                               dpage = sg_page(req->dst);
+                               doff = req->dst->offset;
+                       } else
+                               break;
+
+                       dpage = nth_page(dpage, doff / PAGE_SIZE);
+                       doff = offset_in_page(doff);
+
+                       n = dlen / PAGE_SIZE;
+                       n += (offset_in_page(dlen) + doff - 1) / PAGE_SIZE;
+                       if (PageHighMem(dpage + n) &&
+                           size_add(doff, dlen) > PAGE_SIZE)
+                               break;
                        dst = kmap_local_page(dpage) + doff;
-               else {
-                       if (dlen > SCOMP_SCRATCH_SIZE)
-                               dlen = SCOMP_SCRATCH_SIZE;
-                       dst = scratch->dst;
-               }
+                       max = dlen;
+               } while (0);
+               dlen = min(dlen, max);
        }
 
        spin_lock_bh(&scratch->lock);
 
 /* Set this bit for if virtual address destination cannot be used for DMA. */
 #define CRYPTO_ACOMP_REQ_DST_NONDMA    0x00000010
 
+/* Set this bit if source is a folio. */
+#define CRYPTO_ACOMP_REQ_SRC_FOLIO     0x00000020
+
+/* Set this bit if destination is a folio. */
+#define CRYPTO_ACOMP_REQ_DST_FOLIO     0x00000040
+
 #define CRYPTO_ACOMP_DST_MAX           131072
 
 #define        MAX_SYNC_COMP_REQSIZE           0
                 __##name##_req, (tfm), (gfp), false)
 
 struct acomp_req;
+struct folio;
 
 struct acomp_req_chain {
        struct list_head head;
        void *data;
        struct scatterlist ssg;
        struct scatterlist dsg;
-       const u8 *src;
-       u8 *dst;
+       union {
+               const u8 *src;
+               struct folio *sfolio;
+       };
+       union {
+               u8 *dst;
+               struct folio *dfolio;
+       };
+       size_t soff;
+       size_t doff;
+       u32 flags;
 };
 
 /**
  * struct acomp_req - asynchronous (de)compression request
  *
  * @base:      Common attributes for asynchronous crypto requests
- * @src:       Source Data
- * @dst:       Destination data
+ * @src:       Source scatterlist
+ * @dst:       Destination scatterlist
+ * @svirt:     Source virtual address
+ * @dvirt:     Destination virtual address
+ * @sfolio:    Source folio
+ * @soff:      Source folio offset
+ * @dfolio:    Destination folio
+ * @doff:      Destination folio offset
  * @slen:      Size of the input buffer
  * @dlen:      Size of the output buffer and number of bytes produced
  * @chain:     Private API code data, do not use
        union {
                struct scatterlist *src;
                const u8 *svirt;
+               struct folio *sfolio;
        };
        union {
                struct scatterlist *dst;
                u8 *dvirt;
+               struct folio *dfolio;
        };
+       size_t soff;
+       size_t doff;
        unsigned int slen;
        unsigned int dlen;
 
 {
        u32 keep = CRYPTO_ACOMP_REQ_SRC_VIRT | CRYPTO_ACOMP_REQ_SRC_NONDMA |
                   CRYPTO_ACOMP_REQ_DST_VIRT | CRYPTO_ACOMP_REQ_DST_NONDMA |
+                  CRYPTO_ACOMP_REQ_SRC_FOLIO | CRYPTO_ACOMP_REQ_DST_FOLIO |
                   CRYPTO_TFM_REQ_ON_STACK;
 
        req->base.complete = cmpl;
 
        req->base.flags &= ~(CRYPTO_ACOMP_REQ_SRC_VIRT |
                             CRYPTO_ACOMP_REQ_SRC_NONDMA |
+                            CRYPTO_ACOMP_REQ_SRC_FOLIO |
+                            CRYPTO_ACOMP_REQ_DST_FOLIO |
                             CRYPTO_ACOMP_REQ_DST_VIRT |
                             CRYPTO_ACOMP_REQ_DST_NONDMA);
 }
 
        req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_NONDMA;
        req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_VIRT;
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_FOLIO;
 }
 
 /**
        req->slen = slen;
 
        req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_NONDMA;
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_FOLIO;
        req->base.flags |= CRYPTO_ACOMP_REQ_SRC_VIRT;
 }
 
        req->svirt = src;
        req->slen = slen;
 
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_FOLIO;
        req->base.flags |= CRYPTO_ACOMP_REQ_SRC_NONDMA;
        req->base.flags |= CRYPTO_ACOMP_REQ_SRC_VIRT;
 }
 
+/**
+ * acomp_request_set_src_folio() -- Sets source folio
+ *
+ * Sets source folio required by an acomp operation.
+ *
+ * @req:       asynchronous compress request
+ * @folio:     pointer to input folio
+ * @off:       input folio offset
+ * @len:       size of the input buffer
+ */
+static inline void acomp_request_set_src_folio(struct acomp_req *req,
+                                              struct folio *folio, size_t off,
+                                              unsigned int len)
+{
+       req->sfolio = folio;
+       req->soff = off;
+       req->slen = len;
+
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_NONDMA;
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_SRC_VIRT;
+       req->base.flags |= CRYPTO_ACOMP_REQ_SRC_FOLIO;
+}
+
 /**
  * acomp_request_set_dst_sg() -- Sets destination scatterlist
  *
 
        req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_NONDMA;
        req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_VIRT;
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_FOLIO;
 }
 
 /**
        req->dlen = dlen;
 
        req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_NONDMA;
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_FOLIO;
        req->base.flags |= CRYPTO_ACOMP_REQ_DST_VIRT;
 }
 
        req->dvirt = dst;
        req->dlen = dlen;
 
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_FOLIO;
        req->base.flags |= CRYPTO_ACOMP_REQ_DST_NONDMA;
        req->base.flags |= CRYPTO_ACOMP_REQ_DST_VIRT;
 }
 
+/**
+ * acomp_request_set_dst_folio() -- Sets destination folio
+ *
+ * Sets destination folio required by an acomp operation.
+ *
+ * @req:       asynchronous compress request
+ * @folio:     pointer to input folio
+ * @off:       input folio offset
+ * @len:       size of the input buffer
+ */
+static inline void acomp_request_set_dst_folio(struct acomp_req *req,
+                                              struct folio *folio, size_t off,
+                                              unsigned int len)
+{
+       req->dfolio = folio;
+       req->doff = off;
+       req->dlen = len;
+
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_NONDMA;
+       req->base.flags &= ~CRYPTO_ACOMP_REQ_DST_VIRT;
+       req->base.flags |= CRYPTO_ACOMP_REQ_DST_FOLIO;
+}
+
 static inline void acomp_request_chain(struct acomp_req *req,
                                       struct acomp_req *head)
 {
 
        return crypto_request_chained(&req->base);
 }
 
+static inline bool acomp_request_issg(struct acomp_req *req)
+{
+       return !(req->base.flags & (CRYPTO_ACOMP_REQ_SRC_VIRT |
+                                   CRYPTO_ACOMP_REQ_DST_VIRT |
+                                   CRYPTO_ACOMP_REQ_SRC_FOLIO |
+                                   CRYPTO_ACOMP_REQ_DST_FOLIO));
+}
+
 static inline bool acomp_request_src_isvirt(struct acomp_req *req)
 {
        return req->base.flags & CRYPTO_ACOMP_REQ_SRC_VIRT;
                                  CRYPTO_ACOMP_REQ_DST_NONDMA);
 }
 
+static inline bool acomp_request_src_isfolio(struct acomp_req *req)
+{
+       return req->base.flags & CRYPTO_ACOMP_REQ_SRC_FOLIO;
+}
+
+static inline bool acomp_request_dst_isfolio(struct acomp_req *req)
+{
+       return req->base.flags & CRYPTO_ACOMP_REQ_DST_FOLIO;
+}
+
 static inline bool crypto_acomp_req_chain(struct crypto_acomp *tfm)
 {
        return crypto_tfm_req_chain(&tfm->base);