/**********************************************

	cache deiver Program

	1991.04.15	make by Ken

	compile use : cl /Zp /Ze

***********************************************/
#include    <stdio.h>
#include    <dos.h>
#include    "defs.h"

void		farmemcpy(char far *p,char far *s,int n);
uchar		sum_calc(char far *p,ushort sz);
void		Dev_str_call(char far *ent,REQ far *req);
void		Dev_int_call(char far *ent);
int		DOS_allocmem(int size,unsigned *seg);
void		DOS_memfree(unsigned seg);
DEVHED	far 	*init_dmy_dev(void);
DPB	far	*get_DPB(int dev);
DPB	far	*link_DPB(void);

ushort		psp_seg;
uchar		Cache_off=FALSE;
uchar		hit_flg=0;
uchar		max_dev=0;
ushort		SecLen=0;
uchar		SecAlc=0;
ushort		DatSec=0;
ushort		MaxCls=0;
char	far	*tmp=NULL;
DEVHED	far	*dmy_dev=NULL;
REQ		dum_req;

DPB     far	*dpb_ptr[MAX_DEV];
REQ     far	*req_ptr[MAX_DEV];
DEVHED	far	*dev_ent[MAX_DEV];

SEC_PTR 	*use_tbl[MAX_HASH];
SEC_PTR 	*lru_tbl[MAX_HASH];
SEC_PTR		tbl_buf[MAX_SEC];

int	serch(uchar dev,ulong sec,int md)
{
    int     hs;
    register SEC_PTR *bp;
    register SEC_PTR *tp;

    hs = HASH(sec);

    hit_flg = FALSE;
    tp = bp = lru_tbl[hs];
    while ( bp != NULL ) {
        if ( bp->dev == dev && bp->sec == sec ) {
            hit_flg = TRUE;
            break;
        } else if ( bp->next == NULL )
            break;
        tp = bp;
        bp = bp->next;
    }
    if ( tp != bp && (md != FALSE || hit_flg != FALSE) ) {
        tp->next = bp->next;
        bp->next = lru_tbl[hs];
        lru_tbl[hs] = bp;
    }
    return (bp == NULL ? 0:((uint)(bp) - (uint)(tbl_buf)) / sizeof(SEC_PTR));
}
void    use_set(int pos)
{
    int     hs;
    SEC_PTR *pp;
    register SEC_PTR *bp;
    register SEC_PTR *tp;

    if ( pos >= MAX_SEC )
	return;

    pp = &(tbl_buf[pos]);

    if ( bp->dev == USE_DEV )
        return;

    hs = HASH(pp->sec);
    tp = bp = lru_tbl[hs];
    while ( bp != NULL ) {
        if ( bp == pp )
            break;
        tp = bp;
        bp = bp->next;
    }

    if ( bp == NULL )
        return;

    if ( tp != bp )
        tp->next = bp->next;
    else
        lru_tbl[hs] = bp->next;

    bp->dev = USE_DEV;
    bp->next = use_tbl[hs];
    use_tbl[hs] = bp;
}
void    lru_set(int pos)
{
    int     hs;
    SEC_PTR *pp;
    register SEC_PTR *bp;
    register SEC_PTR *tp;

    if ( pos >= MAX_SEC )
	return;

    pp = &(tbl_buf[pos]);

    if ( pp->dev != USE_DEV )
        return;

    hs = HASH(pp->sec);
    tp = bp = use_tbl[hs];
    while ( bp != NULL ) {
        if ( bp == pp )
            break;
        tp = bp;
        bp = bp->next;
    }

    if ( bp == NULL )
        return;

    if ( tp != bp )
        tp->next = bp->next;
    else
        use_tbl[hs] = bp->next;

    bp->dev = FRE_DEV;
    bp->next = lru_tbl[hs];
    lru_tbl[hs] = bp;
}
void	flush_tbl(int max_sec)
{
    int     i,hs;
    register SEC_PTR *bp;

    for ( i = 0 ; i < MAX_HASH ; i++ )
	use_tbl[i] = lru_tbl[i] = NULL;

    bp = tbl_buf;
    for ( i = 0 ; i < max_sec && i < MAX_SEC ; i++ ) {
	hs = HASH(i);
	bp->sec = i;
	bp->dev = USE_DEV;
	bp->next = use_tbl[hs];
	use_tbl[hs] = bp;
	bp++;
    }
}
void	flush_dev(uchar dev)
{
    int     i;
    register SEC_PTR *bp;

    for ( i = 0 ; i < MAX_HASH ; i++ ) {
	bp = lru_tbl[i];
	while ( bp != NULL ) {
	    if ( bp->dev == dev )
		bp->dev = FRE_DEV;
	    bp = bp->next;
	}
    }
}
void	Device_call(uchar dev,REQ far *req)
{
    union {
	char far *p;
	short	s[2];
    } pp;

    pp.p = (char far *)(dev_ent[dev]);

    pp.s[0] = dev_ent[dev]->str_ent;
    Dev_str_call(pp.p,req);

    pp.s[0] = dev_ent[dev]->int_ent;
    Dev_int_call(pp.p);
}
int	Read_sub(ushort sec,char far *buf)
{
    dum_req.len = 19;
    dum_req.unit = dpb_ptr[0]->UnitNum;
    dum_req.cmds = 4;
    dum_req.stat = 0;
    dum_req.md_id = dpb_ptr[0]->MedId;
    dum_req.x.rdwt.buff = buf;
    dum_req.x.rdwt.sect = sec;
    dum_req.x.rdwt.count = 1;
    Device_call(0,(REQ far *)&dum_req);
    return ((dum_req.stat & 0x8000) != 0 ? ERR:FALSE);
}
int	Write_sub(ushort sec,char far *buf)
{
    dum_req.len = 19;
    dum_req.unit = dpb_ptr[0]->UnitNum;
    dum_req.cmds = 8;
    dum_req.stat = 0;
    dum_req.md_id = dpb_ptr[0]->MedId;
    dum_req.x.rdwt.buff = buf;
    dum_req.x.rdwt.sect = sec;
    dum_req.x.rdwt.count = 1;
    Device_call(0,(REQ far *)&dum_req);
    return ((dum_req.stat & 0x8000) != 0 ? ERR:FALSE);
}
void	Cache_set(uchar dev,ushort sec,char far *buf)
{
    int     rt;
    ushort  sz,pos;
    ulong   lsec;

    sz = dpb_ptr[dev]->SecLen;
    lsec = (ulong)sec * ((sz + SecLen -1) / SecLen);

    while ( (int)sz > 0 ) {
        if ( (pos = serch(dev,lsec,TRUE)) == 0 )
            break;

        if ( sz < SecLen ) {
            farmemcpy(tmp,buf,sz);
            rt = Write_sub(pos,tmp);
        } else
            rt = Write_sub(pos,buf);

	if ( rt != FALSE ) {
	    use_set(pos);
	    continue;
	}

        tbl_buf[pos].dev = dev;
        tbl_buf[pos].sec = lsec;
        tbl_buf[pos].sum = sum_calc(buf,sz);

        buf += SecLen;
        sz -= SecLen;
        lsec++;
    }
}
int	Cache_chk(uchar dev,ushort sec,char far *buf)
{
    int     rt;
    ushort  sz,pos;
    ulong   lsec;

    sz = dpb_ptr[dev]->SecLen;
    lsec = (ulong)sec * ((sz + SecLen - 1) / SecLen);

    while ( (int)sz > 0 ) {
        if ( (pos = serch(dev,lsec,FALSE)) == 0 || hit_flg == FALSE )
	    return FALSE;

        if ( sz < SecLen ) {
            rt = Read_sub(pos,tmp);
            farmemcpy(buf,tmp,sz);
        } else
            rt = Read_sub(pos,buf);

        if ( rt != FALSE || tbl_buf[pos].sum != sum_calc(buf,sz) ) {
            use_set(pos);
            continue;
        }

        buf += SecLen;
        sz -= SecLen;
        lsec++;
    }

    return TRUE;
}
void    cls_lru_set(ushort cls)
{
    int     i;
    ushort  sec;

    if ( cls < 2 || cls >= MaxCls )
        return;

    sec = (cls - 2) * SecAlc + DatSec;

    for ( i = 0 ; i < SecAlc ; i++ )
        lru_set(sec++);
}
void    cls_use_set(ushort cls)
{
    int     i;
    ushort  sec;

    if ( cls < 2 || cls >= MaxCls )
        return;

    sec = (cls - 2) * SecAlc + DatSec;

    for ( i = 0 ; i < SecAlc ; i++ )
        use_set(sec++);
}
void    FAT_chk(int no,char far *p)
{
    int     i,n,max;

    no *= SecLen;
    n = (no + 2) / 3;
    i = n * 3 - no;
    max = SecLen - 3;

    while ( i < max ) {

        if ( (*((short far *)(&p[i])) & 0x0FFF) == 0 )
            cls_lru_set(n * 2);
        else
            cls_use_set(n * 2);

        if ( (*((short far *)(&p[i+1])) & 0xFFF0) == 0 )
            cls_lru_set(n * 2 + 1);
        else
            cls_use_set(n * 2 + 1);

        n++;
        i += 3;
    }
}
void	Device_entry(uchar dev)
{
    int     i;
    DPB far *dp;
    REQ far *rp;
    BPB far *bp;
    ushort  sec;
    char far *buf;

    dp = dpb_ptr[dev];
    rp = req_ptr[dev];

    if ( dev == 0 ) {		/* Cache Buffer Device */
	switch(rp->cmds) {
	case 2:		/* Build BPB */
	    Device_call(dev,rp);
	    bp = rp->x.build.bpb;

	    if ( (rp->stat & 0x8000) != 0 || SecLen < bp->SecLen ) {
		Cache_off = TRUE;
		break;
	    }
	    
	    SecLen = bp->SecLen;
	    SecAlc = bp->SecAlc + 1;
	    DatSec = bp->ResSec + 
		     bp->DirEnt * 32 / bp->SecLen + 
		     bp->FatLen * bp->FatSec;
	    MaxCls = (bp->MaxSec - DatSec) / SecAlc;

	    flush_tbl(bp->MaxSec);
	    Cache_off = FALSE;
	    break;

	case 4:		/* Read */
	case 8:		/* Write */
	case 9:		/* Write & Verify */
	    Device_call(dev,rp);
	    if ( (rp->stat & 0x8000) != 0 )
		break;

	    sec = rp->x.rdwt.sect;
	    buf = rp->x.rdwt.buff;
	    i = rp->x.rdwt.count;

	    while ( i-- > 0 ) {
		if ( sec >= dp->ResSec && (sec - dp->ResSec) < dp->FatSec )
		    FAT_chk(sec - dp->ResSec,buf);
		else
		    use_set(sec);
		sec++;
		buf += dp->SecLen;
	    }
	    break;

	case 80:	/* Ext Command Get Mode */
	    rp->x.ext.mode = Cache_off;
	    rp->x.ext.max_dev = max_dev;
	    rp->x.ext.dpb_ptr = (DPB far *)dpb_ptr;
	    rp->x.ext.lru_tbl = (SEC_PTR far *)lru_tbl;
	    rp->x.ext.use_tbl = (SEC_PTR far *)use_tbl;
	    rp->stat = 0x0100;
	    break;

	case 81:	/* Ext Command Exit Cache */
	    for ( i = 0 ; i < max_dev ; i++ )
		dpb_ptr[i]->DevEnt = dev_ent[i];
	    rp->x.quit.psp_seg = psp_seg;
	    rp->stat = 0x0100;
	    break;

	default:
	    Device_call(dev,rp);
	    break;
	}

    } else {
	if ( Cache_off != FALSE ) {
	    Device_call(dev,rp);
	    return;
	}

	switch(rp->cmds) {
	case 1:		/* Media Check */
	    Device_call(dev,rp);
	    if ( (rp->stat & 0x8000) != 0 || rp->x.chk.now_sts <= 0 )
		flush_dev(dev);
	    break;

	case 2:		/* Build BPB */
	    Device_call(dev,rp);
	    flush_dev(dev);
	    break;

	case 4:		/* Read */
	    sec = rp->x.rdwt.sect;
	    buf = rp->x.rdwt.buff;
	    i = rp->x.rdwt.count;

	    while ( i-- > 0 ) {
		if ( Cache_chk(dev,sec,buf) == FALSE )
		    goto CACHE_SET;
		sec++;
		buf += dp->SecLen;
	    }
	    rp->stat = 0x0100;
	    break;

	case 8:		/* Write */
	case 9:		/* Write & Verify */
	CACHE_SET:
	    sec = rp->x.rdwt.sect;
	    buf = rp->x.rdwt.buff;
	    i = rp->x.rdwt.count;

	    Device_call(dev,rp);
	    if ( (rp->stat & 0x8000) != 0 ) {
		flush_dev(dev);
		break;
	    }

	    while ( i-- > 0 ) {
		Cache_set(dev,sec,buf);
		sec++;
		buf += dp->SecLen;
	    }
	    break;

	default:
	    Device_call(dev,rp);
	    if ( (rp->stat & 0x8000) != 0 )
		flush_dev(dev);
	    break;
	}
    }
}
int	Dev_set(int dev,int unit)
{
    DPB far *dp;

    if ( dev == 0 )
	dp = get_DPB(unit);
    else {
	dp = link_DPB();
	while ( (long)dp != 0xFFFFFFFFL ) {
	    if ( dp->DrvNum == unit )
		break;
	    dp = dp->NxtDPB;
	}
    }

    if ( (long)dp == 0xFFFFFFFFL )
	return ERR;

    dpb_ptr[dev] = dp;
    dev_ent[dev] = dpb_ptr[dev]->DevEnt;
    dpb_ptr[dev]->DevEnt = dmy_dev + dev;

    dmy_dev[dev].next = dev_ent[dev]->next;
    dmy_dev[dev].dev_sts = dev_ent[dev]->dev_sts;
    dmy_dev[dev].res[0] = dev_ent[dev]->res[0];

    return FALSE;
}
int	setup(char *para)
{
    static int    i;
    int    dev=0;
    char   *p;
    union {
	char far *p;
	short	 s[2];
    } sp;

    dmy_dev = init_dmy_dev();

    i = *(para++);
    para[i] = '\0';
    while ( *para != '\0' ) {
	while ( *para != '\0' && *para <= ' ' ) para++;
	if ( (*para >= 'a' && *para <= 'z') ||
	     (*para >= 'A' && *para <= 'Z') ) {
	    if ( Dev_set(dev++,(*para & 0xDF) - 'A') ) {
	        msg_disp("デバイスを確認できません\n\r$");
	        return 1;
	    }
	}
	while ( *para != '\0' && *para > ' ' ) para++;
    }

    if ( dev == 0 ) {
	msg_disp("use: CACHE <Buffer Device> <Cache Device....>\n\r$");
	return 1;
    }

    max_dev = dev;
    SecLen = dpb_ptr[0]->SecLen;
    SecAlc = dpb_ptr[0]->SecAlc + 1;
    DatSec = dpb_ptr[0]->DatSec;
    MaxCls = dpb_ptr[0]->MaxCls;
    flush_tbl(MaxCls * SecAlc);
    Cache_off = FALSE;

    if ( DOS_allocmem((SecLen+15U)/16,(unsigned *)&i) != FALSE ) {
	msg_disp("セクタバッファメモリを確保できませんでした\n\r$");
	goto ERROR;
    }

    FP_OFF(tmp) = 0;
    FP_SEG(tmp) = i;

    for ( i = 0 ; i < dpb_ptr[0]->FatSec ; i++ ) {
	if ( Read_sub(dpb_ptr[0]->ResSec + i,tmp) == FALSE )
	    FAT_chk(i,tmp);
    }

    DOS_memfree(FP_SEG(tmp));

    msg_disp("キャッシュが設定されました！！\n\r$");

    return 0;

ERROR:
    for ( i = 0 ; i < max_dev ; i++ )
	dpb_ptr[i]->DevEnt = dev_ent[i];

    return 1;
}
