Blog

Relearning MSX #21: Programming for MSX-DOS (part 1)

Posted by in How-to, MSX, Retro, Technology | August 28, 2015

In the previous post we learnt about mnemonics and pseudoinstructions, symbols and labels. This is a good start, but learning assembler won’t be any use at all if we don’t also learn the environment where our programs will run. In our case this environment is going to be MSX-DOS, the MSX Disk Operating System. Let’s see what this means.

Functions of the operating system

The most basic functions of any operating system are:

  • Input and output of data from/to peripherals (keyboard, screen, printer, etc)
  • File management
  • Running programs

MSX-DOS is able to handle all these, plus a few other tasks. let’s look at them in more detail:

Data input/output

First things first: the term input/ouput is most of the time abbreviated to just I/O. This is how you’ll see it written on most books and documents.

MSX-DOS handles I/O by using prewritten routines known as system calls. Let’s see what they are and what they do:

MSX-DOS system calls

Peripherals are an essential part of a computer. These are the devices that allow us to enter data in the computer (keyboard, joysticks, mice), output data (display, speakers, printer) or both (disk drives, serial ports, etc). Without them the computer would be just a useless piece of silicon because we couldn’t run any programs. Even if we could somehow run a program by having it on an onboard ROM, without a display or other output device the computer wouldn’t be able to communicate the result to us.

MSX-DOS supports several types of these I/O devices:

  • Keyboards
  • Displays
  • Printers
  • Disk drives (up to 8, labeled using alphabet letters from A to H)

Controlling these I/O devices isn’t a trivial task. Just think about it: reading a key from the keyboard involves examining the values of the keyboard matrix by reading and writing a Z80 port (without even taking into account different keyboard layouts based on the country); writing something on the screen requires keeping track of the screen’s line width, current position of the cursor, etc; reading a file from a disk means keeping track of what sectors in the file occupies, read those sectors, and copy them to memory. We don’t want to deal with all these low-level details. The good news is that the operating system already contains routines to handle these tasks. We call these routines system calls. The MSX-DOS system calls free the programmer from having to write code to manage the keyboard, the screen, or the files on disk. Developing a game or an application becomes a much simpler task.

Another advantage of using MSX-DOS system calls is that they are (mostly) compatible with the CP/M system calls (CP/M was another popular operating system in the 80s, available for many other computers like the Apple IIAltair 8800Amstrad PCWOsborne 1, etc). This means that we can write programs for MSX-DOS that access the peripherals using system calls, and these programs will run without modification in any other computer running CP/M on a Z80 CPU (even non-MSX machines).

wargames

For all we know, Matthew Broderick in the WarGames movie could have been running MSX applications in his IMSAI 8080. (Click to enlarge)

The opposite is also true: applications written for the CP/M operating system run without modification under MSX-DOS. In fact, PMEXT.COM, one of the most popular decompressor on the MSX platform isn’t even an MSX application:

pmext

PMext 2.22 running under MSX-DOS. It’s actually a CP/M program. (Click to enlarge)

How to execute system calls

Executing a system call is a very simple process:

  1. Load the number of the function in the Z80’s C register
  2. If required by the function, load other registers with the relevant data
  3. Call address 0005h

Don’t worry if you don’t know yet what the Z80 registers are or what they’re for. I’ll explain this in another post.

We can see this system call process several times in the example program from the previous post:

keycode_system_call

First, the program defines the symbol SYSTEM to equal 0005h, so from that point we can just enter the symbol name instead of the 0005h address every time.

Second, near the beginning of the main routine we see an LD C,1 followed by CALL SYSTEM. System call number 1 happens to be MSX-DOS’s console input system call, which reads a character from the keyboard. This means that these two assembler instructions:

LD   C,1
CALL SYSTEM

Are equivalent to this BASIC instruction:

C$ = INPUT$(1)

Except that there is no C$. I’ll explain in another post how we can find out what character was pressed.

Now look near the end of the list above, and see what the PUTMSG routine does. It loads the C register with the value 9 and then calls SYSTEM. System call number 9 is the string output call, which prints a text string on the screen. Unlike console input, this system call requires an additional parameter: register DE has to contain the memory address that contains the string we want to print. That’s why before every call to the PUTMSG routine, the program loads the register DE with the address of the text string. In other words, the following assembler code:

         LD   DE,TEXT
         LD   C,9
         CALL SYSTEM
         
TEXT:    DB   "Hello world!$"

Is equivalent to the BASIC instruction:

PRINT "Hello World!"

Now you should be able to read most of the main routine in the example program:

  • Prints the string MSG1
  • Read a key from the keyboard
  • (does something with the AF register)
  • Prints the string MSG2
  • (does something else with the AF register)
  • Print the hexadecimal code (this is what PUTHEX does)
  • Prints the string MSG3
  • Exit the program

And we haven’t even started explaining Z80 mnemonics yet. As you will see, the assembly language is this easy. The complexity comes from having to learn all this stuff about system calls, how to acces the hardware, etc.

List of system calls

MSX-DOS has exactly 42 system calls, numbered from 0 to 30h (48 in hexadecimal). Most of these are compatible with CP/M. MSX-DOS2 supports all the original MSX-DOS system calls, plus around 50 more. None of the MSX-DOS2 system calls are compatible with CP/M, so don’t use them if you want to write portable programs.

We’ll describe the functions with more detail in future posts as we use them. For now, here’s a list so you can get an idea of all the stuff that MSX-DOS(2) can do for us. You’ll see that the list contains holes, or numbers that aren’t assigned to any system call. Don’t use those in your programs! :-)

System call numberNameMSX-DOS versionCP/M compatiblePurpose
00h_TERM0MSX-DOSYesProgram terminate
01h_CONINMSX-DOSYesConsole input
02h_CONOUTMSX-DOSYesConsole output
03h_AUXINMSX-DOSYesAuxiliary input
04h_AUXOUTMSX-DOSYesAuxiliary output
05h_LSTOUTMSX-DOSYesPrinter output
06h_DIRIOMSX-DOSYesDirect console I/O
07h_DIRINMSX-DOSDirect console input
08h_INNOEMSX-DOSConsole input without echo
09h_STROUTMSX-DOSYesString output
0Ah_BUFINMSX-DOSYesBuffered line input
0Bh_CONSTMSX-DOSYesConsole status
0Ch_CPMVERMSX-DOSYesReturn version number
0Dh_DSKRSTMSX-DOSYesDisk reset
0Eh_SELDSKMSX-DOSYesSelect disk
0Fh_FOPENMSX-DOSYesOpen file (FCB)
10h_FCLOSEMSX-DOSYesClose file (FCB)
11h_SFIRSTMSX-DOSYesSearch for first entry (FCB)
12h_SNEXTMSX-DOSYesSearch for next entry (FCB)
13h_FDELMSX-DOSYesDelete file (FCB)
14h_RDSEQMSX-DOSYesSequential read (FCB)
15h_WRSEQMSX-DOSYesSequential write (FCB)
16h_FMAKEMSX-DOSYesCreate file (FCB)
17h_FRENMSX-DOSYesRename file (FCB)
18h_LOGINMSX-DOSYesGet login vector
19h_CURDRVMSX-DOSYesGet current drive
1Ah_SETDTAMSX-DOSYesSet disk transfer address
1Bh_ALLOCMSX-DOSGet allocation information
21h_RDRNDMSX-DOSYesRandom read (FCB)
22h_WRRNDMSX-DOSYesRandom write (FCB)
23h_FSIZEMSX-DOSYesGet file size (FCB)
24h_SETRNDMSX-DOSYesSet random record (FCB)
26h_WRBLKMSX-DOSRandom block write (FCB)
27h_RDBLKMSX-DOSRandom block read (FCB)
28h_WRZERMSX-DOSYesRandom write with zero fill (FCB)
2Ah_GDATEMSX-DOSGet date
2Bh_SDATEMSX-DOSSet date
2Ch_GTIMEMSX-DOSGet time
2Dh_STIMEMSX-DOSSet time
2Eh_VERIFYMSX-DOSSet/reset verify flag
2Fh_RDABSMSX-DOSAbsolute sector read
30h_WRABSMSX-DOSAbsolute sector write
31h_DPARMMSX-DOS2Get disk parameters
40h_FFIRSTMSX-DOS2Find first entry
41h_FNEXTMSX-DOS2Find next entry
42h_FNEWMSX-DOS2Find new entry
43h_OPENMSX-DOS2Open file handle
44h_CREATEMSX-DOS2Create file handle
45h_CLOSEMSX-DOS2Close file handle
46h_ENSUREMSX-DOS2Ensure file handle
47h_DUPMSX-DOS2Duplicate file handle
48h_READMSX-DOS2Read from file handle
49h_WRITEMSX-DOS2Write to file handle
4Ah_SEEKMSX-DOS2Move file handle pointer
4Bh_IOCTLMSX-DOS2I/O control for devices
4Ch_HTESTMSX-DOS2Test file handle
4Dh_DELETEMSX-DOS2Delete file or subdirectory
4Eh_RENAMEMSX-DOS2Rename file or subdirectory
4Fh_MOVEMSX-DOS2Move file or subdirectory
50h_ATTRMSX-DOS2Get/set file attributes
51h_FTIMEMSX-DOS2Get/set file date and time
52h_HDELETEMSX-DOS2Delete file handle
53h_HRENAMEMSX-DOS2Rename file handle
54h_HMOVEMSX-DOS2Move file handle
55h_HATTRMSX-DOS2Get/set file handle attributes
56h_HFTIMEMSX-DOS2Get/set file handle date and time
57h_GETDTAMSX-DOS2Get disk transfer address
58h_GETVFYMSX-DOS2Get verify flag setting
59h_GETCDMSX-DOS2Get current directory
5Ah_CHDIRMSX-DOS2Change current directory
5Bh_PARSEMSX-DOS2Parse pathname
5Ch_PFILEMSX-DOS2Parse filename
5Dh_CHKCHRMSX-DOS2Check character
5Eh_WPATHMSX-DOS2Get whole path string
5Fh_FLUSHMSX-DOS2Flush disk buffers
60h_FORKMSX-DOS2Fork a child process
61h_JOINMSX-DOS2Rejoin parent process
62h_TERMMSX-DOS2Terminate with error code
63h_DEFABMSX-DOS2Define abort exit routine
64h_DEFERMSX-DOS2Define disk error handler routine
65h_ERRORMSX-DOS2Get previous error code
66h_EXPLAINMSX-DOS2Explain error code
67h_FORMATMSX-DOS2Format a disk
68h_RAMDMSX-DOS2Create or destroy RAM disk
69h_BUFFERMSX-DOS2Allocate sector buffers
6Ah_ASSIGNMSX-DOS2Logical drive assignment
6Bh_GENVMSX-DOS2Get environment item
6Ch_SENVMSX-DOS2Set environment item
6Dh_FENVMSX-DOS2Find environment item
6Eh_DSKCHKMSX-DOS2Get/set disk check status
6Fh_DOSVERMSX-DOS2Get MSX-DOS version number
70h_REDIRMSX-DOS2Get/set redirection status

Summary

In this post we’ve learnt how using the already-made system calls in the operating system can help us develop more efficiently. We’ve learnt what system calls can do and how to use them.

In the next post…

We’ll see the two other main functions provided by the operating system: file management and program execution.

Extra!

Here’s some interesting stuff for you to do until the next post is up: go to the CP/M section in RetroArchive and go get some CP/M software for your MSX! You’ll find applications like dBase IIWordStarTurbo PascalFortranLISPMicrosoft BASIC, etc…

Here’s the link: http://www.retroarchive.org/cpm/

Also, thanks to Laurenst Holst for compiling and making available lots of good information on MSX-DOS.


This series of articles is supported by your donations. If you’re willing and able to donate, please visit the link below to register a small pledge. Every little amount helps.

Javi Lavandeira’s Patreon page

Leave a Reply

Your email address will not be published. Required fields are marked *