• Please review our updated Terms and Rules here

"Fun with BIOS" Programming Thread

This sucks... lmao! In any case, I do need to get some sleep, but thanks for your help and insights- I'm not exactly wonderful with disassembling x86 (which this project will help with ;)...). I'll mull it over more once I've gotten some sleep.

Neither this, nor the BIOS, are easy to code. But they're difficult in different ways...
 
This sucks... lmao! In any case, I do need to get some sleep, but thanks for your help and insights- I'm not exactly wonderful with disassembling x86 (which this project will help with ;)...). I'll mull it over more once I've gotten some sleep.

Neither this, nor the BIOS, are easy to code. But they're difficult in different ways...

I'm going to sleep too. But yeah, x86 binary encoding is pretty weird at first. In GRP opcodes like FF where there is a sub-opcode like we have here, the "reg" field of the addressing mode byte determines the sub-op. That field is made up of bits 5, 4, 3. So, for example if it's 16h, that bitfield is "010", or sub-op 2. If 1Eh, that makes it "011" or sub-op 3.

Then you can look it up here to see what those mean: http://www.mlsite.net/8086/#tbl_ext

A bit off-topic I know, but if you want to learn more about disassembling this is vital info.
 
Last edited:
Interesting video. I'm wondering if you also tried to boot using the original IBM 10/27/82 BIOS ? The only other difference that I can think of is that I am running an MDA video card, with an original green screen.

The problem with my test setup was the Monochrome Display Adapter Card. With a VGA card installed I have confirmed the same results with the Monitor program that you have described. FYI. Michael
 
I'm going to sleep too. But yeah, x86 binary encoding is pretty weird at first. In GRP opcodes like FF where there is a sub-opcode like we have here, the "reg" field of the addressing mode byte determines the sub-op. That field is made up of bits 5, 4, 3. So, for example if it's 16h, that bitfield is "010", or sub-op 2. If 1Eh, that makes it "011" or sub-op 3.

Then you can look it up here to see what those mean: http://www.mlsite.net/8086/#tbl_ext

A bit off-topic I know, but if you want to learn more about disassembling this is vital info.

Not off-topic at all. So basically, you're telling me that x86 continues to make no sense even at the binary level :p... I inferred that some time ago!

Joking aside, I don't think 8086 is that much of a mess... it reminds me of 65816 assembly (which also has a concept of 'banks'), but with more registers (65816 only has 3, and can switch between being an 8-bit and 16-bit processor by setting appropriate flags).

Chuck(G): If you're still reading this thread, have you ever done 65816 programming?

The problem with my test setup was the Monochrome Display Adapter Card. With a VGA card installed I have confirmed the same results with the Monitor program that you have described. FYI. Michael

I use int 0x10 for output to the screen- I haven't added logic to autodetect which video display adapter is being used yet.
 
I drop by this thread about once every three days or so to see what's going on.

I've studied the 65816 a bit, but never programmed it. It's one of those chips like the 6809--a "bridge" from the old dead-end 8-bit line to 16 bits. For that matter, the x86 is a "bridge" from the 8008 line. :)
 
The debugger hooks int 0x12 to return 1kB less of memory than actually exists... the debugger then allocates the top kB of memory for itself. The modified int 0x12 invocation plus 1 tells the debugger the start of its own allocated memory area, which solves the problem of having to store a pointer to the start of the memory area.

This works fine as long as your debugger is the only software reserving memory like this. However, there's a lot of software out there that does the same, DDO:s, harddisk controller BIOSes (such as the XTIDE Universal BIOS) not to mention some viruses. Basically any software that needs memory out of reach of the OS. If something hooks Int 12h after your debugger or changes the RAM count directly in the BDA your debugger won't find its own memory area.

Instead I suggest that you do something similar to the XTIDE Universal BIOS (see RamVars.asm). In short; change the count directly in the BDA during init and then scan for a signature on each invokation of the debugger.
 
This works fine as long as your debugger is the only software reserving memory like this. However, there's a lot of software out there that does the same, DDO:s, harddisk controller BIOSes (such as the XTIDE Universal BIOS) not to mention some viruses. Basically any software that needs memory out of reach of the OS. If something hooks Int 12h after your debugger or changes the RAM count directly in the BDA your debugger won't find its own memory area.

Instead I suggest that you do something similar to the XTIDE Universal BIOS (see RamVars.asm). In short; change the count directly in the BDA during init and then scan for a signature on each invokation of the debugger.

Yep, this would be the proper way to do it. This is what I do in NetDrive as well.
 
This works fine as long as your debugger is the only software reserving memory like this. However, there's a lot of software out there that does the same, DDO:s, harddisk controller BIOSes (such as the XTIDE Universal BIOS) not to mention some viruses. Basically any software that needs memory out of reach of the OS. If something hooks Int 12h after your debugger or changes the RAM count directly in the BDA your debugger won't find its own memory area.

Instead I suggest that you do something similar to the XTIDE Universal BIOS (see RamVars.asm). In short; change the count directly in the BDA during init and then scan for a signature on each invokation of the debugger.

I can see how your way is better (though using int 0x12 makes fewer assumptions for non-compatible clones).

A few nights ago, I realized that I cannot reliably allocate memory at all, due to the design of the debugger- it is meant to be allocated at any time*, regardless of whether DOS has booted, or the BIOS is running, or if there is even a valid OS at all (after the debugger's init routine has run, of course). The only assumption the debugger can make at invocation is that the caller's stack is valid and has some free space (even within BIOS, this is okay).

Scanning for a signature would work, if I could reliably assume the BIOS wouldn't do memory testing up to the "true" memory total (and not to the total specified in the BDA) after initializing options ROMs. The Generic XT BIOS- and most likely other ERSO clones (haven't checked yet)- shows that I can't that make assumption, as it tests all memory, disregarding the value in the BDA, after option ROM init. Because of this, when I call the debugger using a keystroke, my data area is not guaranteed to be preserved after the ROM routine init depending on the BIOS.

The debugger can be made completely ROMable at the expense of some convenient features, such as automatically storing breakpoints and patched addresses in a table- this type of data needs to be preserved between invocations, since the debugger exits prior to each instruction execution.

*I make no guarantees about trying to trace hardware interrupts other than:
1. Invoking the debugger itself within a hardware interrupt won't crash the machine.
2. The hardware interrupt will at least return, but may not have executed correctly.
 
Last edited:
Instead I suggest that you do something similar to the XTIDE Universal BIOS (see RamVars.asm). In short; change the count directly in the BDA during init and then scan for a signature on each invokation of the debugger.

How does XTIDE handle the case where variables are overwritten after Option ROM init, but before POST is complete?

Have you tried XT-IDE with the Generic XT BIOS. If not, I suspect it will trash your initialized variables- including the signature- since the Generic XT BIOS waits until after Option ROM init is complete to do a memory test beyond 16k. And according to the listings, it does not use int 0x12 to get the memory size beforehand!

In my debugger, the three pieces of data that must remain the same after Option ROM init are: the breakpoint table, the signature, and the old interrupt vector locations.* (Other RAM data in my debugger, such as a string buffer, can change between invocations without problems). From what I can tell, all you need in XT-IDE is that the signature not be modified.

Guaranteeing that my RAM variables won't be overwritten before POST ends is my main problem.

*This also brings up an interesting point- is there significant speed loss from invoking the debugger via interrupt and scanning for its data area for a signature every time?
 
Sorry for the lack of progress this weekend. I took a much-needed break from programming due to graduate school and just other events. I'll be back on creating the debugger this week. However, I would greatly appreciate (read: I'm desperate) for any feedback addressing the problems I've discussed in my previous two posts in this thread (namely, guaranteeing the values of RAM variables during POST after Option ROM init) :D.
 
Just wondering if a hardware solution is beyond your vision. You could always put a bit of RAM sharing the memory space with your PROM. Since you don't have a write line in a PROM socket, you could appropriate an address line to serve that purpose. Or you could use the old National Semi "phantom" scheme--a sequence of 64 specific addresses must precede memory access--used in the "No Slot Clock" back in the day.

At least that way, you wouldn't have to worry about code you know nothing about doing strange things.
 
Just wondering if a hardware solution is beyond your vision. You could always put a bit of RAM sharing the memory space with your PROM. Since you don't have a write line in a PROM socket, you could appropriate an address line to serve that purpose. Or you could use the old National Semi "phantom" scheme--a sequence of 64 specific addresses must precede memory access--used in the "No Slot Clock" back in the day.

At least that way, you wouldn't have to worry about code you know nothing about doing strange things.

Well, no... a hardware solution doesn't have to be beyond my vision, provided the pinout of my design is JEDEC EPROM-compatible, or 2364-compatible. A TTL static RAM chip would probably be sufficient. But I meant the debugger to be a lead-up to working on BIOS (after a minimal subset of functionality is complete), not a whole separate project.

And Bochs has provisions for RAM images as well.

XT-IDE seemed to have been able to do what I'm trying to accomplish, somehow... but I just don't see how.
 
Howso? I have only the source for Hargle's BIOS here, but there doesn't appear to be any unusual use of RAM.

And if you're not fussy, there's always serial RAM.
 
Howso? I have only the source for Hargle's BIOS here, but there doesn't appear to be any unusual use of RAM.
Does Hargle's BIOS install a signature into RAM* during Option ROM init and after decrementing the RAM total in the BDA? If I understand the Generic XT BIOS listings correctly, that signature (and any RAM variables that were initialized during Option ROM init) will be overwritten. The BIOS therefore won't be able to find it's own data area.

The below code comes from a copy of the Generic XT BIOS listings (2008- it is an outdated copy, FWIW). Compared to IBM's BIOS, the POST hardware checks are done in a considerably different order. When I get my hands on a listing with address offsets, I'll document them and compare. In the meantime, this is approximately lines 700 to 950. Notice that @@find_rom is executed before @@mem_count.**:
Code:
@@com_done:
	mov	ax, di				; Get serial interface count
	or	[ds:11h], al			;   equipment flag

	mov	cx, 100				; Check for game port 100 times
	mov	dx, 201h
@@check_game:
	in	al, dx				; Anything there?
	cmp	al, 0FFh
	jne	@@found_game			; Yep, game port is present
	dec	cx				; Otherwise keep polling
	jcxz	@@game_done			; Countdown is zero; no game port
	jmp	@@check_game
@@found_game:
	or	[byte ds:11h], 00010000b	; Set flag in equipment word
@@game_done:

	call	fpu_check			; check for FPU

	mov	dx, 0C000h			; ROM segment start

	mov	bx, [ds:72h]			; Save warm boot flag
	push	bx				;   (Some ROM inits may trash it)
	push	ds

ifdef	TURBO_BOOT				; if defined enable turbo speed at bootup
	in	al, 61h 			; Read equipment flags
	xor	al, 00001100b			;   toggle speed
	out	61h, al 			; Write new flags back
endif

@@find_rom:
	mov	ds, dx				; Load ROM segment
	xor	bx, bx				;   ID offset
	mov	ax, [bx]			; Read the ROM ID
	cmp	ax, 0AA55h
	jnz	@@next_rom			;   not valid ROM
	mov	ax, 40h
	mov	es, ax
	mov	ah, 0
	mov	al, [bx+2]			; Get ROM size (bytes * 512)
	mov	cl, 5
	shl	ax, cl				; Now ROM size in segments
	add	dx, ax				;   add base segment
	mov	cl, 4
	shl	ax, cl				; ROM address in bytes
	mov	cx, ax				;   checksum requires cx
	call	checksum_entry			; Find ROM checksum
	jnz	@@bad_rom			;   bad ROM
	push	dx
	mov	[word es:67h], 3		; Offset for ROM being setup
	mov	[es:69h], ds			; Segment for ROM being setup
	call	[dword es:67h]			;   call ROM initialization
	pop	dx
	jmp	short @@continue_rom

@@bad_rom:
	or	[byte es:15h], error_rom	; ROM present, bad checksum

@@next_rom:
	add	dx, 80h 			; Segment for next ROM

@@continue_rom:
	cmp	dx, 0F600h			; End of ROM space?
	jl	@@find_rom			;  no, continue

	pop	ds
	pop	bx
	mov	[ds:72h], bx			; Restore warm boot flag

	in	al, 21h 			; Read 8259 interrupt mask
	and	al, 10111100b			;   enable IRQ (0, 1, 6) ints
	out	21h, al 			; (tod_clock, key, floppy_disk)

	mov	ax, 40h				; Test for EGA/VGA
	mov	es, ax
	mov	ah, 12h
	mov	bx, 0FF10h
	int	10h				; Video Get EGA Info
	cmp	bh, 0FFh			; If EGA or later present BH != FFh
	je	@@not_ega
	and	[byte es:10h], 11001111b	; Set video flag in equipment list to EGA/VGA
@@not_ega:
	push	es
	call	video_mem_test			; Do video memory test
	pop	es

	mov	ah, 1
	mov	ch, 0F0h
	int	10h				; Set cursor type
	call	clear_screen			;   clear display
	push	cs
	pop	ds

	test	[byte es:10h], 1		; Floppy disk present?
	jz	@@skip_config			;   no
	cmp	[word es:72h], 1234h		; Bios setup before?
	jne	@@config			;   no
@@skip_config:
	jmp	@@reset				; Else skip memory check

@@config:
	mov	si, offset str_banner
	call	color_print
	mov	si, offset str_banner_2
	call	print

	test	[byte es:15h], 11111111b	; Any errors so far?
	jz	@@no_errors			;   no, skip

	mov	ax, 0300h
print_error:
	call	locate
	mov	si, offset str_error
	call	print				; Print string
	mov	al, [es:15h]			;   get error number
	call	number				;   print hex value
	call	print				;   print prompt
	mov	bl, 2				;   long beep
	call	beep
	call	get_key				; Wait for keypress
	push	ax				;   save answer
	call	out_char			;   echo answer
	pop	ax				;   get  answer
	cmp	al, 'Y' 			; Was it "Y"?
	jz	@@no_errors			;   ok, continue
	cmp	al, 'y' 			; Was it "y"?
	jz	@@no_errors			;   ok, continue
	jmpf	0F000h, cold_boot		; Else cold reset

@@no_errors:
	mov	ax, 0300h			; Where to move cursor
	call	locate				; Position cursor
	call	display_cpu			; Display CPU type

	mov	si, offset str_mono		; Assume mono video
	mov	ax, 0407h
	call	locate
	mov	al, [es:49h]			; Get CRT mode
	cmp	al, 7				; Is it mono?
	jz	@@display_video			; Yes
	mov	al, [es:10h]			; Check equipment word
	and	al, 00110000b			; Is it EGA/VGA?
	jnz	@@is_cga			; No, we have CGA
	mov	si, offset str_ega_vga		; Otherwise we have EGA/VGA
	jmp	short @@display_video
@@is_cga:
	mov	si, offset str_cga
@@display_video:
	call	print				; Print video adapter present

	mov	bx, 0507h
	mov	al, [es:11h]			; Get equipment byte
	push	ax
	mov	cl, 6
	ror	al, cl
	and	al, 3				; Number of LPT ports
	jz	@@display_com_ports		; Skip if none
	mov	bp, 8
	mov	si, offset str_parallel
	call	formatted_ascii_output		; Formatted ASCII output

@@display_com_ports:
	pop	ax
	push	ax
	mov	si, offset str_serial
	ror	al, 1				; Check for COM ports
	and	al, 3
	jz	@@display_game_port		; Skip if none
	xor	bp, bp
	call	formatted_ascii_output		; Formatted ASCII output

@@display_game_port:
	pop	ax				; Equipment byte restore
	mov	si, offset str_game
	test	al, 00010000b			; Check for game port
	jz	@@display_timer			; Skip if none
	mov	ax, bx
	call	locate				; Position cursor
	call	print				;   and print string
	inc	bh				;   scroll line

@@display_timer:
	call	timer_check			; Check for timer device
	jb	@@finish_device_list		; Skip if none
	mov	ax, bx
	call	locate				; Position cursor
	inc	bh
	mov	si, offset str_timer
	call	print

@@finish_device_list:
	dec	bh
	mov	bl, 7
	call	fix_last_line_char

	inc	bh
	inc	bh
	xor	bl, bl
	mov	ax, bx				; Where to position cursor
	call	locate				;   position cursor
	mov	si, offset str_ram_test		; Memory size string
	call	print				;   print string

	push	es
	mov	bp, [es:13h]			; Memory size (1 K blocks)
	dec	bp
	dec	bp
	mov	si, 2
	mov	dx, si
	mov	ax, 80h
	mov	es, ax

	add	bl, 0Dh
	push	bx
@@mem_count:
	pop	ax				; Cursory check of memory
	push	ax

	mov	cx, es

	cmp	bp, 1				; Always show final memory size
	je	@@last_time

	test	cx, 0000000111111111b		; Only print memoy size every 8K
	jz	@@show_mem_size

	xor	ch, ch				; If ch is cleared memory size isn't printed
@@show_mem_size:
	dec	dx
@@last_time:
	call	locate				; Position cursor
	call	print_mem_kb			; Print size in K
	inc	dx

	call	mem_test			; Memory check es:0 - es:0400
	jb	@@bad_ram			;   bad RAM found  (How ???)

	dec	bp
	jnz	@@mem_count
	pop	bx
	pop	es

	call	boot_basic			; Boot BASIC if space pressed

@@reset:
	mov	bl, 1				; Do a warm boot
	call	beep				;   short beep
	call	clear_screen			;   clear display
	mov	ax, 40h
	mov	es, ax
	mov	[word es:72h], 1234h		; Show cold start done

	mov	ah, 1
	mov	cx, 0B0Ch			; Set underline cursor mono
	cmp	[byte es:49h], 7		; Get CRT mode
	jz	@@is_mono			;   monochrome
	mov	cx, 607h			; Set underline cursor color
@@is_mono:
	int	10h				; Set the correct cursor shape

	int	19h				; Boot the machine

If XT-IDE boots correctly and RAM variables are preserved... I want to know how I can "set my global variables once and not worry about them being overwritten by another 'context'/'thread'."

On that note: Did I explain my issue poorly? If so, what did you think my problem was based on previous posts? What could I have done better to explain?


And if you're not fussy, there's always serial RAM.
The whole AT2XT fiasco dampened my desire to work with microcontrollers for a time to come, if that's what you're implying.


*Above the total reported RAM, of course
**Memory sizing/zeroing is done at about line 400, just after DMA initialization.
 
Well, the "1234h" in 00472h is standard for the "three-key salute" restart, versus the "big red switch" restart. But 472 is a BIOS reserved location. I'm not sure if that's what you're asking, however.

I wasn't suggesting use of an MCU, just that a serial RAM is very compact and can be easily programmed using only a couple of lines.
 
I just read the Generic XT BIOS listings more closely, and I realize that I was wrong about how the memory test is conducted. Although the memory test occurs after Option ROM init, it DOES look at the value stored in the BDA before conducting a memory test.

So yea, the previous few pages of my rambling was for naught, and definitely reduced the SNR of this thread.
 
Last edited:
I've been away for a while and I haven't had time to read this whole thread just skimmed it. So forgive me if I overlooked something. :)

Here is a basic rundown of how memory is handled in the "generic" PC/XT BIOS (which btw is up to version 2.5 now, 2012 :D)

save warm boot flag (0040:0072)
read/write pattern test first 2 bytes every 16K to determine conventional memory size (all memory is cleared at this time)
test 0000:0000 to 0040:0000 (interrupt vector table)
restore warm boot flag
store memory size at 0040:0013 (BIOS data area)
test bios checksum
look for rom basic
save warm boot flag (some expansion roms overwrite it)
initialize expansion/option roms
restore warm boot flag
if warm boot flag is true, boot basic/OS
get memory size from 0040:0013 (expansion rom could have changed this value to reserve memory for itself at the "top" of conventional memory)
test memory from 0080:0000 to size (count is displayed while testing)
boot basic/OS

A couple of things to note:
1. 0040:0000 to 0079:FFFF is never tested or overwritten beyond the initial test. This is because the bios data area and stack is here.
2. The only way for an expansion rom to properly reserve memory is to decrease the value in 0040:0013. The BIOS never calls int 12 itself; it reads the size directly from 0040:0013. During the final memory test, memory above the value in 0040:0013 will not be tested and thus not overwritten. So you can use that as ram for your expansion rom. Keep in mind multiple expansion roms may be using this technique so you should not assume the rest of conventional memory is yours.
 
Well, to bring you up to speed Plasma (wb, btw :p):

  • I want to write a BIOS.
  • I started writing it in C/Assembly before I realized that a C compiler can't produce tight-enough code in the ISRs (it will automatically generate code to preserve all registers, requiring stack manipulation to change register values during return from an ISR).
  • I then spent a while trying to devise a build structure for my BIOS, so that one can add/remove features (i.e. dual floppy boot) by editing a config file.
  • Part of my plan for my BIOS was to make use of U28 on the IBM PC and some XT clones. My first ideas were to extend the size of the BIOS to 16k or write a ROM monitor/debugger, among other ideas I listed somewhere in this thread. I chose the debugger.
  • Another part of my plan for BIOS/the Option ROM was to make it run successfully on semi-compatible clones such as the Tandy 1000/2000. This is why I used INT 0x10/0x12. Though the BDA might be consistent even for semi-compatible clones.
  • Writing the debugger had it's own set of issues, which led to the previous few pages of rambling. Until I consulted the listings again and realized everything you said in your post is correct (I didn't see your post before I edited mine).
  • I'm writing this post bringing you up to speed. :p

That being said, you got the gist of my concern from the past few pages.
 
Last edited:
[*]I started writing it in C/Assembly before I realized that a C compiler can't produce tight-enough code in the ISRs (it will automatically generate code to preserve all registers, requiring stack manipulation to change register values during return from an ISR).[/LIST]

Couldn't you just not declare the function as an interrupt? Just make it a void. In Turbo C++ for example this is all you get for the prolog/epilog:

Code:
   ;	
   ;	void far example_isr() {
   ;	
	assume	cs:_TEXT
_example_isr	proc	far
	push	bp
	mov	bp,sp
   ;	
   ;	}
   ;	
	pop	bp
	ret	
_example_isr	endp

Then you can just make it return properly by using this as a skeleton function for all ISRs:

Code:
void far my_isr() {

    //all of your handler code here

    asm { //make sure we avoid the compiler's epilog!!
        pop bp
        iret
    }
}

Then the compiler only wastes 2 bytes of opcodes per function with it's own epilog that never ends up getting touched. You would of course have to be careful when a certain BIOS call is expected to not touch certain registers, and look at the assembly listing the compiler makes for those... and push/pop appropriately with some inline asm. Kind of a headache, yes.
 
Yes, that would work. Additionally WATCOM has provisions to define a custom calling convention and which registers need to be preserved. I'm not sure how I feel about making the BIOS so intricately connected with compiler choice, however.

In light of what you said, and the code I uploaded some time ago, it is feasible to write a BIOS in C, and I suppose that's what WATCOM's extra features are there for; to be used when needed. At this point, I'm not even sure if I can write the BIOS in assembly without using a linker capable of positioning at absolute offsets (which Microsoft's Linker cannot do, but WATCOM's linker can), but this is because of the way source files in my project need to be divided up.
 
Back
Top