/* SPDX-License-Identifier: GPL-2.0-or-later */


#ifndef _CRYPTO_SCATTERWALK_H
#define _CRYPTO_SCATTERWALK_H

#include <crypto/algapi.h>

#include <linux/highmem.h>
#include <linux/mm.h>
#include <linux/scatterlist.h>

static inline void scatterwalk_crypto_chain(struct scatterlist *head,
					    struct scatterlist *sg, int num)
{
	if (sg)
		sg_chain(head, num, sg);
	else
		sg_mark_end(head);
}

static inline void scatterwalk_start(struct scatter_walk *walk,
				     struct scatterlist *sg)
{
	walk->sg = sg;
	walk->offset = sg->offset;
}


static inline void scatterwalk_start_at_pos(struct scatter_walk *walk,
					    struct scatterlist *sg,
					    unsigned int pos)
{
	while (pos > sg->length) {
		pos -= sg->length;
		sg = sg_next(sg);
	}
	walk->sg = sg;
	walk->offset = sg->offset + pos;
}

static inline unsigned int scatterwalk_clamp(struct scatter_walk *walk,
					     unsigned int nbytes)
{
	unsigned int len_this_sg;
	unsigned int limit;

	if (walk->offset >= walk->sg->offset + walk->sg->length)
		scatterwalk_start(walk, sg_next(walk->sg));
	len_this_sg = walk->sg->offset + walk->sg->length - walk->offset;

	
	if (IS_ENABLED(CONFIG_HIGHMEM))
		limit = PAGE_SIZE - offset_in_page(walk->offset);
	else
		limit = PAGE_SIZE;

	return min3(nbytes, len_this_sg, limit);
}


static inline void scatterwalk_get_sglist(struct scatter_walk *walk,
					  struct scatterlist sg_out[2])
{
	if (walk->offset >= walk->sg->offset + walk->sg->length)
		scatterwalk_start(walk, sg_next(walk->sg));
	sg_set_page(sg_out, sg_page(walk->sg),
		    walk->sg->offset + walk->sg->length - walk->offset,
		    walk->offset);
	scatterwalk_crypto_chain(sg_out, sg_next(walk->sg), 2);
}

static inline void scatterwalk_map(struct scatter_walk *walk)
{
	struct page *base_page = sg_page(walk->sg);
	unsigned int offset = walk->offset;
	void *addr;

	if (IS_ENABLED(CONFIG_HIGHMEM)) {
		struct page *page;

		page = base_page + (offset >> PAGE_SHIFT);
		offset = offset_in_page(offset);
		addr = kmap_local_page(page) + offset;
	} else {
		
		addr = page_address(base_page) + offset;
	}

	walk->__addr = addr;
}


static inline unsigned int scatterwalk_next(struct scatter_walk *walk,
					    unsigned int total)
{
	unsigned int nbytes = scatterwalk_clamp(walk, total);

	scatterwalk_map(walk);
	return nbytes;
}

static inline void scatterwalk_unmap(struct scatter_walk *walk)
{
	if (IS_ENABLED(CONFIG_HIGHMEM))
		kunmap_local(walk->__addr);
}

static inline void scatterwalk_advance(struct scatter_walk *walk,
				       unsigned int nbytes)
{
	walk->offset += nbytes;
}


static inline void scatterwalk_done_src(struct scatter_walk *walk,
					unsigned int nbytes)
{
	scatterwalk_unmap(walk);
	scatterwalk_advance(walk, nbytes);
}


static inline void __scatterwalk_flush_dcache_pages(struct page *base_page,
						    unsigned int offset,
						    unsigned int nbytes)
{
	unsigned int num_pages;

	base_page += offset / PAGE_SIZE;
	offset %= PAGE_SIZE;

	
	num_pages = nbytes / PAGE_SIZE;
	num_pages += DIV_ROUND_UP(offset + (nbytes % PAGE_SIZE), PAGE_SIZE);

	for (unsigned int i = 0; i < num_pages; i++)
		flush_dcache_page(base_page + i);
}


static inline void scatterwalk_done_dst(struct scatter_walk *walk,
					unsigned int nbytes)
{
	scatterwalk_unmap(walk);
	if (ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE)
		__scatterwalk_flush_dcache_pages(sg_page(walk->sg),
						 walk->offset, nbytes);
	scatterwalk_advance(walk, nbytes);
}

void scatterwalk_skip(struct scatter_walk *walk, unsigned int nbytes);

void memcpy_from_scatterwalk(void *buf, struct scatter_walk *walk,
			     unsigned int nbytes);

void memcpy_to_scatterwalk(struct scatter_walk *walk, const void *buf,
			   unsigned int nbytes);

void memcpy_from_sglist(void *buf, struct scatterlist *sg,
			unsigned int start, unsigned int nbytes);

void memcpy_to_sglist(struct scatterlist *sg, unsigned int start,
		      const void *buf, unsigned int nbytes);

void memcpy_sglist(struct scatterlist *dst, struct scatterlist *src,
		   unsigned int nbytes);


static inline void scatterwalk_map_and_copy(void *buf, struct scatterlist *sg,
					    unsigned int start,
					    unsigned int nbytes, int out)
{
	if (out)
		memcpy_to_sglist(sg, start, buf, nbytes);
	else
		memcpy_from_sglist(buf, sg, start, nbytes);
}

struct scatterlist *scatterwalk_ffwd(struct scatterlist dst[2],
				     struct scatterlist *src,
				     unsigned int len);

#endif  
