SSブログ
ColdFire V2 ブログトップ
前の20件 | -

YouTube 進出記念 - MCF52233付録基板で作るネットワーク電飾の動画 [ColdFire V2]このエントリーを含むはてなブックマーク#

Yahoo!ビデオキャストが終了するため、乗換え先を探しました。 やっぱり、 YouTube しか無いのかな?


点滅パターンは、Javaで記述したPC上のプログラムからUDPで与えます。 元ネタは、ネットワーク電飾 (1)です。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

RiverSea その後 [ColdFire V2]このエントリーを含むはてなブックマーク#

WS000164.png

***HTML文書にGIFを埋め込んでみた***のその後。 やっとの思いで、ここまで、来たけど、完成させられるかどうか、わかりません。 誰か、仕上げて。

サーバプログラム Main

char *CgiFlag=0x20007f2e;
main() {
  char *board=MemoryAlloc(64);
  char *cmd=MemoryAlloc(81);
  char row,col;
  char ch;
  int i;
  char done=0;
  while (!done) {
    for (i = 0;i < 80; i++) {
      ch = Getc(1);
      cmd[i] = ch;
      if (ch == 0) break;
    }
    cmd[i] = 0;
    switch(cmd[0]) {
      case 'B':  setBoard(board, cmd+3); break;
      case 'R':  row = Atoi(cmd+3);      break;
      case 'C':  col = Atoi(cmd+3);      break;
      case 'b':  putBoard(board);        break;
      case 'p':  putPiece(board, cmd+3); break;
      case 'X':  done = 1;
    }
    *CgiFlag = 0;
  }
  MemoryFree(cmd);
  MemoryFree(board);
}
setBoard(char *b, char *c) {
  char *num_buf=MemoryAlloc(8);
  int x,y;
  long line;
  MemClear(num_buf,8);
  StrCpy(num_buf,"0x");
  MemClear(b, 64);
  for (y = 0; y < 8; y++) {
    BufCopy(num_buf+2,c,4);
    c += 4;
    line = Atoi(num_buf);
    for (x = 8; --x >= 0; ) {
      b[y*8+x]=line & 3;
      line >>= 2;
    }
  }
  MemoryFree(num_buf);
}
putBoard(char *b) {
  int x,y;
  int line;
  for (y = 0; y < 8; y++) {
    line = 0;
    for (x = 0; x < 8; x++) {
      line <<= 2;
      line += b[y*8+x];
    }
    PrHexWord(line);
  }
}
putPiece(char *b, char *c) {
  int index=Atoi(c);
  switch (b[index]) {
    case 1:  PrStr("<img src=\"W.gif\"/>"); break;
    case 2:  PrStr("<img src=\"B.gif\"/>"); break;
    default: PrStr(" "); break;
  }
}

HTML文書プロトタイプ RiverSea.htm

<html><head>
<title>RiverSea</title>
<style>
table.board {text-align:center;}
table.board td {width:40px;height:40px;background:#0F0;}
</style>
</head><body>
<h1 style="text-align:center;">RiverSea</h1>
<form action="RiverSea.htm" style="text-align:center;">
<table cellpadding="10" style="margin:0 auto;">
<td>
<table border="1" cellpadding="2" cellspacing="1" class="board">
<tr><th colspan="14"/></tr>
<tr>
<th rowspan="12"/><th/><th rowspan="12"/>
<th>A</th><th>B</th><th>C</th><th>D</th>
<th>E</th><th>F</th><th>G</th><th>H</th>
<th rowspan="12"/>
</tr>
<tr><th/><th colspan="8"/></tr>
<tr><th>1</th>
<td>$EXEC'p("0")          '</td>
<td>$EXEC'p("1")          '</td>
<td>$EXEC'p("2")          '</td>
<td>$EXEC'p("3")          '</td>
<td>$EXEC'p("4")          '</td>
<td>$EXEC'p("5")          '</td>
<td>$EXEC'p("6")          '</td>
<td>$EXEC'p("7")          '</td>
</tr>
<tr><th>2</th>
<td>$EXEC'p("8")          '</td>
<td>$EXEC'p("9")          '</td>
<td>$EXEC'p("10")         '</td>
<td>$EXEC'p("11")         '</td>
<td>$EXEC'p("12")         '</td>
<td>$EXEC'p("13")         '</td>
<td>$EXEC'p("14")         '</td>
<td>$EXEC'p("15")         '</td>
</tr>
<tr><th>3</th>
<td>$EXEC'p("16")         '</td>
<td>$EXEC'p("17")         '</td>
<td>$EXEC'p("18")         '</td>
<td>$EXEC'p("19")         '</td>
<td>$EXEC'p("20")         '</td>
<td>$EXEC'p("21")         '</td>
<td>$EXEC'p("22")         '</td>
<td>$EXEC'p("23")         '</td>
</tr>
<tr><th>4</th>
<td>$EXEC'p("24")         '</td>
<td>$EXEC'p("25")         '</td>
<td>$EXEC'p("26")         '</td>
<td>$EXEC'p("27")         '</td>
<td>$EXEC'p("28")         '</td>
<td>$EXEC'p("29")         '</td>
<td>$EXEC'p("30")         '</td>
<td>$EXEC'p("31")         '</td>
</tr>
<tr><th>5</th>
<td>$EXEC'p("32")         '</td>
<td>$EXEC'p("33")         '</td>
<td>$EXEC'p("34")         '</td>
<td>$EXEC'p("35")         '</td>
<td>$EXEC'p("36")         '</td>
<td>$EXEC'p("37")         '</td>
<td>$EXEC'p("38")         '</td>
<td>$EXEC'p("39")         '</td>
</tr>
<tr><th>6</th>
<td>$EXEC'p("40")         '</td>
<td>$EXEC'p("41")         '</td>
<td>$EXEC'p("42")         '</td>
<td>$EXEC'p("43")         '</td>
<td>$EXEC'p("44")         '</td>
<td>$EXEC'p("45")         '</td>
<td>$EXEC'p("46")         '</td>
<td>$EXEC'p("47")         '</td>
</tr>
<tr><th>7</th>
<td>$EXEC'p("48")         '</td>
<td>$EXEC'p("49")         '</td>
<td>$EXEC'p("50")         '</td>
<td>$EXEC'p("51")         '</td>
<td>$EXEC'p("52")         '</td>
<td>$EXEC'p("53")         '</td>
<td>$EXEC'p("54")         '</td>
<td>$EXEC'p("55")         '</td>
</tr>
<tr><th>8</th>
<td>$EXEC'p("56")         '</td>
<td>$EXEC'p("57")         '</td>
<td>$EXEC'p("58")         '</td>
<td>$EXEC'p("59")         '</td>
<td>$EXEC'p("60")         '</td>
<td>$EXEC'p("61")         '</td>
<td>$EXEC'p("62")         '</td>
<td>$EXEC'p("63")         '</td>
</tr>
<tr><th colspan="14"/></tr>
</table>
</td>
<td>
<p>
<input type="text" name="EXEC'B'"
  value="$EXEC'b("00000000000001800240000000000000")'"
/>
</p>
<table cellpadding="5" style="text-align:center;">
<tr><th style="text-align:left;" colspan="8">COLUMN:</th></tr>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
<td>D</td>
<td>E</td>
<td>F</td>
<td>G</td>
<td>H</td>
</tr>
<tr>
<td><input type="radio" name="EXEC'C'" value="0" checked/></td>
<td><input type="radio" name="EXEC'C'" value="1"/></td>
<td><input type="radio" name="EXEC'C'" value="2"/></td>
<td><input type="radio" name="EXEC'C'" value="3"/></td>
<td><input type="radio" name="EXEC'C'" value="4"/></td>
<td><input type="radio" name="EXEC'C'" value="5"/></td>
<td><input type="radio" name="EXEC'C'" value="6"/></td>
<td><input type="radio" name="EXEC'C'" value="7"/></td>
</tr>
<tr><th style="text-align:left;" colspan="8">ROW:</th></tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
</tr>
<tr>
<td><input type="radio" name="EXEC'R'" value="0" checked/></td>
<td><input type="radio" name="EXEC'R'" value="1"/></td>
<td><input type="radio" name="EXEC'R'" value="2"/></td>
<td><input type="radio" name="EXEC'R'" value="3"/></td>
<td><input type="radio" name="EXEC'R'" value="4"/></td>
<td><input type="radio" name="EXEC'R'" value="5"/></td>
<td><input type="radio" name="EXEC'R'" value="6"/></td>
<td><input type="radio" name="EXEC'R'" value="7"/></td>
</tr>
<tr><td colspan="9"><input type="submit" value="Decide"/></td></tr>
<tr><td colspan="9"><a href="SetEnd.htm?EXEC%27X%27">EXIT</a></td></tr>
</table>
</td>
</table>
</form>
</body></html>

呼び出し方

http://192.168.1.10/RiverSea.htm?EXEC%27B%27=00000000000001800240000000000000

続 HTTP サーバの CGI 機能を極める [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

ともぞうさんの記事「ColdFireマイコン温度計にLED(2)」で再確認したのですが、 http サーバの CGI 機能は、 SilentC のプログラムが走っていると機能しないようです。 でも、機能していないわけじゃないらしいんだな。

SilentC のプログラムが走っている場合の実験

以下の実験は、シリアル・インターフェースで接続してお試しください。 telnet では、同じ結果が得られません。

まずは、普通に SilentC のプログラムを実行してから、加速度センサ表示ページを呼び出します。 実行したプログラムは、これです。

main(){#stop 0;while(Getc(0)!='q')SystemSleep();}
WS000168.png

すると、みごとにブラウザが停止して、ページが表示されません。 ここで、コンソールから、 'q' をタイプすると、プログラムの実行が停止し、ブラウザには、文字が表示されます。

この実験から、 SilentC のプログラムが実行されている事が原因となって CGI 機能が動作しないらしいことがわかります。

文字表示プログラムが走っている場合の実験

最初の実験は、何もしないプログラムを使いました。 今度は、メッセージを表示するプログラムを使ってみます。

main(){#stop 0;while(Getc(0)!='q'){PrStr("HELLO WORLD\r\n";Sleep(100);}}
WS000169.png

一秒ごとに有名なメッセージを表示するプログラムです。 このプログラムを走らせて、加速度センサ表示ページを呼び出すと、やはりブラウザにページが表示されません。 さらに、コンソールの文字も出てこなくなりました。

ここで、コンソールから、 'q' をタイプすると、プログラムの実行が停止します。 ところが、ブラウザには、あのメッセージの断片が表示されます。 ここは、 "ad::adinit" 関数を呼び出している箇所です。 どうも、コンソール出力が埋め込まれているようです。

サーバ・プログラムを作成した

さて、いよいよ、実用的なプログラムを作成します。 最初は、単なる文字列入出力で対応できるのかと思っていたのですが、一筋縄ではいきませんでした。

main(){
  char ch;
  char *msg=MemoryAlloc(81);
  long index;
  #stop 0
  
  for (;;) {
    index=0;
    for(;;) {
      ch=Getc(1);
      if (ch=='\r') break;
      msg[index++]=ch;
      if (ch==0) break;
    }
    if (ch=='\r') break;
    PrStr("HELLO ");
    PrStr(msg);
    PrStr("\r\n");
    Pokeb(0x20007F2E,0);
  }
  MemoryFree(msg);
}

コンソールから入力された文字列の頭に"HELLO "を付けて返答する簡単なサーバ(?)です。

コンソールから入力された文字は、 msg 配列に格納されます。 配列の長さに関するエラー処理はしていませんので、ご注意ください。 入力文字列は、ヌル文字 (0) で終了します。 このため、この処理は Gets 関数で代用することが出来ず、 Getc で一文字ずつ処理しなくてはなりません。

コンソールから "Enter" が入力されたらプログラムは終了します。

応答には、 "PrStr" 関数を使っています。 全ての応答を出し終わったら、 0x20007F2E 番地の CgiFlag に 0 を書き込みます。 すると、 CGI 処理が応答を受け取って、 HTML 文書に応答を埋め込みます。


WS000170.png

このプログラムを実行している状態で、加速度センサ表示ページを呼び出すとCGIが受け取ったリクエストの先頭に "HELLO " が付加された文字列が表示されます。 この例では、あまり意味のある処理は行われていません。 通常の CGI 機能と異なっているのは、 SilentC のプログラムが実行されている状態なので、内部状態を保持しているということです。 何か、怪しいことが出来るかも。

参考文献

Interface (インターフェース) 2008年 12月号 [雑誌]

Interface (インターフェース) 2008年 12月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/10/25
  • メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentC のメモリ管理をあばく [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

以前、SilentC のメモリ管理をのぞくでヒープ領域の使い方について少しのぞいてみましたが、管理領域が何を意味しているのか判然としませんでした。 その後、すっきり謎が解けたのでレポートします。

ヒープは線形リストである

HeapMemoryAllocation.png

メモリ領域の概要は、この図のようになっています。 ヒープ領域の先頭は、 0x20007DBC 番地にある HeapStart というポインタで示されています。 ヒープ領域は、任意の個数のブロックに分割されており、それぞれのブロックの最初の4バイトに管理情報が入っています。

4バイトの管理情報の最初の2バイトは、そのブロックのサイズを4バイト単位で示しています。 ブロックは、隙間無く配置されるので、この2バイトは事実上次のブロックへの相対アドレスを示すポインタとして機能します。 そして、管理情報後半の2バイトは、そのブロックで実際に使用されている領域のサイズを4バイト単位で示しています。 このような構造になっているため、ひとつのブロックには、ひとつの使用中領域とひとつ以内の未使用領域が存在することになります。

ヒープ領域の最後のブロックの管理情報には、 0 が書き込まれていて、線形リストの終端をあらわしています。

メモリの割り当て状況を調べる

管理情報の意味が判ったので、ヒープ領域の割り当て領域と未使用領域の計算させるプログラムを作成しましょう。 と、思ったのですが、ヒープ領域は時々刻々と変化しており、しかも SilentC インタプリタ自身がヒープを使用して作業をしているので、 SilentC のプログラムでヒープを調べるわけにはいきません。 そこで、ユーザ・ドライバの出番となります。

#include "support_common.h" /* include peripheral declarations and more */
#include "silentmoon.h"

uint32 *HeapStart @ 0x20007DBC;

//
// VECTOR #0 : main
//
uint32 main(uint32 arg) {
  #pragma unused(arg)
  uint32 size;
  uint32 *result;
  uint32 *p;
  
  result = MemoryAlloc(16);
  MemClear(result, 16);
  // result[0] (nBlocks)   Number of memory blocks
  // result[1] (allocSize) Total allocated memory size
  // result[2] (usedSize)  Total used memory size
  asm { // Disable interrupts
    move.w        #0x2700,sr
  }
  p = HeapStart;
  while (*p) {
    result[0]++;
    size = (*p >> 16);
    result[1] += size * 4;
    result[2] += (*p & 0xFFFF) * 4;
    p += size;
  }
  asm { // Enable interrupts
    move.w        #0x2000,sr
  }
  return (uint32)result;
}

//
//  Jump table for vectors
//
__declspec(jump_table)
asm void jump_table(void) {
  jmp main      // Driver #0   
}

プログラムの実行中にヒープの割り当てや開放が行われると、管理情報に矛盾が生じるので、プログラム実行中は割り込みを禁止しています。

このユーザ・ドライバは、配列へのポインタを返します。 配列のそれぞれの要素には、ブロック数、総ブロック容量、総使用容量の三つの値が入っています。 また、この配列は、ユーザ・ドライバ自身が MemoryAlloc 関数で割り当てています。

この配列を受け取って、結果を表示させる作業は、 SilentC のプログラムにやらせます。

main() {
  long *result = UserDriver(0);
  PrStr("BLOCKS    =");PrNum(result[0]);PrStr("\r\n");
  PrStr("ALLOCATED =");PrNum(result[1]);PrStr("\r\n");
  PrStr("USED      =");PrNum(result[2]);PrStr("\r\n");
  MemoryFree(result);
}

ユーザ・ドライバ内で配列を割り当てているので、使い終わったら、開放してやります。 このプログラムを実行すると、それぞれの値が表示されます。

OK
run
BLOCKS    =29
ALLOCATED =11712
USED      =11304
OK

このプログラムを実行した時には、29個のブロックが11712バイトにわたって割り当てられていました。 そのうち、実際に使用されているのは、11304バイトで、差分の408バイトに管理情報と未使用領域が存在するというわけです。

OK
MemoryAlloc(1024)

OK
run
BLOCKS    =30
ALLOCATED =12740
USED      =12328
OK

ここで、1024バイトの領域を新たに割り当てると、1028バイトのブロックがひとつ割り当てられ、使用されている領域が1024バイト増加したことがわかります。

プロジェクト・アーカイブ

ユーザ・ドライバのプロジェクトは、これを CFCQ08.zip という名前で保存すると再現できます。

参考文献

Interface (インターフェース) 2008年 12月号 [雑誌]

Interface (インターフェース) 2008年 12月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/10/25
  • メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

ユーザ・ドライバに複数の引数を与える方法 [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

おおやけには、ユーザ・ドライバには、引数を一つしか渡すことが出来ないとされています。 何とか複数の引数を渡す方法は無いでしょうか。

スタック・フレームと引数構造体

CodeWarrior のコンパイラが生成するコードは、 A6 レジスタを使用して、スタック・フレームの管理を行っています。 たとえば、ユーザが記述したユーザ・ドライバ "uint32 driver(uint32 arg)" が呼び出されると、 "driver" 関数に与えられた引数とそこで使用される局所変数へは、 A6 レジスタを介してアクセスすることができます。

UserDriverStackFrame.png

また、スタック・フレームには、呼び出し元のスタック・フレームへのポインタも含まれています。 そのため、スタック・フレームをさかのぼると、次々と呼び出し元の関数のスタック・フレームにアクセスすることが出来ます。

また、 SilentC は、インタプリタなので、字句解析を行ったのち、該当する関数に相当するコンパイル済み関数を呼び出します。 たとえば、 "UserDriver" 関数の場合には、 "UserDriverSub" という関数が呼び出されます。 この呼び出しの際には、 SilentC の引数を表現した引数構造体へのポインタが渡されます。 そこで、この構造体を介して、ユーザ・ドライバに複数の引数を渡すことが出来ます。

UserDriver 関数の引数をコピーするプログラム

さっそく、プログラムを作って試してみます。 このプログラムは、 UserDriver 関数に与えられた引数構造体を 0x20007F80 からの空きエリアに単純にコピーするプログラムです。

#include "support_common.h" /* include peripheral declarations and more */
#include "silentmoon.h"

//
// Stack frame structure
//
struct stack_frame {
  struct stack_frame *invoker;
  void *return_value;
  uint32 args[0];
};

//
// SilentC's argument structure
//
struct arguments {
  uint32 mode;
  uint32 args[0];
};

//
// VECTOR #0 : main
//
uint32 main(uint32 arg) {
  #pragma unused(arg)
  struct stack_frame *this_frame;
  struct stack_frame *invoker_frame;
  struct arguments *silentc_args;
  uint32 *table = (uint32 *)0x20007f80;
  
  asm {
    move a6,this_frame
  }
  invoker_frame = this_frame->invoker;
  silentc_args = (struct arguments *)invoker_frame->args[0];
  BufCopy(table, silentc_args, 64);
  return (uint32)0;
}

//
//  Jump table for vectors
//
__declspec(jump_table)
asm void jump_table(void) {
  jmp main      // Driver #0   
}

書き込んで実行してみます。 9個の引数が、 0x20007F88 から 0x20007FA7 まで並んでいるのがわかります。

OK
UserDriver(0,1,2,3,4,5,6,7,8)

OK
m::d(0x20007f80)
20007f80  41 00 00 09 00 00 00 00  A.......
20007f88  00 00 00 01 00 00 00 02  ........
20007f90  00 00 00 03 00 00 00 04  ........
20007f98  00 00 00 05 00 00 00 06  ........
20007fa0  00 00 00 07 00 00 00 08  ........
20007fa8  00 00 6e e8 00 00 65 c0  ..n...e.
20007fb0  02 00 00 00 00 02 00 00  ........
20007fb8  00 00 00 00 00 00 00 00  ........

違う引数を与えると、こうなりました。 7個の引数が、 0x20007F88 から 0x20007F9F まで並んでいるのがわかります。

OK
UserDriver(0,256,257,258,259,260,261)

OK
m::d(0x20007f80)
20007f80  41 00 00 07 00 00 00 00  A.......
20007f88  00 00 01 00 00 00 01 01  ........
20007f90  00 00 01 02 00 00 01 03  ........
20007f98  00 00 01 04 00 00 01 05  ........
20007fa0  00 00 01 00 00 00 01 01  ........
20007fa8  00 00 01 02 00 00 01 03  ........
20007fb0  00 1d 00 07 00 00 00 06  ........
20007fb8  00 00 00 00 00 00 01 00  ........

この結果から、 "UserDriver" 関数の第二引数が args[1] に格納され、第三引数が args[2] に格納され、以下、順に格納されていることがわかります。 また、 mode の下8ビット、ダンプ・リストでいう所の 0x20007f83 番地には、 "UserDriver" 関数に与えられた引数の数も保存されています。 この仕掛けを使えば、可変長の引数をユーザ・ドライバに渡すことも出来ますね。

プロジェクト・アーカイブ

ユーザ・ドライバのプロジェクトは、これを CFCQ06.zip という名前で保存すると再現できます。

参考文献

Interface (インターフェース) 2008年 12月号 [雑誌]

Interface (インターフェース) 2008年 12月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/10/25
  • メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

I2CでLEDピカピカ ユーザ・ドライバ版 [ColdFire V2]このエントリーを含むはてなブックマーク#

2073158

ユーザ・ドライバが使えるようになったので、「I2CでLEDピカピカ」を移植してみました。

元記事

この記事の元になった記事は、以下の通りです。

メイン・プログラム

まずは、 SilentC で記述されたメイン・プログラムを変更しました。

main(){
  int  i;
  char *cmd = MemoryAlloc(4);
  UserDriver(0, 0); // i2c_init
  cmd[0] = 0x20; // address
  cmd[1] = 0x03; // command (DDR)
  cmd[2] = 0x00; // data
  UserDriver(1, cmd); // i2c_send
  #stop 0
  i=0;
  for(;;){
    if(Getc(0)=='q')break;
    Sleep(25);
    cmd[1] = 0x01; // command (DATA)
    cmd[2] = 0xEF << i; // data
    UserDriver(1, cmd); // i2c_send
    i = (i + 1) & 3;
  }
  MemoryFree(cmd);
}

ユーザ・ドライバとして、I2Cモジュールを操作するための二つのベクタを用意しています。

  • #0 - i2c_init : I2C モジュールを初期化します。
  • #1 - i2c_send : I2C モジュールにコマンドをデータを送ります。

コマンドを送るためのデータは、 char 型の配列として渡します。 配列には、I2Cデバイスのアドレス、デバイスへのコマンド、デバイスへのデータの3バイトのデータが格納されます。

ユーザ・ドライバの記述

このメイン・プログラムから呼び出すためのユーザ・ドライバは、こんな風になりました。

#include "support_common.h" /* include peripheral declarations and more */

uint32 i2c_init(uint32 arg);
uint32 i2c_send(uint32 arg);

asm void jump_table(void) {
  jmp i2c_init      // Driver #0   
  jmp i2c_send      // Driver #1   
}

asm void SystemSleep(void) {
  move 0x210,a0
  jmp (a0)
}

uint32 i2c_init(uint32 arg) {
  #pragma unused(arg)
  // Configure ports.
  MCF_GPIO_PQSPAR =
    MCF_GPIO_PQSPAR_QSPI_CLK_SCL |
    MCF_GPIO_PQSPAR_QSPI_CS0_SDA;
  // Specify I2C clock rate.
  MCF_I2C_I2FDR =
    MCF_I2C_I2FDR_IC(0x38); // (x640)
  // Configure I2C module
  MCF_I2C_I2CR =
    MCF_I2C_I2CR_MTX |  // Transmit
    MCF_I2C_I2CR_IEN;   // I2C enable
}

uint32 i2c_send(uint32 arg) {
  uint8  address = ((uint8*)arg)[0];
  uint8  command = ((uint8*)arg)[1];
  uint8  data    = ((uint8*)arg)[2];

  while (MCF_I2C_I2SR & MCF_I2C_I2SR_IBB) {
    SystemSleep(); // Wait for BUS idle
  }
  MCF_I2C_I2SR = 0x00; // Clear flags
  MCF_I2C_I2CR |= MCF_I2C_I2CR_MSTA; // START
  do {
    MCF_I2C_I2DR = (uint8)(address << 1); // WRITE for 0100000
    while (!(MCF_I2C_I2SR & MCF_I2C_I2SR_IIF)) {
      SystemSleep(); // Wait for IIF
    }
    if (MCF_I2C_I2SR & MCF_I2C_I2SR_RXAK) {
      break; // Quit if no ACK
    }
    MCF_I2C_I2SR = 0x00; // Clear flags
    MCF_I2C_I2DR = command; // command code
    while (!(MCF_I2C_I2SR & MCF_I2C_I2SR_IIF)) {
      SystemSleep(); // Wait for IIF
    }
    if (MCF_I2C_I2SR & MCF_I2C_I2SR_RXAK) {
      break; // Quit if no ACK
    }
    MCF_I2C_I2SR = 0x00; // Clear flags
    MCF_I2C_I2DR = data; // data to be sent
    while (!(MCF_I2C_I2SR & MCF_I2C_I2SR_IIF)) {
      SystemSleep(); // Wait for IIF
    }
  } while (0);
  MCF_I2C_I2CR &= ~MCF_I2C_I2CR_MSTA; // STOP
}

CodeWarrior 純正のシンボルを使っているので、かなり長ったらしくなりました。

ユーザ・ドライバから呼び出している "SystemSleep" 関数は、アセンブラで記述された分岐命令になっており、 SilentMoon オペレーティングシステムを呼び出します。 また、 I2C の通信は、機械語の実行に比べて長い時間を要します。 このため、通信終了待ちループでは "SystemSleep" 関数を呼び出して、他のスレッドに実行権を明け渡しています。

メイン・プログラムを実行すると、LEDが点滅を始めます。

プロジェクト・アーカイブ

ユーザ・ドライバのプロジェクトは、これを CFCQ05.zip という名前で保存すると再現できます。

参考文献

Interface (インターフェース) 2008年 12月号 [雑誌]

Interface (インターフェース) 2008年 12月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/10/25
  • メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

ユーザ・ドライバでLEDピカピカ [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

ユーザ・ドライバが書けるようになったので、応用プログラムを書いてみます。 題材は、もちろん、LEDピカピカです。

ユーザドライバで割り込みを使う

今回のプログラムのコンセプトは、例外ベクタを調べた (5)で取り上げた PIT1 割り込みを使った処理の移植です。 前回は、SilentCで全ての設定を行わせたため、機械語で書いた割り込みサービス・ルーチン (Interrrupt Service Routine) をRAMに書き込む部分までSilentCのプログラムで書きました。 今回は、すべての部分を CodeWarrior の C で記述します。

#include "support_common.h" /* include peripheral declarations and more */

//
//  Exception vector table on RAM
//
extern uint32 __VECTOR_RAM[];


//
//  Jump table for drivers.
//
uint32 init(uint32 arg);

asm void jump_table(void) {
  jmp init      // Driver #0   
}

//
//  PIT1 interrupt service routine
//
__declspec(interrupt) pit1_isr(void) {
  MCF_PIT1_PCSR |= MCF_PIT_PCSR_PIF;          // Clear PIF
  MCF_GPIO_PORTLD ^= MCF_GPIO_PORTLD_PORTLD1; // Toggle LNKLED
}

//
//  Driver #0
//    Initialize PIKA PIKA
//    arg  0 : Stop LED blinking
//         1 : Start LED blinking
//
uint32 init(uint32 arg){
  switch (arg) {
    case 1: // Enable PIKA
      // Prepare GPIO
      MCF_GPIO_PORTLD = 0x00; // PORTLD[1:0]=0
      MCF_GPIO_DDRLD  = 0x03; // DDRLD[1:0]=1
      MCF_GPIO_PLDPAR = 0x00; // PLDPAR[1:0]=0
      // Prepare INT
      __VECTOR_RAM[120] = (uint32)pit1_isr;         // Register ISR
      MCF_INTC0_IMRH &= ~MCF_INTC_IMRH_INT_MASK56;  // enable PITI
      MCF_INTC0_ICR56 = 0x08; // PRIORITY=0,LEVEL=1 
      // Prepare PIT
      MCF_PIT1_PMR    = 29297-1;  // Period count
      MCF_PIT1_PCSR   = 0x081F;   // Start PIT1
      return 1;
    case 0: // Disable PIKA
      MCF_PIT1_PCSR   = 0x0000;   // Stop PIT1
      return 0;
  }
  return (uint32)-1;
}

使用しているベクタは、#0のひとつだけです。 引数に 1 を指定すると、PIT1割り込みに必要な処理をすべて行い、呼び出し元に 1 を返します。 すると、割り込み処理によって、LEDは勝手に点滅を行います。 引数に 0 を指定すると、PIT1の動作を停止し、呼び出し元に 0 を返します。 割り込みイベントが発生しないので、LEDの点滅は止まります。 引数にそれ以外の値を指定すると、呼び出し元に -1 を返します。

RAM上にある割り込みベクタテーブルの場所は、リンカ命令ファイル (lcf) に書いてあります。 プログラムからは、 "__VECTOR_RAM" というシンボルとして、この場所を知ることが出来ます。 ここでは、32ビット幅、256要素の配列を示すシンボルとして宣言しています。

割り込みサービス・ルーチンには、関数の型宣言の代わりに "__declspec(interrurpt)" を付けます。 すると、通常の関数呼び出しは保存されないレジスタの退避・回復などが行われ、呼び出し元に戻る際には、 RTS (ReTurn from Subroutine) の代わりに RTE (ReTurn from Exception) が使われるようになります。

使ってみよう

コンパイル後、"COLDFIRE.BIN"を作成し、転送したら、準備完了です。

OK
?UserDriver(0,1)
1

基板上の LED3 が点滅を始めます。

OK
?UserDriver(0,0)
0

LED3 の点滅が止まります。

OK
?UserDriver(0,10)
-1

0 と 1 以外の引数は、受け付けられません。

参考文献

Interface (インターフェース) 2008年 12月号 [雑誌]

Interface (インターフェース) 2008年 12月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/10/25
  • メディア: 雑誌
Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

TELNET が勝手に切れちゃう [ColdFire V2]このエントリーを含むはてなブックマーク#

SOUND ONLY

付録基板を telnet で接続していて、しばらく放って置くと切れちゃう現象を確認しました。 10分ぐらい置いて、久しぶりにターミナル・ソフトをつつくと、telnetサーバが応答してくれなくて、ターミナル・ソフトがあきらめて落ちちゃいます。 それでも、再度ターミナル・ソフトで接続すると、ちゃんと接続できますので、 telnet サーバが止まっているわけでもなさそうです。

この現象、色々と条件があるようで、再現が難しいのですが、ゲートウェイの存在しない隔離されたLAN環境で作業をしていると発生するみたいです。 原因は、いまだ不明ですが、落とさないためには、たまにつついてやるのが良いようです。


Sレコードをバイナリに変換するプログラム [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

いよいよ、Sレコードからバイナリ・ファイル COLDFIRE.BIN を作成します。 あと、一歩だよ。

どこで誰がフォーマット変換するか

要するに、Sレコードを読み込んで、バイナリ・ファイルを作成してやれば良いのですが、この作業を誰にやらせるのかが、問題になります。 メモリが少ない環境で作成する場合には、Sレコードを一行ずつ呼んで、途中結果を逐一ファイルに書き出すことになると思います。 付録基板でプログラムを作成する場合にも、一行ずつ処理して、逐次FLASHに書き込むことになりますが、Sレコードの送受信でハンドシェイクが働くかどうかが心配です。

どうも、付録基板にやらせると、ハードルがいくつもありそうなので、まずは、PCでフォーマット変換する方法を考えました。 最近のPCで処理する場合には、Sレコードを全て読み込んで、主記憶上にメモリ・イメージを展開して、それを一気にファイルに書き出せば簡単です。 128kバイトのメモリなんて、十分小さいことでしょう。

じゃ~ん。フォーマット変換プログラム

というわけで、急ごしらえではありますが、フォーマット変換プログラムを作成しました。

size = 128*1024
offset = 128*1024
mem=[255 for x in xrange(0, size)]


def parse(line):
    if line[0] != 'S':
        return -1, []
    try:
        nibbles = [int(ch, 16) for ch in line[1:]]
    except ValueError:
        return -1, []
    type = nibbles[0]
    bytes = [(nibbles[i*2+1] * 16 + nibbles[i*2+2])
             for i in xrange(0, len(nibbles) / 2)
            ]
    if bytes[0] != len(bytes) - 1:
        return -1, []
    csum = sum(bytes) & 255
    if csum != 255:
        return -1, []
    return type, bytes[1:-1]

def type0(payload):
    print "".join([chr(ch) for ch in payload[2:]])

def write(address, data):
    address -= offset
    for x in data:
#        print "WRITE %02X to %08X" % (x, address)
        if address < 0:
            return False
        if address >= size:
            return False
        mem[address] = x
        address = address + 1
    return True

def type1(payload):
    address = payload[0] * 256 + payload[1]
    return write(address, payload[2:])
    
def type2(payload):
    address = (payload[0] * 256 + payload[1]) * 256 + payload[2]
    return write(address, payload[3:])

def type3(payload):
    address = ((payload[0] * 256 + payload[1]) * 256 + payload[2]) * 256 + payload[3]
    return write(address, payload[4:])

def type5(payload):
    return True

def type7(payload):
    address = ((payload[0] * 256 + payload[1]) * 256 + payload[2]) * 256 + payload[3]
    print "Entry Point=%08X" % address
    return True

def type8(payload):
    address = (payload[0] * 256 + payload[1]) * 256 + payload[2]
    print "Entry Point=%06X" % address
    return True

def type9(payload):
    address = payload[0] * 256 + payload[1]
    print "Entry Point=%04X" % address
    return True

def loadLine(line):
    if not line:
        return True
    (type, payload) = parse(line)
    if type == 0:
        return type0(payload)
    elif type == 1:
        return type1(payload)
    elif type == 2:
        return type2(payload)
    elif type == 3:
        return type3(payload)
    elif type == 5:
        return type5(payload)
    elif type == 7:
        return type7(payload)
    elif type == 8:
        return type8(payload)
    elif type == 9:
        return type9(payload)
    else:
        print "INVALID: %s\n" % line
        return False

def loadFile(fileName):
    mem=[255 for x in xrange(0, size)]
    f = open(fileName, 'r')
    for line in f.readlines():
        loadLine(line.strip('\n'))
    f.close()

def saveFile(fileName):
    f = open(fileName, 'wb')
    for ch in mem:
        f.write(chr(ch))
    f.close()
    

え~と、プログラミング言語は、どの環境でも実行できると思われる "Python" です。 クラスにすれば良かった。 条件分岐は連想を使えば良かった。 と、色々と突っ込みどころ満載ですので、どなたか適宜ブラッシュ・アップしてください。

このプログラムを smem.py という名前のファイルにして、 Python のコンソールから、手動で呼び出して使用します。

>>> import smem
>>> smem.loadFile("C:/Projects/CW/CFCQ03/bin/MCF52233_INTERNAL_FLASH.elf.S19")

Entry Point=00020000
>>> smem.saveFile("Z:/noritan/COLDFIRE.BIN")
>>>

読み出しているSレコードは、前回の記事で作成したファイルです。 読み出すファイルと書き込むファイルは、各自の環境に合わせてください。

付録基板にバイナリ・ファイルを送り込む

Sレコードには、100バイトほどのプログラムしか書かれていませんが、出来上がったバイナリ・ファイルは、もれなく128kバイトの大きさがあります。 これを tftp でターゲットの付録基板に送り込みます。 もちろん、前々回の記事で紹介したベクタ・テーブルが書き込まれている付録基板を使用してください。

Z:\noritan>tftp -i 192.168.1.105 put COLDFIRE.BIN
Transfer successful: 131072 bytes in 6 seconds, 21845 bytes/s

あっという間に転送完了です。 これで、ユーザ・ドライバが使用できるようになりました。

使ってみよう

今回作成したユーザ・ドライバは、引数の値に従って、三つのメッセージを返すという仕様でした。

OK
PrStr(UserDriver(0,0))
HELLO WORLD!!

OK
PrStr(UserDriver(0,1))
Nice to meet you.

OK
PrStr(UserDriver(0,2))
Good bye.

OK

いい感じです。 ユーザ・ドライバを入れ替えたくなったら、再度、バイナリ・ファイル COLDFIRE.BIN を送り込むだけで済みます。

今後の課題

たかが100バイトのプログラムに128kバイトのバイナリ・ファイルを作らせるなんて、もったいなさ過ぎます。 PCのプログラムが必要な所も、引っかかります。

そこで、次なる課題は、 SilentC のプログラムだけでSレコードを読み込ませて、 FLASH に書き込むことです。 さて、間に合うかな?

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

なぜか、これも。

Interface (インターフェース) 2008年 12月号 [雑誌]

Interface (インターフェース) 2008年 12月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/10/25
  • メディア: 雑誌


楽してユーザ・ドライバを開発する方法の探求 [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

前回の記事で、ユーザ・ドライバのためのベクタ・テーブルを書き込みました。 しかし、肝心のプログラム本体を書き込んでいないので、使えるのはヘルプ表示だけです。 どうやったら、楽してユーザ・ドライバが書けるかな?

CodeWarriorをインストールするの?

MCF52233などの"ColdFire V2"には、Interface誌に付いてきた「CodeWarrior for ColdFire」が使えることは想像に難くないと思います。 でも、あの巨大な"CodeWarrior"をもうひとつインストールするのは、気が引けます。

その理由は、HCS08を使った事があれば、"ColdeFire V1"の開発環境が含まれた「CodeWarrior for Microcontrollers」がインストールされているからです。 こいつが、 "ColdFire V2" のコンパイルぐらいはやってくれないかなと画策しました。 ところが、致命的な問題が判明しました。

ColdFire V1 の CPU は、 ColdFire V2 の CPU のスーパー・セットである。

MCF52233 の命令セットは、 ISA (Instruction Set Architecture) Rev.A と呼ばれています。 一方、 MCF51QE128 の命令セットは、 ISA Rev.C という MCF52233 よりも新しいものです。 つまり、「CodeWarrior for Microcontrollers」でコンパイルしたプログラムは、 MCF52233 ではサポートしていない命令セットを生成してくれるので、使用することが出来ません。 反対に MCF52233 のプログラムを 「CodeWarrior for Microcontrollers」 のデバッガなどで扱うことは可能です。 MCF51QE128 のコンパイル結果の方が洗練されているように見えたのは、こういう理由があったのか。

と、いう訳で、不本意ながら「CodeWarrior for ColdFire」をインストールしました。

必要の無いものは、削除、削除

まずは、MCF52233 をターゲットとして、新規プロジェクトを作成します。

項目設定
DeviceMCF52233 を選びます。
Connectionどれも使うつもりはないのだけど、P&E USB BDM Multilinkでも選んでおきますか。
Processor ExpertSilentC を使うのであれば、 Processor Expert を使える環境ではないので、 None を選びます。
Hardware Supportコンソールもシミュレーションも使う予定が無いので、 Minimal を選びます。
Compiler Optimization今は、がんばって最適化する必要も無いので Easy Debug を選びます。デバッガは使わないんだけどね。

新規プロジェクトには、三つのターゲットが定義されています。 このうち二つは必要ないので削除してしまいます。

ターゲット解説
CONSOLE_INTERNAL_RAMこれは、プログラムをRAM上において実行させる設定で、デバッグ用にコンソールも利用します。ユーザ・ドライバ向きではないので、削除してしまいます。
INTERNAL_RAMこれも、プログラムをRAM上において実行させる設定ですが、コンソールは使用しません。ユーザ・ドライバ向きではないので、削除してしまいます。
INTERNAL_FLASHプログラムをFLASHに置く、通常の組み込みプログラムを開発する設定です。ユーザ・ドライバの条件に最も近いので、これを使います。
WS000161.png

不要なターゲットは、"Targets"タブから右クリックで"Delete"を選び削除します。


新規プロジェクトには、ユーザ・ドライバの作成には必要の無いファイルがたくさん出てきてしまいます。 これも、不要なものは削除してしまいます。

不要ファイル解説
Sources/ board_sysinit.cパワーON時の初期設定ルーチンが含まれています。初期設定は、SilentCがやってくれるので、このファイルは不要です。
Sources/ uart_support.cUARTの最下層ルーチンが記述されています。使えるのかも知れませんが、今は使わないので削除してしまいます。
Sources/ exceptions.c例外ベクタ・テーブルと標準的な割り込みハンドラが定義されています。どちらもSilentCが提供してくれていますので、削除してしまいます。
Project Settings/ Startup Code/ startcf.cリセット後の初期設定を行うルーチンが記述されています。これもSilentCが提供してくれるので、削除します。
Project Settings/ Startup Code/ cfm.cプロテクション・キーなどが入ったFLASHのconfiguration領域の値が定義されています。ここは、ユーザ・ドライバが書くべき領域ではないので、ファイルも削除します。
WS000162.png

不要なファイルは、 "Files" タブから右クリックで "Remove" を選び削除します。 これらのファイルは、 IDE の表示からは消えてしまいますが、ファイルそのものは削除されないので、後で回復させることも出来ます。

とりあえず、Make

WS000163.png

ファイルを削除したら、とりあえず Make ボタンを押してみます。 すると、リンカ・エラーが発生します。

Link Error   : Linker command file error at line 45
File not found: exceptions.c

リンカ命令ファイル(Linker command file)が、削除してしまった "exceptions.c" ファイルを参照していたようです。 リンカ命令ファイルは、 "Project Settings/ Linker Files/ MCF52233_INTERNAL_FLASH.lcf" にあります。

WS000164.png

このファイルの45行目に問題の箇所がありました。

#	.vectors :
#	{
#		exceptions.c(.vectortable)
#		. = ALIGN (0x4); 
#	} > vectorrom

これは、一括してコメント・アウトしてしまいます。

Link Error   : Undefined : "_startup"

このエラーは、リセット後のプログラムカウンタの値として指定された"_startup"が見つからないというものです。

WS000166.png

これは、メニューの "Edit → INTERNAL_FLASH Settings..." ダイアログの "Linker → ColdFilre Linker" の "Entry Point" に書いてあります。 この項目は、削除してしまうと別のエラーが発生するので、とりあえず"_main"と入れておきます。

同じ場所にある "Force Active Symbols" に入っている "__vect,__cfm" も不要なので、削除します。

これで、エラー無く "Make" が出来るようになりました。

最初のユーザ・ドライバ

ここで、 "Make" していたのは、デフォルトの main 関数です。 このままでは、仕事をしてくれないので、ここで最初のユーザ・ドライバを "main.c" ファイルに書いてみます。

#include "support_common.h" /* include peripheral declarations and more */

uint32 main(uint32 arg);

asm void jump_table(void) {
  jmp main      // Driver #0   
}

uint32 main(uint32 arg) {
  char *msg;
  switch (arg) {
    case 0:
      msg = "HELLO WORLD!!\r\n";
      break;
    case 1:
      msg = "Nice to meet you.\r\n";
      break;
    default:
      msg = "Good bye.\r\n";
  }
  return (uint32)msg;
}

コメントは省いてあります。 また、条件付きでヘッダファイル stdio.h をインクルードする部分も削除してしまいました。

ユーザ・ドライバ0番は、与えられた引数に対応するメッセージ文字列を返す関数です。 "uint32" という型は、プログラムのどこにも宣言されていませんが、これは、インクルード・ファイル "support_common.h" のなかで宣言されています。 ユーザ・ドライバの規約では、 "uint32" 型の引数をひとつ受け取り、 "uint32" 型の値を返すことになっています。 このプログラムも無事に "Make" することが出来ます。

Sレコードを見てみよう

コンパイル後のコードは、プロジェクト・ディレクトリの "bin/ MCF52233_INTERNAL_FLASH.elf.S19" というファイルにSレコード形式で書き込まれます。

S0030000FC
S321000005004E560000202E00084A80670A0C8000000001670A601041F900000534C3
S3210000051C600E41F900000544600641F90000055820084E5E4E7551FC48454C4CC6
S321000005384F20574F524C4421210D0A004E69636520746F206D65657420796F7587
S315000005542E0D0A00476F6F64206279652E0D0A001E
S30900000564000000008D
S31D00000568000005642000040000000004000000000000000000000000E4
S70500000500F5

コンパイル後のコードは、0x00000500から書き込まれています。 本来は、0x00020000から書き込みたいので、後で修正します。 6行にわたる"S3"レコードのうち、最初の4行はプログラムと文字定数です。 残りの2行は、さて、何でしょう。

リンカ命令ファイルの修正

WS000164.png

プログラムの開始番地を変更するためには、リンカ命令ファイル(Linker command file)を変更します。 具体的には、 "Project Settings/ Linker Files/ MCF52233_INTERNAL_FLASH.lcf" を開いて、 "MEMORY" の宣言を変更します。

MEMORY {
   vectorrom   (RX)  : ORIGIN = 0x00000000, LENGTH = 0x00000400
   cfmprotrom  (RX)  : ORIGIN = 0x00000400, LENGTH = 0x00000020   
   code        (RX)  : ORIGIN = 0x00020000, LENGTH = 0x00020000
   vectorram   (RWX) : ORIGIN = 0x20000000, LENGTH = 0x00000400
   userram     (RWX) : ORIGIN = 0x20007F80, LENGTH = 0x00000080
}

変更箇所は、 "code" と "userram" の二つです。 これで、プログラムを配置すべき場所と作業領域が指定されます。

"code" のサイズを 64k バイトから 128kバイトに変更しました。 (2008-10-23 06:34)

次に以下の箇所をコメント・アウトまたは削除します。

#	.data : AT(___ROM_AT) 
#	{  
#		___DATA_RAM = .;
#		. = ALIGN(0x4);
#		*(.exception)	
#		. = ALIGN(0x4);	
#		__exception_table_start__ = .;
#		EXCEPTION
#		__exception_table_end__ = .;
#		
#		___sinit__ = .;
#	    STATICINIT
#		__START_DATA = .;
#
#		*(.data)
#		. = ALIGN (0x4);
#		__END_DATA = .;
#
#		__START_SDATA = .;
#		*(.sdata)
#		. = ALIGN (0x4);
#		__END_SDATA = .;
#
#		___DATA_END = .;
#		__SDA_BASE = .;
#		. = ALIGN (0x4);
#	} >> userram

これは、静的変数領域の初期状態を格納する場所の宣言です。 最後から2行目の"S3"レコードがこれに相当します。 さらに以下の箇所もコメント・アウトまたは削除します。

#	_romp_at = ___ROM_AT + SIZEOF(.data);
#	.romp : AT(_romp_at)
#	{
#		__S_romp = _romp_at;
#		WRITEW(___ROM_AT);
#		WRITEW(ADDR(.data));
#		WRITEW(SIZEOF(.data));
#		WRITEW(0);
#		WRITEW(0);
#		WRITEW(0);
#	}

これは、静的変数領域の場所と長さとその初期値を格納する場所の宣言です。 最後の"S3"レコードに相当します。

これらの静的変数領域の初期値情報を出さないようにしてしまうということは、静的変数は初期化されないということを示します。 通常のCのプログラムには、静的変数が初期化されていることを期待するプログラムもあるのですが、SilentCのユーザ・ドライバに限って言えば、静的変数は全く初期化されませんので、ユーザのプログラム内で明示的にクリアする必要があります。

リンカ命令ファイルには、他にも不要な記述があるのですが、今のところ、ワルサをする様子も無いので、そのままにしておきます。 ここまでの変更で、Sレコードファイルは、こうなりました。

S0030000FC
S321000200004E560000202E00084A80670A0C8000000001670A601041F900020034C9
S3210002001C600E41F900020044600641F90002005820084E5E4E7551FC48454C4CCF
S321000200384F20574F524C4421210D0A004E69636520746F206D65657420796F758A
S315000200542E0D0A00476F6F64206279652E0D0A0021
S70500020000F8

開始アドレスも 0x00020000 からに変更されて、純粋なプログラムだけのスリムな姿になりました。

ジャンプ・テーブルが無い

次にシンボル・ファイルを確認してみます。 シンボル・ファイルは、 "bin/ MCF52233_INTERNAL_FLASH.elf.xMAP" にあります。 FLASHへのプログラムの配置を見てみると、ジャンプ・テーブル(jump_table)がありません。

# .text
  00020000 00000034 .text   main	(main.c)
  00020034 00000010 .rodata @15	(main.c)
  00020044 00000014 .rodata @16	(main.c)
  00020058 0000000C .rodata @17	(main.c)
#>00020064          ___ROM_AT (linker command file)
#>00020064          ___DATA_ROM (linker command file)
WS000166.png

これは、メニューの "Edit → INTERNAL_FLASH Settings..." ダイアログの "Linker → ColdFilre Linker" の "Entry Point" を"_main"に書き換えてしまったのが原因です。 ここには、全てのプログラムをたどることが出来るシンボルを入れる必要があります。 このユーザ・ドライバの場合には、 "_main" に代えて "_jump_table" と入れます。 "Make" しなおすと、

# .text
  00020000 00000008 .text   jump_table	(main.c)
  00020008 00000034 .text   main	(main.c)
  0002003C 00000010 .rodata @15	(main.c)
  0002004C 00000014 .rodata @16	(main.c)
  00020060 0000000C .rodata @17	(main.c)
#>0002006C          ___ROM_AT (linker command file)
#>0002006C          ___DATA_ROM (linker command file)

"jump_table" が並びました。

この後、「引数の渡し方を変更する」という項目がありましたが、不必要かつ有害なステップであることが判明しました。 謹んで訂正いたします。

Sレコードをバイナリ・ファイルに変換するには?

あとは、Sレコードファイルをバイナリ・ファイル "COLDFIRE.BIN" に変換して、 tftp で送り込むだけです。 ところで、バイナリ・ファイルへの変換って、どうやってやるの?

乞うご期待。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

ユーザ・ドライバ領域にベクタを書き込む [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

SilentC のユーザ・ドライバ機能は、処理速度の遅いインタプリタを補うために必要です。 でも、 ColdFire にとって 4096 バイトの領域は決して十分とはいえません。 できれば、 0x00020000 からの 128k バイトの領域を優雅に使いたいところです。 いっそのこと、 0x00020000 からの領域へのベクタ・テーブルを書いて固定してしまおう。

SilentCだけで十分ですよ

"UserDriver" 領域に何か書き込むためには、バイナリ・ファイル "UserDriver.bin" を tftp で送り込んでおいて次回再起動の際に 0x00013000 からの領域へ SilentC に転記させるという方法が、公式とされています。 でも、バイナリ・ファイルを作らなきゃいけないし、ファイル・システムに 4096 バイトの空きが無ければ、ファイルを置くこともできないし。

今回書き込みたいのは、ベクタ・テーブルだけです。 このテーブルは、機械的な繰り返しで簡単に生成できるので、あらかじめ 4096 バイトのファイルを作る必要は無いと考えます。 そこで、 SilentC でベクタ・テーブルを書き込ませるプログラムを作成しました。

main(){
  long  *jump_table = 0x00013000;
  long  table_size  = 128;
  long  hook_table  = 0x00020000;

  long  index;
  long  *table_content;
  for (index = 0; index < table_size; index++) {
    if (jump_table[index] != -1) {
      PrStr("Jump Table not Erased\r\n");
      return;
    }
  }
  table_content = MemoryAlloc(table_size * 4);
  for (index = 0; index < table_size; index++) {
    table_content[index] = hook_table + index * 6;
  }
  table_content[127] = 0x00013200;
  NvmWrite(jump_table+0xFFF00000, table_content, table_size * 4);
  MemoryFree(table_content);
}

対応するベクタは、0から126の127種類です。 0x00020000 番地から6バイトずつ絶対番地 JMP 命令が並んでいるものとしてベクタを決めています。 また、ベクタ127だけは、 0x00013200 に分岐させて後述する別の用途で使用します。

00013000  0002 0000    dc.l  hook_table+6*0
00013004  0002 0006    dc.l  hook_table+6*1
00013008  0002 000C    dc.l  hook_table+6*2
0001300C  0002 0012    dc.l  hook_table+6*3
:
000131F4  0002 02EE    dc.l  hook_table+6*125
000131F8  0002 02F4    dc.l  hook_table+6*126
000131FC  0001 3200    dc.l  0x00013200
00013000  00 02 00 00 00 02 00 06  ........
00013008  00 02 00 0c 00 02 00 12  ........
00013010  00 02 00 18 00 02 00 1e  ........
00013018  00 02 00 24 00 02 00 2a  ...$...*
:
000131e0  00 02 02 d0 00 02 02 d6  ........
000131e8  00 02 02 dc 00 02 02 e2  ........
000131f0  00 02 02 e8 00 02 02 ee  ........
000131f8  00 02 02 f4 00 01 32 00  ......2.

ヘルプ表示機能

ベクタ・テーブルで消費したのは、512バイトです。 ユーザ・ドライバ領域は、 3.5k バイトも残っていますが、もともと固定して使うつもりでベクタ・テーブルを書いているので、プログラムを入れと本末転倒になってしまいます。 そこで、文字列形式のデータを入れておいて、その先頭アドレスをベクタ#127で返すようにしました。 プログラムは 0x00013200 から、データは 0x00013208 から入っています。

013200    203C 0001 3210   move.l  #0x00013210,d0
013206    4E75             rts

プログラムは、こうなりました。

main(){
  char  *help_module = 0x00013200;
  long  module_size  = 0x00000E00;
  long  program_size = 0x00000010;
  long  content_size = module_size-program_size;
  char  *help_file   = "help.txt"

  long  index;
  long  fp;
  int   *program;
  char  *content;
  long  actual_size;
  long  addr;

  for (index = 0; index < module_size; index++) {
    if (help_module[index] != -1) {
      PrStr("Help module not Erased\r\n");
      return;
    }
  }
  fp = OpenFile(help_file);
  if (fp == 0) {
    PrStr("Help file not found.\r\n");
    return;
  }
  program = MemoryAlloc(program_size);
  content = MemoryAlloc(content_size);
  MemClear(program, program_size);
  MemClear(content, content_size);
  program[0] = 0x203c; // move.l #xx,d0
  program[1] = help_module >> 16;
  program[2] = (help_module + program_size);
  program[3] = 0x4E75; // rts
  actual_size = ReadFile(fp, content, content_size-1);
  CloseFile(fp);
  actual_size = (actual_size + 4) & 0xFFFFFFFC;
  addr = help_module + 0xFFF00000;
  NvmWrite(addr, program, program_size);
  NvmWrite(addr + program_size, content, actual_size);
  MemoryFree(content);
  MemoryFree(program);
}

文字列データ本体は、 "help.txt" というファイルに入れておいて書き込むようにしました。 ファイルのサイズが3576バイト以内に収まるように注意してください。 また、行末を "CR+LF" にするのもお忘れなく。 本当は、ネットワーク経由で送るようにすると、ファイル領域も喰わないし、もっと良いのでしょうね。

さて、文字列データの中身ですが、何を入れましょうか。 0x00000200 からの領域には、 SilentMoon オペレーティング・システムのシステム・コールへの入り口アドレスが記述されています。 ここでは、システム・コールのアドレス一覧を返すようにしてみました。 以下、実行後のメモリの内容です。

00013200  20 3c 00 01 32 10 4e 75   <..2.Nu
00013208  00 00 00 00 00 00 00 00  ........
00013210  24 30 32 30 30 20 2d 20  $0200 -
00013218  4d 65 6d 6f 72 79 41 6c  MemoryAl
:
00013290  6d 65 72 0d 0a 24 30 32  mer..$02
00013298  31 43 20 2d 20 47 65 74  1C - Get
000132a0  54 69 6d 65 72 43 6f 75  TimerCou
000132a8  6e 74 0d 0a 00 00 00 00  nt......
000132b0  ff ff ff ff ff ff ff ff  ........

4バイト単位で必要なサイズだけ書き込んであります。

さあ、使ってみよう

早速、使ってみようと思いましたが、まだベクタ・テーブルを定義しただけで、肝心の中身がありません。 動くのは、ベクタ#127だけですね。

OK
PrStr(UserDriver(127))
$0200 - MemoryAlloc
$0204 - BufCopy
$0208 - MemClear
$020C - MemoryFree
$0210 - SystemSleep
$0214 - Sleep
$0218 - CreateTimer
$021C - GetTimerCount

OK

ヘルプ・テキストは、まだ未完成です。

ベクタ・テーブルを書き換えたくなったら

このプログラムは、ユーザ・ドライバ領域が消去された状態であることを確認して、書き込み作業を行っています。 そのため、ユーザ・ドライバ領域を書き換える時にはあらかじめ消去する必要があります。 消去するためには、こんな命令を使います。

NvmErase(0xFFF13000);
NvmErase(0xFFF13800);

プログラムに入れてしまっても良いのだけれど、うっかり走ってしまう事態を考えるとちょっと怖いので入れていません。 くれぐれもタイプミスにはご注意を。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

これも

Interface (インターフェース) 2008年 12月号 [雑誌]

Interface (インターフェース) 2008年 12月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/10/25
  • メディア: 雑誌

eval関数を実現する (2) [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

前回の記事では、プログラム・ファイルを作成する方法を考えました。 今回は、ファイルを作らずに済む方法を考えます。

タイマに実行させる

ファイルを書き換えるのではなく、もっと手軽な方法はないでしょうか。 「SilentC (Oct 10 2008)版」以降のアップデート版では、 "CreateTimer" 関数で func 引数に指定したスクリプトが動作するように変更されました。 この機能のスクリプトの実行部分だけを利用させてもらいます。

char done;
eval(char *func){
  char *command;
  char th;
  
  done=0;
  command=MemoryAlloc(StrLen(func)+32);
  StrCpy(command, "{");
  StrCat(command, func);
  StrCat(command, ";done=1;}");
  PrStr(command);PrStr("\r\n");
  th=CreateTimer(0,1,command);
  while (!done) {
    SystemSleep();
  }
  CreateTimer(th);
  MemoryFree(command);
}

このプログラムでは、スクリプトの実行が終了したことをグローバル変数 "done" でメインルーチンに知らせています。

OK
Main::eval("PrStr(\"HELLO WORLD\\r\\n\");")
{PrStr("HELLO WORLD\r\n");;done=1;}
HELLO WORLD

OK
Main::eval("PrStr(\"HELLO WORLD\\r\\n\");")
{PrStr("HELLO WORLD\r\n");;done=1;}
HELLO WORLD

OK

タイマに渡したコマンドは、 "eval" 関数で渡したスクリプトに "done" をセットするステートメントを加えたものです。

何度か実行した限りは、うまく動いているように見えますが、タイマは周期的にスクリプトを実行しようとしますので、メインルーチンが "done" を検出する前にスクリプトの二回目の実行が動いてしまう可能性もあるので、確実に一回だけ実行するためには、タイマに渡すコマンドに "done" がクリアされているときに限りプログラムを実行する仕掛けを入れなくてはならないでしょう。

タイマを使わなくても

スクリプトつき "CreateTimer" 関数でやっているのは、実行すべきスクリプトの保存と内部フラグを立てるコールバックを持ったタイマの起動の二つです。 だったら、スクリプトを保存して、すぐに内部フラグを立てたら、スクリプトが実行されるんじゃないかな。 というわけで、プログラムにしてみました。

long *LibTimerFunc  =0x20007F6A;
char *LibTimerFlag  =0x20007F7A;
eval(char *func){
  char *command;
  
  command=MemoryAlloc(StrLen(func)+32);
  StrCpy(command, "{");
  StrCat(command, func);
  StrCat(command, "}");
  PrStr(command);PrStr("\r\n");
  LibTimerFlag[3]=0;
  LibTimerFunc[3]=command;
  LibTimerFlag[3]=1; // Invoke
  while (LibTimerFlag[3]) {
    SystemSleep();
  }
  MemoryFree(command);
}

スクリプト付きタイマは、最大4個まで対応しています。 ここでは、その最後のエントリである3番目を借りました。 "LibTimerFunc" の三番目のエントリにスクリプトを格納し、 "LibTimerFlag" の三番目のエントリを1にセットしてトリガをかけると、スクリプトの実行が始まります。

OK
Main::eval("PrStr(\"HELLO WORLD\\r\\n\");")
{PrStr("HELLO WORLD\r\n");}
HELLO WORLD

OK
Main::eval("PrStr(\"HELLO WORLD\\r\\n\");")
{PrStr("HELLO WORLD\r\n");}
HELLO WORLD

OK

お、何だかうまく動いているみたいです。 これなら、ファイルも使わないので、安心して使えますね。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

eval関数を実現する (1) [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

SilentCは、インタプリタなので、逐次、字句解析を行って処理を行っています。 このため、プログラム実行中に作成した文字列を実行する事だって出来そうです。 あるいは、ネットワークを通じて、スクリプトを送り込むこともできます。 こんな事のできる、「eval関数が欲しい」な。 何だか、危険な香りがするぞ。

CGIを利用する

SilentC のhttpサーバには、 CGI と呼ばれる機能があり、HTML文書に埋め込まれたスクリプトを実行することができます。 外部からHTML文書を入れ替えて、そのHTML文書を呼び出せば、スクリプトが実行できます。

これと同等の機能を備えた関数をSilentCから呼び出すことが出来ます。

ReplaceCgi関数を使う

"ReplaceCgi"関数は、HTML文書に埋め込んだCGI表現を置き換えるために使われる関数です。 これを使って、実行すべき関数を外部から指定するプログラムを作成しました。

char *message;
main(char *func){
  char *buf;
  if (message==0) {
    message=MemoryAlloc(256);
  }
  buf=MemoryAlloc(256);
  MemClear(buf,256);
  StrCpy(buf,"$EXEC'Main::");
  StrCat(buf,func);
  StrCat(buf,"()'");
  PrStr(buf);PrStr("\r\n");
  ReplaceCgi(buf,255);
  PrStr(message);
  PrStr(buf);PrStr("\r\n");
  MemoryFree(buf);
}
a(){StrCpy(message,"HELLO");}
b(){StrCpy(message,"GOOD BYE");}

実行してみると、

OK
Main::main("a")
$EXEC'Main::a()'

どこかに飛んでいってしまいました。 グローバル変数を使用したことに問題があるのかと思いましたが、別の方法で実行結果を受け取るようにしても、飛んでいってしまいました。 原因は、不明ですが、別の方法を考えましょう。

プログラム・ファイルを入れ替えちゃえ

SilentCで実行しているプログラムは、メモリ上に展開されたソースコードではなく、ファイルシステム上のファイルということになっています。 そのため、あるファイルのある関数をメインプログラムから呼び出す前に、呼び出されるファイルを入れ替えちゃえば実行すべきプログラムを書き換えるのは簡単です。

eval(char *func){
  char *filename="f";
  
  DeleteFile(filename);
  CreateFile(filename);
  WriteFile("run(){",6);
  WriteFile(func, StrLen(func));
  WriteFile("}\r\n",3);
  CloseFile(0);
  f::run();
}

このサンプルでは、実行するスクリプトをeval関数に渡します。

OK
Main::eval("PrStr(\"HELLO WORLD\\r\\n\");")
HELLO WORLD

OK
type f
run(){PrStr("HELLO WORLD\r\n");}
OK

ファイル"f"にプログラムが書き込まれているのが分かります。 この方法の難点は、"eval"の実行のたびにファイル、つまりFLASHに書き込みが発生してしまうことです。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

TCPサーバは、無事に終了できるのか [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

付録基板同士のクライアント・サーバ・システムで作成したサーバは、最後に Listen ソケット(親ソケット、ルート・ソケット、何て呼べばいいんだ?)をクローズするようにはなっていましたが、無限ループでリクエストを待っているので事実上クローズはされていませんでした。 某所で、「Listen ソケットをクローズすると SilentC が沈黙する」との話題がありましたので、検証してみます。

停止機能付きサーバプログラム

今回作成したサーバ・プログラムは、TCPでリクエストを受け付けて、コネクション終了後に処理を行うインタプリタです。

main() {
  char *recv_buf;
  char listen,soc;
  long stat;
  recv_buf = MemoryAlloc(256);
  do {
    listen = CreateSocket(1);
    if (listen < 0) {
      PrStr("Failed to open LISTEN\r\n");
      break;
    }
    PrStr("Listen socket=");PrNum(listen);PrStr("\r\n");
    Bind(listen, 30049, 1);
    for (;;) {
      GetNetLine(0,0,0,0);
      soc = Accept(listen, -1);
      if (soc < 0) break;
      do {
        stat = GetNetLine(soc, recv_buf, 255, -1);
        if (stat < 0) {
          recv_buf[0] = '\0';
        }
      } while (0);
      CloseSocket(soc);
      GetNetLine(0,0,0,1);
      if (StrStr(recv_buf, "hello") != 0) {
        PrStr("HELLO\r\n");
      } else if (StrStr(recv_buf, "bye") != 0) {
        PrStr("BYE BYE\r\n");
        break;
      } else {
        PrStr("What ?\r\n");
      }
    }
    CloseSocket(listen);
  } while (0);
  MemoryFree(recv_buf);
}

通信文に"hello"が含まれていたら"HELLO"とコンソールに表示し、"bye"が含まれていたら"BYE BYE"と表示しLISTENソケットを閉じます。 それ以外は"What ?"と表示します。 こういう構成なので、 telnet でお話できます。

telnetで話しかけたら

プログラムが準備できました。 ColdFire でプログラムを実行しておいて、telnetで"hello"を送信します。 続いて、"bye"を送信すると、

OK
run
Listen socket=4
HELLO
BYE BYE

げっ、確かに沈黙してしまいました。

どこで止まっているのだろう

"CloseSocket(listen)"をコメントアウトすると、プログラム自体は正常に終了しますが、ソケットが閉じられていないので、?CreateSocket(0)と打ち込むと新しいソケットが割り当てられます。 また、再度実行すると沈黙してしまいます。 どうやら、CloseSocket関数で止まっているのは間違いなさそうです。

深追いしたところ、"LISTEN"状態からソケットを閉じる状態遷移が定義されていないように見えます。 "LISTEN"から"CLOSED"へのアーク(弧)が定義されていないので、ソケットを閉じるための条件である"FIN"の到着を待っているのだけどそんなものは来ない。 といった所でしょうか。

コンテストの〆切が間近なのに、解析作業ばっかりで、作品に取りかかれないよ。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

外部ネットワークのIPアドレスはARPテーブルに登録されるか [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

ARPテーブルには、外部ネットワークのIPアドレスも並ぶのか、調べました。

HTTPクライアントを作る

ARPテーブルは、IPアドレスとMACアドレスを関連付ける表です。 そのため、ルータの向こう側にある外部ネットワークのIPアドレスとルータのMACアドレスが関連付けられてもおかしくないはずです。 もし、外部ネットワークのIPアドレスがARPテーブルに登録されるとしたら、ARPテーブルはあっという間にパンクするでしょう。 外部ネットワークがARPテーブルに登録されるかどうか、確認する必要があります。

これを確認するためには、付録基板から外部ネットワークに接続するクライアントが必要になります。 ここは、ひとつ、HTTPクライアントでもつくってみますか。 以下のプログラムを"http"というファイルとして保存しました。

get(char *name){
  char soc;
  long stat;
  long server;
  char *command;
  long n;
  char *getBase="GET / HTTP/1.0\r\nHost:";
  char *buf;
  
  buf=MemoryAlloc(256);
  command=MemoryAlloc(StrLen(getBase)+StrLen(name)+16);
  StrCpy(command, getBase);
  StrCat(command, name);
  StrCat(command, "\r\n\r\n");
  do {
    server=GetHostByName(name);
    if (server==0){PrStr("%% Server?\r\n");break;}
    GetNetLine(0,0,0,0);
    soc=CreateSocket(1);
    if (soc<0){PrStr("%% Soc?\r\n");break;}
    do {
      PrStr("%% Connect HTTP server ");PrStr(name);
      PrStr("\r\n");
      stat=Connect(soc,server,80);
      if (stat<0){PrStr("%% HTTP?\r\n");break;}
      PrStr("%% GET request\r\n");
      stat=Write(soc,command,StrLen(command));
      if (stat<0) break;
      stat=WaitWriteComplete(soc);
      if (stat<0) break;
      for(n=0;n<15;n++){
        stat=GetNetLine(soc,buf,255,100);
        if (stat<0) break;
        PrStr(buf);PrStr("\r\n");
      }
    } while (0);
    PrStr("%% Close\r\n");
    CloseSocket(soc);
    GetNetLine(0,0,0,1);
  } while (0);
  MemoryFree(command);
  MemoryFree(buf);
}

このプログラムの原案は、masatoさんの記事にあったHTTPサーバです。 引数で与えられたホストのHTTPサーバに接続し、ルート・ページの最初から15行を表示します。

ネットワーク内で確かめた

最初は、ネットワーク内のHTTPサーバに接続します。 ターゲットは、別の付録基板です。

arp::show
192.168.1.102  00-1f-e8-49-**-76
192.168.1.1  00-15-c5-5b-**-38

OK
http::get("192.168.1.105")
%% Connect HTTP server 192.168.1.105
%% GET request
HTTP/1.0 200 OK
Content-Type: text/html
Content-Length: 409

<HTML>
<HEAD>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<TITLE>トップページ</TITLE></HEAD>
<BODY><CENTER>
<H1><FONT size="6" face="Arial Black">インターフェース ColdFireボード</FONT></H1>
<BR><H2><A href="netconfig.htm">ネットワーク設定</A></H2>
<BR><H2><A href="accel.htm">加速度センサー</A></H2>
<BR><H2><A href="port.htm">ポート操作</A></H2>
</CENTER></BODY>
%% Close

OK
arp::show
192.168.1.102  00-1f-e8-49-**-76
192.168.1.1  00-15-c5-5b-**-38
192.168.1.105  00-1f-e8-49-**-bf

OK

"192.168.1.1"は、コンソール兼ルータとして使っているPCです。 HTTPサーバに接続すると"192.168.1.105"のエントリが追加されたのがわかります。

外部ネットワークで確かめた

それでは、いよいよ外部ネットワークに接続します。 ターゲットは、"www.yahoo.com"にお願いしました。

http::get("www.yahoo.com")
%% Connect HTTP server www.yahoo.com
%% GET request
HTTP/1.1 200 OK
Date: Thu, 09 Oct 2008 13:22:50 GMT
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE LOC GOV"
Cache-Control: private
Vary: User-Agent
X-XRDS-Location: http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
Last-Modified: Thu, 09 Oct 2008 13:20:34 GMT
Accept-Ranges: bytes
Content-Length: 9554
Connection: close
Content-Type: text/html; charset=utf-8

<html>
<head>
<title>Yahoo!</title>
%% Close

OK
arp::show
192.168.1.102  00-1f-e8-49-**-76
192.168.1.1  00-15-c5-5b-**-38

OK

ご覧のように、ARPテーブルには、"www.yahoo.com"のエントリは追加されていません。 油断していたら、"192.168.1.105"のエントリも削除されてしまっていました。

今日の考察

  1. 外部ネットワークに接続してもARPテーブルには記録されない。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

(続)付録基板が発行するシーケンス番号を調べた [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

前回の記事で衝突が確認されたシーケンス番号を取りなおしました。

シーケンス番号一覧

着目したのは、3ウェイ・ハンドシェイクの二つ目のサーバから送られた"[SYN, ACK]"パケットです。

コネクション番号 サーバ側シーケンス番号(Seq) クライアント側シーケンス番号(Ack)
00x0115A0070x0109263A
10x011C00070x011C050A
20x012260020x01226506
30x0128D9200x0128C50A
40x012F20020x012F2506
50x013599190x0135850A
60x013BF91C0x013BE506
70x014259190x0142450A
80x0148B9160x0148A50A
90x014F19140x014F050B

サーバ基板とクライアント基板は、電源分配器につないで同時に電源を投入したため、シーケンス番号の初期値が近い値を示していることがわかります。 また、これらの初期値は、時間を追うごとに値が増えていきます。

何をしたのかは、今はナイショ。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

ARPテーブルを探求する [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

そろそろ、複数の付録基板同士で通信をさせてみたくなりました。 付録基板もネットワーク機器としての当然の機能としてARP (Address Resolution Protocol) テーブルを持っています。 ちょっと、のぞいてみましょう。

ARPテーブル表示プログラム

ARPテーブルは、0x20000440番地から6エントリ分あります。 もちろん、ダンプ・リストで見ることもできますが、せっかくだから表示させるプログラムを作りました。

show(){
  long i, j, *plong;
  char *p;
  p=0x20000440;
  for(i=0;i<6;i++,p+=16){
    if (p[0]==0) continue;
    plong=p+12;
    PrAdrs(*plong);PrStr("  ");
    for(j=0;j<6;j++){
      if(j>0) PrChar('-');
      PrHexByte(p[j+4]);
    }
    PrStr("\r\n");
  }
}

これを"arp"という名前のファイルとして保存して使います。

OK
arp::show
192.168.1.106  00-1f-e8-49-**-d4
192.168.1.2  00-15-c5-5b-**-38

OK

桁そろえ関数は、装備されていないんだよな。

複数のホストにUDPパケットを送るプログラム

ここからが、本日の本題です。 一枚の付録基板から複数の付録基板およびPCにUDPパケットを送ります。 ひとつのプログラムで送り先の数を可変できるようにプログラムを工夫してみました。

main(int n){
  arp::show();
  PrStr("\r\n");
  switch(n){
    case 7:  send("192.168.1.10");
    case 6:  send("192.168.1.102");
    case 5:  send("192.168.1.103");
    case 4:  send("192.168.1.104");
    case 3:  send("192.168.1.105");
    case 2:  send("192.168.1.106");
    default: send("192.168.1.2");
  }
  PrStr("\r\n");
  arp::show();
}
send(char *host){
  char soc, err_code;
  long ip;
  
  soc=CreateSocket(0);
  ip=GetHostByName(host);
  err_code=SendTo(soc, ip, 30049, host, StrLen(host)+1);
  CloseSocket(soc);
  PrStr(host);PrStr(" : err_code=");
  PrNum(err_code);PrStr("\r\n");
}

最後のIPアドレス以外は、すべて付録基板です。 受け側のポートは開いていないので、送りっぱなしになります。

6個のホストにパケットを送る

自分自身(192.168.1.106)も含めて、6個のホストに対してパケットを送ります。

Main::main(6)
192.168.1.106  00-1f-e8-49-**-d4

192.168.1.102 : err_code=0
192.168.1.103 : err_code=0
192.168.1.104 : err_code=0
192.168.1.105 : err_code=0
192.168.1.106 : err_code=22
192.168.1.2 : err_code=0

192.168.1.106  00-1f-e8-49-**-d4
192.168.1.102  ff-ff-ff-ff-ff-ff
192.168.1.103  00-1f-e8-49-**-79
192.168.1.104  00-1f-e8-49-**-e8
192.168.1.105  00-1f-e8-49-**-bf
192.168.1.2  00-15-c5-5b-**-38

OK

最初は、ARPテーブルには、自分自身だけが登録されてるので、その他のホストに対してはARP要求だけが発信されます。 このため、これらのホストについては、"err_code=0"が返されます。 パケット送信後は、ほとんどのホストのMACアドレスについては解決していますが、"192.168.1.102"だけは応答が返ってきていないらしく、暫定値ff-ff-ff-ff-ff-ffが入っています。 パケット・モニタで見ると、こうなりました。

#1	0.000000	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.103?  Tell 192.168.1.106
#2	0.000017	Kurusuga_49:**:79	Kurusuga_49:**:d4	ARP	192.168.1.103 is at 00:1f:e8:49:**:79
#3	0.013762	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.104?  Tell 192.168.1.106
#4	0.013820	Kurusuga_49:**:e8	Kurusuga_49:**:d4	ARP	192.168.1.104 is at 00:1f:e8:49:**:e8
#5	0.027552	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.105?  Tell 192.168.1.106
#6	0.027649	Kurusuga_49:**:bf	Kurusuga_49:**:d4	ARP	192.168.1.105 is at 00:1f:e8:49:**:bf
#7	0.041418	192.168.1.106	192.168.1.106	UDP	Source port: 1024  Destination port: 30049
#8	0.055215	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.2?  Tell 192.168.1.106
#9	0.055231	Dell_5b:**:38	Kurusuga_49:**:d4	ARP	192.168.1.2 is at 00:15:c5:5b:**:38
#10	2.157876	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.102?  Tell 192.168.1.106
#11	2.157917	Kurusuga_49:**:76	Kurusuga_49:**:d4	ARP	192.168.1.102 is at 00:1f:e8:49:**:76

プログラム上、"192.168.1.102"が一番にパケットの送信を要求したはずなのですが、これを見るとARP要求は後回しにされちゃったのですね。

Main::main(6)
192.168.1.106  00-1f-e8-49-**-d4
192.168.1.102  00-1f-e8-49-**-76
192.168.1.103  00-1f-e8-49-**-79
192.168.1.104  00-1f-e8-49-**-e8
192.168.1.105  00-1f-e8-49-**-bf
192.168.1.2  00-15-c5-5b-**-38

192.168.1.102 : err_code=22
192.168.1.103 : err_code=22
192.168.1.104 : err_code=22
192.168.1.105 : err_code=22
192.168.1.106 : err_code=22
192.168.1.2 : err_code=20

192.168.1.106  00-1f-e8-49-**-d4
192.168.1.102  00-1f-e8-49-**-76
192.168.1.103  00-1f-e8-49-**-79
192.168.1.104  00-1f-e8-49-**-e8

192.168.1.105  00-1f-e8-49-**-bf
192.168.1.2  00-15-c5-5b-**-38

OK

二回目の実行は、全てのホストのMACアドレスが解決しているので、すんなりと実行されます。

#12	11.107054	192.168.1.106	192.168.1.102	UDP	Source port: 1024  Destination port: 30049
#13	11.120758	192.168.1.106	192.168.1.103	UDP	Source port: 1024  Destination port: 30049
#14	11.134493	192.168.1.106	192.168.1.104	UDP	Source port: 1024  Destination port: 30049
#15	11.148224	192.168.1.106	192.168.1.105	UDP	Source port: 1024  Destination port: 30049
#16	11.162056	192.168.1.106	192.168.1.106	UDP	Source port: 1024  Destination port: 30049
#17	11.175863	192.168.1.106	192.168.1.2	UDP	Source port: 1024  Destination port: 30049

パケットの送受信もきれいです。 三回目の実行も同様でしたので、ここでは省略します。

7個のホストにパケットを送る

さらにホストの数を7個に増やします。

Main::main(7)
192.168.1.106  00-1f-e8-49-**-d4
192.168.1.102  00-1f-e8-49-**-76
192.168.1.103  00-1f-e8-49-**-79
192.168.1.104  00-1f-e8-49-**-e8
192.168.1.105  00-1f-e8-49-**-bf
192.168.1.2  00-15-c5-5b-**-38

192.168.1.10 : err_code=0
192.168.1.102 : err_code=22
192.168.1.103 : err_code=0
192.168.1.104 : err_code=0
192.168.1.105 : err_code=0
192.168.1.106 : err_code=22
192.168.1.2 : err_code=20

192.168.1.106  00-1f-e8-49-**-d4
192.168.1.105  00-1f-e8-49-**-bf
192.168.1.103  00-1f-e8-49-**-79
192.168.1.10  00-1f-e8-49-**-2d
192.168.1.104  00-1f-e8-49-**-e8
192.168.1.2  00-15-c5-5b-**-38

OK

7個中4個について失敗したという実行結果ですが、実際にはそれだけではありません。 "192.168.1.10"への送信時に長時間待たされてしまいました。

#24	61.166842	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.2?  Tell 192.168.1.106
#25	61.166860	Dell_5b:**:38	Kurusuga_49:**:d4	ARP	192.168.1.2 is at 00:15:c5:5b:**:38
#26	61.166874	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.10?  Tell 192.168.1.106
#27	61.166935	Kurusuga_49:**:2d	Kurusuga_49:**:d4	ARP	192.168.1.10 is at 00:1f:e8:49:**:2d
#28	61.180441	192.168.1.106	192.168.1.102	UDP	Source port: 1024  Destination port: 30049
#29	61.194215	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.103?  Tell 192.168.1.106
#30	61.194231	Kurusuga_49:**:79	Kurusuga_49:**:d4	ARP	192.168.1.103 is at 00:1f:e8:49:**:79
#31	61.208001	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.104?  Tell 192.168.1.106
#32	61.208016	Kurusuga_49:**:e8	Kurusuga_49:**:d4	ARP	192.168.1.104 is at 00:1f:e8:49:**:e8
#33	64.167279	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.105?  Tell 192.168.1.106
#34	64.167296	Kurusuga_49:**:bf	Kurusuga_49:**:d4	ARP	192.168.1.105 is at 00:1f:e8:49:**:bf
#35	64.181126	192.168.1.106	192.168.1.106	UDP	Source port: 1024  Destination port: 30049
#36	64.194973	192.168.1.106	192.168.1.2	UDP	Source port: 1024  Destination port: 30049

パケット・モニタで見ると原因が分かりました。 ARP要求が発行されずに待ち時間が発生したようです。 どうやら、ARPテーブルに空きが無くなった場合、古いエントリを削除するのではなく、いずれかのエントリが時間切れになるのを待っているようです。 有効期限は実験的に60秒と判断しました。

Main::main(7)
192.168.1.106  00-1f-e8-49-**-d4
192.168.1.105  00-1f-e8-49-**-bf
192.168.1.103  00-1f-e8-49-**-79
192.168.1.10  00-1f-e8-49-**-2d
192.168.1.104  00-1f-e8-49-**-e8
192.168.1.2  00-15-c5-5b-**-38

192.168.1.10 : err_code=21
192.168.1.102 : err_code=0
192.168.1.103 : err_code=0
192.168.1.104 : err_code=0
192.168.1.105 : err_code=22
192.168.1.106 : err_code=22
192.168.1.2 : err_code=20

192.168.1.106  00-1f-e8-49-**-d4
192.168.1.105  00-1f-e8-49-**-bf
192.168.1.103  00-1f-e8-49-**-79
192.168.1.102  ff-ff-ff-ff-ff-ff
192.168.1.104  00-1f-e8-49-**-e8
192.168.1.2  00-15-c5-5b-**-38

二回目の実行でも待ち時間が発生しました。

#37	81.911598	192.168.1.106	192.168.1.10	UDP	Source port: 1024  Destination port: 30049
#38	122.175817	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.2?  Tell 192.168.1.106
#39	122.175834	Dell_5b:**:38	Kurusuga_49:**:d4	ARP	192.168.1.2 is at 00:15:c5:5b:**:38
#40	122.175848	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.102?  Tell 192.168.1.106
#41	122.175957	Kurusuga_49:**:76	Kurusuga_49:**:d4	ARP	192.168.1.102 is at 00:1f:e8:49:**:76
#42	122.189641	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.103?  Tell 192.168.1.106
#43	122.189730	Kurusuga_49:**:79	Kurusuga_49:**:d4	ARP	192.168.1.103 is at 00:1f:e8:49:**:79
#44	122.203479	Kurusuga_49:**:d4	Broadcast	ARP	Who has 192.168.1.104?  Tell 192.168.1.106
#45	122.203496	Kurusuga_49:**:e8	Kurusuga_49:**:d4	ARP	192.168.1.104 is at 00:1f:e8:49:**:e8
#46	122.217283	192.168.1.106	192.168.1.105	UDP	Source port: 1024  Destination port: 30049
#47	122.231076	192.168.1.106	192.168.1.106	UDP	Source port: 1024  Destination port: 30049
#48	122.244911	192.168.1.106	192.168.1.2	UDP	Source port: 1024  Destination port: 30049

#38のARP要求がいずれかのエントリ(この場合は、192.168.1.10)が時間切れになるまで待たされたようです。

本日の考察

  1. 付録基板のARPテーブル長は、自身も含めて6エントリである。
  2. ARPテーブルが足りなくなったら、いずれかのエントリの有効期限が切れて欠員が出るまで待つ。

いわゆる"arp"コマンドのように、SilentCから積極的にARPテーブルを操作することは、できないようです。 私が考える最終形態は、付録基板7枚とPCという構成だから、6エントリじゃ足りません。 ネットワークを工夫するしかないかな。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

SilentC で作った乱数生成プログラム [ColdFire V2]このエントリーを含むはてなブックマーク#

main(){
  long seed=0x5180009F;
  long i;
  for (i=0;i<20;i++){
    seed+=102;
    seed = ((seed << 8) ^ 0x31415926) + 0x65358979;
    if (seed < 0) seed = ~seed;
    PrHex(seed); PrStr("\r\n");
  }
}

102は、魔法の数字です。

OK
run
1675e59f
56231a60
7791289f
059de59f
11dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
52dce59f
OK

あれっ?


付録基板が発行するシーケンス番号を調べた [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

前回実験したクライアント・サーバ・システムのパケットで、不可解な[TCP Retransmission]がレポートされていました。 今回は、その原因を探ります。

シーケンス番号一覧

前回は簡易レポートしか見ていませんでしたが、今回はパケットの中身まで深く調べました。 その結果、使い回しされていたのがポート番号だけではなかったことが判明してしまいました。

#7	2.014995	1024 > 30049 [SYN] Seq=0 Win=1454 Len=0
#8	2.015247	30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0
#9	2.015419	1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0

着目したのは、3ウェイ・ハンドシェイクの二つ目のサーバから送られたパケットです。 この簡易レポートでは、"Seq=0 Ack=1"となっていますが、これらの値は、相対値でしかありません。 そのため、これらの絶対値を表にしてみました。

コネクション番号 サーバ側シーケンス番号(Seq) クライアント側シーケンス番号(Ack)
00x5180009F0x5180C8A0
10x1675E59F0x16BE1DA0
20x56231A600x0B6B1A61
30x7791289F0x406ED761
40x059DE59F0x3B33D761
50x11DCE59F0x67CC28A0
60x52DCE59F0x629DE5A0
70x52DCE59F0x11DCE5A0
80x52DCE59F0x52DCE5A0
90x52DCE59F0x52DCE5A0

なんと、サーバ側のシーケンス番号は、6番目のコネクションで0x52DCE59Fに収束してしまい、クライアント側のシーケンス番号も9番目と10番目で(0x52DCE59F+1)に収束してしまいました。 道理で Wireshark が「再送」と解釈してしまうわけです。 問題は、シーケンス番号の生成アルゴリズムにありそうです。

それにしても、"E59F"というのは、何のおまじない?

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

付録基板同士のクライアント・サーバ・システム [ColdFire V2]このエントリーを含むはてなブックマーク#

2047506

MCF52233付録基板を二枚使って、TCPによるサーバ・クライアント通信をさせてみました。 パケット・モニタでの監視も行います。

サーバは、これだ

サーバは、クライアントから到着したメッセージの末尾に'*'を加えてクライアントに返信します。

main() {
  char *send_buf,*recv_buf;
  char soc,newsoc,err_code;
  send_buf = MemoryAlloc(32);
  soc = CreateSocket(1);
  Bind(soc, 30049, 1);
  for (;;) {
    newsoc = Accept(soc, -1);
    if (newsoc < 0) break;
    do {
      err_code = Read(newsoc, -1);
      if (err_code < 0) break;
      recv_buf = GetReceiveBuffer(newsoc,1);
      StrCpy(send_buf, recv_buf);
      StrCat(send_buf, "*");
      MemoryFree(recv_buf);
      Write(newsoc, send_buf, StrLen(send_buf));
      err_code = WaitWriteComplete(newsoc);
      if (err_code < 0) break;
    } while (0);
    CloseSocket(newsoc);
  }
  CloseSocket(soc);
  MemoryFree(send_buf);
}

クライアントは、これだ

クライアントは、1秒インターバルでサーバに短い電文"I=?"を送り、サーバからの返信を受け取ります。 全部で10回のコネクションを行います。

main() {
  char *send_buf,*recv_buf;
  char soc,err_code;
  long i;
  send_buf = MemoryAlloc(32);
  for (i = 0; i < 10; i++) {
    soc = CreateSocket(1);
    if (soc < 0) continue;
    do {
      err_code = Connect(soc,GetHostByName("192.168.1.103"),30049);
      if (err_code < 0) break;
      StrCpy(send_buf, "I=");
      GetDigit(i, &send_buf[2]);
      Write(soc, send_buf, StrLen(send_buf));
      err_code = WaitWriteComplete(soc);
      if (err_code < 0) break;
      err_code = Read(soc, -1);
      if (err_code < 0) break;
      recv_buf = GetReceiveBuffer(soc,1);
      MemoryFree(recv_buf);
    } while (0);
    CloseSocket(soc);
    Sleep(100);
  }
  MemoryFree(send_buf);
}

リピータ・ハブでパケットのモニタリングもできる

中古のリピータ・ハブ(通称バカ・ハブ)を入手したので、パケット・モニタでクライアントとサーバの通信の様子を見てみます。

#7	2.014995	1024 > 30049 [SYN] Seq=0 Win=1454 Len=0
#8	2.015247	30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0
#9	2.015419	1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0

今回は、IPアドレスとプロトコルのカラムを削除しました。 今までどおりの3ウェイ・ハンドシェイクです。 それぞれを Ethernet フレームの単位で詳しく見たところ、60バイトのフレームの最後の6バイトは、 "Trailer" と書いてあり、 00 で埋まっています。 54バイトじゃ、だめなの?

#10	2.019569	1024 > 30049 [PSH, ACK] Seq=1 Ack=1 Win=1454 Len=3
#11	2.019672	30049 > 1024 [ACK] Seq=1 Ack=4 Win=1454 Len=0

#10で"I=0"をサーバに送り、#11でサーバから受信確認が届きます。 いずれのフレームも60バイトで、 Trailer の長さがそれぞれ3バイト、6バイトと異なっています。

#12	2.025681	30049 > 1024 [PSH, ACK] Seq=1 Ack=4 Win=1454 Len=4
#13	2.025938	1024 > 30049 [ACK] Seq=4 Ack=5 Win=1454 Len=0

#12で"I=0*"がサーバから返され、#13でサーバに受信確認が届きます。 いずれのフレームも60バイトで、 Trailer の長さはそれぞれ2バイトと6バイトです。 #12の Trailer には 00 が入っていますが、#13の Trailer には "I=0" が入っています。 何だこりゃ?

#14	2.027416	30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0
#15	2.028981	1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0
#16	2.028990	[TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0
#17	2.029080	[TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0

コネクションの終了要求は、サーバとクライアントからほぼ同時に発行され、それに対する"ACK"が送信されます。 いずれも60バイトのフレームで、 Trailer に "I=0" と "I=0*" が残ったままです。 なぜ、#16と#17が「TCP Keep-Alive」と判断されたのかは、わかりません。 一言、「This is a TCP keep-alive segment」とだけ表示されています。

#18	2.029693	1024 > 30049 [RST] Seq=5 Win=1454 Len=0

"RST"というのが、初めて出てきました。 コネクションを強制的に終了するのですね。

#19	3.035180	[TCP Port numbers reused] 1024 > 30049 [SYN] Seq=0 Win=1454 Len=0
#20	3.035420	30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0
#21	3.035593	1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0
#22	3.039698	1024 > 30049 [PSH, ACK] Seq=1 Ack=1 Win=1454 Len=3
#23	3.039980	30049 > 1024 [ACK] Seq=1 Ack=4 Win=1454 Len=0
#24	3.045840	30049 > 1024 [PSH, ACK] Seq=1 Ack=4 Win=1454 Len=4
#25	3.046068	1024 > 30049 [ACK] Seq=4 Ack=5 Win=1454 Len=0
#26	3.047556	30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0
#27	3.049124	1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0
#28	3.049257	[TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0
#29	3.049292	[TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0
#30	3.049891	1024 > 30049 [RST] Seq=5 Win=1454 Len=0

二つ目のコネクションは、#22で"I=1"をサーバに送り、#24で"I=1*"をサーバから受け取ります。 #19の「TCP Port numbers reused」でポート番号が再利用されたことがわかります。 いずれのフレームも60バイトでした。

#115	11.196476	1024 > 30049 [SYN] Seq=0 Win=1454 Len=0
#116	11.196695	30049 > 1024 [SYN, ACK] Seq=0 Ack=1 Win=1454 Len=0
#117	11.196900	1024 > 30049 [ACK] Seq=1 Ack=1 Win=1454 Len=0
#118	11.201017	[TCP Retransmission] 1024 > 30049 [PSH, ACK] Seq=1 Ack=1 Win=1454 Len=3
#119	11.201248	30049 > 1024 [ACK] Seq=1 Ack=4 Win=1454 Len=0
#120	11.207180	[TCP Retransmission] 30049 > 1024 [PSH, ACK] Seq=1 Ack=4 Win=1454 Len=4
#121	11.207406	[TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=5 Win=1454 Len=0
#122	11.208912	30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0
#123	11.210478	1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0
#124	11.210613	[TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0
#125	11.210640	[TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0
#126	11.211247	1024 > 30049 [RST] Seq=5 Win=1454 Len=0

順調に通信をこなしましたが、最後の10回目のコネクションで「TCP Retransmission」が発生しています。 Wiresharkの解説によると、#118は#111の、#120は#110の再送と理解されたようです。

#110	10.188839	30049 > 1024 [FIN, ACK] Seq=5 Ack=4 Win=1454 Len=0
#111	10.190404	1024 > 30049 [FIN, ACK] Seq=4 Ack=5 Win=1454 Len=0
#112	10.190462	[TCP Keep-Alive] 1024 > 30049 [ACK] Seq=4 Ack=6 Win=1454 Len=0
#113	10.190534	[TCP Keep-Alive] 30049 > 1024 [ACK] Seq=5 Ack=5 Win=1454 Len=0
#114	10.191173	1024 > 30049 [RST] Seq=5 Win=1454 Len=0

でも、#110と#111は、前回のコネクションの終了要求パケットですよ。 しかも#114で"RST"まで発行されているし。 想像するに、ポート番号の使い回しが激しすぎたので、誤解したのだとおもわれます。 実際には、再送は行われていません。

本日の考察

  1. TCP クライアントは、ポート番号が使い回しされる。
  2. Ethernet フレームにTrailer が付加されることがあり、そこには直前の通信の残りが入っている。
  3. Wireshark の解析機能も万能ではない。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌

前の20件 | - ColdFire V2 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。