20

I'm using Open Watcom 1.9 to write some 286 real mode, and 386 protected mode software for DOS in C.

In real mode, I can retrieve a pointer to the system ROM character bitmaps in real mode using int 10h where AX = 1130h:

const uint8_t far *video_get_font_pointer(int font_type)
{
   union REGPACK regs;
   regs.w.ax = INT_VIDEO_10_GET_FONT_INFORMATION;
   regs.h.bh = font_type;
   intr(INT_VIDEO_10, &regs);
   
   seg_off_ptr ptr; // union with seg/off consecutive
   
   ptr.seg_off.s = regs.w.es;
   ptr.seg_off.o = regs.w.bp;
   
   return (const uint8_t far *)ptr.ptr;
}

I've tried using the same approach in protected mode (under the assumption that conventional memory/memory mapped devices like VGA can be accessed flat in Open Watcom), but the resulting es and bp don't seem to be correct if I manually combine them to make the pointer value ((es << 4) + bp).

I've tried setting ds,es,fs,gs to zero in regs before calling intr() as they're unimportant but it didn't help.

How can I access these system font bitmaps in protected mode?

3
  • What are the values of es and bp? Are you running in 32-bit or 16-bit protected mode? I’d assume you need to use an appropriate conventional-memory far pointer selector, as protected-mode DOS programs don’t run in a linear address space. I don’t know how that’s done in OpenWatcom, though. Aug 3 at 15:36
  • The function is an example of how it works in real mode, which gives me the return value C000:0900 - within the video BIOS ROM. I want to know how I can adapt it to protected mode. In OW, protected mode programs can access the 0x00000-0x10000 range directly.
    – knol
    Aug 3 at 15:39
  • DPMI extenders tend to want you to follow their documentation for how to access real-mode services from a protected-mode application. Assuming you're using DOS/4GW or the drop-in compatible DOS/32A, have you checked the "%WATCOM%\DOS4GW.DOC, and in the online help under the DOS/4GW section of the user's guide" recommended by Paul Hsieh's Watcom C/C++ FAQ for DPMI-related questions?
    – ssokolow
    Aug 3 at 17:48
20

In OpenWatcom, which was the example given in the question, the intr function performs transparent translation between segment values and protected-mode selectors, presumably using DPMI service 0x0002. This means that for example this program runs correctly whether compiled for real or protected mode:

#include <stdio.h>
#include <string.h>
#include <i86.h>

int main(int argc, char *argv[]) {
    union REGPACK r;
    unsigned char _far *font;
    int i;

    memset(&r, 0, sizeof(r));
    r.w.ax = 0x1130;
    r.h.bh = 6;
    intr(0x10, &r);

    printf("%04x:%04x\n", r.w.es, r.w.bp);

    font = MK_FP(r.w.es, r.w.bp);
    for (i = 0; i < 64; ++i) {
        printf("%02x ", font[i]);
        if ((i & 15) == 15)
            printf("\n");
    }

    return 0;
}

Its output on my laptop when compiled for real mode (with wcl) is:

c000:9761
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 7e 81 a5 81 81 bd 99 81 81 7e 00 00 00 00 
00 00 7e ff db ff ff c3 e7 ff ff 7e 00 00 00 00 
00 00 00 00 6c ee fe fe fe 7c 38 10 00 00 00 00 

And this is the output when compiled for protected mode (with wcl386) and run under DOS/4GW’s DPMI host:

01c8:9761
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 7e 81 a5 81 81 bd 99 81 81 7e 00 00 00 00 
00 00 7e ff db ff ff c3 e7 ff ff 7e 00 00 00 00 
00 00 00 00 6c ee fe fe fe 7c 38 10 00 00 00 00 

While this is the output when the protected-mode program is run under Windows 3.11:

0287:9761
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 7e 81 a5 81 81 bd 99 81 81 7e 00 00 00 00 
00 00 7e ff db ff ff c3 e7 ff ff 7e 00 00 00 00 
00 00 00 00 6c ee fe fe fe 7c 38 10 00 00 00 00 

The only difference is the value of the ES register, which is a segment value in real mode and a selector in protected mode. The actual interrupt handler returns a real-mode segment; it’s the DPMI host which translates it to a protected-mode selector on behalf of the C library. The selector points to the same memory as the segment does in real mode; the only difference is that you cannot perform segment arithmetic on selectors.

Other APIs may fail to perform such transparent translation. When that happens, you may have to either manually invoke the appropriate DPMI calls to allocate selectors (service 0x0000 to allocate, then service 0x0007 to set the base address and service 0x0008 to set the segment limit), or use a pre-allocated selector for the entire range of conventional memory, and use linear memory addresses as offsets relative to that selector. Open Watcom C/C++ Programmer’s Guide §5.4 describes how DOS extenders provide such pre-allocated selectors for that purpose, but you will need to check whether you are in fact running under that extender’s DPMI implementation to know whether you can actually use them. (While under DOS/4GW selector 0 addresses conventional memory, in a Windows DOS box it is an unusable null selector that will crash your program! Beware.)

4
  • I see! I think the key here is the MK_FP macro which uses the 'base' operator :> to reconstruct the appropriate pointer type with respect to the processor mode. As you say, es is a selector not a direct numeric value as I expected so that's why my code didn't work.
    – knol
    Aug 3 at 19:29
  • Also MK_FP(seg,ptr) returns a pointer with the _far attribute - I didn't know that protected mode pointers also had a _far counterpart which included a segment (selector). I assumed the keyword was #defined to empty string.
    – knol
    Aug 3 at 19:57
  • 2
    Protected mode per-se doesn’t obviate the need for _far pointers. Windows 32 doesn’t have anything like _far pointers and a real MK_FP() because Windows (and Linux) chooses to use only one selector value for everybody and everything (mostly); a segment declared as wide as the whole address space; since you will never update selector registers, you don’t need to track selectors in _far pointers anymore. All pointers are _near. Protected mode doesn’t mandate the use of a flat model, it’s just easier that way for everybody (x64 does mandate a flat memory model because of AMD’s pragmatism) Aug 4 at 0:55
  • 2
    @knol What Euro said. Also, there is a 16-bit protected mode, in which far pointers are as present as they ever were in real mode, and have the same form, only with a different interpretation (16-bit selector, 16-bit offset). Aug 4 at 6:30
9

I've accepted user3840170's answer as it solved my issue but I want to add a second answer that contains just the concepts that I lacked in order to solve my problem:

  • The far keyword is still applicable in protected mode, having a similar role to its role in real mode. It allows a pointer to point to another segment through a selector as well as an address. Most pointers in protected mode code are not far - they work in the same large segment as the rest of your own code, but it is possible for pointers to regions outside of that segment to exist. In this case, the const uint8_t * pointer into the Video BIOS I'm trying to retrieve must be a const uint8_t far *.
  • In real mode the relation between segment, offset and the machine address space is linear, and can be manipulated with shifts (to get the linear address, as a DMAC parameter) or unions (assigning the two components of the pointer to fields to produce a far pointer when they're combined). In protected mode, that relationship is not valid. The MK_FP macro or :> operator (which MK_FP uses) are necessary to combine the separately obtained two parts of a far pointer together. (You should use these in real mode code anyway instead of trying to be clever with either of the other two methods. :)
  • The interrupt correctly returns the segment in es and the offset in bp as usual. Zeroing the REGPACK before the intr() call is necessary. The es is a segment selector in protected mode, and the pair es:bp forms a valid far pointer in protected mode when MK_FP is used. That's the main difference between the code in my question and the code in user3840170's answer, and why theirs works!
2
  • I believe the zeroing is necessary because the intr function performs reverse translation on entry. If you have uninitialised garbage in the segment/selector register fields, the translation is going to fail. (Before I added the memset, DOS/4GW crashed with a GPF at the intr call.) Aug 5 at 12:44
  • The relevant text from the Watcom C Library Reference: Before the interrupt, the CPU registers are loaded from the structure located by regs. All of the segment registers must contain valid values. Failure to do so will cause a segment violation when running in protect mode. If you don’t care about a particular segment register, then it can be set to 0 which will not cause a segment violation. Following the interrupt, the structure located by regs is filled with the contents of the CPU registers.
    – knol
    Aug 5 at 12:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.