インターネット/トランスポート層として、リンク層上にTCP/IPを実装します。今回の実装では、uIP*1を利用しました。
uIP
uIPは、Swedish Institute of Computer ScienceのAdam Dunkels氏が開発したオープンソースのTCP/IPスタックです。商用、非商用どちらでもフリーで利用できます。uIPはC言語で記述された、8bit CPUでも実装可能な小コードサイズとRAM使用サイズを特徴としたTCP/IPスタックであり、豊富なアプリケーションサンプル(Webサーバ、Webクライアント、SMTPクライアント、Telnetサーバ等)が付属しています。
uIPは以下のディレクトリで構成されています。
uip-1.0 uip/ apps/ webserver/ doc/ lib/ unix/
uipディレクトリにTCP/IPスタックコードが、apps/webserverディレクトリにWebサーバのアプリケーションサンプルが格納されています。今回は、主にこの2つのディレクトリのデータを利用して、TCP/IP及びHTTPの実装を行います。
uIPの移植
ターゲットとするシステムに組み込む為に、uIPコンフィグレーション・ファイルの設定とタイマー機能の実装を行います。
コンフィグレーションファイルの設定
uip-conf.hを書き換えて、各パラメータの設定を行います。主に、uIPで使用されるデータタイプの定義と、バッファサイズの指定を行います。
#ifndef __UIP_CONF_H__ #define __UIP_CONF_H__ // データタイプの定義 typedef unsigned char u8_t; typedef unsigned short u16_t; typedef unsigned short uip_stats_t; // #define UIP_CONF_MAX_CONNECTIONS 40 // 最大Connection数 #define UIP_CONF_MAX_LISTENPORTS 40 // 最大 Listening TCPポート数 #define UIP_CONF_BUFFER_SIZE 840 // パケットバッファサイズ #define UIP_CONF_BYTE_ORDER LITTLE_ENDIAN #define UIP_CONF_UDP 0 #define UIP_CONF_UDP_CHECKSUMS 1 #define UIP_CONF_STATISTICS 1 // http用ヘッダファイル #include "webserver.h" #endif // __UIP_CONF_H__
タイマーの実装
タイマーは、TCPの状態遷移をTIME_WAITからCLOSEDに遷移させる為や、パケットの再送タイミングとして使用されます。
今回は、SH-4Aの32bitタイマユニット(TMU2)を利用してタイマーを実装しています。以下の設定では、TMU2に対して1秒ごとにアンダーフローインタラプトが発生するように指定を行っています。(clock_init())
(clock-arch.h)
#ifndef __CLOCK_ARCH_H__ #define __CLOCK_ARCH_H__ typedef unsigned int clock_time_t; #define CLOCK_CONF_SECOND 1000 int get_timer_count_ether(int ms_count); #endif // __CLOCK_ARCH_H__
(clock-arch.c)
#include "iodefine.h" #include "clock-arch.h" clock_time_t clock_time(void) { // return TMU2 current counter unsigned int x = TMU2.TCNT; return x; } int get_timer_count_ether(int ms_count) { return (int)((float)ms_count/0.030720); // 30ns x 1024 (PCK/1024) } void clock_init(void) { // TMU2 is used for Networking // Configured to count ARP timeout // pck = 33MHz = 30ns, pck/1024 = 130KHz = 30us // set interrupt priority for TMU0(bit28:24),TMU1(bit20:16), TMU2(bit12:8) INTC.INT2PRI0.LONG = 0x0c0c0c00; // bit 28-24 (0,1 = mask) INTC.INT2MSKCR.LONG = 1; // TMU0-2 TMU2.TCOR = get_timer_count_ether( 1000); // 1Sec TMU2.TCNT = get_timer_count_ether( 1000); TMU2.TCR.WORD = 0x0024; // timer control UNF=0,UNIE=1(int),CKEG=00 TPSC=100 }
uIPの組み込み
TCP/IP処理のメインルーチンは、Ethernet Frameの受信インタラプトをトリガとして処理を開始します。Ethernet Frameを受信すると、typeフィールドの値を確認し、データがIP(Internet Protocol)の場合とARP(Address Resolution Protocol)の場合で処理を分岐します。
FrameデータがARPパケットの場合、uip_arp_arpin()をコールして応答用のARPパケットを生成します。応答用ARPには送信元のMACアドレスがセットされます。ARPパケットの生成後、ether_write_frame()をコールしてEthernet Frameを送信します。
FrameデータがIPの場合、次の順に処理を行います。
- uip_arp_ipin()をコールして、送信元のIPアドレスとMACアドレスの対応を確認し、内部ARPテーブルのアップデートを行います。
- uip_input()をコールしてTCPパケットの処理を行います。現在のTCPの遷移状態に従って、処理を行うイベントを判別し、UIP_APP_CALL()をコールします。HTTPは、この関数内で処理されます。
- UIP_APP_CALL()の結果、送信用のIPパケットが存在すれば、uip_arp_out()をコールしてEthernet Headerの作成を行います。
- ether_write_frame()をコールして、Ethernet Frameを送信します。
実際のコードは以下のようになります。コード中のuip_lenとuip_bufは、uIPで定義されているグローバル変数です。uIPとリンク層は、uip_lenとuip_bufを用いてデータの受け渡しを行います。uip_bufは送受信用のデータを格納する為に使用され、uip_lenは、送受信用データの長さを格納する為に使用されます。
(実装コード:SH4インタラプトハンドラ INTR IRQ2)
void INT_IRL_LEVEL9() { // INTR IRQ2 struct st_intsrc int_src; int i; int_src.LONG = ETHER_INT_SOURCE.LONG; if (int_src.TXB) { ETHER_INT_SOURCE.LONG = 1; // Clear Int } if (int_src.RXB) { // Ethernet Frameの受信 ETHER_INT_SOURCE.LONG = 4; // インタラプトのクリア uip_len = ether_read_frame(uip_buf); // Frameデータと長さを獲得 if(uip_len > 0) { if(BUF->type == htons(UIP_ETHTYPE_IP)) { // IP Packet uip_arp_ipin(); // ARPテーブルアップデート uip_input(); // TCPパケット処理、(UIP_APP_CALL()呼び出し) if(uip_len > 0) { uip_arp_out(); // Ethernet Headerの作成 ether_write_frame(uip_len, uip_buf); // Ethernet Frameの送信 } } else if(BUF->type == htons(UIP_ETHTYPE_ARP)) { // ARP uip_arp_arpin(); // 応答用ARPパケットの生成 if(uip_len > 0) { ether_write_frame(uip_len,uip_buf); // ARPパケットの送信 } } } } }