| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- // 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);
|