Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re: How to use BIOS int 13h ah=48h ?

612 views
Skip to first unread message

Rod Pemberton

unread,
Apr 3, 2016, 5:36:17 PM4/3/16
to
On Sun, 3 Apr 2016 03:52:01 -0700
bilsch <kin...@comcast.net> wrote:

> I've been trying to use int 13h to get the number of
> sectors on the hard drive.

FYI, not many people have posted here (alt.comp.bios)
over the past 5 years or so, Bill.

I saw your post on comp.lang.asm.x86. Why didn't you
look at Int 13H, AH=41h, BX=55AAh, as I suggested? ...

comp.lang.asm.x86 was a good place to post. alt.lang.asm
and even comp.os.msdos.programmer would've been good places
to post. alt.os.development is probably the best place.
I've added AOD for replies.

> From Ralph Brown List:
> <code>
> AH = 48h
> DL = drive (80h-FFh)
> DS:SI -> buffer for drive parameters
>
> Return:
> CF clear if successful
> AH = 00h
> DS:SI buffer filled
> CF set on error
> AH = error code
>
> Buffer for drive parameters:
>
> Offset Size Description
> 00h WORD (call) size of buffer
> (001Ah for v1.x, 001Eh for v2.x, 42h for v3.0)
> (ret) size of returned data
> 02h WORD information flags (see #00274)
> 04h DWORD number of physical cylinders on drive
> 08h DWORD number of physical heads on drive
> 0Ch DWORD number of physical sectors per track
> 10h QWORD total number of sectors on drive
> 18h WORD bytes per sector
>
> It seems to me, the following code would be the
> correct way to call int 13h ah=48h:

First, are you doing this in a boot loader at 7C00h,
or attempting to call from DOS .com or .exe,
or calling this from an emulator, or calling
this while using Linux or Windows, or using v86 mode?

If in a boot loader at 7C00h, you can call it.
If in DOS as a .com or .exe, DOS may be redirecting
the BIOS IVT interrupt to it's own routines. If
using DOS, you should use DOS routines, not BIOS.
If in an emulator, there could be a bug.
If under another OS like Linux or Windows,
you should not expect to be able to call it.
If you're using v86 mode, you may have problems
with the PM OS or monitor controlling the v86 mode.

> sxbuf times 0x42 db 0 ;bufr for BIOS to fill

Is this buffer actually before your code? Do you
jump to the 'mov' instruction below? I.e., you're
not executing the empty buffer are you? ...

> mov si,sxbuf

What segment value is in DS? It may be wise to make
sure it is below 512KB. Early machines had only 512KB,
while later ones have 640KB. Between 640KB and 1MB+64KB-16,
you have BIOS ROMs, video cards, etc. Above 1MB, shouldn't
be possible, unless you're using "unreal" mode. However,
the BIOS can't use a DS with a limit allowing access above
1MB+64KB-16. I.e., your DS must be a 16-bit RM segment value
below 1MB, actually need to be below 640KB or 512KB, and you
must not be using the "unreal" mode to call the BIOS.

> mov word [si], 0x42
> mov ah,0x48
> mov dl,0x80 ;bit 7 = hard drive
> int 0x13

The buffer value is not necessarily 0x42. It might be
some other value. You should call Int 13h, AH=41h, BX=55AAh
to get the version number in register AH. The AH byte needs
to be zero-extended to word AX and stored at DS:SI.

You also need to check the supported function bitmap
returned in register CX from Int 13h, AH=41h, BX=55AAh
to determine if the 48h call is even supported. Also,
it might be supported, but just not for your device.

Yes, I see that you're attempting to use the most recent
values for modern machines which should in theory work.
However, each BIOS is different. It's better to implement
it fully to get things working. Then, find out why the
abbreviated version doesn't work.

> And the following would be the correct way to read the sectors info:
>
> mov di,0x10 ;offset into sxbuf
> getbuf:
> mov ax,[sxbuf+di]

Location +0x10 is a QWORD or 8 bytes.

AH and AL are 1 byte (available in all modes)
AX is two bytes (available in 16-bit mode) WORD
EAX is four bytes (available in 32-bit mode) DWORD
RAX is eight bytes (available int 64-bit mode) QWORD

> inc di

DI is not scaled by the size of AX like a C variable.

I.e., 'inc di' increments DI by one (1). So, you're
loading two bytes into AX, but moving DI by only one
byte at a time ... You probably want another 'inc'
instruction here.

> cmp di,4

With two 'inc' instructions, you'd need ,8 here.

> je done
> jmp getbuf
> <code>

While you are loading AX with values, I don't see code
to save or use AX within the loop. IIRC, you called
a print loop in your CLAX post.

> but it doesn't work. The call returns:
> CF=1 (failed)
> ah=01 (invalid parameter)
> ax = 0000, 0000, 0000, 0000

I would assume that this is because the 0x42h in DS:SI
buffer is not the correct value, or that DS:SI is not
accessable by the BIOS, or that hard drive 80h is not
actually present.

Perhaps, you're attempting to access a floppy or USB
device or CD-ROM/DVD-ROM?

A floppy device can't be on 80h or higher. A USB device
can only be 80h if it was the boot device and is being
emulated by BIOS as a hard disk. IIRC, a CD-ROM or
DVD-ROM can never be 80h, it must be secondary, e.g., 81h.

Also, note that some BIOS calls need you to explicitly
set CF via STC before being called. Another option would
be that AH or other register is being corrupted somehow,
e.g., use of a debugger or emulator.

> I've tried variations, but none work. I've tried it on five
> relatively new (last 5 years) computers - never works.
>
> How do you do it?

I haven't, but some on alt.os.development have recently.
There was a short competition, IIRC, which used Int 13h
BIOS disk routines. I added AOD to the reply newsgroups.


Other questions:

What value does Int 13h, AH=41h, BX=55AAh
return in in register CX?

This word value has a bitmap which indicates
which "IBM/MS INT 13 Extensions" are supported
by the BIOS. Bit 1, 2, and 3 are listed as
extended, removable, and enhanced, respectively.
48h is listed for all three. 48h description
is as an extension, so, I'd suspect bit 1 to
be set.


What value does Int 13h, AH=41h, BX=55AAh
return in in register AH?

This byte value should be filled in to the first
word of the DS:SI buffer for Int 13h, AH=48h.


Int 13h, AH=48h, indicates that some BIOSes
need word at DS:[SI+2] set to 0000h. So, you
should set both the version number word and
clear the next word in 'sxbuf'.


Is your DS:SI buffer placed below 1MB, preferably
below 640K or 512K? Can you confirm the DS:SI
segment:offset?


Are you initializing the USB controllers? If so,
you'll lose access to a USB boot device that is
being emulated by the BIOS as a hard disk.


If RBIL is not providing enough information,
you can search for the Phoenix Enhanced Disk
Drive Specifications. There are a few versions.


Rod Pemberton

bilsch

unread,
Apr 5, 2016, 8:34:54 AM4/5/16
to
On 04/03/2016 02:36 PM, Rod Pemberton wrote:

>
> First, are you doing this in a boot loader at 7C00h,
> or attempting to call from DOS .com or .exe,
> or calling this from an emulator, or calling
> this while using Linux or Windows, or using v86 mode?

Rod, thanks for your comments.

First, good news: I revised the program (see listing below), and for the
first time ah=0x48 returns CF=0 (success) and ah=0. But the number of
sectors doesn't work out right. It says EAB025420000FFFF. Backwards
EAB02542 is 24520BAE is 609356718 decimal, times 512 is 311,990,639,616
bytes. But that doesn't take little endian into account. GPARTED says
320,071,700,616 bytes.

To answer your questions: The program (code & data) fills one sector. I
use org 7c00. I write it to the boot sector of a partition with boot
flag set, or I write it to the boot sector of a partition and chain load
it using GRUB - depending on which computer I use.

It has one 'section': text. The segment registers: when I do set them,
I use the following, which makes no difference in the output:
mov ax, 0 ; set up segments
mov ds, ax
mov es, ax
mov ss, ax ; setup stack
mov sp, 0x7C00 ; stack grows downwards from 0x7C00

> The buffer value is not necessarily 0x42. It might be
> some other value. You should call Int 13h, AH=41h, BX=55AAh
> to get the version number in register AH. The AH byte needs
> to be zero-extended to word AX and stored at DS:SI.
>
> You also need to check the supported function bitmap
> returned in register CX from Int 13h, AH=41h, BX=55AAh
> to determine if the 48h call is even supported. Also,
> it might be supported, but just not for your device.

I revised my code (listed below): it now reports version and bitmap. On
this PC it reports version 3.0 and bitmap says bits 0 and 2 set (ah=0x48
is supported).
Please see revised code below, all these points are addressed.
<code>
;kern1.asm nasm -f bin kern1.asm -o kern1.bin

;reads an 8 byte buffer containing number of sectors info; buffer begins
at ds:si + 0x10

org 0x7c00
bits 16
SECTION .text

mov ax, 0 ; set up segments
mov ds, ax
mov es, ax
mov ss, ax ; setup stack
mov sp, 0x7C00 ; stack grows downwards from 0x7C00

mov si,msg41
call print_string
mov bx,0x55aa
mov dl, 0x80
mov ah,0x41
int 0x13
push cx
push ax
cmp ah,0x10 ;ver 1.x
mov word[bsiz],0x1a
cmp ah,0x20 ;ver 2.0
mov word[bsiz],0x1e
cmp ah,0x21 ;ver 2.1
mov word[bsiz],0x1e
cmp ah,0x30 ;ver 3.0
mov word[bsiz],0x42

mov si,ahmsg41 ;'ah41 = '
call print_string
mov bx,i2xt
xor cx,cx ;cx counts to 1
pop ax
lup1: ;here ax is data from ah=0x41
rol ax,4 ;put hi hex digit on right
push ax ;rol'd version
and al,0x0f ;digit in al, is index into i2xt
xlat ;table result in al, bx already points i2xt
mov ah,0xe
int 0x10 ;write
pop ax
cmp cx,1
je api ;cx=1 then done w/ah
inc cx
jmp lup1
api:
mov si,apimsg ;'api = '
call print_string
pop cx
mov al,cl
xlat ;table result in al, bx already points i2xt
mov ah,0xe
int 0x10 ;write

mov si, msg48 ;display BIOS call
call print_string
clc ;clear CF
mov si,sxbuf ;bufr for BIOS to fill
mov di,word[bsiz]
mov [sxbuf],di ;bufr size for BIOS call
mov ah,0x48
mov dl,0x80 ;bit 7 = hd
int 0x13
push ax

jc cf1
mov si, cfzro
call print_string
jmp ah48
cf1:
mov si, cfone
call print_string
ah48:
mov si,ahmsg48 ;'ah='
call print_string
xor cx,cx ;cx counts to 1
pop ax
lup2: ;here ax is data from ah=0x48
rol ax,4 ;put hi hex digit on right
push ax ;rol'd version
and al,0x0f ;digit in al, is index into i2xt
xlat ;table result in al, bx already points i2xt
mov ah,0xe
int 0x10 ;write
pop ax
cmp cx,1
je dsxinfo ;cx=1 then done w/ah
inc cx
jmp lup2

dsxinfo:
mov si, sxmsg ;'hex # sectors = '
call print_string
xor cx,cx
xor dx,dx
xor di,di
add di,0x10 ;offset into sxbuf
get4hex:
add di,dx
add di,dx ;di=2*dx, dx counts words, di counts bytes
mov ax,[sxbuf+di]
outdig:
rol ax,4 ;put hi hex digit on right
push ax ;save rol'd ax
and al,0x0f ;digit in al, is index into i2xt
xlat ;table result in al
mov ah,0xe
int 0x10 ;write
pop ax ;rol'd version of ax
inc cx
cmp cx,4 ;cx is inner loop counter
jne outdig ;4 digit loop - 1 nibble/loop

xor cx,cx
inc dx ;dx counts words
cmp dx,4 ;4 word loop - 1 word/loop
je nend
jmp get4hex ;get next 4 hex digits
nend:
mov si, tstamp ;date & time (manual)
call print_string
jmp $ ;jmp here infinite loop

msg41 db 0xd,0xa,'int 13h, ah=41h', 0xd, 0xa, 0
msg48 db 0xd,0xa,'int 13h, ah=48h', 0xd, 0xa, 0
bsiz dw 0
sxbuf times 0x42 db 0 ;bufr for BIOS to fill
ahmsg41 db 'ah = ', 0
apimsg db ' api = ', 0
cfzro db 'CF=0. success.', 0xd, 0xa, 0
cfone db 'CF=1. failed.', 0xd, 0xa, 0
ahmsg48 db 'ah>0 is error code. ah = ', 0
sxmsg db 0xd, 0xa ,'L-endian sectors = ', 0
tstamp db 0xd, 0xa ,'date: 4/05/16 1:00 am', 0
i2xt db '0123456789ABCDEF'

; ===============================================================
; calls start here
; ===============================================================

print_string:
push ax ;preserve ax
.lup:
lodsb ; grab a byte from si
or al, al ; logical or al by itself
jz .done ; if the result is zero, get out
mov ah, 0xe
int 0x10 ; otherwise, print out the character!
jmp .lup
.done:
pop ax ;preserve ax
ret

times 510-($-$$) db 0
dw 0xAA55
<code>

Benjamin David Lunt

unread,
Apr 5, 2016, 3:53:42 PM4/5/16
to

"bilsch" <kin...@comcast.net> wrote in message
news:ne0b78$m2g$1...@dont-email.me...
> On 04/03/2016 02:36 PM, Rod Pemberton wrote:

Hi bilsch, Hi guys,

I just have a few comments on your code, if you don't mind.

> <code>
> ;kern1.asm nasm -f bin kern1.asm -o kern1.bin
>
> ;reads an 8 byte buffer containing number of sectors info; buffer begins
> at ds:si + 0x10
>
> org 0x7c00
> bits 16
> SECTION .text
>
> mov ax, 0 ; set up segments

; in boot sectors, space is sometimes an issue. Every byte
; counts, so I would change the above to
; xor ax,ax ; saves a byte

> mov ds, ax
> mov es, ax
> mov ss, ax ; setup stack
> mov sp, 0x7C00 ; stack grows downwards from 0x7C00
>
> mov si,msg41
> call print_string
> mov bx,0x55aa
> mov dl, 0x80
> mov ah,0x41
> int 0x13

; on return:
; carry set if error
; jc short to_some_error_handler
; carry clear if successful
; cmp bx,0xAA55
; jne short to_some_error_handler

> push cx
> push ax
> cmp ah,0x10 ;ver 1.x
> mov word[bsiz],0x1a
> cmp ah,0x20 ;ver 2.0
> mov word[bsiz],0x1e
> cmp ah,0x21 ;ver 2.1
> mov word[bsiz],0x1e
> cmp ah,0x30 ;ver 3.0
> mov word[bsiz],0x42

; the above code will *always* put a value of 0x42 in the
; [bsiz] address. You need some 'jcc' in there somewhere.

> mov si,ahmsg41 ;'ah41 = '
> call print_string

> mov bx,i2xt
> xor cx,cx ;cx counts to 1
> pop ax
> lup1: ;here ax is data from ah=0x41
> rol ax,4 ;put hi hex digit on right
> push ax ;rol'd version
> and al,0x0f ;digit in al, is index into i2xt
> xlat ;table result in al, bx already points i2xt
> mov ah,0xe int 0x10 ;write
> pop ax
> cmp cx,1
> je api ;cx=1 then done w/ah
> inc cx
> jmp lup1

; I noticed that you use this print code a few times
; within your listing here. Can you call a routine
; to do this? Rather than listing it numerous times,
; which takes up space, you can just call a function
; to do it. Also, I would change it a bit.
;
; first comment:
; Setting CX to 0, then comparing it with a non-zero
; value adds code, specifically an extra 'cmp' and 'jcc'
; instruction. Slower and larger.
;
; mov cx,2
; @@:
; ; do something here
;
; dec cx
; jnz short @b
; or change the above to just the 'loop' instruction
; loop @b
;
; Saves time and space.

; the code above now becomes
; mov bx,i2xt
; mov cx,2 ; printing 2 nibbles
; pop ax
; lup1: ; here ax is data from ah=0x41
; rol ax,4 ; put hi hex digit on right
; push ax ; rol'd version
; and al,0x0f ; digit in al, is index into i2xt
; xlat ; table result in al, bx already points i2xt
; mov ah,0xe
; int 0x10 ; write
; pop ax
; loop lup1
;
; (It is easier on the eyes if you align the colums of code
; and comments. i.e.: which is easier to read, your code
; above, or my code here?)
;
; Second comment:
; printing hex digits is something everyone has done so
; there is a lot of code around. A few years ago, more than
; I would like to admit, a regular in c.l.a.x passed away.
; However, he left us with a very nice hex printing routine.
; http://www.fysnet.net/prthex.htm#crayne
; It uses 32-bit registers and prints to a string, but you
; should be able to modify it to use 16-bit registers and
; directly to the screen.
;
> api:
> mov si,apimsg ;'api = '
> call print_string
> pop cx
> mov al,cl

; As you probably know, the push instruction does push
; the register, it pushes the contents of the register.
; Therefore, you don't have to use the same register
; when you pop the value. You can easily use a different
; register. Changing the above to
; pop ax
; removes the 'mov' instruction. Save space and time!
; mov di,0x10
; (but I'm sure you are not worried about it at the
; moment. You just want the value printed)

> get4hex:
> add di,dx
> add di,dx ;di=2*dx, dx counts words, di counts bytes
> mov ax,[sxbuf+di] outdig:
> rol ax,4 ;put hi hex digit on right
> push ax ;save rol'd ax
> and al,0x0f ;digit in al, is index into i2xt
> xlat ;table result in al
> mov ah,0xe int 0x10 ;write
> pop ax ;rol'd version of ax
> inc cx
> cmp cx,4 ;cx is inner loop counter
> jne outdig ;4 digit loop - 1 nibble/loop
> xor cx,cx
> inc dx ;dx counts words
> cmp dx,4 ;4 word loop - 1 word/loop
> je nend
> jmp get4hex ;get next 4 hex digits


; I would change the above to use SI and 'lodsb'
; mov cx,8 ; 8 bytes
; lea si,[sxbuf+0x10]
; @@:
; lodsb
; call hexdb ; Crayne's print nibble
; loop @b

Please note that your code all prints the hex digits
backwards. Which through your comments, I think you
realize this. :-)

Anyway, just a few comments.

I know it is C code instead of assembly, but I have
source code at
http://www.fysnet.net/syscore_help.htm
that will show you how to get all of the information from
the drive, including total sector count.

Hope this helps,
Ben


Rod Pemberton

unread,
Apr 6, 2016, 12:09:38 AM4/6/16
to
On Tue, 5 Apr 2016 05:34:50 -0700
bilsch <kin...@comcast.net> wrote:

> First, good news: I revised the program (see listing below), and for
> the first time ah=0x48 returns CF=0 (success) and ah=0.

Congratulations!

Did you find out the issue with the earlier code? ...

> But the number of sectors doesn't work out right.
> It says EAB025420000FFFF.

Ben made a comment on your print routines being backwards.

I'm assuming that this value is word correct, but
the QWORD word order is reversed, because things seem
to work out below ...

Technically, then, assuming EAB025420000FFFF is word
correct but the QWORD word order is reversed, then the
byte order is B0 EA 42 25 00 00 FF FF. So, the QWORD
value is FFFF00002542EAB0.

> Backwards EAB02542 is 24520BAE is 609356718 decimal,
> times 512 is 311,990,639,616 bytes. But that doesn't
> take little endian into account. GPARTED says
> 320,071,700,616 bytes.

"24520BAE" is "incorrectly" reversed from EAB02542.

24520BAE is EAB02542 reversed by nybbles (4-bits).
4225B0EA is EAB02542 reversed by bytes (8-bits).
2542EAB0 is EAB02542 reversed by words (16-bits).

I think you want the last value since your code gets AX.

2542EAB0 is 625142448 in decimal which times 512 gives
320,072,933,376. That is only slightly more than the
320,071,700,616 which GPARTED reported.

Some programs use 1000 instead of 1024 for a KB (kilobyte).
This is especially true for programs which report disk
capacity since manufacturers do that. It would seem that
GPARTED is using 500 here instead of 512. I.e., the
value 311,990,639,616 from GPARTED divided by 625,142,448
(2542EAB0 in hex) gives 499 ...

I'll let you track down the off-by-one discrepancy between
499 and 500, which should be around 7.5GB to 8GB on a 300GB
to 320GB drive. If using GPARTED, i.e., Linux etc., maybe
check 'fdisk -l' for the reported device capacity.

> [snip]
>
> mov bx,i2xt
> xor cx,cx ;cx counts to 1
> pop ax
> lup1: ;here ax is data from ah=0x41
> rol ax,4 ;put hi hex digit on right
> push ax ;rol'd version
> and al,0x0f ;digit in al, is index into i2xt
> xlat ;table result in al, bx already
> points i2xt mov ah,0xe
> int 0x10 ;write
> pop ax
> cmp cx,1
> je api ;cx=1 then done w/ah
> inc cx
> jmp lup1

You rolled your own hex routine. That's a good start.
Is it your first? There is usually a virtual round
of applause for your first one on x86! (clap! clap!)

There are many other ways to do hex for x86, including
some which are faster (larger) and some that are much
shorter. E.g., some simply add a value to a nybble,
IIRC 0x30, then compare and add seven for the upper
hex digits 'A' to 'F'. This eliminates the need for
the 'xlat' table. It might be a few bytes shorter.
IIRC, some routines use one of the BCD instructions
(AAA, AAD, AAM, AAS, DAA, DAS) to unpack nybbles.
It might be the AAA instruction ...


Rod Pemberton

bilsch

unread,
Apr 9, 2016, 5:09:17 PM4/9/16
to
OK, I've got it how I want it. Your suggestions about saving space were
good, as I ran out of room at one point. Code is more efficient and
compact now. The program is listed below if anybody is interested.
Thanks. Bill S.

<code>
org 0x7c00
bits 16
SECTION .text

xor ax,ax ; set up segments
mov ds, ax
mov es, ax
mov ss, ax ; setup stack
mov sp, 0x7c00 ; stack grows downwards from 0x7c00

mov si,msg41
call print_string
mov bx,0x55aa
mov dl,0x80
mov ah,0x41
int 0x13
push cx
cmp ah,0x10 ;ver 1.x
jne ver20
mov word[bsiz],0x1a
ver20:
cmp ah,0x20 ;ver 2.0
jne ver21
mov word[bsiz],0x1e
ver21:
cmp ah,0x21 ;ver 2.1
jne ver30
mov word[bsiz],0x1e
ver30:
cmp ah,0x30 ;ver 3.0
jne ckbsiz
mov word[bsiz],0x42
ckbsiz:
cmp word[bsiz],0
jne ah41
mov si,noext ;no int 13h ext
call print_string
jmp nend ;w/o bsiz we're going nowhere
ah41:
mov si,ahmsg41 ;ah =
call print_string
mov bx,i2xt
mov cx,2 ;cx = # of digits to translate
call b2xa
api:
mov si,apimsg ;'api = '
call print_string
pop ax ;this is pushed cx
xlat ;table result in al, bx already points i2xt
mov ah,0xe
int 0x10 ;write
ah48:
mov si, msg48 ;display BIOS call
call print_string
clc ;clear CF
mov si,sxbuf ;bufr for BIOS to fill
mov di,word[bsiz]
mov [sxbuf],di ;bufr size for BIOS call
mov ah,0x48
mov dl,0x80 ;bit 7 = hd
int 0x13

jc cf1
mov si, cfzro
call print_string
jmp ah48m
cf1:
mov si, cfone
call print_string
ah48m:
mov si,ahmsg48 ;'ah='
call print_string
mov cx,2 ;cx = # of digits to translate
call b2xa
dsxinfo:
mov si, sxmsg ;'hex # sectors = '
call print_string
mov di,0x16 ;start w/bytes 0x16 & 0x17
get4digit:
mov cx,4 ;cx counts 4 nibbles/word
mov ax,[sxbuf+di]
outdigit:
rol ax,4 ;put hi hex digit on right
push ax ;save rol'd ax
and al,0x0f ;digit in al, is index into i2xt
xlat ;table result in al
mov ah,0xe
int 0x10 ;write
pop ax ;rol'd version of ax
dec cx
jnz outdigit ;4 digit loop - 1 nibble/loop
dec di
dec di
cmp di,0xe ;end w/bytes 0x10 & 0x11
jnz get4digit ;get next 4 hex digits
nend:
mov si, tstamp ;date & time (manual)
call print_string
jmp $ ;jmp here infinite loop

msg41 db 0xd,0xa,'int 13h, ah=41h', 0xd, 0xa, 0
msg48 db 0xd,0xa,'int 13h, ah=48h', 0xd, 0xa, 0
ahmsg41 db 'ah = ', 0
noext db ' no int 13h ext', 0
apimsg db ' api = ', 0
cfzro db 'CF=0=success.', 0xd, 0xa, 0
cfone db 'CF=1=failed.', 0xd, 0xa, 0
ahmsg48 db 'ah>0 is an error code. ah = ', 0
sxmsg db 0xd, 0xa ,'# of sectors = ', 0
tstamp db 0xd, 0xa ,'4/08/16 7:00 am', 0
i2xt db '0123456789ABCDEF'
bsiz dw 0
sxbuf times 0x42 db 0 ;bufr for BIOS to fill
; ===============================================================
; calls start here
; ===============================================================

print_string:
push ax ;preserve ax
.lup:
lodsb ; grab a byte from si
or al, al ; logical or al by itself
jz .done ; if the result is zero, get out
mov ah, 0xe
int 0x10 ; otherwise, print out the character!
jmp .lup
.done:
pop ax ;preserve ax
ret

b2xa: ;ax has data, cx has # nibs to translate
rol ax,4 ;put hi hex digit on right
push ax ;rol'd version
and al,0x0f ;digit in al, is index into i2xt
xlat ;table result in al, bx already points i2xt
mov ah,0xe
int 0x10 ;write
pop ax
dec cx
jnz b2xa
ret

times 510-($-$$) db 0
dw 0xAA55
</code>

Mike Gonta

unread,
Apr 9, 2016, 6:43:08 PM4/9/16
to
"bilsch" wrote:
> OK, I've got it how I want it. Your suggestions about
> saving space were good, as I ran out of room at one point.
> Code is more efficient and compact now. The program is
> listed below if anybody is interested.
> ....
> mov bx,0x55aa
> mov dl,0x80
> ....

Best practice is not to assume the drive ID. At this point
in the code the BIOS has the boot drive ID in DL (which
should be used (and saved for later use as well).

> ....
> print_string:
> push ax ;preserve ax
> .lup:
> lodsb ; grab a byte from si
> or al, al ; logical or al by itself
> jz .done ; if the result is zero, get out
> mov ah, 0xe
> int 0x10 ; otherwise, print out the character!
> jmp .lup
> .done:
> pop ax ;preserve ax
> ret
> ....

BIOS INT 0x10, AH=0x0E expects a BH="video page" (typically
this is BH=0, simply use the first "video page").
Oh, and you spelled loop wrong - the correct spelling should
be "lupe".


Mike Gonta
look and see - many look but few see

http://mikegonta.com
http://tawk.to/mikegonta


Benjamin David Lunt

unread,
Apr 10, 2016, 12:28:37 AM4/10/16
to

"bilsch" <kin...@comcast.net> wrote in message
news:nebqrl$8it$1...@dont-email.me...

> OK, I've got it how I want it. Your suggestions about saving space were
> good, as I ran out of room at one point. Code is more efficient and
> compact now. The program is listed below if anybody is interested.
> Thanks. Bill S.

Glad to help. I'll make a few more suggestions.

> <code>
> org 0x7c00
> bits 16
> SECTION .text
>
> xor ax,ax ; set up segments
> mov ds, ax
> mov es, ax
> mov ss, ax ; setup stack
> mov sp, 0x7c00 ; stack grows downwards from 0x7c00
>
> mov si,msg41
> call print_string
> mov bx,0x55aa
> mov dl,0x80

; As Mike noted, DL is passed to your code.
mov [saved_dl],dl
; and comment out the line you used above,
; being sure not to modify DL beforehand.
Depending on how you look at it, the above
code is missing some 'jmp's or in the case
I show below, has way too many :-)

First, in the code above, even after you put the
correct value into [bsiz], you still cmp ah for
each additional check. No problem, except that
it is slower code and larger code. How about
something like this:

mov word[bsiz],0x1a
cmp ah,0x10 ;ver 1.x
je ah41
mov word[bsiz],0x1e
cmp ah,0x20 ;ver 2.0
je ah41
; mov word[bsiz],0x1e
cmp ah,0x21 ;ver 2.1
je ah41
mov word[bsiz],0x42
cmp ah,0x30 ;ver 3.0
je ah41
mov si,noext ;no int 13h ext
call print_string
jmp nend ;w/o bsiz we're going nowhere
ah41:

Less code, fewer jumps.

> mov si,ahmsg41 ;ah =
> call print_string
> mov bx,i2xt
> mov cx,2 ;cx = # of digits to translate
> call b2xa
> api:
> mov si,apimsg ;'api = '
> call print_string
> pop ax ;this is pushed cx
> xlat ;table result in al, bx already points i2xt
> mov ah,0xe
> int 0x10 ;write

Also as Mike said, bh should be zero before the
interrupt call above.

> ah48:
> mov si, msg48 ;display BIOS call
> call print_string
> clc ;clear CF
> mov si,sxbuf ;bufr for BIOS to fill
> mov di,word[bsiz]
> mov [sxbuf],di ;bufr size for BIOS call
> mov ah,0x48
> mov dl,0x80 ;bit 7 = hd
> int 0x13


> jc cf1
> mov si, cfzro
> call print_string
> jmp ah48m
> cf1:
> mov si, cfone
> call print_string
> ah48m:

Again, the 'mov' instruction does not modify the
flags register, so you can do a mov before the
carry check:

mov si, cfone
jc cf1
mov si, cfzro
cf1:
call print_string
; less code, fewer jumps


> mov si,ahmsg48 ;'ah='
> call print_string
> mov cx,2 ;cx = # of digits to translate
> call b2xa
> dsxinfo:
> mov si, sxmsg ;'hex # sectors = '
> call print_string
> mov di,0x16 ;start w/bytes 0x16 & 0x17
> get4digit:
> mov cx,4 ;cx counts 4 nibbles/word
> mov ax,[sxbuf+di]
> outdigit:
> rol ax,4 ;put hi hex digit on right
> push ax ;save rol'd ax
> and al,0x0f ;digit in al, is index into i2xt
> xlat ;table result in al
> mov ah,0xe
xor bh,bh
I didn't go through it with as much detail, but it does
look better. Good job. Isn't learning fun :-)

Ben


Rod Pemberton

unread,
Apr 10, 2016, 4:01:47 AM4/10/16
to
On Sat, 9 Apr 2016 18:42:51 -0400
"Mike Gonta" <mike...@gmail.com> wrote:

> > .lup:
>
> Oh, and you spelled loop wrong - the correct
> spelling should be "lupe".

Huh?


Rod Pemberton

Alexei A. Frounze

unread,
Apr 10, 2016, 4:31:59 AM4/10/16
to
Italian for female wolves. With some imagination it could
become Guadalupe or lupus. Just kiddin'.

Alex

wolfgang kern

unread,
Apr 10, 2016, 5:02:21 AM4/10/16
to

bilsch posted:

[about]

seems you are on the right track yet, well done so far.

Ben already showed you possible shorter code solutions,
and here is what I'd do to save some more bytes:

I commented out ~175 bytes and added ~ 75.
Hope that I didn't make too many typos here ;)

__
wolfgang

> <code>
> org 0x7c00
> bits 16
> SECTION .text
>
> xor ax,ax ; set up segments
> mov ds, ax
> mov es, ax
> mov ss, ax ; setup stack
> mov sp, 0x7c00 ; stack grows downwards from 0x7c00
>
> mov si,msg41
> call print_string
> mov bx,0x55aa
> mov dl,0x80
> mov ah,0x41
> int 0x13

* jc errr ;could be not supported
* mov bx,0x7e00 ;points to your buffer now

> push cx
> cmp ah,0x10 ;ver 1.x
* mov al,0x1a
* je ah41

;> jne ver20
;> mov word[bsiz],0x1a
;> ver20:

> cmp ah,0x20 ;ver 2.0
* ver21:
* mov al,0x1e
* je ah41

;> jne ver21
;> mov word[bsiz],0x1e
;> ver21:

> cmp ah,0x21 ;ver 2.1
* je ver21

;> jne ver30
;> mov word[bsiz],0x1e
;> ver30:

> cmp ah,0x30 ;ver 3.0
* mov al,0x42
* je ah41

;> jne ckbsiz
;> mov word[bsiz],0x42
;> ckbsiz:
;> cmp word[bsiz],0
;> jne ah41

errr:
> mov si,noext ;no int 13h ext
> call print_string
> jmp nend ;w/o bsiz we're going nowhere

> ah41:

* mov word[bsiz],al
> mov si,ahmsg41 ;ah =
> call print_string

;> mov bx,i2xt
;> mov cx,2 ;cx = # of digits to translate
;> call b2xa

* call hex2ah ;AH 2 hex digits

Yeah, nibble based hex-print routines are fine.
But the XLAT method isn't short nor fast, I use eax/ebx
(you can use it too also within 16-bit code) to hold up
to 8 nibbles for hex dwords display, but see below.

> api:
> mov si,apimsg ;'api = '
> call print_string
> pop ax ;this is pushed cx

good, it works even it assumes that the pushed CL < 0x10 !

;> xlat ;table result in al, bx already points i2xt
;> mov ah,0xe
;> int 0x10 ;write

* call hex1al ;use only low nibble of AL

> ah48:
> mov si, msg48 ;display BIOS call
> call print_string
> clc ;clear CF

;> mov si,sxbuf ;bufr for BIOS to fill
> mov di,word[bsiz]
;> mov [sxbuf],di ;bufr size for BIOS call
* mov [bx],di

> mov ah,0x48
> mov dl,0x80 ;bit 7 = hd
> int 0x13

* mov si, cfone
> jc cf1
> mov si, cfzro
* cf1:
> call print_string

;> jmp ah48m
;>cf1:
;> mov si, cfone
;> call print_string
;>ah48m:

> mov si,ahmsg48 ;'ah='
> call print_string
;> mov cx,2 ;cx = # of digits to translate
;> call b2xa

* call hex2ah ;AH

> dsxinfo:
> mov si, sxmsg ;'hex # sectors = '
> call print_string
> mov di,0x16 ;start w/bytes 0x16 & 0x17
> get4digit:

;> mov cx,4 ;cx counts 4 nibbles/word
;> mov ax,[sxbuf+di]

* mov ax,[bx+di]
* call hex4ax ;four nibbles

;> outdigit:
;> rol ax,4 ;put hi hex digit on right
;> push ax ;save rol'd ax
;> and al,0x0f ;digit in al, is index into i2xt
;> xlat ;table result in al
;> mov ah,0xe
;> int 0x10 ;write
;> pop ax ;rol'd version of ax
;> dec cx
;> jnz outdigit ;4 digit loop - 1 nibble/loop

> dec di
> dec di
> cmp di,0xe ;end w/bytes 0x10 & 0x11
> jnz get4digit ;get next 4 hex digits

Have you got a reason to show it topdown ? :)

> nend:
> mov si, tstamp ;date & time (manual)
> call print_string
> jmp $ ;jmp here infinite loop

> msg41 db 0xd,0xa,'int 13h, ah=41h', 0xd, 0xa, 0
> msg48 db 0xd,0xa,'int 13h, ah=48h', 0xd, 0xa, 0
> ahmsg41 db 'ah = ', 0
> noext db ' no int 13h ext', 0
> apimsg db ' api = ', 0
> cfzro db 'CF=0=success.', 0xd, 0xa, 0
> cfone db 'CF=1=failed.', 0xd, 0xa, 0
> ahmsg48 db 'ah>0 is an error code. ah = ', 0
> sxmsg db 0xd, 0xa ,'# of sectors = ', 0
> tstamp db 0xd, 0xa ,'4/08/16 7:00 am', 0

;> i2xt db '0123456789ABCDEF'
> bsiz dw 0
;> sxbuf times 0x42 db 0 ;bufr for BIOS to fill

I'd assign this buffer outside see: mov bx,0x7e00 above
hex4ax: ;4 nibbles from ax
push ax
call hex2ah
pop ax
shl ax,8
hex2ah: ;2 digits of AH
mov al,ah
shr al,4 ;high part first
call hex1al
mov al,ah
hex1al: ;a single nibble from AL
push ax
and al,0x0f
cmp al,0x0a
jc hex1
add al,0x07
hex1:
add al,0x30
mov ah 0x0e
mov bh,0
int 0x10
pop ax
ret

;-------
my own routine:
it always uses ebx and need to be preceded by MOVZX ebx,ah
for AH only, but it terminates 'automated' w/o size info.

hexout:
mov eax,ebx
ror eax,4 ;start with MSD
and al,0x0f
cmp al,0x0a
jc itsnum ;skip next instruction
add al,0x07 ;if > 9 then 'A..F'
itsnum:
add al,0x30 ;make it ASCII
call charout ;I don't use the BIOS here
shl ebx,4
jnz hexout ;until nothing else to print
ret
;--------

wolfgang kern

unread,
Apr 10, 2016, 7:31:48 AM4/10/16
to

I wrote:

and forgot to restore BX to 0x7e00 after the BIOS call !

> hex4ax: ;4 nibbles from ax
> push ax
> call hex2ah
> pop ax
> shl ax,8
> hex2ah: ;2 digits of AH
> mov al,ah
> shr al,4 ;high part first
> call hex1al
> mov al,ah
> hex1al: ;a single nibble from AL
> push ax
> and al,0x0f
> cmp al,0x0a
> jc hex1
> add al,0x07
> hex1:
> add al,0x30
> mov ah 0x0e
> mov bh,0
> int 0x10
* mov bx,0x7e00 ;restore buf-ptr
> pop ax
> ret

__
wolfgang

bilsch

unread,
Apr 13, 2016, 8:30:16 PM4/13/16
to
>> mov si,msg41
>> call print_string
>> mov bx,0x55aa
>> mov dl,0x80
>
> ; As Mike noted, DL is passed to your code.
> mov [saved_dl],dl
> ; and comment out the line you used above,
> ; being sure not to modify DL beforehand.


I don't understand this comment. Ralph Brown shows dl is a value that
you must provide when calling int13 ah=41 bx=55aa. dl can't contain the
drive number BEFORE I call int13. Also RBIL doesn't show dl as a
returned value.

Rod Pemberton

unread,
Apr 13, 2016, 10:08:43 PM4/13/16
to
7C00 start up code is called from Int 19h
with some entry values set in registers.
RBIL documents some of them.

7C00 entry values
http://www.delorie.com/djgpp/doc/rbinter/it/53/6.html

INT 19
http://www.delorie.com/djgpp/doc/rbinter/id/79/22.html

INT 18
http://www.delorie.com/djgpp/doc/rbinter/id/50/22.html

Additional values were added per later BIOS and drive
specifications and are not documented by RBIL. Later
specifications also changed boot order to include
INT 18h. None of that is in RBIL.

I don't recall where those are at the moment, perhaps
Phoenix EDD specifications or maybe IDE or maybe BIOS
Boot specification or maybe PCI specifications. I can
look up my old posts or this info if you need it.

Ben or someone else might recall.


Rod Pemberton

0 new messages