/*
 * device.c
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 */


#include"config.h"
#include"types.h"
#include"lib.h"
#include"mm.h"
#include"interrupt.h"
#include"fs.h"
#include"errno.h"
#include"device.h"


enum{
	MAX_DEVICES=16,		/* Number of register devices */
	MAX_NAME=16,		/* Device name size */
};


/* Partition table */
typedef struct{
	uchar active;			/* Boot active flag=0x80 */
	uchar chs_begin[3];		/* CHS begin block */
	uchar type;				/* Partition type */
	uchar chs_last[3];		/* CHS last block */
	uint lba_begin;			/* LBA begin block */
	uint size;				/* Partition size by secter */
}PAT_TABLE;


static DEV_INFO *device[MAX_DEVICES];
DEV_INODE dinode[MAX_DEVICE_OPEN];


static void *open(const char*,void*,int);
static int read(void*,void*,size_t,size_t);
static int write(void*,void*,size_t,size_t);
static int ioctl(void*,int,void*);
static int close(void*);


static FS dev_fs={
	"dev_fs",NULL,NULL,open,read,write,ioctl,NULL,NULL,NULL,NULL,close
};


/************************************************************************************************
 *
 * Inline function
 *
 ************************************************************************************************/


 /*
  * デバイスパス名を比較する。
  * parameters : destination path,sorce path
  * return : 前方一致 一致した次のポインタ,不一致 NULL
  */
extern inline const char *cmp_devpath(const char *s1,const char *s2)
{
	while(*s1!='\0')
		if(*s1++!=*s2++)return NULL;

	return s2;
}


/************************************************************************************************
 *
 * Misc function
 *
 ************************************************************************************************/


/************************************************************************************************
 *
 * Device initialize
 *
 ************************************************************************************************/


/*
 * Init device system
 * すべてのデバイスの初期化前に行う。
 * return : 0 or error=-1
 */
int init_device()
{
	int i;


	for(i=0;i<MAX_DEVICES;++i)device[i]=NULL;
	for(i=0;i<MAX_DEVICES;++i)dinode[i].ref_count=0;

	return regist_fs(&dev_fs);
}


/*
 * Register device
 * parameters : Device infomation struct address
 * return : 0 or Error number
 */
int regist_device(DEV_INFO *info)
{
	int i;


	/* 配列0はエラー値用に空けておく。 */
	for(i=0;i<MAX_DEVICES;++i)
		if(device[i]==NULL)
		{
			device[i]=info;
			return 0;
		}

	return -EMDEV;
}


/*
 * Delete from regist table
 * parameters : name
 * return : 0,error -1
 */
int delete_device(const char *name)
{
	return 0;
}


/************************************************************************************************
 *
 * System call interface
 *
 ************************************************************************************************/

/*
 * Open device
 * parameters : Device name,inode,DEV_INFO struct(for copy)
 * return : device inode pointer
 */
void *open(const char *dev_name,void *dir,int dev)
{
	enum{
		/* Partition table */
		PT_NUM=4,				/* 1セクター内のパーティションテーブル数 */
		PT_BEGIN=0x1be,			/* パーティションテーブル開始オフセット */
		PT_MAGIC_ADDR=0x1fe,	/* パーティションテーブルマジックナンバーオフセット */
		PT_MAGIC_NUMBER=0xaa55,	/* パーティションテーブルマジックナンバー */

		/* Partition type */
		PT_TYPE_NULL=0,			/* NULL partition */
		PT_TYPE_EXT=0x5,		/* ext partition */
		PT_TYPE_LEXT=0xf,		/* ext(LBA) partition */
	};

	const char *str;
	char *buf;
	int inode_num;
	int pt_num;
	uint beg_blk,next_blk;
	PAT_TABLE *pt;
	int i;


	/* 登録デバイスから名前で検索 */
	for(i=0;(str=cmp_devpath(device[i]->name,dev_name))==NULL;++i)
		if(i>=MAX_DEVICES)return (void*)-1;
	dev=i;

	/* 名前にパーティション番号があれば、整数に変換する */
	pt_num=0;
	if(*str!='\0')
		if((pt_num=atoi(str))<=0)return (void*)-1;		/* パーティション番号は1以上 */

	/* すでにオープンされているか確認 */
	for(i=0;i<MAX_DEVICE_OPEN;++i)
		if((dinode[i].dev==device[dev])&&(dinode[i].pt_num==pt_num))
		{
			++dinode[i].ref_count;
			return (void*)i;
		}

	/* 空きデバイスinode検索をする。 */
	for(i=0;dinode[i].ref_count!=0;++i)
		if(i>=MAX_DEVICE_OPEN)return (void*)-1;
	inode_num=i;

	/* Open device */
	if(device[dev]->open()<0)return (void*)-1;

	/* パーティションテーブルを検索する */
	if((buf=(char*)kmalloc(device[dev]->secter_size))==NULL)return (void*)-1;
	if(device[dev]->read(buf,1,0)!=1)goto ERR;
	if(pt_num==0)
	{
		if(*(ushort*)(buf+PT_MAGIC_ADDR)!=PT_MAGIC_NUMBER)dinode[inode_num].pt_prn=1;
		else dinode[inode_num].pt_prn=0;

		dinode[inode_num].pt_prn=0;
		dinode[inode_num].pt_num=0;
		dinode[inode_num].pt_type=0;
		dinode[inode_num].begin_blk=device[dev]->begin_blk;
		dinode[inode_num].last_blk=device[dev]->last_blk;
	}
	else
	{
		if(*(ushort*)(buf+PT_MAGIC_ADDR)!=PT_MAGIC_NUMBER)goto ERR;

		pt=(PAT_TABLE*)(buf+PT_BEGIN);
		if(pt_num<=PT_NUM)
		{
			dinode[inode_num].pt_num=pt_num;
			--pt_num;
			dinode[inode_num].pt_type=pt[pt_num].type;
			dinode[inode_num].begin_blk=pt[pt_num].lba_begin;
			dinode[inode_num].last_blk=pt[pt_num].lba_begin+pt[pt_num].size-1;
		}
		else
		{
			for(i=0;pt[i].type!=PT_TYPE_EXT;++i)
				if(i>=PT_NUM)goto ERR;			/* 拡張パーティションがない */

			/* 拡張パーティションリンクをたどる。 */
			beg_blk=next_blk=pt[i].lba_begin;
			for(i=PT_NUM;;)
			{
				if(device[dev]->read(buf,1,next_blk)!=1)goto ERR;
				if(++i==pt_num)break;
				if(pt[1].type!=PT_TYPE_EXT)goto ERR;
				next_blk=beg_blk+pt[1].lba_begin;
			}

			dinode[inode_num].pt_num=pt_num;
			dinode[inode_num].pt_type=pt[0].type;
			dinode[inode_num].begin_blk=pt[0].lba_begin+next_blk;
			dinode[inode_num].last_blk=pt[0].lba_begin+pt[0].size-1;
		}

		dinode[inode_num].pt_prn=1;
	}

	kfree(buf);

	dinode[inode_num].dev=device[dev];
	dinode[inode_num].ref_count=1;

	return (void*)inode_num;

ERR:
	kfree(buf);

	return (void*)-1;
}


/*
 * parameters : Device regist number,Buffer,Transfer size(byte),Begin block number
 */
int read(void *inode,void *buf,size_t size,size_t begin)
{
	return 0;
}

/*
 * parameters : Device regist number,Buffer,Transfer size(byte),Begin block number
 */
int write(void *inode,void *buf,size_t size,size_t begin)
{
	return 0;
}

/*
 * parameters : Device regist number,Command,Parameter struct pointer
 */
int ioctl(void *inode,int command,void *param)
{
	return 0;
}

/*
 * parameters : device inode index
 */
int close(void *din)
{
	return 0;
}
/****************************************************************************/
void test_device()
{
	int i;


	printk("open=%d\n",i=(int)open("hdb6",NULL,0));
	printk("dinode.pt_num=%d\n",dinode[i].pt_num);
	printk("dinode.pt_prn=%d\n",dinode[i].pt_prn);
	printk("dinode.pt_type=%x\n",dinode[i].pt_type);
	printk("dinode.ref_count=%d\n",dinode[i].ref_count);
	printk("dinode.dev=%x\n",dinode[i].dev);
	printk("dinode.begin_blk=%x\n",dinode[i].begin_blk);
	printk("dinode.last_blk=%x\n",dinode[i].last_blk);
	printk("device[2]=%x\n",device[2]);
}
/*****************************************************************************/
