/* FDC */
#include "bootpack.h"

struct fdc fdc;			/* FDC処理 */
struct fifo *fifo_fdc;	/* FDCタスクのFIFO */
int fdcbuf[32];			/* FIFOのバッファ */

/*
FDCとのやりとりを仲介をするタスクである。
処理を要求するならfdc_reqを使い、結果を取得するにはfdc_stat
を使い、正常なら0、異常なら1、不正な命令なら2を返す。
*/
void fdc_task(void)
{
	struct task *task = task_now();
	struct timer *timer;
	char mot = 0, ph = 0, err = 1;
	int i;

	/* DMAの初期化 */
	fdc_dmainit();

	/* モータの初期化 */
	outb(PORT_FDC_MOTOR, 0x0c);	/* モータストップ */
	timer = timer_alloc();
	timer_init(timer, fifo_fdc, 1);

start:
	for (;;) {
		cli();
		if (fifo_stat(fifo_fdc) == 0) {
			task_sleep(task);
			sti();
		} else {
			i = fifo_get(fifo_fdc);
			sti();

			if(i == 1) {	/* モータ制御 */
				if(mot != 0) {	/* モータストップ */
					outb(PORT_FDC_MOTOR, 0x0c);
					mot = 0;
				} else {	/*　ヘッドの調整 */
					fdc_setdma();
					fdc_sethead();
					mot = 1;
					ph = 0;
				}
			} else if(i == 2) {	/* 読み書きスタート */
				if(mot == 0) {
					outb(PORT_FDC_MOTOR, 0x1c);	/* モータスタート */
					timer_set(timer, 300);
				} else {
					timer_cancel(timer);
					fdc_setdma();
					fdc_sethead();
					ph = 0;
				}
			} else if(i == 6) {	/* FDCからのINT */
				if(ph == 0) {	/* シーク完了 */
					fdc_getint();
					if((fdc.st0 & 0xc0) == 0x00) {	/* 正常終了 */
						fdc_setcmd();
						ph = 1;
						err = 1;
					} else {	/* 異常なステータス */
						fdc.st0 = 2;
						goto end;
					}
				} else {	/* コマンド完了 */
					fdc_getrstat();
					if((fdc.st0 & 0xc0) == 0x00) {		/* 正常終了 */
						fdc.st0 = 0;
						goto end;
					} else if ((fdc.st0 & 0xc0) == 0x40) {	/* 異常終了 */
						if(err > 5) {
							fdc.st0 = 1;
							goto end;
						} else {
							err++;
							fdc_setcmd();
						}
					} else {	/* 異常なステータス */
						fdc.st0 = 2;
						goto end;
					}
				}
			}
		}
	}

end:	/* 再開 */
	outb(PORT_DMAC0_SMR, 0x06);	/* マスタのch2をマスク */
	timer_set(timer, 300);
	fdc.mode = IO_EXIT;
	goto start;
}

void fdc_tinit(struct task *task)
{
	/* タスクの設定 */
	task->stack = memory_alloc(mem, 65536);
	task->tss.eip = (int) &fdc_task;
	task->tss.esp = task->stack + 65536;
	task->tss.es = 1 * 8;
	task->tss.cs = 2 * 8;
	task->tss.ss = 1 * 8;
	task->tss.ds = 1 * 8;
	task->tss.fs = 1 * 8;
	task->tss.gs = 1 * 8;

	/* 要求構造体とFIFOの初期化 */
	fdc.mode = 0;
	fifo_fdc = &task->fifo;
	fifo_init(&task->fifo, task, fdcbuf, 32);

	/* タスクの実行 */
	task_run(task, 1, 1);
	return;
}

void fdc_ihandle(int *esp)
{
	inb(PORT_FDC_STAT);		/* IRQにCPUが気づいたことをFDCに教える */
	outb(PIC0_OCW2, 0x66);	/* IRQ-06を終了 */
	fifo_put(fifo_fdc, 6);
	return;
}

void fdc_dmainit(void)
{
	/* DMAの初期化 */
	outb(PORT_DMAC1_MODE, 0xc0);	/* マスタのch0をカスケードモードに */
	outb(PORT_DMAC1_CBAR, 0x00);	/* スレーブのDMAを許可 */
	outb(PORT_DMAC0_SMR, 0x06);		/* マスタのch2のDMAをマスク */
	return;
}

void fdc_setdma(void)
{
	int addr = DISKIMG_ADDR + (fdc.cyl * 36 + fdc.head * 18 + fdc.sect - 1) * 512;

	if(fdc.mode == IO_READ) {
		outb(PORT_DMAC0_MODE, 0x06);	/* デマンド・アドレス増加方向・メモリへの書き込み・ch2 */
	} else if (fdc.mode == IO_WRITE) {
		outb(PORT_DMAC0_MODE, 0x0a);	/* デマンド・アドレス増加方向・メモリからの読み込み・ch2 */
	}

	outb(PORT_DMAC0_CBAR, addr & 0xff);	/* メモリ番地の設定 */
	outb(PORT_DMAC0_CBAR, (addr >> 8) & 0xff);
	outb(PORT_DMAP_ADDRUP, (addr >> 16) & 0xff);

	outb(PORT_DMAC0_CBCR, 0xff);	/* バイト数の設定 */
	outb(PORT_DMAC0_CBCR, fdc.sects * 2 - 1);

	outb(PORT_DMAC0_SMR, 0x02);	/* マスタのch2のマスクを解除 */
	return;
}

void fdc_sethead(void)
{
	fdc_initwait(0x11);
	fdc_sendcmd(0x0f);			/* シーク */
	fdc_sendcmd(fdc.head << 2);	/* ヘッド */
	fdc_sendcmd(fdc.cyl);		/* シリンダ */
	return;
}

void fdc_setcmd(void)
{
	fdc_initwait(0x11);

	if(fdc.mode == IO_READ) {
		fdc_sendcmd(0xe6);	/* 読み込み */
	} else if (fdc.mode == IO_WRITE) {
		fdc_sendcmd(0xc5);	/* 書き込み */
	}

	fdc_sendcmd(fdc.head << 2);	/* FD番地の指定 */
	fdc_sendcmd(fdc.cyl);
	fdc_sendcmd(fdc.head);
	fdc_sendcmd(fdc.sect);

	fdc_sendcmd(0x02);	/* セクタ長：512B */
	if(fdc.mode == IO_READ) {
		fdc_sendcmd(0x12);	/* トラック数：18 */
		fdc_sendcmd(0x01);	/* GAP3 */
	} else if(fdc.mode == IO_WRITE) {
		fdc_sendcmd(0x7f);	/* ? */
		fdc_sendcmd(0x12);	/* ? */
	}
	fdc_sendcmd(0xff);	/* 指定セクタサイズ全体が対象 */
	return;
}

void fdc_getint(void)
{
	fdc_initwait(0x10);
	fdc_sendcmd(0x08);	/* 割り込み状態取得 */
	fdc.st0 = fdc_getrstatsub();
	fdc_getrstatsub();	/* 終了時の番地：シリンダ */
	return;
}

void fdc_getrstat(void)
{
	char i;

	fdc.st0 = fdc_getrstatsub();
	i = fdc_getrstatsub();	/* st1 */
	i = fdc_getrstatsub();	/* st2 */
	i = fdc_getrstatsub();	/* 終了時の番地：シリンダ */
	i = fdc_getrstatsub();	/* 終了時の番地：ヘッド */
	i = fdc_getrstatsub();	/* 終了時の番地：セクタ */
	i = fdc_getrstatsub();	/* セクタサイズ */
	return;
}

char fdc_getrstatsub(void)
{
	char i;

	for(;;) {
		if((inb(PORT_FDC_STAT) & 0xc0) == 0xc0) {
			i = (char) inb(PORT_FDC_DATA);
			return i;
		}
	}
}

void fdc_initwait(int wait)
{
	for(;;) {
		if((inb(PORT_FDC_STAT) & wait) == 0) {
			return;
		}
	}
}

void fdc_sendcmd(int data)
{
	for(;;) {
		if((inb(PORT_FDC_STAT) & 0xc0) == 0x80) {
			outb(PORT_FDC_DATA, data);
			return;
		}
	}
}

void fdc_req(char mode, char cyl, char head, char sect, char sects)
{
	for(;;) {
		if(fdc.mode == 0) {
			fdc.mode = mode;
			fdc.cyl = cyl;
			fdc.head = head;
			fdc.sect = sect;
			fdc.sects = sects;
			return;
		}
	}
}

int fdc_reqstat(void)
{
	fifo_put(fifo_fdc, 2);
	for(;;) {
		if(fdc.mode == IO_EXIT) {
			fdc.mode = 0;
			return (int) fdc.st0;
		} else {
			timer_sleep(1);
		}
	}
}
