/*	
 *	clipuio.c
 *
 *	USB I/O (バルク転送による、単純なI/O)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2005 Naoyuki Sawa
 *
 *	* Fri Jul 08 06:00:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 *	* Sat Jul 09 23:23:00 JST 2005 Naoyuki Sawa
 *	- uio_stat()を追加しました。
 *	* Sun Jul 31 02:03:00 JST 2005 Naoyuki Sawa
 *	- SET_ADDRESSをclipusb.cに内蔵したので、個別の実装は不要となりました。
 *	* Sun Jul 31 05:41:00 JST 2005 Naoyuki Sawa
 *	- PceComドライバの効率化に伴い、送信データがある場合のみ送信するよう変更しました。
 *	　これまでは、送信データが無い場合、ゼロバイトのパケットを送信し続ける仕様でした。
 *	- 送信完了割り込み発生時に、送信バッファが空だった場合にのみ、送信完了コールバックを行うよう変更しました。
 *	  これまでは、1パケット送信完了毎(0バイトパケットの送信完了時含む)に送信完了コールバックを行っていました。
 *	* Mon Aug 01 20:26:00 JST 2005 Naoyuki Sawa
 *	- ストリングディスクリプタを追加しました。
 *	* Mon Aug 15 04:11:00 JST 2005 Naoyuki Sawa
 *	- ストリングディスクリプタの定義に関するコメントを追記しました。
 *	  「文字列中に[0-7]を含むと、'\0'の続きと見なされるので、注意してください。」
 *	* Mon Aug 22 04:24:00 JST 2005 Naoyuki Sawa
 *	- uio_open(),uio_close()->uio_start(),uio_stop()に名前変更しました。
 *	- usb_init(),usb_free()->usb_start(),usb_stop()の名前変更に追従しました。
 *	* 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:		UIO_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"
	"\x2c\3U\0S\0B\0 \0I\0/\0O\0 \0c\0o\0n\0f\0i\0g\0u\0r\0a\0t\0i\0o\0n\0",	// 3: iConfiguration = "USB I/O configuration"
	"\x24\3U\0S\0B\0 \0I\0/\0O\0 \0i\0n\0t\0e\0r\0f\0a\0c\0e\0",			// 4: iInterface     = "USB I/O interface"
};

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

/* ドライバ構造体 */
typedef struct _UIO {
	unsigned char* rxbuf/*[rxcap]*/;	/* 受信バッファ([pos]〜[(pos+len-1)%cap]まで有効) */
	int rxcap;				/* 受信バッファキャパシティ */
	int rxpos;				/* 受信データの先頭位置 */
	int rxlen;				/* 受信データサイズ */
	int rxlog;				/* 受信データ総バイト数(デバッグ用) */
	int rxerr;				/* 受信オーバーラン総バイト数 */
	void (*rxdone)();			/* 受信完了コールバック(NULL:無し) */
	//
	unsigned char* txbuf/*[txcap]*/;	/* 送信バッファ([pos]〜[(pos+len-1)%cap]まで有効) */
	int txcap;				/* 送信バッファキャパシティ */
	int txpos;				/* 送信データの先頭位置 */
	int txlen;				/* 送信データサイズ */
	int txlog;				/* 送信データ総バイト数(デバッグ用) */
	int txing;				/* 送信停止中ならば0、送信動作中ならば1 */
	void (*txdone)();			/* 送信完了コールバック(NULL:無し) */
} UIO;
static UIO uio;

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);
//{{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 void ep2_txsub();

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

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

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 */
}

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

//{{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 pos;
	int i;

	/* 空パケットの受信ならば、何もせずに帰ります。 */
	if(!len) {
		return;
	}

	/* コピーするデータサイズを、受信バッファの空きサイズに切り詰めます。 */
	if(len > (uio.rxcap - uio.rxlen)) {
		uio.rxerr += len - (uio.rxcap - uio.rxlen); /* オーバーラン */
		             len = (uio.rxcap - uio.rxlen);
	}

	/* 受信パケットから、受信バッファのデータ末尾へコピーします。 */
	pos = uio.rxpos + uio.rxlen;
	if(pos >= uio.rxcap) {
		pos -= uio.rxcap;
	}
	for(i = 0; i < len; i++) {
		uio.rxbuf[pos++] = *dat++;
		if(pos == uio.rxcap) {
			pos = 0;
		}
	}

	/* コピーした分、受信バッファの有効サイズを増やします。 */
	uio.rxlen += len;

	/* 受信完了コールバックが設定されていたら、通知します。 */
	if(uio.rxdone) {
		uio.rxdone();
	}
}

static void
ep2_txdone()
{
	/* 送信データが残っていたら... */
	if(uio.txlen) {

		/* 送信データをエンドポイントへ書き込みます。 */
		ep2_txsub();

	/* 送信データが無くなったら... */
	} else {

		/* 送信停止中をマークします。(送信完了コールバックよりも先に行うこと。必須!!) */
		uio.txing = 0;

		/* 送信完了コールバックが設定されていたら、通知します。 */
		if(uio.txdone) {
			uio.txdone();
			/* コールバックの中でuio_send()が呼ばれた場合、ここでtxing=1に戻っています。 */
		}
	}
}

static void
ep2_txsub()
{
	unsigned char dat[EP2_NONISO_PACKET_SIZE];
	int len;
	int i;

	/* コピーするデータサイズを、送信バッファの有効サイズに切り詰めます。 */
	len = EP2_NONISO_PACKET_SIZE;
	if(len > uio.txlen) {
		len = uio.txlen;
	}

	/* 送信バッファのデータ先頭から、送信パケットへコピーします。 */
	for(i = 0; i < len; i++) {
		dat[i] = uio.txbuf[uio.txpos++];
		if(uio.txpos == uio.txcap) {
			uio.txpos = 0;
		}
	}

	/* コピーした分、送信バッファの有効サイズを減らします。 */
	uio.txlen -= len;

	/* パケットを送信します。 */
//{{2007/12/08:clipusbの仕様変更を吸収
//	D12_WriteEndpoint(EP2IN, dat, len);
//↓2007/12/08:clipusbの仕様変更を吸収
	D12_WriteBuffer(EP2IN, dat, len);
	D12_ValidateBuffer(EP2IN);
//}}2007/12/08:clipusbの仕様変更を吸収
}

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

void
uio_start(int rxcap, int txcap, void (*rxdone)(), void (*txdone)())
{
	/* まず、確実に切断します。 */
	uio_stop();

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

	/* 受信バッファを確保します。 */
	if(rxcap < EP2_NONISO_PACKET_SIZE) {
		rxcap = EP2_NONISO_PACKET_SIZE;
	}
	uio.rxcap = rxcap;
	uio.rxbuf = malloc(rxcap);
	if(!uio.rxbuf) {
		DIE();
	}
	uio.rxdone = rxdone; /* 受信コールバック、または、NULLを格納 */

	/* 送信バッファを確保します。 */
	if(txcap < EP2_NONISO_PACKET_SIZE) {
		txcap = EP2_NONISO_PACKET_SIZE;
	}
	uio.txcap = txcap;
	uio.txbuf = malloc(txcap);
	if(!uio.txbuf) {
		DIE();
	}
	uio.txdone = txdone; /* 送信コールバック、または、NULLを格納 */

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

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

	/* 受信バッファを開放します。 */
	free(uio.rxbuf); /* free(0)は安全 */

	/* 送信バッファを開放します。 */
	free(uio.txbuf); /* free(0)は安全 */

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

void
uio_reset()
{
ENTER_CS;

	/* 受信バッファ、総バイト数、オーバーランをクリアします。 */
	uio.rxpos = 0;
	uio.rxlen = 0;
	uio.rxlog = 0;
	uio.rxerr = 0;

	/* 送信バッファ、総バイト数をクリアします。 */
	uio.txpos = 0;
	uio.txlen = 0;
	uio.txlog = 0;
	/*uio.txingは変更しません*/

LEAVE_CS;
}

int
uio_recv(void* _dat, int len)
{
	unsigned char* dat = _dat;
	int i;

	/* 0バイトの受信要求でも、以下の処理を継続します。
	 * 0バイトの受信要求でも、受信オーバーランの有無は取得できるからです。
	 */

ENTER_CS;

	if(!uio.rxerr) {

		/* コピーするデータサイズを、受信バッファの有効サイズに切り詰めます。 */
		if(len > uio.rxlen) {
			len = uio.rxlen;
		}

		/* 受信バッファのデータ先頭から、ユーザーバッファへコピーします。 */
		for(i = 0; i < len; i++) {
			*dat++ = uio.rxbuf[uio.rxpos++];
			if(uio.rxpos == uio.rxcap) {
				uio.rxpos = 0;
			}
		}

		/* コピーした分、受信バッファの有効サイズを減らします。 */
		uio.rxlen -= len;

		/* 総受信バイト数をカウントアップします。 */
		uio.rxlog += len;

	} else {

		/* 受信オーバーランが発生していたら、−(バイト数)を戻り値とします。 */
		len = -uio.rxerr;
	}

LEAVE_CS;

	return len;
}

int
uio_send(const void* _dat, int len)
{
	const unsigned char* dat = _dat;
	int pos;
	int i;

	/* 送信データサイズに負値が指定されていたら、AsciiZ文字列と見なします。 */
	if(len < 0) {
		len = strlen((char*)dat);
	}

ENTER_CS;

	/* コピーするデータサイズを、送信バッファの空きサイズに切り詰めます。 */
	if(len > (uio.txcap - uio.txlen)) {
		len = (uio.txcap - uio.txlen);
	}

	/* 送信バッファに追加可能なデータがあれば... */
	if(len) {

		/* ユーザーバッファから、送信バッファのデータ末尾へコピーします。 */
		pos = uio.txpos + uio.txlen;
		if(pos >= uio.txcap) {
			pos -= uio.txcap;
		}
		for(i = 0; i < len; i++) {
			uio.txbuf[pos++] = *dat++;
			if(pos == uio.txcap) {
				pos = 0;
			}
		}

		/* コピーした分、送信バッファの有効サイズを増やします。 */
		uio.txlen += len;

		/* 総送信バイト数をカウントアップします。 */
		uio.txlog += len;

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

			/* 送信動作中をマークします。 */
			uio.txing = 1;

			/* 送信データをエンドポイントに書き込みます。以降は、ep2_txdone()で連鎖します。 */
			ep2_txsub();
		}
	}

LEAVE_CS;

	return len;
}

void
uio_stat(UIOSTAT* stat)
{
ENTER_CS;

	stat->rxlen = uio.rxlen;
	stat->rxlog = uio.rxlog;
	stat->rxerr = uio.rxerr;
	stat->txlen = uio.txlen;
	stat->txlog = uio.txlog;

LEAVE_CS;
}
