initial commit

This commit is contained in:
Frater 2026-01-08 10:26:52 +01:00
commit 08f66b73e1
10 changed files with 783 additions and 0 deletions

7
build.bat Normal file
View file

@ -0,0 +1,7 @@
@echo off
cd src
"c:\program files\NASM\nasm" -f bin boot.asm -o ..\build\bios.bin
cd ..
qemu-system-x86_64 -bios build/bios.bin -device loader,file=build/vgabios.bin,addr=0xC0000,force-raw=on -device isa-debugcon,chardev=myconsole -chardev stdio,id=myconsole -k be -display sdl

123
readme.md Normal file
View file

@ -0,0 +1,123 @@
# Custom BIOS / ROM Project
## Projet BIOS / ROM personnalisé
---
## 🇬🇧 English
### Overview
This project aims to design and implement a **PC-compatible BIOS/ROM from scratch**, primarily targeted for **QEMU emulation**, with a strong focus on **low-level PC architecture** and **x86 real-mode assembly programming**.
Rather than booting an operating system from disk, the PC starts **directly from the ROM** into a **minimalist graphical interface**, using **CGA High-Resolution Monochrome (640×200)** mode.
The project is intentionally educational, experimental, and minimalist.
---
### Objectives
- **Write a PC-compatible BIOS from scratch**
- No reuse of existing BIOS code
- Minimal but functional implementation
- Compatible with QEMU
- Target architecture: **8086 → 80486**
- **Boot directly from ROM**
- No DOS, no bootloader, no disk dependency
- Execution starts at reset vector (F000:FFF0)
- ROM is responsible for full system initialization
- **Provide a minimalist graphical interface**
- Graphics mode: **CGA High-Resolution Monochrome (640×200)**
- Direct video memory access
- Custom cursor and basic UI primitives
- **Deep understanding of PC architecture**
- CPU reset behavior
- Memory map (RAM, ROM, EBDA, IVT, BDA)
- Interrupt Vector Table
- Hardware initialization (PIC, PIT, keyboard, video)
- Real-mode execution constraints
- **Improve mastery of x86 assembly**
- Real-mode x86 assembly (8086-compatible)
- Performance-oriented routines
- Hardware-near programming
- Clear separation between hardware-specific drivers and core logic
---
### Non-Goals
- Full IBM BIOS compatibility
- Protected mode or modern CPUs
- Advanced OS services
- DOS or legacy OS support
This is a **learning and exploration project**, not a drop-in BIOS replacement.
---
## 🇫🇷 Français
### Présentation
Ce projet consiste à concevoir et développer un **BIOS/ROM PC compatible entièrement écrit “from scratch”**, destiné principalement à lémulation **QEMU**, avec pour objectif principal l**apprentissage approfondi de larchitecture PC** et de la **programmation assembleur x86 en mode réel**.
Au lieu de charger un système dexploitation depuis un disque, le PC **démarre directement depuis la ROM** vers une **interface graphique minimaliste**, utilisant le mode **CGA Haute Résolution Monochrome (640×200)**.
Le projet est volontairement **éducatif, expérimental et minimaliste**.
---
### Objectifs
- **Écrire un BIOS PC depuis zéro**
- Aucun code BIOS existant réutilisé
- Implémentation minimale mais fonctionnelle
- Compatible avec QEMU
- Architecture ciblée : **8086 jusquau 486**
- **Démarrer directement depuis la ROM**
- Sans DOS, sans bootloader, sans disque
- Exécution depuis le vecteur de reset (F000:FFF0)
- La ROM initialise entièrement la machine
- **Fournir une interface graphique minimaliste**
- Mode graphique : **CGA Hi-Res Monochrome (640×200)**
- Accès direct à la mémoire vidéo
- Curseur personnalisé et primitives graphiques simples
- **Approfondir la compréhension de larchitecture PC**
- Séquence de reset CPU
- Cartographie mémoire (RAM, ROM, EBDA, IVT, BDA)
- Table des vecteurs dinterruptions
- Initialisation matérielle (PIC, PIT, clavier, vidéo)
- Contraintes du mode réel
- **Perfectionner la maîtrise de lassembleur**
- Assembleur x86 en mode réel (compatible 8086)
- Routines optimisées pour la performance
- Programmation proche du matériel
- Séparation claire entre cœur du BIOS et drivers matériels
---
### Hors périmètre
- Compatibilité BIOS IBM complète
- Mode protégé ou CPU modernes
- Services système avancés
- Support DOS ou OS legacy
Ce projet est avant tout un **laboratoire dapprentissage et dexploration**, et non un BIOS de production.
---
### Status
🚧 **Work in progress**
Designed for experimentation, learning, and documentation.
---

3
run.bat Normal file
View file

@ -0,0 +1,3 @@
@echo off
qemu-system-x86_64 -bios build/bios.bin -device loader,file=build/vgabios.bin,addr=0xC0000,force-raw=on -device isa-debugcon,chardev=myconsole -chardev stdio,id=myconsole -k be -display sdl

29
src/bda.asm Normal file
View file

@ -0,0 +1,29 @@
;
; https://wiki.nox-rhea.org/back2root/ibm-pc-ms-dos/hardware/informations/bios_data_area
;
%define BDA_SEGMENT 0x0040 ; segment BDA
; custom var
%define BDA_INFO_MEM_SIZE 0x0014 ; Memory size in Kbytes
%define BDA_INFO_MEM_SEG BDA_INFO_MEM_SIZE+2 ; highest Memory Segment
%define BDA_MOUSE_BUFFER 0x0068 ; dword
%define BDA_MOUSE_IDX BDA_MOUSE_BUFFER+4 ; byte
%define BDA_MOUSE_X BDA_MOUSE_BUFFER+5 ; word
%define BDA_MOUSE_Y BDA_MOUSE_BUFFER+7 ; word
; video related information (compatible with VGA bios)
%define BDA_VIDEO_CURR_MODE 0x0049
%define BDA_VIDEO_COLUMNS 0x004A
%define BDA_VIDEO_BUF_SIZE 0x004C
%define BDA_VIDEO_OFS_PAGE 0x004E
; 40:50 8 words Cursor position of pages 1-8, high order byte=row low order byte=column; changing this data isn't reflected immediately on the display
; 40:60 byte Cursor ending (bottom) scan line (don't modify)
; 40:61 byte Cursor starting (top) scan line (don't modify)
; 40:62 byte Active display page number
; 40:63 word Base port address for active 6845 CRT controller 3B4h = mono, 3D4h = color
; 40:65 byte 6845 CRT mode control register value (port 3x8h) ; EGA/VGA values emulate those of the MDA/CGA
; 40:66 byte CGA current color palette mask setting (port 3d9h) ; EGA and VGA values emulate the CGA

261
src/boot.asm Normal file
View file

@ -0,0 +1,261 @@
;
;
;
;
BITS 16
org 0
;
; configuration du bios
;
%define DEBUG_PORT 0xe9 ; 0x402 ou 0xe9
%define VIDEO_SEG 0xB800 ; ou 0xB000
%define VIDEO_ATTR 0x07
; initial stack
%define STACK_SEG 0x0030
%define STACK_TOP 0x0100 ; choix commun en RAM basse (pile descend)
%define MEM_SEG_DEB 0x0800 ; 0x0800:0000 = 0x8000 (32KB) évite IVT/BDA + stack 7C00
%define MEM_SEG_END 0xA000 ; 0xA000:0000 = 0xA0000 (début zone vidéo)
%define MEM_SEG_STEP 0x0040 ; 1KB = 0x400 bytes = 0x40 paragraphs
; definition du BDA
%include ".\bda.asm"
jmp reset
%include ".\drivers\debug.asm"
%include ".\drivers\gfx_cgam.asm"
%include ".\drivers\mouse_ps2.asm"
err_novga db 'No VGA Card BIOS',0
err_vganok db 'VGA Not Initialized',0
err_end db 'code completed successfully',0
reset:
cli
; il n'existe aucun 'stack' par défaut
mov ax, STACK_SEG
mov ss, ax
mov sp, STACK_TOP ; haut du segment, mot-aligné
; detection de la mémoire totale
call setup_ram
; modification du stack en "haut" de la RAM
mov ax, dx
sub ax, 0x0400 ; réserver les 16 dernier KB
mov ss, ax
mov sp, 0x1000 ; sommet de pile
; installé une table d'interruption "dummy"
call setup_ivt
sti
; load other Rom
call setup_load_rom
; on vérifie que le BIOS VGA a installer l'INT 10h
call setup_check_vga
call gfx_init
call mouse_init
mov ax,cs
mov ds,ax
mov si, err_end
call debug_puts
endless: nop
jmp endless
; ---------------------------------------------------------------------------
; Détection les ROM supplementaires
; Teste la RAM de 0xC000 jusquà 0xE000, par pas de 2KB.
;
; Si une ROM est détectée, le code de la ROM sera appelé.
; ---------------------------------------------------------------------------
setup_check_vga:
; vérification de l'offset 0x0000:0x0040 qui DOIT etre différent de @default_isr
xor ax,ax
mov es,ax
; Lire INT10: offset+segment depuis 0000:0040
mov bx, [es:0x0040] ; BX = offset INT10
mov cx, [es:0x0042] ; CX = segment INT10
mov ax, cs
; Comparer avec default_isr (offset) et CS (segment)
mov dx, default_isr ; DX = offset default_isr (dans notre CS)
cmp bx, dx
jne .init_ok ; interrupt different
cmp cx, ax
jne .init_ok
; no VGA init
mov ax,cs
mov ds,ax
mov si, err_vganok
call debug_puts
.init_ok:
ret
; ---------------------------------------------------------------------------
; Détection les ROM supplementaires
; Teste la RAM de 0xC000 jusquà 0xE000, par pas de 2KB.
;
; Si une ROM est détectée, le code de la ROM sera appelé.
; ---------------------------------------------------------------------------
setup_load_rom:
mov bx, 0xC000
mov al,'*'
.scanloop:
mov ds, bx
cmp word[0x0000],0xAA55 ; ROM signature
jne .norom
; Debug
mov al,'.'
call debug_putc
; appel le code (en 0x0003) de la ROM
push cs
push word .norom ; address de retour
push ds
push 0x0003
retf ; call far ds:0x0003
.norom:
add bx, 0x80 ; add 2kb
cmp bx, 0xE000
jbe .scanloop
cmp al,'.'
je .end
; no rom found :
mov bx,cs
mov ds,bx
mov si, err_novga
call debug_puts
.end:
ret
; ---------------------------------------------------------------------------
; Détection RAM conventionnelle (8088, BIOS maison) — sans BIOS
; Teste la RAM de MEM_SEG_DEB jusquà 0xA000 (640KB), par pas de 1KB.
; Méthode: sauvegarde 1 mot, écrit 2 patterns, relit, restaure.
;
; Résultats:
; AX = taille détectée en Ko (KB) à partir de 0 jusquau “top conventionnel”
; DX = top_segment (segment du premier Ko NON valide) (optionnel)
;
; Précautions:
; - Ne testez PAS une zone où se trouve votre pile / variables.
; - Si votre pile est à 0000:7C00 (phys 0x7C00), commencez au moins à 0x0800.
; - Ne testez pas au-delà de 0xA000 (début VGA/vidéo).
; ---------------------------------------------------------------------------
setup_ram:
xor ax, ax ; AX = compteur KB trouvés
mov dx, MEM_SEG_DEB ; DX = segment courant testé
mov cx, MEM_SEG_END ; CX = segment fin (exclu)
xor di, di ; tester au début du bloc 1KB: ES:0000
.loop_seg:
cmp dx, cx
jae .done
mov es, dx
; sauvegarder le mot existant
mov bx, [es:di]
; pattern 1
mov word [es:di], 0x55AA
cmp word [es:di], 0x55AA
jne .fail_restore
; pattern 2 (inverse)
mov word [es:di], 0xAA55
cmp word [es:di], 0xAA55
jne .fail_restore
; restaurer
mov [es:di], bx
; OK: avancer d1KB
inc ax ; +1KB valide
add dx, MEM_SEG_STEP
jmp .loop_seg
.fail_restore:
; restaurer avant de sortir
mov [es:di], bx
.done:
; AX contient le nb de KB valides *à partir de MEM_SEG_DEB*.
; Si vous voulez une taille “depuis 0KB”, ajoutez MEM_SEG_DEB*16/1024 = MEM_SEG_DEB/64.
;
; base_kb = MEM_SEG_DEB / 0x0040 (car 1KB = 0x40 segments)
mov bx, MEM_SEG_DEB
shr bx, 6 ; /64 = /0x40 => KB de base
add ax, bx ; AX = taille conventionnelle totale en KB (approx. 0..640)
; DX = segment du premier bloc NON valide (top)
; DX est déjà positionné (segment courant)
; stocker l'information dans le BDA
mov bx, BDA_SEGMENT
mov ds, bx
mov [BDA_INFO_MEM_SIZE], ax
mov [BDA_INFO_MEM_SEG],dx
ret
; ---------------------------------------------------------------------------
; IVT install (8088, mode réel) - remplit les 256 vecteurs avec un handler IRET
; Hypothèses:
; - code en ROM (CS typiquement = 0xF000), handler réside dans ce même segment
; - interruptions désactivées (CLI) pendant l'installation
; ---------------------------------------------------------------------------
setup_ivt:
; installation de la table d'interrupts
xor ax, ax
mov es, ax ; ES = 0000h -> base IVT
xor di, di ; DI = 0000h -> offset IVT
mov ax, default_isr ; offset du handler
mov dx, cs ; segment du handler (ROM)
mov cx, 256 ; 256 vecteurs
.fill:
stosw ; write offset (AX) -> [ES:DI], DI += 2
xchg ax, dx ; AX = segment, DX = offset
stosw ; write segment (AX) -> [ES:DI], DI += 2
xchg ax, dx ; AX = offset, DX = segment
loop .fill
; fin de la table d'interrupt
ret
; ---------------------------------------------------------------------------
; Handler par défaut: fait juste IRET
; IMPORTANT: doit être FAR (appelé par le CPU via IVT), et terminer par IRET
; ---------------------------------------------------------------------------
default_isr:
iret
; ------------------------------------------------------------------
; Padding jusqu'au reset vector
; ------------------------------------------------------------------
times 0xFFF0 - ($ - $$) db 0xFF
; ------------------------------------------------------------------
; RESET VECTOR (exécuté par le CPU)
; ------------------------------------------------------------------
reset_vector:
jmp far 0xF000:reset
builddate db '06/01/2026',0

20
src/drivers/debug.asm Normal file
View file

@ -0,0 +1,20 @@
debug_puts:
.next:
lodsb ; AL = *SI++
test al, al
jz .done
mov dx, DEBUG_PORT
out dx, al
jmp .next
.done:
ret
debug_putc:
.next:
mov dx, DEBUG_PORT
out dx, al
ret

62
src/drivers/gfx.asm Normal file
View file

@ -0,0 +1,62 @@
%define GFX_MODE 0x06 ; MCGA HiRes (B/W)
%define GFX_WIDTH 640
%define GFX_HEIGHT 200
gfx_init:
; init graphics mode
mov ah, 0x00 ; AH=00h set video mode
mov al, GFX_MODE
int 0x10
call gfx_background
ret
; PutPixel(x=cx, y=dx, color=al) sur page bh
gfx_putpixel:
mov ah, 0Ch
;mov al, 0Fh ; couleur (0..15)
;mov bh, 00h ; page
;mov cx, 100 ; x
;mov dx, 50 ; y
int 10h
ret
gfx_background:
mov ax,VIDEO_SEG
mov es,ax
xor di,di
mov ax,0xaaaa
mov cx,0x1000
rep stosw
mov ax,0x5555
mov cx,0x1000 ; 640/8/2
rep stosw
ret
; gfx_background:
xor dx,dx ; Y
.loop_y:
test dl,1
jnz .pair
mov cx,0 ; X = 0
jmp .loop_x
.pair:
mov cx,1 ; X = 1
.loop_x:
mov ah,0x0c ; putpixel
mov al,0x0f ; blanc
xor bh,bh
int 10h
add cx,2 ; x=x+2
cmp cx,GFX_WIDTH
jb .loop_x
inc dx
cmp dx,GFX_HEIGHT
jb .loop_y
ret

45
src/drivers/gfx_cgam.asm Normal file
View file

@ -0,0 +1,45 @@
;
; graphics drivers for CGA 640x200x2
;
%define GFX_MODE 0x06 ; MCGA HiRes (B/W)
%define GFX_WIDTH 640
%define GFX_HEIGHT 200
%define CGA_STRIDE 80
%define CGA_ODD_BANK 0x2000
%define BG16_SIZE 48
; ce mode est entrelacé, un bit/pixel, 8 pixels par octet
gfx_init:
; init graphics mode
mov ah, 0x00 ; AH=00h set video mode
mov al, GFX_MODE
int 0x10
call gfx_background
ret
; PutPixel(x=cx, y=dx, color=al) sur page bh
gfx_putpixel:
mov ah, 0Ch
;mov al, 0Fh ; couleur (0..15)
;mov bh, 00h ; page
;mov cx, 100 ; x
;mov dx, 50 ; y
int 10h
ret
; draw background pattern
gfx_background:
mov ax,VIDEO_SEG
mov es,ax
xor di,di
mov ax,0xaaaa
mov cx,0x1000
rep stosw
mov ax,0x5555
mov cx,0x1000 ; 640/8/2
rep stosw
ret

127
src/drivers/mouse_ps2.asm Normal file
View file

@ -0,0 +1,127 @@
;
; controlleur de souris via le i8042 (PC Classique)
;
%define PS2_PORT_BUFFER 0x60
%define PS2_PORT_CTRL 0x64
%define MOUSE_CMD_RESET 0xff
%define MOUSE_CMD_DEFAULT 0xf6
%define MOUSE_EN_STREAM 0xf4
%define MOUSE_DIS_STREAM 0xf5
mouse_arrow:
dw 1001111111111111b ; 0x9FFF
dw 1000111111111111b ; 0x8FFF
dw 1000011111111111b ; 0x87FF
dw 1000001111111111b ; 0x83FF
dw 1000000111111111b ; 0x81FF
dw 1000000011111111b ; 0x80FF
dw 1000000001111111b ; 0x807F
dw 1000000000111111b ; 0x803F
dw 1000000000011111b ; 0x801F
dw 1000000000001111b ; 0x800F
dw 1000000011111111b ; 0x80FF
dw 1000100001111111b ; 0x887F
dw 1001100001111111b ; 0x987F
dw 1111110000111111b ; 0xFC3F
dw 1111110000111111b ; 0xFC3F
dw 1111111000111111b ; 0xFE3F
dw 0000000000000000b ; 0x0000
dw 0010000000000000b ; 0x2000
dw 0011000000000000b ; 0x3000
dw 0011100000000000b ; 0x3800
dw 0011110000000000b ; 0x3C00
dw 0011111000000000b ; 0x3E00
dw 0011111100000000b ; 0x3F00
dw 0011111110000000b ; 0x3F80
dw 0011111111000000b ; 0x3FC0
dw 0011111000000000b ; 0x3E00
dw 0011011000000000b ; 0x3600
dw 0010001100000000b ; 0x2300
dw 0000001100000000b ; 0x0300
dw 0000000110000000b ; 0x0180
dw 0000000110000000b ; 0x0180
dw 0000000000000000b ; 0x0000
mouse_init:
; Activer le port souris
mov ax, BDA_SEGMENT
mov ds,ax
; effacer les variables du drivers
mov [BDA_MOUSE_IDX],0
mov dword [BDA_MOUSE_BUFFER],0
mov al, 0xA8
out PS2_PORT_CTRL, al
; Lire le command byte
mov al, 0x20
out PS2_PORT_CTRL, al
in al, PS2_PORT_BUFFER
; Activer IRQ12 (bit 1)
or al, 0x02
; Réécrire le command byte
mov ah, al
mov al, PS2_PORT_BUFFER
out PS2_PORT_CTRL, al
mov al, ah
out PS2_PORT_BUFFER, al
ret
; envoyer une commande souris
mouse_sendcmd:
.wait:
in al, PS2_PORT_CTRL
test al, 2
jnz .wait
mov al, 0xD4
out PS2_PORT_CTRL, al
.wait2:
in al, PS2_PORT_CTRL
test al, 2
jnz .wait2
mov al, bl ; BL = commande souris
out PS2_PORT_BUFFER, al
ret
mouse_handler:
push ax
push bx
; use BDA segment
push ds
mov ax,BDA_SEGMENT
mov ds,ax
xor bx,bx
in al, PS2_PORT_BUFFER ; lire octet souris
mov [BDA_MOUSE_BUFFER + BDA_MOUSE_IDX], al
inc [BDA_MOUSE_IDX]
cmp [BDA_MOUSE_IDX], 3
jne .done
mov [BDA_MOUSE_IDX], 0
; decode buffer
mov al, [BDA_MOUSE_BUFFER+1]
cbw
add [BDA_MOUSE_X], ax
mov al, [BDA_MOUSE_BUFFER+2]
cbw
sub [BDA_MOUSE_Y], ax
.done:
mov al, 0x20
out 0xA0, al ; EOI PIC esclave
out 0x20, al ; EOI PIC maître
pop ds
pop bx
pop ax
iret

106
src/drivers/textmode.asm Normal file
View file

@ -0,0 +1,106 @@
;
; text mod functions
;
;
vid_seg dw VIDEO_SEG
cursor_ofs dw 0
txt_attr db VIDEO_ATTR
; -----------------------------------------------------------------------------
; cls : efface l'écran en remplissant txtmode_CELLS cellules par " " + ATTR
; -----------------------------------------------------------------------------
txtmode_clear:
mov ax,0xB800 ; select video segment
mov es,ax
xor ax,ax ; 0
; mov [cursor_ofs],ax ; reset 'virtual' cursor
mov di,ax
; default attr + space
mov ah, [txt_attr]
mov al, ' '
;
mov cx,2000 ; 25x80
rep stosw ; attr + char
ret
; -----------------------------------------------------------------------------
; puts : imprime une chaîne ASCIIZ (DS:SI) en VRAM (ES:cursor_ofs)
; - gère '\n' (LF=10) comme nouvelle ligne
; - ignore '\r' (CR=13)
; -----------------------------------------------------------------------------
txtmode_puts:
.next:
lodsb ; AL = *SI++
test al, al
jz .done
cmp al, 10 ; '\n'
je .lf
cmp al, 13 ; '\r'
je .next
call txtmode_putc
jmp .next
.lf:
call txtmode_newline
jmp .next
.done:
ret
; -----------------------------------------------------------------------------
; putc : imprime le caractère AL à la position du curseur logiciel
; -----------------------------------------------------------------------------
txtmode_putc:
push ax
push bx
push di
mov di, [cursor_ofs]
; écrire char + attr
mov ah, txt_attr
stosw ; écrit AX à ES:DI, puis DI += 2
; mettre à jour cursor_ofs
mov [cursor_ofs], di
; si fin d'écran, on “reboucle” au début (simple)
; (vous pouvez remplacer par un scroll si besoin)
mov bx, 4000 ; 80x25x2
cmp di, bx
jb .ok
mov word [cursor_ofs], 0
.ok:
pop di
pop bx
pop ax
ret
; -----------------------------------------------------------------------------
; newline : avance le curseur au début de la ligne suivante (80 colonnes)
; -----------------------------------------------------------------------------
txtmode_newline:
push ax
push dx
mov ax, [cursor_ofs] ; offset en octets
xor dx, dx
mov cx, 160 ; taille d'une ligne en octets = 160
div cx ; AX = ligne, DX = reste (offset dans la ligne)
inc ax ; ligne suivante
cmp ax, 25
jb .set
xor ax, ax ; si dépasse, revient en haut (simple)
.set:
mov cx, 160 ; cursor_ofs = ligne * 160
mul cx ; DX:AX = AX*CX ; ici AX suffit
mov [cursor_ofs], ax
pop dx
pop ax
ret