/*	
 *	clipeth.c
 *
 *	P/ECE USB Ethernet (DIX Ethernet Ver2.0)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Tue Aug 23 04:43:00 JST 2005 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Dec 08 00:00:11 JST 2007 Naoyuki Sawa
 *	- clipusbの仕様変更を吸収しました。
 *	  受信オーバーランを回避する実装を可能\とするための仕様変更ですが、本モジュールは対応していません。
 *	  clipusbの仕様変更を本モジュール内で吸収したので、本モジュールの機能\はこれまでどおりです。
 *	  本モジュールを、受信オーバーランを回避する実装への変更は、今後、必要が生じたときに行う予\定です。
 */
#include "clip.h"

/****************************************************************************
 *	ディスクリプタ定義
 ****************************************************************************/

/* デバイスディスクリプタ */
static const DEVICE_DESCRIPTOR device_desc = {
	bLength:		sizeof(DEVICE_DESCRIPTOR),
	bDescriptorType:	DEVICE_DESCRIPTOR_TYPE,
	bcdUSB:			0x0110,
	bMaxPacketSize0:	EP0_PACKET_SIZE,
	idVendor:		PCE_VENDOR_ID,
	idProduct:		ETHERNET_PRODUCT_ID,			/* 重複禁止!! */
	bcdDevice:		0x0001,
	iManufacture:		1,
	iProduct:		2,
	bNumConfigurations:	1,
};

/* コンフィギュレーションディスクリプタ */
static const CONFIGURATION_DESCRIPTOR config_desc = {
	bLength:		sizeof(CONFIGURATION_DESCRIPTOR),
	bDescriptorType:	CONFIGURATION_DESCRIPTOR_TYPE,
	wTotalLength:		sizeof(CONFIGURATION_DESCRIPTOR) +	/* config_desc */
				sizeof(INTERFACE_DESCRIPTOR    ) +	/* interface_desc */
				sizeof(ENDPOINT_DESCRIPTOR     ) +	/* endpoint_desc_ep2in */
				sizeof(ENDPOINT_DESCRIPTOR     ),	/* endpoint_desc_ep2out */
	bNumInterfaces:		1,
	bConfigurationValue:	1,
	iConfiguration:		3,
	bmAttributes:		0x80,					/* Bus-powered */
	MaxPower:		100 / 2,				/* 100[mA] (仮) */
};

/* インターフェイスディスクリプタ */
static const INTERFACE_DESCRIPTOR interface_desc = {
	bLength:		sizeof(INTERFACE_DESCRIPTOR),
	bDescriptorType:	INTERFACE_DESCRIPTOR_TYPE,
	bNumEndpoints:		2,
	bInterfaceClass:	0xff,					/* Vendor-specific */
	bInterfaceSubClass:	0xff,					/* Vendor-specific */
	bInterfaceProtocol:	0xff,					/* Vendor-specific */
	iInterface:		4,
};

/* エンドポイントディスクリプタ
 * * P/ECEコンパイラの不具合対策のため、endpoint_descを構\造体配列にしないように!!
 *   詳しくは、clip/clipusb.hのコメントを参照してください。
 */
static const ENDPOINT_DESCRIPTOR endpoint_desc_ep2in = {
	bLength:		sizeof(ENDPOINT_DESCRIPTOR),
	bDescriptorType:	ENDPOINT_DESCRIPTOR_TYPE,
	bEndpointAddress:	0x82,					/* EP2IN */
	bAttributes:		2,					/* Bulk */
	wMaxPacketSize:		EP2_NONISO_PACKET_SIZE,
};
static const ENDPOINT_DESCRIPTOR endpoint_desc_ep2out = {
	bLength:		sizeof(ENDPOINT_DESCRIPTOR),
	bDescriptorType:	ENDPOINT_DESCRIPTOR_TYPE,
	bEndpointAddress:	0x02,					/* EP2OUT */
	bAttributes:		2,					/* Bulk */
	wMaxPacketSize:		EP2_NONISO_PACKET_SIZE,
};

/* ストリングディスクリプタ
 * * gccは、L"..."形式の16bit UNICODEが使えません。(32bitになってしまいます)
 *   しかたがないので、1バイトづつ記述しました。
 * * 文字列中に[0-7]を含むと、'\0'の続きと見なされるので、注意してください。
 */
static const char* const string_desc[] = {
//	   ++--- 04 06 08 0a 0c 0e 10 12 14 16 18 1a 1c 1e 20 22 24 26 28 2a 2c 2e 30
//	   ||
//	"\x??\3_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0_\0"
	"\x04\3\x09\x04",								// 0: wLANGID = 0x0409 English (United States)
	"\x16\3P\0i\0e\0c\0e\0 \0L\0a\0b\0.\0",						// 1: iManufacture   = "Piece Lab."
	"\x22\3P\0/\0E\0C\0E\0 \0U\0S\0B\0 \0d\0e\0v\0i\0c\0e\0",			// 2: iProduct       = "P/ECE USB device"
	"\x2e\3E\0t\0h\0e\0r\0n\0e\0t\0 \0c\0o\0n\0f\0i\0g\0u\0r\0a\0t\0i\0o\0n\0",	// 3: iConfiguration = "Ethernet configuration"
	"\x26\3E\0t\0h\0e\0r\0n\0e\0t\0 \0i\0n\0t\0e\0r\0f\0a\0c\0e\0",			// 4: iInterface     = "Ethernet interface"
};

/****************************************************************************
 *	
 ****************************************************************************/

/* ドライバ構\造体 */
typedef struct _ETHERNETDRIVER {
	unsigned char address[6];		// Ethernetアドレス (vendor_requestで自動設定)
	//
	unsigned char rxbuf[1514];		// 受信フレームバッファ
	int rxlen;				// 受信フレーム長
	//
	QUEUE* txque;				// 送信キュー
	unsigned char txbuf[1514];		// 送信フレームバッファ
	int txlen;				// 送信フレーム長
	int txpos;				// 送信フレームポインタ
	int txing;				// 送信停止中=0, 動作中=1
	//
	ETHERNETSTAT stat;			// 通信状態 (デバッグ用)
	//
	LIST_ENTRY client_list;			// クライアントリスト
} ETHERNETDRIVER;
static ETHERNETDRIVER driver;

static void get_descriptor(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void set_configuration(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void set_interface(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void vendor_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len);

static void bus_reset();
//{{2007/12/08:clipusbの仕様変更を吸収
//static void ep2_rxdone(const unsigned char dat[/*len*/], int len);
//↓2007/12/08:clipusbの仕様変更を吸収
static void ep2_rxdone();
static void ep2_rxsub(const unsigned char dat[/*len*/], int len);
//}}2007/12/08:clipusbの仕様変更を吸収
static void ep2_txdone();
static int  ep2_txsub();

static ETHERNETCLIENT* ethernet_find_client(int type);

/* USBフック情報 */
static const USBINF usb_inf = {
	/* USBC mode */
	mode:			D12_ENDP_NONISO,
	/* USBC raw event */
	bus_reset:		bus_reset,
	ep2_rxdone:		ep2_rxdone,
	ep2_txdone:		ep2_txdone,
	/* Device request */
	get_descriptor:		get_descriptor,
	set_configuration:	set_configuration,
	set_interface:		set_interface,
	vendor_request:		vendor_request,
};

/* ベンダリクエストコード (デバイスドライバの定義と一致させること) */
#define SET_DEVICE_ADDRESS	0	/* Ethernetアドレスを設定します */

/* 無効アドレス */
const unsigned char ethernet_null_address[6]      = { 0x00,0x00,0x00,0x00,0x00,0x00 };

/* ブロードキャストアドレス */
const unsigned char ethernet_broadcast_address[6] = { 0xff,0xff,0xff,0xff,0xff,0xff };

/****************************************************************************
 *	
 ****************************************************************************/

static void
get_descriptor(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	static unsigned char tmpbuf[sizeof config_desc /* ＳＴＡＴＩＣです！ */
				  + sizeof interface_desc
				  + sizeof endpoint_desc_ep2in
				  + sizeof endpoint_desc_ep2out];
	//
	int type  = HIBYTE(req->wValue);
	int index = LOBYTE(req->wValue);

	/* 要求されたディスクリプタを返送します。 */
	switch(type) {
	case DEVICE_DESCRIPTOR_TYPE:
		usb_ep0_tx(&device_desc, sizeof device_desc);
		return;
	case CONFIGURATION_DESCRIPTOR_TYPE:
		switch(index) {
		case 0:
			len = 0;
			memcpy(&tmpbuf[len], &config_desc         , sizeof config_desc         ); len += sizeof config_desc         ;
			memcpy(&tmpbuf[len], &interface_desc      , sizeof interface_desc      ); len += sizeof interface_desc      ;
			memcpy(&tmpbuf[len], &endpoint_desc_ep2in , sizeof endpoint_desc_ep2in ); len += sizeof endpoint_desc_ep2in ;
			memcpy(&tmpbuf[len], &endpoint_desc_ep2out, sizeof endpoint_desc_ep2out); len += sizeof endpoint_desc_ep2out;
			usb_ep0_tx(tmpbuf, len);
			return;
		}
		break;
	case STRING_DESCRIPTOR_TYPE:
		if(index < ARRAY_SIZE(string_desc)) {
			usb_ep0_tx(string_desc[index], string_desc[index][0]);
			return;
		}
		break;
	}
	usb_ep0_stall(); /* 未対応 */
}

static void
set_configuration(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	/* コンフィグレーション#1(これしかありません)が選択されたら、メインエンドポイントを有効にします。 */
	D12_SetEndpointEnable(req->wValue == 1);
	usb_ep0_tx(NULL, 0); /* ACK */
}

static void
set_interface(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	/* インターフェイスの代替設定はひとつだけなので、何もしません。 */
	usb_ep0_tx(NULL, 0); /* ACK */
}

static void
vendor_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	switch(req->bRequest) {
	case SET_DEVICE_ADDRESS:
		/* Ethernetアドレスを設定します。 */
		if(len == 6) {
			ethernet_set_address(dat);
			usb_ep0_tx(NULL, 0); /* ACK */
		} else {
			usb_ep0_stall();
		}
		return;
	}
	usb_ep0_stall();
}

/****************************************************************************
 *	
 ****************************************************************************/

static void
bus_reset()
{
	/* USBが再接続されたときにコールバックされ、送受信状態をクリアします。
	 *
	 * - Windows 2000による実験では、デバイスドライバ初期化時に続けて二回発生するみたいです。
	 *   デバイスドライバ終了時には、発生しません。
	 *   二回呼ばれる必要はありませんが、二回呼ばれても動作上は問題ありません。
	 *
	 * - 送信状態をクリアする理由は、次のとおりです。
	 *   USBが切断されていた間に、送信要求されたフレームが、送信フレームバッファとキューに溜まっています。
	 *   送信キューがいっぱいになっていると、USBが再接続された直後の送信要求を受け付けることができません。
	 *   USB再接続直後に、PC側のデバイスドライバからEthernetアドレスが設定され、Gratuitous ARPの送信要求があるはずです。
	 *   USB再接続直後に、送信要求を直ちに受け付けられるよう、旧い送信要求フレームを破棄することにします。
	 *
	 * - 受信状態をクリアする理由は、次のとおりです。
	 *   フレームを受信途中にUSBを切断した場合、途中まで受信していたフレームが受信バッファに残っている可能\性があります。
	 *   USB再接続後に、新しく受信を開始したフレームと、途中まで受信していたフレームがくっついてしまう可能\性があります。
	 *   このようなケースは非常に稀であり、再現して実験することも困難ですが、念のため対策しておくことにしました。
	 */

	/* 送信状態をクリアします。 */
	clear_queue(driver.txque);	/* 送信キュー */
	driver.txlen = 0;		/* 送信フレームバッファ */
	driver.txing = 0;		/* 送信動作中フラグ */

	/* 受信状態をクリアします。 */
	driver.rxlen = 0;		/* 受信フレームバッファ */

	/* バスリセット回数を、カウントアップします。 */
	driver.stat.reset++;
}

//{{2007/12/08:clipusbの仕様変更を吸収
static void
ep2_rxdone()
{
	int len;
	unsigned char dat[EP2_NONISO_PACKET_SIZE];
	for(;;) { /* ダブルバッファに到着したパケットを全て処理 */
		len = D12_ReadBuffer(EP2OUT, dat, sizeof dat);
		if(len == -1) break;
		D12_ClearBuffer(EP2OUT);
		ep2_rxsub(dat, len);
	}
}
//}}2007/12/08:clipusbの仕様変更を吸収

//{{2007/12/08:clipusbの仕様変更を吸収
//static void
//ep2_rxdone(const unsigned char dat[/*len*/], int len)
//↓2007/12/08:clipusbの仕様変更を吸収
static void
ep2_rxsub(const unsigned char dat[/*len*/], int len)
//}}2007/12/08:clipusbの仕様変更を吸収
{
	int type;
	ETHERNETCLIENT* client;

	/* 受信データを受信フレームバッファへ追加します。
	 * 最大フレーム長を超過した場合は、データを捨てて、長さのみ増加させます。
	 * フレーム受信完了時に、フレーム長から、無効フレームを判断するためです。
	 */
	if(driver.rxlen + len <= 1514) {
		memcpy(driver.rxbuf + driver.rxlen, dat, len);
	}
	driver.rxlen += len;

	/* エンドポイントサイズ未満の受信ならば、現在のフレーム受信を完了します。
	 * フレーム長がエンドポイントサイズの倍数である場合は、
	 * 最後に空データが送られて来るはずなので、同じ条件で完了を判断できます。
	 */
	if(len < EP2_NONISO_PACKET_SIZE) {

		/* 空フレームは、エラーカウントせず、単に無視することにします。
		 * PC側ドライバが、エンドポイントサイズ未満のデータ送信の後に、
		 * さらに念を入れて空フレームを送って来る可能\性があるからです。
		 */
		if(!driver.rxlen) {
			/** no job **/
			goto L_SKIP;
		}

		/* 受信完了したフレームの、フレーム長を検査します。 */
		if((driver.rxlen < 60) || (driver.rxlen > 1514)) {
			driver.stat.rxerr++; /* フレーム長不正 */
			goto L_SKIP;
		}

		/* 自分宛て、または、ブロードキャストフレーム以外は、破棄します。 */
		if((memcmp(driver.rxbuf + 0, driver.address,             6) != 0) &&
		   (memcmp(driver.rxbuf + 0, ethernet_broadcast_address, 6) != 0)) {
			/** no job **/
			goto L_SKIP;
		}

		/* 受信成功フレーム数を、カウントアップします。 */
		driver.stat.rxok++;

		/* フレームタイプに対応するクライアントへ送ります。 */
		type = BEHALF(driver.rxbuf + 12);
		client = ethernet_find_client(type);
		if(client) {
			if(client->recv) {
				client->recv(client,
					driver.rxbuf +  6,	/* 送信元Ethernetアドレス */
					driver.rxbuf + 14,	/* データ */
					driver.rxlen - 14);	/* データ長 */
			}
		}
L_SKIP:
		/* 受信バッファを空に戻します。 */
		driver.rxlen = 0;
	}
}

static void
ep2_txdone()
{
	/* 送信フレームを、エンドポイントへ書き込みます。
	 * 送信フレームが無くなったら、txing=0(送信停止中)へ移行します。
	 */
	ASSERT(driver.txing);
	driver.txing = ep2_txsub();
}

/* 送信中のフレームを、エンドポイントへ書き込みます。
 * 送信中のフレームが無ければ、キューから次の送信フレームを取得し、エンドポイントへ書き込みます。
 * キューも空ならば、何もしません。
 * エンドポイントへの書き込みを行った場合は、      1(送信動作中)を返します。
 * エンドポイントへの書き込みを行わなかった場合は、0(送信停止中)を返します。
 */
/* ★割り込み禁止状態で呼び出してください!!★ */
static int
ep2_txsub()
{
	int len;

	/* 送信中のフレームが無ければ... */
	if(!driver.txlen) {

		/* 送信キューから次の送信フレームを取得します。 */
		len = read_queue(driver.txque, driver.txbuf, sizeof driver.txbuf);
		if(len < 0) {

			/* 送信キューも空ならば、帰ります。 */
			return 0; /* 送信停止中 */
		}

		/* 最小フレーム長未満のフレームは、最小フレーム長までパディングします。
		 * パディング部分のデータは何でも構\いませんが、デバッグし易さのために、ゼロクリアしておくことにしました。(必須ではありません)
		 */
		if(len < 60) {
			memset(driver.txbuf + len, 0, 60 - len); /* 無くても可 */
			len = 60;
		}

		/* 送信フレーム長とポインタを初期化します。 */
		driver.txlen = len;
		driver.txpos = 0;
	}

	/* 送信フレームを、エンドポイントへ書き込みます。 */
	len = driver.txlen - driver.txpos; /* len=0も有り得ます */
	if(len > EP2_NONISO_PACKET_SIZE) {
		len = EP2_NONISO_PACKET_SIZE;
	}
//{{2007/12/08:clipusbの仕様変更を吸収
//	D12_WriteEndpoint(EP2IN, driver.txbuf + driver.txpos, len);
//↓2007/12/08:clipusbの仕様変更を吸収
	D12_WriteBuffer(EP2IN, driver.txbuf + driver.txpos, len);
	D12_ValidateBuffer(EP2IN);
//}}2007/12/08:clipusbの仕様変更を吸収
	driver.txpos += len;

	/* エンドポイントサイズ未満の送信で、フレームを終端します。 */
	if(len < EP2_NONISO_PACKET_SIZE) {
		driver.txlen = 0;

		/* 送信成功フレーム数を、カウントアップします。 */
		driver.stat.txok++;
	}

	return 1; /* 送信動作中 */
}

/****************************************************************************
 *	
 ****************************************************************************/

/* クライアントリストから、指定したフレームタイプに対応するクライアントを探します。
 * 指定したフレームタイプに対応するクライアントが見つかったら、ポインタを返します。
 * 指定したフレームタイプに対応するクライアントが見つからなければ、NULLを返します。
 */
/* ★割り込み禁止状態で呼び出してください!!★ */
static ETHERNETCLIENT*
ethernet_find_client(int type)
{
	LIST_ENTRY* list_head = &driver.client_list;	/* リストのルート要素 */
	LIST_ENTRY* list_entry = list_head->Flink;	/* 先頭の要素(or終端) */
	ETHERNETCLIENT* client;

	while(list_entry != list_head) {
		if(!list_entry) {
			DIE(); /* 未初期化、または、クライアントリスト破損 */
		}
		client = CONTAINING_RECORD(list_entry, ETHERNETCLIENT, list_entry);
		if(client->type == type) {
			return client;
		}
		list_entry = list_entry->Flink;
	}

	return NULL;
}

void
ethernet_start(int txque_capacity)
{
	/* まず、確実に切断します。 */
	ethernet_stop();

	/* ドライバ構\造体をクリアします。 */
	memset(&driver, 0, sizeof driver);

	/* 送信キューを作成します。 */
	//if(txque_capacity < (sizeof(int) + 1514)) {
	//	txque_capacity = (sizeof(int) + 1514);
	//}
	//↓2005/09/16 create_queue()が暗黙でsizeof(int)を加算するよう仕様変更したことに追従しました。
	if(txque_capacity < 1514) {
		txque_capacity = 1514;
	}
	driver.txque = create_queue(txque_capacity);

	/* クライアントリストを初期化します。 */
	InitializeListHead(&driver.client_list);

	/* USBをフックします。 */
	usb_start(&usb_inf);
}

void
ethernet_stop()
{
	/* USBのフックを解除します。 */
	usb_stop();

	/* 送信キューを削除します。 */
	if(driver.txque) {
		delete_queue(driver.txque);
	}

	/* ドライバ構\造体をクリアします。 */
	memset(&driver, 0, sizeof driver);
}

void
ethernet_open(ETHERNETCLIENT* client)
{
ENTER_CS;

	if((client->type < 0x0600) || (client->type > 0xffff)) {
		DIE(); /* 不正なフレームタイプ */
	}

	if(ethernet_find_client(client->type)) {
		DIE(); /* 同じフレームタイプが登録済み */
	}

	/* クライアントリストに追加します。 */
	InsertTailList(&driver.client_list, &client->list_entry);

LEAVE_CS;
}

void
ethernet_close(ETHERNETCLIENT* client)
{
ENTER_CS;

	if(ethernet_find_client(client->type) != client) {
		DIE(); /* 未登録のクライアント */
	}

	/* クライアントリストから取り除きます。 */
	RemoveEntryList(&client->list_entry);

LEAVE_CS;
}

int
ethernet_send(ETHERNETCLIENT* client, const unsigned char* address/*[6]*/, const void* data/*[length]*/, int length)
{
	int retval;
	//
	int tmpbuf_length;
	unsigned char* tmpbuf = NULL; /* 要開放 */

	if(length < 0) {
		DIE(); /* パラメータ不正 */
	}

	/* 最小フレーム長へのパディングは、送信キューから取り出した後に行います。 */
	if(length < (60 - 14)) {
		/** no job **/
	}

	/* 最大フレーム長を超過する送信要求は、エラー停止とします。 */
	if(length > (1514 - 14)) {
		DIE();
	}

	/* Ethernetフレーム構\築用バッファを確保します。 */
	tmpbuf_length = 14/*Ethernetヘッダ*/ + length/*データ*/;
	tmpbuf = malloc(tmpbuf_length);
	if(!tmpbuf) {
		DIE();
	}

	/* Ethernetフレームを作成します。 */
	              memcpy(tmpbuf +  0, address, 6);		/* + 0,     6: 宛て先Ethernetアドレス */
	ethernet_get_address(tmpbuf +  6);			/* + 6,     6: 送信元Ethernetアドレス */
	          PUT_BEHALF(tmpbuf + 12, client->type);	/* +12,     2: フレームタイプ */
	              memcpy(tmpbuf + 14, data, length);	/* +14,length: データ */

ENTER_CS; /*{{割り込み内、割り込み外の両方から呼ばれるため、ドライバ構\造体の排他制御が必要です*/

	/* Ethernetフレームを、送信キューへ追加します。 */
	retval = write_queue(driver.txque, tmpbuf, 14/*ヘッダ*/ + length/*データ*/);
	if(retval >= 0) { /* 追加成功 */

		/* 送信停止中ならば... */
		if(!driver.txing) {

			/* 送出を開始し、txing=1(送信動作中)へ移行します。 */
			driver.txing = ep2_txsub();
			ASSERT(driver.txing);
		}

		/* 成功時の戻り値は、データ長をそのまま返します。 */
		retval = length;

	} else { /* 追加失敗 */

		driver.stat.txerr++; /* 送信オーバーラン */
	}

LEAVE_CS; /*}}割り込み内、割り込み外の両方から呼ばれるため、ドライバ構\造体の排他制御が必要です*/

	/* Ethernetフレーム構\築用バッファを解放します。 */
	free(tmpbuf);

	return retval;
}

void
ethernet_set_address(const unsigned char* address/*[6]*/)
{
ENTER_CS;

	/* Ethernetアドレスが変化したら... */
	if(memcmp(address, driver.address, 6) != 0) {

		/* 新しいEthernetアドレスを格納します。 */
		memcpy(driver.address, address, 6);

		/* クライアントの設定変更コールバックへ、Ethernetアドレスの変化を通知します。
		 * - Ethernetクライアントとして、ARPプロトコルドライバが登録済みで、かつ、ネットワーク設定の条件も整っていれば、
		 *   以下の処理で呼び出される、ARPプロトコルドライバの設定変更コールバックが、Gratuitous ARPを送信するはずです。
		 */
		{
			LIST_ENTRY* list_head = &driver.client_list;	/* リストのルート要素 */
			LIST_ENTRY* list_entry = list_head->Flink;	/* 先頭の要素(or終端) */
			ETHERNETCLIENT* client;

			while(list_entry != list_head) {
				if(!list_entry) {
					DIE(); /* 未初期化、または、クライアントリスト破損 */
				}
				client = CONTAINING_RECORD(list_entry, ETHERNETCLIENT, list_entry);
				if(client->conf) {
					client->conf(client);
				}
				list_entry = list_entry->Flink;
			}
		}
	}

LEAVE_CS;
}

void
ethernet_get_address(unsigned char* address/*[6]*/)
{
ENTER_CS;

	memcpy(address, driver.address, 6);

LEAVE_CS;
}

void
ethernet_stat(ETHERNETSTAT* stat)
{
ENTER_CS;

	*stat = driver.stat;

LEAVE_CS;
}

