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 = SysCAlloc(offset(CCacheBlk.body));
        blkdev.cache_base = SysMAlloc(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 = SysMAlloc(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 blocks 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 blocks 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);
    }
}