Bool DriveLock(CDrive *drive)
{//Make this task have exclusive access to drive & BlkDev.
    DriveCheck(drive);
    BlkDevLock(drive->bd);
    if (!Bt(&drive->locked_flags, DVlf_LOCKED) || drive->owning_task != Fs)
    {
        while (LBts(&drive->locked_flags, DVlf_LOCKED))
            Yield;
        drive->owning_task = Fs;
        return TRUE;
    }
    else
        return FALSE;
}

Bool DriveUnlock(CDrive *drive, Bool reset=FALSE)
{//Release exclusive lock on access to drive & BlkDev.
    DriveCheck(drive);
    if (Bt(&drive->locked_flags, DVlf_LOCKED) && drive->owning_task == Fs)
    {
        BlkDevUnlock(drive->bd, reset);
        drive->owning_task = NULL;
        LBtr(&drive->locked_flags, DVlf_LOCKED);
        Yield; //Prevent deadlock
        return TRUE;
    }
    else
        return FALSE;
}

U0 DrivesRelease()
{//When task dies, release all owned drvs.
    I64      i;
    CDrive  *drive;

    for (i = 0; i < DRIVES_NUM; i++)
    {
        drive = &blkdev.drvs[i];
        if (drive->owning_task == Fs && drive->drive_signature == DRIVE_SIGNATURE_VAL)
            DriveUnlock(drive, TRUE);
    }
}

CDrive *DriveMakeFreeSlot(U8 drv_let)
{//Make a slot free for a new drive, like during Mount().
    I64      i = Letter2Letter(drv_let) - 'A';
    CDrive  *res;

    if (!(0 <= i < DRIVES_NUM))
        throw('Drive');
    res = &blkdev.drvs[i];
    MemSet(res, 0, sizeof(CDrive));
    res->drv_let = 'A' + i;
    return res;
}

U8 DriveNextFreeLet(U8 first_drive_let='C')
{//Locate free slot for new drive, like during Mount().
    I64 i = Letter2Letter(first_drive_let) - 'A', type = Letter2BlkDevType(first_drive_let);

    if (!(0 <= i < DRIVES_NUM))
        throw('Drive');
    do
        if (blkdev.drvs[i].drive_signature != DRIVE_SIGNATURE_VAL)
        {
            if (Letter2BlkDevType(i + 'A') != type)
                throw('Drive');
            else
                return i + 'A';
        }
    while (++i < DRIVES_NUM);
    throw('Drive');
    return 0; //Never gets here.
}

U0 DriveDel(CDrive *drive)
{//Delete drive
    if (drive->fs_type == FSt_REDSEA && drive->next_free)
        RedSeaFreeFreeList(drive);
    Free(drive->cur_fat_blk);
    Free(drive->fis);
    MemSet(drive, 0, sizeof(CDrive));
}

U0 DriveBlkDevDel(CBlkDev *bd)
{//Delete drives of BlkDev
    I64      i;
    CDrive  *drive;

    for (i = 0; i < DRIVES_NUM; i++)
    {
        drive = &blkdev.drvs[i];
        if (drive->bd == bd)
            DriveDel(drive);
    }
}

U0 DriveFATBlkAlloc(CDrive *drive)
{
    DriveCheck(drive);
    Free(drive->cur_fat_blk);
    drive->cur_fat_blk      = SysMAlloc(BLK_SIZE);
    drive->cur_fat_blk_num  = 0;
    drive->fat_blk_dirty    = 0;
    BlkRead(drive, drive->cur_fat_blk, drive->fat1, 1);
}

U0 DriveFATBlkClean(CDrive *drive, I64 fat_sel=3)
{
    if ((drive->fs_type == FSt_FAT32 || drive->fs_type == FSt_REDSEA) && Bt(&drive->fat_blk_dirty, 0))
    {
        if (drive->fat1 == drive->fat2)
        {
            BlkWrite(drive, drive->cur_fat_blk, drive->fat1 + drive->cur_fat_blk_num, 1);
            LBtr(&drive->fat_blk_dirty, 0);
        }
        else
        {
            if (fat_sel == 3 || !fat_sel)
                BlkWrite(drive, drive->cur_fat_blk, drive->fat1 + drive->cur_fat_blk_num, 1);
            if (fat_sel == 3 || fat_sel == 1)
            {
                BlkWrite(drive, drive->cur_fat_blk, drive->fat2 + drive->cur_fat_blk_num, 1);
                LBtr(&drive->fat_blk_dirty, 0);
            }
        }
    }
}

U0 DriveFATBlkSet(CDrive *drive, I64 c, I64 fat_sel=3)
{
    I64 fat_blk_num;

    if (c == INVALID_CLUS)
        throw('Drive');
    switch (drive->fs_type)
    {
        case FSt_FAT32:
            fat_blk_num = c >> (BLK_SIZE_BITS - 2);
            break;

        case FSt_REDSEA:
            fat_blk_num = (c-drive->data_area) >> (BLK_SIZE_BITS + 3);
            break;

        default:
            throw('Drive');
    }
    if (fat_blk_num != drive->cur_fat_blk_num)
    {
        DriveFATBlkClean(drive, fat_sel);
        drive->cur_fat_blk_num = fat_blk_num;
        if (fat_sel == 3 || !fat_sel)
            BlkRead(drive, drive->cur_fat_blk, drive->fat1 + drive->cur_fat_blk_num, 1);
        else
            BlkRead(drive, drive->cur_fat_blk, drive->fat2 + drive->cur_fat_blk_num, 1);
    }
}

CDrive *DriveCheck(CDrive *drive, Bool except=TRUE)
{//Check for valid drive. Throw exception.
    if (!drive || drive->drive_signature != DRIVE_SIGNATURE_VAL)
    {
        if (except)
            throw('Drive');
        else
            return NULL;
    }
    else
        return drive;
}

U8 Drive2Letter(CDrive *drive=NULL)
{//Drive pointer to Drive letter.
    if (!drive)
        drive = Fs->cur_dv;
    DriveCheck(drive);
    return drive->drv_let;
}

U8 Letter2Letter(U8 drv_let=0)
{//Drive letter to Drive letter.
    if (!drv_let)
        drv_let = Drive2Letter(Fs->cur_dv);
    else if (drv_let == ':')
        drv_let = blkdev.boot_drive_let;
    else if (drv_let == '~')
        drv_let = *blkdev.home_dir;

    return ToUpper(drv_let);
}

I64 Letter2BlkDevType(U8 drv_let)
{//Drive letter to BlkDev Type. drv_let=0 not allowed. See BDT_NULL.
    drv_let = Letter2Letter(drv_let);

    if ('A' <= drv_let <= 'B')
        return BDT_RAM;
    if ('C' <= drv_let <= 'L')
        return BDT_ATA;
    if ('M' <= drv_let <= 'P')
        return BDT_ISO_FILE_READ;
    if ('Q' <= drv_let <= 'S')
        return BDT_ISO_FILE_WRITE;
    if ('T' <= drv_let <= 'Z')
        return BDT_ATAPI;

    return BDT_NULL;
}

CDrive *Letter2Drive(U8 drv_let=0, Bool except=TRUE)
{//Drive letter to Drive pointer.
    CDrive *drive;

    if (!drv_let)
        drive = Fs->cur_dv;
    else
    {
        drv_let = Letter2Letter(drv_let);
        if (!('A' <= drv_let <= 'Z'))
        {
            if (except)
                throw('Drive');
            else
                return NULL;
        }
        drive = blkdev.let_to_drive[drv_let - 'A'];
    }
    return DriveCheck(drive, except);
}

CBlkDev *DriveIsWritable(U8 drv_let=0, Bool except=FALSE)
{//Is drive writable?
    CBlkDev *bd;

    if (!(bd = Letter2BlkDev(drv_let, except)) || bd->flags & BDF_READ_ONLY)
    {
        if (except)
            throw('Drive');
        else
            return NULL;
    }
    else
        return bd;
}

U0 DiskCacheInvalidate(CDrive *drive)
{//Needed for removable media. Called by DiskChange().
    Bool     unlock;
    CBlkDev *bd = drive->bd;

    DriveCheck(drive);
    try
    {
        unlock = DriveLock(drive);
        BlkDevInit(bd);
        if (bd->flags & BDF_READ_CACHE)
            DiskCacheInvalidate2(drive);
        if (bd->type == BDT_ATAPI && !(bd->flags & BDF_READ_ONLY_OVERRIDE))
            ISOInit(drive, (32767 / bd->blk_size + 1) * bd->blk_size >> BLK_SIZE_BITS);
        if (unlock)
            DriveUnlock(drive);
    }
    catch
        if (unlock)
            DriveUnlock(drive);
}

U0 DiskChange(U8 drv_let=0)
{//Change disk. (Needed for removable media.)
    CDrive  *drive = Letter2Drive(drv_let);
    CBlkDev *bd    = drive->bd;

    if (!(bd->flags & BDF_INITIALIZED))
        BlkDevInit(bd);
    else if (bd->flags & BDF_REMOVABLE)
    {
        if (bd->type == BDT_ATAPI)
            AHCIAtaInit(bd); //TODO: This is a kludge for QEMU?
        DiskCacheInvalidate(drive);
    }
    Drive(drv_let);
    RedSeaFreeFreeList(drive);
}

Bool Drive(U8 drv_let=0)
{//Change drive.    You can set drive with Cd() as well.
    CDrive  *drive = Letter2Drive(drv_let);
    CBlkDev *bd;

    bd = BlkDevCheck(drive->bd);
    if (drive != Fs->cur_dv)
    {
        if (bd->flags & BDF_REMOVABLE && !(bd->flags & BDF_INITIALIZED))
            DiskChange(Drive2Letter(drive));
        if (bd->type == BDT_RAM || bd->type == BDT_ISO_FILE_READ || bd->type == BDT_ISO_FILE_WRITE)
            BlkDevInit(bd);
    }
    Fs->cur_dv = drive;
    Free(Fs->cur_dir);
    Fs->cur_dir = StrNew("/");
    switch (drive->fs_type)
    {
        case FSt_REDSEA:
        case FSt_ISO9660:
        case FSt_FAT32:
            return TRUE;

        default:
            PrintErr("File System Not Supported\n");
            return FALSE;
    }
}

U8 *DriveSerialNum(U8 drv_let=0)
{//20 bytes max.
    CBlkDev *bd = Letter2BlkDev(drv_let);
    U16     *st, *res = NULL;
    I64      i;

    if (bd->dev_id_record)
    {
        st = CAlloc(20 + 1);
        for (i = 0; i < 10; i++)
            st[i] = EndianU16(bd->dev_id_record[10 + i]);
        res = MStrUtil(st, SUF_REM_LEADING | SUF_REM_TRAILING);
        Free(st);
    }

    return res;
}

U8 *DriveModelNum(U8 drv_let=0)
{//40 bytes max.
    CBlkDev *bd = Letter2BlkDev(drv_let);
    U16     *st, *res = NULL;
    I64      i;

    if (bd->dev_id_record)
    {
        st = CAlloc(40 + 1);
        for (i = 0; i < 20; i++)
            st[i] = EndianU16(bd->dev_id_record[27 + i]);
        res = MStrUtil(st, SUF_REM_LEADING | SUF_REM_TRAILING);
        Free(st);
    }
    return res;
}

U8 blkdev_text_attr[BDT_TYPES_NUM]  = {BLACK, LTCYAN, WHITE, LTGREEN, LTRED, LTBLUE};
U8 drv_text_attr[3]                 = {BLACK, BLUE, RED};

U8 DriveTextAttrGet(U8 drv_let=0)
{//Get color of drive.
    drv_let = Letter2Letter(drv_let);
    if ('A' <= drv_let <= 'Z')
        return blkdev_text_attr[Letter2BlkDevType(drv_let)] << 4 | drv_text_attr[drv_let % sizeof(drv_text_attr)];
    else
        return BLACK << 4 | WHITE;
}

U0 DriveRep()
{//Drive report.
    CDrive  *drive;
    CBlkDev *bd;
    I64      ch, i, drv_let, attr;
    U8      *st;

    "\nDefined Drives:\n";
    for (i = 0, drive = blkdev.drvs; i < DRIVES_NUM; i++, drive++)
    {
        if (drive->drive_signature == DRIVE_SIGNATURE_VAL)
        {
            bd = drive->bd;
            drv_let = Drive2Letter(drive);
            if (Bt(&drive->fs_type, FStf_DISABLE))
                ch = '-';
            else if (drv_let == blkdev.boot_drive_let)
                ch = ':';
            else
                ch = '+';
            attr = DriveTextAttrGet(drv_let);

            if (!IsRaw)
                "$FG,%d$$BG,%d$", attr & 15, attr >> 4;

            "%C %-9Z %-10Z ",   drv_let,
                                drive->fs_type & FSG_TYPE_MASK,
                                "ST_DRIVE_TYPES",
                                bd->type,
                                "ST_BLKDEV_TYPES";
            if (bd->ahci_port)
                "SATA Port: %02d", bd->port_num;
            '\n';

            if (st = DriveModelNum(drv_let))
            {
                "   Model:   %s\n", st;
                Free(st);
            }
            if (st = DriveSerialNum(drv_let))
            {
                "   Serial:  %s\n", st;
                Free(st);
            }
            if (bd->type == BDT_ISO_FILE_READ || bd->type == BDT_ISO_FILE_WRITE)
                "   File=\"%s\"\n", bd->file_disk_name;
            "   Offset:\t\t\tOffset + Size:\n";
            "   %016X-%016X\n", drive->drv_offset, drive->drv_offset + drive->size - 1;
            if (!IsRaw)
                "$FG$$BG$";
        }
    }
    "Home Dir:\"%s\"\n", blkdev.home_dir;
}