TOPPERSのコンフィギュレータで独自静的APIを追加する

# TOPPERSのコンフィギュレータで独自静的APIを追加する

この記事ではTOPPERSのコンフィギュレータで独自静的APIを追加する方法を説明します。
一つ前のバージョンですが、こちらの資料「[TOPPERSコンフィギュレータの進んだ使い方](https://www.toppers.jp/docs/tech/cfg-tutorial.pdf)」に詳しい説明が書かれています。
まず、静的APIについて説明します。

## 静的APIとは

TOPPERSのRTOSはμITRON仕様を実装・発展したものとなっています。
μITRON仕様には静的APIというものがあります。例えば以下のようなものです。

```c
CRE_TSK(TSKID_MAIN, {TA_HLNG|TA_ACT, 0, main, 1, 128, NULL});
```

[ここ](https://monozukuri-c.com/itron-multitask/)を参照しました。

この例ではタスクを1つ定義しています。この記述はC言語のソースコードファイルではなくcfgファイルに書きます。

ビルドの際にコンフィギュレータというプロセスを通すことでC言語のソースコードに変換し、RTOSで必要な情報を定義することが出来ます。TOPPERSのRTOSの場合`CRE_TSK`は、`kernel_cfg.c`や`kernel_cfg.h`に出力されます。

TOPPERSのコンフィギュレータは2種類ありますが、この記事では第三世代カーネル向けを使用します。前の世代の仕様書は[ここ](https://www.toppers.jp/documents.html#new-cfg)にありますが、最新版の仕様書は見当たりませんでした。

コンフィギュレータの生成したコードを見てみます。`kernel_cfg.c`の該当部分の出力イメージです。

```c:kernel_cfg.c
const ID _kernel_tmax_tskid = (TMIN_TSKID + TNUM_TSKID - 1);

static STK_T _kernel_stack_TSKID_MAIN[COUNT_STK_T(128)];

const TINIB _kernel_tinib_table[TNUM_TSKID] = {

  { (TA_HLNG|TA_ACT), (intptr_t)(0), (TASK)(main), INT_PRIORITY(1), ROUND_STK_T(128), _kernel_stack_TSKID_MAIN },

}

TCB _kernel_tcb_table[TNUM_TSKID];
```

カーネルの内部では、全てのタスクの情報を配列に入れて管理しています。ユーザーがIDを指定すると、カーネル内部では配列のインデックスとして使用するようになっています。
コンフィギュレータはその配列やIDをcfgファイルから生成する機能を担っています。
タスクの初期化情報は、`_kernel_tinib_table`という配列にまとめられます。cfgファイルに定義した内容とともに初期値が設定されます。
タスクの実行時に変化する情報を入れる領域が`_kernel_tcb_table`という配列で、全タスク数分確保されます。
タスクのスタック領域も指定がなければ、変数を定義して確保されます。

次に`kernel_cfg.h`の該当部分の出力イメージです。

```c:kernel_cfg.h
#define TNUM_TSKID 3

#define TSKID_MAIN 1
```

静的APIで指定した`TSKID_MAIN`を定義名とした、タスクIDが定義されます。IDが配列のインデックスになっています。
コーディング時にタスクIDを自分で割り当てなくても、コンフィギュレータが採番します。

このように静的APIをコンフィギュレータに掛けることで、cfgファイルに定義したい内容で、C言語のコードを出力し領域の確保やIDの採番、定義の出力などを行ってくれます。

また、TOPPERSのcfgファイルは別のcfgファイルを`INCLUDE`することが出来るので、共通のcfgファイルを用意して複数のアプリから使用することが出来ます。

コーディングをしていると、何かオブジェクトを配列で複数管理することはよくあると思うので、静的APIを独自に定義すると便利になると思いませんか？
TOPPERSの第三世代カーネル（ASP3やFMP3など）向けのコンフィギュレータはRubyスクリプトになっていますので、独自の静的APIを追加するには、テキストファイルの出力プログラムとして書けばよいので、特殊な作法はいりません。VSCodeなどを使ってコンフィギュレータのデバッグも出来ます。

## lwIPのTCP APIを静的APIにしてみる

lwIPのAPIを例に静的APIを追加し、それをコンフィギュレータで処理するコードを作ってみたいと思います。
説明用の試作なので完全なものではないですが、参考になればと思います。

組込み系のOSSでよく使われる[lwIP](https://savannah.nongnu.org/projects/lwip/)というTCP/IPプロトコルスタックがあります。
lwIPではプロトコル動作のための情報を保存する領域をPCB(Protocol Control Block)と呼びます。一方μITRONではタスク動作のための情報を保存する領域をTCB(Task Control Block)と呼びます。似てます。

lwIPでTCP通信を行う場合は`tcp_new`で`struct tcp_pcb`の領域を確保して`tcp_bind`で、通信の準備を始めます。
lwIPの`tcp_new`はメモリ領域を確保するだけなので、静的APIでTCP PCBの配列にメモリを割り当てます。その後TCP PCBを使う別のAPIには、IDで指定するようなμITRON的な使い方をするような設計にします。追加する静的APIを`CRE_TCP`としてcfgファイルには下記のように書けるようにしたいと思います。

```c:main.cfg
#include "main.h"

CRE_TCP(TCP_SERVER, {IP_ADDR_ANY, 0})
```

`CRE_TCP`についてコンフィギュレータで行う処理は、`tcp_new`で返る領域をTCP PCBの配列の一項目として確保し、`tcp_bind`の呼び出しで構造体に設定する値を、項目の初期値としてコードを出力することです。静的なコードを出力することで`tcp_new`と`tcp_bind`の呼び出しを不要にします。
使い方は下記のコードになります。まず`main.c`です。

```c:main.c
#include "main.h"
#include "lwip_cfg.h"

static err_t
server_accept(void *arg, struct tcp_pcb *pcb, err_t err)
{
  return ERR_OK;
}

void main()
{
  //struct tcp_pcb *TCP_SERVER;
  struct tcp_pcb *lpcb;

  //ここから
  //TCP_SERVER = tcp_new();
  //tcp_bind(TCP_SERVER, IP_ADDR_ANY, 0);
  //ここまでをCRE_TCPで静的に変数定義してしまう。

  lpcb = tcp_listen(TCP_SERVER);

  tcp_accept(lpcb, server_accept);
}
```

`mann.h`は下記のようになります。`TA_NULL`はTOPPERSのヘッダーファイルに定義されていますが、説明のため書いてあります。

```c:mann.h
#include "lwip/opt.h"
#include "lwip/ip_addr.h"
#include "lwip/tcp.h"

#define TA_NULL 0
```

静的APIを使うと、実行時に行っていたメモリの割り当てと変数の初期化を、生成したコードに書いて静的に定義することが出来ます。
RTOS領域の組込みソフトでは、コードで静的に定義するとこがメリットになることがあります。

## 独自静的APIの仕様

今回説明するlwIP向けコンフィギュレータに必要なファイルは下記になります。

|ファイル|概要|
|-|-|
|lwip_api.def|静的APIの定義を書くファイル|
|lwip_sym.def|静的APIで使用する定義を書く|
|lwip.trb|コンフィギュレータのRubyスクリプト|

コンフィギュレータで出力するファイルは下記のようにします。これは、`lwip.trb`のRubyコードで出力します。

|ファイル|概要|
|-|-|
|lwip_cfg.h|TCP PCBのIDを定義する|
|lwip_cfg.c|TCP PCBの配列を定義する|

作成した静的APIを試すためのコードは下記のファイルになります。

|ファイル|概要|
|-|-|
|main.cfg|コンフィギュレータへの入力する用意するファイル|
|main.h|ソースコード|
|main.c|ソースコード|

静的APIの仕様的な定義は下記になります。これは人間向けでコンフィギュレータへの入力にはなりません。

```c
CRE_TCP(ID tcpid, { ATR tcpatr, ip_addr_t ipaddr, u16_t portno })
```

## コンフィギュレータで出力する内容

静的APIで出力する内容は`lwip_cfg.c`ではTCP PCBの配列を定義し、下記のようにします。
`#include`はコンフィギュレータのコードで固定的に出力する以外に、cfgファイルから拾ってきたものを`GenerateIncludes`で出力ることも出来ます。

```c:lwip_cfg.c
/* lwip_cfg.c */
#include "lwip_cfg.h"
#include "main.h"

struct tcp_inib tcp_inib_table[TNUM_TCP_PCB] = {
  {IP_ADDR_ANY, 0}
};
```

`lwip_cfg.h`ではTCP PCBのIDを定義し、下記のようにします。lwIPのAPIが数値のIDではなくPCB構造体へのポインタを渡す仕様になっているので、IDを数値ではなく配列の1項目を返すようにしています。

話がそれますが、数値のIDを使うようなlwIPのラッパーAPIを作れば、lwIPが操作するメモリと、ユーザーの操作するメモリを分離できると考えています。CPUのメモリ保護機能を使えるようにするのも、RTOS領域の組み込みソフトではメリットがあるのではないかと思っています。

```c:lwip_cfg.h
/* lwip_cfg.h */
#ifndef LWIP_CFG_H
#define LWIP_CFG_H

#include "lwip/ip_addr.h"
#include "lwip/tcp.h"

struct tcp_inib {
  ip_addr_t *addr;
  u16_t port;
};

#define TNUM_TCP_PCB 1
#define TCP_SERVER &tcp_pcb_table[1]

extern struct tcp_pcb tcp_pcb_table[TNUM_TCP_PCB];

#endif /* LWIP_CFG_H */
```

## 静的APIの実装

静的APIからコードを出力するコンフィギュレータの実装について説明します。

コンフィギュレータは、カーネルの配布物に含まれています。例えば、[ASP3](https://toppers.jp/asp3-d-download.html)の[asp3-3.6.0.tar.gz](https://toppers.jp/download.cgi/asp3-3.6.0.tar.gz)をダウンロードして、展開した中にある`cfg`フォルダに一式入っています。

`cfg.rb`を呼び出すことで、コンフィギュレータを実行することが出来ます。`cfg.rb`では静的APIやcfgファイルからコードを出力するための情報収集を行う処理を行い、カーネルオブジェクトごとに用意してある個別の`*.trb`に渡してコードを出力します。

今回の静的APIのコンフィギュレータのメインの処理は`lwip.trb`になります。

静的APIの定義は`lwip_api.def`に行います。人間向けの仕様的な定義から引数型やカンマを削除し、記号を追加したもになります。記号の意味は上で紹介した資料を参照してください。

```c:lwip_api.def
CRE_TCP #tcpid* { .tcpatr &ipv4addr &portno }
```

メインの処理で使用する`#define`定義などC言語の値で、コンフィギュレータで参照したい定義名を`lwip_sym.def`に列挙します。コンフィギュレータでは`lwip_sym.def`の定義をC言語で出力しコンパイル、リンク、ダンプして、シンボルのアドレスとダンプしたメモリから値を取得するために必要です。

```c:lwip_sym.def
TA_NULL
```

最後にメインの処理は以下のようになります。

```ruby:lwip.trb
$lwipCfgH = GenFile.new("lwip_cfg.h")
$lwipCfgH.add(<<EOS)
/* lwip_cfg.h */
#ifndef LWIP_CFG_H
#define LWIP_CFG_H

struct tcp_inib {
  ip_addr_t addr;
  u16_t port;
};
EOS

$lwipCfgH.add("#define TNUM_TCP_PCB #{$cfgData[:CRE_TCP].size}")
$cfgData[:CRE_TCP].each do |key, params|
  $lwipCfgH.add("#define #{params[:tcpid].str}\t&tcp_pcb_table[#{params[:tcpid].val}]")
end

$lwipCfgH.add(<<EOS)

extern struct tcp_pcb tcp_pcb_table[TNUM_TCP_PCB];
EOS

$lwipCfgC = GenFile.new("lwip_cfg.c")
$lwipCfgC.add(<<EOS)
/* lwip_cfg.c */
#include "lwip_cfg.h"
EOS
GenerateIncludes($lwipCfgC)

$lwipCfgC.add("struct tcp_inib tcp_inib_table[TNUM_TCP_PCB] = {")
$cfgData[:CRE_TCP].each do |key, params|
  $lwipCfgC.add("\t{#{params[:ipv4addr]}, #{params[:portno]}}")
end
$lwipCfgC.add("};")

$lwipCfgH.append(<<EOS)
#endif /* LWIP_CFG_H */
EOS
```

cfgファイルで`CRE_TCP`で定義した値は、`$cfgData[:CRE_TCP]`で取り出すことが出来ます。Rubyの配列になっているので`each`で、一項目の情報を`params`に取り出して、配列の初期値を出力するのに使用します。引数の内容は`params[:tcpid]`のように引き数名で取り出せます。IDに関しては、`str`が定義名`TCP_SERVER`で`val`が採番したIDの値になります。

そのほか実装に必要な知識はRubyでのプログラム知識なので、分岐や繰り返し制御、変換処理やチェック処理があればRubyの使い方を調べて実装することが出来ます。

## コンフィギュレータの実行

コンフィギュレータは下記の手順で行われます。

1. C言語の定義からの情報収集用コード(`cfg1_out.*`)を出力(--pass 1)
2. (`cfg1_out.*`)をビルドし、ダンプして`*.dump`と`*.syms`を作成
3. 1と2の情報からコードを出力(--pass 2)

上記の手順は`Makefile`に定義されているのですが、コンフィギュレータのデバッグのために、個別に実行できた方が良いと思いますので、コマンドの例を下記の紹介します。

1に対するコマンドは、下記のようになります。

```bash
ruby cfg/cfg.rb --pass 1 --kernel asp -T lwip.trb --api-table lwip_api.def --symval-table lwip_sym.def main.cfg
```

2に対するコマンドは、下記のようになります。

```bash
gcc -o cfg1_out.exe -I lwip/src/include -I contrib/examples/example_app -I contrib/ports/win32/include cfg1_out.c
nm -n cfg1_out.exe > cfg1_out.syms
objdump -s cfg1_out.exe > cfg1_out.dump
```

3に対するコマンドは、下記のようになります。

```bash
ruby cfg/cfg.rb --pass 2 --kernel asp -T lwip.trb
```

## おわりに

簡単になるよう内容を絞ったので説明が足らない点があるかと思いますが、独自静的APIで静的なコードを生成できるようにすると、後続開発などの助けになるのではと思いますので、ぜひ試してみてください。