| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Cryptographic API. | |
| * | |
| * Cipher operations. | |
| * | |
| * Copyright (c) 2002 James Morris <jmorris@intercode.com.au> | |
| * 2002 Adam J. Richter <adam@yggdrasil.com> | |
| * 2004 Jean-Luc Cooke <jlcooke@certainkey.com> | |
| */ | |
| #include <crypto/scatterwalk.h> | |
| #include <linux/kernel.h> | |
| #include <linux/mm.h> | |
| #include <linux/module.h> | |
| #include <linux/scatterlist.h> | |
| void scatterwalk_skip(struct scatter_walk *walk, unsigned int nbytes) | |
| { | |
| struct scatterlist *sg = walk->sg; | |
| nbytes += walk->offset - sg->offset; | |
| while (nbytes > sg->length) { | |
| nbytes -= sg->length; | |
| sg = sg_next(sg); | |
| } | |
| walk->sg = sg; | |
| walk->offset = sg->offset + nbytes; | |
| } | |
| EXPORT_SYMBOL_GPL(scatterwalk_skip); | |
| 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(memcpy_from_scatterwalk); | |
| inline void memcpy_to_scatterwalk(struct scatter_walk *walk, const void *buf, | |
| unsigned int nbytes) | |
| { | |
| 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(memcpy_to_scatterwalk); | |
| void memcpy_from_sglist(void *buf, struct scatterlist *sg, | |
| unsigned int start, unsigned int nbytes) | |
| { | |
| struct scatter_walk walk; | |
| if (unlikely(nbytes == 0)) /* in case sg == NULL */ | |
| return; | |
| scatterwalk_start_at_pos(&walk, sg, start); | |
| memcpy_from_scatterwalk(buf, &walk, nbytes); | |
| } | |
| EXPORT_SYMBOL_GPL(memcpy_from_sglist); | |
| void memcpy_to_sglist(struct scatterlist *sg, unsigned int start, | |
| const void *buf, unsigned int nbytes) | |
| { | |
| struct scatter_walk walk; | |
| if (unlikely(nbytes == 0)) /* in case sg == NULL */ | |
| return; | |
| scatterwalk_start_at_pos(&walk, sg, start); | |
| memcpy_to_scatterwalk(&walk, buf, nbytes); | |
| } | |
| EXPORT_SYMBOL_GPL(memcpy_to_sglist); | |
| /** | |
| * 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; | |
| if (unlikely(nbytes == 0)) /* in case src and/or dst is NULL */ | |
| return; | |
| 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; | |
| /* | |
| * 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(memcpy_sglist); | |
| struct scatterlist *scatterwalk_ffwd(struct scatterlist dst[2], | |
| struct scatterlist *src, | |
| unsigned int len) | |
| { | |
| for (;;) { | |
| if (!len) | |
| return src; | |
| if (src->length > len) | |
| break; | |
| len -= src->length; | |
| src = sg_next(src); | |
| } | |
| 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_ffwd); |