/home/tnishinaga/TechMEMO

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

UEFIアプリでファイルを任意のメモリアドレスにロードする方法メモ

UEFIアプリケーションでファイルを任意のメモリアドレスにロードする方法についてのメモです. ブートローダーを作ったりするのに役立つと思います.

開発環境はEDK2(28f27af6f007c3794fcc9d098ef91713160f4e5b),OSはArch Linuxを使いました.

これを行うにあたって,そもそもUEFIって何? 環境構築の方法は? 仕様書の読み方は? プロトコルとハンドラって何? どうやって動作確認するの?...等々いろいろわからないことがあり調べたのですが,今回はメモなので記述を省略します. 気力があれば後日書きます.

まだまだ勉強途中なので,間違い等ありましたらコメントでの指摘をお願いします.

ファイルを任意メモリアドレスにロードする方法

ソースコード

コードはAppPkg/Application/Helloを改変して作りました.

まずはiniファイルを示します.

## @file
#  A simple, basic, EDK II native, "hello" application.
#
#   Copyright (c) 2010, Intel Corporation. All rights reserved.<BR>
#   This program and the accompanying materials
#   are licensed and made available under the terms and conditions of the BSD License
#   which accompanies this distribution. The full text of the license may be found at
#   http://opensource.org/licenses/bsd-license.
#
#   THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#   WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Hello
  FILE_GUID                      = a912f198-7f0e-4803-b908-b757b806ec83
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = UefiMain

#
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  Hello.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

[Guids]
  gEfiFileInfoGuid

[Protocols]
  gEfiSimpleFileSystemProtocolGuid

次にCのソースを示します.

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Protocol/DevicePath.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimpleFileSystem.h>


#define BUF_MAX_SIZE (512*1024) // 512KB

#define KERNEL_BASE  0x200000

EFI_SYSTEM_TABLE  *gST;
EFI_BOOT_SERVICES *gBS;

EFI_STATUS
EFIAPI
UefiMain (
      IN     EFI_HANDLE        ImageHandle,
      IN     EFI_SYSTEM_TABLE  *SystemTable
      )
{
  Print(L"Hello there fellow Programmer.\n");
  Print(L"Welcome to the world of EDK II.\n");


  EFI_STATUS                       Status = EFI_SUCCESS;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *SimpleFile;
  EFI_FILE_PROTOCOL                *Root;
  EFI_FILE_PROTOCOL                *File;
  CHAR16 *Path = L"hoge.txt";

  gST = SystemTable;
  gBS = gST->BootServices;

  Status = gBS->LocateProtocol (
                &gEfiSimpleFileSystemProtocolGuid,
                NULL,
                (VOID **)&SimpleFile
                );
  if (EFI_ERROR (Status)) {
    Print(L"%r on Locate EFI Simple File System Protocol.\n", Status);
    return Status;
  }

  Status = SimpleFile->OpenVolume (SimpleFile, &Root);
  if (EFI_ERROR (Status)) {
    Print(L"%r on Open volume.\n", Status);
    return Status;
  }

  Status = Root->Open (Root, &File, Path, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
  if (EFI_ERROR (Status)) {
    Print(L"%r on Open file.\n", Status);
    return Status;
  }

  UINTN                            BufferSize = 0;
  EFI_PHYSICAL_ADDRESS Buffer;
  BufferSize = BUF_MAX_SIZE;
  Buffer = (EFI_PHYSICAL_ADDRESS)KERNEL_BASE;
  // if you want to know MemoryType List, please refer UEFI Spec2.5 document pp.152-153
  Status = gBS->AllocatePages(
                 AllocateAddress,
                 EfiLoaderData,
                 BufferSize / 4 / 1024, // 1page = 4KiB 
                 &Buffer
                 );
  if (EFI_ERROR (Status)) {
    Print(L"%Could not allocate memory pool %r\n", Status);
    return Status;
  }

    
  Status = File->Read(
              File,
              &BufferSize,
              (VOID *)Buffer
              );
  if (EFI_ERROR (Status)) {
    Print(L"%r on Open file.\n", Status);
    return Status;
  }
  Print(L"BufferSize = %d\n", BufferSize);
  Print(L"Address = %p\n", Buffer);

  for (UINTN i = 0; i < BufferSize; i++) {
    Print(L"%c", ((CHAR8 *)Buffer)[i]);
  }
  Print(L"\n");


  File->Close(File);
  Root->Close(Root);

  gBS->FreePages(Buffer, BUF_MAX_SIZE);

  Print(L"All success hoge.txt\n");

  return EFI_SUCCESS;
}

処理の流れ

このコードの流れを日本語で示すと,次のようになっています.

  1. BootService(以降BS)からLocateProtocol関数を呼び出し,ファイルアクセスするためのプロトコルEFI_SIMPLE_FILE_SYSTEM_Protocol(以降SimpleFile)を取得する
  2. SimpleFileのOpenVolume関数でデバイスのルートディレクトリを開き,ルートディレクトリのファイルハンドラ(Root)を取得する
  3. RootのOpen関数で指定したファイル(今回はhoge.txt)を開き,そのファイルハンドラ(File)を取得する
  4. BSからAllocatePages関数を呼び出し,任意アドレスからメモリを確保する
  5. ファイルハンドラFileのRead関数を呼び出し,ファイルの内容を確保したメモリ領域に読みこむ
  6. ファイルハンドラを閉じる
  7. メモリを開放する

ファイルを場所を気にせず読みこむだけであれば,AllocatePool関数を使うことで手軽にメモリを確保できます. しかし,今回は「任意」のメモリアドレスにファイルを読み込むため,AllocatePagesという関数を用いています.

AllocatePages関数は第一引数にAllocateAddressを指定することで,第4引数で指摘したアドレスから第3引数で指定したページ分(1ページは4KiB)メモリを確保しようと試みます.

メモリが確保できない場合は,NOT FOUNDエラーが返ります.どの領域でどの程度メモリが取れるのかは,調べているところなのでまだわかっていません.

実行結果

メモリの確保が行われ,正常に動作した場合のログは以下のようになります.

UEFI Interactive Shell v2.1
EDK II
UEFI v2.50 (EDK II, 0x00010000)
Mapping table
      FS0: Alias(s):HD7a1:;BLK3:
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
     BLK2: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK4: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK0: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x0)
     BLK1: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x0)/Floppy(0x1)
Press ESC in 5 seconds to skip startup.nsh or any other key to continue.
Shell> fs0:
FS0:\> Hello.efi  
Hello there fellow Programmer.
Welcome to the world of EDK II.
BufferSize = 5
Address = 200000
hoge

コードで指定したKERNEL_BASEのアドレス0x200000からメモリを取得し,hoge.txtの内容(hoge)を読み込み,出力できていることが確認できます.

emacs使い始めましたメモ1

リモート接続してCUI環境で作業することが多くなってきたので,CUIテキストエディタも使えるように環境を整備し始めました.

基本的に政治と宗教の話はしないのですが,私はemacsを使うことにしました. emacsベテランの方はいろいろ教えていただけると嬉しいです.

環境

OS: Mac OSX 10.10.4 パッケージ管理: Homebrew, Homebrew-cask

emacs のインストール

デフォルトで入ってるのは古いので,以下のコマンドで新しいのをインストールします.

$ brew install --with-cocoa emacs
$ ln -sfv /usr/local/opt/emacs/*.plist ~/Library/LaunchAgents
$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.emacs.plist
$ brew linkapps emacs

--with-cocoa オプションをつけると,GUIでもemacsが使えるようになります. ファイルをダブルクリックしてemacsを開いて編集したいときは,必要です. ただし,関連付けは行ってくれないので,自分でやる必要があります(コマンドで設定する方法があれば教えてください).

基本操作

とりあえず,

  • C-? は Ctrl + ?
  • M-? が Esc + ?

であることがわかれば,後は自分で検索して何とか出来るはず.

init.elの作成

emacsはホームディレクトリ以下の .emacs ファイルや .emacs.d 以下にプラグインを記述することで機能を拡張できるようです. プラグインEmacs 用のLispEmacs Lisp を使って記述します.拡張子.el です.

以下のコマンドを実行して, .emacs.d/init.el を作ります(もし.emacsファイルがインストール時にできていた場合は,それを削除してください).

$ mkdir ~/.emacs.d
$ touch ~/.emacs.d/init.el

パッケージ管理システム

リポジトリの登録

emacs24からは,emacsプラグイン用のパッケージ管理システムがデフォルトで入っています. ただし,リポジトリを登録しないと使えないので,その設定をします.

.emacs.d/init.el を開いて以下を追記し,リポジトリの一つであるMELPAを追加してください.

(require 'package)
(add-to-list 'package-archives
             '("melpa" . "http://melpa.org/packages/") t)
(when (< emacs-major-version 24)
  ;; For important compatibility libraries like cl-lib
  (add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/")))
(package-initialize)

記述後,emacsを再起動するとパッケージ管理システムが使えるようになります.

パッケージ管理システムの使い方

以下のコマンドを実行すると,パッケージ管理システムの画面が立ち上がります.

M-x package-list-packages

起動した後の画面はこんな感じ

f:id:tnishinaga:20150704221831p:plain

パッケージのインストール方法は,インストールしたいパッケージ名(青色になってるところ)にカーソルを移動し,Enterを押すと画面の下半分にパッケージの詳細が出ます.

その状態で C-x o(Ctrl+xを入力後,Ctrlを離してoを入力) を入力すると,下半分の詳細欄にカーソルが移動します. その後「Install」のところにカーソルを持っていき,Enterを押すと以下の様な確認メッセージが出るので,そこで「y」を押せばインストールが開始されます.

f:id:tnishinaga:20150704221836p:plain

インストールしたパッケージ

とりあえずmultiple-cursorsをインストールして,SublimeTextのCmd+DやCmd+Shift+Lを使えるようにしてみました.

region-bindings-mode(インストールが必要)を使ってキーバインドを登録しているので,Ctrl+spaceで範囲を選択後

  • aを押すと,選択した単語すべてを選択してカーソルを増殖
  • nを押すと,選択した単語を順番に選択してカーソルを増殖
  • lを押すと,選択した行全てにカーソルを増殖

できるようにしています.

カーソルを1つに戻したいときは,Ctrl+spaceを入力して範囲選択モードに入った後,「;」を入力するとmultiple-cursorsモードが終了します.

今後追加したい機能たち

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デバッガも同じ方法でエラーを回避できるはずです.