ポインタ
ポインタと文字列
#include <stdio.h> int main(void){ char *p; char a[5]="Hello"; p = a; printf("%c ", a[0]); printf("%c\n", *p); printf("%c ", a[1]); printf("%c\n", *(p+1) ); return 0; }
上のファイルをコンパイルして,実行すると以下の結果が得られる
H H e e
メモリ上の様子を表してみる(アドレスはテキトー)
ポインタのポインタ
#include <stdio.h> int main(void){ char *q[3]; char **pp; pp = q; q[0]="Hello"; q[1]="World"; q[2]="!"; printf("%c ", *(q[0]) ); printf("%c\n", **pp); printf("%c ", *(q[1]) ); printf("%c\n", **(pp+1) ); printf("%c\n", *(q[0]+1) ); return 0; }
上のファイルをコンパイルして,実行すると以下の結果が得られる
H H W W e
メモリ上の様子を表してみる(アドレスはテキトー)
Makefileでのヘッダファイルの依存関係の追加
問題設定
まず以下の4つのファイルを作る
/* main.c */ #include "foo.h" int main(){ foo(10); return 0; } /* foo.h */ void foo(int a); /* foo.c */ #include "foo.h" #include <stdio.h> void foo(int a){ printf("%d\n", a); }
#Makefile PROG = myapp SRCS = main.c foo.c OBJS = $(SRCS:%.c=%.o) CC = gcc all: $(PROG) $(PROG): $(OBJS) $(CC) -o $@ $^ %.o: %.c $(CC) -c $<
これでmakeする
$ make gcc -c main.c gcc -c foo.c gcc -o myapp main.o foo.o $ ./myapp 10
このように問題なく実行できる.ここでfoo.hに変更を加えた場合を想定する.
$ touch foo.h
touchコマンドは空ファイルを作るだけでなく,ファイルの中身を変更の有無にかかわらずファイルの更新時間を現在時刻に変えられる.
この状態でもう一度makeすると,main.cとfoo.cはfoo.hをインクルードしているので,それらのファイルに対し,もう一度コンパイルをしてほしい.しかし,もうmakeし直すと,
$ make make: Nothing to be done for 'all'.
と出力され,何も起こらない.この原因はMakefile内にヘッダファイルの依存関係が書かれていないことにある.
解決案
この問題を解決ための,愚直な方法はMakefileの最終行にでも
main.o: main.c foo.h foo.o: foo.c foo.h
と書き込む方法だがファイル数が多くなると面倒.なので別の方法を考える.
MMDオプション
MMDオプションをつけると,各ファイルのコンパイル時に,#include文でダブルコーテーションで囲まれたヘッダファイルを.dファイルに出力してくれる.
(例)先ほどのMakefileを編集する.
#Makefile PROG = myapp SRCS = main.c foo.c OBJS = $(SRCS:%.c=%.o) CC = gcc all: $(PROG) $(PROG): $(OBJS) $(CC) -o $@ $^ %.o: %.c $(CC) -c -MMD $<
これでmakeしてみる.(その前に,先ほど作られたオブジェクトファイルなどを消去する.)
$ rm *.o myapp $ ls foo.c foo.h main.c Makefile $ make gcc -c -MMD main.c gcc -c -MMD foo.c gcc -o myapp main.o foo.o $ ls foo.c foo.d foo.h foo.o main.c main.d main.o Makefile myapp
.dのファイルが作られてるのが分かる.中身を見てみると
$ cat main.d main.o: main.c foo.h $ cat foo.d foo.o: foo.c foo.h
のように,依存ファイルが得られる.
includeオプション
つぎは,得られた依存ファイルの情報をMakefileに追加する.それにはincludeオプションを使う.
#Makefile PROG = myapp SRCS = main.c foo.c OBJS = $(SRCS:%.c=%.o) DEPS := $(SRCS:%.c=%.d) CC = gcc all: $(PROG) $(PROG): $(OBJS) $(CC) -o $@ $^ %.o: %.c $(CC) -c -MMD $< -include $(DEPS)
.dファイルがない場合(1回目でのmake)では,.dファイルがないというエラーが出てmakeが止まってしまうのでincludeの先頭にハイフンをつける(ハイフンをつけるとファイルがなくてもエラーが出なくなる)
補足1
includeオプションは,今のmakefileの処理を止めて,指定した別のmakefileの処理を行うというものらしい.上の例では,
-include $(DEPS)
の部分は
main.o: main.c foo.h foo.o: foo.c foo.h
と解釈される.なので,.dファイルもmakefileなのだろう(makefileの文法で書かれてる).また一番下にincludeを書いても真ん中の処理をする前にincludeの処理をするみたい.
補足2
includeオプションは,allよりも上に書いてはいけない.makeというコマンドは一番上のターゲットに対する処理を行うため,ターゲットが変ってしまう.今までの例で
-include $(DEPS) all: $(PROG)
としてしまうと,
main.o: main.c foo.h foo.o: foo.c foo.h all: $(PROG)
となり,main.oを作ることが目的となってしまう.
05
はじめに
Githubで公開されている,GitHub - bztsrc/raspi3-tutorial: Bare metal Raspberry Pi 3 tutorialsのコードを読んでいく.今回は05_uart0を理解することを目的とする.
周辺知識
uart0について
The SoCs used on the Raspberry Pis have two built-in UARTs, a PL011 and a mini UART. They are implemented using different hardware blocks, so they have slightly different characteristics.
上で書かれているように,ラズパイのSoCには2種類のUARTが内臓されている.それは,PL011とミニUARTという名前で,uart0とuart1に対応する.チュートリアル03ではミニUART(uart1)を実装したので,今回はPL011(uart1)を実装する.
UARTクロック数の設定について
uart0を使うためには,UARTに関するクロック数を知っておく必要がある.
All clocks are the base clocks for those peripherals, e.g. 3MHz for UART, 50/100MHz for EMMC, not the dividers applied using the peripheral.
Mailbox property interface · raspberrypi/firmware Wiki · GitHub
デフォルトでは3MHzのようだが,今回はメールボックスを使って4MHzに指定する.
ボーレートの設定
上でUARTに関するクロック数を知る必要があると述べたが,それはボーレートを設定するレジスタであるIBRDとFBRDに書き込む値を決定するためである.その式は以下の通り,(式の意味は省略する)
BAUDDIV = 4000000 / (16 * 115200) = 2.170138
IBRD(整数部) = 2
FBRD(少数部) = 0.1701 * 64 + 0.5 = 11.3864 = 11 = 0xb
BAUDDIV = 3000000 / (16 * 115200)= 1.62760
IBRD(整数部) = 1
FBRD(少数部) = 0.627 * 64 + 0.5 = 40.628 = 40 = 0x28
UARTの初期化手順
どのレジスタにどのような値を書き込むかは「BareMetalで遊ぶ RaspberryPi」に詳しく載っている.ラズパイ3でも同じ設定で大丈夫.ピンをプルダウンにする必要があるのかは,いまだよくわからない.
04
はじめに
Githubで公開されている,GitHub - bztsrc/raspi3-tutorial: Bare metal Raspberry Pi 3 tutorialsのコードを読んでいく.今回は04_mailboxesを理解することを目的とする.
Mailboxについて
Mailboxの役割はARMとVideoCore間の通信を補助すること.Mailboxは2つ用意されていて,以下のようにそれぞれ用途が違う.
MB 0: ARMから書き込むと,その情報がVCに伝わりVCが応答を返す
MB 1: VCから書き込むと,その情報がARMに伝わりARMが応答を返す
今回はARMから書き込むことを想定しているのでMB 0だけ扱う.
具体的な手順は以下の通り
ARMからVCに通信する方法(ARMからMB1に書き込み,応答を読む)
- リクエストとレスポンス用のバッファを用意する
- バッファに必要なパラメータを書き込む
- リクエスト可能になるのを待つ(メールボックスのstatusレジスタを見る)
- メールボックスにライトリクエストを出す(メールボックスのWriteレジスタにバッファのアドレスの上位28ビットとチャンネル4ビットを書き込む)
- VCが処理を終えてステータスが読み取り可能になるのを待つ(メールボックスのstatusレジスタを見る)
- メールボックスのリード情報を読む(メールボックスのReadレジスタの上位28ビットはバッファのアドレス,下位4ビットはチャンネルが書き込まれてるはずなのでそれを読む)
- リード情報に含まれるチャンネルが,ライトリクエストのチャンネルと同じであれば良し
- バッファの中に,VCによってデータが書き込まれているはず
対象ファイル:mbox.h, mbox.c, main.c
キーワード:mailbox interface
mbox.h
/* a properly aligned buffer */ extern volatile unsigned int mbox[36]; #define MBOX_REQUEST 0 /* channels */ #define MBOX_CH_POWER 0 #define MBOX_CH_FB 1 #define MBOX_CH_VUART 2 #define MBOX_CH_VCHIQ 3 #define MBOX_CH_LEDS 4 #define MBOX_CH_BTNS 5 #define MBOX_CH_TOUCH 6 #define MBOX_CH_COUNT 7 #define MBOX_CH_PROP 8 /* tags */ #define MBOX_TAG_GETSERIAL 0x10004 #define MBOX_TAG_LAST 0 int mbox_call(unsigned char ch);
mbox.c
#include "gpio.h" /* mailbox message buffer */ volatile unsigned int __attribute__((aligned(16))) mbox[36]; #define VIDEOCORE_MBOX (MMIO_BASE+0x0000B880) #define MBOX_READ ((volatile unsigned int*)(VIDEOCORE_MBOX+0x0)) #define MBOX_POLL ((volatile unsigned int*)(VIDEOCORE_MBOX+0x10)) #define MBOX_SENDER ((volatile unsigned int*)(VIDEOCORE_MBOX+0x14)) #define MBOX_STATUS ((volatile unsigned int*)(VIDEOCORE_MBOX+0x18)) #define MBOX_CONFIG ((volatile unsigned int*)(VIDEOCORE_MBOX+0x1C)) #define MBOX_WRITE ((volatile unsigned int*)(VIDEOCORE_MBOX+0x20)) #define MBOX_RESPONSE 0x80000000 #define MBOX_FULL 0x80000000 #define MBOX_EMPTY 0x40000000 /** * Make a mailbox call. Returns 0 on failure, non-zero on success */ int mbox_call(unsigned char ch) { unsigned int r = (((unsigned int)((unsigned long)&mbox)&~0xF) | (ch&0xF)); /* wait until we can write to the mailbox */ do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_FULL); /* write the address of our message to the mailbox with channel identifier */ *MBOX_WRITE = r; /* now wait for the response */ while(1) { /* is there a response? */ do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_EMPTY); /* is it a response to our message? */ if(r == *MBOX_READ) /* is it a valid successful response? */ return mbox[1]==MBOX_RESPONSE; } return 0; }
cソースファイル(main.c)
#include "uart.h" #include "mbox.h" void main() { // set up serial console uart_init(); // get the board's unique serial number with a mailbox call mbox[0] = 8*4; // length of the message mbox[1] = MBOX_REQUEST; // this is a request message mbox[2] = MBOX_TAG_GETSERIAL; // get serial number command mbox[3] = 8; // buffer size mbox[4] = 8; mbox[5] = 0; // clear output buffer mbox[6] = 0; mbox[7] = MBOX_TAG_LAST; // send the message to the GPU and receive answer if (mbox_call(MBOX_CH_PROP)) { uart_puts("My serial number is: "); uart_hex(mbox[6]); uart_hex(mbox[5]); uart_puts("\n"); } else { uart_puts("Unable to query serial!\n"); } // echo everything back while(1) { uart_send(uart_getc()); } }
03
はじめに
Githubで公開されている,GitHub - bztsrc/raspi3-tutorial: Bare metal Raspberry Pi 3 tutorialsのコードを読んでいく.今回は03_uart1を理解することを目的とする.
対象ファイル:uart.c, main.c
キーワード:uart, GPIO
uart.c
#include "gpio.h" /* Auxilary mini UART registers */ #define AUX_ENABLE ((volatile unsigned int*)(MMIO_BASE+0x00215004)) #define AUX_MU_IO ((volatile unsigned int*)(MMIO_BASE+0x00215040)) #define AUX_MU_IER ((volatile unsigned int*)(MMIO_BASE+0x00215044)) #define AUX_MU_IIR ((volatile unsigned int*)(MMIO_BASE+0x00215048)) #define AUX_MU_LCR ((volatile unsigned int*)(MMIO_BASE+0x0021504C)) #define AUX_MU_MCR ((volatile unsigned int*)(MMIO_BASE+0x00215050)) #define AUX_MU_LSR ((volatile unsigned int*)(MMIO_BASE+0x00215054)) #define AUX_MU_MSR ((volatile unsigned int*)(MMIO_BASE+0x00215058)) #define AUX_MU_SCRATCH ((volatile unsigned int*)(MMIO_BASE+0x0021505C)) #define AUX_MU_CNTL ((volatile unsigned int*)(MMIO_BASE+0x00215060)) #define AUX_MU_STAT ((volatile unsigned int*)(MMIO_BASE+0x00215064)) #define AUX_MU_BAUD ((volatile unsigned int*)(MMIO_BASE+0x00215068)) /** * Set baud rate and characteristics (115200 8N1) and map to GPIO */ void uart_init() { register unsigned int r; /* UARTの初期化 */ *AUX_ENABLE |=1; // enable UART1を有効化 *AUX_MU_IER = 0; // 割込みを無効化 *AUX_MU_CNTL = 0; // Tx, Rxを無効化 *AUX_MU_LCR = 3; // UART8-bitモードに *AUX_MU_MCR = 0; // uart lineをhighに *AUX_MU_IER = 0; // 割込みを無効化 *AUX_MU_IIR = 0xc6; // 受信FIFOと送信FIFOをクリア *AUX_MU_BAUD = 270; // ボードレートを設定 /* GPIO14,15をalt5モードに */ r=*GPFSEL1; r&=~((7<<12)|(7<<15)); // gpio14, gpio15 r|=(2<<12)|(2<<15); // alt5に *GPFSEL1 = r; /* GPIO14,15のプルアップ・ダウンを無効に */ *GPPUD = 0; r=150; while(r--) { asm volatile("nop"); } //150クロック待つ *GPPUDCLK0 = (1<<14)|(1<<15); r=150; while(r--) { asm volatile("nop"); } *GPPUDCLK0 = 0; // flush GPIO setup /* Tx, Rxを有効化 */ *AUX_MU_CNTL = 3; // Tx, Rxを有効化 } /** * Send a character */ void uart_send(unsigned int c) { /* wait until we can send */ do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x20)); /* write the character to the buffer */ *AUX_MU_IO=c; } /** * Receive a character */ char uart_getc() { char r; /* wait until something is in the buffer */ do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x01)); /* read it and return */ r=(char)(*AUX_MU_IO); /* convert carrige return to newline */ return r=='\r'?'\n':r; } /** * Display a string */ void uart_puts(char *s) { while(*s) { /* convert newline to carrige return + newline */ if(*s=='\n') uart_send('\r'); uart_send(*s++); } }
- わからないこと一覧
- なぜ割込み無効化を2回もするのか?
- MCRレジスタに関して何をしているのか?
uart lineをlowにするってどゆこと?信号がないときはHighになるように設定しているっぽい - ボードレート設定の270とい数はどこから来たのか
- UARTで使うGPIOをプルアップ・ダウン無効にする理由は?
マニュアルには書いていないが,他の人たちもみなこのようにしているので当たり前なのか
cソースファイル(main.c)
#include "uart.h" void main() { // set up serial console uart_init(); // say hello uart_puts("Hello World!\n"); // echo everything back while(1) { uart_send(uart_getc()); } }
無限ループし,アセンブリ言語のコードには戻らない
02
はじめに
Githubで公開されている,GitHub - bztsrc/raspi3-tutorial: Bare metal Raspberry Pi 3 tutorialsのコードを読んでいく.今回は02_multicorecを理解することを目的とする.
登場ファイル:start.S, main.c, link.ld, Makefile
キーワード:アセンブリ言語,c言語,無限ループ,マルチコア
アセンブリ言語ソースファイル(start.S)
.section ".text.boot" .global _start _start: // CPUの番号を取得 mrs x1, mpidr_el1 //x1レジスタにmpidr_el1レジスタの値を格納 and x1, x1, #3 //x1=x1 & 3 cbz x1, 2f //x1==0なら,直後のラベル2へジャンプ // CPU番号0番以外は, スリープ 1: wfe //スリープ b 1b //直前のラベル1へジャンプ 2: // cpu id == 0 // スタックポインタの設定 ldr x1, =_start //ラベル_startのアドレスをx1レジスタに格納 mov sp, x1 //spレジスタにx1レジスタの値を格納 // BSS領域のゼロクリア ldr x1, =__bss_start //__bss_start(アドレス)をx1レジスタに格納 ldr w2, =__bss_size //__bss_size(アドレス)をw2レジスタに格納 3: cbz w2, 4f //w2==0なら,直後のラベル4へジャンプ str xzr, [x1], #8 //x1番地に0をストア,その後x1=x1+8 sub w2, w2, #1 //w2=w2-1 cbnz w2, 3b //w2!=0なら,直前の3にジャンプ // C言語のコードに処理を移す.ここには戻ってこない 4: bl main //main関数へジャンプ // 戻ってきてしまった場合に備えて無限ループ b 1b //直前のラベル1へジャンプ
- 登場するレジスタ
・x1レジスタ ... 64 ビット汎用レジスタの一つ
・mpidr_el1レジスタ ... レジスタの全体的な機能はよくわからない.しかし,Stanford CS140e - Operating Systemsによると,下位2ビットにはコードを実行しているコアの番号が入るらしい.
・spレジスタ ... スタックポインタ
・w2レジスタ ... x2レジスタの下半分の32ビット部分
・xzrレジスタ ... 64ビットのゼロレジスタ
リンカスクリプト(link.ld)
SECTIONS { . = 0x80000; .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } PROVIDE(_data = .); .data : { *(.data .data.* .gnu.linkonce.d*) } .bss (NOLOAD) : { . = ALIGN(16); __bss_start = .; *(.bss .bss.*) *(COMMON) __bss_end = .; } _end = .; /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } } __bss_size = (__bss_end - __bss_start)>>3;
Makefile
SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) CFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles all: clean kernel8.img start.o: start.S aarch64-elf-gcc $(CFLAGS) -c start.S -o start.o %.o: %.c aarch64-elf-gcc $(CFLAGS) -c $< -o $@ kernel8.img: start.o $(OBJS) aarch64-elf-ld -nostdlib -nostartfiles start.o $(OBJS) -T link.ld -o kernel8.elf aarch64-elf-objcopy -O binary kernel8.elf kernel8.img clean: rm kernel8.elf *.o >/dev/null 2>/dev/null || true run: qemu-system-aarch64 -M raspi3 -kernel kernel8.img -d in_asm
01
はじめに
Githubで公開されている,GitHub - bztsrc/raspi3-tutorial: Bare metal Raspberry Pi 3 tutorialsのコードを読んでいく.今回は01_bareminimumの無限ループを理解することを目的とする.
アセンブリ言語ソースファイル(start.S)
.section ".text.boot" .global _start _start: 1: wfe b 1b
- 拡張子.sと拡張子.Sの違い?
*.sの場合は,アセンブル時にプリプロセスが行われない.
*.Sの場合は,アセンブル時にプリプロセスが行われる.
(今回はインクルードやマクロを使用しないためstart.sで良い気がする)
- .section命令?
次の.section命令までを".text.boot"セクションと指定
今回は次の.section命令が出てこないので,すべて.text.bootセクションに指定
- .global命令?
指定したラベルを外部から参照可能にする
- wfe命令?
割込みなどのイベントが発生するまで実行を保留?
(今回はイベントが起こらないから,実行を保留していたらループしないのでは?)
詳細は割込みなどを扱う回でもう一度調べよう
- b命令?
無条件分岐命令.
1bは1beforeの意味で直前の1ラベルを示している.
リンカスクリプト(link.ld)
SECTIONS { . = 0x80000; .text : { *(.text.boot) } /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } }
- . = 0x*?
「. 」はロケーションカウンタと呼ばれ,カレントアドレスを指定する
この指定により.textセクションが0x8000直後に配置される
- .text : { *(.text.boot) }?
リンク前のオブジェクトファイルの.text.bootセクションを,リンク後のオブジェクトファイルの.textセクションに再配置する
- /DISCARD/?
指定したセクションは,リンクによりに再配置しない,つまり捨ててしまう
(後の回で,.commentセクションや.gnu*セクションが出てくるのだろうか?今回はこれがなくても結果は変わらない)
Makefile
CFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles all: clean kernel8.img start.o: start.S aarch64-elf-gcc $(CFLAGS) -c start.S -o start.o kernel8.img: start.o aarch64-elf-ld -nostdlib -nostartfiles start.o -T link.ld -o kernel8.elf aarch64-elf-objcopy -O binary kernel8.elf kernel8.img clean: rm kernel8.elf *.o >/dev/null 2>/dev/null || true run: qemu-system-aarch64 -M raspi3 -kernel kernel8.img -d in_asm
- コンパイルオプション?
・-ffreestanding ... フリースタンディング実行環境用に コンパイルを行う.つまり-fno-builtinオプションが有効となり,mainがなくてもよくなる
・-nostdinc ... ヘッダファイルのための,標準システムディレクトリ検索をしない
・-nostdlib ... リンク時に,標準のシステムライブラリとスタートアップファイルを使用しない
・-nostartfiles ... リンク時に,標準ライブラリは使用するが,標準のスタートアップファイルは使用しない
(今回は-nostdlibオプションを使用しているので-nostartfilesは不要な良い気がする)
- objcopy -O binary?
ELF のヘッダ等を取り払い、Raw バイナリに変換