rpi_start.Sを読む (第7回)
さて、前回175行目にジャンプしたところで終わったので、175行目から始めましょう。
今回は、どどんと10行読みます。ちょっと長いかな。
itableにはmmu_init_tableのアドレスが入っており、
といった感じにメモリに展開されていたわけですから、ldmia命令によって
先に進みましょう。
lsrは(Logical Shift Right)で右シフト。L1_S_SHIFTが20だったので、
MMU_INITマクロの3つ目はn_secが0x010FFFFFだったので、0x010FFFFF >> 20はn_sec = 0x10ですね。
カーネルのサイズが4MBのところを0x6MBぶん、ペリフェラルの所は16MBなので0x10MBぶん。カーネルは1MB多めにするって書いてありましたけど2MB増えているような気がしますが、よくわかりません。
n_secはセクション数ということで、カーネルは4セクション、ペリフェラルは16セクションということになります。
次に進みます。
ではなぜ、4倍するんでしょうか?
コメントにva からL1オフセットに変換する、とありますね。32ビットのpa+attrを入れるとアドレスは4つ進むから、L1オフセットは4刻みで増える、だから4倍するのですね。
実際の数字がどうなるかというと、カーネルとペリフェラルのそれぞれで次のようになります。
つぎは、PAをL1エントリに変換です。
L1_S_FRAMEが0xFFF00000ですから、ページテーブルのフォーマットに合わせて、上位12ビットにセクションを入れて下位20ビットにアトリビュートを入れているわけですね。
第4回で触れたページテーブルのフォーマットを再掲しておきます。
先に進みましょう。
ということで、今回ぶんをCっぽくしてみると次のような感じでしょうか。
今回は、どどんと10行読みます。ちょっと長いかな。
175 3: ldmia itable!, {va,pa,n_sec,attr} 176 mov n_sec, n_sec, lsr #L1_S_SHIFT 177 /* Convert va to l1 offset: va = 4 * (va >> L1_S_SHIFT) */ 178 mov va, va, LSR #L1_S_SHIFT 179 mov va, va, LSL #2 180 /* Convert pa to l1 entry: pa = (pa & L1_S_FRAME) | attr */ 181 and pa, pa, l1sfrm 182 orr pa, pa, attr 183 cmp n_sec, #0 184 bne 2bさて始めましょう。
175 3: ldmia itable!, {va,pa,n_sec,attr}ldmは複数アドレスからのロード、iaは転送ごとにitableをインクリメントするのでした。
itableにはmmu_init_tableのアドレスが入っており、
va | pa | nsec | attr |
---|---|---|---|
0x0 | 0x0 | 0x643033 | 0x412 |
0xC0000000 | 0x0 | 0x643033 | 0x041E |
0xF2000000 | 0x20000000 | 0x010FFFFF | 0x0412 |
va = 0x0 pa = 0x0; n_sec = 0x643033; attr = 0x0412;てな感じに、各レジスタに値が読み込まれます。
先に進みましょう。
176 mov n_sec, n_sec, lsr #L1_S_SHIFTmovでシフト演算ができるそうで、176行目もそのパターンですね。
lsrは(Logical Shift Right)で右シフト。L1_S_SHIFTが20だったので、
n_sec = n_sec >> 20;ということで0x643033 >> 20ですから0x6になります。n_sec = 0x6。
MMU_INITマクロの3つ目はn_secが0x010FFFFFだったので、0x010FFFFF >> 20はn_sec = 0x10ですね。
カーネルのサイズが4MBのところを0x6MBぶん、ペリフェラルの所は16MBなので0x10MBぶん。カーネルは1MB多めにするって書いてありましたけど2MB増えているような気がしますが、よくわかりません。
n_secはセクション数ということで、カーネルは4セクション、ペリフェラルは16セクションということになります。
次に進みます。
177 /* Convert va to l1 offset: va = 4 * (va >> L1_S_SHIFT) */ 178 mov va, va, LSR #L1_S_SHIFT 179 mov va, va, LSL #2これもmovを使ったシフト演算です。やっていることはコメントのとおりですね。vaをL1_S_SHIFT分だけ右シフトさせ、4倍(つまり左シフト2)します。
ではなぜ、4倍するんでしょうか?
コメントにva からL1オフセットに変換する、とありますね。32ビットのpa+attrを入れるとアドレスは4つ進むから、L1オフセットは4刻みで増える、だから4倍するのですね。
実際の数字がどうなるかというと、カーネルとペリフェラルのそれぞれで次のようになります。
va = 4 * (0xC0000000 >> 20) = 0x00002000 va = 4 * (0xF2000000 >> 20) = 0x00003C80L1オフセットが計算できました。
つぎは、PAをL1エントリに変換です。
180 /* Convert pa to l1 entry: pa = (pa & L1_S_FRAME) | attr */ 181 and pa, pa, l1sfrm 182 orr pa, pa, attrこれもコメントのとおりですね。pa & L1_S_FRAMEしてからattrでORをとると。
L1_S_FRAMEが0xFFF00000ですから、ページテーブルのフォーマットに合わせて、上位12ビットにセクションを入れて下位20ビットにアトリビュートを入れているわけですね。
第4回で触れたページテーブルのフォーマットを再掲しておきます。
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 |
183 cmp n_sec, #0 184 bne 2bここはループの条件ですね。n_secと0を比較して、番兵の0つまりMMU_INIT(0, 0, 0, 0)じゃなければラベル2、つまり169行目にジャンプせよ、ということになります。
ということで、今回ぶんをCっぽくしてみると次のような感じでしょうか。
va = *(itable++); pa = *(itable++); n_sec = *(itable++); attr = *(itable++); n_sec = n_sec >> L1_S_SHIFT; /* L1_S_SHIFT = 20 */ va = 4 * (va >> L1_S_SHIFT); pa = (pa & L1_S_FRAME) | attr; /* L1_S_FRAME = 0xFFF00000 */ if (n_sec != 0) goto 162行目
コメント(0件)
- TB-URL http://www.tokuda.net/diary/0809/tb/
rpi_start.Sを読む (第6回)
第3回で154行目まできていましたが、第4回と第5回のMMU_INITマクロでずいぶんと寄り道してしまいました。
今回は、第3回の154行目から空行をはさんで、156行目から始めましょう。
レジスタに名前を付ける、と思っていればよさそうです。r0はl1tableと呼びますよ、ということでしょうね。ここから先が読みやすくなっていいですねぇ。
それでは次です。
先に進みます。
Ll1_s_frameは後ろのほうに定義されています。
ということで、l1sfrm = 0xFFF00000;になります。
ということで、今回は実質3行だけでした。Cっぽく書いてみます。
今回は、第3回の154行目から空行をはさんで、156行目から始めましょう。
156 /* Now create our entries per the mmu_init_table. */ 157 l1table .req r0 158 va .req r1 159 pa .req r2 160 n_sec .req r3 161 attr .req r4 162 itable .req r5 163 l1sfrm .req r6 164 165 adr itable, mmu_init_table 166 ldr l1sfrm, Ll1_s_frame 167 b 3fまずは、mmu_init_tableのエントリーを作ります。というコメントからスタートです。
156 /* Now create our entries per the mmu_init_table. */ 157 l1table .req r0 158 va .req r1 159 pa .req r2 160 n_sec .req r3 161 attr .req r4 162 itable .req r5 163 l1sfrm .req r6157行目から163行目に.recというのが出てきています。これはGNU ASのディレクティブと呼ばれるものだそうです。
レジスタに名前を付ける、と思っていればよさそうです。r0はl1tableと呼びますよ、ということでしょうね。ここから先が読みやすくなっていいですねぇ。
それでは次です。
165 adr itable, mmu_init_tableadrは先ほども出てきた疑似命令です。mmu_init_tableのアドレスをitableにロードします。mmu_init_tableは、第3回と第4回でさんざんやったMMU_INITマクロですね。
先に進みます。
166 ldr l1sfrm, Ll1_s_frameldrはロード命令です。l1sfrmにラベルLl1_s_frameの値をロードします。
Ll1_s_frameは後ろのほうに定義されています。
255 Ll1_s_frame: 256 .word L1_S_FRAMEつまりL1_S_FRAMEの値をl1sfrmにロードするわけです。L1_S_FRAMEは先の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つまり、L1_S_FRAME = ~(0x00100000 - 1)=0xFFF00000です。
ということで、l1sfrm = 0xFFF00000;になります。
167 b 3fbだけなので無条件ジャンプ。ラベルは3fなので3、つまり175行目にジャンプします。3fのfはなんでしょうか... forwardのfかな?
ということで、今回は実質3行だけでした。Cっぽく書いてみます。
itable = &mmu_init_table; l1sfrm = 0xFFF00000; /* L1_S_FRAME (12bit) */ goto 175行目
- TB-URL http://www.tokuda.net/diary/0808/tb/
rpi_start.Sを読む (第5回)
MMU_INITマクロの続編です。前回だいぶ解明したおかげで残りは順調に進みそうです。
二つ目のMMU_INITマクロを読み解きましょう。
最後の引数も新しいのはL1_S_B, L1_S_Cの二つです。
おなじみ、pte.hで定義されています。先ほどのL1_S_IMPの上にいました。
ということで、
さて、最後のMMU_INITマクロです。
コメントによると16MBのペリフェラルのマップとのこと。メモリマップI/Oされた周辺機器に関するエントリのようですね。
まずは、RPI_KERNEL_IO_VBASE, RPI_KERNEL_IO_PBASEです。
これは、arch/evbarm/rpi/rpi.hにある
RPI_KERNEL_IO_VBASEはBCM2835_IOPHYSTOVIRTというマクロを経由しますが、計算するとRPI_KERNEL_IO_VBASE = 0xF2000000になります。
三つ目のMMU_INITマクロである
番兵を除く、MMU_INITマクロを表にしてみましょう。
二つ目のMMU_INITマクロを読み解きましょう。
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)三つめまでの引数は先ほどのほぼ同様ですね。KERNEL_BASEもわかっているので問題ありません。
最後の引数も新しいのはL1_S_B, L1_S_Cの二つです。
おなじみ、pte.hで定義されています。先ほどのL1_S_IMPの上にいました。
/* L1 Section Descriptor */ #define L1_S_B 0x00000004 /* bufferable Section */ #define L1_S_C 0x00000008 /* cacheable Section */ #define L1_S_IMP 0x00000010 /* implementation defined */ですから、L1_S_PROTO | L1_S_AP_KRW | L1_S_B | L1_S_C = 0x0012 | 0x0400 | 0x0004 | 0x0008 = 0x041Eです。
ということで、
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)というMMU_INITは
va = 0xC0000000; pa = 0x0; n_sec = 0x643033; attr = 0x041E;と表現できることがわかります。
さて、最後のMMU_INITマクロです。
コメントによると16MBのペリフェラルのマップとのこと。メモリマップI/Oされた周辺機器に関するエントリのようですね。
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)これは、四つ目の引数が一つ目のMMU_INITマクロと同じですから最初の三つの引数が読み解ければよいですね。
まずは、RPI_KERNEL_IO_VBASE, RPI_KERNEL_IO_PBASEです。
これは、arch/evbarm/rpi/rpi.hにある
#define RPI_KERNEL_IO_VBASE BCM2835_PERIPHERALS_VBASE #define RPI_KERNEL_IO_PBASE BCM2835_PERIPHERALS_BASE #define RPI_KERNEL_IO_VSIZE BCM2835_PERIPHERALS_SIZEからのarch/arm/broadcom/bcm2835reg.hにおいて定義されています。
#define BCM2835_PERIPHERALS_BASE 0x20000000 #define BCM2835_PERIPHERALS_SIZE 0x01000000 /* 16MBytes */ #define BCM2835_IOPHYSTOVIRT(a) \ ((0xf0000000 | (((a) & 0xf0000000) >> 4)) + ((a) & ~0xf0000000)) #define BCM2835_PERIPHERALS_VBASE \ BCM2835_IOPHYSTOVIRT(BCM2835_PERIPHERALS_BASE)RPI_KERNEL_IO_PBASEはそのままBCM2835_PERIPHERALS_BASEですから0x20000000になります。
RPI_KERNEL_IO_VBASEはBCM2835_IOPHYSTOVIRTというマクロを経由しますが、計算するとRPI_KERNEL_IO_VBASE = 0xF2000000になります。
三つ目のMMU_INITマクロである
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)については次のように表現できます。
va = 0xF2000000 pa = 0x20000000 n_sec = 0x01000000 + 0x00100000 - 1 = 0x010FFFFF attr = 0x0412最後のMMU_INITマクロは、
323 /* end of table */ 324 MMU_INIT(0, 0, 0, 0)ということで、テーブルの最後にすべてを0にしたものです。テーブルの最後を示す、番兵の役目ということでしょうか。
番兵を除く、MMU_INITマクロを表にしてみましょう。
va | pa | nsec | attr |
---|---|---|---|
0x0 | 0x0 | 0x643033 | 0x412 |
0xC0000000 | 0x0 | 0x643033 | 0x041E |
0xF2000000 | 0x20000000 | 0x010FFFFF | 0x0412 |
- TB-URL http://www.tokuda.net/diary/0807/tb/