/home/tnishinaga/TechMEMO

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

2017年版おすすめ本のご紹介

蔵書の写真をTwitterにあげたところ思いの他反響があったので,おすすめの書籍を紹介したいと思います.

これから紹介する本は,私が今まで一部でも良いので読んだ本の内,所有しているもの,または記憶に残っているものになります.

UNIX, Linux系学習用

はじめてUNIXで仕事する人が読む本

ログイン方法からsed, grepなどの使い方,ネットワークやセキュリティの説明など,UNIXLinux環境での基本的な作業のやり方や,そのために必要な基本的な知識が網羅された本です.

この本に書かれている内容を覚えれば,基本的なLinuxのサーバー管理等で困ることは無いと思います.

コンピュータ・アーキテクチャ(低レイヤー)

パターソン&ヘネシー コンピュータの構成と設計(上・下)

コンピュータを理解する上で必要な2進数の話から,コンピュータを構成する要素や高速化の手法など,コンピュータ・アーキテクチャに関する知識が網羅された本です.

ただし,この本は全てが少し深めに書かれている上に1ページの文字数も多いので,読んでいて結構疲れます.

読んでいて難しいな,読むのが辛いなと思ったらもっと簡単なコンピュータアーキテクチャについて書かれた本を読んだり,マイコン開発をするなどして知識をつけてから読んだほうが良いかもしれません.

リンカ・ローダ実践開発テクニック

オブジェクト同士を結合し,実行可能なプログラムを作るリンカについて書かれた本です.

OS上の実行バイナリ(ELF)の構造やリンカスクリプトについて日本語で知りたい場合はまずこの本を読むと良いと思います.

著者の坂井さんの書かれた本では,対象の仕組みを説明するだけでなく,実際にそれを作ってみて理解を深めていくというということをよくされており,この本も途中から実際にリンカやローダーを自作することを行っていきます.

なので,仕組みを知りたいだけの方は前半のみ,手を動かして深く知りたい方は後半も含め読むと良い本だと思います.

ARMで学ぶアセンブリ言語入門

ARM(AARCH32)アセンブリコードの書き方,QEMU上での実行方法,基本的なARMアセンブリ命令の解説がわかりやすく書かれている本です.

ARMアセンブリコードを書く際の辞書として手元に1冊用意しておきたいです.

Raspberry Piで学ぶ ARM デバイスドライバープログラミング

Raspberry Pi上でLinuxデバイスドライバを書き,自作ハードウエアを制御する本です.

実はほとんど読めていないので詳細は書けませんが,Linuxデバイスドライバ作成の参考になる本だと思います.

Hacker’s Delight

ビットカウント演算やa<x<bの比較を1つの条件式で行う方法など,様々な黒魔術の書かれた本です.

黒魔術を使ってでもコードを高速化したい場合,例えばマイコン向けの高速なアセンブリコードを書く場合などを書く際に,辞書として利用するのが良いと思います.

目次は以下で公開されています.

http://www.hackersdelight.org/hacker2TOC.pdf

30日でできる!OS自作入門

x86(BIOS)環境上で動作するOSを作っていく本です.

比較的早いうちから画面描画を行うので作っていて楽しいですし,C言語のプログラムの書き方など必要な知識も一緒に教えてくれるので大変親切で読みやすいです.

スーパコンピュータ関係

絵でわかるスーパーコンピュータ

そもそもスパコンってなに? パソコンと何が違うの? どういうことができる? あると私たちにとってどのような良いことがあるの? といったことが絵を交えてわかりやすく書かれた本です.

大まかにスパコンについて知りたい方におすすめです.

スパコンプログラミング入門

スパコンプログラミングとありますが,正確には大規模並列計算を行うプログラムの書き方やアルゴリズムなどが書かれた本です.

MPIを使って通信を行う並列計算プログラミング方法も学ぶことができます.

ネットワーク関連

ルーター自作で学ぶ パケットの仕組み

その名の通り,Linux上でRawソケットを用いてイーサネットのパケットを受け取り,ルーティング等を行うルーターC言語で自作していく本です.

ルーターがどのようなことを行っているか,パケットの構造はどのようになっているのかなどを手を動かしながら学ぶことができます.

マスタリングTCP/IPシリーズ

ネットワークに関係する規格などが網羅的に書かれた本です.

よく注目を浴びる部分だけでなく関係する多くのことが細かく解説されて居る上,対応するRFCの番号なども示されているため深く知りたい場合はこの番号を頼りに調べられるようになっています.

一方,パターソン&ヘネシーと同様に内容が濃いので読んでいて疲れます.別の書籍を読んで全体を俯瞰して見た後,詳しく知りたい部分についてこの本を使って調べるのも良いかもしれません.

プログラミング関係

Beautiful code

プログラムをきれいに書いていくためのテクニックが書かれた本です.

自他共に読みやすいコードを書くため,一度は読むべき本と思います.

作文技術関係

数学文章作法(基本編・推敲編)

どのようにすれば自分の書きたいことを理論的に筋道立てて書くことができるかについて書かれた本です.

人に伝える文章を書く必要のある方におすすめの本です.

おわり

以上です. 学習の手助けになれば幸いです.

QEMUでCortex-M3/M4マイコンボードをエミュレーションしてLチカする話

Cortex-Mマイコン上で計算を行うアセンブリコードのデバッグを、エミュレータを使ってパソコンの上で行いたいです。 大学までのバスの中でパソコンからマイコン垂らしながらデバッグするのは不便で少し恥ずかしいので。

Cortex-M3プロセッサに対応しているエミュレータとしてQEMUがありますが、用意されているマイコンボードが LM3S811EVB / LM3S6965EVB の2つしかありません。

wiki.qemu.org

私は普段シリアルの設定などちょっと大変なところはすべてmbedライブラリにおまかせしているので、できればmbedの対応しているボードを使いたいです。 mbedに対応したマイコンボードの使えるエミュレータはないのでしょうか? ということで調べてみたのが今回の内容となります。

まとめ

  • GNU ARM Eclipse QEMUを使えばmbed対応ボードのNUCLEO-F103RBのエミュレーションができる
  • mbedライブラリに一部修正を加えることでLチカは動作した
  • TimerやUARTは今の所利用方法不明。要調査。

GNU ARM Eclipse

mbed対応ボードのエミュレーションができるツールを探していたところ、GNU ARM Eclipseというプロジェクトを見つけました。 こちらはARM開発のためのツールやEclipseプラグインなどを開発するプロジェクトのようです。

この中の一つにCortex-Mマイコン対応を増やしたQEMU forkであるGNU ARM Eclipse QEMUがあり、mbed対応ボードも2つほど サポートボードリストに含まれています。 これを使えばmbedライブラリを使いつつ、Cortex-Mマイコンのプログラムをエミュレータで実行できるかもしれません。 早速試してみましょう。

実行環境

以降の作業はThinkPad x201(x86_64、RAM 8GB)のArch Linuxで行いました。

対象ボードはNUCLEO-F103RBを選択します。

GNU ARM Eclipse のインストール

基本は以下を参照です。

gnuarmeclipse.github.io

Windowsの方はEXEファイルをダウンロードしてインストールするだけ。 Macの方はpkgファイルをダウンロードしてインストールするだけ。 Linuxの方はtgzファイルをダウンロードして適当なディレクトリで解凍するだけです。

私の使っているArch LinuxではAURにパッケージがあったので、yaourt を使ってgnuarmeclipse-qemu-binをインストールするだけでインストール完了しました。

$ yaourt -S gnuarmeclipse-qemu-bin

インストールしたファイルは /opt/gnuarmeclipse/qemu/ 以下に置かれます。

Lチカプログラムの作成

Lチカのプログラムはmbedのサンプルを作成してGCC用にエクスポートしてビルドするだけ……だと良かったのですが、 うまくいかないのでビルドを通すための修正を加えた後、QEMUで動くように修正を加える必要がありました。

※このときのmbed-srcのリビジョンはこちらの表記で635:a11c0372f0baでした

mbed-srcライブラリのインポート

まずはじめにmbedコンパイラでmbed_blinkyサンプルを作り、mbedライブラリをmbed-srcライブラリに置き換え、GCC用にエクスポートを行ってください。 mbed-srcライブラリのインポート方法については、以下のページにわかりやすく書かれています。

nora66.com

Makefileの修正

エクスポートしたソースを解凍した後、ビルドが通るようにするためMakefileに以下の修正を加えます。

  1. CC_FLAGSとCPPC_FLAGSとCC_SYMBOLSに書かれた -DDEVICE_SERIAL_ASYNCH=1 を -DDEVICE_SERIAL_ASYNCH=0 に変更する
  2. CC_FLAGSとCPPC_FLAGSとCC_SYMBOLSに書かれた -DDEVICE_CAN=1 を -DDEVICE_CAN=0 に変更する
  3. LD_FLAGSから-Wl,--wrap,main -Wl,--wrap,malloc_r -Wl,--wrap,free_r -Wl,--wrap,realloc_r -Wl,--wrap,calloc_rを削除する

1.はビルド時に出る以下のエラーを回避します。

../mbed-src/targets/hal/TARGET_STM/TARGET_STM32F1/serial_api.c: In function 'serial_break_set':
../mbed-src/targets/hal/TARGET_STM/TARGET_STM32F1/serial_api.c:337:48: error: 'serial_t {aka struct <anonymous>}' has no member named 'uart'
     UartHandle.Instance = (USART_TypeDef *)(obj->uart);

DEVICE_SERIAL_ASYNCHが有効になっている場合、serial_api.hで定義されているserial_t型の中身がuartメンバを持つserial_s型でなくなるために起こるエラーです。 根本的な解決は大変そうなので、今回はDEVICE_SERIAL_ASYNCHを0にして対応します。

2.は以下のエラーを回避します。

In file included from ../mbed-src/api/mbed.h:50:0,
                 from ../main.cpp:1:
../mbed-src/api/CAN.h:235:21: error: field '_can' has incomplete type 'can_t {aka can_s}'
     can_t           _can;
                     ^~~~
In file included from ../mbed-src/api/CAN.h:23:0,
                 from ../mbed-src/api/mbed.h:50,
                 from ../main.cpp:1:
../mbed-src/hal/can_api.h:55:16: note: forward declaration of 'can_t {aka struct can_s}'
 typedef struct can_s can_t;

C++詳しくなくてすぐに理由がわからないので、DEVICE_CANを0にして対応します。

3.を行わない場合、main等のラッパ関数を用意してないので以下のエラーが出ます。 ラッパ関数は特に必要ない(と思う)ので、オプションを削除して対応します。

/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-fclose.o): In function `_fclose_r':
fclose.c:(.text._fclose_r+0x4a): undefined reference to `__wrap__free_r'
fclose.c:(.text._fclose_r+0x58): undefined reference to `__wrap__free_r'
fclose.c:(.text._fclose_r+0x7a): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-fflush.o): In function `__sflush_r':
fflush.c:(.text.__sflush_r+0x7c): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-fvwrite.o): In function `__sfvwrite_r':
fvwrite.c:(.text.__sfvwrite_r+0x22c): undefined reference to `__wrap__malloc_r'
fvwrite.c:(.text.__sfvwrite_r+0x2b4): undefined reference to `__wrap__realloc_r'
fvwrite.c:(.text.__sfvwrite_r+0x2c4): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-makebuf.o): In function `__smakebuf_r':
makebuf.c:(.text.__smakebuf_r+0x2c): undefined reference to `__wrap__malloc_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-malloc.o): In function `malloc':
malloc.c:(.text.malloc+0x6): undefined reference to `__wrap__malloc_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-malloc.o): In function `free':
malloc.c:(.text.free+0x6): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-wsetup.o): In function `__swsetup_r':
wsetup.c:(.text.__swsetup_r+0x98): undefined reference to `__wrap__free_r'
/usr/lib/gcc/arm-none-eabi/6.3.0/../../../../arm-none-eabi/lib/armv7-m/libc.a(lib_a-mprec.o): In function `_Balloc':
mprec.c:(.text._Balloc+0x22): undefined reference to `__wrap__calloc_r'
mprec.c:(.text._Balloc+0x3e): undefined reference to `__wrap__calloc_r'
collect2: error: ld returned 1 exit status

クロックソースの変更

クロックソースは内蔵RCオシレータ(HSI)にしないと動かなかったので、system_stm32f1xx.cのUSE_PLL_HSE_EXTCとUSE_PLL_HSE_XTALを0にセットします。

#define USE_PLL_HSE_EXTC (0) /* Use external clock */
#define USE_PLL_HSE_XTAL (0) /* Use external xtal */

C言語ソースの修正

wait関数(というよりTimer関係)がうまく動いてくれないので、Lチカプログラムのwait関数をfor文の空回しに変更します。

#include "mbed.h"

DigitalOut myled(LED1);

int main() {
    
    while(1) {
        myled = 1;
        for (int i = 0; i < 100000000; i++);
        myled = 0;
        for (int i = 0; i < 100000000; i++);
    }
}

修正に関するdiff

上記修正前と修正後のdiffを以下に示します。

diff --git a/Makefile b/Makefile
index d4de525..567fecf 100644
--- a/Makefile
+++ b/Makefile
@@ -50,19 +50,20 @@ SIZE    = $(GCC_BIN)arm-none-eabi-size
 
 
 CPU = -mcpu=cortex-m3 -mthumb 
-CC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu99 -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=1 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=1 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
-CPPC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu++98 -fno-rtti -Wvla -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=1 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=1 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
+CC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu99 -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=0 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=0 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
+CPPC_FLAGS = -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os -std=gnu++98 -fno-rtti -Wvla -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=0 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=0 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB -include mbed_config.h -MMD -MP
 ASM_FLAGS = -x assembler-with-cpp -D__CMSIS_RTOS -D__MBED_CMSIS_RTOS_CM -D__CORTEX_M3 -DARM_MATH_CM3 -c -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -fmessage-length=0 -fno-exceptions -fno-builtin -ffunction-sections -fdata-sections -funsigned-char -MMD -fno-delete-null-pointer-checks -fomit-frame-pointer -mcpu=cortex-m3 -mthumb -Os
-CC_SYMBOLS = -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=1 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=1 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB 
+CC_SYMBOLS = -D__MBED__=1 -DDEVICE_I2CSLAVE=1 -DTARGET_LIKE_MBED -DDEVICE_PORTINOUT=1 -D__MBED_CMSIS_RTOS_CM -DTARGET_STM32F1 -DDEVICE_RTC=1 -DTOOLCHAIN_object -DDEVICE_SERIAL_ASYNCH=0 -D__CMSIS_RTOS -DMBED_BUILD_TIMESTAMP=1483112652.49 -DTOOLCHAIN_GCC -DDEVICE_CAN=0 -DTARGET_LIKE_CORTEX_M3 -DTARGET_CORTEX_M -DARM_MATH_CM3 -DTARGET_UVISOR_UNSUPPORTED -DTARGET_M3 -DDEVICE_SERIAL=1 -DDEVICE_INTERRUPTIN=1 -DDEVICE_I2C=1 -DDEVICE_PORTOUT=1 -D__CORTEX_M3 -DDEVICE_STDIO_MESSAGES=1 -DTARGET_FF_MORPHO -DTARGET_FF_ARDUINO -DTARGET_RELEASE -DTARGET_STM -DTOOLCHAIN_GCC_ARM -DDEVICE_PORTIN=1 -DDEVICE_SLEEP=1 -DTARGET_NUCLEO_F103RB -DDEVICE_SPI=1 -DDEVICE_SPISLAVE=1 -DDEVICE_ANALOGIN=1 -DDEVICE_PWMOUT=1 -DTARGET_STM32F103RB 
 
-LD_FLAGS =-Wl,--gc-sections -Wl,--wrap,main -Wl,--wrap,_malloc_r -Wl,--wrap,_free_r -Wl,--wrap,_realloc_r -Wl,--wrap,_calloc_r -mcpu=cortex-m3 -mthumb 
+LD_FLAGS =-Wl,--gc-sections -mcpu=cortex-m3 -mthumb 
 LD_SYS_LIBS = -lstdc++ -lsupc++ -lm -lc -lgcc -lnosys
 
+DEBUG=1
 
 ifeq ($(DEBUG), 1)
-  CC_FLAGS += -DDEBUG -O0
+  CC_FLAGS += -DDEBUG -O0 -g3
 else
-  CC_FLAGS += -DNDEBUG -Os
+  CC_FLAGS += -DNDEBUG -Os -g3
 endif
 
 
diff --git a/main.cpp b/main.cpp
index ccb0f20..2cea268 100644
--- a/main.cpp
+++ b/main.cpp
@@ -6,9 +6,8 @@ int main() {
     
     while(1) {
         myled = 1;
-        wait(0.5);
+        for (int i = 0; i < 100000000; i++);
         myled = 0;
-        wait(0.5);
+        for (int i = 0; i < 100000000; i++);
     }
 }

diff --git a/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c b/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c
index 196ddff..65ff176 100644
--- a/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c
+++ b/mbed-src/targets/cmsis/TARGET_STM/TARGET_STM32F1/TARGET_NUCLEO_F103RB/system_stm32f1xx.c
@@ -141,8 +141,8 @@
   */
 
 /* Select the clock sources (other than HSI) to start with (0=OFF, 1=ON) */
-#define USE_PLL_HSE_EXTC (1) /* Use external clock */
-#define USE_PLL_HSE_XTAL (1) /* Use external xtal */
+#define USE_PLL_HSE_EXTC (0) /* Use external clock */
+#define USE_PLL_HSE_XTAL (0) /* Use external xtal */
 
 /**
   * @}

動かしてみる

インストールしたQEMUを用いて、Lチカのプログラムを動かしてみたいと思います。

コマンドラインからQEMUを動かす方法は以下に書かれています。

gnuarmeclipse.github.io

QEMU実行コマンドのテンプレートは以下になります。

qemu-system-gnuarmeclipse --verbose --verbose --board ボード名 \
--mcu マイコン名 -d unimp,guest_errors --gdb tcp::1234 \
--nographic --image プログラム名.elf \
--semihosting-config enable=on,target=native \
--semihosting-cmdline プログラム名

今回の対象ボードはNUCLEO-F103RBなので、ボード名にはNUCLEO-F103RB、マイコン名にはSTM32F103RBを設定します。 プログラム名はmbed_blinkyなので、実行するコマンドは以下になります。

$ /opt/gnuarmeclipse/qemu/bin/qemu-system-gnuarmeclipse \
--verbose --verbose --board NUCLEO-F103RB --gdb tcp::1234 \
--mcu STM32F103RB -d unimp,guest_errors  \
--image mbed_blinky.elf \
--semihosting-config enable=on,target=native \
--semihosting-cmdline mbed_blinky

このコマンドはmbed_blinky.elfのあるディレクトリで行う必要があるため、 mbedからエクスポートしてきたプログラムをmakeした後、 mbed_blinky.elfの生成されている.buildディレクトリに移動してQEMUを起動します。

$ cd .build
$ /opt/gnuarmeclipse/qemu/bin/qemu-system-gnuarmeclipse \
--verbose --verbose --board NUCLEO-F103RB --gdb tcp::1234 \
--mcu STM32F103RB -d unimp,guest_errors  \
--image mbed_blinky.elf \
--semihosting-config enable=on,target=native \
--semihosting-cmdline mbed_blinky

これで正しく動いていればNUCLEOの画像が表示されLEDの部分が点滅するはずです。

とりあえず今日はここまで。

おまけ: デバッガを繋いでみる

QEMU起動時に--gdb tcp::1234オプションをつけているので、TCP1234ポートにgdbでアタッチしてデバッグができます。

まず以下のコマンドでgdbを起動します。

$ arm-none-eabi-gdb mbed_blinky.elf

gdbが起動したら、QEMUgdbサーバーに接続します。

(gdb) tar remote :1234

後は普通にデバッグするだけです。

おまけ: その他情報

LPC1768サポートを追加した方がいらっしゃるようです(当方未調査)。

myokota.hatenablog.jp

QEMUのFeaturesリストを見るとCortex-M0サポートなども予定してるみたいです。

Features/QOM/CPU - QEMU

proc-v7m.Sに定義されているnop_cache_fnsはどこにあるのか

ARM Cortex-M7マイコンでキャッシュを使えるようにするためにいろいろ頑張っています。

現在のLinux Kernelでは、アーキテクチャとしてARMv7Mが選択されると同時にCPU_CACHE_NOPが選択*1され、キャッシュを制御するコードとしてarch/arm/mm/cache-nop.S内のコードが利用されるようになっています*2。 このcache-nop.Sにはキャッシュを持たないマイコンのため、キャッシュを操作するための処理として「何もしない(NOP)」というコードが書かれています。 一方、今回対象としているARM Cortex-M7はキャッシュを持つため、このキャッシュを利用するためにはきちんとキャッシュの処理をするコードを書いてあげなければいけません。

さて、このcache-nop.S内の関数はどこでキャッシュを操作する関数として登録されているか調べてみると、proc-v7m.Sにproc_info_list構造体というものがあり、ここのcacheというメンバにセットするとキャッシュを操作する関数として登録されるようです。 以下、proc-v7m.Sの該当部分を引用します。

/*
 * Match any ARMv7-M processor core.
 */
.type   __v7m_proc_info, #object
__v7m_proc_info:
.long   0x000f0000      @ Required ID value
.long   0x000f0000      @ Mask for ID
.long   0           @ proc_info_list.__cpu_mm_mmu_flags
.long   0           @ proc_info_list.__cpu_io_mmu_flags
initfn  __v7m_setup, __v7m_proc_info    @ proc_info_list.__cpu_flush
.long   cpu_arch_name
.long   cpu_elf_name
.long   HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT
.long   cpu_v7m_name
.long   v7m_processor_functions @ proc_info_list.proc
.long   0           @ proc_info_list.tlb
.long   0           @ proc_info_list.user
.long   nop_cache_fns       @ proc_info_list.cache
.size   __v7m_proc_info, . - __v7m_proc_info

ここでproc_info_list.cacheに定義されているnop_cache_fnsシンボルの中身はどこにあるか調べるためgrepをかけてみると、以下のようにproc-v7m.SとSystem.mapにシンボル名は見つかりますが、中身は見つかりません。

$ grep -R "nop_cache_fns" ./
./arch/arm/mm/proc-v7m.S:       .long   nop_cache_fns           @ proc_info_list.cache
Binary file ./arch/arm/mm/cache-nop.o matches
Binary file ./arch/arm/mm/proc-v7m.o matches
Binary file ./arch/arm/mm/built-in.o matches
Binary file ./vmlinux.o matches
Binary file ./.tmp_vmlinux1 matches
Binary file ./.tmp_vmlinux2 matches
Binary file ./vmlinux matches
./System.map:c0128ea0 T nop_cache_fns
./.tmp_System.map:c0128ea0 T nop_cache_f

さっぱりわからないのでcache-nop.Sをじっくり眺めていたところ、以下のような記述を発見しました。

 @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S)
    define_cache_functions nop

define_cache_functionsはマクロっぽいので、コメントに従ってarch/arm/mm/proc-macros.Sを開いてみると、ビンゴでした。 マクロの定義は以下のようになっており、name部分に設定した名前と_cache_fnsを連結した名前の構造体を作るようになっているようです。 上記の例ではnopがセットされているので、nop_cache_fnsという名前の構造体がこのマクロによって作られることなります。

.macro define_cache_functions name:req
    .align 2
    .type   \name\()_cache_fns, #object
ENTRY(\name\()_cache_fns)
    .long   \name\()_flush_icache_all
    .long   \name\()_flush_kern_cache_all
    .long   \name\()_flush_kern_cache_louis
    .long   \name\()_flush_user_cache_all
    .long   \name\()_flush_user_cache_range
    .long   \name\()_coherent_kern_range
    .long   \name\()_coherent_user_range
    .long   \name\()_flush_kern_dcache_area
    .long   \name\()_dma_map_area
    .long   \name\()_dma_unmap_area
    .long   \name\()_dma_flush_range
    .size   \name\()_cache_fns, . - \name\()_cache_fns
.endm

ちなみにnop_cache_fnsの型はcpu_cache_fnsであり、arch/arm/include/asm/cacheflush.hに定義されています。

とりあえず、以上です。

*1:arch/arm/Kconfig 及び arch/arm/mm/Kconfig を参照

*2:arch/arm/mm/Makefile参照

of_property_match_stringを使ってみたメモ

あらすじ

Google Summer of Code(GSoC)でやることの一つとして、STM32F7マイコンでUART(シリアル)を使うためのLinuxドライバを書いています。 STM32F7とSTM32F4のシリアルペリフェラルはほとんど同じ*1なので、STM32F4のものをベースにSTM32F7向けのコードを追記して、STM32F7で利用する際は追記したコードに処理を切り替えるということを行えば、既存のドライバのコードを再利用してSTM32F7でもシリアルが利用できるようになります。

この切り替えを行うためにDevice Tree Blobに書かれた情報からSTM32F4で動いているのかSTM32F7で動いているのかを判断するようなコードを書いていましたが、メンターの方と相談した結果、差分もほとんどない上、#define 部も切り替える必要がありコードがわかりづらくなるなどの問題もあり、#ifディレクティブを用いて書いてみることになりました。

以上より、Device Tree Blobに書かれた情報からSTM32F4で動いているのかSTM32F7で動いているのかを判断するようなコードはひとまず不要となったので、そのやり方についてをこちらで供養したいと思います。

コードを切り替えるには

ランタイム時、ドライバ内でコードの切り替えを行うためには、STM32F4で動いているのかSTM32F7で動いているのかを判断する必要があります。

この判断については、カーネルがDeviceTreeBlob(DTB)に従ってどういう名前のserialドライバを読み込もうとするかを見ることで行うことにしました。例えば、stm32-usartというドライバ名で読み込もうとする場合はSTM32F4として、stm32f7-usartというドライバ名で読み込もうとする場合はSTM32F7として判断するという感じです。

実際の arch/arm/boot/dts/stm32f429.dtsi のserialに関する記述は、以下のようになっています。

usart6: serial@40011400 {
 compatible = "st,stm32-usart", "st,stm32-uart";
 reg = <0x40011400 0x400>;
 interrupts = <71>;
 clocks = <&rcc 0 165>;
 status = "disabled";
};

このように書かれているとき、stm32f429.dtsiをincludeしているDTBを読み込んだカーネルは、stm32-usartをcompatibleとして設定しているドライバを読み込んで使用します。

ドライバ側のコード(drivers/tty/serial/stm32-usart.c)には次のような構造体が定義されており、ここに追記を行うとcompatibleとして扱う名前を増やすことができます。

static const struct of_device_id stm32_match[] = {
        { .compatible = "st,stm32-usart", },
        { .compatible = "st,stm32-uart", },
        {},
};

ここにcompatibleとしてstm32f7-usartとstm32f7-uartの記述を増やすと、次のような感じになります。

static const struct of_device_id stm32_match[] = {
        { .compatible = "st,stm32-usart", },
        { .compatible = "st,stm32-uart", },
        { .compatible = "st,stm32f7-usart", },
        { .compatible = "st,stm32f7-uart", },
        {},
};

ドライバ内でDTBのcompatibleに設定されている名前が「st,stm32f7-usart」であるかを確認するには、of_property_match_string関数が使えます*2。 この関数は第3引数に設定した文字列と一致する文字列がcompatibleに設定されている場合、compatibleの何番目に設定されているかのインデックスを返します。そうでない場合はマイナスの値が返ってくるようです。 よって、次に示すstm32_of_is_stm32f7関数のようなコードを作ってドライバ内のprobe関数で呼び出すと、そのドライバがSTM32F4向けとして呼び出されたのか、STM32F7向けとして呼び出されたのかを知ることができます。

static inline bool stm32_of_is_stm32f7(struct platform_device *pdev){
    struct device_node *np = pdev->dev.of_node;

    if (of_property_match_string(np, "compatible", "st,stm32f7-uart") >= 0
        || of_property_match_string(np, "compatible", "st,stm32f7-usart") >= 0)
        return true;
    else
        return false;
}

後は、この情報をどこかで覚えておいて、ifを使ってコードを切り替えれば良いと思います。

以上です。


おまけ。

ドライバ内ではpr_info関数をprintfデバッグに使えるっぽい(?)

pr_info("Hogehoge\n");

of_property_read_string関数を用いると、DTBから特定の要素に設定された文字列を読み出せる。

const char *comp;
of_property_read_string(pdev->dev.of_node, "compatible", &comp);
pr_info("%s\n",comp);

*1:詳細はSTマイクロエレクトロニクス社の資料(Migration of microcontroller applications from STM32F42xxx/STM32F43xxx to STM32F74xxx/STM32F75xxx) を参照してください。

*2:この関数の詳しい仕様について、日本語では以下の記事がとてもわかりやすいです。とあるエンジニアの備忘log: Device Tree アクセス関数まとめ (Linux Kernel)

Raspberry Pi 3を64-bitモードで扱うための現状について調べてみた

f:id:tnishinaga:20160417154950j:plain

これまでのあらすじ

2016年の2月の終わり頃,Raspberry Pi 3(以下,RasPi3)が発売されました. 日本では技適のいろいろがありまして,その1ヶ月後の3月終わり頃から秋月やマルツなどで購入できるようになりました.

RasPi3の特徴は,Raspberry Pi 2まで採用していた32-bit ARMアーキテクチャのCPUではなく,64-bit ARMアーキテクチャのCPUを採用している点です.

32-bitから64-bitになると,CPUが1度の命令で処理できるデータの長さが32から64と2倍になるので,同じ処理なら一般的に64-bit環境で実行するほうが高速*1です. しかし,2016年3月はまだ公式から64-bit版のraspbianは提供されていないどころか,そもそもファームウェアが64-bitに対応していなかったために,簡単に64-bitのOSやプログラムを動かすことができませんでした.

ここまでが私がRasPi3を購入した時点での状況です. 私は64-bit環境で利用するためにRasPi3を購入したので,64-bit 対応環境が出るまでRasPi3は放置して別のことをやることにしていましたが,最近どうやら64-bitで動作するようになったようなので,改めてその状況を調べて,まとめてみました.

64-bit対応状況

基本的には以下のフォーラムページで議論されているので,ここを読めばだいたい今どんな状況なのかわかります.

Raspberry Pi • View topic - Entering aarch64 execution state

ファームウェアの件

ご存じの方も多いと思いますが,Raspberry PiはCPUから起動が始まるのではなく,GPUが先に起動し,その上で動作するファームウェアがCPUやその他周辺機器の初期化を行い,カーネルをメモリにロードして制御を移し,起動するようになっています.

以前はこのファームウェアがCPUを64-bitモードでなく,32-bitモードにセットして起動を始めるので,そのまま64-bitのプログラムを起動できない……という問題があったのですが,4月13日のコミットで64-bitモードで起動する機能が追加され,この問題は解決したようです.

kernel: Bump to 4.4.7 · raspberrypi/firmware@efdcf16 · GitHub

カーネルの読み込まれる位置は0x00080000になるようです.

git.denx.de Git - u-boot.git/commitdiff

Kernelの件

ARM64のRasPi 3向けのdtsを追加するなどして,64-bit modeの起動に対応したカーネルを公開してくれている方がいます.

GitHub - anholt/linux

まだ試していませんが,こちらをARM64向けにビルドして,ブートローダーから起動すればよいのでしょう.

ブートローダーの件

gitのlogを見ると,U-Bootは既に対応しているようです.

git.denx.de Git - u-boot.git/summary

こちらもraspi3向けのdefconfigを読み込んでビルドすればよいのでしょう.

ビルド済みイメージ

64-bitモードで動作するRasPi3向けイメージが,有志の方から配布されています.

これらを用いることで手軽に64-bit Linux環境を試すことができます.

Debian

http://www.tom-yam.or.jp/rpi3/rpi3-arm64-debian-20160414.img.xz

rootのパスワードにraspberryといれるとログインできます.

ためしてみたところ,確かに64bitなLinux kernelが動作しました.

f:id:tnishinaga:20160417232329j:plain

Docker Engine入りイメージ

Raspberry pi 3用64bit Docker Engineを含むイメージを公開しました – 株式会社 あっとBSD

株式会社あっとBSDという,「基礎から学ぶ 組み込みAndroid」などの本を書かれている方の会社で作られたイメージです.

Dockerをすぐに試せるようです.

Gentoo

http://mirror.bytemark.co.uk/gentoo/experimental/arm64/

詳細不明ですが,Gentoo使ってる方は多分なんとかできると思います.

64-bitモードで動作するLinux イメージを作ってみる

今やってる途中なので,できたら別の記事として投稿します.

おわり

以上でおわりです.

その他情報がありましたら,コメントやTwitterで教えていただけると嬉しいです.

よろしくお願いします.

*1:暗号化処理など整数演算は確実に高速になりますが,それ以外はさほど変わりないので,すべてのプログラムが必ずしも高速になるわけではありません

Linux Kernelコードを.configの内容に応じてコードを切り替える方法メモ

一連のツイート

やりたいこと

現在,Linux Kernel 4.6をSTM32F746-discovery(Cortex-M7)上で動作させることを目標に,コードをいじっています.

現時点のMainline Linux Kernel(4.5)は,STM32F746-discoveryには対応していません. しかし,STM32F429I-discovery(Cortex-M4)には対応*1しており,ドライバやDeviceTreeSourceなどが存在しています. そのため,STM32F7への対応は,STM32F4向けのコードをベースに行っています.

それぞれのペリフェラルの差異はレジスタが増減していたりアドレスが違ったりするだけなので,STM32F4のコードをほんの10行程度修正するだけでSTM32F7でも動くようになります. そのため,STM32F4とSTM32F7で別々のドライバを用意するのではなく,以下のように#ifを用いて共通のドライバを一部切り替えるようにして,コードを節約したいです. また,どちらのCPUを使用するかの選択は,カーネルのconfigでいじれるようにしたいです.

#if STM32F7
   // STM32F7向けのレジスタ定義
#else
   // STM32F4向けのレジスタ定義
#endif

やりたいことまとめ

  • #if ディレクティブでマイコンごとにコードを切り替えられるようにしたい
  • 使用するCPUの選択をKernelのconfigで設定できるようにしたい

Kconfig で CPUを選択できるようにする

やりたいことは,以下の2点です.

  • 使用するSoCを.configに設定するのは make ARCH=arm menuconfig から選択して行えるようにしたい
  • STM32F4とSTM32F7は排他的に選択できるようにしたい

まず,menuconfig時に出てくる設定のリストは,各ディレクトリ以下のKconfigファイルに定義されています. 今回のSTM32マイコン向けの設定は arch/arm/Kconfigに書かれているので,これを編集します.

使用するSoCを選択するには,choiceを使えば良いことをnekomatsuさんに教えていただきました. 今回の場合,以下のようにchoiceからendchoiceまでの間にconfigを書くと,排他的に選択ができるようになります.

choice
    prompt "SoC Name"
        depends on ARCH_STM32
    default MACH_STM32F746

config MACH_STM32F429
    bool "STMicrolectronics STM32F429"

config MACH_STM32F746
    bool "STMicrolectronics STM32F746"

endchoice

実際にmenuconfigで開いてみた結果はこんな感じになります.

f:id:tnishinaga:20160411033033p:plain

.configの設定をC言語で使う

さて,次に.configの設定をCやアセンブリのコードで読むにはどうすれば良いのかと調べてみると,詳しく解説されているページがありました.

とあるエンジニアの備忘log: Linux ライクな autoconf.h を簡単に作る

こちらのページに書かれているように,カーネルをmakeした後には include/generated/autoconf.h ができており,このファイルを覗くと先ほど追加した設定が以下の用に定義されていました.

#define CONFIG_MACH_STM32F746 1

つまり,Kconfigに config MACH_STM32F746 と書いたものが選択された場合,MACH_STM32F746の頭にCONFIG_がついた CONFIG_MACH_STM32F746 定数が定義されます. 定数の値は,今回のようにboolの場合は選択時(yes)の場合は1が入ります.

このautoconf.hはすべてのCやアセンブリのビルド時にインクルードされています. なので,STM32F746が選択された場合のみ有効にしたいコードがある場合は,以下のようにして書くことができます.

#if CONFIG_MACH_STM32F746 == 1
 // STM32F746が選択された場合のみ有効にしたいコード
#endif

おわり

以上で.configの設定に応じてコードを切り替えることができるようになりました.

STM32F7でLinux Kernelを動かす件は,今の所シリアル出力が得られるようになりましたが,その他ペリフェラルのサポートはできていません. また,ある方法で起動しないと道中でクラッシュするというよくわからないバグも発生しており,解決できていません. そのあたりはこれから直していく予定です.

おしまい.

*1:動かし方はeLinuxのページにまとまっています

Failed to find path for dmidecode binaryと言ってlibvirtが起動しなくなった件

問題

いつの間にかlibvirtを使っていた仮想マシンが起動も設定もできなくなっていた.

以下のコマンドでlibvirtを再起動しても,長時間待たされた後にタイムアウトする.

$ sudo systemctl restart libvirtd.service

journalctlでログを見てみると,dmidecodeが見つからないと言われる.

$ journalctl -xe

libvirtd[2847]: internal error: Failed to find path for dmidecode binary

ぐぐってみると以下の記事が引っかかるが,ebtablesが見つからないとは言われていない.

kernhack.hatenablog.com

直し方

調べてみると,dmidecodeはハードウェアの情報を得るためのコマンドらしい.

linux.die.net

HWの情報を取得できるdmidecodeコマンドが結構便利だと思った - 完熟トマト

このパッケージをpacmanで検索してみるとインストールされてなかったので,インストールするとエラーが出なくなった.

$ sudo pacman -S dmidecode

この後システムを再起動したところ,libvirtを使った仮想マシンが起動できるようになった.

めでたしめでたし.