summaryrefslogtreecommitdiff
path: root/crypto/scatterwalk.c
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/scatterwalk.c')
-rw-r--r--crypto/scatterwalk.c230
1 files changed, 154 insertions, 76 deletions
diff --git a/crypto/scatterwalk.c b/crypto/scatterwalk.c
index 7281b8a93ad3..be0e24843806 100644
--- a/crypto/scatterwalk.c
+++ b/crypto/scatterwalk.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Cryptographic API.
*
@@ -6,121 +7,198 @@
* Copyright (c) 2002 James Morris <jmorris@intercode.com.au>
* 2002 Adam J. Richter <adam@yggdrasil.com>
* 2004 Jean-Luc Cooke <jlcooke@certainkey.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 2 of the License, or (at your option)
- * any later version.
- *
*/
#include <crypto/scatterwalk.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
-#include <linux/pagemap.h>
-#include <linux/highmem.h>
#include <linux/scatterlist.h>
-static inline void memcpy_dir(void *buf, void *sgdata, size_t nbytes, int out)
+void scatterwalk_skip(struct scatter_walk *walk, unsigned int nbytes)
{
- void *src = out ? buf : sgdata;
- void *dst = out ? sgdata : buf;
+ struct scatterlist *sg = walk->sg;
- memcpy(dst, src, nbytes);
-}
+ nbytes += walk->offset - sg->offset;
-void scatterwalk_start(struct scatter_walk *walk, struct scatterlist *sg)
-{
+ while (nbytes > sg->length) {
+ nbytes -= sg->length;
+ sg = sg_next(sg);
+ }
walk->sg = sg;
+ walk->offset = sg->offset + nbytes;
+}
+EXPORT_SYMBOL_GPL(scatterwalk_skip);
- BUG_ON(!sg->length);
-
- walk->offset = sg->offset;
+inline void memcpy_from_scatterwalk(void *buf, struct scatter_walk *walk,
+ unsigned int nbytes)
+{
+ do {
+ unsigned int to_copy;
+
+ to_copy = scatterwalk_next(walk, nbytes);
+ memcpy(buf, walk->addr, to_copy);
+ scatterwalk_done_src(walk, to_copy);
+ buf += to_copy;
+ nbytes -= to_copy;
+ } while (nbytes);
}
-EXPORT_SYMBOL_GPL(scatterwalk_start);
+EXPORT_SYMBOL_GPL(memcpy_from_scatterwalk);
-void *scatterwalk_map(struct scatter_walk *walk)
+inline void memcpy_to_scatterwalk(struct scatter_walk *walk, const void *buf,
+ unsigned int nbytes)
{
- return kmap_atomic(scatterwalk_page(walk)) +
- offset_in_page(walk->offset);
+ do {
+ unsigned int to_copy;
+
+ to_copy = scatterwalk_next(walk, nbytes);
+ memcpy(walk->addr, buf, to_copy);
+ scatterwalk_done_dst(walk, to_copy);
+ buf += to_copy;
+ nbytes -= to_copy;
+ } while (nbytes);
}
-EXPORT_SYMBOL_GPL(scatterwalk_map);
+EXPORT_SYMBOL_GPL(memcpy_to_scatterwalk);
-static void scatterwalk_pagedone(struct scatter_walk *walk, int out,
- unsigned int more)
+void memcpy_from_sglist(void *buf, struct scatterlist *sg,
+ unsigned int start, unsigned int nbytes)
{
- if (out) {
- struct page *page;
+ struct scatter_walk walk;
- page = sg_page(walk->sg) + ((walk->offset - 1) >> PAGE_SHIFT);
- if (!PageSlab(page))
- flush_dcache_page(page);
- }
+ if (unlikely(nbytes == 0)) /* in case sg == NULL */
+ return;
- if (more) {
- walk->offset += PAGE_SIZE - 1;
- walk->offset &= PAGE_MASK;
- if (walk->offset >= walk->sg->offset + walk->sg->length)
- scatterwalk_start(walk, scatterwalk_sg_next(walk->sg));
- }
+ scatterwalk_start_at_pos(&walk, sg, start);
+ memcpy_from_scatterwalk(buf, &walk, nbytes);
}
+EXPORT_SYMBOL_GPL(memcpy_from_sglist);
-void scatterwalk_done(struct scatter_walk *walk, int out, int more)
+void memcpy_to_sglist(struct scatterlist *sg, unsigned int start,
+ const void *buf, unsigned int nbytes)
{
- if (!(scatterwalk_pagelen(walk) & (PAGE_SIZE - 1)) || !more)
- scatterwalk_pagedone(walk, out, more);
-}
-EXPORT_SYMBOL_GPL(scatterwalk_done);
+ struct scatter_walk walk;
-void scatterwalk_copychunks(void *buf, struct scatter_walk *walk,
- size_t nbytes, int out)
-{
- for (;;) {
- unsigned int len_this_page = scatterwalk_pagelen(walk);
- u8 *vaddr;
+ if (unlikely(nbytes == 0)) /* in case sg == NULL */
+ return;
- if (len_this_page > nbytes)
- len_this_page = nbytes;
+ scatterwalk_start_at_pos(&walk, sg, start);
+ memcpy_to_scatterwalk(&walk, buf, nbytes);
+}
+EXPORT_SYMBOL_GPL(memcpy_to_sglist);
- vaddr = scatterwalk_map(walk);
- memcpy_dir(buf, vaddr, len_this_page, out);
- scatterwalk_unmap(vaddr);
+/**
+ * memcpy_sglist() - Copy data from one scatterlist to another
+ * @dst: The destination scatterlist. Can be NULL if @nbytes == 0.
+ * @src: The source scatterlist. Can be NULL if @nbytes == 0.
+ * @nbytes: Number of bytes to copy
+ *
+ * The scatterlists can describe exactly the same memory, in which case this
+ * function is a no-op. No other overlaps are supported.
+ *
+ * Context: Any context
+ */
+void memcpy_sglist(struct scatterlist *dst, struct scatterlist *src,
+ unsigned int nbytes)
+{
+ unsigned int src_offset, dst_offset;
- scatterwalk_advance(walk, len_this_page);
+ if (unlikely(nbytes == 0)) /* in case src and/or dst is NULL */
+ return;
- if (nbytes == len_this_page)
+ src_offset = src->offset;
+ dst_offset = dst->offset;
+ for (;;) {
+ /* Compute the length to copy this step. */
+ unsigned int len = min3(src->offset + src->length - src_offset,
+ dst->offset + dst->length - dst_offset,
+ nbytes);
+ struct page *src_page = sg_page(src);
+ struct page *dst_page = sg_page(dst);
+ const void *src_virt;
+ void *dst_virt;
+
+ if (IS_ENABLED(CONFIG_HIGHMEM)) {
+ /* HIGHMEM: we may have to actually map the pages. */
+ const unsigned int src_oip = offset_in_page(src_offset);
+ const unsigned int dst_oip = offset_in_page(dst_offset);
+ const unsigned int limit = PAGE_SIZE;
+
+ /* Further limit len to not cross a page boundary. */
+ len = min3(len, limit - src_oip, limit - dst_oip);
+
+ /* Compute the source and destination pages. */
+ src_page += src_offset / PAGE_SIZE;
+ dst_page += dst_offset / PAGE_SIZE;
+
+ if (src_page != dst_page) {
+ /* Copy between different pages. */
+ memcpy_page(dst_page, dst_oip,
+ src_page, src_oip, len);
+ flush_dcache_page(dst_page);
+ } else if (src_oip != dst_oip) {
+ /* Copy between different parts of same page. */
+ dst_virt = kmap_local_page(dst_page);
+ memcpy(dst_virt + dst_oip, dst_virt + src_oip,
+ len);
+ kunmap_local(dst_virt);
+ flush_dcache_page(dst_page);
+ } /* Else, it's the same memory. No action needed. */
+ } else {
+ /*
+ * !HIGHMEM: no mapping needed. Just work in the linear
+ * buffer of each sg entry. Note that we can cross page
+ * boundaries, as they are not significant in this case.
+ */
+ src_virt = page_address(src_page) + src_offset;
+ dst_virt = page_address(dst_page) + dst_offset;
+ if (src_virt != dst_virt) {
+ memcpy(dst_virt, src_virt, len);
+ if (ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE)
+ __scatterwalk_flush_dcache_pages(
+ dst_page, dst_offset, len);
+ } /* Else, it's the same memory. No action needed. */
+ }
+ nbytes -= len;
+ if (nbytes == 0) /* No more to copy? */
break;
- buf += len_this_page;
- nbytes -= len_this_page;
-
- scatterwalk_pagedone(walk, out, 1);
+ /*
+ * There's more to copy. Advance the offsets by the length
+ * copied this step, and advance the sg entries as needed.
+ */
+ src_offset += len;
+ if (src_offset >= src->offset + src->length) {
+ src = sg_next(src);
+ src_offset = src->offset;
+ }
+ dst_offset += len;
+ if (dst_offset >= dst->offset + dst->length) {
+ dst = sg_next(dst);
+ dst_offset = dst->offset;
+ }
}
}
-EXPORT_SYMBOL_GPL(scatterwalk_copychunks);
+EXPORT_SYMBOL_GPL(memcpy_sglist);
-void scatterwalk_map_and_copy(void *buf, struct scatterlist *sg,
- unsigned int start, unsigned int nbytes, int out)
+struct scatterlist *scatterwalk_ffwd(struct scatterlist dst[2],
+ struct scatterlist *src,
+ unsigned int len)
{
- struct scatter_walk walk;
- unsigned int offset = 0;
-
- if (!nbytes)
- return;
-
for (;;) {
- scatterwalk_start(&walk, sg);
+ if (!len)
+ return src;
- if (start < offset + sg->length)
+ if (src->length > len)
break;
- offset += sg->length;
- sg = scatterwalk_sg_next(sg);
+ len -= src->length;
+ src = sg_next(src);
}
- scatterwalk_advance(&walk, start - offset);
- scatterwalk_copychunks(buf, &walk, nbytes, out);
- scatterwalk_done(&walk, out, 0);
+ sg_init_table(dst, 2);
+ sg_set_page(dst, sg_page(src), src->length - len, src->offset + len);
+ scatterwalk_crypto_chain(dst, sg_next(src), 2);
+
+ return dst;
}
-EXPORT_SYMBOL_GPL(scatterwalk_map_and_copy);
+EXPORT_SYMBOL_GPL(scatterwalk_ffwd);