gccx86inline-assemblybiosfloppy

Assembly: Unable to read sectors after the first track


As part of my operating system I wrote this read sector function.

It takes a sector address to read from a BIOS device id. But when I set to read from sector 19 (Head: 0, Track: 1, Sector 2) the result at 0x1000:0x0000 is likely past that sector (I checked that several times with a hex viewer).

Also, when I read more than one sector, so that sector 19 is included, at the address mentioned above, I can read sector 19 which is copied at 0x1000:(512*19) without a problem.

void __NOINLINE resetDisk(const int device_id) {
    __asm__ __volatile__("" : : "d"(0x0000|device_id)); //set device id
    __asm__ __volatile__("mov $0x0000,%ax"); //function 0x02
    __asm__ __volatile__("int $0x13");
}

void __NOINLINE readDiskSector(const int sector, const int device_id) {
    resetDisk(device_id);

    int sector_count = 2880;
    int heads = 2;
    int tracks = 18;

    int h = sector/(sector_count/heads);
    int c = (sector-h*(sector_count/heads))/tracks;
    int s = sector-c*tracks-h*(sector_count/heads)+1;

    __asm__ __volatile__("push %es");

    __asm__ __volatile__("" : : "a"(c));
    __asm__ __volatile__("" : : "b"(s));
    __asm__ __volatile__("mov %al,%ch");
    __asm__ __volatile__("mov %bl,%cl");
    __asm__ __volatile__("" : : "a"(h));
    __asm__ __volatile__("" : : "b"(device_id));
    __asm__ __volatile__("mov %al,%dh");
    __asm__ __volatile__("mov %bl,%dl");

    __asm__ __volatile__("mov $0x03,%si");
    __asm__ __volatile__("try_again_reading:");
    __asm__ __volatile__("cmp $0x00,%si");
    __asm__ __volatile__("je stop_trying");
    __asm__ __volatile__("mov $0x1000,%bx");
    __asm__ __volatile__("mov %bx,%es");
    __asm__ __volatile__("mov $0x0000,%bx");
    __asm__ __volatile__("mov $0x02,%ah");
    __asm__ __volatile__("mov $0x01,%al");
    __asm__ __volatile__("int $0x13");
    __asm__ __volatile__("dec %si");
    __asm__ __volatile__("jc try_again_reading");
    __asm__ __volatile__("stop_trying:");
    __asm__ __volatile__("pop %es");
}

Solution

  • The code has some serious issues from the perspective of proper GCC basic inline assembly and extended inline assembly, but fundamentally the problems with accessing Logical Block Address 19 (LBA) is in the calculations. LBA 19 is CHS (Cylinder, Head, Sector) = (0, 1, 2) where the OP suggests it is (Head: 0, Track: 1, Sector 2) which is incorrect.

    Int 13h/ah=2 takes CHS values. You can convert an LBA to CHS values with the formula (or equivalent):

    C = (LBA ÷ SPT) ÷ HPC
    H = (LBA ÷ SPT) mod HPC
    S = (LBA mod SPT) + 1
    
    HPC = Heads per cylinder (aka Number of Heads)
    SPT = Sectors per Track, 
    LBA = logical block address
    
    "mod" is the modulo operator (to get the remainder of a division)
    

    I have written more about the LBA to CHS calculation in this other Stackoverflow answer in the section Translation of LBA to CHS.

    One observation is that the maximum sector number doesn't factor into the equation at all. The real issue here is that the OP's formula is incorrect:

    int sector_count = 2880;
    int heads = 2;   /* Head per cylinder */
    int tracks = 18; /* Sectors per Track */
    
    int h = sector/(sector_count/heads);
    int c = (sector-h*(sector_count/heads))/tracks;
    int s = sector-c*tracks-h*(sector_count/heads)+1;
    

    The only the part of the equation that yields a correct result (in a round about way) is s (sector). c (cylinder) and h (head) are computed incorrectly. Because of this it is causing the issues observed in the question and the OP's followup answer. To get an idea of the values being produced by the OP's equation I wrote a program to compare their values with correct ones using a proper formula:

    LBA =    0:   CHS = ( 0,  0,  1)    |    CHS = ( 0,  0,  1)
    
    LBA =    1:   CHS = ( 0,  0,  2)    |    CHS = ( 0,  0,  2)
    LBA =    2:   CHS = ( 0,  0,  3)    |    CHS = ( 0,  0,  3)
    LBA =    3:   CHS = ( 0,  0,  4)    |    CHS = ( 0,  0,  4)
    LBA =    4:   CHS = ( 0,  0,  5)    |    CHS = ( 0,  0,  5)
    LBA =    5:   CHS = ( 0,  0,  6)    |    CHS = ( 0,  0,  6)
    LBA =    6:   CHS = ( 0,  0,  7)    |    CHS = ( 0,  0,  7)
    LBA =    7:   CHS = ( 0,  0,  8)    |    CHS = ( 0,  0,  8)
    LBA =    8:   CHS = ( 0,  0,  9)    |    CHS = ( 0,  0,  9)
    LBA =    9:   CHS = ( 0,  0, 10)    |    CHS = ( 0,  0, 10)
    LBA =   10:   CHS = ( 0,  0, 11)    |    CHS = ( 0,  0, 11)
    LBA =   11:   CHS = ( 0,  0, 12)    |    CHS = ( 0,  0, 12)
    LBA =   12:   CHS = ( 0,  0, 13)    |    CHS = ( 0,  0, 13)
    LBA =   13:   CHS = ( 0,  0, 14)    |    CHS = ( 0,  0, 14)
    LBA =   14:   CHS = ( 0,  0, 15)    |    CHS = ( 0,  0, 15)
    LBA =   15:   CHS = ( 0,  0, 16)    |    CHS = ( 0,  0, 16)
    LBA =   16:   CHS = ( 0,  0, 17)    |    CHS = ( 0,  0, 17)
    LBA =   17:   CHS = ( 0,  0, 18)    |    CHS = ( 0,  0, 18)
    LBA =   18:   CHS = ( 1,  0,  1)    |    CHS = ( 0,  1,  1)
    LBA =   19:   CHS = ( 1,  0,  2)    |    CHS = ( 0,  1,  2)
    LBA =   20:   CHS = ( 1,  0,  3)    |    CHS = ( 0,  1,  3)
    LBA =   21:   CHS = ( 1,  0,  4)    |    CHS = ( 0,  1,  4)
    LBA =   22:   CHS = ( 1,  0,  5)    |    CHS = ( 0,  1,  5)
    LBA =   23:   CHS = ( 1,  0,  6)    |    CHS = ( 0,  1,  6)
    LBA =   24:   CHS = ( 1,  0,  7)    |    CHS = ( 0,  1,  7)
    LBA =   25:   CHS = ( 1,  0,  8)    |    CHS = ( 0,  1,  8)
    LBA =   26:   CHS = ( 1,  0,  9)    |    CHS = ( 0,  1,  9)
    LBA =   27:   CHS = ( 1,  0, 10)    |    CHS = ( 0,  1, 10)
    LBA =   28:   CHS = ( 1,  0, 11)    |    CHS = ( 0,  1, 11)
    LBA =   29:   CHS = ( 1,  0, 12)    |    CHS = ( 0,  1, 12)
    LBA =   30:   CHS = ( 1,  0, 13)    |    CHS = ( 0,  1, 13)
    LBA =   31:   CHS = ( 1,  0, 14)    |    CHS = ( 0,  1, 14)
    LBA =   32:   CHS = ( 1,  0, 15)    |    CHS = ( 0,  1, 15)
    LBA =   33:   CHS = ( 1,  0, 16)    |    CHS = ( 0,  1, 16)
    LBA =   34:   CHS = ( 1,  0, 17)    |    CHS = ( 0,  1, 17)
    LBA =   35:   CHS = ( 1,  0, 18)    |    CHS = ( 0,  1, 18)
    LBA =   36:   CHS = ( 2,  0,  1)    |    CHS = ( 1,  0,  1)
    LBA =   37:   CHS = ( 2,  0,  2)    |    CHS = ( 1,  0,  2)
    LBA =   38:   CHS = ( 2,  0,  3)    |    CHS = ( 1,  0,  3)
    LBA =   39:   CHS = ( 2,  0,  4)    |    CHS = ( 1,  0,  4)
    LBA =   40:   CHS = ( 2,  0,  5)    |    CHS = ( 1,  0,  5)
    LBA =   41:   CHS = ( 2,  0,  6)    |    CHS = ( 1,  0,  6)
    LBA =   42:   CHS = ( 2,  0,  7)    |    CHS = ( 1,  0,  7)
    LBA =   43:   CHS = ( 2,  0,  8)    |    CHS = ( 1,  0,  8)
    LBA =   44:   CHS = ( 2,  0,  9)    |    CHS = ( 1,  0,  9)
    LBA =   45:   CHS = ( 2,  0, 10)    |    CHS = ( 1,  0, 10)
    LBA =   46:   CHS = ( 2,  0, 11)    |    CHS = ( 1,  0, 11)
    LBA =   47:   CHS = ( 2,  0, 12)    |    CHS = ( 1,  0, 12)
    LBA =   48:   CHS = ( 2,  0, 13)    |    CHS = ( 1,  0, 13)
    LBA =   49:   CHS = ( 2,  0, 14)    |    CHS = ( 1,  0, 14)
    LBA =   50:   CHS = ( 2,  0, 15)    |    CHS = ( 1,  0, 15)
    LBA =   51:   CHS = ( 2,  0, 16)    |    CHS = ( 1,  0, 16)
    LBA =   52:   CHS = ( 2,  0, 17)    |    CHS = ( 1,  0, 17)
    LBA =   53:   CHS = ( 2,  0, 18)    |    CHS = ( 1,  0, 18)
    LBA =   54:   CHS = ( 3,  0,  1)    |    CHS = ( 1,  1,  1)
    LBA =   55:   CHS = ( 3,  0,  2)    |    CHS = ( 1,  1,  2)
    LBA =   56:   CHS = ( 3,  0,  3)    |    CHS = ( 1,  1,  3)
    LBA =   57:   CHS = ( 3,  0,  4)    |    CHS = ( 1,  1,  4)
    LBA =   58:   CHS = ( 3,  0,  5)    |    CHS = ( 1,  1,  5)
    LBA =   59:   CHS = ( 3,  0,  6)    |    CHS = ( 1,  1,  6)
    LBA =   60:   CHS = ( 3,  0,  7)    |    CHS = ( 1,  1,  7)
    LBA =   61:   CHS = ( 3,  0,  8)    |    CHS = ( 1,  1,  8)
    LBA =   62:   CHS = ( 3,  0,  9)    |    CHS = ( 1,  1,  9)
    LBA =   63:   CHS = ( 3,  0, 10)    |    CHS = ( 1,  1, 10)
    LBA =   64:   CHS = ( 3,  0, 11)    |    CHS = ( 1,  1, 11)
    LBA =   65:   CHS = ( 3,  0, 12)    |    CHS = ( 1,  1, 12)
    LBA =   66:   CHS = ( 3,  0, 13)    |    CHS = ( 1,  1, 13)
    LBA =   67:   CHS = ( 3,  0, 14)    |    CHS = ( 1,  1, 14)
    LBA =   68:   CHS = ( 3,  0, 15)    |    CHS = ( 1,  1, 15)
    LBA =   69:   CHS = ( 3,  0, 16)    |    CHS = ( 1,  1, 16)
    LBA =   70:   CHS = ( 3,  0, 17)    |    CHS = ( 1,  1, 17)
    LBA =   71:   CHS = ( 3,  0, 18)    |    CHS = ( 1,  1, 18)
    LBA =   72:   CHS = ( 4,  0,  1)    |    CHS = ( 2,  0,  1)
    LBA =   73:   CHS = ( 4,  0,  2)    |    CHS = ( 2,  0,  2)
    LBA =   74:   CHS = ( 4,  0,  3)    |    CHS = ( 2,  0,  3)
    LBA =   75:   CHS = ( 4,  0,  4)    |    CHS = ( 2,  0,  4)
    LBA =   76:   CHS = ( 4,  0,  5)    |    CHS = ( 2,  0,  5)
    LBA =   77:   CHS = ( 4,  0,  6)    |    CHS = ( 2,  0,  6)
    LBA =   78:   CHS = ( 4,  0,  7)    |    CHS = ( 2,  0,  7)
    LBA =   79:   CHS = ( 4,  0,  8)    |    CHS = ( 2,  0,  8)
    ...
    

    The OP's results are on the left and the correct ones on the right. LBA 0 through LBA 17 are correct. If you start reading one or more sectors with an LBA less than 18 it will be correct. If you use the CHS values computed for LBA 19 they are incorrect.

    The OP suggests in their answer that the documentation for the cylinder and head values are incorrect, and that the registers are reversed. The documentation is correct:

    AL = number of sectors to read (must be nonzero)
    
    CH = low eight bits of cylinder number
    CL = sector number 1-63 (bits 0-5)
    high two bits of cylinder (bits 6-7, hard disk only)
    DH = head number
    DL = drive number (bit 7 set for hard disk)
    ES:BX -> data buffer
    

    The OP's answer suggests a fix is to swap the heads and cylinders around. That in fact happens to make his code work by accident for the LBA 0 through LBA 35. LBA >= 36 are incorrect.

    The fix is to use a proper calculation in the OP's code:

    c = (sector / tracks) / heads;
    h = (sector / tracks) % heads;
    s = (sector % tracks) + 1;
    

    Code to Test LBA to CHS Equations

    #include <stdio.h>
    
    int main()
    {
        const int sector_count = 2880;
        const int heads = 2;
        const int tracks = 18; /* tracks per sector */
    
        unsigned char h, h2;
        unsigned char c, c2;
        unsigned char s, s2;
    
        int sector; /* LBA */
        for (sector=0; sector < sector_count; sector++) {
            /* Improper calculation */
            h = sector/(sector_count/heads);
            c = (sector-h*(sector_count/heads))/tracks;
            s = sector-c*tracks-h*(sector_count/heads)+1;
    
            /* Proper calculation */
            c2 = (sector / tracks) / heads;
            h2 = (sector / tracks) % heads;
            s2 = (sector % tracks) + 1;
    
            printf ("LBA = %4d:   CHS = (%2d, %2d, %2d)    |    CHS = (%2d, %2d, %2d)\n",
                    sector, c, h, s, c2, h2, s2);
        }
        return 0;
    }
    

    Sample GCC Code Using Inline Assembly to do Disk Reads

    biosdisk.h

    #ifndef BIOSDISK_H
    #define BIOSDISK_H
    
    #include <stdint.h>
    
    /* BIOS Parameter Block (BPB) on floppy media */
    typedef struct __attribute__((packed)) {
        char     OEMname[8];
        uint16_t bytesPerSector;
        uint8_t  sectPerCluster;
        uint16_t reservedSectors;
        uint8_t  numFAT;
        uint16_t numRootDirEntries;
        uint16_t numSectors;
        uint8_t  mediaType;
        uint16_t numFATsectors;
        uint16_t sectorsPerTrack;
        uint16_t numHeads;
        uint32_t numHiddenSectors;
        uint32_t numSectorsHuge;
        uint8_t  driveNum;
        uint8_t  reserved;
        uint8_t  signature;
        uint32_t volumeID;
        char     volumeLabel[11];
        char     fileSysType[8];
    } disk_bpb_s;
    
    /* State information for CHS disk accesses */
    typedef struct __attribute__((packed)) {
        uint16_t segment;
        uint16_t offset;
        uint16_t status;
        /* Drive geometry needed to compute CHS from LBA */
        uint16_t sectorsPerTrack;
        uint16_t numHeads;
        /* Disk parameters */
        uint16_t cylinder;
        uint8_t  head;
        uint8_t  sector;
        uint8_t  driveNum;
        uint8_t  numSectors;    /* # of sectors to read */
        /* Number of retries for disk operations */
        uint8_t  retries;
    } disk_info_s;
    
    extern fastcall uint8_t
    reset_disk (disk_info_s *const disk_info);
    extern fastcall uint8_t
    read_sector_chs (disk_info_s *const disk_info);
    
    /* Forced inline version of reset_sector */
    static inline fastcall always_inline uint8_t
    reset_disk_i (disk_info_s *const disk_info)
    {
        uint16_t temp_ax = 0x0000;
        uint8_t  carryf;
    
        __asm__ __volatile__ (
                "int $0x13\n\t"
    #ifdef __GCC_ASM_FLAG_OUTPUTS__
                : [cf]"=@ccc"(carryf),
    #else
                "setc %[cf]\n\t"
                : [cf]"=qm"(carryf),
    #endif
                  "+a"(temp_ax)
                : "d"(disk_info->driveNum)
                : "cc");
    
        disk_info->status = temp_ax;
    
        return (carryf);
    
    }
    
    /* Forced inline version of read_sector */
    static inline fastcall always_inline uint8_t
    read_sector_chs_i (disk_info_s *const disk_info)
    {
        uint16_t temp_ax;
        uint16_t temp_dx;
        uint8_t  carryf = 0;
        uint8_t  retry_count = 0;
    
    #ifndef BUGGY_BIOS_SUPPORT
        temp_dx = (disk_info->head << 8) | disk_info->driveNum;
    #endif
    
        do {
            /* Only reset disk if error detected previously */
            if (carryf)
                reset_disk_i (disk_info);
    
            /* Need to reload AX during each iteration since a previous
             * int 0x13 call will destroy its contents. There was a bug on
             * earlier BIOSes where DX may have been clobbered.
             */
    
            temp_ax = (0x02 << 8) | disk_info->numSectors;
    #ifdef BUGGY_BIOS_SUPPORT
            temp_dx = (disk_info->head << 8) | disk_info->driveNum;
    #endif
    
            __asm__ __volatile__ (
                    "push %%es\n\t"
                    "mov %w[seg], %%es\n\t"
    #ifdef BUGGY_BIOS_SUPPORT
                    "stc\n\t"        /* Some early bioses have CF bug */
                    "int $0x13\n\t"
                    "sti\n\t"        /* Some early bioses don't re-enable interrupts */
    #else
                    "int $0x13\n\t"
    #endif
                    "pop %%es\n\t"
    #ifdef __GCC_ASM_FLAG_OUTPUTS__
                    : [cf]"=@ccc"(carryf),
    #else
                    "setc %[cf]\n\t"
                    : [cf]"=qm"(carryf),
    #endif
    #ifdef BUGGY_BIOS_SUPPORT
                      "+a"(temp_ax),
                      "+d"(temp_dx)
                      :
    #else
                      "+a"(temp_ax)
                      :
                      "d"(temp_dx),
    #endif
                      "c"(((disk_info->cylinder & 0xff) << 8) |
                         ((disk_info->cylinder >> 2) & 0xC0) |
                         (disk_info->sector & 0x3f)),
                      "b"(disk_info->offset),
                      [seg]"r"(disk_info->segment)
                    : "memory", "cc");
    
        } while (carryf && (++retry_count < disk_info->retries));
    
        disk_info->status = temp_ax;
        return (carryf);
    }
    
    /* Forced inline version of read_sector_lba */
    static inline fastcall always_inline uint8_t
    read_sector_lba_i (disk_info_s *const disk_info, const uint32_t lba)
    {
        disk_info->cylinder = lba / disk_info->sectorsPerTrack / disk_info->numHeads;
        disk_info->head     = (lba / disk_info->sectorsPerTrack) % disk_info->numHeads;
        disk_info->sector   = (lba % disk_info->sectorsPerTrack) + 1;
    
        return read_sector_chs_i (disk_info);
    }
    #endif
    

    biosdisk.c:

    #include <stdint.h>
    #include "biosdisk.h"
    
    fastcall uint8_t
    reset_disk (disk_info_s *const disk_info)
    {
        return reset_disk_i (disk_info);
    }
    
    fastcall uint8_t
    read_sector_chs (disk_info_s *const disk_info)
    {
        return read_sector_chs_i (disk_info);
    }
    
    fastcall uint8_t
    read_sector_lba (disk_info_s *const disk_info, const uint32_t lba)
    {
        return read_sector_lba_i (disk_info, lba);
    }
    

    x86helper.h:

    #ifndef X86HELPER_H
    #define X86HELPER_H
    
    #define fastcall  __attribute__((regparm(3)))
    
    /* noreturn lets GCC know that a function that it may detect
       won't exit is intentional */
    #define noreturn      __attribute__((noreturn))
    #define always_inline __attribute__((always_inline))
    #define used          __attribute__((used))
        
    #endif
    

    A small proof of concept project that creates a 2 stage bootloader in GCC can be found on my website


    Notes

    Do not expect a sequence of asm statements to remain perfectly consecutive after compilation, even when you are using the volatile qualifier. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement.