正如@0andriy 所指出的,您不应该直接访问 iomem。有memcpy_toio()和memcpy_fromio()等函数可以在iomem和普通内存之间进行复制,但它们只对内核虚拟地址起作用。
注意: 对于 Linux 内核版本 5.6 及更高版本,应更改下面描述的 get_user_pages_fast()、set_page_dirty_lock() 和 put_page() 的使用。稍后将描述所需的更改。
为了在不使用中间数据缓冲区的情况下从用户空间地址复制到 iomem,需要将用户空间内存页面“固定”到物理内存中。这可以使用get_user_pages_fast() 来完成。但是,固定页面可能位于内核中永久映射内存之外的“高内存”(highmem)中。此类页面需要使用kmap_atomic() 在短时间内临时映射到内核虚拟地址空间。 (kmap_atomic() 的使用有规则,highmem 的长期映射还有其他功能。有关详细信息,请查看highmem 文档。)
一旦用户空间页面被映射到内核虚拟地址空间,memcpy_toio() 和 memcpy_fromio() 就可以用于在该页面和 iomem 之间进行复制。
由kmap_atomic() 临时映射的页面需要由kunmap_atomic() 取消映射。
由get_user_pages_fast() 固定的用户内存页面需要通过调用put_page() 单独取消固定,但如果页面内存已被写入(例如由memcpy_fromio(),则必须首先由@987654337 将其标记为“脏” @在调用put_page()之前。
注意:内核版本 5.6 及以后的更改。
- 对
get_user_pages_fast() 的调用应更改为pin_user_pages_fast()。
-
pin_user_pages_fast() 固定的脏页应由 unpin_user_pages_dirty_lock() 取消固定,最后一个参数设置为 true。
-
pin_user_pages_fast() 固定的干净页面应由 unpin_user_page()、unpin_user_pages() 或 unpin_user_pages_dirty_lock() 取消固定,最后一个参数设置为 false。
-
put_page() 不得用于取消固定由 pin_user_pages_fast() 固定的页面。
- 为了代码与早期内核版本兼容,
pin_user_pages_fast()、unpin_user_page()等的可用性可以通过FOLL_PIN宏是否已经被#include <linux/mm.h>定义。
将所有这些放在一起,可以使用以下函数在用户内存和 iomem 之间进行复制:
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/io.h>
/**
* my_copy_to_user_from_iomem - copy to user memory from MMIO
* @to: destination in user memory
* @from: source in remapped MMIO
* @n: number of bytes to copy
* Context: process
*
* Returns number of uncopied bytes.
*/
long my_copy_to_user_from_iomem(void __user *to, const void __iomem *from,
unsigned long n)
{
might_fault();
if (!access_ok(to, n))
return n;
while (n) {
enum { PAGE_LIST_LEN = 32 };
struct page *page_list[PAGE_LIST_LEN];
unsigned long start;
unsigned int p_off;
unsigned int part_len;
int nr_pages;
int i;
/* Determine pages to do this iteration. */
p_off = offset_in_page(to);
start = (unsigned long)to - p_off;
nr_pages = min_t(int, PAGE_ALIGN(p_off + n) >> PAGE_SHIFT,
PAGE_LIST_LEN);
/* Lock down (for write) user pages. */
#ifdef FOLL_PIN
nr_pages = pin_user_pages_fast(start, nr_pages, FOLL_WRITE, page_list);
#else
nr_pages = get_user_pages_fast(start, nr_pages, FOLL_WRITE, page_list);
#endif
if (nr_pages <= 0)
break;
/* Limit number of bytes to end of locked-down pages. */
part_len =
min(n, ((unsigned long)nr_pages << PAGE_SHIFT) - p_off);
/* Copy from iomem to locked-down user memory pages. */
for (i = 0; i < nr_pages; i++) {
struct page *page = page_list[i];
unsigned char *p_va;
unsigned int plen;
plen = min((unsigned int)PAGE_SIZE - p_off, part_len);
p_va = kmap_atomic(page);
memcpy_fromio(p_va + p_off, from, plen);
kunmap_atomic(p_va);
#ifndef FOLL_PIN
set_page_dirty_lock(page);
put_page(page);
#endif
to = (char __user *)to + plen;
from = (const char __iomem *)from + plen;
n -= plen;
part_len -= plen;
p_off = 0;
}
#ifdef FOLL_PIN
unpin_user_pages_dirty_lock(page_list, nr_pages, true);
#endif
}
return n;
}
/**
* my_copy_from_user_to_iomem - copy from user memory to MMIO
* @to: destination in remapped MMIO
* @from: source in user memory
* @n: number of bytes to copy
* Context: process
*
* Returns number of uncopied bytes.
*/
long my_copy_from_user_to_iomem(void __iomem *to, const void __user *from,
unsigned long n)
{
might_fault();
if (!access_ok(from, n))
return n;
while (n) {
enum { PAGE_LIST_LEN = 32 };
struct page *page_list[PAGE_LIST_LEN];
unsigned long start;
unsigned int p_off;
unsigned int part_len;
int nr_pages;
int i;
/* Determine pages to do this iteration. */
p_off = offset_in_page(from);
start = (unsigned long)from - p_off;
nr_pages = min_t(int, PAGE_ALIGN(p_off + n) >> PAGE_SHIFT,
PAGE_LIST_LEN);
/* Lock down (for read) user pages. */
#ifdef FOLL_PIN
nr_pages = pin_user_pages_fast(start, nr_pages, 0, page_list);
#else
nr_pages = get_user_pages_fast(start, nr_pages, 0, page_list);
#endif
if (nr_pages <= 0)
break;
/* Limit number of bytes to end of locked-down pages. */
part_len =
min(n, ((unsigned long)nr_pages << PAGE_SHIFT) - p_off);
/* Copy from locked-down user memory pages to iomem. */
for (i = 0; i < nr_pages; i++) {
struct page *page = page_list[i];
unsigned char *p_va;
unsigned int plen;
plen = min((unsigned int)PAGE_SIZE - p_off, part_len);
p_va = kmap_atomic(page);
memcpy_toio(to, p_va + p_off, plen);
kunmap_atomic(p_va);
#ifndef FOLL_PIN
put_page(page);
#endif
to = (char __iomem *)to + plen;
from = (const char __user *)from + plen;
n -= plen;
part_len -= plen;
p_off = 0;
}
#ifdef FOLL_PIN
unpin_user_pages(page_list, nr_pages);
#endif
}
return n;
}
其次,您可能可以通过将 pci_iomap() 替换为 pci_iomap_wc() 将 iomem 映射为“写入组合”来加速内存访问。
第三,在访问慢速内存时避免等待 CPU 的唯一真正方法是不使用 CPU 而使用 DMA 传输。这在很大程度上取决于您的 PCIe 设备的总线主控 DMA 功能(如果有的话)。在 DMA 传输期间仍需要固定用户内存页面(例如,通过 get_user_pages_fast() 或 pin_user_pages_fast() 酌情),但不需要通过 kmap_atomic() 临时映射。