#help_index "PCI;Info;File/System;Devices"
public I64 SATARep(I64 bd_type=BDT_NULL)
{ // Report ATA and ATAPI drives implemented by SATA controller.
    I64          bdf, i, j, num = 0;
    CAHCIPort   *port;
    CPCIDev     *pci;
    CBlkDev     *temp_blkdev;
    U16         *st, *model, *serial;
    Bool         show_atapi = FALSE, show_ata = FALSE;


    switch (bd_type)
    {
        case BDT_NULL:
            show_atapi = show_ata = TRUE;
            break;
        case BDT_ATAPI:
            show_atapi = TRUE;
            break;
        case BDT_ATA:
            show_ata = TRUE;
            break;
    }

    if (!IsRaw)
        "\n$LTBLUE$AHCI version %X.%1X%1X$FG$\n\n",
            blkdev.ahci_hba->version >> 16, (blkdev.ahci_hba->version & 0xFF00) >> 8, blkdev.ahci_hba->version & 0xFF;
    else
        "\nAHCI version %X.%1X%1X\n\n",
            blkdev.ahci_hba->version >> 16, (blkdev.ahci_hba->version & 0xFF00) >> 8, blkdev.ahci_hba->version & 0xFF;

    if (dev.pci_head)
    {
        pci = PCIDevFind(PCIC_STORAGE, PCISC_AHCI);
        if (!IsRaw)
        {
            "$RED$$HL,1$Bus: 0x%02X, Dev: 0x%02X, Fun: 0x%02X$HL,0$$FG$\n\n", pci->bus, pci->dev, pci->fun;
            "$RED$Vendor$FG$: $LTBLUE$%s$FG$\n", pci->vendor_str;
            "$RED$Device$FG$: $LTBLUE$%s$FG$\n", pci->dev_id_str;
        }
        else
        {
            "Bus: 0x%02X, Dev: 0x%02X, Fun: 0x%02X\n\n", pci->bus, pci->dev, pci->fun;
            "Vendor: %s\n", pci->vendor_str;
            "Device: %s\n", pci->dev_id_str;
        }
    }   
    else
    {
        bdf = PCIClassFind(PCIC_STORAGE << 16 | PCISC_AHCI << 8 + 1, 0);
        "Bus:%02X, Dev:%02X, Fun:%02X\n", bdf.u8[2], bdf.u8[1], bdf.u8[0];
        "HBA Base Address: 0x%X", PCIReadU32(bdf.u8[2], bdf.u8[1], bdf.u8[0], PCIR_BASE5) & ~0x1F;
    }

    if (blkdev.ahci_hba)
    {
        "\nImplemented Ports:\n";
        "(Show ATA:   %Z)\n", show_ata, "ST_FALSE_TRUE";
        "(Show ATAPI: %Z)\n\n", show_atapi, "ST_FALSE_TRUE";

        for (i = 0; i < AHCI_MAX_PORTS; i++)
        {
            if (PCIBt(&blkdev.ahci_hba->ports_implemented, i))
            {
                port = &blkdev.ahci_hba->ports[i];

                if (port->signature == AHCI_PxSIG_ATAPI && show_atapi ||
                    port->signature == AHCI_PxSIG_ATA   && show_ata)
                {
                    if (!IsRaw)
                        "$PURPLE$ $BT,\"%d\",LM=\"%d\n\"$$FG$", i, i;
                    else
                        "[%d]", i;

                    if (port->signature == AHCI_PxSIG_ATA)
                    {
                        if (!IsRaw)
                            "$LM,4$$RED$Hard Drive   $LTBLUE$ATA$FG$\n";
                        else
                            "\tHard Drive   ATA\n";
                    }
                    else if (port->signature == AHCI_PxSIG_ATAPI)
                    {
                        if (!IsRaw)
                            "$LM,4$$RED$CD/DVD Drive $LTBLUE$ATAPI$FG$\n";
                        else
                            "\tCD/DVD Drive ATAPI\n";
                    }

                    if (!IsRaw)
                        "$LM,0$";
                    '\n\t';

                    temp_blkdev = CAlloc(sizeof(CBlkDev));
                    if (port->signature == AHCI_PxSIG_ATAPI)
                        temp_blkdev->first_drive_let = 'T';
                    else // ATA
                        temp_blkdev->first_drive_let = 'C';

                    try
                        AHCIPortInit(temp_blkdev, port, i);
                    catch
                    {
                        Fs->catch_except = TRUE;
                        "Error at SATA Port %d", i;
                    }

                    if (i == blkdev.ins_port)
                        "\t(Drive originally installed from.)\n";

                    "\n\t";

                    if (temp_blkdev->dev_id_record)
                    {
                        st = CAlloc(40 + 1);
                        for (j = 0; j < 20; j++)
                            st[j] = EndianU16(temp_blkdev->dev_id_record[27 + j]);
                        model = MStrUtil(st, SUF_REM_LEADING | SUF_REM_TRAILING);
                        "Model:  %s\n\t", model;
                        Free(st);
                        Free(model);

                        st = CAlloc(20 + 1);
                        for (j = 0; j < 10; j++)
                            st[j] = EndianU16(temp_blkdev->dev_id_record[10 + j]);
                        serial = MStrUtil(st, SUF_REM_LEADING | SUF_REM_TRAILING);
                        "Serial: %s\n", serial;
                        Free(st);
                        Free(serial);
                    }

                    "\n";

                    BlkDevDel(temp_blkdev);
                    Free(temp_blkdev);
                }
                num++;
            }
        }
    }
    else
        "blkdev.ahci_hba is NULL !\n\n";

    return num;
}

#help_index "Install;File/Cmd Line (Typically);Cmd Line (Typically);"
U8 Mount2(U8 boot_drive_let, CDoc *_doc, Bool _caller_is_prtdisk)
{//If _doc, called by ::/Kernel/KConfig.ZC else called by Mount().
    I64      count, total = 0, num_hints, drv_let, type, prt_num, port;
    U8       blks_buf[STR_LEN], addr_buf[STR_LEN], port_str[STR_LEN],
            *filename = NULL, *filename2 = NULL, res = 0;
    Bool     whole_drive, make_free;
    CDoc    *doc;

    if (boot_drive_let)
        boot_drive_let = Letter2Letter(boot_drive_let);
    do
    {
        count = 0;
        if (!_doc)
            DriveRep;

        if (!IsRaw)
            "\n****** Mount Drives ******\n"
            "$GREEN$A$FG$-$GREEN$B$FG$ are RAM drives.\n"
            "$GREEN$C$FG$-$GREEN$L$FG$ are ATA hard drives.\n"
            "$GREEN$M$FG$-$GREEN$P$FG$ are ISO file read drives.\n"
            "$GREEN$Q$FG$-$GREEN$S$FG$ are ISO file write drives.\n"
            "$GREEN$T$FG$-$GREEN$Z$FG$ are ATAPI CD/DVD drives.\n"
            "\nDrive Letter ($PURPLE$<ENTER>$FG$ to exit):";
        else
            "\n****** Mount Drives ******\n"
            "A-B are RAM drives.\n"
            "C-L are ATA hard drives.\n"
            "M-P are ISO file read drives.\n"
            "Q-S are ISO file write drives.\n"
            "T-Z are ATAPI CD/DVD drives.\n"
            "\nDrive Letter (<ENTER> to exit):";

        drv_let = Letter2Letter(CharGet);
        '\n';
        if (type = Letter2BlkDevType(drv_let))
        {
            whole_drive = FALSE;
            if (_doc)
            { //Called by ::/Kernel/KConfig.ZC
                doc = _doc;
                make_free = FALSE;
            }
            else
            { //Called by Mount()
                doc = DocNew;
                DocPrint(doc, "CBlkDev *bd;\n");
                make_free = TRUE;
            }
            prt_num = I64_MIN;
            port = -1;
            switch (type)
            {
                case BDT_RAM:
                    if (!IsRaw)
                        "Addr of RAM disk ($PURPLE$<ENTER>$FG$ to MAlloc):";
                    else
                        "Addr of RAM disk (<ENTER> to MAlloc):";
                    StrNGet(addr_buf, STR_LEN);
                case BDT_ISO_FILE_WRITE:
                    "Blocks of 512 bytes:";
                    StrNGet(blks_buf, STR_LEN);
                    break;

                case BDT_ISO_FILE_READ:
                    filename = StrGet("File Name:");
                    break;

                case BDT_ATA:
                    prt_num = I64Get("Partition Num (Default=All):", prt_num);
                case BDT_ATAPI:
                    num_hints = SATARep(type);
                    if (type == BDT_ATAPI && boot_drive_let)
                        "<ENTER> to use booted CD/DVD\n"; //Only ::/Kernel/KConfig.ZC
                    do
                    {
                        if (num_hints)
                            "Enter port number: \n";
                        StrNGet(port_str, STR_LEN);
                    }
                    while ((type == BDT_ATAPI && AHCIPortSignatureGet(Str2I64(port_str)) != AHCI_PxSIG_ATAPI    ||
                            type == BDT_ATA   && AHCIPortSignatureGet(Str2I64(port_str)) != AHCI_PxSIG_ATA      ||
                            type == BDT_ATA   && !*port_str                                                     ||
                            0 > Str2I64(port_str) || Str2I64(port_str) > num_hints - 1
                           ) && (type != BDT_ATAPI || !boot_drive_let));

                    port = Str2I64(port_str);
                    break;
            }
            DocPrint(doc, "\"bd = BlkDevNextFreeSlot('%C', %d);\n\";\n", drv_let, type);
            DocPrint(doc, "bd = BlkDevNextFreeSlot(\'%C\', %d);\n", drv_let, type);
            if (port != -1 && *port_str)
            {
                DocPrint(doc, "\"AHCIPortInit(bd, &blkdev.ahci_hba->ports[%d], %d);\n\";\n", port, port);
                DocPrint(doc, "AHCIPortInit(bd, &blkdev.ahci_hba->ports[%d], %d);\n", port, port);

            }

            switch (type)
            {
                case BDT_RAM:
                    if (!*addr_buf) StrCopy(addr_buf, "0");
                    DocPrint(doc, "bd->RAM_disk = %s;\n", addr_buf);
                case BDT_ISO_FILE_WRITE:
                    if (!*blks_buf) StrCopy(blks_buf, "0");
                    DocPrint(doc, "bd->max_blk = (%s) - 1;\n", blks_buf);
                    DocPrint(doc, "bd->drv_offset = 19 << 2 + (DVD_BLK_SIZE * 2 + DVD_BOOT_LOADER_SIZE) / BLK_SIZE;\n");
                    break;

                case BDT_ISO_FILE_READ:
                    filename2 = FileNameAbs(filename);
                    DocPrint(doc, "bd->file_disk_name = SysStrNew(\"%s\");\n", filename2);
                    DocPrint(doc, "bd->drv_offset = 19 << 2 + (DVD_BLK_SIZE * 2 + DVD_BOOT_LOADER_SIZE) / BLK_SIZE;\n");
                    break;

                case BDT_ATAPI:
                    if (!*port_str && _doc)
                    {
                        DocPrint(doc, "\"AHCIBootDVDProbeAll(bd);\n\";\n");
                        DocPrint(doc, "AHCIBootDVDProbeAll(bd);\n"); //Only ::/Kernel/KConfig.ZC

                        if (drv_let == boot_drive_let)
                            make_free = TRUE;
                    }
                    break;

                case BDT_ATA:
                    if (_caller_is_prtdisk)
                    {
                        "\nReformat WHOLE drive!";
                        whole_drive = YorN;
                    }
                    break;
            }
            DocPrint(doc, "\"BlkDevAdd(bd, 0x%0X, %Z, %Z);\n\";\n", prt_num, whole_drive, "ST_FALSE_TRUE", make_free, "ST_FALSE_TRUE");
            DocPrint(doc, "BlkDevAdd(bd, 0x%0X, %d, %d);\n", prt_num, whole_drive, make_free);
            if (_doc) //Called by ::/Kernel/KConfig.ZC
                count++;
            else
            { //Called by Mount()
                if ((count = ExeDoc(doc)) && whole_drive)
                {
                    if (_caller_is_prtdisk)
                    {
                        res = drv_let;
                        DiskPart(drv_let, 1.0); //First mount whole drive.
                    }
                    else
                        DiskPart(drv_let);
                }
                DocDel(doc);
            }
        }
        total += count;
    }
    while (count && !_caller_is_prtdisk || !total && _doc); //At least 1 if Called by ::/Kernel/KConfig.ZC

    Free(filename);
    Free(filename2);

    return res;
}

public U8 Mount(Bool caller_is_prtdisk=FALSE)
{//Mount drives. Called from DiskPart(Mount).
    return Mount2(0, NULL, caller_is_prtdisk);
}

public U0 Unmount(U8 drv_let=0)
{//Unmount drive(s).
    BlkDevDel(Letter2BlkDev(drv_let));
}

public U8 MountFile(U8 *filename)
{//Mount ISO.C file.
    U8      *filename2 = ExtDefault(filename, "ISO.C"), *filename3 = FileNameAbs(filename2);
    CDrive  *drive = DriveMakeFreeSlot(DriveNextFreeLet('M')); //First BDT_ISO_FILE_READ
    CBlkDev *bd = BlkDevNextFreeSlot(drive->drv_let, BDT_ISO_FILE_READ);

    bd->drv_offset = 19 << 2 + (DVD_BLK_SIZE * 2 + DVD_BOOT_LOADER_SIZE) / BLK_SIZE;
    bd->file_disk_name = SysStrNew(filename3);
    BlkDevAdd(bd,, TRUE, TRUE);
    Free(filename3);
    Free(filename2);

    return drive->drv_let;
}