捉虫日记 0008: Cache aliases issue (2)
(以“== Solution == === 保守的 === 参照 [Documentation/cachetlb.txt] 的指引,和引入 kmap_coherent 的 patch,发现焦点集中在 3 个函数中: <pre>...”为内容创建页面) |
(→保守的) |
||
| (未显示1个用户的4个中间版本) | |||
| 第14行: | 第14行: | ||
kmap_coherent 就在这三个函数中被调用,且他们都会根据 cpu_has_dc_aliases 的值判断是否用 kmap_coherent,若其值为 0,则这些函数就会走另一条路径: | kmap_coherent 就在这三个函数中被调用,且他们都会根据 cpu_has_dc_aliases 的值判断是否用 kmap_coherent,若其值为 0,则这些函数就会走另一条路径: | ||
| − | < | + | <source lang=cpp> |
void copy_user_highpage(struct page *to, struct page *from, | void copy_user_highpage(struct page *to, struct page *from, | ||
unsigned long vaddr, struct vm_area_struct *vma) | unsigned long vaddr, struct vm_area_struct *vma) | ||
| 第64行: | 第64行: | ||
memcpy(dst, src, len); | memcpy(dst, src, len); | ||
} | } | ||
| − | </ | + | </source> |
| 第70行: | 第70行: | ||
改进的做法有点流氓 :) 引入了 cpu_use_kmap_coherent,让他们都走 else,然后对 else 中的程序块做了额外的 Cache flush 以消除因 Cache Aliases 引起的数据不一致问题: | 改进的做法有点流氓 :) 引入了 cpu_use_kmap_coherent,让他们都走 else,然后对 else 中的程序块做了额外的 Cache flush 以消除因 Cache Aliases 引起的数据不一致问题: | ||
| − | < | + | <source lang=cpp> |
--- a/arch/mips/mm/init.c | --- a/arch/mips/mm/init.c | ||
+++ b/arch/mips/mm/init.c | +++ b/arch/mips/mm/init.c | ||
| 第130行: | 第130行: | ||
-#define cpu_has_dc_aliases 0 | -#define cpu_has_dc_aliases 0 | ||
+#define cpu_use_kmap_coherent 0 | +#define cpu_use_kmap_coherent 0 | ||
| − | </ | + | </source> |
| − | + | ||
| − | |||
| + | 关于如何如此 flush 的原因,可参考这个文档的描述: [http://wiki.jackslab.org/images/Mips.cache.arch.pdf MIPS Cache 结构和代码分析 2008-05-30] | ||
=== 激进的 === | === 激进的 === | ||
| 第159行: | 第158行: | ||
<br><br> | <br><br> | ||
| − | + | ||
| − | + | == Reference == | |
| + | |||
| + | * [[GNU/Linux 内核移植捉虫笔记 (Linux Kernel Debug Notes)]] | ||
| + | |||
<br><br> | <br><br> | ||
2025年12月4日 (四) 14:15的最后版本
目录 |
[编辑] 1 Solution
[编辑] 1.1 保守的
参照 [Documentation/cachetlb.txt] 的指引,和引入 kmap_coherent 的 patch,发现焦点集中在 3 个函数中:
copy_to_user_page() copy_from_user_page() copy_user_highpage()
kmap_coherent 就在这三个函数中被调用,且他们都会根据 cpu_has_dc_aliases 的值判断是否用 kmap_coherent,若其值为 0,则这些函数就会走另一条路径:
void copy_user_highpage(struct page *to, struct page *from,
unsigned long vaddr, struct vm_area_struct *vma)
{
void *vfrom, *vto;
vto = kmap_atomic(to, KM_USER1);
if (cpu_has_dc_aliases) {
vfrom = kmap_coherent(from, vaddr);
copy_page(vto, vfrom);
kunmap_coherent(from);
} else {
vfrom = kmap_atomic(from, KM_USER0);
copy_page(vto, vfrom);
kunmap_atomic(vfrom, KM_USER0);
}
if (((vma->vm_flags & VM_EXEC) && !cpu_has_ic_fills_f_dc) ||
pages_do_alias((unsigned long)vto, vaddr & PAGE_MASK))
flush_data_cache_page((unsigned long)vto);
kunmap_atomic(vto, KM_USER1);
/* Make sure this page is cleared on other CPU's too before using it *
smp_wmb();
}
void copy_to_user_page(struct vm_area_struct *vma,
struct page *page, unsigned long vaddr, void *dst, const void *src,
unsigned long len)
{
if (cpu_has_dc_aliases) {
void *vto = kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK);
memcpy(vto, src, len);
kunmap_coherent(page);
} else
memcpy(dst, src, len);
if ((vma->vm_flags & VM_EXEC) && !cpu_has_ic_fills_f_dc)
flush_cache_page(vma, vaddr, page_to_pfn(page))
}
void copy_from_user_page(struct vm_area_struct *vma,
struct page *page, unsigned long vaddr, void *dst, const void *src,
unsigned long len)
{
if (cpu_has_dc_aliases) {
void *vfrom =
kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK);
memcpy(dst, vfrom, len);
kunmap_coherent(page);
} else
memcpy(dst, src, len);
}
改进的做法有点流氓 :) 引入了 cpu_use_kmap_coherent,让他们都走 else,然后对 else 中的程序块做了额外的 Cache flush 以消除因 Cache Aliases 引起的数据不一致问题:
--- a/arch/mips/mm/init.c
+++ b/arch/mips/mm/init.c
@@ -207,11 +207,13 @@
void *vfrom, *vto;
vto = kmap_atomic(to, KM_USER1);
- if (cpu_has_dc_aliases) {
+ if (cpu_has_dc_aliases && cpu_use_kmap_coherent) {
vfrom = kmap_coherent(from, vaddr);
copy_page(vto, vfrom);
kunmap_coherent(from);
} else {
vfrom = kmap_atomic(from, KM_USER0);
+ if(pages_do_alias((unsigned long)vfrom, vaddr & PAGE_MASK))
+ flush_cache_page(vma, vaddr, page_to_pfn(from));
copy_page(vto, vfrom);
kunmap_atomic(vfrom, KM_USER0);
@@ -230,13 +231,14 @@
struct page *page, unsigned long vaddr, void *dst, const void *src,
unsigned long len)
{
- if (cpu_has_dc_aliases) {
+ if (cpu_has_dc_aliases && cpu_use_kmap_coherent) {
void *vto = kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK);
memcpy(vto, src, len);
kunmap_coherent(page);
} else
memcpy(dst, src, len);
- if ((vma->vm_flags & VM_EXEC) && !cpu_has_ic_fills_f_dc)
+ if ((vma->vm_flags & VM_EXEC) && !cpu_has_ic_fills_f_dc ||
+ pages_do_alias((unsigned long)dst, vaddr & PAGE_MASK))
flush_cache_page(vma, vaddr, page_to_pfn(page));
}
@@ -246,13 +248,16 @@
struct page *page, unsigned long vaddr, void *dst, const void *src,
unsigned long len)
{
- if (cpu_has_dc_aliases) {
+ if (cpu_has_dc_aliases && cpu_use_kmap_coherent) {
void *vfrom =
kmap_coherent(page, vaddr) + (vaddr & ~PAGE_MASK);
memcpy(dst, vfrom, len);
kunmap_coherent(page);
- } else
+ } else {
+ if(pages_do_alias((unsigned long)src, vaddr & PAGE_MASK))
+ flush_cache_page(vma, vaddr, page_to_pfn(page));
memcpy(dst, src, len);
+ }
}
EXPORT_SYMBOL(copy_from_user_page);
--- a/include/asm-mips/mach-bcm56218/cpu-feature-overrides.h
+++ b/include/asm-mips/mach-bcm56218/cpu-feature-overrides.h
@@ -11,6 +11,6 @@
#define __ASM_MACH_BCM56218_CPU_FEATURE_OVERRIDES_H
#define cpu_has_llsc 1
-#define cpu_has_dc_aliases 0
+#define cpu_use_kmap_coherent 0
关于如何如此 flush 的原因,可参考这个文档的描述: MIPS Cache 结构和代码分析 2008-05-30
[编辑] 1.2 激进的
此后轻松的盯了 kmap_coherent() 近一天,最后浮出水面的石头几乎让人崩溃,怀疑了很多不该怀疑的,可从来没有怀疑过 broadcom 的这个 MIPS 实现,它的 KSEG2 高端有一段地址空间,居然不经过 TLB,而 kmap_coherent 恰恰是用位于这个区间的地址来做 fixed map 的,经测试在清空所有 TLB 项的情形下,访问这段区域不会出现异常,且始终返回 0 值,感觉就像固定映射到了一个外设的内部 RAM(我称之为 black hole),要命的是 broadcom 没有任何的文档描述。下面这张图是以页为步进单位,多次访问得出:
FIXADDR_TOP 往下 8 页左右,即是 kmap_coherent 用来作临时固定映射的。知道的这一点,修正的手段也就很容易了,只要把 FIXADDR_TOP 往下移到 Black Hole 区以下即可,最终的修正比较简洁:
--- a/include/asm-mips/fixmap.h.orig +++ b/include/asm-mips/fixmap.h @@ -79,6 +79,7 @@ */ -#if defined(CONFIG_CPU_TX39XX) || defined(CONFIG_CPU_TX49XX) +#if defined(CONFIG_CPU_TX39XX) || defined(CONFIG_CPU_TX49XX) || defined (CONFIG_BCM5621X) #define FIXADDR_TOP ((unsigned long)(long)(int)(0xff000000 - 0x20000)) #else #define FIXADDR_TOP ((unsigned long)(long)(int)0xfffe0000) #endif
[编辑] 2 Reference
