Navigate

Overview

Example

System calls

Extra

The MikeOS Assembly App Developer Handbook

For version 4.7, 9 April 2022 - (C) MikeOS Developers

This documentation file explains how write software for MikeOS in assembly language. It shows you the tools you need, how MikeOS programs work, and how to use the system calls included in the kernel. If you have any questions, see the MikeOS website for contact details and mailing list information.

Click the links on the left to navigate around this guide.



Overview

Introduction

Many MikeOS programs are written in 16-bit, real mode assembly language. (The OS also includes a BASIC interpreter.) Because MikeOS and its programs live in a single 64KiB memory segment, you do not need to concern yourself with segment registers.

MikeOS programs are loaded at the 32K point (32768) in the segment, and have a maximum size of 32KiB. Consequently, your programs will need to begin with these directives:

BITS 16
ORG 32768

There are many system calls available in MikeOS for controlling the screen, getting input, manipulating strings, loading/saving files, and more. All parameters to MikeOS system calls are passed in registers, and not on the stack. To use them in your programs, you need this line:

%INCLUDE "mikedev.inc"

This doesn't include any code, but a list of equ directives that point to system call vectors in the kernel. So, by including this file you can call, for instance, the MikeOS os_print_string routine without having to know exactly where it is in the kernel. mikedev.inc is included in the programs/ directory of the MikeOS download -- it also provides a quick reference to the system calls.

If a MikeOS program is run from the command line, and one or more parameters was provided with the command, they are passed to the program via the SI register as a zero-terminated string.


Tools needed

To write MikeOS programs you need:

  • NASM -- A powerful, free and open source assembler
  • mikedev.inc -- The system call vectors described above
  • A way to add programs to the floppy disk

Let's clarify this: MikeOS uses NASM for its programs and kernel source code, hence why we recommend it here. You can, of course, use any other assembler that can output plain binary files (i.e. with no header) and accept 16-bit code. NASM is available in most Linux distro repositories, or you can download the Windows version from here (get the 'win32' file).

For the second point, copy programs/mikedev.inc so that it's alongside your program's source code for inclusion.

For the third point, if you've written MikeOS to a real floppy disk, you can just copy your .BIN programs onto that disk (root directory only!), boot MikeOS and test them out. If you're working with the virtual mikeos.flp disk image, see the Copying files section of the User Handbook.



Example

Source code

Here is an example MikeOS program, in NASM format, which prints a string to the screen and waits for the user to press a key before finishing:

	BITS 16
	ORG 32768
	%INCLUDE 'mikedev.inc'

start:
	mov si, mystring
	call os_print_string

	call os_wait_for_key

	ret

	mystring db 'My first MikeOS program!', 0

Building

Save the above code as testapp.asm, and enter this command to assemble it (works on both Linux and Windows):

nasm -f bin -o testapp.bin testapp.asm

Using the '-f bin' option we tell NASM that we just want a plain binary file: no header or sections. The resulting executable file is testapp.bin that we can copy to our floppy disk or add to the virtual disk image as described in Copying files in the User Handbook. Then we can boot MikeOS and run the program.


Explanation

This is a very short program, but we'll explain exactly how it works for complete clarity. The first three lines will not be assembled to machine code instructions, but are just directives to tell NASM that we want to operate in 16 bit mode, our code is written to be executed from the 32KiB mark, and we want to use system calls from the MikeOS API.

Next we have the 'start:' label, which is not essential but good practice to make things clear. We put the location of a zero-terminated string into the SI register, then call the MikeOS os_print_string routine which we can access via the vectors listed in mikedev.inc. After that we pause program until a the user presses a key.

All MikeOS programs must end with a ret instruction, which passes control back to the OS. After that instruction we have the data for our string. And that's it!



System calls

Introduction

The MikeOS kernel includes system calls for outputting to the screen, taking keyboard input, manipulating strings, producing PC speaker sounds, math operations and disk I/O. You can get a quick reference to the registers that these calls take and pass back by looking at mikedev.inc, and see practical use of the calls in the programs/ directory.

Here we have a detailed API explanation with examples. You can see the full source code behind these system calls in the source/features/ directory in MikeOS. Each aspect of the API below is contained within an assembly source file, so you can enhance the system calls as per the MikeOS System Developer Handbook.



Disk

os_get_file_list

Generate comma-separated string of files on floppy

  • IN/OUT: AX = location to store zero-terminated filename string (the programmer must ensure the buffer is large enough to hold the entire root directory)

Example:

	mov ax, .filestring
	call os_get_file_list

	; Now .filestring will contain something like
	; 'HELLO.BIN,FOO.BAR,THREE.TXT', 0

	.filestring	times 256 db 0

os_load_file

Load file into RAM

  • IN: AX = location of zero-terminated filename string, CX = location in RAM to load file
  • OUT: EBX = file size (in bytes: if < 64 KiB testing BX is sufficient), carry set if file not found

Example:

	mov ax, filename
	mov cx, 36960		; 4KiB after where external program loads
	call os_load_file

	...

	filename db 'README.TXT', 0

os_write_file

Save (max 64KiB) file to disk

  • IN: AX = filename, BX = data location, CX = bytes to write
  • OUT: Carry clear if OK, set if failure (if OK the last write date and time, archive attribute and file size are updated in the disk directory)

Example:

	; For this example, there's some text stored in .data

	mov ax, .filename
	mov bx, .data
	mov cx, 4192
	call os_write_file

	.filename	db 'HELLO.TXT', 0
	.data		times 4192 db 0

os_file_exists

Check for presence of file on the floppy

  • IN: AX = filename location
  • OUT: carry clear if found, set if not

Example:

	mov ax, .filename
	call os_file_exists
	jc .not_found

	...

.not_found:
	; Print error message here

	.filename	db 'HELLO.TXT', 0

os_create_file

Creates a new 0-byte file on the floppy disk

  • IN: AX = location of filename
  • OUT: Nothing (if the function is successful, a disk directory entry is created with the current system date and time, the archive attribute set and a file size of 0)

Example:

	mov ax, .filename
	call os_create_file

	...

	.filename	db 'HELLO.TXT', 0

os_remove_file

Deletes the specified file from the filesystem

  • IN: AX = location of filename to remove
  • OUT: Nothing

os_rename_file

Change the name of a file on the disk

  • IN: AX = filename to change, BX = new filename (zero-terminated strings)
  • OUT: carry set on error

Example:

	mov ax, .filename1
	mov bx, .filename2
	call os_rename_file

	jc .error

	...

.error:
	; File couldn't be renamed (may already exist)

	.filename1	db 'ORIG.TXT', 0
	.filename2	db 'NEW.TXT', 0

os_get_file_size

Get file size information for specified file

  • IN: AX = filename
  • OUT: EBX = file size in bytes (theoretically, up to 4 GB) or carry set if file not found


Keyboard

os_wait_for_key

Waits for key press and returns key

  • IN: Nothing
  • OUT: AX = key pressed, other regs preserved

Example:

.loop:
	call os_wait_for_key
	cmp al, 'y'
	je .yes
	cmp al, 'n'
	je .no
	jmp .loop

os_check_for_key

Scans keyboard for input, but doesn't wait

  • IN: Nothing
  • OUT: AX = 0 if no key pressed, otherwise scan code

Example: see code above



Math

os_bcd_to_int

Converts binary coded decimal number to an integer

  • IN: AL = BCD number
  • OUT: AX = integer value

Example:

	mov al, 00010110b	; 0001 0110 = 16 (decimal) or 10h in BCD
	call os_bcd_to_int 
     
	; AX now contains the 16 bit-integer 00000000 00010000b

os_long_int_negate

Multiply value in DX:AX by -1

  • IN: DX:AX = long integer
  • OUT: DX:AX = -(initial DX:AX)

Example:

	mov dx, 01h
	mov ax, 45h
	call os_long_int_negate

	; DX now contains 0xFFFF
	; and AX 0xFEBB

os_get_random

Get a random integer between high and low values (inclusive)

  • IN: AX = low integer, BX = high
  • OUT: CX = random number between AX and BX


Misc

os_get_api_version

Return current version of MikeOS API

  • IN: Nothing
  • OUT: AL = API version number

Example:

	call os_get_api_version
	; AL now contains version number (eg 8)

os_pause

Delay execution for specified 10ths of second

  • IN: AX = number of 10ths of second to wait
  • OUT: nothing

Example:

	; Halt execution for 3 secs

	mov ax, 30
	call os_pause

os_fatal_error

Display error message and halt execution

  • IN: AX = error message string location
  • OUT: nothing

Example:

	mov ax, .error_msg
	call os_fatal_error

	.error_msg	db 'Massive error!', 0


Ports

os_port_byte_out

Sends a byte to the specified port

  • IN: DX = port address, AL = byte
  • OUT: Nothing

os_port_byte_in

Retrieves a byte from the specified port

  • IN: DX = port address
  • OUT: AL = byte

os_serial_port_enable

Turn on the first serial port

  • IN: AX = 0 for normal mode (9600 baud), or 1 for slow mode (1200 baud)
  • OUT: AH = bit 7 clear on success (AH is line status and AL is modem status)

os_send_via_serial

Send a byte via the serial port

  • IN: AL = byte to send via serial
  • OUT: AH = bit 7 clear on success

Example:

	mov al, 'a'			; Place char to transmit in AL
	call os_send_via_serial
	cmp ah, 128			; If bit 7 is set, there's an error
	jnz all_ok			; Otherwise it's all OK
	jmp oops_error			; Deal with the error

os_get_via_serial

Get a byte from the serial port

  • IN: nothing
  • OUT: AL = byte that was received; OUT: AH = bit 7 clear on success

Example:

	call os_get_via_serial
	cmp ah, 128		; If bit 7 is set, there's an error.
	jnz all_ok		; Otherwise it's all OK
	jmp oops_error		; Deal with the error

all_ok:           
	mov [rx_byte], al	; Store byte we retrieved


Screen

os_print_string

Displays text

  • IN: SI = message location (zero-terminated string)
  • OUT: Nothing (registers preserved)

Example:

	mov si, .message
	call os_print_string

	...

	.message	db 'Hello, world', 0

os_clear_screen

Clears the screen to background

  • IN/OUT: Nothing (registers preserved)

os_move_cursor

Moves cursor in text mode

  • IN: DH, DL = row, column
  • OUT: Nothing (registers preserved)

Example:

	; Move to middle of screen

	mov dh, 12
	mov dl, 40
	call os_move_cursor

os_get_cursor_pos

Return position of text cursor

  • OUT: DH, DL = row, column

os_print_horiz_line

Draw a horizontal line on the screen

  • IN: AX = line type (1 for double, otherwise single)
  • OUT: Nothing (registers preserved)

os_show_cursor

Turns on cursor in text mode

  • IN/OUT: Nothing

os_hide_cursor

Turns off cursor in text mode

  • IN/OUT: Nothing

os_draw_block

Render block of specified colour

  • IN: BL/DL/DH/SI/DI = colour/start X pos/start Y pos/width/finish Y pos
  • OUT: Nothing

Example:

	mov bl, 0100111b	; White on red
	mov dl, 20		; Start X position
	mov dh, 2		; Start Y position
	mov si, 40		; Width
	mov di, 23		; Finish Y position
	call os_draw_block

os_file_selector

Show a file selection dialog

  • IN: Nothing
  • OUT: AX = location of filename string (or carry set if Esc pressed)

Example:

	call os_file_selector
	jc .esc_pressed

	; Now AX points to the chosen filename
	...

.esc_pressed:
	...

os_list_dialog

Show a dialog with a list of options

  • IN: AX = comma-separated list of strings to show (zero-terminated),
  •  BX = first help string, CX = second help string
  • OUT: AX = number (starts from 1) of entry selected carry set if Esc pressed

Example:

	mov ax, .list
	mov bx, .msg1
	mov cx, .msg2
	call os_list_dialog
	jc .esc_pressed

	; Now AX = number (from 1) of option chosen
	...

.esc_pressed:
	...

	.list	db 'Open,Close,Reboot', 0
	.msg1	db 'Choose an option', 0
	.msg2	db 'Or press Esc to quit', 0

os_draw_background

Clear screen with white top and bottom bars, containing text, and a coloured middle section

  • IN: AX/BX = top/bottom string locations, CX = colour
  • OUT: Nothing

Example:

	mov ax, .title_msg
	mov bx, .footer_msg
	mov cx, 00100000b	; Colour
	call os_draw_background

	...

	.title_msg	db 'File manager', 0
	.footer_msg	db 'Choose an option...', 0

os_print_newline

Reset cursor to start of next line

  • IN/OUT: Nothing (registers preserved)

os_dump_registers

Displays register contents in hex on the screen

  • IN/OUT: AX/BX/CX/DX/SI/DI/BP = registers to show

os_input_dialog

Get text string from user via a dialog box

  • IN: AX = string location, BX = message to show
  • OUT: AX = string location

Example:

	mov bx, .filename_msg
	mov ax, .filename_input
	call os_input_dialog

	...

	.filename_msg	'Please enter a filename:', 0
	.filename_input	times 12 db 0

os_dialog_box

Print dialog box in middle of screen, with button(s)

  • IN: AX, BX, CX = string locations (set registers to 0 for no display),
  •  DX = 0 for single 'OK' dialog, 1 for two-button 'OK' and 'Cancel'
  • OUT: If two-button mode, AX = 0 for OK and 1 for cancel
  • NOTE: Each string is limited to 40 characters

Example:

	mov ax, .string1
	mov bx, .string2
	mov cx, .string3
	mov dx, 1
	call os_dialog_box

	cmp ax, 1
	je .first_option_chosen

	; Otherwise second was chosen
	...

.first_option_chosen:
	...

	.string1	db 'Welcome to my program!', 0
	.string2	db 'Please choose to destroy the world,', 0
	.string3	db 'or play with fluffy kittens.', 0

os_print_space

Print a space to the screen

  • IN/OUT: nothing

os_dump_string

Dump string as hex bytes and printable characters

  • IN: SI = points to string to dump
  • OUT: Nothing

os_print_digit

Displays contents of AX as a single digit (works up to base 37, ie digits 0-Z)

  • IN: AX = "digit" to format and print
  • OUT: Nothing

os_print_1hex

Displays low nibble of AL in hex format

  • IN: AL = number to format and print
  • OUT: Nothing

os_print_2hex

Displays AL in hex format

  • IN: AL = number to format and print
  • OUT: Nothing

os_print_4hex

Displays AX in hex format

  • IN: AX = number to format and print
  • OUT: Nothing

os_input_string

Take string from keyboard entry

  • IN/OUT: AX = location of string, BX = maximum number of characters
  • (Location will contain up to 255 characters, zero-terminated)

Example:

	mov ax, .string
	call os_input_string

	...

	.string		times 256 db 0


String

os_string_length

Return length of a string

  • IN: AX = string location
  • OUT AX = length (other regs preserved)

Example:

	jmp Get_Len

	Test_String db 'This string has 46 characters including spaces', 0

Get_Len:
	mov ax, Test_String
	call os_string_length
	; AX now contains the number 46

os_find_char_in_string

Get the position of a character in a string

  • IN: SI = string location, AL = character to find
  • OUT: AX = location in string, or 0 if char not present

Example:

jmp Search_Str

     Test_String db 'This is the test string', 0
     message_1   db 'Character not found', 0
     message_2   db 'Character found at position', 0
     message_3   db '  ', 0
     str_len     dw  0

Search_Str:

     mov ax, Test_String           ; string to search
     mov si, ax
     xor ax, ax                    ; clear ax

     mov al, 'x'                   ; x is the character to find
     call os_find_char_in_string

     mov [str_len], ax             ; store result
     cmp ax, 0                  
     jz Char_Not_Found 
     jmp Char_Found

Char_Not_Found:

     mov si, message_1
     call os_print_string     
     jmp Main_Pgm

Char_Found:

     mov ax, [str_len]             ; convert result into string first
     mov bx, message_3
     call os_int_to_string

     mov si, message_2
     call os_print_string          ; print message_2
     call os_print_space           ; print a space

     mov si, message_3
     call os_print_string          ; print the position at which x was found     

os_string_reverse

Swar order of characters in a string

  • IN: SI = location of zero-terminated string

Example:

	mov si, mystring
	call os_string_reverse

	; Now mystring contains 'olleH'

	mystring db 'Hello', 0

os_string_charchange

Change instances of character in a string

  • IN: SI = string location, AL = char to find, BL = char to replace with

os_string_uppercase

Convert zero-terminated string to upper case

  • IN/OUT: AX = string location

os_string_lowercase

Convert zero-terminated string to lower case

  • IN/OUT: AX = string location

os_string_copy

Copy one string into another

  • IN/OUT: SI = source, DI = destination (programmer ensure sufficient room)

Example:

	mov si, .old_string
	mov di, .new_string
	call os_string_copy

	...

	.old_string	db 'Hello', 0
	.new_string	times 16 db 0

os_string_truncate

Chop string down to specified number of characters

  • IN: SI = string location, AX = number of characters
  • OUT: string modified, registers preserved

Example:

	mov si, .string
	mov ax, 3
	call os_string_truncate

	; .string now contains 'HEL', 0

	.string		db 'HELLO', 0

os_string_join

Join two strings into a third string

  • IN/OUT: AX = string one, BX = string two, CX = destination string

Example:

	mov ax, .string1
	mov bx, .string2
	mov cx, .string3
	call os_string_join

	; CX now points to 'HELLOYOU', 0

	.string1	db 'HELLO', 0
	.string2	db 'YOU', 0
	.string3	times 50 db 0

os_string_chomp

Strip leading and trailing spaces from a string

  • IN: AX = string location
  • OUT: nothing

Example:

	mov ax, .string
	call os_string_chomp

	; AX now points to 'KITTEN', 0

	.string		db '  KITTEN  ', 0

os_string_strip

Removes specified character from a string

  • IN: SI = string location, AL = character to remove
  • OUT: nothing

Example:

	mov si, .string
	mov al, 'U'
	call os_string_strip

	; .string now contains 'MOSE', 0

	.string		db 'MOUSE', 0

os_string_compare

See if two strings match

  • IN: SI = string one, DI = string two
  • OUT: carry set if same, clear if different

Example:

	mov si, .string1
	mov di, .string2
	call os_string_compare
	jc .same

	; Strings are different

	...

.same:
	; Strings are the same

	.string1	db 'ONE', 0
	.string2	db 'TWO', 0

os_string_strincmp

See if two strings match up to set number of chars

  • IN: SI = string one, DI = string two, CL = chars to check
  • OUT: carry set if same, clear if different

Example: like above, but with CL = number of characters to check


os_string_parse

Take string (eg "run foo bar baz") and return pointers to zero-terminated strings (eg AX = "run", BX = "foo" etc.)

  • IN: SI = string
  • OUT: AX, BX, CX, DX = individual strings

Example:

	mov si, .string
	call os_string_parse

	; Now AX points to 'This',
	; BX to 'is',
	; CX to 'a',
	; and DX to 'test'

	.string		db 'This is a test', 0

os_string_to_int

Convert decimal string to integer value

  • IN: SI = string location (max 5 chars, up to '65536')
  • OUT: AX = number

Example:

	mov si, .string
	call os_string_to_int

	; Now AX contains the number 1234

	.string		db '1234', 0

os_int_to_string

Convert unsigned value in AX to string

  • IN: AX = unsigned integer
  • OUT: AX = location of internal string buffer with result

Example:

	mov ax, 1234
	call os_int_to_string

	; Now AX points to '1234', 0

os_sint_to_string

Convert signed value in AX to string

  • IN: AX = signed integer
  • OUT: AX = location of internal string buffer with result

Example:

	mov ax, -1234
	call os_int_to_string

	; Now AX points to '-1234', 0

os_long_int_to_string

Convert value in DX:AX to string

  • IN: DX:AX = long unsigned integer, BX = number base, DI = string location
  • OUT: DI = location of converted string

os_string_to_long_int

Convert a decimal string to a long integer value

  • IN: SI = string location (up to 10 digits, value of '4294967295')
  • OUT: EAX = long unsigned integer

os_set_time_fmt

Set time reporting format (eg '10:25 AM' or '2300 hours')

  • IN: AL = format flag, 0 = 12-hr format
  • OUT: nothing

os_get_time_string

Get current time in a string (eg '10:25')

  • IN/OUT: BX = string location

os_set_date_fmt

Set date reporting format (M/D/Y, D/M/Y or Y/M/D - 0, 1, 2)

  • IN: AX = format flag, 0-2
  •  If AX bit 7 = 1 = use name for months
  •  If AX bit 7 = 0, high byte = separator character
  • OUT: nothing

os_get_date_string

Get current date in a string (eg '12/31/2007')

  • IN/OUT: BX = string location


Sound

os_speaker_tone

Generate PC speaker tone (call os_speaker_off to turn off)

  • IN: AX = note frequency
  • OUT: Nothing (registers preserved)

Example: see code below


os_speaker_off

Turn off PC speaker

  • IN/OUT: Nothing (registers preserved)

Example:

; Generate "middle C" 261.626 Hz (263 Hz close enough) for 2 secs
; 2 secs = 2,000,000 uS which is 1E8480h

	jmp Play_It

	music_note  dw  263

Play_It:
	mov ax, [music_note]
	call os_speaker_tone

	mov cx, 1Eh
	mov dx, 8480h
	call os_pause

	call os_speaker_off         


BASIC

os_run_basic

Runs kernel BASIC interpreter at the specified point

  • IN: AX = location of BASIC code, BX = size of code (in bytes)


Extra

Help

If you have any questions about MikeOS, or you're developing a similar OS and want to share code and ideas, go to the MikeOS website and join the mailing list as described.


License

MikeOS is open source and released under a BSD-like license (see doc/LICENSE.TXT in the MikeOS .zip file). Essentially, it means you can do anything you like with the code, including basing your own project on it, providing you retain the license file and give credit to the MikeOS developers for their work.