U0 DiskCacheInit(I64 size_in_U8s)
{
	CCacheBlk	*tmpc;
	I64			 i, count;

	while (LBts(&sys_semas[SEMA_DISK_CACHE], 0))
		Yield;
	Free(blkdev.cache_ctrl);
	Free(blkdev.cache_base);
	Free(blkdev.cache_hash_table);
	if (size_in_U8s < 0x2000)
	{
		blkdev.cache_ctrl		= NULL;
		blkdev.cache_base		= NULL;
		blkdev.cache_hash_table	= NULL;
	}
	else
	{
		blkdev.cache_ctrl = ZCAlloc(offset(CCacheBlk.body));
		blkdev.cache_base = ZMAlloc(size_in_U8s);
		QueueInit(blkdev.cache_ctrl);

		count = MSize(blkdev.cache_base) / sizeof(CCacheBlk);
		blkdev.cache_size = count * BLK_SIZE;
		for (i = 0; i < count; i++)
		{
			tmpc = blkdev.cache_base+i;
			QueueInsert(tmpc, blkdev.cache_ctrl->last_lru);
			tmpc->next_hash	= tmpc->last_hash = tmpc;
			tmpc->drive		= NULL;
			tmpc->blk		= 0;
		}

		blkdev.cache_hash_table = ZMAlloc(DISK_CACHE_HASH_SIZE * sizeof(U8 *) * 2);
		for (i = 0; i < DISK_CACHE_HASH_SIZE; i++)
		{
			tmpc = blkdev.cache_hash_table(U8 *) + i * sizeof(U8 *) * 2 - offset(CCacheBlk.next_hash);
			tmpc->next_hash = tmpc->last_hash = tmpc;
		}
	}
	LBtr(&sys_semas[SEMA_DISK_CACHE], 0);
}

I64 DiskCacheHash(I64 blk)
{
	I64 i = blk & (DISK_CACHE_HASH_SIZE - 1);

	return blkdev.cache_hash_table(U8 *) + i << 4 - offset(CCacheBlk.next_hash);
}

U0 DiskCacheQueueRemove(CCacheBlk *tmpc)
{
	QueueRemove(tmpc);
	tmpc->next_hash->last_hash = tmpc->last_hash;
	tmpc->last_hash->next_hash = tmpc->next_hash;
}

U0 DiskCacheQueueIns(CCacheBlk *tmpc)
{
	CCacheBlk *tmp_n, *tmp_l;

	QueueInsert(tmpc, blkdev.cache_ctrl->last_lru);
	tmp_l = DiskCacheHash(tmpc->blk);
	tmp_n = tmp_l->next_hash;
	tmpc->last_hash = tmp_l;
	tmpc->next_hash = tmp_n;
	tmp_l->next_hash = tmp_n->last_hash = tmpc;
}

CCacheBlk *DiskCacheFind(CDrive *drive, I64 blk)
{
	CCacheBlk *tmpc, *tmpc1 = DiskCacheHash(blk);

	tmpc = tmpc1->next_hash;
	while (tmpc != tmpc1)
	{
		if (tmpc->drive == drive && tmpc->blk == blk)
			return tmpc;
		tmpc = tmpc->next_hash;
	}
	return NULL;
}

U0 DiskCacheAdd(CDrive *drive, U8 *buf, I64 blk, I64 count)
{
	CCacheBlk *tmpc;

	if (blkdev.cache_base)
	{
		while (LBts(&sys_semas[SEMA_DISK_CACHE], 0))
			Yield;
		while (count-- > 0)
		{
			if (!(tmpc = DiskCacheFind(drive, blk)))
				tmpc = blkdev.cache_ctrl->next_lru;
			DiskCacheQueueRemove(tmpc);
			MemCopy(&tmpc->body, buf, BLK_SIZE);
			tmpc->drive = drive;
			tmpc->blk   = blk;
			DiskCacheQueueIns(tmpc);
			blk++;
			buf += BLK_SIZE;
		}
		LBtr(&sys_semas[SEMA_DISK_CACHE], 0);
	}
}

U0 DiskCacheInvalidate2(CDrive *drive)
{
	CCacheBlk *tmpc, *tmpc1;

	if (blkdev.cache_base)
	{
		while (LBts(&sys_semas[SEMA_DISK_CACHE], 0))
			Yield;
		tmpc = blkdev.cache_ctrl->last_lru;
		while (tmpc != blkdev.cache_ctrl)
		{
			tmpc1 = tmpc->last_lru;
			if (tmpc->drive == drive)
			{
				DiskCacheQueueRemove(tmpc);
				tmpc->drive		= NULL;
				tmpc->blk		= 0;
				tmpc->next_hash	= tmpc->last_hash = tmpc;
				QueueInsert(tmpc, blkdev.cache_ctrl->last_lru);
			}
			tmpc = tmpc1;
		}
		LBtr(&sys_semas[SEMA_DISK_CACHE], 0);
	}
}

U0 RCache(CDrive *drive, U8 **_buf, I64 *_blk, I64 *_count)
{
	CCacheBlk *tmpc;

	if (blkdev.cache_base)
	{
		while (LBts(&sys_semas[SEMA_DISK_CACHE], 0))
			Yield;
//fetch leading blks from cache
		while (*_count > 0)
		{
			if (tmpc = DiskCacheFind(drive, *_blk))
			{
				MemCopy(*_buf, &tmpc->body, BLK_SIZE);
				*_count -= 1;
				*_buf += BLK_SIZE;
				*_blk += 1;
			}
			else
				break;
		}
//fetch trailing blks from cache
		while (*_count > 0)
		{
			if (tmpc = DiskCacheFind(drive, *_blk + *_count - 1))
			{
				MemCopy(*_buf + (*_count - 1) << BLK_SIZE_BITS, &tmpc->body, BLK_SIZE);
				*_count -= 1;
			}
			else
				break;
		}
		LBtr(&sys_semas[SEMA_DISK_CACHE], 0);
	}
}
