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