diff options
Diffstat (limited to 'crypto/scatterwalk.c')
| -rw-r--r-- | crypto/scatterwalk.c | 230 |
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); |
