This is an old revision of the document!
NEC PC-98 C Code Examples
So you've got your NP21Kai PC-98 emulator up and running, or you've resurrected an ancient NEC PC-9801 or PC-9821 and you've decided you want to do some C programming for it.
Where do you start? Well, first, get a working development environment up and running. You can get one here.
Next you will want to figure out how to do some interesting things with the hardware, as otherwise it's just a normal command line DOS machine….
Non-hardware Specific Examples
Determine If Running on PC-98/DOS or PC/DOS
The macros ISPCAT() and ISPC98() are introduced in the patched version of the DJGPP C library. They can be used to check if your exe is running on either a IBM PC, or NEC PC-98.
The macros take one parameter, a global; crt0_mtype, which is set at call time with the machine type.
Example use follows below:
#include <stdio.h> #include <libc/pc9800.h> int main(){ int m; m = __crt0_mtype; if(ISPCAT(m)){ printf("Hello, PC world\n"); } if(ISPC98(m)){ printf("Hello, PC98 world\n"); } return 0; }
PC-9821 PEGC 256 Colour Graphics Hardware
Packed-Pixel or Chunky Video Modes
In order to access the PEGC linear framebuffer for packed-pixel mode, the PC-98 BIOS should have the following option set:
- “16MB System Space” or “Use memory hole at 15-16MB” or “Use memory region at 16MB”
The reason for this is that the PEGC hardware locates its framebuffer in the region between 15MB and 16MB in the x86 address space. From C you can detect whether this has been set or not by checking port 0x43B.
int vramHasMemoryHole(){ // Checks if the memory hole is set at 16MB to enable us to set the VRAM framebuffer int x; x = inp(0x043b); printf("x: %x\n", x); if (x & 0x04){ // Memory hole not present return -1; } else { // Memory hole present return 0; } }
If the memory space at 16MB is being used for RAM, then bit 2 will be set. If it is free to use to map the VRAM framebuffer, than it should be unset.
The difference in behaviour can be seen on an emulated PC-9821 system using NP21Kai, where printing the binary representation of the value of 0x43B can show the following:
Alternatively, on a machine on which the BIOS setting has been enabled, the value of 0x43B should be clear:
If the memory hole at 16MB is not present, or more than 16MB of RAM is present, then an alternative memory region needs to be used at 4095MB.
In conclusion:
- VRAM can be accessed as a single linear framebuffer at 16MB, if the BIOS option “Memory hole at 16MB” is enabled.
- VRAM can be accessed as a single linear framebuffer at 4095MB, if the BIOS option “Memory hole at 16MB” is disabled or >=16MB of RAM is fitted.
- VRAM must be accessed in a series of 32KB banks (I am not going to cover this method) if neither of the above are true, or the linear option is not enabled in your code.
DPMI Server Differences
In order to map IO memory (for example, our linear framebuffer, above) to a region which is addressable by the running application, the DPMI call __dpmi_physical_address_mapping() needs to be made, and this requires a running DPMI server.
Here is the results of that call on various PC-98 DPMI servers:
Source | Exe | Size | Reported DPMI Version | Result |
---|---|---|---|---|
Epson DOS 5.00 | DPMI32.EXE | 64752 | 1.00 | Call returns -1. Error. |
MS-DOS 5.00 | DPMI.EXE | 405566 | 1.00 | Call returns -1. Error. |
MS-DOS 6.22 | DPMI.EXE | 63792 | 1.04 | Call returns 0. Success. |
Enabling 256 Colour Mode
This fragment enables 256 colour packed-pixel mode on compatible PC-9821 hardware. IO ports in both the normal DOS memory space and in far memory need to be written to.
This is accomplished with outportb() (included in dos.h) for ports in the low memory range, and with _farpokeb() (included in farptr.h) for ports in the extended memory range.
// 256 colour packed-pixel mode #include <dos.h> #include <sys/farptr.h> #define PEGC_MODE_ADDR 0x6A // Normal address space #define PEGC_PIXELMODE_ADDR 0xE0100 // Register in PEGC space in far memory #define PEGC_BPPMODE_16c 0x20 // Enable 16 colour mode #define PEGC_BPPMODE_256c 0x21 // Enable 256 colour mode #define PEGC_PIXELMODE_PACKED 0x00 // Set for packed pixel mode #define PEGC_PIXELMODE_PLANAR 0x01 // Set for planar pixel mode #define PEGC_BPPMODE_DUAL_PAGE 0x68 // Enable single graphics page (in 640x400) #define PEGC_BPPMODE_SINGLE_PAGE 0x69 // Enable single graphics page (in 640x400 or 640x480) outportb(PEGC_MODE_ADDR, 0x07); outportb(PEGC_MODE_ADDR, PEGC_BPPMODE_256c); outportb(PEGC_MODE_ADDR, PEGC_BPPMODE_DUAL_PAGE); outportb(PEGC_MODE_ADDR, 0x06); // Enable Packed Pixel mode _farpokeb(_dos_ds, PEGC_PIXELMODE_ADDR, PEGC_PIXELMODE_PACKED);
Enabling Linear Framebuffer
As mentioned earlier, by default, the VRAM address space is split into 32KB banked chunks, which need to be swapped in and out of the low memory address space (<1MB) in turn. However, the PC-9821 has that elusive linear framebuffer mode that appears to be so difficult to access.
The following fragment enables linear framebuffer access on supported PC-9821 hardware:
// Enable linear framebuffer at 16MB and 4095MB #include <dos.h> #include <sys/farptr.h> #define PEGC_FB_CONTROL_ADDR 0xE0102 // Sets whether the linear VRAM framebuffer is at F00000h-F7FFFFh (set with 0x01) or not (0x00) #define PEGC_FB_ON 0x01 // Enable PEGC VRAM linear framebuffer #define PEGC_FB_OFF 0x00 // Disable PEGC VRAM linear framebuffer _farpokeb(_dos_ds, PEGC_FB_CONTROL_ADDR, PEGC_FB_ON);
The code is just that one line, but the following should be noted:
- If you have less than 16MB of RAM - the framebuffer should appear at 0x00F00000
- If you have > 16MB of RAM and have the BIOS option set “Do not use memory hole at 16MB as memory” - the framebuffer should appear at 0x00F00000
- If you have > 16MB of RAM and have not set the BIOS option “Do not use memory hole at 16MB as memory” - the framebuffer should appear at 0xFFF00000
- In all cases the framebuffer should also appear at 0xFFF00000
You can use the following code to detect if the memory hole at 16MB is in use or not, and alter the framebuffer address accordingly:
#include <dos.h> #define PEGC_FB_LOCATION_LOW 0x00F00000 // The VRAM framebuffer is located at 16MB if the 15-16MB hole is present #define PEGC_FB_LOCATION_HIGH 0xFFF00000 // The VRAM framebuffer is located at 4095MB if the 15-16MB hole is in use #define MEMORY_HOLE_CHECK_ADDR 0x043B // If bit 2 is set at this address, then the memory hole at 15-16MB is not present (it is being used by RAM) int x; x = inportb(MEMORY_HOLE_CHECK_ADDR); if (x & 0x04){ printf("%s.%d\t Memory hole at 16MB is not available\n", __FILE__, __LINE__); printf("%s.%d\t Using VRAM framebuffer at 0x%x\n", __FILE__, __LINE__, PEGC_FB_LOCATION_HIGH); pegc_fb_location = PEGC_FB_LOCATION_HIGH; } else { printf("%s.%d\t Memory hole at 16MB is available\n", __FILE__, __LINE__); printf("%s.%d\t Using VRAM framebuffer at 0x%x\n", __FILE__, __LINE__, PEGC_FB_LOCATION_LOW); pegc_fb_location = PEGC_FB_LOCATION_LOW; }
Note: Currently (as of August 2020), the common NP21Kai emulator always has the 16MB memory hole in use as RAM (the BIOS option to disable it is not available), so the above check always fails and the far framebuffer address is always used.