rpi_start.Sを読む (第4回)
続きです、と行きたいところですが、今回は先に以下の部分を読み解きます。
では、一行目のMMU_INITからいってみましょう。
三つめは(_end - KERNEL_BASE + 2 * L1_S_SIZE - 1)です。これは面倒ですね。_end, KERNEL_BASE, L1_S_SIZEはどう定義されているのでしょう。
まず、KERNEL_BASEはarch/evbarm/include/vmparam.hに定義されています。
KERNEL_BASE_EXTはconf/std.rpiに定義されています。
NetBSD 6.1_RC1のnetbsd-RPI.gzをreadelfしてみると、次のようになります。
残りのL1_S_SIZEは先ほども出てきたpte.hに書かれています。
n_sec = (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1)は、_end - KERNEL_BASEはkernelの末尾 - kernelの頭、つまりkernelのサイズと読めます。それにL1_S_SIZEつまり1MBの二倍、2MBを足して1を引くだから、0x1FFFFFを足すと以下のように計算されます。
ここで、attrつまりアトリビュートがどのようなものなのかイメージするために、ページテーブルの形式について触れておきます。
MMUはメモリアクセスの方式を複数選択でき、ページ(タイニー、スモール、ラージの三種)かセクション(セクション、スーパーセクション?)を選びます。NetBSD/rpiではセクションタイプのページテーブルを使っています。
セクションタイプのページテーブルは、ベースアドレス12ビットとアトリビュート20ビットから構成されています。
セクションの単位は1MBです。32bitメモリアドレスのCPUで表現できるアドレス空間は4GBですから、それを1MB単位で管理するには4096個のページテーブルエントリが必要になります。
4096は0も含めると0-4095で表現できますから4095=0xFFFつまり12ビットあればよいわけです。
あらためてattr = L1_S_PROTO | L1_S_AP_KRWを読みましょう。
L1_S_PROTO のSはどうやらセクションのことのようです。その頭文字をとってSなのでしょう。
L1_S_PROTOを追うと、pmap.hの L1_TYPE_Sに行き着きました。
さきのページテーブルの表でも下位2bitは10でしたよね。
L1_S_IMPは0X00000010で定義されています。IMPがimplementationの略だというのはわかりますが、なにをimplementatiionしているんでしょうかねぇ。
ということで、L1_S_PROTO = L1_S_PROTO_generic = L1_TYPE_S | L1_S_IMP = 0x02 | 0x00000010 = 0x0012になります。
つぎは、L1_S_AP_KRWです。
L1_S_AP_KRWはgenassym.cfでマクロになっていて、
L1_S_AP(AP_KRW)のAP_KRWもおなじみpte.hで定義されています。
まとめると、L1_S_AP_KRW = 0x01 << 10 = 0x0400になります。
L1_S_PROTO | L1_S_AP_KRW = 0x0012 | 0x0400 = 0x0412ということがわかりました。
MMU_INITの引数がすべてわかりました。
308 mmu_init_table: 309 /* Add 1MB of VA==PA at 0x00000000 so we can keep the kernel going */ 310 MMU_INIT(0x0, 0x0, 311 (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1), 312 L1_S_PROTO | L1_S_AP_KRW) 313 314 MMU_INIT(KERNEL_BASE, 0x0, 315 (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1), 316 L1_S_PROTO | L1_S_AP_KRW | L1_S_B | L1_S_C) 317 318 /* Map the 16MB of peripherals */ 319 MMU_INIT(RPI_KERNEL_IO_VBASE, RPI_KERNEL_IO_PBASE, 320 (RPI_KERNEL_IO_VSIZE + L1_S_SIZE - 1), 321 L1_S_PROTO | L1_S_AP_KRW) 322 323 /* end of table */ 324 MMU_INIT(0, 0, 0, 0)MMU_INITとはすぐ上にあるマクロのようです。
301 /* We'll modify va and pa at run time so we can use relocatable addresses. */ 302 #define MMU_INIT(va,pa,n_sec,attr) \ 303 .word va ; \ 304 .word pa ; \ 305 .word n_sec ; \ 306 .word attr ;単純に引数を.wordで並べればいいということですね。301行目のコメントが気になりますよねぇ。訳すと、再配置可能なアドレスを使えるように、実行時にva(仮想アドレス)とpa(物理アドレス)を変換する、といったところでしょうか。引数はそれぞれ、仮想アドレスと物理アドレスの先頭、n_sec はセクション数ですかね。attrはそのアドレス領域の属性でしょう。
では、一行目のMMU_INITからいってみましょう。
309 /* Add 1MB of VA==PA at 0x00000000 so we can keep the kernel going */ 310 MMU_INIT(0x0, 0x0, 311 (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1), 312 L1_S_PROTO | L1_S_AP_KRW)MMU_INITの引数を読み解いていきます。最初の二つ0x0, 0x0はいいですね。va=0x0, pa=0x0です。
三つめは(_end - KERNEL_BASE + 2 * L1_S_SIZE - 1)です。これは面倒ですね。_end, KERNEL_BASE, L1_S_SIZEはどう定義されているのでしょう。
まず、KERNEL_BASEはarch/evbarm/include/vmparam.hに定義されています。
#ifdef KERNEL_BASE_EXT #define KERNEL_BASE KERNEL_BASE_EXT #else #define KERNEL_BASE 0x80000000 #endifおっと、KERNEL_BASE_EXTが必要になりました。
KERNEL_BASE_EXTはconf/std.rpiに定義されています。
options KERNEL_BASE_EXT=0xc0000000次に、_endですが、うーん、arch/evbarm/conf/ldscript.evbarmになるのかな。
} . = ALIGN(32 / 8); _end = .; _bss_end__ = . ; __bss_end__ = . ; __end__ = . ; PROVIDE (end = .); }_endの具体的な定義はどうなるのでしょうか? カーネルをreadelfしてendというシンボルを探すと(ABSとendで探す) ちゃんとあるようです。
NetBSD 6.1_RC1のnetbsd-RPI.gzをreadelfしてみると、次のようになります。
n61rc1# readelf -a netbsd-RPI|grep end|grep ABS 6634: c0443034 0 NOTYPE GLOBAL DEFAULT ABS end 10028: c0443034 0 NOTYPE GLOBAL DEFAULT ABS __end__ 10100: c0443034 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__ 11198: c0443034 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__ 12138: c0443034 0 NOTYPE GLOBAL DEFAULT ABS _endですから_end = 0xc0443034ということになりそうです。
残りのL1_S_SIZEは先ほども出てきたpte.hに書かれています。
#define L1_S_SIZE 0x00100000 /* 1M */ #define L1_S_OFFSET (L1_S_SIZE - 1) #define L1_S_FRAME (~L1_S_OFFSET) #define L1_S_SHIFT 20これで謎だった_end, KERNEL_BASE, L1_S_SIZEがわかりました。
n_sec = (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1)は、_end - KERNEL_BASEはkernelの末尾 - kernelの頭、つまりkernelのサイズと読めます。それにL1_S_SIZEつまり1MBの二倍、2MBを足して1を引くだから、0x1FFFFFを足すと以下のように計算されます。
n_sec = 0xc0443034 - 0xC0000000 + 2 * 0x00100000 - 1 = 0x643033;だいぶ長くなりました。どこを読んでいたんでしたっけ? ということで再掲します。
309 /* Add 1MB of VA==PA at 0x00000000 so we can keep the kernel going */ 310 MMU_INIT(0x0, 0x0, 311 (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1), 312 L1_S_PROTO | L1_S_AP_KRW)MMU_INITの最後の引数、attr = L1_S_PROTO | L1_S_AP_KRWを読みましょう。
ここで、attrつまりアトリビュートがどのようなものなのかイメージするために、ページテーブルの形式について触れておきます。
MMUはメモリアクセスの方式を複数選択でき、ページ(タイニー、スモール、ラージの三種)かセクション(セクション、スーパーセクション?)を選びます。NetBSD/rpiではセクションタイプのページテーブルを使っています。
セクションタイプのページテーブルは、ベースアドレス12ビットとアトリビュート20ビットから構成されています。
31-20 | 19 | 18 | 17 | 16 | 15 | 14-12 | 11-10 | 9 | 8-5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
セクションのベースアドレス(12bit) | SBZ | 0 | nG | S | APX | TEX | AP | IMP | ドメイン | XN | C | B | 1 | 0 |
4096は0も含めると0-4095で表現できますから4095=0xFFFつまり12ビットあればよいわけです。
あらためてattr = L1_S_PROTO | L1_S_AP_KRWを読みましょう。
L1_S_PROTO のSはどうやらセクションのことのようです。その頭文字をとってSなのでしょう。
L1_S_PROTOを追うと、pmap.hの L1_TYPE_Sに行き着きました。
#define L1_S_PROTO_generic (L1_TYPE_S | L1_S_IMP)L1_TYPE_S, L1_S_IMPはpte.hで定義されています。
/* * ARM L1 Descriptors */ #define L1_TYPE_INV 0x00 /* Invalid (fault) */ #define L1_TYPE_C 0x01 /* Coarse L2 */ #define L1_TYPE_S 0x02 /* Section */ #define L1_TYPE_F 0x03 /* Fine L2 */ #define L1_TYPE_MASK 0x03 /* mask of type bits */ /* L1 Section Descriptor */ #define L1_S_B 0x00000004 /* bufferable Section */ #define L1_S_C 0x00000008 /* cacheable Section */ #define L1_S_IMP 0x00000010 /* implementation defined */ #define L1_S_DOM(x) ((x) << 5) /* domain */ #define L1_S_DOM_MASK L1_S_DOM(0xf) #define L1_S_AP(x) ((x) << 10) /* access permissions */ #define L1_S_ADDR_MASK 0xfff00000 /* phys address of section */L1_TYPE_Sは0x02で定義されており、テーブルの下位2bitを表現しています。
さきのページテーブルの表でも下位2bitは10でしたよね。
L1_S_IMPは0X00000010で定義されています。IMPがimplementationの略だというのはわかりますが、なにをimplementatiionしているんでしょうかねぇ。
ということで、L1_S_PROTO = L1_S_PROTO_generic = L1_TYPE_S | L1_S_IMP = 0x02 | 0x00000010 = 0x0012になります。
つぎは、L1_S_AP_KRWです。
L1_S_AP_KRWはgenassym.cfでマクロになっていて、
define L1_S_AP_KRW L1_S_AP(AP_KRW)からの
#define L1_S_AP(x) ((x) << 10) /* access permissions */なのですね。APはAccess Permissionsの略称だったのですね。
L1_S_AP(AP_KRW)のAP_KRWもおなじみpte.hで定義されています。
/* * Short-hand for common AP_* constants. * * Note: These values assume the S (System) bit is set and * the R (ROM) bit is clear in CP15 register 1. */ #define AP_KR 0x00 /* kernel read */ #define AP_KRW 0x01 /* kernel read/write */ #define AP_KRWUR 0x02 /* kernel read/write usr read */ #define AP_KRWURW 0x03 /* kernel read/write usr read/write */名前の謎がだいぶ解けてきました。SがSystem bit、KRWがkernel read/writeということのようです。
まとめると、L1_S_AP_KRW = 0x01 << 10 = 0x0400になります。
L1_S_PROTO | L1_S_AP_KRW = 0x0012 | 0x0400 = 0x0412ということがわかりました。
MMU_INITの引数がすべてわかりました。
309 /* Add 1MB of VA==PA at 0x00000000 so we can keep the kernel going */ 310 MMU_INIT(0x0, 0x0, 311 (_end - KERNEL_BASE + 2 * L1_S_SIZE - 1), 312 L1_S_PROTO | L1_S_AP_KRW)というのは次のように表現できます。
va = 0x0 pa = 0x0; n_sec = 0xc0443034 - 0xC0000000 + 2 * 0x00100000 - 1 = 0x643033; attr = 0x0412
コメント(0件)
- TB-URL http://www.tokuda.net/diary/0806/tb/