/*	
 *	clipusb.c
 *
 *	USBドライバを実装します。
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2015 Naoyuki Sawa
 *
 *	* Sat Oct 30 05:43:00 JST 2004 Naoyuki Sawa
 *	- 作成開始。
 *	* Wed Jul 06 05:26:00 JST 2005 Naoyuki Sawa
 *	- エンドポイントインデクスの定義追加に伴い、直接数値を記述していた部分を、
 *	  エンドポイントインデクスのシンボルを使うように修正しました。
 *	* Fri Jul 08 18:04:00 JST 2005 Naoyuki Sawa
 *	- D12_CLOCKRUNNINGを常に有効とするように変更しました。
 *	- D12_CLOCKRUNNINGが無効の場合に、PC上で「デバイスの停止」を行うと、
 *	  PCのデバイスマネージャ上に「不明なデバイス」として残ってしまいます。
 *	  P/ECE側でも割り込みが無限に発生するなど、予測できない異常動作となります。
 *	- 「デバイスの停止」でなく、USBケーブルを取り外した場合は、問題ありません。
 *	  P/ECEドライバは「デバイスの停止」アイコンを表示しないので、
 *	  P/ECEカーネルがD12_CLOCKRUNNINGを無効としているのは、問題ありません。
 *	  既知のとおり、D12_CLOCKRUNNING無効の方が、電池動作時に省電力となります。
 *	* Sun Jul 31 00:57:00 JST 2005 Naoyuki Sawa
 *	- D12_WriteEndpoint()を、バッファが空で無い場合は書き込まないよう変更しました。
 *	- 一般的なデバイスリクエストの処理を内蔵しました。(CLEAR_FEATURE、SET_ADDRESS)
 *	* Wed Aug 03 22:08:00 JST 2005 Naoyuki Sawa
 *	- USB切断〜接続ウェイトを200[ms]→500[ms]に変更しました。
 *	  Windows XPの場合、Windows 2000よりもUSB切断〜接続ウェイトを長めに取らないと、
 *	  P/ECE USBデバイスの変化を認識しない場合があるみたいだからです。
 *	* Mon Aug 22 04:24:00 JST 2005 Naoyuki Sawa
 *	- usb_init(),usb_free()->usb_start(),usb_stop()に名前変更しました。
 *	* Fri Dec 07 16:25:46 JST 2007 Naoyuki Sawa
 *	- D12_AcknowledgeSetup()を修正しました。
 *	  可読性向上のためだけの修正であり、修正前のコードでも問題ありません。
 *	* Sat Dec 08 00:00:11 JST 2007 Naoyuki Sawa
 *	- 受信オーバーランを回避する実装を可能とするための仕様変更を行いました。
 *	  これまでは、ep1_txdone(),ep2_rxdone()をコールバックする前に受信バッファを読み出し、クリアしていましたが、
 *	  今後は、ep1_txdone(),ep2_rxdone()内で受信バッファを読み出し、処理できた場合のみクリアする仕様となりました。
 *	- この仕様変更を吸収し、これまでと同じ動作とする方法は、clipuio.c,clipeth.cを参照してください。
 *	  この仕様変更に対応し、受信オーバーランを回避する実装とする方法は、clipacm.cを参照してください。
 *	* Tue Dec 11 14:59:31 JST 2007 Naoyuki Sawa
 *	- D12_WriteBuffer()の戻り値を変更しました。
 *	  0バイト送信(ありえます!!)と送信バッファフルを区別するために、
 *	  送信バッファフルの戻り値はは0ではなく-1を返すことにしました。
 *	* Thu Sep 04 22:00:29 JST 2014 Naoyuki Sawa
 *	- 一般的なデバイスリクエストの処理に、get_statusを追加しました。
 *	* Sat Sep 06 23:41:20 JST 2014 Naoyuki Sawa
 *	- device_request_endpoint()のバグを修正しました。
 *	  エンドポイント番号の対応が間違っていました。
 *	  今迄このバグが原因でget_status()とclear_feature()が正しく動作していませんでした。
 *	  今後は正しく動作するようになりました。
 *	* Tue Dec 30 15:44:40 JST 2014 Naoyuki Sawa
 *	- usb_start()にて、D12_NOLAZYCLOCKを常に有効とするように変更しました。
 *	  これまで、D12_NOLAZYCLOCKを指定するとサスペンド時に高速クロックを出力し、D12_NOLAZYCLOCKを指定しなければサスペンド時に低速クロックを出力する仕様だと思っていたのですが、
 *	  正しくは、D12_NOLAZYCLOCKを指定するとサスペンド時にクロック出力を停止し、D12_NOLAZYCLOCKを指定しなければサスペンド時に低速クロックを出力する仕様でした。
 *	  P/ECEの回路はPDIUSBD12のクロック出力を使用していないので、クロック出力を停止しても問題無く、D12_NOLAZYCLOCKを指定可能で、D12_NOLAZYCLOCKを指定する方が省電力になります。
 *	- ■参照資料:「FAQ - PDIUSBD12 1 October 1998」(faq_pdiusbd12.pdf)
 *	  ┃□3.5 What is the CLKOUT frequency during suspend?
 *	  ┃The behaviour of the output clock is configured based on the Configuration Byte written using the Set Mode Command (0xF3).
 *	  ┃┌───────────────┬────────────────────────────────────────┐
 *	  ┃│Configuration Byte            │CLKOUT                                                                          │
 *	  ┃├───────┬───────┤                                                                                │
 *	  ┃│No Lazy Clock │Clock Running │                                                                                │
 *	  ┃├───────┼───────┼────────────────────────────────────────┤
 *	  ┃│0             │0             │CLKOUT switches to Lazy Clock on suspend.                                       │
 *	  ┃│              │              │The output frequency is 18KHz to 48 KHz.                                        │
 *	  ┃│              │              │The PLL clock switched off to reduce current consumption.                       │
 *	  ┃├───────┼───────┼────────────────────────────────────────┤
 *	  ┃│1             │0             │The CLKOUT stops on suspend.                                                    │
 *	  ┃├───────┼───────┼────────────────────────────────────────┤
 *	  ┃│0             │1             │CLKOUT switches to Lazy Clock on suspend.                                       │
 *	  ┃│              │              │The output frequency is 18KHz to 48 KHz.                                        │
 *	  ┃│              │              │The PLL clock remains on.                                                       │
 *	  ┃├───────┼───────┼────────────────────────────────────────┤
 *	  ┃│1             │1             │The suspend state does not affect the CLKOUT frequency with this configuration. │
 *	  ┃└───────┴───────┴────────────────────────────────────────┘
 *	- ちなみに、P/ECEカーネル1.20の変更は、この点において間違っています。(■参照資料 http://aquaplus.jp/piece/dl/up118to120.txt の 2003.02.14)
 *	  しかし実際には、D12_NOLAZYCLOCKを指定しても(クロックを止めても)、指定しなくても(低速クロックを出力しても)、消費電力の差は小さいようです。
 *	  「D12_CLOCKRUNNING」の方は指定の有無でサスペンド時の消費電力の差が大きいのですが、「D12_NOLAZYCLOCK」の方はそれほど変わらないみたいです。
 *	  だから現状の'D12_NOLAZYCLOCKを指定しない'ままでも大きな問題はないのですが、もう一度カーネルを更新する機会が有ったら修正させて欲しいです。
 *	* Sun Jan 04 14:56:51 JST 2015 Naoyuki Sawa
 *	- usb_isr()の定義にISR_DEF()マクロを使うように変更しました。コードサイズが8バイト小さくなりました。動作内容は変わりません。
 */
#include "clip.h"

/****************************************************************************
 *	PDIUSBD12 ポートアクセス
 ****************************************************************************/

/* ★★★ 重要!! ★★★
 *
 * * データポートから二命令連続で読み込むと、正しい値が読み出せません。
 *   たとえば、次のようなコードが生成されると、正しく動かなくなります。
 *
 *	xld.w %r9, 0x400000
 *	xld.w %r10, [%r9] ────┬ この間隔が速すぎる!!
 *	xld.w %r10, [%r9] ────┘
 *
 *   D12_ReadEndpoint()での、ダミーとサイズの読み込みなどが危険です。
 *
 * * 正確な原因は不明です。
 *   CPU〜USBCへのCE7信号線に含まれているディレイ生成回路が原因ではないかと思います。
 *   (必要なディレイなのでしょうけれど、そのぶん後ろに影響が出てる?)
 *
 * * 正しく動作させるには、連続読み込みの間にnopが必要です。
 *
 *	xld.w %r9, 0x400000
 *	xld.w %r10, [%r9] ────┐
 *	nop ←注目!!              ├ この間隔ならば大丈夫
 *	xld.w %r10, [%r9] ────┘
 *	nop ←注目!!
 *
 * * 実際には関数呼び出しのオーバーヘッドがあるので、通常この問題は表面化しません。
 *   D12OutCmd()をインライン展開しない限り、問題となるコードは生成されないはずです。
 *   通常の命令実行手順は、次のようになります。
 *
 *	; 1回目のD12InDat呼び出し
 *	xcall D12InDat
 *	xld.w %r9, 0x400000
 *	xld.w %r10, [%r9] ────┐
 *	ret                       │
 *	; 2回目のD12InDat呼び出し ├ 充分な間隔
 *	xcall D12InDat            │
 *	xld.w %r9, 0x400000       │
 *	xld.w %r10, [%r9] ────┘
 *	ret
 *
 * * しかしながら、今後もしアセンブラで高速化した場合に忘れないように、
 *   (今現在は無駄ですが)明示的にnopを挟んでおくことにしました。
 *
 *	; 1回目のD12InDat呼び出し
 *	xcall D12InDat
 *	xld.w %r9, 0x400000
 *	xld.w %r10, [%r9] ────┐
 *      nop ←注目!!              │
 *	ret                       │
 *	; 2回目のD12InDat呼び出し ├ 充分すぎる間隔
 *	xcall D12InDat            │
 *	xld.w %r9, 0x400000       │
 *	xld.w %r10, [%r9] ────┘
 *      nop ←注目!!
 *	ret
 *
 * * コマンド/データポートへの二連続命令書き込みは検証していませんが、
 *   たぶん同じ不具合が出ると思うので、こちらもnopを挟むことにしました。
 */

#define D12_COMMAND	(*(volatile unsigned char *)0x400004)	/* コマンドレジスタ */
#define D12_DATA	(*(volatile unsigned char *)0x400000)	/* データレジスタ */

static void
D12OutCmd(int cmd)
{
	D12_COMMAND = cmd;
	asm("nop"); /* 必要 */
}

static void
D12OutDat(int dat)
{
	D12_DATA = dat;
	asm("nop"); /* 必要 */
}

static int
D12InDat()
{
	int dat;
	dat = D12_DATA;
	asm("nop"); /* 必要 */
	return dat;
}

/****************************************************************************
 *	PDIUSBD12 コマンド発行
 ****************************************************************************/

/* Initialization commands */
#define SET_ADDRESS_ENABLE		0xd0
#define SET_ENDPOINT_ENABLE		0xd8
#define SET_MODE			0xf3
#define SET_DMA				0xfb
/* Data flow commands */
#define READ_INTERRUPT_REGISTER		0xf4
#define SELECT_ENDPOINT			0x00	/* 0x00〜0x05 */
#define READ_LAST_TRANSACTION_STATUS	0x40	/* 0x40〜0x45 */
#define READ_ENDPOINT_STATUS		0x80	/* 0x80〜0x85 */
#define READ_BUFFER			0xf0
#define WRITE_BUFFER			0xf0
#define SET_ENDPOINT_STATUS		0x40	/* 0x40〜0x45 */
#define ACKNOWLEDGE_SETUP		0xf1
#define CLEAR_BUFFER			0xf2
#define VALIDATE_BUFFER			0xfa
#define SEND_RESUME			0xf6
#define READ_CURRENT_FRAME_NUMBER	0xf5
/* Undocumented commands */
#define READ_CHIP_ID			0xfd

void
D12_SetAddressEnable(int addr, int enable)
{
	D12OutCmd(SET_ADDRESS_ENABLE);
	if(enable) addr |= 0x80;
	D12OutDat(addr);
}

void
D12_SetEndpointEnable(int enable)
{
	D12OutCmd(SET_ENDPOINT_ENABLE);
	D12OutDat(enable ? 0x01 : 0x00);
}

void
D12_SetMode(int config, int clkdiv)
{
	D12OutCmd(SET_MODE);
	D12OutDat(config);
	D12OutDat(clkdiv);
}

void
D12_SetDMA(int mode)
{
	D12OutCmd(SET_DMA);
	D12OutDat(mode);
}

int
D12_ReadInterruptRegister()
{
	int lo;
	int hi;
	D12OutCmd(READ_INTERRUPT_REGISTER);
	lo = D12InDat();
	hi = D12InDat();
	return lo | hi << 8;
}

int
D12_SelectEndpoint(int endp)
{
	D12OutCmd(SELECT_ENDPOINT | endp);
	return D12InDat();
}

int
D12_ReadLastTransactionStatus(int endp)
{
	D12OutCmd(READ_LAST_TRANSACTION_STATUS | endp);
	return D12InDat();
}

int
D12_ReadEndpointStatus(int endp)
{
	D12OutCmd(READ_ENDPOINT_STATUS | endp);
	return D12InDat();
}

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//int
//D12_ReadEndpoint(int endp, void* buf, int maxlen)
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
int
D12_ReadBuffer(int endp, void* buf, int maxlen)
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
{
	unsigned char* dst = (unsigned char*)buf;
	//
	int dummy;
	int len;
	int i;

	/* 読み込むエンドポイントを選択します。
	 * 受信完了していなければ、まだ読み込めません。
	 * * 受信完了(FULL )の判定は“D12_FULLEMPTY=1”です。
	 *   送信完了(EMPTY)の判定の“D12_FULLEMPTY=0”とは0<=>1逆です。要注意!!
	 * * 0バイト受信(ありえます!!)と受信未完了を区別するために、
	 *   受信未完了時の戻り値はは0ではなく、-1を返すことにしました。要注意!!
	 */
	if(!(D12_SelectEndpoint(endp) & D12_FULLEMPTY)) return -1/*受信未完了*/;

	/* 受信データを読み込みます。 */
	D12OutCmd(READ_BUFFER);
	dummy = D12InDat(); /* ダミー */
	len   = D12InDat(); /* 受信バイト数 */
	if(len > maxlen) len = maxlen; /* 読み込みバイト数切り詰め */
	for(i = 0; i < len; i++) dst[i] = D12InDat();

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//	/* 受信バッファを空にします。 */
//	D12OutCmd(CLEAR_BUFFER);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

	return len;
}

/* * 2005/07/31追記
 * - エンドポイントのパケットサイズを超えるlenを指定しないで下さい!!
 *   パケットサイズを超えるlenが指定された場合の動作は、未定義です。
 * - usb_inf.modeを見れば、各エンドポイントのパケットサイズが判るので、
 *   この関数にてlenを切り詰めることも可能ですが、そうはしませんでした。
 *   D12_*関数群には複雑な処理を含めないで、単純に保ちたかったからです。
 */
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//int
//D12_WriteEndpoint(int endp, const void* ptr, int len)
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
int
D12_WriteBuffer(int endp, const void* ptr, int len)
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
{
	const unsigned char* src = (const unsigned char*)ptr;
	int i;

//#if 0
////	/* 書き込むエンドポイントを選択します。
////	 * 送信完了していなければ、まだ書き込めません。
////	 * * 送信完了(EMPTY)の判定は“D12_FULLEMPTY=0”です。
////	 *   受信完了(FULL )の判定の“D12_FULLEMPTY=1”とは0<=>1逆です。要注意!!
////	 */
////	if(D12_SelectEndpoint(endp) & D12_FULLEMPTY) return 0;
//#else
//	/* インタラプト転送で未転送データを破棄することがあるようなので、
//	 * EMPTY検査はやめました。
//	 * ただし、未転送データが残っているときに次のデータを書き込むと、
//	 * 本当に前のデータ転送がキャンセルされるのかどうかは未確認です。
//	 */
//	D12_SelectEndpoint(endp);
//#endif
////////↓2005/07/31 やっぱり空でないときは書き込まないように変更しました。
	/* 書き込むエンドポイントを選択します。
	 * 送信完了していなければ、まだ書き込めません。
	 * * 送信完了(EMPTY)の判定は“D12_FULLEMPTY=0”です。
	 *   受信完了(FULL )の判定の“D12_FULLEMPTY=1”とは0<=>1逆です。要注意!!
	 * [備考]
	 * * INバッファは、送信完了以外にも、SETUPパケットの受信やStall解除によってクリアされます。
	 *   OUTバッファは、CLEAR_BUFFER以外にも、SETUPパケットの受信やStall解除によってクリアされます。
	 */
//{{2007/12/11:D12_WriteBuffer()の戻り値を変更しました。
//	if(D12_SelectEndpoint(endp) & D12_FULLEMPTY) return 0;
//↓2007/12/11:D12_WriteBuffer()の戻り値を変更しました。
//↓	0バイト送信(ありえます!!)と送信バッファフルを区別するために、
//↓	送信バッファフルの戻り値はは0ではなく-1を返すことにしました。
	if(D12_SelectEndpoint(endp) & D12_FULLEMPTY) return -1;
//}}2007/12/11:D12_WriteBuffer()の戻り値を変更しました。

	D12OutCmd(WRITE_BUFFER);
	D12OutDat(  0); /* ダミー */
	D12OutDat(len); /* 送信バイト数 */
	for(i = 0; i < len; i++) D12OutDat(src[i]);

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//	/* 送信開始します。 */
//	D12OutCmd(VALIDATE_BUFFER);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

	return len;
}

void
D12_SetEndpointStatus(int endp, int stall)
{
	D12OutCmd(SET_ENDPOINT_STATUS | endp);
	D12OutDat(stall ? 0x01 : 0x00);
}

void
D12_AcknowledgeSetup(int endp)
{
	/*{{2007/12/07:修正*/
	//D12OutCmd(endp);
	//↓* Fri Dec 07 16:25:46 JST 2007 Naoyuki Sawa
	//↓- SelectEndpoint発行後の1 byte読み出しは任意なので、修正前のコードでも正しいのですが、
	//↓  プログラムの可読性向上のために、明示的にD12_SelectEndpoint()を使うよう修正しました。
	D12_SelectEndpoint(endp);
	/*}}2007/12/07:修正*/
	D12OutCmd(ACKNOWLEDGE_SETUP);
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//	/* Acknowledge Setupは、EP0OUT(endp=0)またはEP0IN(endp=1)に対してのみ発行します。
//	 * EP0OUT(endp=0)への発行の場合、先にD12_ReadEndpoint()を呼んでいると思いますが、
//	 * D12_ReadEndpoint()の中で最後に行われるClear Bufferは無効です。
//	 * EP0ではSETUP受信〜Acknowledge Setup間のClear/Validate Bufferが無効だからです。
//	 * よってAcknowledge Setup後にもういちどClear Bufferを行います。(今度は有効です)
//	 */
//	if(endp == EP0OUT) D12OutCmd(CLEAR_BUFFER);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
}

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
void
D12_ClearBuffer(int endp)
{
	D12_SelectEndpoint(endp);
	D12OutCmd(CLEAR_BUFFER);
}
void
D12_ValidateBuffer(int endp)
{
	D12_SelectEndpoint(endp);
	D12OutCmd(VALIDATE_BUFFER);
}
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

void
D12_SendResume()
{
	D12OutCmd(SEND_RESUME);
}

int
D12_ReadCurrentFrameNumber()
{
	int lo;
	int hi;
	D12OutCmd(READ_CURRENT_FRAME_NUMBER);
	lo = D12InDat();
	hi = D12InDat();
	return lo | hi << 8;
}

int
D12_ReadChipID()
{
	int lo;
	int hi;
	D12OutCmd(READ_CHIP_ID);
	lo = D12InDat();
	hi = D12InDat();
	return lo | hi << 8;
}

/****************************************************************************
 *	USBドライバ
 ****************************************************************************/

static PCEKSENT old_usb_isr;		/* 元の割り込みハンドラ(NULL:フックしていない) */
static USBINF usb_inf;			/* USBフック情報 */

#define TRAP_USBC		TRAP_P1	/* USBC割り込みベクタ番号 */
//#define RECONNECT_WAIT	200	/* USB切断〜接続ウェイト[ms] */
//↓2005/08/03 WinXPはWin2Kよりも長めのウェイトでないと認識しない場合があるみたいです。
#define RECONNECT_WAIT		500	/* USB切断〜接続ウェイト[ms] */

/* CONTROL_XFER.state */
#define STATE_IDLE		 0	/* コントロールデータ送受信なし */
#define STATE_RXING		 1	/* コントロールデータ受信中 */
#define STATE_TXING		 2	/* コントロールデータ送信中 */

/* CONTROL_XFER.data_buffer */
#define DATA_BUFFER_SIZE	16	/* 受信コントロールデータサイズ(最大)・調整可 */

typedef struct _CONTROL_XFER {
	int state;					/* 送受信状態 */
	DEVICE_REQUEST device_request;			/* デバイスリクエスト受信バッファ */
	int data_length;				/* 送受信コントロールデータサイズ(全体) */
	int data_count;					/* 送受信済みコントロールデータサイズ(進行) */
	unsigned char data_buffer[DATA_BUFFER_SIZE];	/* 受信コントロールデータバッファ */
	const unsigned char* data_ptr;			/* 送信コントロールデータポインタ(先頭) */
} CONTROL_XFER;
static CONTROL_XFER control_xfer;

//static void usb_isr();	//{{2015/01/04削除:usb_isr()の定義にISR_DEF()マクロを使うように変更しました。コードサイズが8バイト小さくなりました。動作内容は変わりません。}}
static void fn_usb_isr();
static void bus_reset();
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//static void ep0_rxdone(int st, const unsigned char* dat, int len);
//static void ep0_txdone(int st);
//static void ep1_rxdone(int st, const unsigned char* dat, int len);
//static void ep1_txdone(int st);
//static void ep2_rxdone(int st, const unsigned char* dat, int len);
//static void ep2_txdone(int st);
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
static void ep0_rxdone(int st);
static void ep0_txdone();
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
static void device_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
/*{{2005/07/31 一般的なデバイスリクエストの処理を内蔵しました。*/
static int device_request_endpoint(const DEVICE_REQUEST* req);
static void get_status(const DEVICE_REQUEST* req, const unsigned char* dat, int len);	/*{{2014/09/04追加:一般的なデバイスリクエストの処理に、get_statusを追加しました。}}*/
static void clear_feature(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
static void set_address(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
/*}}2005/07/31 一般的なデバイスリクエストの処理を内蔵しました。*/

#if 0 //{{2015/01/04変更:usb_isr()の定義にISR_DEF()マクロを使うように変更しました。コードサイズが8バイト小さくなりました。動作内容は変わりません。
/* 注意!!
 * INT_END2からretiで直接戻るので、(明示or暗黙の)ローカル変数が生成されてはいけません。
 * だからこの関数内ではなるべく処理を行わずに、実際の処理はfn_usb_isr()に任せます。
 */
static void
usb_isr()
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//{{2004/10/28 コメントアウト ★このブロックの記述は誤りです★
	//
	///* * 割り込み要因フラグ関連の状態遷移は次のようになります：
	// *
	// *								USBCの割り込み	USBCの		ITCの割り込み
	// *								要因フラグ	INT_N端子	要因フラグ
	// *	0. 割り込み発生						１		１		１
	// *	1. ITCの割り込み要因フラグクリア			１		１		０
	// *	2. 割り込み許可						１		１		０
	// *	   CPUの割り込み許可レベルはUSBC割り込みレベルに
	// *	   なってるので、USBC割り込みはブロックされる。
	// *	   より上位の割り込み(サウンドなど)は受け付ける。
	// *	3. USBCの割り込み要因フラグクリア			０		０		０
	// *	4. retiにより、CPUの割り込みレベル復元			０		０		０
	// *	   USBC割り込みも受け付けられるようになる。
	// *
	// *   この手順を見ると、一見、次のような心配をしてしまいます。
	// *
	// *	2.の時点で INT_N=1 なので再び ITCの割り込み要因フラグ=1 になり、(※USBC割り込みはレベルトリガ)
	// *	4.の時点までペンディングされた後、retiの直後にまた割り込みが発生するのではないか？
	// *	ITCの割り込み要因フラグをクリアする前に、USBCの割り込み要因フラグをクリアしなければいけないのではないか？
	// *
	// *   しかし、この心配は不要です。
	// *   ITCの割り込み要因フラグがセットされるのは、『実際に割り込みが発生した時点』だからです。
	// *   (s1c33209_221_222j.pdf II-5 ITC(割り込みコントローラ) B-II-5-2 「マスク可能な割り込みの発生条件」 参照)
	// *   DMAトリガなどとは異なり、割り込みブロック状態で割り込み要因フラグだけがセット・ペンディングされることはありません。
	// *   2.の時点で、USBC割り込みはCPUの割り込みレベルによってブロックされているので、割り込み要因フラグはセットされません。
	// */
	//INT_BEGIN2;
	//bINT_FK01_FP03_FP1 = 0;	/* (1.) */
	////他の割り込み処理との排他制御をきちんとやらない場合は、多重割り込みしない方が安全です。
	////ENABLE;		/* (2.) */
	//fn_usb_isr();		/* (3.) */
	//INT_END2;		/* (4.) */
	//
	//}}2004/10/28 コメントアウト ★このブロックの記述は誤りです★
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//
	// 2004/10/28 19:40 追記 ↑の記述は間違いでした!!
	//
	//“ITCの割り込み要因フラグがセットされるのは、『実際に割り込みが発生した時点』”★ではありませんでした★。
	// USBCのINT_N=1になった瞬間に、CPU側で割り込みがマスクされているか否か(実際に割り込みが発生するか否か)に関係なく、
	// 割り込み要因フラグ(bINT_FK01_FP03_FP1)がセットされます。
	// 上のコードではUSBCの割り込み要因をクリアする前にITCの割り込み要因をクリアしていますが、USBCのINT_N=1のままなので、
	// ITCの割り込み要因をクリアした直後にまたITCの割り込み要因フラグがセットされてしまいます。
	// その後、USBCの割り込み要因をクリアしてINT_N=0になるわけですが、既にITUへの割り込み要求は発行されてしまっているため、
	// もういちど無駄な割り込みが発生してしまいます。この様子を、次に示します。
	// 
	//	[正しくない手順]					USBCの割り込み	USBCの		ITCの割り込み
	//								要因フラグ	INT_N端子	要因フラグ
	//	0. 割り込み発生						１		１		１
	//	1. ITCの割り込み要因フラグクリア			１		１		０→直後に１
	//	2. (割り込み許可)					１		１		１
	//	3. USBCの割り込み要因フラグクリア			０		０		１
	//	4. retiにより、CPUの割り込みレベル復元			０		０		１
	//	5. 無駄な割り込みが発生!!				０		０		１
	//
	// 実際にこのように動作してしまっていたことを、プログラムで確認しました。
	// 無駄な割り込みが発生しても、二度目の割り込みから呼ばれるfn_usb_isr()が何も処理を行わないだけなので、
	// 実害はないのですけれど、わずかに無駄な時間が発生していることになります。
	// ちなみにP/ECEカーネル(1.20現在)もこれと同じ手順なので、同様の無駄な割り込みが発生しているものと思われます。
	//
	// 無駄な割り込みを防ぐためには、次のような手順としなければいけません。
	//
	//	[正しい手順]						USBCの割り込み	USBCの		ITCの割り込み
	//								要因フラグ	INT_N端子	要因フラグ
	//	0. 割り込み発生						１		１		１
	//	1. (割り込み許可)					１		１		１
	//	2. USBCの割り込み要因フラグクリア			０		０		１
	//	3. ITCの割り込み要因フラグクリア			０		０		０
	//	4. retiにより、CPUの割り込みレベル復元			０		０		０
	//
	// (2.)〜(3.)の間に次のUSBC割り込み要因が発生した場合も安全です。割り込み要因が失われてしまうことはありません。
	// (2.)〜(3.)の間に次のUSBC割り込み要因が発生した場合の流れを示します。(予測です。まだ確認はしていません。)
	//
	//	[正しい手順]						USBCの割り込み	USBCの		ITCの割り込み
	//								要因フラグ	INT_N端子	要因フラグ
	//	0. 割り込み発生						１		１		１
	//	1. (割り込み許可)					１		１		１
	//	2. USBCの割り込み要因フラグクリア			０		０		１
	//	X. 新しいUSBC割り込み要因発生				１		１		１
	//	3. ITCの割り込み要因フラグクリア			１		１		０→直後に１
	//	4. retiにより、CPUの割り込みレベル復元			１		１		１
	//	5. 新しいUSBC割り込みを処理するための割り込み発生(必要)	１		１		１
	//
	// 以上を踏まえて、以下ようにコードを修正しました。
	//
	INT_BEGIN2;
	//他の割り込み処理との排他制御をきちんとやらない場合は、多重割り込みしない方が安全です。
	//ENABLE;		/* (1.) */
	fn_usb_isr();		/* (2.) */
	bINT_FK01_FP03_FP1 = 0;	/* (3.) */
	INT_END2;		/* (4.) */
}
#else //↓2015/01/04変更:usb_isr()の定義にISR_DEF()マクロを使うように変更しました。コードサイズが8バイト小さくなりました。動作内容は変わりません。
ISR_DEF(usb_isr) {
	//ENABLE;		/* (1.) */
	fn_usb_isr();		/* (2.) */
	bINT_FK01_FP03_FP1 = 0;	/* (3.) */
}				/* (4.) */
#endif//}}2015/01/04変更:usb_isr()の定義にISR_DEF()マクロを使うように変更しました。コードサイズが8バイト小さくなりました。動作内容は変わりません。

static void
fn_usb_isr()
{
	int ir;
	int st;
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//	int len;
//	unsigned char dat[EP0TO2_MAX_PACKET_SIZE];
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

	/* 割り込み要因を取得・クリアします。(送受信割り込み要因のクリアは個別に行います) */
	ir = D12_ReadInterruptRegister();

	/* バスリセットならば、他の要因は無視します。
	 * * バスリセットイベントは、USBケーブルを接続した瞬間に二回連続で発生するようです。
	 *   USBケーブルを抜いた時には発生しません。(実験より)
	 */
	if(ir & D12_INT_BUSRESET) {
		bus_reset();

	/* バスリセット以外の要因を処理します。 */
	} else {
		if(ir & D12_INT_SUSPENDCHANGE) { /* 何もしません */ }
		if(ir & D12_INT_EOT) { /* DMA制御信号は結線されていません */ }
		/* EP0 (コントロールエンドポイント、シングルバッファ) */
		if(ir & D12_INT_ENDP0OUT) {
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//			st = D12_ReadLastTransactionStatus(EP0OUT);	/* 割り込み要因クリア */
//			len = D12_ReadEndpoint(EP0OUT, dat, sizeof dat);
//			if(len != -1) { /* ありえないが... */
//				ep0_rxdone(st, dat, len);
//			}
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
			st = D12_ReadLastTransactionStatus(EP0OUT);	/* 割り込み要因クリア */
			ep0_rxdone(st);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		}
		if(ir & D12_INT_ENDP0IN) {
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//			st = D12_ReadLastTransactionStatus(EP0IN);	/* 割り込み要因クリア */
//			ep0_txdone(st);
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
			D12_ReadLastTransactionStatus(EP0IN);		/* 割り込み要因クリア */
			ep0_txdone();
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		}
		/* EP1 (ジェネリックエンドポイント、シングルバッファ) */
		if(ir & D12_INT_ENDP1OUT) {
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//			st = D12_ReadLastTransactionStatus(EP1OUT);	/* 割り込み要因クリア */
//			len = D12_ReadEndpoint(EP1OUT, dat, sizeof dat);
//			if(len != -1) { /* ありえないが... */
//				ep1_rxdone(st, dat, len);
//			}
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
			D12_ReadLastTransactionStatus(EP1OUT);		/* 割り込み要因クリア */
			if(usb_inf.ep1_rxdone) usb_inf.ep1_rxdone();
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		}
		if(ir & D12_INT_ENDP1IN) {
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//			st = D12_ReadLastTransactionStatus(EP1IN);	/* 割り込み要因クリア */
//			ep1_txdone(st);
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
			D12_ReadLastTransactionStatus(EP1IN);		/* 割り込み要因クリア */
			if(usb_inf.ep1_txdone) usb_inf.ep1_txdone();
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		}
		/* EP2 (メインエンドポイント、シングルバッファまたはダブルバッファ) */
		if(ir & D12_INT_ENDP2OUT) {
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//			st = D12_ReadLastTransactionStatus(EP2OUT);	/* 割り込み要因クリア */
//			for(;;) { /* ダブルバッファ設定時のために、到着済みパケットを全て処理すること(必須!!) */
//				len = D12_ReadEndpoint(EP2OUT, dat, sizeof dat);
//				if(len == -1) break;
//				ep2_rxdone(st, dat, len);
//			}
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
			D12_ReadLastTransactionStatus(EP2OUT);		/* 割り込み要因クリア */
			if(usb_inf.ep2_rxdone) usb_inf.ep2_rxdone();
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		}
		if(ir & D12_INT_ENDP2IN) {
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//			st = D12_ReadLastTransactionStatus(EP2IN);	/* 割り込み要因クリア */
//			ep2_txdone(st);
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
			D12_ReadLastTransactionStatus(EP2IN);		/* 割り込み要因クリア */
			if(usb_inf.ep2_txdone) usb_inf.ep2_txdone();
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		}
	}
}

static void
bus_reset()
{
	if(usb_inf.bus_reset != NULL) usb_inf.bus_reset();
}

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//static void
//ep0_rxdone(int st, const unsigned char* dat, int len)
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
static void
ep0_rxdone(int st)
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
{
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
	int len;
	unsigned char dat[EP0_PACKET_SIZE];
	len = D12_ReadBuffer(EP0OUT, dat, sizeof dat);
	if(len == -1) return;
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

	/* SETUPパケットを受信したら... */
	if(st & D12_SETUPPACKET) {
		/* まずアイドル状態にしておきます。
		 * コントロールデータ送受信中でも、SETUPパケットを受信すると送受信はキャンセルされます。
		 */
		control_xfer.state = STATE_IDLE;

		/* デバイスリクエストを保存します。 */
		if(len != sizeof(DEVICE_REQUEST)) { /* デバイスリクエストサイズ不正 */
			usb_ep0_stall();
			return;
		}
		memcpy(&control_xfer.device_request, dat, sizeof(DEVICE_REQUEST));

		/* SETUPパケット受信によりEP0が停止しているので、復帰します。 */
		D12_AcknowledgeSetup(EP0OUT);	/* これ以降、「D12_ClearBuffer(EP0OUT);」が可能となります。 */
		D12_AcknowledgeSetup(EP0IN);	/* これ以降、「D12_ValidateBuffer(EP0IN);」が可能となります。 */

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		D12_ClearBuffer(EP0OUT);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

		/* コントロールデータの送受信サイズを初期化します。 */
		control_xfer.data_length = control_xfer.device_request.wLength;
		control_xfer.data_count  = 0;

		/* コントロールデータの送受信方向によって... */
		if(control_xfer.device_request.bmRequestType & 0x80) { /* Data transfer direction */
			/* Device-to-host (GET) */
			device_request(&control_xfer.device_request, NULL, 0);
		} else {
			/* Host-to-device (SET) */
			if(control_xfer.data_length == 0) {
				/* コントロールデータはないので、すぐにデバイスリクエストを処理します。 */
				device_request(&control_xfer.device_request, NULL, 0);
			} else {
				/* コントロールデータを受信したあとで、デバイスリクエストを処理します。 */
				if(control_xfer.data_length > DATA_BUFFER_SIZE) {
					usb_ep0_stall();
					return;
				}
				control_xfer.state = STATE_RXING;
			}
		}

	/* コントロールデータ受信中ならば... */
	} else if(control_xfer.state == STATE_RXING) {
		/* コントロールデータを格納します。 */
		if(control_xfer.data_count + len > control_xfer.data_length) { /* コントロールデータ超過 */
			usb_ep0_stall();
			return;
		}
		memcpy(&control_xfer.data_buffer[control_xfer.data_count], dat, len);
		control_xfer.data_count += len;

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		D12_ClearBuffer(EP0OUT);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

		/* コントロールデータの受信が完了したら、デバイスリクエストを処理します。
		 * ホスト→デバイスのコントロールデータは、必ずデバイスリクエストで指定されたサイズぴったりで終了します。
		 * バルク転送のように、ペイロードサイズ未満のパケットや空のパケットによって終端されたりはしません。
		 * [usb_20.pdf p.30]
		 * The Data stage of a control transfer from the host to an endpoint is complete when all of the data has been
		 * transferred. If the endpoint receives a larger-than-expected data payload from the host, it halts the pipe.
		 */
		if(control_xfer.data_count == control_xfer.data_length) {
			control_xfer.state = STATE_IDLE;
			device_request(&control_xfer.device_request, control_xfer.data_buffer, control_xfer.data_length);
		}

	} else {
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		D12_ClearBuffer(EP0OUT);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

		/* たぶんGETリクエストのステータスステージで、ホストからの空パケット受信完了通知です。
		 * ここでは何もしません。
		 */
	}
}

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//static void
//ep0_txdone(int st)
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
static void
ep0_txdone()
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
{
	int len;

	/* コントロールデータ送信中ならば... */
	if(control_xfer.state == STATE_TXING) {
		/* コントロールデータをペイロードサイズに分割して送信します。
		 * * デバイス→ホストのコントロールデータは、デバイスリクエストで指定されたサイズぴったりか、
		 *   またはバルク転送と同様に、ペイロードサイズ未満のパケットか空のパケットによって終端します。
		 * * 現在の実装では「デバイスリクエストで指定されたサイズぴったり」という条件を考慮していないので、
		 *   指定されたサイズがペイロードサイズの整数倍だった場合に、余計な空パケットを送ってしまいます。
		 *   ホスト側で余計な空パケットを無視してくれることを期待して、とりあえずそのままにしておきます。→追記:2004/11/02修正、下のコメント参照
		 * [usb_20.pdf p.30]
		 * The Data stage of a control transfer from an endpoint to the host is complete when the endpoint does one of
		 * the following:
		 * ･ Has transferred exactly the amount of data specified during the Setup stage
		 * ･ Transfers a packet with a payload size less than wMaxPacketSize or transfers a zero-length packet
		 * When a Data stage is complete, the Host Controller advances to the Status stage instead of continuing on
		 * with another data transaction. If the Host Controller does not advance to the Status stage when the Data
		 * stage is complete, the endpoint halts the pipe as was outlined in Section 5.3.2. If a larger-than-expected data
		 * payload is received from the endpoint, the IRP for the control transfer will be aborted/retired.
		 */
		len = control_xfer.data_length - control_xfer.data_count;
		if(len > EP0_PACKET_SIZE) len = EP0_PACKET_SIZE;
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//		D12_WriteEndpoint(EP0IN, control_xfer.data_ptr + control_xfer.data_count, len);
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		D12_WriteBuffer(EP0IN, control_xfer.data_ptr + control_xfer.data_count, len);
		D12_ValidateBuffer(EP0IN);
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
		control_xfer.data_count += len;
		//if(len < EP0_PACKET_SIZE) control_xfer.state = STATE_IDLE;
		//↓
		//↓2004/11/02 Naoyuki Sawa 追記#1
		//↓* 上述の“指定されたサイズがペイロードサイズの整数倍だった場合に、余計な空パケットを送ってしまう”を修正しました。
		//↓  指定されたサイズがペイロードサイズの整数倍だった場合は、ぴったりペイロードサイズのパケットを送って終了します。
		//↓
		///* "len<EP0_PACKET_SIZE"の場合も"data_count==data_length"になるので、"len<EP0_PACKET_SIZE"の条件は不要です。 */
		//if(control_xfer.data_count == control_xfer.data_length) control_xfer.state = STATE_IDLE;
		//↓
		//↓2004/11/02 Naoyuki Sawa 追記#2
		//↓* と、思ったのですが、実際に上のように変更してみると、Windows 2000がMass storageデバイスを認識しなくなりました...
		//↓  どうやらコントロール転送において、ペイロードサイズ未満または空パケットによる終端は必須だったみたいです。
		//↓  P/ECEカーネルを見ると、やはりペイロードサイズ未満または空パケットによる終端を行っているようです。(mainloop.c ep0_txdone() 参照)
		//↓  そんなわけで、元に戻しました。
		//↓
		if(len < EP0_PACKET_SIZE) control_xfer.state = STATE_IDLE;

	} else {
		/* たぶんSETリクエストのステータスステージで、ホストへの空パケット送信完了通知です。
		 * ここでは何もしません。
		 */
	}
}

//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//static void
//ep1_rxdone(int st, const unsigned char* dat, int len)
//{
//	if(usb_inf.ep1_rxdone != NULL) usb_inf.ep1_rxdone(dat, len);
//}
//static void
//ep1_txdone(int st)
//{
//	if(usb_inf.ep1_txdone != NULL) usb_inf.ep1_txdone();
//}
//static void
//ep2_rxdone(int st, const unsigned char* dat, int len)
//{
//	if(usb_inf.ep2_rxdone != NULL) usb_inf.ep2_rxdone(dat, len);
//}
//static void
//ep2_txdone(int st)
//{
//	if(usb_inf.ep2_txdone != NULL) usb_inf.ep2_txdone();
//}
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更

static void
device_request(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	DEVICE_REQUEST_CALLBACK drc = NULL;

	switch(req->bmRequestType & 0x60) { /* Type */
	case 0x00: /* Standard request */
		switch(req->bRequest) {
		case GET_STATUS:        drc = usb_inf.get_status;        break;
		case CLEAR_FEATURE:     drc = usb_inf.clear_feature;     break;
		case SET_FEATURE:       drc = usb_inf.set_feature;       break;
		case SET_ADDRESS:       drc = usb_inf.set_address;       break;
		case GET_DESCRIPTOR:    drc = usb_inf.get_descriptor;    break;
		case SET_DESCRIPTOR:    drc = usb_inf.set_descriptor;    break;
		case GET_CONFIGURATION: drc = usb_inf.get_configuration; break;
		case SET_CONFIGURATION: drc = usb_inf.set_configuration; break;
		case GET_INTERFACE:     drc = usb_inf.get_interface;     break;
		case SET_INTERFACE:     drc = usb_inf.set_interface;     break;
		case SYNCH_FRAME:       drc = usb_inf.synch_frame;       break;
		}
		break;
	case 0x20: /* Class request */
		drc = usb_inf.class_request;
		break;
	case 0x40: /* Vendor request */
		drc = usb_inf.vendor_request;
		break;
	}
	if(drc) {
		drc(req, dat, len);
	} else {
		usb_ep0_stall(); /* 未対応のリクエストならば、ストールします */
	}
}

void
usb_start(const USBINF* ui)
{
	int T0;

	/*{{念のため構造体定義に誤りがないことを確認*/
	if(sizeof(DEVICE_REQUEST          ) !=  8) DIE();
	if(sizeof(DEVICE_DESCRIPTOR       ) != 18) DIE();
	if(sizeof(CONFIGURATION_DESCRIPTOR) !=  9) DIE();
	if(sizeof(STRING_DESCRIPTOR       ) !=  2) DIE();
	if(sizeof(INTERFACE_DESCRIPTOR    ) !=  9) DIE();
	if(sizeof(ENDPOINT_DESCRIPTOR     ) !=  7) DIE();
	/*}}念のため構造体定義に誤りがないことを確認*/

	/* まず確実にフック解除します。 */
	usb_stop();

	/* カーネルのUSB制御権を放棄。 */
	pceUSBDisconnect();

	/* ホストが切断を検出できるように、少しウェイト。 */
	T0 = pceTimerGetCount();
	while(pceTimerGetCount() - T0 < RECONNECT_WAIT) { }

	/* USBフック情報を格納します。 */
	usb_inf = *ui;

	/*{{2005/07/31 一般的なデバイスリクエストの処理を内蔵しました。*/
	if(!usb_inf.get_status   ) usb_inf.get_status    = get_status   ;	/*{{2014/09/04追加:一般的なデバイスリクエストの処理に、get_statusを追加しました。}}*/
	if(!usb_inf.clear_feature) usb_inf.clear_feature = clear_feature;
	if(!usb_inf.set_address  ) usb_inf.set_address   = set_address  ;
	/*}}2005/07/31 一般的なデバイスリクエストの処理を内蔵しました。*/

	/* USBC割り込みをフック。 */
	old_usb_isr = pceVectorSetTrap(TRAP_USBC, usb_isr);

	/* USB接続。 */
	ENTER_CS;
	//D12_SetMode(usb_inf.mode | D12_SOFTCONNECT, D12_SETTOONE);
	//↓2005/07/08 D12_CLOCKRUNNINGを常に有効とするように変更しました。
	//D12_SetMode(usb_inf.mode | D12_SOFTCONNECT | D12_CLOCKRUNNING, D12_SETTOONE);
	//↓2014/12/30 D12_NOLAZYCLOCKを常に有効とするように変更しました。
	D12_SetMode(usb_inf.mode | D12_SOFTCONNECT | D12_CLOCKRUNNING | D12_NOLAZYCLOCK, D12_SETTOONE);
	LEAVE_CS;
}

void
usb_stop()
{
	int T0;

	/* フックされていなければ何もしません。 */
	if(old_usb_isr == NULL) return;

	/* USB切断。 */
	ENTER_CS;
	D12_SetMode(0, D12_SETTOONE);
	LEAVE_CS;

	/* ホストが切断を検出できるように、少しウェイト。 */
	T0 = pceTimerGetCount();
	while(pceTimerGetCount() - T0 < RECONNECT_WAIT) { }

	/* USBC割り込みを元に戻す。 */
	pceVectorSetTrap(TRAP_USBC, old_usb_isr);
	old_usb_isr = NULL; /* フック解除したことをマーク */

	/* カーネルのUSB制御権を復帰。 */
	pceUSBReconnect();
}

void
usb_ep0_tx(const void* ptr, int len)
{
	/* ★☆★超重要★☆★
	 * * GETリクエストへの応答で、wLengthよりも大きなサイズのデータを送り返そうとすると、
	 *   ホスト側との状態遷移の動機が取れなくなり、「不明なデバイス」になってしまいます！
	 * * 例えばGET_DESCRIPTOR(CONFIGURATION)の場合、まずwLength=9で要求されるみたいです。(Win2Kで確認)
	 *   ふつう、コンフィギュレーションディスクリプタは9バイトでは収まらないので、
	 *   コンフィギュレーションディスクリプタ全体の先頭9バイトだけを返すようにします。
	 *   すると、ホストがもっと大きなwLengthを指定して、再度GET_DESCRIPTORを要求するようです。
	 * * wLength=9なのに9バイト以上のデータを送り返そうとすると、前述のとおりハマリます。
	 *   要注意です！！
	 * * wLengthが小さすぎるからといって、ストールしてしまってはいけません！
	 *   wLengthの分だけは、送り返す必要があります。
	 *   ストールしてしまうと、やはり「不明なデバイス」になってしまいます。
	 * * wLengthを参照してデータサイズを切り詰める作業は、本当はd12usr.cのデバイスリクエストハンドラで行うべきです。
	 *   が、常に必要な作業なので、ここでまとめて切り詰めてしまうことにします。
	 *   d12usr.cのデバイスリクエストハンドラは、自分が送信したデータが切り詰められたことを関知しません。
	 * [usb_20.pdf p.253]
	 * The wLength field specifies the number of bytes to return. If the descriptor is longer than the wLength field,
	 * only the initial bytes of the descriptor are returned. If the descriptor is shorter than the wLength field, the
	 * device indicates the end of the control transfer by sending a short packet when further data is requested. A
	 * short packet is defined as a packet shorter than the maximum payload size or a zero length data packet (refer
	 * to Chapter 5).
	 */
	if(control_xfer.device_request.bmRequestType & 0x80) { /* Data transfer direction = Device-to-host (GET) */
		if(len > control_xfer.device_request.wLength) len = control_xfer.device_request.wLength;
	}

	/* * GETリクエストに対するコントロールデータステージ(送信)で、データを送信するために利用する場合は、
	 *   既に data_count=0 になっているので、data_count<-0 はダミー処理となります。
	 * * SETリクエストに対するステータスステージ(送信)で、空パケットを返信するために利用する場合は、
	 *   コントロールデータ受信のために data_count が使われた後なので、data_count<-0 が必須です。
	 * * というわけで、簡単のために、いつでも data_count<-0 を行うことにします。
	 */
	control_xfer.data_length = len;
	control_xfer.data_count = 0;
	control_xfer.data_ptr = ptr;

	/* 初回のデータ転送。(後続のデータ転送とやること同じなので、転送完了ハンドラを利用) */
	control_xfer.state = STATE_TXING; /* 必要！ */
//{{2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
//	ep0_txdone(D12_INT_ENDP0IN);
//↓2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
	ep0_txdone();
//}}2007/12/08:受信オーバーランを回避する実装を可能とするための仕様変更
	/* もしも一回で転送完了したら、ep0_txdone()で state=STATE_IDLE に戻っています。 */
}

void
usb_ep0_stall()
{
	D12_SetEndpointStatus(EP0OUT, 1); /* EP0OUT(ホスト→デバイス)停止 */
	D12_SetEndpointStatus(EP0IN , 1); /* EP0IN (デバイス→ホスト)停止 */
}

/*{{2005/07/31 一般的なデバイスリクエストの処理を内蔵しました。*/
static int
device_request_endpoint(const DEVICE_REQUEST* req)
{
	int endp;
	switch(req->wIndex) {
//{{2014/09/06変更:device_request_endpoint()のバグを修正しました。エンドポイント番号の対応が間違っていました。今迄このバグが原因でget_status()とclear_feature()が正しく動作していませんでした。今後は正しく動作するようになりました。
//	case 0x00: endp = EP0OUT; break;
//	case 0x01: endp = EP0IN ; break;
//	case 0x02: endp = EP1OUT; break;
//	case 0x80: endp = EP1IN ; break;
//	case 0x81: endp = EP2OUT; break;
//	case 0x82: endp = EP2IN ; break;
//↓2014/09/06変更:device_request_endpoint()のバグを修正しました。エンドポイント番号の対応が間違っていました。今迄このバグが原因でget_status()とclear_feature()が正しく動作していませんでした。今後は正しく動作するようになりました。
	case 0x00: endp = EP0OUT; break;
	case 0x80: endp = EP0IN ; break;
	case 0x01: endp = EP1OUT; break;
	case 0x81: endp = EP1IN ; break;
	case 0x02: endp = EP2OUT; break;
	case 0x82: endp = EP2IN ; break;
//}}2014/09/06変更:device_request_endpoint()のバグを修正しました。エンドポイント番号の対応が間違っていました。今迄このバグが原因でget_status()とclear_feature()が正しく動作していませんでした。今後は正しく動作するようになりました。
	default:   endp = -1    ; break;
	}
	return endp;
}
/*{{2014/09/04追加:一般的なデバイスリクエストの処理に、get_statusを追加しました。*/
static void
get_status(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	static const unsigned short status[2] = {0,1};	/* For device, D0=SelfPowered,D1=RemoteWakeup. For endpoint, D0=Halt. */
	PCEPWRSTAT ps;
	int endp;
	switch(req->bmRequestType & 0x1f) { /* recipient */
	case 0x00: /* device */
		pcePowerGetStatus(&ps); /* \usr\PIECE\sysdev\pcekn\powerman.cの実装を見ると、割り込み中に呼出しても安全です。 */
		usb_ep0_tx(&status[ps.status & 1], 2);	/* {SelfPowered=0,RemoteWakeup=0},又は,{SelfPowered=1,RemoteWakeup=0} */
		return;
	case 0x01: /* interface */
		goto L_ERR;
	case 0x02: /* endpoint */
		endp = device_request_endpoint(req);
		if(endp < 0) { goto L_ERR; }
		usb_ep0_tx(&status[D12_ReadEndpointStatus(endp) >> 7], 2);	/* {Halt=0},又は,{Halt=1} */
		return;
	}
L_ERR:
	usb_ep0_stall();
}
/*}}2014/09/04追加:一般的なデバイスリクエストの処理に、get_statusを追加しました。*/
static void
clear_feature(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	int endp;
	switch(req->bmRequestType & 0x1f) { /* recipient */
	case 0x00: /* device */
		switch(req->wValue) {
		case 1: /* device remote wakeup */
			goto L_ERR;
		case 2: /* test mode */
			goto L_ERR;
		}
		goto L_ERR;
	case 0x01: /* interface */
		goto L_ERR;
	case 0x02: /* endpoint */
		switch(req->wValue) {
		case 0: /* endpoint halt */
			endp = device_request_endpoint(req);
			if(endp < 0) {
				goto L_ERR;
			}
			D12_SetEndpointStatus(endp, 0);	/* Stall解除 */
			usb_ep0_tx(NULL, 0);		/* Ack */
			return;
		}
		goto L_ERR;
	}
L_ERR:
	usb_ep0_stall();
}
static void
set_address(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	D12_SetAddressEnable(req->wValue, 1);	/* Address設定 */
	usb_ep0_tx(NULL, 0);			/* Ack */
}
/*}}2005/07/31 一般的なデバイスリクエストの処理を内蔵しました。*/
