ポインタ

ポインタと文字列

#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

メモリ上の様子を表してみる(アドレスはテキトー)
f:id:bunkyu3:20181217200521p:plain:w450

ポインタのポインタ

#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

メモリ上の様子を表してみる(アドレスはテキトー)
f:id:bunkyu3:20181217201519p:plain

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.

The Raspberry Pi UARTs - Raspberry Pi Documentation

上で書かれているように,ラズパイの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に書き込む値を決定するためである.その式は以下の通り,(式の意味は省略する)

クロック数4MHz, ボーレート115200のとき
BAUDDIV = 4000000 / (16 * 115200) = 2.170138
IBRD(整数部) = 2
FBRD(少数部) = 0.1701 * 64 + 0.5 = 11.3864 = 11 = 0xb

クロック数3MHz, ボーレート115200のとき
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に書き込み,応答を読む)

  1. リクエストとレスポンス用のバッファを用意する
  2. バッファに必要なパラメータを書き込む
  3. リクエスト可能になるのを待つ(メールボックスのstatusレジスタを見る)
  4. メールボックスにライトリクエストを出す(メールボックスのWriteレジスタにバッファのアドレスの上位28ビットとチャンネル4ビットを書き込む)
  5. VCが処理を終えてステータスが読み取り可能になるのを待つ(メールボックスのstatusレジスタを見る)
  6. メールボックスのリード情報を読む(メールボックスのReadレジスタの上位28ビットはバッファのアドレス,下位4ビットはチャンネルが書き込まれてるはずなのでそれを読む)
  7. リード情報に含まれるチャンネルが,ライトリクエストのチャンネルと同じであれば良し
  8. バッファの中に,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ビットのゼロレジスタ

cソースファイル(main.c)

void main()
{
    while(1);
}

無限ループし,アセンブリ言語のコードには戻らない

リンカスクリプト(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, link.ld, Makefile
キーワード:アセンブリ言語,無限ループ

アセンブリ言語ソースファイル(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 バイナリに変換