/home/tnishinaga/TechMEMO

日々行ったこと、面白かったことを書き留めます。

VyOSのISOイメージをビルドする

VyOSのISOイメージをビルドする方法のメモです.

最新バージョンのVyOSを用いる場合等は,自分でISOビルドしないと使えないはずです.

基本的な手順は以下のサイトを参照.

github.com

ISOの作り方

1. ビルドマシンの構築

VyOSのビルドはDebian Squeezeに依存しているので,Squeezeの入ったマシンが必要です.

Squeezeのインストールイメージは以下からダウンロードできます.

あとは仮想マシン等にSqueezeをインストールしてください.

必要パッケージのインストール

最低限必要なパッケージをインストールします.

debian-archive-keyringのインストール

インストールされてない場合を考慮してインストールします.

$ sudo apt-get install debian-archive-keyring

SquashFS toolsのインストール

ちょっと古いsquashfsパッケージが必要なので,backportから持ってきます.

$ sudo sh -c 'echo "deb http://backports.debian.org/debian-backports squeeze-backports main" >> /etc/apt/sources.list'
$ sudo apt-get update
$ sudo apt-get -t squeeze-backports install squashfs-tools

依存パッケージのインストール

$ sudo apt-get install build-essential git autoconf automake dpkg-dev live-helper syslinux genisoimage pdebuild-cross facechroot

以上で環境の構築は完了です.

2. ISOのビルド

githubからリポジトリをクローンしてビルドするだけです.

$ git clone https://github.com/vyos/build-iso.git
$ cd build-iso
$ export PATH=/sbin:/usr/sbin:$PATH
$ autoreconf -i
$ ./configure
$ sudo make iso

しばらくほっとくと ./livecd/binary.iso にVyOSのISOイメージが生成されます.

あとはこのISOを用いてVyOSにインストールしたり,すでにインストールされたVyOSに

# add system image binary.iso

でイメージを追加して使います.

ソースに変更を加えたISOの作り方

VyOSのソースに変更を加えたISOイメージは次の手順で作ることができます.

1. VyOSのsubmoduleソースをとってくる

VyOSのソースに変更を加える場合は,最初にsubmoduleのソースを取ってくる必要があります. 例えば,pppに変更を加えたい場合は,以下のコマンドを実行してpppのソースを取得します.

$ git submodule update --init pkgs/ppp

2. submoduleをビルドする

submoduleのビルドは build-iso/ ディレクトリ下のMakefileを使って行います.

例えばpppのビルドを行う場合は以下のようにします.

$ make ppp

ビルドが成功すると pkgs/ ディレクトリ以下にパッケージファイル(.debファイル)が生成されます.

ビルドに失敗した場合は次を参照してください.

必要パッケージが不足している場合の対処法

submoduleのビルドに必要なパッケージがない場合,ビルドが失敗することがあります. 例えばpppのビルドを行った場合は,次のようなログを出してビルドが失敗しました.

$ sudo make ppp
 dpkg-buildpackage -rfakeroot -D -us -uc -i -b -nc
dpkg-buildpackage: warning: using a gain-root-command while being root
dpkg-buildpackage: export CFLAGS from dpkg-buildflags (origin: vendor): -g -O2
dpkg-buildpackage: export CPPFLAGS from dpkg-buildflags (origin: vendor):
dpkg-buildpackage: export CXXFLAGS from dpkg-buildflags (origin: vendor): -g -O2
dpkg-buildpackage: export FFLAGS from dpkg-buildflags (origin: vendor): -g -O2
dpkg-buildpackage: export LDFLAGS from dpkg-buildflags (origin: vendor):
dpkg-buildpackage: source package vyatta-ppp
dpkg-buildpackage: source version 2.4.4rel-8+vyos2+lithium4
dpkg-buildpackage: source changed by Alex Harpin <development@landsofshadow.co.uk>
 dpkg-source -i --before-build ppp
dpkg-buildpackage: host architecture amd64
dpkg-checkbuilddeps: Unmet build dependencies: libpcap0.8-dev libpam0g-dev zlib1g-dev
dpkg-buildpackage: warning: Build dependencies/conflicts unsatisfied; aborting.
dpkg-buildpackage: warning: (Use -d flag to override.)
debuild: fatal error at line 1325:
dpkg-buildpackage -rfakeroot -D -us -uc -i -b -nc failed
make: *** [ppp] Error 29

このエラーメッセージ内に必要なパッケージは示されており,今回は

dpkg-checkbuilddeps: Unmet build dependencies: libpcap0.8-dev libpam0g-dev zlib1g-dev

と出ているので, libpcap0.8-dev libpam0g-dev zlib1g-devが足りてないことがわかります.

$ sudo apt-get install libpcap0.8-dev libpam0g-dev zlib1g-dev

として必要パッケージをインストールしたあと,再度 make ppp を叩けばビルドが成功するはずです.

3. ISOの再ビルドを行う

VyOSは pkgs/ 以下にビルドされたsubmoduleのパッケージ(.debファイル)が存在する場合,そちらを優先してISOに取り込み,ISOのビルドを行います.

使用するビルドコマンドは通常と同じです.

$ sudo make iso

以上です.

おやすみなさい,

EdgeRouterLiteでPPPoE IPv6接続ができない件(未解決)

以下の様な設定をEdgeRouterに行ったが,DHCPv6-pdが失敗してIPが降ってこない.

set interfaces ethernet eth0 pppoe 1 user-id imhXXXXXXX@bnf6.iij.ad.jp
set interfaces ethernet eth0 pppoe 1 password mypass
set interfaces ethernet eth0 pppoe 1 mtu 1454
set interfaces ethernet eth0 pppoe 1 firewall out modify pppoe-out

set interfaces ethernet eth0 pppoe 1 dhcpv6-pd rapid-commit enable
set interfaces ethernet eth0 pppoe 1 dhcpv6-pd pd 0 interface eth2 service slaac
set interfaces ethernet eth2 ipv6 dup-addr-detect-transmits 1
set interfaces ethernet eth2 ipv6 address

set system offload ipv6 forwarding enable
set system offload ipv6 pppoe enable

set interfaces ethernet eth0 pppoe 1 ipv6 enable

この原因について出来る限り調べてみたので,忘れないうちにメモしておく.

※ 私もよくわかっていない部分が多いので,間違っている部分があれば指摘をお願いします.

原因は何?

原因は多分,pppデバイス名のミスマッチ.

EdgeOS(vyatta/vyOSも多分同じ)ではpppoeを設定してpppデバイスを作ると,そのデバイス名はpppoeX(Xはid)で登録される.

実際にPPPoE IPv4 接続を行っているpppoe0は,デバイス名もpppoe0として登録されている.

pppoe0    Link encap:Point-to-Point Protocol
          inet addr:xxx.xxx.xxx  P-t-P:xxx.xxx.xxx.xxx  Mask:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1454  Metric:1
          RX packets:58433 errors:0 dropped:0 overruns:0 frame:0
          TX packets:57537 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:100
          RX bytes:25005546 (23.8 MiB)  TX bytes:8970832 (8.5 MiB)

しかし,PPPoE IPv6 接続を行っているpppoe1は,なぜかデバイス名がppp0で登録される.

ppp0    Link encap:Point-to-Point Protocol
          inet6 addr:
          ~省略~

これが原因で,pppoeX形式でデバイス名を欲しがっているスクリプトが軒並みエラーを起こしている.

例えば /var/log/vyatta/vyatta-commit.log を見てみると次のようなエラーが出ている.

# cat /var/log/vyatta/vyatta-commit.log

~ 省略 ~

[ interfaces ethernet eth0 pppoe 1 ipv6 dup-addr-detect-transmits 1 ]
Will set dup_addr_detect_transmits when pppoe1 comes up

~ 省略 ~

[ interfaces ethernet eth0 pppoe 1 dhcpv6-pd ]
sysctl: cannot stat /proc/sys/net/ipv6/conf/pppoe1/accept_ra: No such file or directory
Starting new daemon...

前者の

Will set dup_addr_detect_transmits when pppoe1 comes up

このエラーは,dup_addr_detect_transmitsのスクリプトによると /proc/sys/net/ipv6/conf/pppoeX/dad_transmits がない場合に出る.

github.com

私の環境ではpppoe1のpppデバイス名はppp0として登録されてしまっているので, /proc/sys/net/ipv6/conf/pppoe1/dad_transmits が見つからずにエラーとなる. (代わりに, /proc/sys/net/ipv6/conf/ppp0/dad_transmits)はある.

DHCPv6-PDのエラーも同様.

pppoeデバイスの名前はどこで決めている?

そもそも,pppデバイスのデバイス名はどこで決められるのかがわからないので調べてみた.

まず,一般的なLinuxマシンでpppoeの接続等を行っているのは,pppd というコマンド. manページは以下.

Man page of PPPD

EdgeOSではこのpppdコマンドを以下のように呼んでpppoeの接続を行わせている.

/usr/sbin/pppd call pppoe0

pppdは pppd call NAME と呼び出すと,ファイル /etc/ppp/peers/NAME からオプションを読み込んで設定する.

/etc/ppp/peers/NAME に書かれたオプションのうち,pppデバイス名を左右しそうなのはipparam というオプション.pppoe0の設定では次のように設定されている.

ipparam "pppoe0 "

このオプションについてpppdのmanページには次のように書かれている.

ipparam string
ip-up と ip-down スクリプト用に余分のパラメータを指定します。 このオプションが与えられた場合、 string が 6 番目のパラメータと して、これらのスクリプトに与えられます。

ip-up/ip-down スクリプトというのは,IPパケットが送受信可能になった際に使用される初期設定スクリプト. 加えてEdgeRouterには /etc/ppp/ip-pre-up という前段階のスクリプトが用意されており,その辺りを調べてみると /etc/ppp/ip-pre-up.d/0002rename-pppoe という怪しいスクリプトを見つけた.

このスクリプトを読んでみると

OLD_IFNAME=$1
NEW_IFNAME=$6

というように,ipparamで設定される6番目の引数がNEW_IFNAMEに設定され,

LINKADDR=$(ip addr show dev $OLD_IFNAME | grep inet6 | cut -c11-)
if [ ! -z "$LINKADDR" ]; then
  ip link set $OLD_IFNAME down
fi

で古いpppデバイス(未確認だが,多分pppoeXになる前はpppXという名前で登録されている)を停止させ,

if [ ! -z "$LINKADDR" ]; then
  ip addr add $LINKADDR dev $NEW_IFNAME
fi

このように新しい名前(pppoeX)に設定しなおしている. PPPoE接続のPPPデバイス名をpppoeXに設定しているのはこのスクリプトで間違いないだろう.

なぜPPPoE IPv6ではデバイス名変更スクリプトが動かない?

たぶん,PPPoE IPv6 では 読み込むスクリプトファイルが,IPv4の場合と異なるのが原因.

おるみんちゃんのブログに書いてあったように,NTT西/東日本でのPPPoE IPv6はPPPoe IPv4と違い,PPPoE接続が確立しただけではIPv6が降ってこない.

技術者見習いの独り言: EdgeRouterLite-3をGentooで運用しはじめました+IPv6導入(本篇)

IPv6を得るためには,PPP接続が確立したあと,DHCPv6-PDなどを使ってIPv6を取ってくる必要がある. つまり,IPv4の時と手順が異なるので,IPv4のip-up/ip-down スクリプトが使いまわせないのだと思う.

IPv6用のip-up/ip-down スクリプトは同じく /etc/ppp/ 以下に ipv6-up/ipv6-down という名前で存在している.しかし,中身は空っぽなので,機能しない.

ここにpppデバイスのリネームスクリプトを放り込んだらうまくいくような気がしているが,まだうまく動かせていない.

今日はここまで

大学のレポート書かないとダメなので,一旦中断.

2015/05/11 01:30追記

見えてきた気がする.

EdgeOSでは,PPPoEでPPPを張ると必ずIPv4が降ってくる前提でシステムが組まれている気がする.

/etc/ppp/ip-pre-up.d/0002rename-pppoe も,よく見るとinet6の値を見たりしている.IPv6を意識している.

私の使っているISPでは,IPv6のPPPoEサーバーにつなぐとIPv4は降ってこない. IPv4が降ってこないので,/etc/ppp/ 以下のIPv4用configを通らず,空っぽのIPv6の設定のみを行おうとする. で,各所でエラーが発生する.多分,そういうことが起きている.

もう少し色々試してみて,ダメだったらフォーラムに質問を投げることにする.

EdgeRouterLiteを使ってiijmioひかりでインターネット接続する方法メモ

家のネット回線が光になりました.

プロパイダは iijmioひかり です. キャッシュバック価格などを含めたひと月あたりの回線価格は他のプロパイダのほうが安いのですが,評判を見ると耐えられないほど速度が出ないとあったので,国内老舗ISPであるIIJなら信頼できると考え選びました.(が......速度に関しては上りは速くなりましたが,下りは逆に遅くなったような......時間帯が悪かったのか,IPv4は速度が出ないのか......)

さて,以前はモデムにルーターをぶっさせばDHCPでプライベートIP降ってくる環境だったのですが,光になってからはONUやらHGWやらPPPoEやら,見慣れない単語と設定のオンパレードで混乱します.

とりあえず今出てきた3語の意味は以下.

光回線からインターネットに接続するためには,ONUにHGW(または用意したルーター)を接続し,PPPoE接続でプロパイダのネットワークに接続する必要があります. PPPoE接続が確立されると,グローバルなIPが降ってきてインターネットに接続できるようになります.

この辺りの話は,おるみんちゃんのブログ記事がわかりやすいです.

技術者見習いの独り言: EdgeRouterLite-3をGentooで運用しはじめました+IPv6導入(本篇)

EfgeRouterの設定(IPv4

とりあえずIPv4でネットに繋がるようにしたいので,ONUにEdgeRouterのeth0を接続し,eth1をLANとして設定を行いました.

最終的なconfig.bootと設定コマンドは以下のgistを参照してください.

EdgeRouterLite iijmioひかり IPv4 config

MTUとMSS-clamp設定

重要なのは以下のPPPoEのMTUとmss-clamp設定の部分.

set interfaces ethernet eth0 pppoe 0 mtu 1454
 
set firewall modify pppoe-out rule 1 action modify
set firewall modify pppoe-out rule 1 modify tcp-mss 1414
set firewall modify pppoe-out rule 1 protocol tcp
set firewall modify pppoe-out rule 1 tcp flags 'SYN'
set interfaces ethernet eth0 pppoe 0 firewall out modify pppoe-out

光通信はPPPoEを用いるので,最低でもPPPoE接続なヘッダ分(8byte),一度に送れるデータが小さくなってしまします. そのため,一度に送れるデータの最大値を決める事ができるMTUの設定を下げなければ,うまく通信ができません.

IIJではこのMTUの値として1454以下を推奨しているので,その値を設定します.

IIJ4Uサービス終了のお知らせ

次に設定するのはmss-clampの部分. これを設定するとネットワークの接続相手に「xxx byte 以上のパケットを送らないで!」と予め伝えることができます.

こうすることでMTUの低い経路が途中にあったとしても,パケットサイズはmss-clampで設定した大きさ以下になるので通ることができるようになります.

一般にこのmss-clampの値はMTUから40引いた値が良いとされており,IIJでは1414が推奨されています.

Firewallの適応先

Firewallの適応先インターフェースは,今までの感覚だとONUに接続しているeth0にかければ良いと考えていました.

しかし,光回線でインターネットに接続しているのは物理的なポートではなく,PPPoEにより確立されたトンネルです. なので,トンネルに対してFirewallを適応する必要があります.

その設定を行うのが以下のコマンドです.

set interfaces ethernet eth0 pppoe 0 firewall in name OUTSIDE-IN
set interfaces ethernet eth0 pppoe 0 firewall local name OUTSIDE-LOCAL

とりあえずこれでIPv4は通るようになりました.

次はもう一本IPv6用のPPPoEトンネルを通してIPv6をつなげるよう,ルーター設定と格闘したいと思います.

Tips: どはまりぽいんと

EdgeRouterのEthernetポートにMacBookAirを直接接続して作業していたところ,ルーターの設定が問題ないにもかからわず,何故かネットに,それどころかルーターのWeb管理画面にすら全く接続できなくなりました.

原因がよくわからないのですが,EdgeRouterのEthernetポートにスイッチングハブを接続し,スイッチ経由で接続したらつながるようになりました(なんだそれ......

何か心当たりのある方は教えて下さい......

ARM_VMM: KernelVMキャンプの成果報告

3月27-29日にKernelVMキャンプに参加してきました. その成果報告をここに書きます.

知らない方のために説明すると,KernelVMキャンプは東京の山奥に籠もり昼夜問わず黙々とハックし続ける非常に厳しいキャンプで……えっ? 温泉? あいまいみー? 女装ヒルクライム? 何言ってるのかさっぱりわからないですね……

やったこと1:Raspberry Pi 2 を JTAGデバッグする

以前からsyuuさんと進めているRasPi2用VMM実装のため,JTAGデバッガが欲しいという話をしていました.

tnishinaga.hatenablog.com

この時はまだdbgbaseの話がフォーラムに上がっておらず,何故動かないのかをコードを追って探っていました. 結果,

  • Cortex-A7/A15 のサポートが 0.8.0リリース以降に追加されていること
  • JTAG接続先が見つかり,接続(TAP)はできていること
  • バスのアクセスポイントは見つかっており,接続できていること

までわかったところで2日目の夜になり,一旦切り上げて他のことをすることにしました.

※この問題は,後日ざますさんに聞いたら数分で解決しましたとさ(あびゃ〜)

tnishinaga.hatenablog.com

やったこと2: Raspberry Pi 2 のレジスタダンプ表示機能実装

「デバッガがうまくいかないなら,せめてシリアルからレジスタダンプを出したい」という要望を受けて,何らかの例外が飛んできた時にバンクレジスタを含めたレジスタダンプを表示するコードを作りました.

成果物は以下を参照してください.

github.com

ARMの一部のレジスタはバンクレジスタとなっており,CPUモードが切り替わるたびに,モードに合わせてレジスタが切り替わるようになっています. 詳細は,以下を参照してください.

今までこのバンクレジスタは対応するCPUモードからでないと読めないと思っていましたが,実はそうでなく,以下のようにmrs命令とmsr命令を用いれば各バンクレジスタにアクセスできました.

// IRQモードのspレジスタ値をr0にロード
mrs r0, SP_irq

後は読んだものを適当にスタックに積んで,printfで出力するだけ.

動かしてみると,バンクレジスタ含めたレジスタダンプが得られました.

swi called
swi_hello: cpsr = 0x600001d3
lr_und: 0xefdf6ed7
sp_und: 0xfeb74eb7

lr_abt: 0xebdd7acf
sp_abt: 0xff8fcbff

lr_svc: 0x00008070
sp_svc: 0x063fff5c

lr_irq: 0xbf9cfd7f
sp_irq: 0x00008000

lr_fiq: 0xf5feff8f
sp_fiq: 0xecf5ff66
r12_fiq: 0xe7a43d5b
r11_fiq: 0xdcfab3b9
r10_fiq: 0xef66f5ba
r9_fiq: 0x7ff7f9d3
r8_fiq: 0x7eeb5d7f

lr_usr: 0x3caafffd
sp_usr: 0xb7ffa6fb
r12_usr: 0x00000000
r11_usr: 0x063ffffc
r10_usr: 0x7b7f7138
r9_usr: 0x7dfdf7ff
r8_usr: 0xff777dff
r7_usr: 0x98f1fcfb
r6_usr: 0xffd77ff7
r5_usr: 0xfdfdaf5f
r4_usr: 0x00008000
r3_usr: 0x00000000
r2_usr: 0x00382157
r1_usr: 0x00000000
r0_usr: 0x0000000a

どはまりぽいんと

バンクレジスタ名はcase sensitive

バンクレジスタ名はcase sensitive(大文字小文字を区別する)なので,SPとLRは大文字にしないとコンパイルエラーとなる点に注意しないといけない.(最近のgccとかllvmだと治ってるかも)

HYPモードとMONモードのバンクレジスタ

このコードはSVCモードで走っているが,HYPモードとMONモードはSVCモードより権限が上なので,SVCモードからHYPやMONモードのバンクレジスタにアクセスしに行くとアクセス違反で落ちてしまう.

HYPモードとMONモードのバンクレジスタは,モード切り替えして取りに行かないとダメかもしれない(まだ試してない).

今後の方針

  • 仮想化支援機構をつかって,HYPモードと別モード間を行き来するデモを作る

Raspberry Pi2をJTAGデバッグできました

タイトルのとおりです. BareMetalなRaspberry Pi2にJTAGデバッガをつなげて,gdbレジスタ読んだり実行止めたり出来ました.

とりあえず,メモだけ残します.

参考にしたサイト

用意するもの

  • JTAGデバッガ : ARM-USB-TINY-H

使ったソフト

  • OpenOCD 0.9.0-dev-00358-gd3c2679-dirty

手順

1. OpenOCDのビルド

0.8.0リリース以降にCortex-A7/A15用のサポートが入ったので,最新版のコードを持ってきて各環境でビルドする.

ビルド方法は時間がないので省略.

ARM-USB-TINY-Hを使うには

$ ./configure --enable-ftdi

としなければならないことに注意.

2. configファイルの用意

RasPi2用のターゲットファイルは,フォーラムの情報を参考に作成したので,これを使う

OpenOCD target config file for Raspberry Pi 2

最初,dbgbaseアドレスの意味がわからず苦戦したが,ざますさん(@At_Zamasu_Zansu)のお力添えにより,解決した.

dbgbaseアドレスの場所は,以下の資料を参照する.

3. OpenOCDでGDBサーバーを建てる

$ openocd -f interface/ftdi/olimex-arm-usb-tiny-h.cfg -c "transport select jtag" -f rpi2.cfg

とすると,OpenOCDがGDBサーバーを立ち上げてくれる

4. GDBサーバーに接続する

$ arm-none-eabi-gdb
(gdb) target remote localhost:3333

GDBサーバーに接続できる. あとはデバッグするだけ.

余談

enable-legacy-ft2232_libftdi オプションは,OpenOCD 0.9.0では非推奨となっていた.

イマドキのやり方は,

$ ./configure --enable-ftdi

を指定し,インターフェース設定ファイルは interface/ftdi/ 以下のものを使うらしい.

しかし,この新しいインターフェース設定ファイルと既存のターゲット設定ファイルを混ぜると,エラーが出て動かなくなる.

$ openocd -f interface/ftdi/olimex-arm-usb-tiny-h.cfg  -f rpi2.cfg
Open On-Chip Debugger 0.9.0-dev-00358-gd3c2679-dirty (2015-04-03-21:53)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.sourceforge.net/doc/doxygen/bugs.html
adapter speed: 1000 kHz
adapter_nsrst_delay: 400
none separate
Info : auto-selecting first available session transport "jtag". To override use 'transport select <transport>'.
embedded:startup.tcl:160: Error: session transport is "jtag" but your config requires JTAG
in procedure 'script'
at file "embedded:startup.tcl", line 58
in procedure 'jtag' called at file "rpi2.cfg", line 19
in procedure 'default_to_jtag' called at file "embedded:startup.tcl", line 165
at file "embedded:startup.tcl", line 160

原因はよくわからない.後日調べて動くようにしたい.

ついでに,BareMetalなRasPi2について語っているところを,フォーラムに見つけた

今日は,もう眠いので,寝ます. おやすみなさい.

追記

イマドキのやり方でARM-USB-TINY-H使えました.

変更点はopenocdの起動オプションに

-c "transport select jtag"

をつけるだけです. つまり,openocdの起動を

$ openocd -f interface/ftdi/olimex-arm-usb-tiny-h.cfg -c "transport select jtag" -f rpi2.cfg

とすると,エラーをださずに起動できます.

他のFTDI系JTAGデバッガも同じ方法でエラーを回避できるはずです.

ARMアセンブラの16bit即値代入でコンパイルエラーが出た件

以下の様な16bit即値をレジスタr0に代入するコードをコンパイルしたところ,エラーとなったので,解決策をメモする.

ターゲットはRaspberry Pi 2.コンパイラgcc 4.7.2

mov r0, #0xffff

ARMの公式ドキュメントを読んでみると

ARMv6T2 以上のアーキテクチャでは、MOV は 0x0-0xFFFF (0 ~ 65535)範囲内にある任意の 16 ビットの数値をロードできます。

と書かれているので,armv7-aなRaspberry Pi 2では問題ないはずだが……

数分後...

エラーログで調べてみると,stackoverflowに同じ問題で悩んでいる人が居た.

stackoverflow.com

読んでみると,コンパイラアーキテクチャを教えない場合,armv5としてコンパイルが行われるので,movの範囲が0x00~0xffに制限されてしまうようだ.

ということは,コンパイルオプションに以下を追加してarmv7-aであると知らせるだけで治る...とおもったら,うまくいかなかった.

-march=armv7-a

最終的にmov命令をmovw命令に変更したところ,コンパイルが通った.

movw #0xffff

ARM_VMM: Cortex-A7でMMUを有効化する方法メモ

ARMの仮想化支援機能を使ってみたかったので, 現在しゅううさん(@syuu1228)と一緒に教育用自作VMMを作成中です. もちろん,BareMetalで.

対象ボードは,とりあえずCortex-A7を積んだRaspberry Pi 2を予定中. (ARM64なボードが届いたらそちらに移るかも?)

まだ色々調べている段階なので,道中わかったことをメモしていきます.

ARMの仮想化支援

ARMの仮想化支援機構は,Cortex-A15(+ Cortex-A7)から導入された. ここで行われた主なアーキテクチャ拡張は以下の3つ:

  • ハイパバイザモードの追加
  • 2重メモリアドレス変換機能
  • 物理アドレス空間の40bitへの拡張

各機能の詳細はまだ調べきれていない. 以下の資料を参照.

http://www.hotchips.org/wp-content/uploads/hc_archives/hc22/HC22.23.220-1-Brash-ARMv7A.pdf

VMMの概要

ホストOSとゲストOSの動作イメージが,うまくつかめていない.

以下の資料を見る限りだと,VMMはHYPモードで動くが,ホストOSとゲストOSはどちらもSystemモードとユーザーモードで動くらしい.

http://www.linux-kvm.org/wiki/images/d/d4/2012-forum-arm-christoffer-dall.pdf

つまり,

モード OS
User ゲストOS
System ホストOS
HYP ハイパバイザ

ではなく

モード OS
User ホスト & ゲストOS
System ホスト & ゲストOS
HYP ハイパバイザ

というイメージで動くっぽい.

ゲストOSからハイパバイザへの復帰,HWアクセスのトラップ方法についてはまだわかってない(後者はSystem MMUというのを使うっぽい?).

System MMUの資料(要ログイン)

MMUの有効化方法

VMMを作るにはMMUの理解が必須だと思うけれど,私はMMUについて全く知らない.

これではまずいので,MMUについて理解を深めるため,まずはRaspberry Pi 2のMMUを有効化する方法について調べ始めた.

資料

資料は以下の3つ:

一番最後の資料はRenesasの資料.

日本語なのでわかりやすいけど,説明が一部間違っているようなので注意.

VMSAとPMSA

アーキテクチャマニュアルを読むと,メモリシステムアーキテクチャの項目が,VMSAとPMSAの2つある.両者の違いは以下.

アーキテクチャ 説明
VMSA MMUベースの仮想メモリシステムアーキテクチャ.Cortex-A対象
PMSA MPUベースの保護メモリシステムアーキテクチャ.Cortex-R対象

つまり今回はCortex-A7が対象なので,VMSAについて調べれば良い.

Renesasの資料は,どうも一部の記述がVMSAとPMSAを間違えているようなので,適宜読み替えて読む(もしかするとRenesasのCortex-A9では正しいのかもしれない)

MMUアドレス変換の仕組み

まだ説明できるほど理解してない.

とりあえず,アドレスの変換のレイアウトを物理アドレスと同じにすれば,物理アドレスの時と同じように扱えるっぽい.

MMUの初期化

起動直後はMMUが無効になっているので,有効にしないといけない.

各種L2キャッシュはMMUが動いていないと使えないので,MMU動かすついでにL2キャッシュも有効にしてあげるといいらしい.

有効化までの手順は先ほど上げたRenesasの資料にわかりやすく書かれており,以下のようにすれば良い.

  1. TLBの初期化
  2. 命令キャッシュの初期化(Invalidate)
  3. データキャッシュの初期化
  4. 予測分岐の初期化
  5. MMU, 命令キャッシュ,データキャッシュ, 分岐予測の有効化

MMUの初期化手順のみなら,アーキテクチャマニュアルの「Enabling MMUs」にも書かれている.

コプロセッサ設定のやり方

MMUやキャッシュの設定はメモリの特定アドレスにアクセスして行うのではなく,コプロセッサに対し専用命令を使って行う.

専用命令は以下の2つ. 前者が書き込み命令で,後者が読み込み命令

命令 構文
mcr mcr pn, Op1, Rt, CRn, CRm, Op2
mrc mrr pn, Op1, Rt, CRn, CRm, Op2

構文の各項目の意味は以下.

項目 説明
pn 命令が実行されるコプロセッサ
Op1,2 コプロセッサ固有のオペコード
Rt コプロセッサとデータをやりとりするARMレジスタ
CRn, CRm コプロセッサレジスタ

Rtを除く項目は,アーキテクチャマニュアルに命令と項目の対応表があるので,参考に設定する.

TLBの初期化

TLBの初期化(Invalidate)は,TLBIALLレジスタを叩いて行う. Rtの値は不問.

mcr p15, 0, r0, c8, c7, 0

命令キャッシュの初期化

キャッシュにはゴミが残ってる可能性があり,これを掃除しないと実メモリにゴミが書かれてしまうかもしれないので,こちらも初期化を行う.

命令キャッシュの初期化はICIALLUレジスタに0を書き込んで行う.

mov r0, #0
mcr p15, 0, r0, c7, c7, 0

データキャッシュの初期化

データキャッシュの初期化はDCISWレジスタに0を書き込んで行う.

mcr p15, 0, r0, c7, c6, 0

分岐予測キャッシュの初期化

分岐予測の初期化はDCISWレジスタに0を書き込んで行う.

mcr p15, 0, r0, c7, c5, 6

MMU, 命令キャッシュ, データキャッシュ, 分岐予測 の有効化

MMUとキャッシュの有効化は,システム制御レジスタ SCTLR で行う. このレジスタのキャッシュとMMUに関するビットを立てると,各機能が有効になる.

ビット 機能
12 命令キャッシュ
11 分岐予測
1 データキャッシュ
0 MMU

SCTLRレジスタの詳細はアーキテクチャマニュアルを参照.

movw r0, #0x1803
mcr p15, 0, r0, c1, c0, 0

MMUの初期設定

今後の課題.

参考になりそうなサイトとか

d.hatena.ne.jp

Enabling HYP mode on the Raspberry Pi 2 | flexVDI

http://www.linux-kvm.org/wiki/images/d/d4/2012-forum-arm-christoffer-dall.pdf

続く...