/*	
 *	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()を追加しました。
 */
#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,
	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,
	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 */
};

/* エンドポイントディスクリプタ
 * * 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,
};

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

/* ドライバ構造体 */
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;				/* 送信データ総バイト数(デバッグ用) */
	void (*txdone)();			/* 送信完了コールバック(NULL:無し) */
	//
	unsigned char cxbuf[sizeof config_desc	/* デバイスリクエスト応答用の一時バッファ */
			  + sizeof interface_desc
			  + sizeof endpoint_desc_ep2in
			  + sizeof endpoint_desc_ep2out];
} UIO;
static UIO uio;

static void set_address(const DEVICE_REQUEST* req, const unsigned char* dat, int len);
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 ep2_rxdone(const unsigned char dat[/*len*/], int len);
static void ep2_txdone();

/* 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 */
	set_address:		set_address,
	get_descriptor:		get_descriptor,
	set_configuration:	set_configuration,
	set_interface:		set_interface,
};

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

static void
set_address(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	/* デバイスアドレスを設定します。 */
	D12_SetAddressEnable(req->wValue, 1);
	usb_ep0_tx(NULL, 0); /* ACK */
}

static void
get_descriptor(const DEVICE_REQUEST* req, const unsigned char* dat, int len)
{
	/* 要求されたディスクリプタを返送します。 */
	switch(req->wValue & 0xff00) {
	case 0x0100: /* Device */
		usb_ep0_tx(&device_desc, sizeof device_desc);
		return;
	case 0x0200: /* Configuration */
		len = 0;
		memcpy(&uio.cxbuf[len], &config_desc         , sizeof config_desc         ); len += sizeof config_desc         ;
		memcpy(&uio.cxbuf[len], &interface_desc      , sizeof interface_desc      ); len += sizeof interface_desc      ;
		memcpy(&uio.cxbuf[len], &endpoint_desc_ep2in , sizeof endpoint_desc_ep2in ); len += sizeof endpoint_desc_ep2in ;
		memcpy(&uio.cxbuf[len], &endpoint_desc_ep2out, sizeof endpoint_desc_ep2out); len += sizeof endpoint_desc_ep2out;
		usb_ep0_tx(uio.cxbuf, len);
		return;
	}
	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 */

	/* 送信開始トリガ。以後は、ep2_txdone()で連鎖します。 */
	ep2_txdone();
}

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

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

static void
ep2_rxdone(const unsigned char dat[/*len*/], int len)
{
	int pos;
	int i;

	/* コピーするデータサイズを、受信バッファの空きサイズに切り詰めます。 */
	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()
{
	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;

	/* パケットを送信します。 */
	D12_WriteEndpoint(EP2IN, dat, len);

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

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

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

	/* ドライバ構造体をクリアします。 */
	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_init(&usb_inf);
}

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

	/* 受信バッファを開放します。 */
	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;

LEAVE_CS;
}

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

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);
	}

	/* ユーザーバッファから、送信バッファのデータ末尾へコピーします。 */
	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;

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;
}
