Relearning MSX #21: Programming for MSX-DOS (part 1)
Posted by Javi Lavandeira in How-to, MSX, Retro, Technology | August 28, 2015In 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 II, Altair 8800, Amstrad PCW, Osborne 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).
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:
How to execute system calls
Executing a system call is a very simple process:
- Load the number of the function in the Z80’s C register
- If required by the function, load other registers with the relevant data
- 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:
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 number | Name | MSX-DOS version | CP/M compatible | Purpose |
---|---|---|---|---|
00h | _TERM0 | MSX-DOS | Yes | Program terminate |
01h | _CONIN | MSX-DOS | Yes | Console input |
02h | _CONOUT | MSX-DOS | Yes | Console output |
03h | _AUXIN | MSX-DOS | Yes | Auxiliary input |
04h | _AUXOUT | MSX-DOS | Yes | Auxiliary output |
05h | _LSTOUT | MSX-DOS | Yes | Printer output |
06h | _DIRIO | MSX-DOS | Yes | Direct console I/O |
07h | _DIRIN | MSX-DOS | Direct console input | |
08h | _INNOE | MSX-DOS | Console input without echo | |
09h | _STROUT | MSX-DOS | Yes | String output |
0Ah | _BUFIN | MSX-DOS | Yes | Buffered line input |
0Bh | _CONST | MSX-DOS | Yes | Console status |
0Ch | _CPMVER | MSX-DOS | Yes | Return version number |
0Dh | _DSKRST | MSX-DOS | Yes | Disk reset |
0Eh | _SELDSK | MSX-DOS | Yes | Select disk |
0Fh | _FOPEN | MSX-DOS | Yes | Open file (FCB) |
10h | _FCLOSE | MSX-DOS | Yes | Close file (FCB) |
11h | _SFIRST | MSX-DOS | Yes | Search for first entry (FCB) |
12h | _SNEXT | MSX-DOS | Yes | Search for next entry (FCB) |
13h | _FDEL | MSX-DOS | Yes | Delete file (FCB) |
14h | _RDSEQ | MSX-DOS | Yes | Sequential read (FCB) |
15h | _WRSEQ | MSX-DOS | Yes | Sequential write (FCB) |
16h | _FMAKE | MSX-DOS | Yes | Create file (FCB) |
17h | _FREN | MSX-DOS | Yes | Rename file (FCB) |
18h | _LOGIN | MSX-DOS | Yes | Get login vector |
19h | _CURDRV | MSX-DOS | Yes | Get current drive |
1Ah | _SETDTA | MSX-DOS | Yes | Set disk transfer address |
1Bh | _ALLOC | MSX-DOS | Get allocation information | |
21h | _RDRND | MSX-DOS | Yes | Random read (FCB) |
22h | _WRRND | MSX-DOS | Yes | Random write (FCB) |
23h | _FSIZE | MSX-DOS | Yes | Get file size (FCB) |
24h | _SETRND | MSX-DOS | Yes | Set random record (FCB) |
26h | _WRBLK | MSX-DOS | Random block write (FCB) | |
27h | _RDBLK | MSX-DOS | Random block read (FCB) | |
28h | _WRZER | MSX-DOS | Yes | Random write with zero fill (FCB) |
2Ah | _GDATE | MSX-DOS | Get date | |
2Bh | _SDATE | MSX-DOS | Set date | |
2Ch | _GTIME | MSX-DOS | Get time | |
2Dh | _STIME | MSX-DOS | Set time | |
2Eh | _VERIFY | MSX-DOS | Set/reset verify flag | |
2Fh | _RDABS | MSX-DOS | Absolute sector read | |
30h | _WRABS | MSX-DOS | Absolute sector write | |
31h | _DPARM | MSX-DOS2 | Get disk parameters | |
40h | _FFIRST | MSX-DOS2 | Find first entry | |
41h | _FNEXT | MSX-DOS2 | Find next entry | |
42h | _FNEW | MSX-DOS2 | Find new entry | |
43h | _OPEN | MSX-DOS2 | Open file handle | |
44h | _CREATE | MSX-DOS2 | Create file handle | |
45h | _CLOSE | MSX-DOS2 | Close file handle | |
46h | _ENSURE | MSX-DOS2 | Ensure file handle | |
47h | _DUP | MSX-DOS2 | Duplicate file handle | |
48h | _READ | MSX-DOS2 | Read from file handle | |
49h | _WRITE | MSX-DOS2 | Write to file handle | |
4Ah | _SEEK | MSX-DOS2 | Move file handle pointer | |
4Bh | _IOCTL | MSX-DOS2 | I/O control for devices | |
4Ch | _HTEST | MSX-DOS2 | Test file handle | |
4Dh | _DELETE | MSX-DOS2 | Delete file or subdirectory | |
4Eh | _RENAME | MSX-DOS2 | Rename file or subdirectory | |
4Fh | _MOVE | MSX-DOS2 | Move file or subdirectory | |
50h | _ATTR | MSX-DOS2 | Get/set file attributes | |
51h | _FTIME | MSX-DOS2 | Get/set file date and time | |
52h | _HDELETE | MSX-DOS2 | Delete file handle | |
53h | _HRENAME | MSX-DOS2 | Rename file handle | |
54h | _HMOVE | MSX-DOS2 | Move file handle | |
55h | _HATTR | MSX-DOS2 | Get/set file handle attributes | |
56h | _HFTIME | MSX-DOS2 | Get/set file handle date and time | |
57h | _GETDTA | MSX-DOS2 | Get disk transfer address | |
58h | _GETVFY | MSX-DOS2 | Get verify flag setting | |
59h | _GETCD | MSX-DOS2 | Get current directory | |
5Ah | _CHDIR | MSX-DOS2 | Change current directory | |
5Bh | _PARSE | MSX-DOS2 | Parse pathname | |
5Ch | _PFILE | MSX-DOS2 | Parse filename | |
5Dh | _CHKCHR | MSX-DOS2 | Check character | |
5Eh | _WPATH | MSX-DOS2 | Get whole path string | |
5Fh | _FLUSH | MSX-DOS2 | Flush disk buffers | |
60h | _FORK | MSX-DOS2 | Fork a child process | |
61h | _JOIN | MSX-DOS2 | Rejoin parent process | |
62h | _TERM | MSX-DOS2 | Terminate with error code | |
63h | _DEFAB | MSX-DOS2 | Define abort exit routine | |
64h | _DEFER | MSX-DOS2 | Define disk error handler routine | |
65h | _ERROR | MSX-DOS2 | Get previous error code | |
66h | _EXPLAIN | MSX-DOS2 | Explain error code | |
67h | _FORMAT | MSX-DOS2 | Format a disk | |
68h | _RAMD | MSX-DOS2 | Create or destroy RAM disk | |
69h | _BUFFER | MSX-DOS2 | Allocate sector buffers | |
6Ah | _ASSIGN | MSX-DOS2 | Logical drive assignment | |
6Bh | _GENV | MSX-DOS2 | Get environment item | |
6Ch | _SENV | MSX-DOS2 | Set environment item | |
6Dh | _FENV | MSX-DOS2 | Find environment item | |
6Eh | _DSKCHK | MSX-DOS2 | Get/set disk check status | |
6Fh | _DOSVER | MSX-DOS2 | Get MSX-DOS version number | |
70h | _REDIR | MSX-DOS2 | Get/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 II, WordStar, Turbo Pascal, Fortran, LISP, Microsoft 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