はじめに
筆者は MPFS-DISCO-KIT(Microchip PolarFire SoC FPGA Discovery Kit) で動作する Ubuntu 22.04 を独自に構築しています。この記事では、この MPFS-DISCO-KIT 向け Ubuntu 22.04 でのメモリマップを解説します。
MPFS-DISCO-KIT とは
MPFS-DISCO-KIT(Microchip PolarFire SoC FPGA Discovery Kit) は、Microchip Technology 社の PolarFire SoC FPGA(MPFS095T_1FCSG325E) を搭載した開発用のキットです。
- PolarFire SoC FPGA with 95k LEs
- 1GByte main memory
- 1x Gigabit Ethernet
- 3x UART
- 1x micro-SD interface
- form factor 4.1" x3.3"
詳細は次の URL を参照してください。
MPFS-DISCO-KIT 向け Ubuntu 22.04 とは
筆者は MPFS-DISCO-KIT で動作する Ubuntu 22.04 を独自に構築しています。以下の URL で公開しています。
なお、これらはオフィシャルなものではなく、筆者の魔改造がはいっています。ご利用の際はこの点にご留意してください。
PolarFire SoC の特徴
メモリマップを解説するにあたり、あらかじめ知っておいたほうが良い事項を説明します。
MSS(Micro Processor Sub System)
ブロック図
MSS(Micro Processor Sub System) は PolarFire SoC FPGA のうち、CPU Core Complex(U54 Core、U51 Core、L2 Cache、割り込み、TileLink)、 DDR Memory Controller、ENET、USB、各種ペリフェラル、AXI Switch をひとくくりにまとめたものです。だいたい次の図のような構成になっています。
Fig.1 PolarFire SoC MSS Blcok Diagram
MSS の各ブロックの詳細は『 PolarFire SoC MSS Technical Reference Manual』を参照してください。
PolarFire SoC のメモリマップ
CPU キャッシュが有効か否かは物理アドレスによって決まる
CPU Core Complex がメモリにアクセスする場合には次の3つのモードがあります。
- Cached: CPU キャッシュが有効
- Non-Cached: CPU キャッシュが無効かつ WCB(Write Combining Buffer) も無効
- Non-Cached WCB: CPU キャッシュが無効だがWCB(Write Combining Buffer) は有効
これらのモードの切り替えは、物理アドレスによって決め打ちされています。
物理アドレスの下位と上位の二つのセグメントを持っている
CPU Core Complex がアクセスする領域は次の二つに分類しています。
- Low: 物理アドレス 0x00_FFFF_FFFF 以下の領域
- High: 物理アドレス0x01_0000_0000以上の領域
6つのメモリ領域の物理アドレスとサイズは固定
CPU Core Complex からみた DDR Memory へのアクセスは、Cached、Non-Cached、Non-Cached WCBの3通り × LowとHigh の2通りの合計6領域あり、PolarFire SoC では各々の物理アドレスとサイズは次のように決まっています。
Table.1 PolarFire SoC の DDR Memory Address Space
Name | Memory Address Space | ||
Start Address | End Address | Size | |
DDR-Cached Low | 0x00_8000_0000 | 0x00_BFFF_FFFF | 1GiB |
DDR-Cached High | 0x10_0000_0000 | 0x13_FFFF_FFFF | 16GiB |
DDR-Non-Cached Low | 0x00_C000_0000 | 0x00_CFFF_FFFF | 256MiB |
DDR-Non-Cached High | 0x14_0000_0000 | 0x17_FFFF_FFFF | 16GiB |
DDR-Non-Cached WCB Low | 0x00_D000_0000 | 0x00_DFFF_FFFF | 256MiB |
DDR-Non-Cached WCB High | 0x18_0000_0000 | 0x1B_FFFF_FFFF | 16GiB |
MPFS-DISCO-KIT の場合
MPFS-DISCO-KIT は 1GB の DDR-SDRAM が搭載されています。MPFS-DISCO-KIT 向け Ubuntu22.04 では、polarfire-soc-discovery-kit-reference-design を元にしており、このリファレンスデザインでは、上記6つの領域の DDR-SDRAM へのマッピングは次のように、すべて同じ開始アドレスが割り振られています。
Fig.2 MPFS-DISCO-KIT DDR-Memory Address Mapping
つまり、MSS からみた物理アドレス上は異なる領域からのアクセスであっても、DDR-Memory 上は同一のアドレスの可能性があります。この点に注意して DDR-Memory を割り当てる必要があります。
デバイスツリーによるメモリマッピング
MPFS-DISCO-KIT 向け Ubuntu22.04 では DDR Memory の割り振りを Device Tree で行っています。この章ではこの Device Tree を詳細に説明します。
メモリ領域
MPFS-DISCO-KIT は1GB の DDR Memory を搭載しています。MPFS-DISCO-KIT 向け Ubuntu22.04 では、この1GB を次の5つのメモリ領域に分けて管理しています。
- kernel: memory@80000000
- ddr_cached_low: memory@8a000000
- ddr_non_cached_low: memory@c4000000
- ddr_cached_high: memory@1022000000
- ddr_non_cached_high: memory@1412000000
これらの設定は Device Tree (mpfs-disco-kit.dts) で行っています。mpfs-disco-kit.dts のメモリ領域に関する記述を取り出すと次の通りです。
kernel: memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x4000000>;
};
ddr_cached_low: memory@8a000000 {
device_type = "memory";
reg = <0x0 0x8a000000 0x0 0x8000000>;
};
ddr_non_cached_low: memory@c4000000 {
device_type = "memory";
reg = <0x0 0xc4000000 0x0 0x6000000>;
};
ddr_cached_high: memory@1022000000 {
device_type = "memory";
reg = <0x10 0x22000000 0x0 0x1e000000>;
};
ddr_non_cached_high: memory@1412000000 {
device_type = "memory";
reg = <0x14 0x12000000 0x0 0x10000000>;
};
この節ではこれらのメモリ領域について解説します。
kernel: memory@80000000
Kernel は MSS アドレス空間のDDR-Cached Low 領域に配置されます。
kernel のMSS からみた開始アドレスは 0x00_8000_0000 で、領域の大きさは 0x0400_0000 です。DDR Memory からみた開始アドレスは0x0000_0000 になります。
この領域は U-Boot などブート時に Linux Kernel が配置されます。ブート後は、Linux Kernel の実行バイナリおよび各種ヒープに使用されます。
kernel: memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x4000000>;
};
Fig.3 kernel: memory@80000000
ddr_cached_low: memory@8a000000
ddr_cached_low は MSS アドレス空間のDDR-Cached Low 領域に配置されます。
ddr_cached_low の開始アドレスは 0x00_8a00_0000 で、領域の大きさは 0x0800_0000 です。DDR Memory からみた開始アドレスは0x0a00_0000 になります。
この領域はブート時には Device Tree Blob が一時的にロードされます。また、Linux 起動後はユーザープログラムの実行などに使用されます。
ddr_cached_low: memory@8a000000 {
device_type = "memory";
reg = <0x0 0x8a000000 0x0 0x8000000>;
};
Fig.4 ddr_cached_low: memory@8a000000
ddr_non_cached_low: memory@c4000000
ddr_non_cached_low は MSS アドレス空間のDDR-Non-Cached Low 領域に配置されます。
ddr_non_cached_low の開始アドレスは 0x00_c400_0000 で、領域の大きさは 0x0600_0000 です。DDR Memory からみた開始アドレスは0x0a00_0000 になります。
ddr_non_cached_low: memory@c4000000 {
device_type = "memory";
reg = <0x0 0xc4000000 0x0 0x6000000>;
};
Fig.5 ddr_non_cached_low: memory@c4000000
ddr_cached_high: memory@1022000000
ddr_cached_high は MSS アドレス空間のDDR-Cached High 領域に配置されます。
ddr_cached_high の開始アドレスは 0x10_2200_0000 で、領域の大きさは 0x1e00_0000 です。DDR Memory からみた開始アドレスは0x2200_0000 になります。
この領域はユーザープログラムの実行などに使用されます。
ddr_cached_high: memory@1022000000 {
device_type = "memory";
reg = <0x10 0x22000000 0x0 0x1e000000>;
};
Fig.6 ddr_cached_high: memory@1022000000
ddr_non_cached_high: memory@1412000000
ddr_non_cached_high は MSS アドレス空間のDDR-Non-Cached High 領域に配置されます。
ddr_non_cached_high の開始アドレスは 0x14_1200_0000 で、領域の大きさは 0x1000_0000 です。DDR Memory からみた開始アドレスは0x1200_0000 になります。
この領域は DMA Buffer として使用されます。
ddr_non_cached_high: memory@1412000000 {
device_type = "memory";
reg = <0x14 0x12000000 0x0 0x10000000>;
};
Fig.7 ddr_non_cached_high: memory@1412000000
予約されたメモリ領域
MPFS-DISCO-KIT 向け Ubuntu22.04 では 前節で割り振ったメモリ領域のうち、DMA Buffer などの特殊な用途専用にメモリ空間を予約しています。これらは Device Tree の reserved-memory ノードに記述されています。この節では、この予約されたメモリ領域の説明をします。
hss: hss-buffer@103fc00000
hss は メモリ領域 ddr_cached_high に予約されます。
hss の開始アドレスは 0x10_3fc0_0000 で、領域の大きさは 0x0020_0000 です。DDR Memory からみた開始アドレスは0x3fc0_0000 になります。
この領域は HSS と Linux Kernel との通信用バッファとして使用されます。
reserved-memory {
hss: hss-buffer@103fc00000 {
compatible = "shared-dma-pool";
reg = <0x10 0x3fc00000 0x0 0x200000>;
no-map;
};
};
Fig.8 hss: hss-buffer@103fc00000
dma_non_cached_low: non-cached-low-buffer
dma_non_cached_low は メモリ領域 ddr_non_cached_low に予約されます。
dma_non_cached_low の開始アドレスは 0x00_c400_0000 で、領域の大きさは 0x0400_0000 です。DDR Memory からみた開始アドレスは0x0400_0000 になります。
この領域は DMA Buffer として使用されます。
reserved-memory {
dma_non_cached_low: non-cached-low-buffer {
compatible = "shared-dma-pool";
size = <0x0 0x4000000>;
no-map;
alloc-ranges = <0x0 0xc4000000 0x0 0x4000000>;
};
};
Fig.9 dma_non_cached_low: non-cached-low-buffer
dma_non_cached_high: non-cached-high-buffer
dma_non_cached_high は メモリ領域 ddr_non_cached_high に予約されます。
dma_non_cached_high の開始アドレスは 0x14_1200_0000 で、領域の大きさは 0x1000_0000 です。DDR Memory からみた開始アドレスは0x1200_0000 になります。
この領域は DMA Buffer に使われます。特に linux,dma-default プロパティが設定されているため、各種デバイスドライバが DMA Buffer を確保する際は優先的にこの領域が使われます。
reserved-memory {
dma_non_cached_high: non-cached-high-buffer {
compatible = "shared-dma-pool";
size = <0x0 0x10000000>;
no-map;
linux,dma-default;
alloc-ranges = <0x14 0x12000000 0x0 0x10000000>;
};
};
Fig.10 dma_non_cached_high: non-cached-high-buffer
fabricbuf0ddrc: buffer@88000000
fabricbuf0ddrc は メモリ領域 ddr_non_cached_low に予約されます。
fabricbuf0ddrc の開始アドレスは 0x00_8800_0000 で、領域の大きさは 0x0020_0000 です。DDR Memory からみた開始アドレスは0x0800_0000 になります。
ここで話がややこしくなるのですが、DDR Memory の領域上は ddr_non_cached_low にあるのですが、MSS アドレス空間上のアドレスは DDR-Cached Low にあることです。したがって、ここへのアクセスは、キャッシュが有効になります。また、後述の fabricbuf1ddrnc や fabricbuf2ddrncwcb と DDR-Memory 上のメモリ領域を共有しています。
reserved-memory {
fabricbuf0ddrc: buffer@88000000 {
compatible = "shared-dma-pool";
reg = <0x0 0x88000000 0x0 0x2000000>;
no-map;
};
};
Fig.11 fabricbuf0ddrc: buffer@88000000
fabricbuf1ddrnc: buffer@c8000000
fabricbuf1ddrnc は メモリ領域 ddr_non_cached_low に予約されます。
fabricbuf1ddrnc の開始アドレスは 0x00_C800_0000 で、領域の大きさは 0x0020_0000 です。DDR Memory からみた開始アドレスは0x0800_0000 になります。
DDR Memory の領域上は ddr_non_cached_low にあり、MSS アドレス空間上のアドレスは DDR-Non-Cached Low にあります。したがって、ここへのアクセスは、キャッシュが無効になります。
また、前述の fabricbuf0ddrc や後述の fabricbuf2ddrncwcb と DDR-Memory 上のメモリ領域を共有しています。
reserved-memory {
fabricbuf1ddrnc: buffer@c8000000 {
compatible = "shared-dma-pool";
reg = <0x0 0xc8000000 0x0 0x2000000>;
no-map;
};
};
Fig.12 fabricbuf1ddrnc: buffer@c8000000
fabricbuf2ddrncwcb: buffer@d8000000
fabricbuf2ddrncwcb は メモリ領域 ddr_non_cached_low に予約されます。
fabricbuf2ddrncwcb の開始アドレスは 0x00_D800_0000 で、領域の大きさは 0x0020_0000 です。DDR Memory からみた開始アドレスは0x0800_0000 になります。
DDR Memory の領域上は ddr_non_cached_low にありますが、MSS アドレス空間上のアドレスは DDR-Non-Cached WCB Low にあります。したがって、ここへのアクセスは、キャッシュが無効になりますが WCB(Write Combining Buffer) は有効になります。
また、前述の fabricbuf0ddrc や fabricbuf1ddrnc と DDR-Memory 上のメモリ領域を共有しています。
reserved-memory {
fabricbuf2ddrncwcb: buffer@d8000000 {
compatible = "shared-dma-pool";
reg = <0x0 0xd8000000 0x0 0x2000000>;
no-map;
};
};
Fig.13 fabricbuf2ddrncwcb: buffer@d8000000
ペリフェラル各種からみたメモリマップ
デバイスツリー
PolarFire SoC の MSS には、CPU Core Complex 以外にも ENET や USB などの各種ペリフェラルがあります。これらのペリフェラルは mpfs.dtsi という Device Tree で soc ノードの配下として定義されています。一部を抜粋すると次のようになっています。実際には mac0 の他にも gpio や spi 等の各種ペリフェラルが定義されています。
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Copyright (c) 2020-2021 Microchip Technology Inc */
/dts-v1/;
#include "dt-bindings/clock/microchip,mpfs-clock.h"
/ {
#address-cells = <2>;
#size-cells = <2>;
model = "Microchip PolarFire SoC";
compatible = "microchip,mpfs";
:
(中略)
:
soc {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges;
:
(中略)
:
mac0: ethernet@20110000 {
compatible = "microchip,mpfs-macb", "cdns,macb";
reg = <0x0 0x20110000 0x0 0x2000>;
#address-cells = <1>;
#size-cells = <0>;
interrupt-parent = <&plic>;
interrupts = <64>, <65>, <66>, <67>, <68>, <69>;
local-mac-address = [00 00 00 00 00 00];
clocks = <&clkcfg CLK_MAC0>, <&clkcfg CLK_AHB>;
clock-names = "pclk", "hclk";
resets = <&mss_top_sysreg CLK_MAC0>;
status = "disabled";
};
:
(中略)
:
};
};
mpfs.dtsi は MPFS(Microchip PolarFire SoC) 共通のデバイスツリーです。各種ボード固有のデバイスツリーは、この mpfs.dtsi をインクルードして、個別の定義を追加します。例えば、MPFS-DISCO-KIT のデバイスツリーは次のようになっています。
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/* Copyright (c) 2020-2021 Microchip Technology Inc */
/dts-v1/;
#include "mpfs.dtsi"
#include "mpfs-disco-kit-fabric.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/leds/common.h>
/* Clock frequency (in Hz) of the rtcclk */
#define RTCCLK_FREQ 1000000
/ {
#address-cells = <2>;
#size-cells = <2>;
model = "Microchip PolarFire-SoC Discovery Kit";
compatible = "microchip,mpfs-disco-kit", "microchip,mpfs";
soc {
dma-ranges = <0x14 0x0 0x0 0x80000000 0x0 0x4000000>,
<0x14 0x4000000 0x0 0xc4000000 0x0 0x6000000>,
<0x14 0xa000000 0x0 0x8a000000 0x0 0x8000000>,
<0x14 0x12000000 0x14 0x12000000 0x0 0x10000000>,
<0x14 0x22000000 0x10 0x22000000 0x0 0x1e000000>;
};
:
(中略)
:
};
:
(中略)
:
&mac0 {
dma-noncoherent;
status = "okay";
phy-mode = "sgmii";
phy-handle = <&phy0>;
phy0: ethernet-phy@b {
reg = <0xb>;
};
};
:
(後略)
:
mpfs-disco-kit.dts では mac0 のノードに次のような変更を加えています。
- status プロパティに "okay" を上書きして mac0 ノードを有効化
- phy0 ノードを追加して PHY デバイスドライバと接続
- dma-noncoherent プロパティを追加(詳細は次々節)
これらの変更により MPFS-DISCO-KIT でmac0 を使えるようにしています。
CPUからみたメモリマップとの違い
もうひとつの留意点は soc ノードの dma-ranges プロパティです。dma-ranges は2セルの dma-address、2セルの cpu-address、2セルの size を一組とした dma-range を複数記述したものです。mpfs-disco-kit.dts では次のようになっています。
Table.2 MPFS-DISCO-KIT の soc の dma-ranges
dma address | cpu address | Size |
---|---|---|
0x14_0000_0000 | 0x00_8000_0000 | 64MiB |
0x14_0400_0000 | 0x00_c400_0000 | 96MiB |
0x14_0a00_0000 | 0x00_8a00_0000 | 128MiB |
0x14_1200_0000 | 0x14_1200_0000 | 256MiB |
0x14_2200_0000 | 0x10_2200_0000 | 480MiB |
dma-ranges は CPU からみたアドレスと DMA デバイスからみたアドレスが異なる場合に指定されるプロパティです。上記の dma-ranges が何を意味するかというと、CPU からみたアドレスとサイズは、それぞれメモリ領域で説明した kernel、ddr_non_cached_low、ddr_cached_low、ddr_non_cached_high、ddr_cached_high に相当しますが、DMA デバイスからみたアドレスは全て DDR-Non-Cached High に対応しています。これらを表で示すと次のようになります。
Table.3 MPFS-DISCO-KIT の CPU メモリマップと SoC メモリマップの対応
Memory Map(CPU) | Memory Map(DMA Device) | ||
memory range | cpu address | memory range | dma address |
kernel | 0x00_8000_0000 | DDR-Non-Cached High | 0x14_0000_0000 |
ddr_non_cached_low | 0x00_c400_0000 | 0x14_0400_0000 | |
ddr_cached_low | 0x00_8a00_0000 | 0x14_0a00_0000 | |
ddr_non_cached_high | 0x14_1200_0000 | 0x14_1200_0000 | |
ddr_cached_high | 0x10_2200_0000 | 0x14_2200_0000 |
CPU が確保した DMA Buffer がどのメモリ領域であっても DMA Device からみたメモリ領域はすべて DDR-NonCached Highに配置されています。このことは、キャッシュのコヒーレンシ—に注意しなければならないことを意味します。
キャッシュのコヒーレンシーに注意
PolarFire SoC の MSS ではキャッシュのコヒーレンシ—は TileLink によって自動的に保証されます。
Fabricや各種ペリフェラルがメモリにアクセスする際は一旦 AXI Switch を通します。AXI Switch は与えられたアドレスによってどのスレーブポートにアクセスするか決まります。『PolarFire SoC MSS Technical Reference Manual』の「Table 3-35. Address Ranges of Slaves」では次のようになっています。
Table.4 Address Range of Slaves (PolarFire SoC MSS Technical Reference Manual)
Slave Port | Region 1 | Region 2 | Region 3 | |||
Start | End | Start | End | Start | End | |
S1 | 0x20_0000_0000 | 0x2F_FFFF_FFFF | 0x00_6000_0000 | 0x00_7FFF_FFFF | - | - |
S2 | 0x30_0000_0000 | 0x3F_FFFF_FFFF | 0x00_E000_0000 | 0x00_FFFF_FFFF | - | - |
S3 | 0x00_4000_0000 | 0x00_5FFF_FFFF | - | - | - | - |
S4 | 0x00_2200_0000 | 0x00_2201_FFFF | - | - | - | - |
S5 | 0x00_2000_0000 | 0x00_21FF_FFFF | - | - | - | - |
S6 | 0x00_2800_0000 | 0x00_2FFF_FFFF | - | - | - | - |
S7 | 0x14_0000_0000 | 0x1B_FFFF_FFFF | 0x00_C000_0000 | 0x00_DFFF_FFFF | - | - |
S8 | 0x10_0000_0000 | 0x13_FFFF_FFFF | 0x00_8000_0000 | 0x00_BFFF_FFFF | 0x00_0000_0000 | 0x00_1FFF_FFFF |
S9 | 0x00_2300_0000 | 0x00_2303_FFFF | - | - | - | - |
DDR Memory に対するアクセスはアドレスによってS7(DDR Memory に直接アクセス)か S8(TileLinkを経由して DDR Memory にアクセス) かが選択されます。S8が選択された場合(TileLink を経由する場合)は、キャッシュのコヒーレンシーは TileLink によって自動的に保証されますが、S7 が選択された場合(DDR Memory に直接アクセス)は、キャッシュのコヒーレンシーは自動的に保証されません。
Fig.14 CPU Cached Access and Peripheral Non-Cached Access
したがって、S7 が選択された場合はキャッシュのコヒーレンシーはソフトウェアで保証する必要があります。具体的には Peripheral が DMA Buffer にアクセスする前後でCPUが明示的にキャッシュと DDR Memory と同期します。
そのために、デバイスツリーの soc ノード配下の DMA を行うデバイスには dma-noncoherent プロパティを定義して、キャッシュのコヒーレンシをソフトウェアで保証することを明示しなければなりません。
Linux のキャッシュコヒーレンシの詳細は別記事にまとめているので、[参考]の[Linux のキャッシュコヒーレンシーに関する記事]を参照してください。
参考
MPFS(Microchip PolarFire SoC) の資料
MPFS-DISCO-KIT(Microchip PolarFire SoC FPGA Discovery Kit) の資料
MPFS-DISCO-KIT 向け Ubuntu22.04 の記事
Linux のキャッシュコヒーレンシーに関する記事
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Cache Coherence 問題とは何か)』@Qiita
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Cache Coherence 問題の解決方法)』@Qiita
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (Linux Kernel の Cache 問題の扱い)』@Qiita
- 『Linux で DMA Bufferを mmap した時に CPU Cacheが無効になる場合がある (RISC-V CPU の注意点)』@Qiita