#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