/*	
 *	clipacm.c
 *
 *	USB CDC ACM (Communication Device Class Abstract Control Model)
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2007 Naoyuki Sawa
 *
 *	* Tue Oct 30 17:22:39 JST 2007 Naoyuki Sawa
 *	- 1st リリース。
 *	- clipuioモジュールを基にして作成しました。
 *	  処理の内容はclipuioと完全に同じなので、説明を省きました。
 *	  clipuio.cのコメントを参照してください。
 *	- clipuioとの違いは、以下の点です。
 *	  clipuioはベンダ独自クラスのデバイスであり、自作のpcecom.sysによって、シリアルポートと認識させていました。
 *	  clipacmはCDC ACMクラスのデバイスであり、Windows標準のusbser.sysによって、シリアルポートと認識されます。
 *	  ただし、インストール時のみ、独自のcdcacm.infが必要です。(usbser.sysを利用するデバイスの共通仕様です。)
 *	* Wed Oct 31 22:31:30 JST 2007 Naoyuki Sawa
 *	- SERIAL_STATE Notificationに対応しました。
 *	* Sun Nov 04 14:04:02 JST 2007 Naoyuki Sawa
 *	- (DTR)を(DSR)だけでなく、(DSR)と(CD)にループバックするよう変更しました。
 *	* Thu Nov 08 17:28:08 JST 2007 Naoyuki Sawa
 *	- 64×Nバイトのデータを送信したときに、Windows側で受信が完了しない問題を回避しました。
 *	* Fri Dec 07 16:28:26 JST 2007 Naoyuki Sawa
 *	- class_request()内のコメント訂正を追記しました。
 *	* Sat Dec 08 14:51:28 JST 2007 Naoyuki Sawa
 *	- clipusbの仕様変更に追従し、受信オーバーランを回避する実装としました。
 *	  もう、cdcacm_recv()が、受信オーバーランを示すマイナス値の戻り値を返すことは無くなったので、
 *	  アプリケーションが戻り値を見て、必要に応じてcdcacm_reset()を呼び出す処理は不要となりました。
 *		[変更前]
 *			len = cdcacm_recv(dat, sizeof dat);
 *			if(len < 0) {
 *				受信オーバーラン時の処理();
 *				cdcacm_reset();
 *			}
 *			if(len > 0) {
 *				正しく受信できた時の処理();
 *			}
 *		[変更後]
 *			len = cdcacm_recv(dat, sizeof dat);
 *			if(len > 0) {
 *				正しく受信できた時の処理();
 *			}
 *	  受信オーバーランの概念が無くなったので、アプリケーションの処理が大分簡単になったと思います。
 *	- 今回の変更により、基にしたclipuioと関数仕様の一部相違が生じたので、コメントを追記しました。
 *	* Tue Dec 11 14:26:45 JST 2007 Naoyuki Sawa
 *	- 送信ダブルバッファを利用するよう変更しました。
 *	- これまでは、受信ダブルバッファは利用していましたが、送信ダブルバッファは利用していませんでした。
 *	  送信バッファは片面のみを用い、そのパケット送信完了割り込みを待って、次のパケットを送信していました。
 *	  今回の変更により、送信バッファも両面を用いて、両面開いていればまとめて2パケット書き込むよう変更しました。
 *	  また、もし片面書き込み中にもう片面が開いたら、開いたもう片面にも書き込みます。
 *	  (タイミング的にまず無いとは思いますが、3パケット以上まとめて書き込むこともありえます。)
 *	- この変更により、片面への書き込みともう片面の送信が並行処理するので、送信速度が向上すると思いました。
 *	  しかし、実測してみると、送信速度は変更前と変わりませんでした。最大スループットは、以下のとおりです。
 *		P/ECE <- PC	約 120000 バイト/秒
 *		P/ECE -> PC	約  60000 バイト/秒
 *	  アップストリームのスループットが、ダウンストリームのスループット半分しか出ない原因は、送信バッファを
 *	  片面しか使っていないのが原因だと考えて、今回の変更を行ったのですが、前述のとおり効果ありませんでした。
 *	  USBの特性か、または、WindowsのUSBシリアルドライバの特性だと思います。
 *	- そんなわけで、今回の変更は、速度的には効果がなかったのですけれど、以下のような別の利点が得られました。
 *	- 利点1: 受信処理と送信処理でコードが対称的になりました。
 *		 cdcacm_recv()とcdcacm_send()、ep2_rxsub()とep2_txsub()の処理内容が、ほぼ対象です。
 *		 変更前よりも、わかりやすいプログラムになったと思います。
 *	- 利点2: 安定性が向上しました。
 *		 変更前は、以下のような場合に、P/ECE->PCの送信ができなくなってしまう場合がありました。
 *			1. P/ECEをPCに接続し、USB CDC ACMを実行する。PCが仮想COMポートを認識する。
 *			2. Windowsアプリケーションが仮想COMポートを開く前に、P/ECEがデータを送信する。
 *			3. Windowsアプリケーションが仮想COMポートを開く。
 *			4. これ以降、どうやってもP/ECE->PCのデータが届かなくなります。(P/ECEをつなぎ直せば回復)
 *			   この問題を防ぐには、Windowsアプリケーションが仮想COMポートを開くまで、
 *			   P/ECEがデータを送信しないようにするしかありませんでした。
 *		 今回の変更により、上述のようなケースでも、P/ECE->PCのデータが届くようになりました。
 *		 (ただし、Windowsアプリケーションが仮想COMポートを開く前に送信したデータは破棄されるようです。)
 *		 なぜ改善されたのか理由がはっきりしないのですけれど、変更前は送信バッファの空き状態をプログラムで
 *		 追跡していたのを、変更後はUSBCに実際に書き込んでみて判断するようになったのが理由かも知れません。
 *		 変更前のコードは、送信バッファの空き状態を追跡する方法に問題があったのかもしれません。(※要調査)
 *		 ↓2007/12/13:追記
 *		 原因がわかりました。
 *		 原因は、SetAddressEnableやSetEndpointEnableが行われる前に、送信を行ってしまっていたことでした。
 *		 以下に、詳しく説明します。
 *		 - 上で、「Windowsアプリケーションが仮想COMポートを開く前に、P/ECEがデータを送信する」と問題が発生すると書きましたが、やや不正確でした。
 *		   正しくは、「WindowsのUSBシリアルドライバが仮想COMポートを認識し、SET_ADDRESS,SET_CONFIGURATIONのデバイスリクエストを発行する前に、
 *		   P/ECEがデータを送信する」と問題が発生します。
 *		   USB CDC ACMを実行した後、10秒ぐらい待って仮想COMポートとして認識された後であれば、Windowsアプリケーションが仮想COMポートを開く前に
 *		   P/ECEがデータを送信しても、WindowsのUSBシリアルドライバが適切にブロックするので問題は発生しません。
 *		 - SET_ADDRESS,SET_CONFIGURATIONのデバイスリクエストを発行する前、すなわち、PDIUSBD12のSetAddressEnableとSetEndpointEnableを行う前に
 *		   WriteBufferすると、一見、書き込みは成功しているように見えるのですが、書き込んだデータは捨てられてしまうようです。
 *		   どこにもエラーステータスは示されず、送信完了割り込みも発生しません。(←実験より。PDIUSBD12のマニュアルには記載されていません。)
 *		   変更前のコードは、連続してデータを送信する場合は、送信完了割り込みだけをトリガとしていたため、いったんトリガが失われると、
 *		   永遠に次の送信が行えなくなってしまっていたのです。
 *		   変更後のコードは、トリガが失われても、次のcdcacm_send()時にエンドポイントが空であることを見てエンドポイントに書き込みます。
 *		   書き込んだデータはまた捨てられますが、いずれ、WindowsのUSBシリアルドライバがSET_ADDRESS,SET_CONFIGURATIONのデバイスリクエストを発行し、
 *		   SetAddressEnableとSetEndpointEnableが実行されると、それ以降に書き込んだデータは捨てられずに有効に送信されるのです。
 *		   変更前のコードも、変更後のコードも、無効なエンドポイントにWriteBufferしているという点で、PDIUSBD12のUndoc'dな挙動に依存する、
 *		   望ましくない動作です。変更後のコードは、たまたま回復が可能であったという違いだけです。
 *		 - 望ましくない動作を防ぐためには、厳密には、WindowsのUSBシリアルドライバがSET_ADDRESS,SET_CONFIGURATIONのデバイスリクエストを発行し、
 *		   SetAddressEnableとSetEndpointEnableが実行されるまでは、cdcacm_send()を使えなくしておく必要があります。
 *		   しかし、現時点では、その改修は行わないことにしました。
 *		   改修は行わない理由として、P/ECE側から能動的にデータ送信を開始することは稀であるからです。(今回はテストのためにわざとやりました)
 *		   もし、P/ECE側から能動的にデータ送信を開始することがあったとしても、変更後のコードであれば、前述のように単に捨てられるだけです。
 *		   PC側の準備が整う前に送信したデータが捨てられるという挙動は、本物のRS-232Cと同じであり、まず、問題にはならないと思います。
 *		   以上のような理由と、プログラムを単純に保つために、改修を行わないことにしました。
 *		 - なお、「WindowsのUSBシリアルドライバが仮想COMポートを認識し、SET_ADDRESS,SET_CONFIGURATIONのデバイスリクエストを発行する前に、
 *		   P/ECEがデータを送信する」という状況は、P/ECEに乾電池を入れて使うと、簡単に再現できます。
 *		   P/ECEに乾電池を入れて、USB CDC ACMを実行し、送信を開始し、その後でPCに接続すれば、同じ状況となります。
 *		   この場合であっても、変更後のコードならば、PCに接続して仮想COMポートとして認識される前に送信したデータは、単に捨てられるだけです。
 *		   PCに接続して仮想COMポートとして認識された後は、正常にデータが送信されます。
 */
#include "clip.h"

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

static const DEVICE_DESCRIPTOR device_desc = {
	bLength:		sizeof(DEVICE_DESCRIPTOR),
	bDescriptorType:	DEVICE_DESCRIPTOR_TYPE,
	bcdUSB:			0x0110,
	bDeviceClass:		0x02,			/* Communication Device Class */
	bMaxPacketSize0:	EP0_PACKET_SIZE,
	idVendor:		PCE_VENDOR_ID,
	idProduct:		CDCACM_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_0 */
				(5+4+5+5) +				/* cdc_class_specific_desc_0 */
				sizeof(ENDPOINT_DESCRIPTOR) +		/* endpoint_desc_ep1in */
				sizeof(INTERFACE_DESCRIPTOR) +		/* interface_desc_1 */
				sizeof(ENDPOINT_DESCRIPTOR) +		/* endpoint_desc_ep2out */
				sizeof(ENDPOINT_DESCRIPTOR),		/* endpoint_desc_ep2in */
	bNumInterfaces:		2,
	bConfigurationValue:	1,
	bmAttributes:		0x80,		/* Bus-powered */
	MaxPower:		100 / 2,	/* 100[mA] (仮) */
};
static const INTERFACE_DESCRIPTOR interface_desc_0 = {
	bLength:		sizeof(INTERFACE_DESCRIPTOR),
	bDescriptorType:	INTERFACE_DESCRIPTOR_TYPE,
	bInterfaceNumber:	0,
	bNumEndpoints:		1,
	bInterfaceClass:	0x02,	/* Communication Interface Class */
	bInterfaceSubClass:	0x02,	/* Abstract Control Model */
	bInterfaceProtocol:	0x01,	/* V.25ter */
};
static const unsigned char cdc_class_specific_desc_0[5+4+5+5] = {
	5,		/* bFunctionLength */
	0x24,		/* bDescriptorType = CS_INTERFACE */
	0x00,		/* bDescriptorSubtype = Header Functional Descriptor */
	0x10, 0x01,	/* bcdCDC */
	//
	4,		/* bFunctionLength */
	0x24,		/* bDescriptorType = CS_INTERFACE */
	0x02,		/* bDescriptorSubtype = Abstract Control Management Functional Descriptor */
	0x02,		/* bmCapabilities = SET_LINE_CODING,GET_LINE_CODING,SET_CONTROL_LINE_STATE,SERIAL_STATE */
	//
	5,		/* bFunctionLength */
	0x24,		/* bDescriptorType = CS_INTERFACE */
	0x06,		/* bDescriptorSubtype = Union Functional descriptor */
	0,		/* bMasterInterface */
	1,		/* bSlaveInterface0 */
	//
	5,		/* bFunctionLength */
	0x24,		/* bDescriptorType = CS_INTERFACE */
	0x01,		/* bDescriptorSubtype = Call Management Functional Descriptor */
	0x00,		/* bmCapabilities */
	1,		/* bDataInterface */
};
static const ENDPOINT_DESCRIPTOR endpoint_desc_ep1in = {
	bLength:		sizeof(ENDPOINT_DESCRIPTOR),
	bDescriptorType:	ENDPOINT_DESCRIPTOR_TYPE,
	bEndpointAddress:	0x81,				/* EP1IN */
	bAttributes:		3,				/* Interrupt */
	wMaxPacketSize:		EP1_PACKET_SIZE,
	bInterval:		2,				/* 2[ms] */
};
static const INTERFACE_DESCRIPTOR interface_desc_1 = {
	bLength:		sizeof(INTERFACE_DESCRIPTOR),
	bDescriptorType:	INTERFACE_DESCRIPTOR_TYPE,
	bInterfaceNumber:	1,
	bNumEndpoints:		2,
	bInterfaceClass:	0x0A,				/* Data Interface Class */
};
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,
};
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 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"
};

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

typedef struct _CDCACM {
	unsigned char* rxbuf/*[rxcap]*/;
	int rxcap;
	int rxpos;
	int rxlen;
	int rxlog;
	void (*rxdone)();
	//
	unsigned char* txbuf/*[txcap]*/;
	int txcap;
	int txpos;
	int txlen;
	int txlog;
	void (*txdone)();
} CDCACM;
static CDCACM cdcacm;

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 class_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void ep2_rxdone();
static void ep2_rxsub();
static void ep2_txdone();
static void ep2_txsub();

static const USBINF usb_inf = {
	/* USBC mode */
	mode:			D12_ENDP_NONISO,
	/* USBC raw event */
	/* Communication Class Interface */
/*{{未使用*/
//	ep1_txdone:		ep1_txdone,
/*}}未使用*/
	/* Data Class Interface */
	ep2_rxdone:		ep2_rxdone,
	ep2_txdone:		ep2_txdone,
	/* Device request */
	get_descriptor:		get_descriptor,
	set_configuration:	set_configuration,
	set_interface:		set_interface,
	/* Class request */
	class_request:		class_request,
};

/* * Wed Oct 31 22:31:30 JST 2007 Naoyuki Sawa
 * - 一度でもSERIAL_STATEを通知すると(CTS)=1になり、二度と(CTS)=0に戻りません。
 *   usbser.sysの仕様みたいです。(Win2K SP4にて実験)
 * - wIndexには、Communication Class Interface、または、Data Class Interface、
 *   どちらのInterface番号を指定すべきなのか不明でした。
 *   Win2K SP4で実験したところ、どちらでも正しく動作しました。
 *   wIndex=0(Communication Class Interface)としておくことにします。
 * * Sun Nov 04 14:43:12 JST 2007 Naoyuki Sawa
 * - (CTS)=0に戻らない挙動の理由について、興味深い記事を見つけました。
 *	「戸田孝の私設琵琶湖博物館(http://www.lbm.go.jp/toda/)」さん
 *	→「RS-232Cケーブルのクロス仕様に関する考察(http://www.lbm.go.jp/toda/comp/rscross.html)」
 *   「keep/RS-232Cケーブルのクロス仕様に関する考察.7z」に保存しておいたので、参照してください。
 */
typedef struct _SerialStateNotification {
	unsigned char bmRequestType;	/* +0,1 = 0xA1 (In,ClassRequest,1)*/
	unsigned char bNotification;	/* +1,1 = 0x20 (SERIAL_STATE) */
	unsigned short wValue;		/* +2,2 = 0 (固定) */
	unsigned short wIndex;		/* +4,2 = 0 (Interface) */
	unsigned short wLength;		/* +6,2 = 2 (DataLength) */
	/*{{UART State Bitmap*/		/* +8,2 */
	unsigned short bRxCarrier  : 1;	/*      = (CD) */
	unsigned short bTxCarrier  : 1;	/*      = (DSR) */
	unsigned short bBreak      : 1;
	unsigned short bRingSignal : 1;	/*      = (RI) */
	unsigned short bFraming    : 1;
	unsigned short bParity     : 1;
	unsigned short bOverRun    : 1;
	unsigned short             : 9;
	/*}}UART State Bitmap*/
} SerialStateNotification;		/* =10 */
static SerialStateNotification serial_state_notification = { 0xA1,0x20,0,0,2 };

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

static void
get_descriptor(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	static unsigned char tmpbuf[sizeof(CONFIGURATION_DESCRIPTOR) +	/* config_desc */
				    sizeof(INTERFACE_DESCRIPTOR) +	/* interface_desc_0 */
				    (5+4+5+5) +				/* cdc_class_specific_desc_0 */
				    sizeof(ENDPOINT_DESCRIPTOR) +	/* endpoint_desc_ep1in */
				    sizeof(INTERFACE_DESCRIPTOR) +	/* interface_desc_1 */
				    sizeof(ENDPOINT_DESCRIPTOR) +	/* endpoint_desc_ep2out */
				    sizeof(ENDPOINT_DESCRIPTOR)];	/* endpoint_desc_ep2in */
	//
	int type  = HIBYTE(req->wValue);
	int index = LOBYTE(req->wValue);

	switch(type) {
	case DEVICE_DESCRIPTOR_TYPE:
		usb_ep0_tx(&device_desc, sizeof(DEVICE_DESCRIPTOR));
		return;
	case CONFIGURATION_DESCRIPTOR_TYPE:
		switch(index) {
		case 0:
			len = 0;
			memcpy(&tmpbuf[len], &config_desc              , sizeof(CONFIGURATION_DESCRIPTOR)); len += sizeof(CONFIGURATION_DESCRIPTOR);
			memcpy(&tmpbuf[len], &interface_desc_0         , sizeof(INTERFACE_DESCRIPTOR)    ); len += sizeof(INTERFACE_DESCRIPTOR)    ;
			memcpy(&tmpbuf[len], &cdc_class_specific_desc_0, (5+4+5+5)                       ); len += (5+4+5+5)                       ;
			memcpy(&tmpbuf[len], &endpoint_desc_ep1in      , sizeof(ENDPOINT_DESCRIPTOR)     ); len += sizeof(ENDPOINT_DESCRIPTOR)     ;
			memcpy(&tmpbuf[len], &interface_desc_1         , sizeof(INTERFACE_DESCRIPTOR)    ); len += sizeof(INTERFACE_DESCRIPTOR)    ;
			memcpy(&tmpbuf[len], &endpoint_desc_ep2out     , sizeof(ENDPOINT_DESCRIPTOR)     ); len += sizeof(ENDPOINT_DESCRIPTOR)     ;
			memcpy(&tmpbuf[len], &endpoint_desc_ep2in      , sizeof(ENDPOINT_DESCRIPTOR)     ); len += sizeof(ENDPOINT_DESCRIPTOR)     ;
			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)
{
	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
class_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	static unsigned char line_coding_structure[7];

	switch(req->bRequest) {
/*{{未対応*/
//	/* SEND_ENCAPSULATED_COMMAND (Required) */
//	case 0x00:
//	/* GET_ENCAPSULATED_RESPONSE (Required) */
//	case 0x01:
/*}}未対応*/
	/* SET_LINE_CODING (Optional) */
	case 0x20:
		memcpy(line_coding_structure, dat, 7);
		usb_ep0_tx(NULL, 0); /* ACK */
		return;
	/* GET_LINE_CODING (Optional) */
	case 0x21:
		usb_ep0_tx(line_coding_structure, 7);
		return;
	/* SET_CONTROL_LINE_STATE (Optional) */
	case 0x22:
		usb_ep0_tx(NULL, 0); /* ACK */
		/* * Wed Oct 31 22:31:30 JST 2007 Naoyuki Sawa
		 * - (DTR)を(DSR)にループバックすることにします。
		 * - 万一、Interruptポーリング間隔よりも短い間隔でSET_CONTROL_LINE_STATEが到着して、
		 *   前回の"D12_WriteEndpoint(EP1IN,〜)"が無効になっても、問題ありません。
		 *   SERIAL_STATEの{bRxCarrier,bTxCarrier}は、最新状態のみを通知すれば良いからです。
		 *   ただし、{bBreak,bRingSignal,bFraming,bParity,bOverRun}はトリガ通知なので、
		 *   失われると問題が発生しますが、2007/10/31現在は使っていないので大丈夫です。
		 *   → * Fri Dec 07 16:28:26 JST 2007 Naoyuki Sawa
		 *   → よく考えたら、D12_WriteEndpoint()はバッファがフルのときは書き込まない仕様で作っていたので、前回の状態が優先されてしまいます。
		 *   → Interruptポーリング間隔よりも短い間隔でSET_CONTROL_LINE_STATEが到着することはまず無いと思うので、たぶん大丈夫だと思いますが、
		 *   → もし問題が発生した場合は、ep1_txdoneコールバックを利用するよう、実装を変更する必要が生じるかも知れません。
		 * - SERIAL_STATEを通知することによって、Windowsは自動的に(CTS)=1と判断します。
		 *   詳しくは、SerialStateNotification構造体の上に書いたコメントを参照してください。
		 */
		//serial_state_notification.bTxCarrier = (req->wValue & 1);
		//↓* Sun Nov 04 14:04:02 JST 2007 Naoyuki Sawa
		//↓- (DTR)を(DSR)だけでなく、(DSR)と(CD)にループバックするよう変更しました。
		//↓  一般的なクロスケーブル接続は以下のようなので、それに従うためです。
		//↓
		//↓	 DTE(パソコン等) 			 DTE(パソコン等) 
		//↓	-----------------			-----------------
		//↓	信号名称 ピン番号			信号名称 ピン番号
		//↓	   GND       1   -----------------------   GND       1   
		//↓	   TxD       2   -----------+   +-------   TxD       2   
		//↓	                        +---|---+                        
		//↓	   RxD       3   <------+   +---------->   RxD       3   
		//↓	   RTS       4   -----------+   +-------   RTS       4   
		//↓	                        +---|---+                        
		//↓	   CTS       5   <------+   +---------->   CTS       5   
		//↓	   DSR       6   <--+               +-->   DSR       6   
		//↓	   GND       7   ---|---------------|---   GND       7   
		//↓	   CD        8   <--+-------+   +---+-->   CD        8   
		//↓	   TxC2     15          +---|---+          TxC2     15   
		//↓	   RxC      17          |   |              RxC      17   
		//↓	   DTR      20   -------+   +-----------   DTR      20   
		//↓	   RI       22   			   RI       22   
		//↓	   TxC1     24   			   TxC1     24   
		//↓
		//↓	『組込みI/Oインタフェース基礎講座』宇野俊夫,翔泳社
		//↓	p.159図4-14:RS-232Cを利用したDTE同士の接続(クロスケーブルを使った接続例)より
		//↓- 現実には、(CD)を監視しているアプリケーションはほとんど無いと思うので、
		//↓  今回の変更はあまり意味が無いかも知れませんが、念のために変更しておくことにしました。
		//↓
		if(req->wValue & 1) { /* (DTR) */
			serial_state_notification.bRxCarrier = 1; /* (CD) */
			serial_state_notification.bTxCarrier = 1; /* (DSR) */
		} else {
			serial_state_notification.bRxCarrier = 0; /* (CD) */
			serial_state_notification.bTxCarrier = 0; /* (DSR) */
		}
		len = D12_WriteBuffer(EP1IN, &serial_state_notification, 10);
		if(len != -1) {
			D12_ValidateBuffer(EP1IN);
		}
		return;
	}
	usb_ep0_stall(); /* 未対応 */
}

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

/* * Sat Dec 08 14:00:19 JST 2007 Naoyuki Sawa
 * - NOTE: 受信ダブルバッファの、バッファ#1がフル(まだ読み出していない)、バッファ#2が空の状態で、
 *   バッファ#2に、新しいパケットが到着したタイミングでも、受信完了割り込みが発生するようです。
 *   今回の実装には特に関係有りませんが、今後のために、以上のとおり記録しておくことにします。
 */
static void
ep2_rxdone()
{
	ep2_rxsub();
	if(cdcacm.rxdone) {
		cdcacm.rxdone();
	}
}

static void
ep2_rxsub()
{
	unsigned char _dat[EP2_NONISO_PACKET_SIZE];
	unsigned char* dat;
	int len;
	int pos;

	for(;;) { /* ダブルバッファに到着したパケットを全て処理 */
		len = D12_ReadBuffer(EP2OUT, _dat, sizeof _dat);
		if((len == -1) || /* 到着したパケットがもう無い */
		   (len > (cdcacm.rxcap - cdcacm.rxlen))) {
			/* cdcacm.rxbufに空きが無い場合は、到着したパケットをクリアせずに、USBCの受信バッファに残しておきます。
			 * 受信バッファに前のパケットが残っていれば、PCは次のパケットを送信できず、ブロックした状態となります。
			 * あとで、cdcacm_recv()が呼び出され、cdcacm.rxbufに空きができたら、残っていたパケットを読み出します。
			 * それによって、受信バッファが空となって、PCのブロック状態も解除されて、次のパケットが送られてきます。
			 */
			break;
		}
		D12_ClearBuffer(EP2OUT);
		if(len) {
			pos = (cdcacm.rxpos + cdcacm.rxlen);
			cdcacm.rxlen += len;
			dat = _dat;
			do {
				if(pos >= cdcacm.rxcap) {
					pos -= cdcacm.rxcap;
				}
				cdcacm.rxbuf[pos] = *dat++;
				pos++;
			} while(--len);
		}
	}
}

static void
ep2_txdone()
{
	if(cdcacm.txlen) {
		ep2_txsub();
	} else {
		if(cdcacm.txdone) {
			cdcacm.txdone();
		}
	}
}

static void
ep2_txsub()
{
	/* * Thu Nov 08 17:28:08 JST 2007 Naoyuki Sawa
	 * - 64×Nバイトのデータを送信したときに、Windows側で受信が完了しない問題を回避しました。(WindowsXP SP2にて現象と修正を確認)
	 *   以下に、考察を記録しておきます。
	 * - Data Class Interface上のBulkトランスファは、シリアル通信データを運びます。
	 *   USB仕様によると、Bulkトランスファは、以下の方法で区切りを示すことになっています。
	 * 	＞ A bulk transfer is complete when the endpoint does one of the following:
	 * 	＞ ･ Has transferred exactly the amount of data expected						(←暗黙の区切り)
	 * 	＞ ･ Transfers a packet with a payload size less than wMaxPacketSize or transfers a zero-length packet	(←明示的な区切り)
	 * 	『Universal Serial Bus Specification Revision 2.0』「5.8.3 Bulk Transfer Packet Size Constraints」より
	 *   シリアル通信データはストリームですから、Data Class Interface上のBulkトランスファには、本来、区切りが不要なはずです。
	 *   言い換えると、常に1トランザクション＝1トランスファと見なすことができ、ペイロードサイズぴったり(=64バイト)でも暗黙の区切りと判断すべきです。
	 *   ところが、Windowsのusbser.sysは、Data Class Interface上のBulkトランスファにも、明示的な区切りを要求するようです。
	 *   トランザクションの区切りは、ホストコントローラや下位ドライバ(usbd.sys)ではなく、usbser.sysが判断すべきことです。
	 *   usbser.sysの実装が不適切なのではないかと思います。(もしかしたら、僕の理解が間違っているのかもしれませんけれど…)
	 * - 解決策としては、64バイト単位で送信が完了した場合、追加の0バイトパケットを送信するのが正しい方法です。
	 *   しかしそのためには、clipusbモジュールでEP0(Control Pipe)に対して行っているのと同様な、状態管理の追加が必要となり、複雑になりすぎます。
	 *   そこで、1トランザンション当りの最大転送サイズを、63バイト以下に制限するという方法を用いることにしました。
	 *   送信待ちデータが64バイト以上残っている場合も、63バイト以下のトランザンションに分割して送信します。
	 *   全てのトランザクションが63バイト以下となるので、usbser.sysは、常に1トランザクション＝1トランスファと見なしてくれます。
	 * - もし、将来的にusbser.sysの挙動が変更されて、最初に述べた望ましい仕様で区切りを判断するようになったとしても、影響は無いはずです。
	 *   64バイト以上の大きなデータを送信するときに、1トランザクション当り1バイト効率が低下するだけで、正常に動作するはずです。
	 * - (※要調査)
	 *   フリーツールのBterm.exeを使って通信テストを行うと、64バイト以上のデータを受信したときに、Bterm.exeが不明なエラー(Err00)を表示するようです。
	 *   また、11264バイト(1画面分)のデータ通信を行うと、Bterm.exeが強制終了しました。
	 *   ハイパーターミナルを使って通信テストを行った場合には問題は発生しないので、Bterm.exeの問題だと思います。(バッファ溢れ?)
	 *   ペイロードサイズ(=64バイト)を境に不具合が発生するのはたまたまであり、本モジュールやusbser.sysが原因ではないと思います。
	 */
	unsigned char _dat[EP2_NONISO_PACKET_SIZE - 1/*上記コメント参照*/];
	unsigned char* dat;
	int len;
	int pos;

	for(;;) { /* ダブルバッファに書き込めるだけ全て書き込む */
		len = cdcacm.txlen < sizeof _dat ? cdcacm.txlen : sizeof _dat;
		if(!len) {
			break;
		}
		pos = cdcacm.txpos;
		dat = _dat;
		do {
			*dat++ = cdcacm.txbuf[pos];
			if(++pos == cdcacm.txcap) {
				pos = 0;
			}
		} while(--len);
		len = D12_WriteBuffer(EP2IN, _dat, dat - _dat);
		if(len == -1) { /* ダブルバッファが両面ともフル */
			break;
		}
		D12_ValidateBuffer(EP2IN);
		cdcacm.txlen -= len;
		cdcacm.txpos  = pos;
	}
}

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

void
cdcacm_start(int rxcap, int txcap, void (*rxdone)(), void (*txdone)())
{
	cdcacm_stop();
	memset(&cdcacm, 0, sizeof cdcacm);
	if(rxcap < EP2_NONISO_PACKET_SIZE) {
		rxcap = EP2_NONISO_PACKET_SIZE;
	}
	cdcacm.rxcap = rxcap;
	cdcacm.rxbuf = malloc(rxcap);
	if(!cdcacm.rxbuf) {
		DIE();
	}
	cdcacm.rxdone = rxdone;
	if(txcap < EP2_NONISO_PACKET_SIZE) {
		txcap = EP2_NONISO_PACKET_SIZE;
	}
	cdcacm.txcap = txcap;
	cdcacm.txbuf = malloc(txcap);
	if(!cdcacm.txbuf) {
		DIE();
	}
	cdcacm.txdone = txdone;
	usb_start(&usb_inf);
}

void
cdcacm_stop()
{
	usb_stop();
	free(cdcacm.rxbuf);
	free(cdcacm.txbuf);
	memset(&cdcacm, 0, sizeof cdcacm);
}

void
cdcacm_reset()
{
ENTER_CS;
	cdcacm.rxpos = 0;
	cdcacm.rxlen = 0;
	cdcacm.rxlog = 0;
	cdcacm.txpos = 0;
	cdcacm.txlen = 0;
	cdcacm.txlog = 0;
LEAVE_CS;
}

int
cdcacm_recv(void* _dat, int maxlen)
{
	unsigned char* dat = _dat;
	int len;

ENTER_CS;
	do {
		len = cdcacm.rxlen < maxlen ? cdcacm.rxlen : maxlen;
		if(len) {
			maxlen       -= len;
			cdcacm.rxlen -= len;
			cdcacm.rxlog += len;
			do {
				*dat++ = cdcacm.rxbuf[cdcacm.rxpos];
				if(++cdcacm.rxpos == cdcacm.rxcap) {
					cdcacm.rxpos = 0;
				}
			} while(--len);
		}
		/* cdcacm.rxbufに空きができたら、その分USBCの受信バッファから読み出す。
		 * USBCの受信バッファをできるだけ早く空にするため、必ず一度は呼び出す。
		 */
		ep2_rxsub();
	} while(cdcacm.rxlen && maxlen);
LEAVE_CS;
	return dat - (unsigned char*)_dat;
}

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

	if(maxlen < 0) {
		maxlen = strlen((char*)dat);
	}
ENTER_CS;
	do {
		len = (cdcacm.txcap - cdcacm.txlen) < maxlen ? (cdcacm.txcap - cdcacm.txlen) : maxlen;
		if(len) {
			pos = (cdcacm.txpos + cdcacm.txlen);
			maxlen       -= len;
			cdcacm.txlen += len;
			cdcacm.txlog += len;
			do {
				if(pos >= cdcacm.txcap) {
					pos -= cdcacm.txcap;
				}
				cdcacm.txbuf[pos] = *dat++;
				pos++;
			} while(--len);
		}
		/* cdcacm.txbufに蓄積されているデータを、USBCの送信バッファに書き込む。
		 * USBCの送信バッファにできるだけ早く書き込むため、必ず一度は呼び出す。
		 */
		ep2_txsub();
	} while((cdcacm.txcap - cdcacm.txlen) && maxlen);
LEAVE_CS;
	return dat - (unsigned char*)_dat;
}

void
cdcacm_stat(CDCACMSTAT* stat)
{
ENTER_CS;
	stat->rxlen = cdcacm.rxlen;
	stat->rxlog = cdcacm.rxlog;
	stat->txlen = cdcacm.txlen;
	stat->txlog = cdcacm.txlog;
LEAVE_CS;
}
