enum { MAXIMUM_TEST_BUFFER_LEN = 1UL << 12 };
        size_t i;
        u8 *computed_output = NULL, *heap_src = NULL;
+       struct scatterlist sg_src;
        bool success = true, ret;
 
        heap_src = kmalloc(MAXIMUM_TEST_BUFFER_LEN, GFP_KERNEL);
                }
        }
 
+       for (i = 0; i < ARRAY_SIZE(chacha20poly1305_enc_vectors); ++i) {
+               if (chacha20poly1305_enc_vectors[i].nlen != 8)
+                       continue;
+               memcpy(heap_src, chacha20poly1305_enc_vectors[i].input,
+                      chacha20poly1305_enc_vectors[i].ilen);
+               sg_init_one(&sg_src, heap_src,
+                           chacha20poly1305_enc_vectors[i].ilen + POLY1305_DIGEST_SIZE);
+               chacha20poly1305_encrypt_sg_inplace(&sg_src,
+                       chacha20poly1305_enc_vectors[i].ilen,
+                       chacha20poly1305_enc_vectors[i].assoc,
+                       chacha20poly1305_enc_vectors[i].alen,
+                       get_unaligned_le64(chacha20poly1305_enc_vectors[i].nonce),
+                       chacha20poly1305_enc_vectors[i].key);
+               if (memcmp(heap_src,
+                                  chacha20poly1305_enc_vectors[i].output,
+                                  chacha20poly1305_enc_vectors[i].ilen +
+                                                       POLY1305_DIGEST_SIZE)) {
+                       pr_err("chacha20poly1305 sg encryption self-test %zu: FAIL\n",
+                              i + 1);
+                       success = false;
+               }
+       }
+
        for (i = 0; i < ARRAY_SIZE(chacha20poly1305_dec_vectors); ++i) {
                memset(computed_output, 0, MAXIMUM_TEST_BUFFER_LEN);
                ret = chacha20poly1305_decrypt(computed_output,
                }
        }
 
+       for (i = 0; i < ARRAY_SIZE(chacha20poly1305_dec_vectors); ++i) {
+               memcpy(heap_src, chacha20poly1305_dec_vectors[i].input,
+                      chacha20poly1305_dec_vectors[i].ilen);
+               sg_init_one(&sg_src, heap_src,
+                           chacha20poly1305_dec_vectors[i].ilen);
+               ret = chacha20poly1305_decrypt_sg_inplace(&sg_src,
+                       chacha20poly1305_dec_vectors[i].ilen,
+                       chacha20poly1305_dec_vectors[i].assoc,
+                       chacha20poly1305_dec_vectors[i].alen,
+                       get_unaligned_le64(chacha20poly1305_dec_vectors[i].nonce),
+                       chacha20poly1305_dec_vectors[i].key);
+               if (!decryption_success(ret,
+                       chacha20poly1305_dec_vectors[i].failure,
+                       memcmp(heap_src, chacha20poly1305_dec_vectors[i].output,
+                              chacha20poly1305_dec_vectors[i].ilen -
+                                                       POLY1305_DIGEST_SIZE))) {
+                       pr_err("chacha20poly1305 sg decryption self-test %zu: FAIL\n",
+                              i + 1);
+                       success = false;
+               }
+       }
 
        for (i = 0; i < ARRAY_SIZE(xchacha20poly1305_enc_vectors); ++i) {
                memset(computed_output, 0, MAXIMUM_TEST_BUFFER_LEN);
 
 #include <crypto/chacha20poly1305.h>
 #include <crypto/chacha.h>
 #include <crypto/poly1305.h>
+#include <crypto/scatterwalk.h>
 
 #include <asm/unaligned.h>
 #include <linux/kernel.h>
 }
 EXPORT_SYMBOL(xchacha20poly1305_decrypt);
 
+static
+bool chacha20poly1305_crypt_sg_inplace(struct scatterlist *src,
+                                      const size_t src_len,
+                                      const u8 *ad, const size_t ad_len,
+                                      const u64 nonce,
+                                      const u8 key[CHACHA20POLY1305_KEY_SIZE],
+                                      int encrypt)
+{
+       const u8 *pad0 = page_address(ZERO_PAGE(0));
+       struct poly1305_desc_ctx poly1305_state;
+       u32 chacha_state[CHACHA_STATE_WORDS];
+       struct sg_mapping_iter miter;
+       size_t partial = 0;
+       unsigned int flags;
+       bool ret = true;
+       int sl;
+       union {
+               struct {
+                       u32 k[CHACHA_KEY_WORDS];
+                       __le64 iv[2];
+               };
+               u8 block0[POLY1305_KEY_SIZE];
+               u8 chacha_stream[CHACHA_BLOCK_SIZE];
+               struct {
+                       u8 mac[2][POLY1305_DIGEST_SIZE];
+               };
+               __le64 lens[2];
+       } b __aligned(16);
+
+       chacha_load_key(b.k, key);
+
+       b.iv[0] = 0;
+       b.iv[1] = cpu_to_le64(nonce);
+
+       chacha_init(chacha_state, b.k, (u8 *)b.iv);
+       chacha_crypt(chacha_state, b.block0, pad0, sizeof(b.block0), 20);
+       poly1305_init(&poly1305_state, b.block0);
+
+       if (unlikely(ad_len)) {
+               poly1305_update(&poly1305_state, ad, ad_len);
+               if (ad_len & 0xf)
+                       poly1305_update(&poly1305_state, pad0, 0x10 - (ad_len & 0xf));
+       }
+
+       flags = SG_MITER_TO_SG;
+       if (!preemptible())
+               flags |= SG_MITER_ATOMIC;
+
+       sg_miter_start(&miter, src, sg_nents(src), flags);
+
+       for (sl = src_len; sl > 0 && sg_miter_next(&miter); sl -= miter.length) {
+               u8 *addr = miter.addr;
+               size_t length = min_t(size_t, sl, miter.length);
+
+               if (!encrypt)
+                       poly1305_update(&poly1305_state, addr, length);
+
+               if (unlikely(partial)) {
+                       size_t l = min(length, CHACHA_BLOCK_SIZE - partial);
+
+                       crypto_xor(addr, b.chacha_stream + partial, l);
+                       partial = (partial + l) & (CHACHA_BLOCK_SIZE - 1);
+
+                       addr += l;
+                       length -= l;
+               }
+
+               if (likely(length >= CHACHA_BLOCK_SIZE || length == sl)) {
+                       size_t l = length;
+
+                       if (unlikely(length < sl))
+                               l &= ~(CHACHA_BLOCK_SIZE - 1);
+                       chacha_crypt(chacha_state, addr, addr, l, 20);
+                       addr += l;
+                       length -= l;
+               }
+
+               if (unlikely(length > 0)) {
+                       chacha_crypt(chacha_state, b.chacha_stream, pad0,
+                                    CHACHA_BLOCK_SIZE, 20);
+                       crypto_xor(addr, b.chacha_stream, length);
+                       partial = length;
+               }
+
+               if (encrypt)
+                       poly1305_update(&poly1305_state, miter.addr,
+                                       min_t(size_t, sl, miter.length));
+       }
+
+       if (src_len & 0xf)
+               poly1305_update(&poly1305_state, pad0, 0x10 - (src_len & 0xf));
+
+       b.lens[0] = cpu_to_le64(ad_len);
+       b.lens[1] = cpu_to_le64(src_len);
+       poly1305_update(&poly1305_state, (u8 *)b.lens, sizeof(b.lens));
+
+       if (likely(sl <= -POLY1305_DIGEST_SIZE)) {
+               if (encrypt) {
+                       poly1305_final(&poly1305_state,
+                                      miter.addr + miter.length + sl);
+                       ret = true;
+               } else {
+                       poly1305_final(&poly1305_state, b.mac[0]);
+                       ret = !crypto_memneq(b.mac[0],
+                                            miter.addr + miter.length + sl,
+                                            POLY1305_DIGEST_SIZE);
+               }
+       }
+
+       sg_miter_stop(&miter);
+
+       if (unlikely(sl > -POLY1305_DIGEST_SIZE)) {
+               poly1305_final(&poly1305_state, b.mac[1]);
+               scatterwalk_map_and_copy(b.mac[encrypt], src, src_len,
+                                        sizeof(b.mac[1]), encrypt);
+               ret = encrypt ||
+                     !crypto_memneq(b.mac[0], b.mac[1], POLY1305_DIGEST_SIZE);
+       }
+
+       memzero_explicit(chacha_state, sizeof(chacha_state));
+       memzero_explicit(&b, sizeof(b));
+
+       return ret;
+}
+
+bool chacha20poly1305_encrypt_sg_inplace(struct scatterlist *src, size_t src_len,
+                                        const u8 *ad, const size_t ad_len,
+                                        const u64 nonce,
+                                        const u8 key[CHACHA20POLY1305_KEY_SIZE])
+{
+       return chacha20poly1305_crypt_sg_inplace(src, src_len, ad, ad_len,
+                                                nonce, key, 1);
+}
+EXPORT_SYMBOL(chacha20poly1305_encrypt_sg_inplace);
+
+bool chacha20poly1305_decrypt_sg_inplace(struct scatterlist *src, size_t src_len,
+                                        const u8 *ad, const size_t ad_len,
+                                        const u64 nonce,
+                                        const u8 key[CHACHA20POLY1305_KEY_SIZE])
+{
+       if (unlikely(src_len < POLY1305_DIGEST_SIZE))
+               return false;
+
+       return chacha20poly1305_crypt_sg_inplace(src,
+                                                src_len - POLY1305_DIGEST_SIZE,
+                                                ad, ad_len, nonce, key, 0);
+}
+EXPORT_SYMBOL(chacha20poly1305_decrypt_sg_inplace);
+
 static int __init mod_init(void)
 {
        if (!IS_ENABLED(CONFIG_CRYPTO_MANAGER_DISABLE_TESTS) &&