Table of Content
Experiment and Programming Environment
Refer to the bootstrap tutorial for the experiment and programming environment, and for how to compile and run the programs here.
Accessing I/O Device Controllers
There are three methods to access memory and registers in I/O device controllers, i.e.,
- via I/O ports,
- via memory mapping (i.e., memory-mapped I/O), and
- via hybrid method (i.e., via both in the above)
For examples using the I/O port method, please check out the examples in Section I/O Schemes. For the example using memory-mapped I/O, see the display example.
Example 1 Displaying Text via Accessing VGA Memory
;
; disp0.asm
;
; VGA
; https://en.wikipedia.org/wiki/VGA-compatible_text_mode
; https://en.wikipedia.org/wiki/Video_Graphics_Array#Color_palette
[org 0x7c00]
mov ax, 0xb800
mov es, ax
mov byte [es:0], 'H'
mov byte [es:1], 0x01
mov byte [es:2], 'e'
mov byte [es:3], 0x02
mov byte [es:4], 'l'
mov byte [es:5], 0x03
mov byte [es:6], 'l'
mov byte [es:7], 0x04
mov byte [es:8], 'o'
mov byte [es:9], 0x05
mov byte [es:10], ','
mov byte [es:11], 0x06
mov byte [es:12], 'W'
mov byte [es:13], 0x17
mov byte [es:14], 'o'
mov byte [es:15], 0x28
mov byte [es:16], 'r'
mov byte [es:17], 0x39
mov byte [es:18], 'l'
mov byte [es:19], 0x4a
mov byte [es:20], 'd'
mov byte [es:21], 0x5b
mov byte [es:22], '!'
mov byte [es:23], 0x6c
jmp $
times 510-($-$$) db 0
dw 0xaa55
I/O Schemes
We divide the pattern to access the device controller into 3 I/O schemes,
- polled I/O (or, the busy-waiting I/O method)
- interrupted I/O, and
- Direct Memory Access (DMA) We sometimes refer the first two as Programmed I/O since we must program to read or write every byte of data via CPU instructions. In contrast to Programmed I/O, once we program a device controller to use DMA, the device controller transfers to and from the main memory directly without involvement of the CPU.
Here we present examples for Programmed I/O. These examples use the subroutines defined in three other .asm files, print_line.asm, print_space.asm, and print_byte.asm whose source code we list below examples.
Example 2 Polled I/O for PS/2 Keyboard
;
; kbdpoll0.asm
;
; http://www-ug.eecg.toronto.edu/msl/nios_devices/datasheets/PS2%20Keyboard%20Protocol.htm
; http://bochs.sourceforge.net/techspec/PORTS.LST
; disable keyboard interrupt
mov al, 0x02
out 21h, al
mov cx, 0
KBD_READ:
push cx
LOOP:
; query keybard status
in al, 0x64
mov cl, al
and al, 0x01
jz LOOP
; read scan code
in al, 0x60
; print scan code
; for PS/2 scan code table, see
; http://www.vetra.com/scancodes.html
mov cl, al
call print_byte
call print_space
pop cx
inc cx
and cx, 0x01
jnz KBD_READ
call print_line
jmp KBD_READ
jmp $
%include "print_byte.asm"
%include "print_space.asm"
%include "print_line.asm"
times 510-($-$$) db 0
dw 0xaa55
Example 3 Interrupted I/O for PS/2 Keyboard
;
; kbdint0.asm
;
[org 0x7c00]
; Keybaord interrupt is maskable. To Intel 8259 Programmable Interrupt
; Controller, the IRQ number of the keyboard interrupt is 1, which
; means if we were to mask the interrupt, we set the 1st bit of the
; mask (a byte) as 1, i.e, 0x02 (0000 0010 in binary).
;
; For interrupt request number assignment on PC, see
; https://en.wikibooks.org/wiki/X86_Assembly/Programmable_Interrupt_Controller
;
; Uncomment the following two statements, and run the code. Does
; the keyboard interrupt till work? why?
;
; mov al, 0x02
; out 21h, al
jmp START
KBD_ISR:
push ax
push cx
; read scan code
in al, 0x60
mov cl, al
call print_byte
call print_space
mov byte ah, [line_counter]
inc ah
mov byte [line_counter], ah
and ah, 0x01
jz LINE_BREAK
jmp EXIT_ISR
LINE_BREAK:
call print_line
EXIT_ISR:
pop cx
pop ax
iret
%include "print_byte.asm"
%include "print_space.asm"
%include "print_line.asm"
line_counter:
db 0
START:
; Set interrupt number 0x16 (or 21 in decimal)'s interrupt
; service routine as ours. For interrupt 0x16, see
; https://en.wikipedia.org/wiki/INT_16H
cli
cld
mov ax, 0
mov es, ax
mov ax, KBD_ISR
mov [es:21*4], ax
mov [es:21*4+2], cs
sti
jmp $
POS:
db 0, 0, 0, 0
times 510-($-$$) db 0
dw 0xaa55
Example 4 DMA I/O
; ============================================================
; dma0.asm
;
; Boot from hard disk.
; Read floppy LBA 0 using DMA channel 2.
; DMA destination: 0000:8000
;
; Display first 16 bytes in hexadecimal.
; Display first 32 bytes as ASCII.
;
; Uses the course routines:
; print_byte.asm
; print_space.asm
; print_line.asm
;
; Assemble:
; nasm -f bin dma0.asm -o dma0.bin
;
; ============================================================
[org 0x7c00]
; ============================================================
; Hardware ports
; ============================================================
FDC_DOR equ 0x3f2
FDC_MSR equ 0x3f4
FDC_FIFO equ 0x3f5
DMA_STAT equ 0x08
DMA_MASK equ 0x0a
DMA_MODE equ 0x0b
DMA_FF equ 0x0c
DMA_ADDR equ 0x04
DMA_COUNT equ 0x05
DMA_PAGE equ 0x81
BUFFER equ 0x8000
; ============================================================
; Start
; ============================================================
START:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00
sti
; ============================================================
; Print startup message
; ============================================================
mov si, MSG
call print_string
; ============================================================
; Turn on floppy drive 0
;
; DOR = 3F2h
;
; 1Ch:
; bit 4 = motor 0 ON
; bit 3 = DMA/IRQ enable
; bit 2 = controller enable
; bit 1-0 = drive 0
; ============================================================
mov dx, FDC_DOR
mov al, 0x1c
out dx, al
call DELAY
; ============================================================
; Program DMA channel 2
;
; Destination = 0000:8000
; Count = 511 = 512 bytes
; Direction = device -> memory
; ============================================================
; Mask channel 2
mov dx, DMA_MASK
mov al, 0x06
out dx, al
; Reset DMA flip-flop
mov dx, DMA_FF
xor al, al
out dx, al
; Address = 8000h
mov dx, DMA_ADDR
mov al, 0x00
out dx, al
mov al, 0x80
out dx, al
; Page = 00h
mov dx, DMA_PAGE
xor al, al
out dx, al
; Reset DMA flip-flop
mov dx, DMA_FF
xor al, al
out dx, al
; Count = 01FFh
; 01FFh + 1 = 512 bytes
mov dx, DMA_COUNT
mov al, 0xff
out dx, al
mov al, 0x01
out dx, al
; DMA mode = 46h
;
; single transfer
; address increment
; device -> memory
; channel 2
mov dx, DMA_MODE
mov al, 0x46
out dx, al
; Unmask channel 2
mov dx, DMA_MASK
mov al, 0x02
out dx, al
; ============================================================
; FDC READ DATA
;
; Read floppy CHS 0/0/1.
; This is floppy LBA 0.
; ============================================================
call FDC_WRITE
mov dx, FDC_FIFO
; READ DATA + MFM
mov al, 0x46
out dx, al
; Drive 0, head 0
xor al, al
out dx, al
; Cylinder 0
xor al, al
out dx, al
; Head 0
xor al, al
out dx, al
; Sector 1
mov al, 0x01
out dx, al
; N = 2 -> 512 bytes
mov al, 0x02
out dx, al
; End of track = 18
mov al, 0x12
out dx, al
; GAP3
mov al, 0x1b
out dx, al
; DTL
mov al, 0xff
out dx, al
; ============================================================
; Wait for DMA channel 2 terminal count
;
; DMA status port = 08h
; Channel 2 TC = bit 2
; ============================================================
WAIT_DMA:
mov dx, DMA_STAT
in al, dx
test al, 0x04
jz WAIT_DMA
; ============================================================
; Read and discard FDC result bytes
;
; ST0 ST1 ST2 C H R N
; ============================================================
call FDC_READ
mov cx, 7
READ_RESULT:
in al, dx
loop READ_RESULT
; ============================================================
; Print first 16 bytes as hexadecimal
; Same technique as kbdpoll0.asm:
; save CX because CL is used by print_byte.
; ============================================================
mov si, BUFFER
mov cx, 16
DISP_HEX:
push cx
mov cl, [si]
call print_byte
call print_space
inc si
pop cx
loop DISP_HEX
call print_line
; ============================================================
; Print first 32 bytes as ASCII
; ============================================================
mov si, MSG_TEXT
call print_string
mov si, BUFFER
mov cx, 32
DISP_ASCII:
mov al, [si]
cmp al, 0x20
jb NOT_PRINTABLE
cmp al, 0x7e
ja NOT_PRINTABLE
jmp short DISP_CHAR_ASCII
NOT_PRINTABLE:
mov al, '.'
DISP_CHAR_ASCII:
mov ah, 0x0e
int 0x10
inc si
loop DISP_ASCII
call print_line
; ============================================================
; Stop
; ============================================================
DONE:
cli
hlt
jmp DONE
; ============================================================
; FDC command phase
;
; Wait for:
; RQM = 1
; DIO = 0
; ============================================================
FDC_WRITE:
mov dx, FDC_MSR
FDC_WRITE_WAIT:
in al, dx
test al, 0x80
jz FDC_WRITE_WAIT
test al, 0x40
jnz FDC_WRITE_WAIT
mov dx, FDC_FIFO
ret
; ============================================================
; FDC result phase
;
; Wait for:
; RQM = 1
; DIO = 1
; ============================================================
FDC_READ:
mov dx, FDC_MSR
FDC_READ_WAIT:
in al, dx
test al, 0x80
jz FDC_READ_WAIT
test al, 0x40
jz FDC_READ_WAIT
mov dx, FDC_FIFO
ret
; ============================================================
; Print zero-terminated string DS:SI
;
; We keep this small local routine because the course routines
; supplied here are specifically byte/space/line routines.
; ============================================================
print_string:
lodsb
test al, al
jz DISP_STRING_DONE
mov ah, 0x0e
int 0x10
jmp print_string
DISP_STRING_DONE:
ret
; ============================================================
; Floppy motor startup delay
; ============================================================
DELAY:
mov cx, 0xffff
DELAY_LOOP:
loop DELAY_LOOP
ret
; ============================================================
; Messages
; ============================================================
MSG:
db 13,10
db 'Booted from hard disk.',13,10
db 'Reading floppy LBA 0 using DMA...',13,10
db 0
MSG_HEX:
db 'Hex: ',0
MSG_TEXT:
db 13,10
db 'Text: ',0
; ============================================================
; Course routines
;
; IMPORTANT:
; These must come AFTER the boot code because BIOS begins
; execution at START (offset 0x7c00).
; ============================================================
%include "print_byte.asm"
%include "print_space.asm"
%include "print_line.asm"
; ============================================================
; Boot-sector padding and signature
; ============================================================
times 510-($-$$) db 0
dw 0xaa55
To run the program, follow the steps illustrated in the shell script below
#!/bin/bash
# ============================================================
# run-dma.sh
#
# DMA demonstration:
#
# Boot from hard disk
# Read first sector of floppy using DMA
#
# Files:
#
# dma1.asm Boot-sector source
# dma1.bin Assembled boot sector
# dma-hd.img Hard-disk image
# dma-floppy.img Floppy image
#
# ============================================================
set -e
ASM="dma0.asm"
BIN="dma0.bin"
HDIMG="dma-hd.img"
FDIMG="dma-floppy.img"
# ============================================================
# Check dependencies
# ============================================================
if ! command -v nasm >/dev/null 2>&1; then
echo "ERROR: nasm is not installed."
exit 1
fi
if ! command -v qemu-system-i386 >/dev/null 2>&1; then
echo "ERROR: qemu-system-i386 is not installed."
exit 1
fi
# ============================================================
# Assemble boot sector
# ============================================================
echo "[1/5] Assembling $ASM..."
nasm -f bin "$ASM" -o "$BIN"
# ============================================================
# Check boot-sector size
# ============================================================
SIZE=$(stat -c%s "$BIN" 2>/dev/null || stat -f%z "$BIN")
if [ "$SIZE" -ne 512 ]; then
echo "ERROR: $BIN is $SIZE bytes."
echo " A boot sector must be exactly 512 bytes."
exit 1
fi
echo " $BIN = 512 bytes"
# ============================================================
# Create hard-disk image
#
# For this simple example we create a 1.44 MB raw image.
#
# The BIOS will boot from sector 0.
# ============================================================
echo "[2/5] Creating hard-disk image..."
rm -f "$HDIMG"
dd if=/dev/zero \
of="$HDIMG" \
bs=512 \
count=2880 \
status=none
# ============================================================
# Install boot sector into hard disk
# ============================================================
echo "[3/5] Installing boot sector..."
dd if="$BIN" \
of="$HDIMG" \
bs=512 \
count=1 \
conv=notrunc \
status=none
# ============================================================
# Create floppy image
#
# 1.44 MB floppy:
#
# 80 cylinders
# 2 heads
# 18 sectors/track
# 512 bytes/sector
#
# Total:
#
# 80 * 2 * 18 * 512 = 1,474,560 bytes
# ============================================================
echo "[4/5] Creating floppy image..."
rm -f "$FDIMG"
dd if=/dev/zero \
of="$FDIMG" \
bs=512 \
count=2880 \
status=none
# ============================================================
# Put recognizable data in FIRST floppy sector.
#
# The assembly program reads:
#
# cylinder = 0
# head = 0
# sector = 1
#
# This is LBA 0.
# ============================================================
printf 'DMA FLOPPY SECTOR 0 - Hello from the floppy!\n' \
| dd of="$FDIMG" \
bs=512 \
count=1 \
conv=notrunc \
status=none
# ============================================================
# Show image information
# ============================================================
echo
echo "Images created:"
echo
ls -lh "$HDIMG" "$FDIMG"
echo
# ============================================================
# Run QEMU
#
# dma-hd.img:
# hard disk
#
# dma-floppy.img:
# floppy drive A:
#
# -boot c:
# boot from hard disk
# ============================================================
echo "[5/5] Starting QEMU..."
echo
echo " Boot device : hard disk"
echo " DMA source : floppy"
echo " DMA buffer : 0000:8000"
echo
qemu-system-i386 \
-drive file="$HDIMG",format=raw,if=ide \
-drive file="$FDIMG",format=raw,if=floppy \
-boot c \
-display curses \
-monitor telnet:127.0.0.1:54321,server,nowait
Required Subroutines
;
; print_byte.asm
;
; print a byte in register cl to hex assuming ASCII encoding
; void * print_byte(char c)
print_byte:
pusha ; Push AX, CX, DX, BX, original SP, BP, SI, and DI.
mov ch, cl
shr ch, 4
and ch, 0x0f
push cx
mov cl, ch
call print_low_4bits
pop cx
mov ch, cl
and ch, 0x0f
push cx
mov cl, ch
call print_low_4bits
pop cx
popa
ret
; print lower 4 bits in register cl to hex assuming ASCII encoding
; void * print_byte(char c)
print_low_4bits:
pusha
mov ah, 0x0e
mov ch, cl
and ch, 0x0f
cmp ch, 0x09
jg A_TO_F
add ch, '0'
jmp PRINT_CHAR
A_TO_F:
sub ch, 0x0a
add ch, 'A'
jmp PRINT_CHAR
PRINT_CHAR:
mov al, ch
int 0x10
popa
ret
;
; print_space.asm
;
print_space:
pusha
mov ah, 0x0e
mov al, ' '
int 0x10
popa
ret
;
; print_line.asm
;
print_line:
pusha
; line feed
mov ah, 0x0e
mov al, 0x0a
int 0x10
; carriage return
mov ah, 0x0e
mov al, 0x0d
int 0x10
popa
ret