blog:pc98_devcode

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….

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;
}

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.


Mapping Framebuffer to DOS Memory


Writing to Framebuffer

  • blog/pc98_devcode.1598085554.txt.gz
  • Last modified: 2020/08/22 08:39
  • by john