RX マイコン用リンカースクリプトとスタートアップ

RX マイコン用 gcc を更新したので、リンカースクリプトも更新してみた。

gcc のリンカースクリプトの記述仕様は「不明」の部分が多く、それでも、C 言語
なら、そう問題にならないが、C++ の場合、事前準備が必要なので、そう簡単では
無いと思える。
また、gcc の新しい機能として、新規のセクションが必要な場合もあるかもしれず、
gcc のバージョンを変更したら、その gcc がインストールされたディレクトリーに
ある基準となるリンカースクリプトをベースにしておく必要がある。

組み込みマイコン用に、自前で gcc をビルドして使っている人の中には、かなり
昔に作られた、乖離したリンカースクリプトを使っている場合もあるようだけど、
これは、少し問題がある、何か新しい機能が新設された場合、ライブラリーが正しく
動作する保障は無いと言える。
※ C 言語のみの場合は、あまり問題にならない場合が多いかもしれないけど・・

自分の環境では:(/usr/local/rx-elf)
gcc 付属のリンカースクリプトは、「/usr/local/rx-elf/rx-elf/lib/rx.ld」にあり、
それを、基準にしている。

・「rx.ld」を観ていこう~
(1) これは、RAM、ROM領域、及びスタックを定義している、自分が使うマイコンの
メモリーマップに従って、領域を設定する。
※スタックは、下位アドレスに向かって消費するので、「LENGTH」に大きな意味は無いものと
思う。

/* This memory layout corresponds to the smallest predicted RX600 chip.  */
MEMORY {
	RAM (w)   : ORIGIN = 0x00000000, LENGTH = 0x00020000 /* 128k */
	STACK (w) : ORIGIN = 0x00020000, LENGTH = 16 /* top of RAM */

	ROM (w)   : ORIGIN = 0xfff40000, LENGTH = 0x000bffd0 /* 768k */
/* This is the largest RX6000: */
/*	ROM (w)   : ORIGIN = 0xffe00000, LENGTH = 0x001fffd0 */ /* 2Mb */
}

(2) このセクションは、プログラムコードが収められている。

  .text :
  {
    PROVIDE (_start = .);
    *(.text P .stub .text.* .gnu.linkonce.t.*)
    KEEP (*(.text.*personality*))
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
    *(.interp .hash .dynsym .dynstr .gnu.version*)
    PROVIDE (__etext = .);
    PROVIDE (_etext = .);
    PROVIDE (etext = .);
    . = ALIGN(4);
    KEEP (*(.init))
    KEEP (*(.fini))
  } > ROM

(3) このセクションは、読み出し専用データが収められている。

  .rodata : {
    . = ALIGN(4);
    *(.plt)
    *(.rodata C C_2 C_1 W W_2 W_1 .rodata.* .gnu.linkonce.r.*)
    *(.rodata1)
    *(.eh_frame_hdr)
    KEEP (*(.eh_frame))
    KEEP (*(.gcc_except_table)) *(.gcc_except_table.*)
    . = ALIGN(4);
    PROVIDE(__romdatastart = .);
  } > ROM

(4) このセクションは、起動時、RAM にコピーしておく必要のあるデータなどで、ROM 領域
に配置する。
※「__romdatacopysize」が定義されている。
この中で、「.ctors」、「.dtors」と二つの重要な部分がある、これは、アプリケーション
を起動する前に準備しておく必要のあるコンストラクター、及び、アプリケーション終了時
に呼び出す、デストラクター関係である。
※通常組み込みでは、アプリケーションは終了しないので、デストラクターは呼び出す必要
が無い、もちろん、複数のアプリケーションを動的に呼び出すような機構を備えたシステム
では、デストラクターを呼び出す事は、必須であるけど。

  .data : {
    . = ALIGN(4);
    PROVIDE (__datastart = .); /* IF_ROROM */
    PROVIDE (__preinit_array_start = .);
    KEEP (*(.preinit_array))
    PROVIDE (__preinit_array_end = .);
    PROVIDE (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array))
    PROVIDE (__init_array_end = .);
    PROVIDE (__fini_array_start = .);
    KEEP (*(.fini_array))
    KEEP (*(SORT(.fini_array.*)))
    PROVIDE (__fini_array_end = .);
    LONG(0); /* Sentinel.  */

    /* gcc uses crtbegin.o to find the start of the constructors, so
       we make sure it is first.  Because this is a wildcard, it
       doesn't matter if the user does not actually link against
       crtbegin.o; the linker won't look for a file to match a
       wildcard.  The wildcard also means that it doesn't matter which
       directory crtbegin.o is in.  */
    KEEP (*crtbegin*.o(.ctors))

    /* We don't want to include the .ctor section from from the
       crtend.o file until after the sorted ctors.  The .ctor section
       from the crtend file contains the end of ctors marker and it
       must be last */
    KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*(.ctors))

    KEEP (*crtbegin*.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*(.dtors))
    KEEP (*(.jcr))
    *(.data.rel.ro.local) *(.data.rel.ro*)
    *(.dynamic)

    *(.data D .data.* .gnu.linkonce.d.*)
    KEEP (*(.gnu.linkonce.d.*personality*))
    SORT(CONSTRUCTORS)
    *(.data1)
    *(.got.plt) *(.got)

    /* We want the small data sections together, so single-instruction offsets
       can access them all, and initialized data all before uninitialized, so
       we can shorten the on-disk segment size.  */
    . = ALIGN(4);
    *(.sdata .sdata.* .gnu.linkonce.s.* D_2 D_1)

    . = ALIGN(4);
    _edata = .;
    PROVIDE (edata = .);
    PROVIDE (__dataend = .);
  } > RAM AT> ROM

  /* Note that __romdatacopysize may be ZERO for the simulator, which
     knows how to intialize RAM directly.  It should ONLY be used for
     copying data from ROM to RAM; if you need to know the size of the
     data section, subtract the end symbol from the start symbol.  */
  /* Note that crt0 assumes this is a multiple of four; all the
     start/stop symbols are also assumed long-aligned.  */
  PROVIDE (__romdatacopysize = SIZEOF(.data));

(5) このセクションは、変数などの領域、通常、起動時に「0」クリアしておく。

  .bss : {
    . = ALIGN(4);
    PROVIDE (__bssstart = .);
    *(.dynbss)
    *(.sbss .sbss.*)
    *(.bss B B_2 B_1 .bss.* .gnu.linkonce.b.*)
    . = ALIGN(4);
    *(COMMON)
    . = ALIGN(4);
    PROVIDE (__bssend = .);
    _end = .;
    PROVIDE (end = .);
  } > RAM
  PROVIDE (__bsssize = SIZEOF(.bss) / 4);

(6) このセクションは、スタック領域
RX マイコンの場合、スタックは2つあり、ハードウェアー依存のスタック(主に、割り込み
発生の場合などで、ステータスやレジスターを退避する)と、ユーザー領域依存のスタックが
ある。

  .stack (ORIGIN (STACK)) :
  {
    PROVIDE (__stack = .);
    *(.stack)
  }

(7) このセクションは、ハードウェアー依存のベクターテーブル専用領域。
※RX マイコンでは、「リセット」信号がアサートされると、「_start」から起動する。
※他に、ハードウェアー依存の例外が発生した場合に起動するベクターを記述する。

  /* Providing one of these symbols in your code is sufficient to have
     it linked in to the fixed vector table.  */

  PROVIDE (__rx_priviledged_exception_handler = 0x00000000);
  PROVIDE (__rx_access_exception_handler = 0x00000000);
  PROVIDE (__rx_undefined_exception_handler = 0x00000000);
  PROVIDE (__rx_floating_exception_handler = 0x00000000);
  PROVIDE (__rx_nonmaskable_exception_handler = 0x00000000);

  .vectors (0xFFFFFFD0) :
  {
    PROVIDE (__vectors = .);
    LONG (__rx_priviledged_exception_handler);
    LONG (__rx_access_exception_handler);
    LONG (0);
    LONG (__rx_undefined_exception_handler);
    LONG (0);
    LONG (__rx_floating_exception_handler);
    LONG (0);
    LONG (0);
    LONG (0);
    LONG (0);
    LONG (__rx_nonmaskable_exception_handler);
    LONG (_start);
  }

・それでは、「_start」関数から、「main」までにどのような手順を踏んだら良いのか?
同じディレクトリーに、「crt0.o」がありこのコードが基準となる。
このオブジェクトは、既にバイナリーになっているので、objdump などで、逆にアセン
ブリコードに戻す事で、内容を確認出来る。

  rx-elf-objdump -S crt0.o

crt0.o:     file format elf32-rx-le

Disassembly of section P:

00000000 <_start>:
	.text

	.global _start
_start:
.LFB2:
	mvtc	#0, psw
   0:	fd 77 00 00                   	mvtc	#0, psw
	/* Enable the DN bit - this should have been done for us by
           the CPU reset, but it is best to make sure for ourselves.  */	
	mvtc    #0x100, fpsw
   4:	fd 7b 03 00 01                	mvtc	#256, fpsw
   9:	fb 02 00 00 00 00             	mov.l	#0, r0
	mov	#__stack, r0
   f:	fd 73 0c 00 00 00 00          	mvtc	#0, intb
	mvtc	#__vectors, intb
  16:	fb 12 00 00 00 00             	mov.l	#0, r1

	mov	#__datastart, r1
  1c:	fb 22 00 00 00 00             	mov.l	#0, r2
	mov	#__romdatastart, r2
  22:	fb 32 00 00 00 00             	mov.l	#0, r3
	mov	#__romdatacopysize, r3
	smovf
  28:	7f 8f                         	smovf
  2a:	fb 12 00 00 00 00             	mov.l	#0, r1

	mov	#__bssstart, r1
	mov	#0, r2
  30:	66 02                         	mov.l	#0, r2
  32:	fb 32 00 00 00 00             	mov.l	#0, r3
	mov	#__bsssize, r3
	sstr.l
  38:	7f 8a                         	sstr.l
  3a:	fb d2 00 00 00 00             	mov.l	#0, r13
	/* Initialise the constant data pointer and small data pointers.  */
	mov	#__pid_base, r13
	mov	#__gp, r12
#else
	/* Initialise the small data area pointer.  */
	mov	#__gp, r13
  40:	05 00 00 00                   	bsr.a	40 <_start+0x40>
	mov	# _start, r1
	mov	# _etext, r2
	bsr.a	__monstartup
#endif

	mov	#0, r1 /* argc */
  44:	66 01                         	mov.l	#0, r1
	mov	#0, r2 /* argv */
  46:	66 02                         	mov.l	#0, r2
	mov	#0, r3 /* envv */
  48:	66 03                         	mov.l	#0, r3
  4a:	05 00 00 00                   	bsr.a	4a <_start+0x4a>
	bsr.a	_main
  4e:	05 00 00 00                   	bsr.a	4e <_start+0x4e>

00000052 <_rx_run_preinit_array>:
        mov      r1, r13       ; Save return code.
	bsr.a	__mcleanup
        mov     r13, r1
#endif

	bsr.a	_exit
  52:	fb 12 00 00 00 00             	mov.l	#0, r1

	.global	_rx_run_preinit_array
	.type	_rx_run_preinit_array,@function
_rx_run_preinit_array:
	mov	#__preinit_array_start,r1
  58:	fb 22 00 00 00 00             	mov.l	#0, r2
	mov	#__preinit_array_end,r2
  5e:	04 25 00 00                   	bra.a	83 <_rx_run_inilist>

00000062 <_rx_run_init_array>:
	bra.a	_rx_run_inilist
  62:	fb 12 00 00 00 00             	mov.l	#0, r1

	.global	_rx_run_init_array
	.type	_rx_run_init_array,@function
_rx_run_init_array:
	mov	#__init_array_start,r1
  68:	fb 22 00 00 00 00             	mov.l	#0, r2
	mov	#__init_array_end,r2
	mov	#4, r3
  6e:	66 43                         	mov.l	#4, r3
  70:	04 13 00 00                   	bra.a	83 <_rx_run_inilist>

00000074 <_rx_run_fini_array>:
	bra.a	_rx_run_inilist
  74:	fb 22 00 00 00 00             	mov.l	#0, r2

	.global	_rx_run_fini_array
	.type	_rx_run_fini_array,@function
_rx_run_fini_array:
	mov	#__fini_array_start,r2
  7a:	fb 12 00 00 00 00             	mov.l	#0, r1
	mov	#__fini_array_end,r1
	mov	#-4, r3
  80:	fb 36 fc                      	mov.l	#-4, r3

00000083 <_rx_run_inilist>:
	/* fall through */

_rx_run_inilist:
next_inilist:
	cmp	r1,r2
  83:	47 12                         	cmp	r1, r2
  85:	20 17                         	beq.b	9c 
	beq.b	done_inilist
	mov.l	[r1],r4
  87:	ec 14                         	mov.l	[r1], r4
	cmp	#-1, r4
  89:	75 04 ff                      	cmp	#-1, r4
  8c:	20 0c                         	beq.b	98 
	beq.b	skip_inilist
	cmp	#0, r4
  8e:	61 04                         	cmp	#0, r4
  90:	20 08                         	beq.b	98 
	beq.b	skip_inilist
	pushm	r1-r3
  92:	6e 13                         	pushm	r1-r3
	jsr	r4
  94:	7f 14                         	jsr	r4
	popm	r1-r3
  96:	6f 13                         	popm	r1-r3

00000098 :
skip_inilist:
	add	r3,r1
  98:	4b 31                         	add	r3, r1
  9a:	2e e9                         	bra.b	83 <_rx_run_inilist>

0000009c :
	bra.b	next_inilist
done_inilist:
	rts
  9c:	02                            	rts

Disassembly of section .fini:

00000000 <__rx_fini>:
	.text

	.global _start
_start:
.LFB2:
	mvtc	#0, psw
   0:	05 00 00 00                   	bsr.a	0 <__rx_fini>

00000003 :
	...

ここから、必要な部分をコピーして、また、自分のシステムに必要な部分を追加して、スタートアップ
ルーチンを構築するのが良いと思う。

で、RX63T(RAM: 8KB, ROM: 64KB) 用のリンカースクリプトと、スタートアップルーチンを作成してみた。

RX63T 用リンカースクリプト
スタートアップルーチン「start.s」
初期化関数「init.c」
※自分の実装では、「スタートアップ」は、「start.s」と「init.c」に分けている。

RXマイコン用、フラッシュ書き込みプログラム(RX63T)

先日、gcc-4.9.3 を使って、R8C の開発環境を整えた。

RXマイコンにおいても、gcc-4.9.3 で、構築でき、そのコンパイラでビルド
したプロジェクトも問題無く動作する事を確認した。

以前に R8C 用のフラッシュ書き込みプログラムを作成して、もはや、ルネサス
のツールを一切利用する事なく、オープンソースだけで開発出来るようになった。
※Mac-Book でも、R8C マイコンの開発が可能となった。

RXマイコン用のフラッシュ書き込みツールも、ブート時のプロトコルが公開さ
れている為、実装は可能だと思っていた。
そこで、RX63T のハードウェアーマニュアルに記載されたプロトコルを理解しな
がら、少しづつ、機能を実装して、書き込みまで確認出来たので、とりあえず公開
する。
同じルネサスでも、R8C とは、構成が違うので、基本的な部分以外は書き直しとな
った。
※RX63T の仕様を元にしているが、他のRXシリーズにも、そのまま、又は少しの
修正で使えると思う。

いつものように GitHub に公開している。
※R8C のソースと重複するソースコードが含まれているが、多少更新している。
そのうち、重複する部分は、別にまとめておきたいと思う。

—–
まず参考にした資料は、「RX63Tグループ ユーザーズマニュアル ハードウェア編」
で、ルネサスからダウンロードできる。

細かい仕様は、マニュアル「40.8 ブートモード」に詳細に書かれているので、それを
理解すれば、誰でも実装する事が出来る。
16ビットや32ビットの値を送る場合や、読み込む場合は、ビッグ・エンディアン
になっているようだ。
※これは、マニュアルには書かれていなかった。

現在使っている、デバイスは、RX63Tの64ピン版で、USBを持たないので、
シリアル通信に限定している。

また、コードプロテクト機能を使わない場合でも、フラッシュ書き込みモード(P/E)
に移行すると、内部フラッシュは自動で消去されてしまう為、内部フラッシュの状態を
読み出す必要がある場合は、必ず、プロテクトIDを設定する必要があるようだ。

ソースコードをマルチプラットホームにしたいので、RS232C の入出力は、「termios.h」
を使って実現しているので、MSYS2 環境でコンパイルする必要があり、また、boost を
使っている為、「/usr/local/」以下に boost のソースを配置しておく必要がある。
※ライブラリーを使っていないのでヘッダーだけあれば良い。
MacOS や Linux でもコンパイルする事が出来ると思う。
※現状では「termios.h」は、MSYS2 環境にのみ存在する。
※ MSYS2 環境では、boost はインストーラーで管理していないみたい。
※ボーレートの設定範囲は、環境に依存する様なので、どの環境でもサポートされている
ボーレートのみを使っている為、上限は、115200 となっている。

IMG_0782

フラッシュへの書き込みは、必ず256バイトのブロック毎に行う仕様で、ベリファイの
コマンドは無いので、比較を行う場合は、P/E モード中に読み出して、比較する必要がある。

コードプロテクト行った場合は、フラッシュの消去は自動では無いので、書き込む前に消去
を行う必要がある。

まだ、未完成ではあるが、とりあえず、RX63T に、書き込めるようになったので、とりあえず
公開しておく。