Why does my conversion from LBA to CHS not work?

I’m developing a bootloader for an x86 BIOS. In my first-stage bootloader (MBR), I need to read 2880 sectors (or more) from the disk, and then jump to the second-stage bootloader placed in the second sector of the disk. The second stage will then load a kernel file using FAT16, which I’ll implement later.

The function works for LBA values lower than 65, which is sufficient for loading the second stage written in C.

I have defined BPB_SecPerTrk as 18 and BPB_NumHeads as 2

Here is my function code in assembly:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>; convert LBA to CHS
; input: si = LBA
; output: ch = cylinder, dh = head, cl = sector
lba_to_chs:
push bx ; save bx
mov ax, si ; load LBA address into ax
; Calculate sectors (CL)
xor dx, dx ; clear dx
div word [BPB_SecPerTrk] ; ax = LBA / SPT, dx = LBA % SPT
mov cl, dl ; cl = (LBA % SPT) + 1 (sector)
inc cl ; increment cl by 1
; Calculate head (DH)
xor dx, dx ; clear dx
div word [BPB_NumHeads] ; ax = LBA / (SPT * NumHeads), dx = (LBA / SPT) % NumHeads
mov dh, dl ; dh = (LBA / SPT) % NumHeads (head)
; Calculate cylinder (CH)
mov ch, al ; ch = ax (cylinder number, lower 8 bits)
mov al, ah ; al = ah (upper 8 bits of cylinder number)
shl al, 6 ; shift upper 2 bits of cylinder to higher bits
or ch, al ; combine them with lower 8 bits of ch
pop bx ; restore bx
ret
</code>
<code>; convert LBA to CHS ; input: si = LBA ; output: ch = cylinder, dh = head, cl = sector lba_to_chs: push bx ; save bx mov ax, si ; load LBA address into ax ; Calculate sectors (CL) xor dx, dx ; clear dx div word [BPB_SecPerTrk] ; ax = LBA / SPT, dx = LBA % SPT mov cl, dl ; cl = (LBA % SPT) + 1 (sector) inc cl ; increment cl by 1 ; Calculate head (DH) xor dx, dx ; clear dx div word [BPB_NumHeads] ; ax = LBA / (SPT * NumHeads), dx = (LBA / SPT) % NumHeads mov dh, dl ; dh = (LBA / SPT) % NumHeads (head) ; Calculate cylinder (CH) mov ch, al ; ch = ax (cylinder number, lower 8 bits) mov al, ah ; al = ah (upper 8 bits of cylinder number) shl al, 6 ; shift upper 2 bits of cylinder to higher bits or ch, al ; combine them with lower 8 bits of ch pop bx ; restore bx ret </code>
; convert LBA to CHS
; input: si = LBA
; output: ch = cylinder, dh = head, cl = sector
lba_to_chs:
    push bx                         ; save bx
    mov ax, si                      ; load LBA address into ax

    ; Calculate sectors (CL)
    xor dx, dx                      ; clear dx
    div word [BPB_SecPerTrk]        ; ax = LBA / SPT, dx = LBA % SPT
    mov cl, dl                      ; cl = (LBA % SPT) + 1 (sector)
    inc cl                          ; increment cl by 1

    ; Calculate head (DH)
    xor dx, dx                      ; clear dx
    div word [BPB_NumHeads]         ; ax = LBA / (SPT * NumHeads), dx = (LBA / SPT) % NumHeads
    mov dh, dl                      ; dh = (LBA / SPT) % NumHeads (head)

    ; Calculate cylinder (CH)
    mov ch, al                      ; ch = ax (cylinder number, lower 8 bits)
    mov al, ah                      ; al = ah (upper 8 bits of cylinder number)
    shl al, 6                       ; shift upper 2 bits of cylinder to higher bits
    or ch, al                       ; combine them with lower 8 bits of ch

    pop bx                          ; restore bx
    ret

and also here is function responsible for disk init.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>disk_init_lba:
pusha
; check if lba extension is supperted
mov ah, 0x41 ; check extensions
mov bx, 0x55AA ; magic number
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
stc ; DEBUG: implicitly disable reading disk using int 0x13 extensions
jc .lba_ext_not_sup ; if carry flag is set, jump to error handler
jmp .read_lba_ext ; if not, jump to read disk using LBA
.read_lba_ext:
mov si, DAPACK ; load DAP address to si
mov ah, 0x42 ; extended read function
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
jc .fail ; if carry flag is set, jump to error handler
jmp .ok ; if not, jump to success handler
.lba_ext_not_sup:
call print_disk_lba_sup_fail ; print failure message
jmp .read_lba_via_chs ; jump to read disk using CHS
.read_lba_via_chs:
clc ; clear carry flag if for some reason it was set
xor si, si ; LBA = 0
xor di, di ; set di to 0
mov bx, START_STAGE1 ; buffer for sector
jmp .loop ; jump to loop
.loop:
inc si ; increment LBA
add bx, 0x200 ; next sector buffer
call lba_to_chs ; convert LBA to CHS
mov ah, 0x02 ; read disk BIOS function
mov al, 0x01 ; number of sectors to read
mov dl, 0x80 ; disk number 0
int 0x13 ; call BIOS
jc .retry ; if carry flag is set, jump to error handler
; FIXME: reading LBAs above 65
; TODO: read up to 1.44 MB (2879 sectors)
cmp si, 65 ; check if we read enough sectors to fill 1.44 MB
jle .loop ; if true read next sector
jmp .ok ; if not, jump to success handler
.retry:
inc di ; increment di
cmp di, 3 ; check if we tried 3 times
jne .loop ; if not, retry
jmp .fail ; if yes, jump to error handler
.fail:
call print_disk_read_fail ; print failure message
jmp .exit ; jump to exit
.ok:
call print_disk_read_ok ; print success message
jmp .exit ; jump to exit
.exit:
popa
ret
</code>
<code>disk_init_lba: pusha ; check if lba extension is supperted mov ah, 0x41 ; check extensions mov bx, 0x55AA ; magic number mov dl, 0x80 ; disk number int 0x13 ; call BIOS stc ; DEBUG: implicitly disable reading disk using int 0x13 extensions jc .lba_ext_not_sup ; if carry flag is set, jump to error handler jmp .read_lba_ext ; if not, jump to read disk using LBA .read_lba_ext: mov si, DAPACK ; load DAP address to si mov ah, 0x42 ; extended read function mov dl, 0x80 ; disk number int 0x13 ; call BIOS jc .fail ; if carry flag is set, jump to error handler jmp .ok ; if not, jump to success handler .lba_ext_not_sup: call print_disk_lba_sup_fail ; print failure message jmp .read_lba_via_chs ; jump to read disk using CHS .read_lba_via_chs: clc ; clear carry flag if for some reason it was set xor si, si ; LBA = 0 xor di, di ; set di to 0 mov bx, START_STAGE1 ; buffer for sector jmp .loop ; jump to loop .loop: inc si ; increment LBA add bx, 0x200 ; next sector buffer call lba_to_chs ; convert LBA to CHS mov ah, 0x02 ; read disk BIOS function mov al, 0x01 ; number of sectors to read mov dl, 0x80 ; disk number 0 int 0x13 ; call BIOS jc .retry ; if carry flag is set, jump to error handler ; FIXME: reading LBAs above 65 ; TODO: read up to 1.44 MB (2879 sectors) cmp si, 65 ; check if we read enough sectors to fill 1.44 MB jle .loop ; if true read next sector jmp .ok ; if not, jump to success handler .retry: inc di ; increment di cmp di, 3 ; check if we tried 3 times jne .loop ; if not, retry jmp .fail ; if yes, jump to error handler .fail: call print_disk_read_fail ; print failure message jmp .exit ; jump to exit .ok: call print_disk_read_ok ; print success message jmp .exit ; jump to exit .exit: popa ret </code>
disk_init_lba:
    pusha
    ; check if lba extension is supperted
    mov ah, 0x41                    ; check extensions
    mov bx, 0x55AA                  ; magic number
    mov dl, 0x80                    ; disk number
    int 0x13                        ; call BIOS
    stc                             ; DEBUG: implicitly disable reading disk using int 0x13 extensions
    jc .lba_ext_not_sup             ; if carry flag is set, jump to error handler
    jmp .read_lba_ext               ; if not, jump to read disk using LBA
.read_lba_ext:
    mov si, DAPACK                  ; load DAP address to si
    mov ah, 0x42                    ; extended read function
    mov dl, 0x80                    ; disk number
    int 0x13                        ; call BIOS
    jc .fail                        ; if carry flag is set, jump to error handler
    jmp .ok                         ; if not, jump to success handler
.lba_ext_not_sup:
    call print_disk_lba_sup_fail    ; print failure message
    jmp .read_lba_via_chs           ; jump to read disk using CHS
.read_lba_via_chs:
    clc                             ; clear carry flag if for some reason it was set
    xor si, si                      ; LBA = 0
    xor di, di                      ; set di to 0
    mov bx, START_STAGE1            ; buffer for sector
    jmp .loop                       ; jump to loop
.loop:
    inc si                          ; increment LBA
    add bx, 0x200                   ; next sector buffer
    call lba_to_chs                 ; convert LBA to CHS
    mov ah, 0x02                    ; read disk BIOS function
    mov al, 0x01                    ; number of sectors to read
    mov dl, 0x80                    ; disk number 0
    int 0x13                        ; call BIOS
    jc .retry                       ; if carry flag is set, jump to error handler
    ; FIXME: reading LBAs above 65
    ; TODO: read up to 1.44 MB (2879 sectors)
    cmp si, 65                      ; check if we read enough sectors to fill 1.44 MB
    jle .loop                       ; if true read next sector
    jmp .ok                         ; if not, jump to success handler
.retry:
    inc di                          ; increment di
    cmp di, 3                       ; check if we tried 3 times
    jne .loop                       ; if not, retry
    jmp .fail                       ; if yes, jump to error handler
.fail:
    call print_disk_read_fail       ; print failure message
    jmp .exit                       ; jump to exit
.ok:
    call print_disk_read_ok         ; print success message
    jmp .exit                       ; jump to exit
.exit:
    popa
    ret

I think it is related to value overflow some where in the code.

2

The error in lba_to_chs

As found by @ecm the 2 most significant bits for the cylinder number belong to the CL register (bits 6 and 7), so change or ch, al into or cl, al, or else consider using next shorter version of the conversion code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>; IN (si) OUT (cx,dh) MOD (ax,dl)
lba_to_chs:
mov ax, si ; LBA
xor dx, dx
div word [BPB_SecPerTrk]
mov cx, dx
inc cx ; Sector
cwd
div word [BPB_NumHeads]
mov dh, dl ; Head
shl ah, 6
xchg al, ah
or cx, ax ; Cylinder
ret
</code>
<code>; IN (si) OUT (cx,dh) MOD (ax,dl) lba_to_chs: mov ax, si ; LBA xor dx, dx div word [BPB_SecPerTrk] mov cx, dx inc cx ; Sector cwd div word [BPB_NumHeads] mov dh, dl ; Head shl ah, 6 xchg al, ah or cx, ax ; Cylinder ret </code>
; IN (si) OUT (cx,dh) MOD (ax,dl)
lba_to_chs:
    mov  ax, si                      ; LBA
    xor  dx, dx
    div  word [BPB_SecPerTrk]
    mov  cx, dx
    inc  cx                          ; Sector
    cwd
    div  word [BPB_NumHeads]
    mov  dh, dl                      ; Head
    shl  ah, 6
    xchg al, ah
    or   cx, ax                      ; Cylinder
    ret

The errors in disk_init_lba

It is not unusual for disk operations to need being repeated. That’s why you have that retry count of 3 in the DI register. What you did however is allow 3 retries for all the sectors together where you should actually be allowing a couple of retries per sector.
But it is even worse than that, you are not retrying on the same sector at all! The “increment LBA” and “next sector buffer” operations must not intervene while retrying.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>clc ; clear carry flag if for some reason it was set
xor si, si ; LBA = 0
</code>
<code>clc ; clear carry flag if for some reason it was set xor si, si ; LBA = 0 </code>
clc                             ; clear carry flag if for some reason it was set
xor si, si                      ; LBA = 0

No need for this clc, the xor si, si that follows clears the carry flag anyway.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>disk_init_lba:
pusha
...
.read_lba_via_chs:
xor si, si ; LBA = 0
mov bx, START_STAGE1 ; buffer for sector
.NextSector:
inc si ; increment LBA
add bx, 0x200 ; next sector buffer
mov di, 3 ; Tries per sector
.NextTry:
call lba_to_chs ; convert LBA to CHS
mov ax, 0x0201 ; read 1 sector
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
jc .retry
; FIXME: reading LBAs above 65
; TODO: read up to 1.44 MB (2879 sectors)
cmp si, 65 ; check if we read enough sectors
jbe .NextSector
jmp .ok ; if not, jump to success handler
.retry:
dec di ; more tries?
jnz .NextTry ; yes
.fail:
call print_disk_read_fail ; print failure message
jmp .exit
.ok:
call print_disk_read_ok ; print success message
.exit:
popa
ret
</code>
<code>disk_init_lba: pusha ... .read_lba_via_chs: xor si, si ; LBA = 0 mov bx, START_STAGE1 ; buffer for sector .NextSector: inc si ; increment LBA add bx, 0x200 ; next sector buffer mov di, 3 ; Tries per sector .NextTry: call lba_to_chs ; convert LBA to CHS mov ax, 0x0201 ; read 1 sector mov dl, 0x80 ; disk number int 0x13 ; call BIOS jc .retry ; FIXME: reading LBAs above 65 ; TODO: read up to 1.44 MB (2879 sectors) cmp si, 65 ; check if we read enough sectors jbe .NextSector jmp .ok ; if not, jump to success handler .retry: dec di ; more tries? jnz .NextTry ; yes .fail: call print_disk_read_fail ; print failure message jmp .exit .ok: call print_disk_read_ok ; print success message .exit: popa ret </code>
disk_init_lba:
    pusha
    ...
.read_lba_via_chs:
    xor  si, si                      ; LBA = 0
    mov  bx, START_STAGE1            ; buffer for sector
.NextSector:
    inc  si                          ; increment LBA
    add  bx, 0x200                   ; next sector buffer
    mov  di, 3                       ; Tries per sector
.NextTry:
    call lba_to_chs                  ; convert LBA to CHS
    mov  ax, 0x0201                  ; read 1 sector
    mov  dl, 0x80                    ; disk number
    int  0x13                        ; call BIOS
    jc   .retry
    ; FIXME: reading LBAs above 65
    ; TODO: read up to 1.44 MB (2879 sectors)
    cmp  si, 65                      ; check if we read enough sectors
    jbe  .NextSector
    jmp  .ok                         ; if not, jump to success handler
.retry:
    dec  di                          ; more tries?
    jnz  .NextTry                    ; yes
.fail:
    call print_disk_read_fail        ; print failure message
    jmp  .exit
.ok:
    call print_disk_read_ok          ; print success message
.exit:
    popa
    ret

; FIXME: reading LBAs above 65

Instead of modifying BX, keep it fixed and advance the ES segment register by 32 (512 / 16).

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>disk_init_lba:
pusha
push es
...
.read_lba_via_chs:
xor si, si ; LBA = 0
mov bx, START_STAGE1 ; buffer for sector
.NextSector:
inc si ; increment LBA
mov ax, es ; next sector buffer ES:BX
add ax, 32
mov es, ax
mov di, 3 ; Tries per sector
.NextTry:
call lba_to_chs ; convert LBA to CHS
mov ax, 0x0201 ; read 1 sector
mov dl, 0x80 ; disk number
int 0x13 ; call BIOS
jc .retry
cmp si, 65 ; check if we read enough sectors
jbe .NextSector
jmp .ok
.retry:
dec di ; more tries?
jnz .NextTry ; yes
.fail:
call print_disk_read_fail ; print failure message
jmp .exit
.ok:
call print_disk_read_ok ; print success message
.exit:
pop es
popa
ret
</code>
<code>disk_init_lba: pusha push es ... .read_lba_via_chs: xor si, si ; LBA = 0 mov bx, START_STAGE1 ; buffer for sector .NextSector: inc si ; increment LBA mov ax, es ; next sector buffer ES:BX add ax, 32 mov es, ax mov di, 3 ; Tries per sector .NextTry: call lba_to_chs ; convert LBA to CHS mov ax, 0x0201 ; read 1 sector mov dl, 0x80 ; disk number int 0x13 ; call BIOS jc .retry cmp si, 65 ; check if we read enough sectors jbe .NextSector jmp .ok .retry: dec di ; more tries? jnz .NextTry ; yes .fail: call print_disk_read_fail ; print failure message jmp .exit .ok: call print_disk_read_ok ; print success message .exit: pop es popa ret </code>
disk_init_lba:
    pusha
    push es
    ...
.read_lba_via_chs:
    xor  si, si                      ; LBA = 0
    mov  bx, START_STAGE1            ; buffer for sector
.NextSector:
    inc  si                          ; increment LBA
    mov  ax, es                      ; next sector buffer ES:BX
    add  ax, 32
    mov  es, ax
    mov  di, 3                       ; Tries per sector
.NextTry:
    call lba_to_chs                  ; convert LBA to CHS
    mov  ax, 0x0201                  ; read 1 sector
    mov  dl, 0x80                    ; disk number
    int  0x13                        ; call BIOS
    jc   .retry
    cmp  si, 65                      ; check if we read enough sectors
    jbe  .NextSector
    jmp  .ok
.retry:
    dec  di                          ; more tries?
    jnz  .NextTry                    ; yes
.fail:
    call print_disk_read_fail        ; print failure message
    jmp  .exit
.ok:
    call print_disk_read_ok          ; print success message
.exit:
    pop  es
    popa
    ret

; TODO: read up to 1.44 MB (2879 sectors)

You can’t hope to read that many bytes. Your present code is confined to using the conventional memory, so at most a little less than 655360 bytes would be feasible…
More than enough for your second stage I would say.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật