NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: Deskman243 on June 30, 2023, 01:12:28 AM
-
Greetings
Here is a pretty intriguing post for review today. I have been practicing the BIOS functions and now the program itself is as compact as I could get it. I'm mainly looking at program segmentation for file management. The good thing is that I've learned how to run files larger than the typical 512 b environment. Right now the program uses the built-in 13h disk function but the new design only gets through the first 18 sections. I've tried changing each of the other variable but I can't get it to run the code. Here is a copy of the main boot file.
; ReTimerOS
; Copyright (C) 2022,2023 Christopher Hoy
;
; This file is part of ReTimerOS
; ReTimerOS is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <https://www.gnu.org/licenses/>.
[org 0x7c00]
[bits 16]
mov cx,0
mov ds,cx
mov es,cx
cld
mov si,msgString
call kprint
disk_read:
mov cx,0
mov ds,cx
mov es,cx
mov bx,0x0000_e600
mov cl,0x24
mov bx,msgString
mov bx,0x0000_e600
mov cl,0x24
mov al,1
call read_section2
mov cx,0xe60
mov es,cx
mov bx,0
mov cx,00000h
mov cl,0x24
;mov bx,0x0000_8000
mov al,1
call read_section2
;mov bx,0x0000_7e00
mov bx,0x0000_8400
;mov bx,0x0000_A000
mov cl,5
mov bx,msgString
mov bx,0x0000_8400
;mov bx,0x0000_9000
;mov bx,0x0000_A000
mov cl,5
mov al,0x30
call read_section
mov cx,0x840
;mov cx,0x900
;mov cx,0xA00
mov es,cx
mov bx,0
mov cx,00000h
;mov al,1
mov cl,5
;mov bx,0x0000_8000
mov al,0x30
call read_section
mov bx,0x0000_8000
mov cl,3
mov bx,msgString
mov bx,0x0000_8000
mov cl,3
mov al,2
call read_section
mov cx,0x800
mov es,cx
mov bx,0
mov cx,00000h
mov cl,3
;mov bx,0x0000_8000
mov al,2
call read_section
;mov bx,0x0000_7e00
mov bx,0x0000_7e00
mov cl,2
mov bx,msgString
mov bx,0x0000_7e00
mov cl,2
mov al,1
call read_section
mov cx,0x7e0
mov es,cx
mov bx,0
mov cx,00000h
mov cl,2
;mov bx,0x0000_8000
mov al,1
call read_section
mov bx,0x0000_7e00
;mov cx,0
;mov es,cx
pusha
jmp 0x7e00
;jmp 0x9200
;jmp 0xD000
;jmp 0x11000
;jmp 0x0000_7e0:0x0000
;jmp 0x7e0:0x0000
;jmp 0x900:0x0000
;jmp 0xfc0:0x0000
jmp $
kprint:
cld
mov ah,0x0E
kstring:
lodsb
int 0x10
cmp al,0x00
jnz kstring
ret
read_section:
mov ah,0x02
; mov al,1
mov ch,0
mov dh,0
int 0x13
jc disk_check
;mov si,checkString2
;call kprint
;lea si,[checkString]
;call printstr
ret
read_section2:
mov ah,0x02
; mov al,1
mov ch,0
mov dh,1
int 0x13
jc disk_check
ret
disk_check:
;lea si,[checkString]
;call printstr
mov si,checkString
call kprint
jmp $
.done:
ret
;section .data
align 4
BOOT_DRIVE: db 0
msgString: db 'Game platform premier',10,13,0
checkString: db 'section incomplete',10,13,0
checkString2: db 'disk complete',10,13,0
times 510 -($-$$) db 0
dw 0xaa55
As I mentioned earlier you can notice the other disk read here (read_section2). Most of the configurations I reference say that each of the drive variables uses 18 sections however anytime I try to change this I get no changes on my screen. I really hope I'm not the only one that has been around this type of configuration and would really appreciate correspondence on this here.
The code is based on my most previous posts and while I believe most of disk reading is contained inside the boot file you can let me know if anyone else would like another copy of the source code for review.
-
Reference to full source posted here
-
Branch 2 Review
I have a revision of the first part of the previous file for simplification.
check_0:
mov bx,msgString
;mov bx,0x0000_a000
mov cl,1
mov al,1
call read_section2
;mov cx,0xa00
;mov es,cx
mov bx,0
mov cx,00000h
mov cl,1
mov al,1
call read_section2
check_1:
mov bx,0x0000_9e00
mov cl,18
mov bx,msgString
mov bx,0x0000_9e00
mov cl,18
mov al,1
call read_section
mov cx,0x9e0
mov es,cx
mov bx,0
mov cl,18
mov al,1
call read_section
mov bx,0x0000_9C00
mov cl,17
mov bx,msgString
mov bx,0x0000_9C00
mov cl,17
mov al,1
call read_section
mov cx,0x9c0
mov es,cx
mov bx,0
mov cx,00000h
mov cl,17
mov al,1
call read_section
mov bx,0x0000_8000
mov cl,3
mov bx,msgString
mov bx,0x0000_8000
mov cl,3
;mov al,16
mov al,14
call read_section
mov cx,0x800
mov es,cx
mov bx,0
mov cx,00000h
mov cl,3
mov al,14
call read_section
mov bx,0x0000_7e00
mov cl,2
mov bx,msgString
mov bx,0x0000_7e00
mov cl,2
mov al,1
call read_section
mov cx,0x7e0
mov es,cx
mov bx,0
mov cx,00000h
mov cl,2
mov al,1
call read_section
mov bx,0x0000_7e00
.1:
pusha
jmp 0x7e00
read_section:
mov ah,0x02
; mov al,1
mov ch,0
mov dh,0
; mov dl,0x80
int 0x13
jc disk_check
call disk_reset
ret
read_section2:
mov ch,1
mov cl,1
mov ah,0x02
mov dh,0
int 0x13
.check:
jc disk_check
call disk_reset
ret
This is now simplified to
read:
mov cx,0x7c0
mov es,cx
xor bx, bx
mov bx,0x0200
mov ah, 0x02 ; Load disk data to ES:BX
mov al, 17 ; Load 17 sectors
mov ch, 0 ; Cylinder=0
mov cl, 2 ; Sector=2
mov dh, 0 ; Head=0
mov dl, 0 ; Drive=0
; mov dl,0x80
int 13h ; Read!
; jc read ; Error, try again
jc disk_check
ret
read2:
mov cx,0x7c0
mov es,cx
mov bx, 0x2400
mov ah, 0x02 ; Load disk data to ES:BX
mov al, 18 ; Load 18 sectors
mov ch, 1 ; Cylinder=0
mov cl, 1 ; Sector=2
mov dh, 0
mov dl, 0
; mov dl,0x80
int 13h ; Read!
; jc read2 ; Error, try again
jc disk_check
ret
Curiously these halts at the same place (the 2nd cylinder passed the first 18 sections). I think this illustrated more obviously what the object in question is.
-
Debugger Review 1
Apparently I can change the section size larger than the cylinder count.Curiously this can go until al=65.
-
Hi Deskman,
Do you mean "sections" or "sectors"?
Best,
Frank
-
Debugger Review 1
Apparently I can change the section size larger than the cylinder count.Curiously this can go until al=65.
This is some general information on writing bootloaders, I have other tips here (https://stackoverflow.com/a/32705076/3857942). Don't ever assume that the segment registers (or any register other than DL) are what you expect when control is transferred to physical address 0x07c00. If you are going to read data into memory set the stack somewhere that you know it won't be trampled on. If you are reading into memory starting at 0x07e00 setting SS:SP to 0x0000:0x7c00 would ensure you don't clobber the stack. On BOCHS the default stack (SS:SP) will appear to grow down from 0x0000:0xffff because they set SS:SP to start at 0x0000:0x0000 (which wraps to the top of the first 64KiB). If you read data on top of the stack weird and wonderful things will happen and likely your Int 13h calls will never properly return.
In QEMU which you seem to be using the stack luckily is set somewhere where it won't be clobbered by the reads you are doing(you should still be setting SS:SP somewhere you know you won't clobber). Emulators are often forgiving, but it shouldn't be assumed on real hardware (especially if you are using very antiquated computers) that you can even cross a track boundary (not all BIOSes support multitrack reads/writes). Multitrack reads (if available) shouldn't be assumed to cross cylinder boundaries. Just because BOCHS and QEMU allow it, don't rely on that on real hardware IF you are targeting a wide array of hardware. Most BIOSes in the past 30 years do support multitrack reads/writes but not necessarily across cylinders.
In BOCHS the maximum number of sectors you can read in one Int 13/AH=2 BIOS call is artificially limited to 72 sectors. In QEMU the maximum number of sectors you can read at once is 128.
In your case though you are running into another problem. If you use Int 13h/ah=2 your read can't cross a 64KiB boundary because of the way DMA works on the IBM-PC. QEMU actually enforces this and why your reads are likely failing. I'd expect that the carry flag for Int 13h/AH=2 is set with a DMA boundary error (ah=0x09).
If you start reading disk data starting at 0x0000:0x7e00 (physical address 0x07e00) you can only read 65 sectors total in QEMU before you cross physical address 0x10000 (the first 64KiB boundary). If you read 66 starting at 0x0000:0x7e00 then you will get a DMA error on the disk read. If you read starting at 0x0000:0x8000 then you'll only be able to read 64 sectors before crossing that boundary. If you start reading at 0x0000:FE00 you will get a DMA error trying to read 2 sectors.
The 64KiB DMA boundaries for an 8086 (memory below 1MiB) are at physical addresses 0x10000, 0x20000, 0x30000... etc.
If you want to read to both sides of a 64KiB DMA boundary you will have to split the read up to read up to the DMA boundary (without crossing it) and then do another read on the other side of the boundary.
One way to avoid all the multitrack and DMA issues etc is to always read 1 sector at a time and ensure a sector is always read to a 512 byte aligned memory address. This is slower on real hardware with physical floppy drives but has the most chance of avoiding problems.
If reading sectors into memory one at a time it can be very useful (simpler) to use Linear Block Addressing (LBA). Even though floppy media uses CHS (Cylinder/Head/Sector) via Int 13h/AH=2, you can also use LBA addressing if you have a way to convert an LBA address to CHS to do the reads. I have an answer on Stackoverflow (https://stackoverflow.com/a/45495410/3857942) that discusses this and provides code for such a lba_to_chs function that supports a 16-bit LBA to CHS translation (Fine for Fat12 media). A similar function (but more complex) that handles translating 32-bit LBAs to CHS can be found in this Stackoverflow answer (https://stackoverflow.com/a/47127909/3857942).
-
Good news
I managed to check out a good amount of the new LBA code from the references into the design.
I have an answer on Stackoverflow that discusses this and provides code for such a lba_to_chs function that supports a 16-bit LBA to CHS translation (Fine for Fat12 media). A similar function (but more complex) that handles translating 32-bit LBAs to CHS can be found in this Stackoverflow answer.
The new model now has a LBA function and runs until the read_root method's AH status compare instruction. I'm trying to make adjustments to this however intriguingly I have gotten through to the first new 512 section. Here is the new file and modified Makefile.
boot.asm
; ReTimerOS
; Copyright (C) 2022,2023 Christopher Hoy
;
; This file is part of ReTimerOS
; ReTimerOS is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <https://www.gnu.org/licenses/>.
[BITS 16]
;[ORG 0x7c00]
jmp short _prep_module
nop
bdb_oem: db 'MSWIN4.1'
bdb_db_per_section: dw 512
bdb_sections_per_pack: db 1
bdb_reserved_sections: dw 1
bdb_fat_count: db 2
bdb_file_entries_count: dw 0E0h
bdb_sections_complete: dw 2880
bdb_type: db 0F0h
bdb_sections_per_fat: dw 9
bdb_sections_per_track: dw 18
bdb_heads: dw 2
bdb_hidden_sections: dd 0
bdb_big_section_count: dd 0
ebr_drive_number: db 0
db 0
ebr_signature: db 29h
ebr_volume_id: db 12h,34h,56h,78h
ebr_volume_label: db 'RETIMERS OS'
ebr_system_id: db 'FAT12 '
;section .text
_prep_module:
xor ax,ax
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0x7C00
sub dword [vidmem_ptr],VIDEO_TEXT_ADDR
call read_fat
read_complete:
jmp 0x7e00
; jmp 0x9000
DriveIdPrep:
mov [ebr_drive_number],dl
mov ah,0x41
mov bx,0x55aa
int 0x13
; jc CheckPrep
cmp bx,0xaa55
; jne CheckPrep
SizeFunction:
mov bx,0x0000_7E00
mov cl,2
mov ah,0x02
; mov al,1
; mov al,12h
mov al,65
mov ch,0
mov dh,0
int 0x13
; jc ReadCheck
mov dl,[ebr_drive_number]
read_fat:
mov cx,0x07c0
mov es,cx
mov ah,0x02
mov al,[bdb_sections_per_fat]
mov si,[bdb_reserved_sections]
call lba_to_chs
mov bx,0x0200
int 13h
.check:
cmp ah,0
jz .read_root_table
mov si,check_string
call print
hlt
.read_root_table:
mov si,verified_read
call print
xor ax,ax
mov al,[bdb_fat_count]
mul word [bdb_sections_per_fat]
add ax,[bdb_reserved_sections]
mov si,ax
mul word [bdb_db_per_section]
mov bx,ax
mov ax,32
cwd
mul word [bdb_sections_complete]
div word [bdb_db_per_section]
call lba_to_chs
mov ah,0x02
int 13h
.read_check:
; cmp ah,0
; jz read_complete
; jmp read_complete
jnc read_complete
mov si,check_string
call print
jmp $
_lba_to_chs:
push ax ; Preserve AX
mov ax, si ; Copy LBA to AX
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [bdb_sections_per_track] ; 32-bit by 16-bit DIV : LBA / SPT
mov cl, dl ; CL = S = LBA mod SPT
inc cl ; CL = S = (LBA mod SPT) + 1
xor dx, dx ; Upper 16-bit of 32-bit value set to 0 for DIV
div word [bdb_heads] ; 32-bit by 16-bit DIV : (LBA / SPT) / HEADS
mov dh, dl ; DH = H = (LBA / SPT) mod HEADS
mov dl, [ebr_drive_number] ; boot device, not necessary to set but convenient
mov ch, al ; CH = C(lower 8 bits) = (LBA / SPT) / HEADS
shl ah, 6 ; Store upper 2 bits of 10-bit Cylinder into
or cl, ah ; upper 2 bits of Sector (CL)
pop ax ; Restore scratch registers
ret
lba_to_chs:
push ax
; push dx
mov ax,si
xor dx,dx ;dx = 0
div word [bdb_sections_per_track] ;ax =LBA/SectionsPerTrack
;dx =LBA%SectionsPerTrack
; inc dx ;dx = (LBA % SectionsPerTrack + 1) = section
; mov cx,dx ;cx = section
mov cl,dl
inc cl
xor dx,dx
div word [bdb_heads] ;ax = (LBA / SectionsPerTrack) / H = cylinder
;dx = (LBA / SectionsPerTrack) % H = h
mov dh,dl
mov dl,[ebr_drive_number] ;dh = h
mov ch,al ;ch = cylinder
shl ah,6
or cl,ah ;put upper 2 cmp of cylinder in cl
; pop ax
; mov dl,al
pop ax
ret
print:
; sub dword [vidmem_ptr],VIDEO_TEXT_ADDR
.setting1:
mov bp,0b800h
mov es,bp
mov bp,[vidmem_ptr]
.prep:
mov cx,0
mov es,cx
mov ax,ROWS
mov bx,es:0x044a
mul bx
add ax,COLS1
mov bp,ax
add bp,[vidmem_ptr]
; add bp,32
add bp,word [new_row]
mov cx,0b800H
mov es,cx
mov ch,055h
jmp .gettext
.gettext:
mov cl,byte [ds:si]
mov ch,0x45
mov word [es:bp],cx
inc bp
inc bp
inc si
test cl,cl
; jnz output
jnz .gettext
; mov [vidmem_ptr],bp
; mov cx,0
; mov ds,cx
add word [new_row],COLS1*2
; mov word [new_row],32
ret
string1: db 'test string',0
check_string: db 'disk checked',0
verified_read: db 'fat read verified',0
ROWS EQU 25
COLS1 EQU 80
VIDEO_TEXT_ADDR EQU 0XB8000
;align 4
new_row: db 0x40
vidmem_ptr: dd VIDEO_TEXT_ADDR
times 510 -($-$$) db 0
dw 0xaa55
Makefile
FILES =boot.o
FILES2 =stage3.o printstr.o
PROCURE = rm -rf
all: boot.bin stage1.bin test1.bin
dd if=boot.bin of=cd.img bs=512 count=2880 conv=notrunc
dd if=stage1.bin of=cd.img bs=512 seek=1 conv=notrunc
dd if=test1.bin of=cd.img bs=512 seek=10 conv=notrunc
boot1.bin: boot.asm
nasm -f bin -g -o boot1.bin boot.asm
test_1.bin: test1.asm
nasm -f bin -g -o test_1.bin test1.asm
boot.o: boot.asm
nasm -f elf -g -o boot.o boot.asm
stage1.o: stage1.asm
nasm -f elf -g stage1.asm -o stage1.o
test1.o: test1.asm
nasm -f elf -g test1.asm -o test1.o
stage2.o: stage2.asm
nasm -f elf -g stage2.asm -o stage2.o
test2.bin: test2.asm
nasm -f bin -g test2.asm -o test2.bin
boot.bin: boot.o
ld -g -m elf_i386 -Ttext 0x7c00 boot.o -o boot.bin --oformat binary
stage1.bin: stage1.o
ld -g -m elf_i386 -T link2.ld stage1.o -o stage1.bin --oformat binary
stage2.bin: stage2.o
ld -g -m elf_i386 -T link4.ld stage2.o -o stage2.bin --oformat binary
test1.bin: test1.o
ld -g -m elf_i386 -Ttext 0x9000 test1.o -o test1.bin --oformat binary
clean:
$(PROCURE) *\.o
$(PROCURE) *\.bin
$(PROCURE) os.img
For improvements I'm looking into the references to modify the values for read_fat.read_check. What I understand is that the cmp instruction checks the status from AH so I'll have to test these parts again.
Revision 1
For the code under the .read_root_table function
;Inspection code
mul word [bdb_sections_complete]
div word [bdb_db_per_section]
call lba_to_chs
mov ah,0x02
;Modification 1
mov al,1
int 13h
.read_check:
cmp ah,0
jz read_complete
;Inspection code
I tested the standard configuration for a 1 size section by manually adding the AL value. Surprisingly this is valid so now I'm reviewing this for an incrementing model.
-
I see you found the lba_to_chs function. I should point out that the other code for using a FAT12 file system is just one example - you don't need to have a FAT12 file system to usse lba_to_chs. https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf . If you really want to use FAT12 on floppy media I recommend looking at the Microsoft Specification for what all the fields are in a BPB etc. That can be found here: https://academy.cba.mit.edu/classes/networking_communications/SD/FAT.pdf .
An example of lba_to_chs that reads sectors after the bootloader into memory (without FAT file system) can be found in this code I wrote on Stackoverflow (https://stackoverflow.com/a/54894586/3857942).
Using a FAT12 file system is more complex but it offers flexibility in that you can copy files to a disk image and have the bootloader search for those and load them. If you do decide to use a FAT file system I recommend the mtools package that is available on many OSes that provides the ability to interact with disk images by copying files, formatting deleting files in disk images without elevated OS privileges (like `sudo`)
-
Proof of Concept and Test Results
Good news everyone
The bpb seems to check out most of the essentials for my proof of concept. This seems to have been a very good investigation because of how the tests have looped back into the earlier subjects.As a proof of concept the one other thing remaining is to test the DMA boundary.
If you start reading disk data starting at 0x0000:0x7e00 (physical address 0x07e00) you can only read 65 sectors total in QEMU before you cross physical address 0x10000 (the first 64KiB boundary). If you read 66 starting at 0x0000:0x7e00 then you will get a DMA error on the disk read.
From the previous references of the source links a few adjustments to the code produces this as a provision. The tests yields for trails were good for the first 64kb . Otherwise at best I passed a print test for a ds set to 0x1000 <<4. What this means is I can read the data @ 0x10000 but I can't jump or call instructions. Here I'm not certain if I had left out a setting earlier or not but similarly this appears really close to the source.
One way to avoid all the multitrack and DMA issues etc is to always read 1 sector at a time and ensure a sector is always read to a 512 byte aligned memory address.
The main function from the source does indeed use such a LBA method.The individual settings are set to the lba call and looped through incrementally.
stages:
; mov [bootDevice], dl ; Save boot drive
jmp .chk_for_last_lba ; Check to see if we are last sector in stage2
.read_sector_loop:
mov bp, DISK_RETRIES ; Set disk retry count
call lba_to_chs ; Convert current LBA to CHS
mov es, di ; Set ES to current segment number to read into
xor bx, bx ; Offset zero in segment
.retry:
mov ax, 0x0201 ; Call function 0x02 of int 13h (read sectors)
; AL = 1 = Sectors to read
int 0x13 ; BIOS Disk interrupt call
jc .disk_error ; If CF set then disk error
.success:
add di, 512>>4 ; Advance to next 512 byte segment (0x20*16=512)
inc si ; Next LBA
.chk_for_last_lba:
cmp si,0x42
jng .read_sector_loop ; If we haven't then read next sector
Unless there is another model design to configure the 64kb boundary this appears to be the only thing left on this post. I understand the original does stipulate a reservation on the first 32kb (0x7c00 - 0xFFFF) however I had assumed that the lba could be reset accordingly. I know this is a good amount but the review was just as fun and I'm optimistic that this is close. Meanwhile I hope y'all have a good day and I'll be available if anyone else would like to look into this further.
Revision
I can shift these values for print till 0x50000. At 0x60000 the BIOS screen is blanked leaving only the print and by 0x70000 everything just blanks. I was thinking this would happen in the reserved areas ( 0xA0000- 0xD0000) however this is just very curious for a test.
-
i came to pass through a source code which was listed as examples by other authors in the old iczelion's website that deals directly on modifying the actual disk...
i think you can find it, which is a .chm download (the zip file is embedded) in tuts 4 u website/forums or something like that.