mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-24 11:30:53 +07:00
9544bc5347
This was introduced more than a decade ago when sg chaining was added, but we never really caught anything with it. The scatterlist entry size can be critical, since drivers allocate it, so remove the magic member. Recently it's been triggering allocation stalls and failures in NVMe. Tested-by: Jordan Glover <Golden_Miller83@protonmail.ch> Acked-by: Christoph Hellwig <hch@lst.de> Signed-off-by: Jens Axboe <axboe@kernel.dk>
173 lines
4.2 KiB
C
173 lines
4.2 KiB
C
/* 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 */
|