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

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