目录
3.3.1 sg_is_chain(判断某个scatterlist是否为一个chain)
3.3.2 sg_is_last(判断某个scatterlist是否是sg_table中最后一个scatterlist)
3.3.3 sg_chain_ptr (获取chain scatterlist指向的那个scatterlist)
3.3.4 sg_dma_address (获取某一个scatterlist的物理地址)
3.3.5 sg_dma_len (获取某一个scatterlist的长度)
3.3.6 sg_assign_page (将page赋给指定的scatterlist(设置page_link字段))
3.3.8 sg_page(获取scatterlist所对应的page指针)
3.3.9 sg_set_buf ( 将指定长度的buffer赋给scatterlist)
3.3.10 for_each_sg (遍历一个scatterlist数组)
3.3.11 sg_chain (将两个scatterlist 数组捆绑在一起)
3.3.12 sg_mark_end (将某个scatterlist 标记为the last one)
3.3.14 sg_phys (获取某个scatterlist的物理地址)
3.3.15 sg_virt (获取某个scatterlist的虚拟地址)
3.3.16 sg_nents (得到scatterlist链表或者数组中scatterlist的个数)
3.3.17 sg_nents_for_len (得到scatterlist链表或者数组中scatterlist的总的数据长度)
1. 简介
我们在那些需要和用户空间交互大量数据的子系统(例如MMC、Video、Audio等)中,经常看到scatterlist的影子。scatterlist即物理内存的散列表。再通俗一些,就是把一些分散的物理内存,以列表的形式组织起来。那么,也许你会问,有什么用处呢?
2. scatterlist产生的背景
我没有去考究scatterlist API是在哪个kernel版本中引入的(年代太久远了),凭猜测,我觉得应该和MMU有关。因为在引入MMU之后,linux系统中的软件将不得不面对一个困扰(下文将以图片1中所示的系统架构为例进行说明):
假设在一个系统中(参考下面图片1)有三个模块可以访问memory:CPU、DMA控制器和某个外设。CPU通过MMU以虚拟地址(VA)的形式访问memory;DMA直接以物理地址(PA)的形式访问memory;Device通过自己的IOMMU以设备地址(DA)的形式访问memory。
然后,某个“软件实体”分配并使用了一片存储空间(参考下面图片2)。该存储空间在CPU视角上(虚拟空间)是连续的,起始地址是va1(实际上,它映射到了3块不连续的物理内存上,我们以pa1,pa2,pa3表示)。
那么,如果该软件单纯的以CPU视角访问这块空间(操作va1),则完全没有问题,因为MMU实现了连续VA到非连续PA的映射。
不过,如果软件经过一系列操作后,要把该存储空间交给DMA控制器,最终由DMA控制器将其中的数据搬移给某个外设的时候,由于DMA控制器只能访问物理地址,只能以“不连续的物理内存块”为单位递交(而不是我们所熟悉的虚拟地址)。
此时,scatterlist就诞生了:为了方便,我们需要使用一个数据结构来描述这一个个“不连续的物理内存块”(起始地址、长度等信息),这个数据结构就是scatterlist(具体可参考下面第3章的说明)。而多个scatterlist组合在一起形成一个表(可以是一个struct scatterlist类型的数组,也可以是kernel帮忙抽象出来的struct sg_table),就可以完整的描述这个虚拟地址了。
最后,从本质上说:scatterlist(数组)是各种不同地址映射空间(PA、VA、DA、等等)的媒介(因为物理地址是真实的、实在的存在,因而可以作为通用语言),借助它,这些映射空间才能相互转换(例如从VA转到DA)。
3. scatterlist API介绍
3.1 struct scatterlist
struct scatterlist用于描述一个在物理地址上连续的内存块(以page为单位),它的定义位于“include/linux/scatterlist.h”中,如下:
struct scatterlist {
#ifdef CONFIG_DEBUG_SG
unsigned long sg_magic;
#endif
unsigned long page_link;
unsigned int offset;
unsigned int length;
dma_addr_t dma_address;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
unsigned int dma_length;
#endif
};
page_link,指示该内存块所在的页面。bit0和bit1有特殊用途,因此要求page最低4字节对齐。
offset,指示该内存块在页面中的偏移(起始位置)。
length,该内存块的长度。
dma_address,该内存块实际的起始地址(PA,相比page更接近我们人类的语言)。
dma_length,相应的长度信息。
3.2 struct sg_table
在实际的应用场景中,单个的scatterlist是没有多少意义的,我们需要多个scatterlist组成一个数组,以表示在物理上不连续的虚拟地址空间。通常情况下,使用scatterlist功能的模块,会自行维护这个数组(指针和长度)。另外kernel抽象出来了一个简单的数据结构:struct sg_table,帮忙保存scatterlist的数组指针和长度:
struct sg_table {
struct scatterlist *sgl; /* the list */
unsigned int nents; /* number of mapped entries */
unsigned int orig_nents; /* original size of list */
};
其中sgl是内存块数组的首地址,orig_nents是内存块数组的size,nents是有效的内存块个数(可能会小于orig_nents)。
scatterlist数组中到底有多少有效内存块呢?这不是一个很直观的事情,主要有如下2个规则决定:
1)如果scatterlist数组中某个scatterlist的page_link的bit0为1,表示该scatterlist不是一个有效的内存块,而是一个chain(铰链),指向另一个scatterlist数组。通过这种机制,可以将不同的scatterlist数组链在一起,因为scatterlist也称作chain scatterlist。
2)如果scatterlist数组中某个scatterlist的page_link的bit1为1,表示该scatterlist是scatterlist数组中最后一个有效内存块(后面的就忽略不计了)。
#define sg_is_chain(sg) ((sg)->page_link & 0x01)
#define sg_is_last(sg) ((sg)->page_link & 0x02)
3.3 API介绍
理解了scatterlist的含义之后,再去看“include/linux/scatterlist.h”中的API,就容易多了,例如(简单介绍一下,不再详细分析):
3.3.1 sg_is_chain(判断某个scatterlist是否为一个chain)
#define sg_is_chain(sg) ((sg)->page_link & 0x01)
3.3.2 sg_is_last(判断某个scatterlist是否是sg_table中最后一个scatterlist)
#define sg_is_last(sg) ((sg)->page_link & 0x02)
3.3.3 sg_chain_ptr (获取chain scatterlist指向的那个scatterlist)
#define sg_chain_ptr(sg) \
((struct scatterlist *) ((sg)->page_link & ~0x03))
3.3.4 sg_dma_address (获取某一个scatterlist的物理地址)
#define sg_dma_address(sg) ((sg)->dma_address)
3.3.5 sg_dma_len (获取某一个scatterlist的长度)
#ifdef CONFIG_NEED_SG_DMA_LENGTH
#define sg_dma_len(sg) ((sg)->dma_length)
#else
#define sg_dma_len(sg) ((sg)->length)
#endif
3.3.6 sg_assign_page (将page赋给指定的scatterlist(设置page_link字段))
/**
* 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->sg_magic != SG_MAGIC);
BUG_ON(sg_is_chain(sg));
#endif
sg->page_link = page_link | (unsigned long) page;
}
3.3.7 sg_set_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;
}
将page中指定offset、指定长度的内存赋给指定的scatterlist(设置page_link、offset、len字段)。
3.3.8 sg_page(获取scatterlist所对应的page指针)
static inline struct page *sg_page(struct scatterlist *sg)
{
#ifdef CONFIG_DEBUG_SG
BUG_ON(sg->sg_magic != SG_MAGIC);
BUG_ON(sg_is_chain(sg));
#endif
return (struct page *)((sg)->page_link & ~0x3);
}
3.3.9 sg_set_buf ( 将指定长度的buffer赋给scatterlist)
/**
* sg_set_buf - Set sg entry to point at given data
* @sg: SG entry
* @buf: Data
* @buflen: Data length
*
**/
static inline void sg_set_buf(struct scatterlist *sg, const void *buf,
unsigned int buflen)
{
#ifdef CONFIG_DEBUG_SG
BUG_ON(!virt_addr_valid(buf));
#endif
sg_set_page(sg, virt_to_page(buf), buflen, offset_in_page(buf));
}
将指定长度的buffer赋给scatterlist(从虚拟地址中获得page指针、在page中的offset之后,再调用sg_set_page)。
3.3.10 for_each_sg (遍历一个scatterlist数组)
/**
* sg_next - return the next scatterlist entry in a list
* @sg: The current sg entry
*
* Description:
* Usually the next entry will be @sg@ + 1, but if this sg element is part
* of a chained scatterlist, it could jump to the start of a new
* scatterlist array.
*
**/
struct scatterlist *sg_next(struct scatterlist *sg)
{
#ifdef CONFIG_DEBUG_SG
BUG_ON(sg->sg_magic != SG_MAGIC);
#endif
if (sg_is_last(sg))
return NULL;
sg++;
if (unlikely(sg_is_chain(sg)))
sg = sg_chain_ptr(sg);
return sg;
}
/*
* 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))
3.3.11 sg_chain (将两个scatterlist 数组捆绑在一起)
/**
* 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;
}
3.3.12 sg_mark_end (将某个scatterlist 标记为the last one)
/**
* 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)
{
#ifdef CONFIG_DEBUG_SG
BUG_ON(sg->sg_magic != SG_MAGIC);
#endif
/*
* Set termination bit, clear potential chain bit
*/
sg->page_link |= 0x02;
sg->page_link &= ~0x01;
}
3.3.13 sg_unmark_end
/**
* 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)
{
#ifdef CONFIG_DEBUG_SG
BUG_ON(sg->sg_magic != SG_MAGIC);
#endif
sg->page_link &= ~0x02;
}
3.3.14 sg_phys (获取某个scatterlist的物理地址)
/**
* sg_phys - Return physical address of an sg entry
* @sg: SG entry
*
* Description:
* This calls page_to_phys() on the page in this sg entry, and adds the
* sg offset. The caller must know that it is legal to call page_to_phys()
* on the sg page.
*
**/
static inline dma_addr_t sg_phys(struct scatterlist *sg)
{
return page_to_phys(sg_page(sg)) + sg->offset;
}
3.3.15 sg_virt (获取某个scatterlist的虚拟地址)
/**
* sg_virt - Return virtual address of an sg entry
* @sg: SG entry
*
* Description:
* This calls page_address() on the page in this sg entry, and adds the
* sg offset. The caller must know that the sg page has a valid virtual
* mapping.
*
**/
static inline void *sg_virt(struct scatterlist *sg)
{
return page_address(sg_page(sg)) + sg->offset;
}
3.3.16 sg_nents (得到scatterlist链表或者数组中scatterlist的个数)
/**
* sg_nents - return total count of entries in scatterlist
* @sg: The scatterlist
*
* Description:
* Allows to know how many entries are in sg, taking into acount
* chaining as well
*
**/
int sg_nents(struct scatterlist *sg)
{
int nents;
for (nents = 0; sg; sg = sg_next(sg))
nents++;
return nents;
}
3.3.17 sg_nents_for_len (得到scatterlist链表或者数组中scatterlist的总的数据长度)
/**
* sg_nents_for_len - return total count of entries in scatterlist
* needed to satisfy the supplied length
* @sg: The scatterlist
* @len: The total required length
*
* Description:
* Determines the number of entries in sg that are required to meet
* the supplied length, taking into acount chaining as well
*
* Returns:
* the number of sg entries needed, negative error on failure
*
**/
int sg_nents_for_len(struct scatterlist *sg, u64 len)
{
int nents;
u64 total;
if (!len)
return 0;
for (nents = 0, total = 0; sg; sg = sg_next(sg)) {
nents++;
total += sg->length;
if (total >= len)
return nents;
}
return -EINVAL;
}
3.3.18 sg_next
/**
* sg_next - return the next scatterlist entry in a list
* @sg: The current sg entry
*
* Description:
* Usually the next entry will be @sg@ + 1, but if this sg element is part
* of a chained scatterlist, it could jump to the start of a new
* scatterlist array.
*
**/
struct scatterlist *sg_next(struct scatterlist *sg)
{
#ifdef CONFIG_DEBUG_SG
BUG_ON(sg->sg_magic != SG_MAGIC);
#endif
if (sg_is_last(sg))
return NULL;
sg++;
if (unlikely(sg_is_chain(sg)))
sg = sg_chain_ptr(sg);
return sg;
}
3.3.19 sg_last
/**
* sg_last - return the last scatterlist entry in a list
* @sgl: First entry in the scatterlist
* @nents: Number of entries in the scatterlist
*
* Description:
* Should only be used casually, it (currently) scans the entire list
* to get the last entry.
*
* Note that the @sgl@ pointer passed in need not be the first one,
* the important bit is that @nents@ denotes the number of entries that
* exist from @sgl@.
*
**/
struct scatterlist *sg_last(struct scatterlist *sgl, unsigned int nents)
{
struct scatterlist *sg, *ret = NULL;
unsigned int i;
for_each_sg(sgl, sg, nents, i)
ret = sg;
#ifdef CONFIG_DEBUG_SG
BUG_ON(sgl[0].sg_magic != SG_MAGIC);
BUG_ON(!sg_is_last(ret));
#endif
return ret;
}
3.3.20 sg_init_table
/**
* sg_init_table - Initialize SG table
* @sgl: The SG table
* @nents: Number of entries in table
*
* Notes:
* If this is part of a chained sg table, sg_mark_end() should be
* used only on the last table part.
*
**/
void sg_init_table(struct scatterlist *sgl, unsigned int nents)
{
memset(sgl, 0, sizeof(*sgl) * nents);
#ifdef CONFIG_DEBUG_SG
{
unsigned int i;
for (i = 0; i < nents; i++)
sgl[i].sg_magic = SG_MAGIC;
}
#endif
sg_mark_end(&sgl[nents - 1]);
}
3.3.21 sg_init_one
/**
* sg_init_one - Initialize a single entry sg list
* @sg: SG entry
* @buf: Virtual address for IO
* @buflen: IO length
*
**/
void sg_init_one(struct scatterlist *sg, const void *buf, unsigned int buflen)
{
sg_init_table(sg, 1);
sg_set_buf(sg, buf, buflen);
}
3.3.22 sg_copy_from_buffer
/**
* sg_copy_from_buffer - Copy from a linear buffer to an SG list
* @sgl: The SG list
* @nents: Number of SG entries
* @buf: Where to copy from
* @buflen: The number of bytes to copy
*
* Returns the number of copied bytes.
*
**/
size_t sg_copy_from_buffer(struct scatterlist *sgl, unsigned int nents,
const void *buf, size_t buflen)
{
return sg_copy_buffer(sgl, nents, (void *)buf, buflen, 0, false);
}
3.3.23 sg_copy_to_buffer
/**
* sg_copy_to_buffer - Copy from an SG list to a linear buffer
* @sgl: The SG list
* @nents: Number of SG entries
* @buf: Where to copy to
* @buflen: The number of bytes to copy
*
* Returns the number of copied bytes.
*
**/
size_t sg_copy_to_buffer(struct scatterlist *sgl, unsigned int nents,
void *buf, size_t buflen)
{
return sg_copy_buffer(sgl, nents, buf, buflen, 0, true);
}
3.3.24 sg_pcopy_from_buffer
/**
* sg_pcopy_from_buffer - Copy from a linear buffer to an SG list
* @sgl: The SG list
* @nents: Number of SG entries
* @buf: Where to copy from
* @buflen: The number of bytes to copy
* @skip: Number of bytes to skip before copying
*
* Returns the number of copied bytes.
*
**/
size_t sg_pcopy_from_buffer(struct scatterlist *sgl, unsigned int nents,
const void *buf, size_t buflen, off_t skip)
{
return sg_copy_buffer(sgl, nents, (void *)buf, buflen, skip, false);
}
3.3.25 sg_pcopy_to_buffer
/**
* sg_pcopy_to_buffer - Copy from an SG list to a linear buffer
* @sgl: The SG list
* @nents: Number of SG entries
* @buf: Where to copy to
* @buflen: The number of bytes to copy
* @skip: Number of bytes to skip before copying
*
* Returns the number of copied bytes.
*
**/
size_t sg_pcopy_to_buffer(struct scatterlist *sgl, unsigned int nents,
void *buf, size_t buflen, off_t skip)
{
return sg_copy_buffer(sgl, nents, buf, buflen, skip, true);
}