mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
4.2 KiB
172 lines
4.2 KiB
/* SPDX-License-Identifier: GPL-2.0 */ |
|
#ifndef SCATTERLIST_H |
|
#define SCATTERLIST_H |
|
#include <linux/kernel.h> |
|
|
|
struct scatterlist { |
|
unsigned long page_link; |
|
unsigned int offset; |
|
unsigned int length; |
|
dma_addr_t dma_address; |
|
}; |
|
|
|
/* Scatterlist helpers, stolen from linux/scatterlist.h */ |
|
#define sg_is_chain(sg) ((sg)->page_link & 0x01) |
|
#define sg_is_last(sg) ((sg)->page_link & 0x02) |
|
#define sg_chain_ptr(sg) \ |
|
((struct scatterlist *) ((sg)->page_link & ~0x03)) |
|
|
|
/** |
|
* sg_assign_page - Assign a given page to an SG entry |
|
* @sg: SG entry |
|
* @page: The page |
|
* |
|
* Description: |
|
* Assign page to sg entry. Also see sg_set_page(), the most commonly used |
|
* variant. |
|
* |
|
**/ |
|
static inline void sg_assign_page(struct scatterlist *sg, struct page *page) |
|
{ |
|
unsigned long page_link = sg->page_link & 0x3; |
|
|
|
/* |
|
* In order for the low bit stealing approach to work, pages |
|
* must be aligned at a 32-bit boundary as a minimum. |
|
*/ |
|
BUG_ON((unsigned long) page & 0x03); |
|
#ifdef CONFIG_DEBUG_SG |
|
BUG_ON(sg_is_chain(sg)); |
|
#endif |
|
sg->page_link = page_link | (unsigned long) page; |
|
} |
|
|
|
/** |
|
* sg_set_page - Set sg entry to point at given page |
|
* @sg: SG entry |
|
* @page: The page |
|
* @len: Length of data |
|
* @offset: Offset into page |
|
* |
|
* Description: |
|
* Use this function to set an sg entry pointing at a page, never assign |
|
* the page directly. We encode sg table information in the lower bits |
|
* of the page pointer. See sg_page() for looking up the page belonging |
|
* to an sg entry. |
|
* |
|
**/ |
|
static inline void sg_set_page(struct scatterlist *sg, struct page *page, |
|
unsigned int len, unsigned int offset) |
|
{ |
|
sg_assign_page(sg, page); |
|
sg->offset = offset; |
|
sg->length = len; |
|
} |
|
|
|
static inline struct page *sg_page(struct scatterlist *sg) |
|
{ |
|
#ifdef CONFIG_DEBUG_SG |
|
BUG_ON(sg_is_chain(sg)); |
|
#endif |
|
return (struct page *)((sg)->page_link & ~0x3); |
|
} |
|
|
|
/* |
|
* Loop over each sg element, following the pointer to a new list if necessary |
|
*/ |
|
#define for_each_sg(sglist, sg, nr, __i) \ |
|
for (__i = 0, sg = (sglist); __i < (nr); __i++, sg = sg_next(sg)) |
|
|
|
/** |
|
* sg_chain - Chain two sglists together |
|
* @prv: First scatterlist |
|
* @prv_nents: Number of entries in prv |
|
* @sgl: Second scatterlist |
|
* |
|
* Description: |
|
* Links @prv@ and @sgl@ together, to form a longer scatterlist. |
|
* |
|
**/ |
|
static inline void sg_chain(struct scatterlist *prv, unsigned int prv_nents, |
|
struct scatterlist *sgl) |
|
{ |
|
/* |
|
* offset and length are unused for chain entry. Clear them. |
|
*/ |
|
prv[prv_nents - 1].offset = 0; |
|
prv[prv_nents - 1].length = 0; |
|
|
|
/* |
|
* Set lowest bit to indicate a link pointer, and make sure to clear |
|
* the termination bit if it happens to be set. |
|
*/ |
|
prv[prv_nents - 1].page_link = ((unsigned long) sgl | 0x01) & ~0x02; |
|
} |
|
|
|
/** |
|
* sg_mark_end - Mark the end of the scatterlist |
|
* @sg: SG entryScatterlist |
|
* |
|
* Description: |
|
* Marks the passed in sg entry as the termination point for the sg |
|
* table. A call to sg_next() on this entry will return NULL. |
|
* |
|
**/ |
|
static inline void sg_mark_end(struct scatterlist *sg) |
|
{ |
|
/* |
|
* Set termination bit, clear potential chain bit |
|
*/ |
|
sg->page_link |= 0x02; |
|
sg->page_link &= ~0x01; |
|
} |
|
|
|
/** |
|
* sg_unmark_end - Undo setting the end of the scatterlist |
|
* @sg: SG entryScatterlist |
|
* |
|
* Description: |
|
* Removes the termination marker from the given entry of the scatterlist. |
|
* |
|
**/ |
|
static inline void sg_unmark_end(struct scatterlist *sg) |
|
{ |
|
sg->page_link &= ~0x02; |
|
} |
|
|
|
static inline struct scatterlist *sg_next(struct scatterlist *sg) |
|
{ |
|
if (sg_is_last(sg)) |
|
return NULL; |
|
|
|
sg++; |
|
if (unlikely(sg_is_chain(sg))) |
|
sg = sg_chain_ptr(sg); |
|
|
|
return sg; |
|
} |
|
|
|
static inline void sg_init_table(struct scatterlist *sgl, unsigned int nents) |
|
{ |
|
memset(sgl, 0, sizeof(*sgl) * nents); |
|
sg_mark_end(&sgl[nents - 1]); |
|
} |
|
|
|
static inline dma_addr_t sg_phys(struct scatterlist *sg) |
|
{ |
|
return page_to_phys(sg_page(sg)) + sg->offset; |
|
} |
|
|
|
static inline void sg_set_buf(struct scatterlist *sg, const void *buf, |
|
unsigned int buflen) |
|
{ |
|
sg_set_page(sg, virt_to_page(buf), buflen, offset_in_page(buf)); |
|
} |
|
|
|
static inline void sg_init_one(struct scatterlist *sg, |
|
const void *buf, unsigned int buflen) |
|
{ |
|
sg_init_table(sg, 1); |
|
sg_set_buf(sg, buf, buflen); |
|
} |
|
#endif /* SCATTERLIST_H */
|
|
|