はじめに
Linux では DMA Bufferを mmap した時に、ある条件が揃うと CPU Cache が無効になり、パフォーマンスが極端に落ちる場合があります。そこで、何故そのようなことが起こるのか説明します。少し長くなるので、次のように記事を幾つかに分けて投稿します。
- はじめに
- Cache Coherence 問題
- Cache Aliasing 問題
- Linux Kernel の Cache 問題の扱い
- Linux では Cache Coherence Hardware を持っていないとDMA Buffer をmmap する際に CPU Cache が無効になる
- Raspberry Pi の例
- RISC-V CPU の注意点 (この記事)
- 所感
1〜3は、コンピューターアーキテクチャの基本的な事項を、簡単に説明したものです。すでにご存じの方は読み飛ばしてください。
4 はこれらの問題を Linux Kernel 内でどのように扱っているかを説明します。
5 がこれらの記事群の結論です。結論だけ知りたい方はここだけ読んでください。
前々回の記事 [『Linux では Cache Cohrence Hardware を持っていないとDMA Buffer をmmap する際に CPU Cache が無効になる』] では、Cache Coherence Hardware を持っていないコンピューターシステムの場合は、例え Cache Aliasing 問題が起きない場合でも、 DMA Buffer を mmap する際にキャッシュが無効になることを説明しました。この記事では、RISC-V CPU を採用しているコンピューターシステムでも、場合によっては注意が必要なことを説明します。
RISC-V CPU の場合
dev_is_dma_coherent の紹介(再掲)
『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Linux Kernel の Cache 問題の扱い)』という記事で、dev_is_dma_coherent という Linux Kernel で Cache Coherence 問題と Cache Aliasing 問題を扱う際にとても重要な役目を持つインライン関数を次のように紹介しました。
dev_is_dma_coherent() は次のように定義されています。
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
extern bool dma_default_coherent;
static inline bool dev_is_dma_coherent(struct device *dev)
{
return dev->dma_coherent;
}
#else
static inline bool dev_is_dma_coherent(struct device *dev)
{
return true;
}
#endif /* CONFIG_ARCH_HAS_DMA_COHERENCE_H */
この定義を見てもわかる通り、dev_is_dma_coherent() は2種類に実装方法があります。この実装方法の違いはアーキテクチャによってだいたい次のように分かれています。
- struct device の dma_cohrent フィールドの値を返すように定義
- arc, arm, arm64,m68k, sh 等の主に組み込み系のプロセッサ
- riscv (linux 6.0 以降)
- 常に true を返すように定義
- x86, ia64 等 PC 用のプロセッサ
- riscv (linux 5.19 以前)
上記のように Linux Kernel 5.19 以前と 6.0 以降で、RISC-V CPU の場合の定義方法が異なっています。5.19 以前は何も指定していなくても dev_is_dma_coherent() は true を返していたのですが、6.0 以降は struct device の dma_cohrent フィールドの値を返すように変更されています。
Linux 6.0 以降の arch/riscv/Kconfig
CONFIG_RISCV_DMA_NONCOHERENT
Linux Kernel 6.0 以降、arch/riscv/Kconfig に CONFIG_RISCV_DMA_NONCOHERENT が追加されました。 Kconfig の内容をみてわかるように、これが指定されている場合は dev_is_dma_coherent() は struct device の dma_cohrent フィールドの値を返すようになりました。
なお、CONFIG_RISCV_DMA_NONCOHERENT は menuconfig 等でユーザーが明示的に指定するものではなく、他の CONFIG によって間接的に指定されるようです。
config RISCV_DMA_NONCOHERENT
bool
select ARCH_HAS_DMA_PREP_COHERENT
select ARCH_HAS_SYNC_DMA_FOR_DEVICE
select ARCH_HAS_SYNC_DMA_FOR_CPU
select ARCH_HAS_SETUP_DMA_OPS
select DMA_DIRECT_REMAP
CONFIG_RISCV_ISA_ZICBOM
CONFIG_RISCV_DMA_NONCOHERENT を間接的に指定しているのが、同じく arch/riscv/Kconfig にある CONFIG_RISCV_ISA_ZICBOM です。
config RISCV_ISA_ZICBOM
bool "Zicbom extension support for non-coherent DMA operation"
depends on CC_HAS_ZICBOM
depends on !XIP_KERNEL && MMU
select RISCV_DMA_NONCOHERENT
select RISCV_ALTERNATIVE
default y
help
Adds support to dynamically detect the presence of the ZICBOM
extension (Cache Block Management Operations) and enable its
usage.
The Zicbom extension can be used to handle for example
non-coherent DMA support on devices that need it.
If you don't know what to do here, say Y.
RISC-V CPU は、当初、Cache の Coherence 問題はハードウェアで解決するポリシーだったようです(原典不詳、間違ってたらゴメンナサイ。たまたま RISC-V CPU の最初の実装がそうだっただけかもしれません。)
しかし、その後、キャッシュをソフトウェアで制御するための命令が RISC-V に追加されました。それが ZICBOM extension (Cache Block Management Operations) です。詳細は次の資料を参照してください。
- 「RISC-V Base Cache Management Operation ISA Extensions」
https://raw.githubusercontent.com/riscv/riscv-CMOs/master/specifications/cmobase-v1.0.1.pdf - 「Cache Management Operations for RISC-V」
https://github.com/riscv/riscv-CMOs
そして、次のように、コンパイル時に RISC-V CPU が ZICBOM をサポートしている場合は、自動的に CONFIG_CC_HAS_ZICBCM が true になり、CONFIG_RISCV_ISA_ZICBOM も true になります。
config CC_HAS_ZICBOM
bool
default y if 64BIT && $(cc-option,-mabi=lp64 -march=rv64ima_zicbom)
default y if 32BIT && $(cc-option,-mabi=ilp32 -march=rv32ima_zicbom)
つまり、Linux Kernel 6.0 以降は、RISC-V が ZICBOM をサポートしている場合は自動的に dev_is_dma_coherent() は struct device の dma_cohrent フィールドの値を返すようになります。
ここで struct device の dma_cohrent フィールドを適切に指定していなければ、場合によっては DMA Buffer を mmap した際にキャッシュが無効になってしまいます。
Linux Kernel 5.19 以前は特に問題なかったのに、Linux Kerne 6.0 にしたら何故かカメラ入力が遅くなったとか、DEVICEとのデータのやり取りが遅くなったとか発生するかもしれません。その場合は、struct device の dma_cohrent フィールドがちゃんと設定されていないのが原因かもしれません。
CONFIG_ERRATA_THEAD
もうひとつ、CONFIG_RISCV_DMA_NONCOHERENT を間接的に指定しているのが、arch/riscv/Kconfig.erratas にある CONFIG_ERRATA_THEAD です。
Errata とは、もともと誤字、誤植、誤謬などを意味する英語ですが、ここでは CPU などの半導体製品に回路設計の段階のミスがあって出荷時(または後)に公表されるものを指します。そして、Linux Kernel の Kconfig.erratas とかには、その Errata(ハードウェアのミス)をソフトウェア側で対処するための回避策に関わる内容が定義されます。
どうやら Alibaba 社の設計した T-HEAD という RISC-V CPU にキャッシュ上の設計ミスが見つかって、その対処が必要な場合に CONFIG_ERRATA_THEAD を定義しているようです。そして、その対処方法の一つが、CONFIG_RISCV_DMA_NONCOHERENT を設定することのようです。もしかしたら、Linux Kernel 6.0 以降、T-HEAD を採用しているコンピューターシステムは、DMA Buffer を mmap した時に CPU Cache が無効になるかもしれません。
config ERRATA_THEAD
bool "T-HEAD errata"
depends on !XIP_KERNEL
select RISCV_ALTERNATIVE
help
All T-HEAD errata Kconfig depend on this Kconfig. Disabling
this Kconfig will disable all T-HEAD errata. Please say "Y"
here if your platform uses T-HEAD CPU cores.
Otherwise, please say "N" here to avoid unnecessary overhead.
config ERRATA_THEAD_PBMT
bool "Apply T-Head memory type errata"
depends on ERRATA_THEAD && 64BIT && MMU
select RISCV_ALTERNATIVE_EARLY
default y
help
This will apply the memory type errata to handle the non-standard
memory type bits in page-table-entries on T-Head SoCs.
If you don't know what to do here, say "Y".
config ERRATA_THEAD_CMO
bool "Apply T-Head cache management errata"
depends on ERRATA_THEAD && MMU
select RISCV_DMA_NONCOHERENT
default y
help
This will apply the cache management errata to handle the
non-standard handling on non-coherent operations on T-Head SoCs.
If you don't know what to do here, say "Y".
まとめ
この記事では[『Linux では Cache Cohrence Hardware を持っていないとDMA Buffer をmmap する際に CPU Cache が無効になる』] コンピューターシステムの例として RISC-V CPU を紹介しました。ただ、必ずそうなるわけではなく、Linux Kernel 6.0 以降ではビルド時の設定や device tree などの設定に注意してくださいという注意喚起です。