I recently got an interesting email from an Atari fan named Wong CK. Wong has been programming the Atari ST computers as a hobby, and was interested in creating a GEM printer driver that would create PDF files. He had read some of my articles here and after not finding a lot of information elsewhere online, was hoping that I might be able to provide some useful information and advice.

Here’s a portion of Wong’s original message:

One of the software that I wanted to do is a GDOS printer driver to create a PDF file. On the web I found some PDF source code and so my idea was just to map the PDF library code to each VDI functions. I also researched on how to make a Atari GDOS printer driver but there was very little information. I found the now public released GEM 3 GDOS printer drivers as well as CPM GSX printer driver source codes, but I have not figured out what it needs to be done, confused futher by the assembly codes and the x86 codes as I program in C language. This is the stumbling block and I have been stuck at this stage for nearly 2 year plus. Even the guys over at Atari-forums.com do not know (or they are not telling).

I thought that a PDF driver was an interesting idea, and Wong’s request kind of overlapped a long- unresolved ambition of my own regarding GEM  drivers.  I replied to Wong, telling him that…

Well, actually, that’s sort of the point of the article so let’s just jump in.

I’m expecting this to be a four-part series, as outlined below. The good news is that I’ve already got parts 2 and 3 mostly done, so there hopefully shouldn’t be a huge delay between installments.

  • Part 1 – Overview of How GEM Works & How Device Drivers Are Called
  • Part 2 – The GDOS Printer Driver Kit
  • Part 3 – Creating A Basic GEM Device Driver Shell
  • Part 4 – Sample Device Driver

Beyond part 3 I don’t have it completely mapped out yet, so that could get expanded a bit when the time comes.

Back To The Beginning

We’re going to start by going back to the beginning and talking about some of the basic fundamentals about GEM VDI.  First, let’s recognize that there are two targets for an application’s VDI requests, VDI itself, and the device driver (for whatever device is involved). This idea ties into the original GEM VDI documentation from Digital Research.  On page 1-2, you’ll find this tidbit (sic):

GEM VDI is composed of two components:

* Graphics Device Operating System (GDOS)
* device drivers and face files

When you open a VDI workstation, you’re asking GDOS to do something.  It has to figure out what device driver is required for the request. For some devices like the screen, the driver may be in ROM, for others it might have to load it from disk.  Then it has to wait for the result of the “open workstation” request so it knows if it should unload the driver or not.

On the other hand, when you draw a circle, you’re not really asking VDI to do it. Really, you’re asking the device driver that’s in charge of the specified workstation to do it. In the latter case, VDI is responsible for routing the request to the correct device driver, but doesn’t otherwise involve itself in the drawing of the circle, because VDI knows nothing about what’s required to draw a circle on a particular device. That’s what the device driver is for.

Atari ST users have typically referred to GDOS as though it was some sort of bolted-on extra piece of GEM VDI that you didn’t need unless you wanted to use loadable fonts or device drivers for things like printers. There’s a grain of truth in there, but it’s also somewhat misleading, because what Atari users call “GDOS” actually is GEM VDI. The term “GDOS” is supposed to refer to everything that’s not a font or device driver, but that idea got corrupted on the Atari side of things for some reason. We used to say that the TOS ROM didn’t include GDOS. Maybe it would have been more accurate to say it didn’t include VDI.

The majority of the code in the Atari’s TOS ROM that everybody has traditionally referred to as “the VDI” is actually a device driver for the screen.  But the “GDOS” aka the rest of VDI is missing. The TOS ROM includes just a tiny piece of code, a mini-VDI you might call it, that catches the GEM system trap and passes through VDI commands to the screen driver.  It doesn’t know anything about other devices or drivers, doesn’t know how to load fonts, or do anything else. In fact, the assembly language source file for it is under 150 lines long.

How Does A VDI Request Get From Application To Driver?

GEM uses a “parameter block” to pass information back and forth between the application and the VDI.  This is a table of pointers to five arrays which contain the input parameters, and which receive the output parameters.  They are:

  • CONTROL
  • INTIN
  • PTSIN
  • INTOUT
  • PTSOUT

Each of these arrays consists of 16-bit values.  The CONTROL array is used for both input and output.  On input, it tells GEM what function is being requested, and how much information is contained in the INTIN and PTSIN arrays. When the function is done, it tells the application how much information was returned in the INTOUT and PTSOUT arrays.

The “PTS*” arrays are used to pass pixel coordinate values. These are always done in pairs. That is, there’s an x-axis value and a y-axis value. The CONTROL array specifies how many coordinate pairs are passed back and forth.

The “INT*” arrays are used to pass integer values back and forth.  The CONTROL array specifies how many values are in INTIN or INTOUT.

To call VDI, an application puts the required input parameters into the CONTROL, INTIN, and PTSIN arrays, then it loads the address of the GEM parameter block into register d1 of the 680×0 CPU, and the magic number $73 into register d0.  Finally, it calls trap #2.

Wondering what “trap #2” means? For you new kids who haven’t ever written assembly code, or accessed things at that low-level, most microprocessors since the 16-bit days have implemented the concept of a system trap.  This is a special processor instruction that causes the processor to jump to whatever code is pointed to by a specific, pre-defined pointer in memory.  It’s sort of an “interrupt on demand” and it allows system programmers to install bits of code that can be called without the calling program known where it resides in memory, as would otherwise need to be the case.

Here’s a bit of assembly code that demonstrates the definition of the parameter arrays, the parameter block, and the actual system trap call.  This assumes the arrays have been loaded with the correct parameters:

    .bss        ; Block storage segment, aka uninitialized data space
_control:       ; Adding underscore makes symbol accessible from C
    ds.w 20     ; Reserve space for 20 elements
_intin:
    ds.w 128    ; 128 elements
_ptsin:
    ds.w 128
_intout:
    ds.w 128
_ptsout:
    ds.w 128

    .data        ; initialized data
_VDIParams:
    dc.l _control
    dc.l _intin
    dc.l _ptsin
    dc.l _intout
    dc.l _ptsout

    .text
_VDI:
    move.l #_VDIParams,d1
    move.l #$73,d0
    trap   #2
    rts

That’s likely the first 68000 assembly code I’ve written in probably at least 15 years, maybe more… excuse me while I catch my breath…

The high-level language binding for a call like vsf_style might look like this:

WORD vsf_style( WORD wsHandle, WORD newStyle )
{
    control[0] = 24;       /* VDI opcode for this function */
    control[3] = 1;        /* # of values in INTIN array */
    control[6] = wsHandle; /* Workstation handle */
    intin[0] = newStyle;   /* Requested fill style */
    VDI();
    return intout[0];      /* return style actually set */
}

These are just examples, of course. Most of these details are generally managed by the function bindings that came with your C compiler (or whatever other language) so that most programmers creating GEM applications don’t have to worry about it, but it’s important for those of us who are doing system stuff like creating device drivers from scratch. We need to make sure the underlying concept is clear here because it ties into the big secret.

What’s the big secret?

The Big Secret

Here’s the big secret of GEM VDI. A secret that wasn’t really a secret, but which nevertheless it seems very few people properly understood, going by the questions that people still ask to this day.

A device driver’s point of entry, where it first starts executing code, is what sits on the other side of the trap #2 call.  Register d0 contains the VDI magic number, and register d1 contains a pointer to the parameter block.  So at that point it’s up to the driver to take that information and do something meaningful with it.

It’s that simple.

How GEM Calls The Device Driver

Oh, technically, the driver isn’t EXACTLY on the other side. The system trap call doesn’t actually point directly into the driver. That would be stupid. But from the driver’s point of view, it looks pretty much like that.

When the system trap is made, VDI/GDOS will first verify that it’s an API call by checking for the magic number in register d0.  If the magic number is found, VDI grabs the address of the parameter block from register d1.  The first entry is a pointer to the CONTROL array, where it grabs the workstation handle and the opcode of the function being requested.

Next, it looks at the function opcode to figure out if the request should be routed to the driver, or handled by GDOS.  Something like v_opnwk (Open Workstation) would be handled by GDOS, while v_pline (Draw Poly Line) would be handled by the driver.

For functions that need to be handled to the driver, GDOS first has to figure out which workstation and driver should receive the command.  GDOS maintains a table of information for each open workstation, including the entry point for the driver. It searches that table until it finds a matching workstation handle.  Then it simply grabs the driver’s entry point, and jumps into the driver.  Something like this:

;; Note that I'm not including important things like
;; saving and restoring registers in this sample code

_VDIEntryPoint:
    cmp.w  #$73,d0      ; d0 have magic number?
    beq.s  .VDIcall     ; no, so not a VDI call
    rte                 ; return from system trap

.VDIcall:
    move.l d1,a0        ; Get address of parameter block
    move.l (a0),a0      ; Get first entry in parameter block
    move.w (a0),d2      ; Get control[0] into register d2

;; At this point, we need to determine if the requested 
;; operation is a VDI/GDOS thing like opening a workstation 
;; or a device driver thing like drawing something.
;; That's too much code to include here, so just assume 
;; this comment does that and then jumps to the label 
;; below if it's a driver thing, and exits otherwise.

_DriverFunction:
    move.w 12(a0),d2      ; Get workstation handle from 
                          ; control[6] into register d2

;; OK, now VDI would search its internal data to find 
;; the workstation and device driver associated with the
;; workstation handle passed in. Again, too much code, so
;; let's just assume that we found the information and that
;; the driver entry point is now contained in register a0.

    jsr    (a0)           ; Jump to driver entry point
    rte                   ; Return back to application

Once it gets control, the driver is expected to do whatever is called for by the specific function opcode, and return whatever data is appropriate.

The big secret here is that VDI doesn’t really have any big secrets. The VDI manual pretty much tells you exactly what GDOS does and what’s expected of a drivers. It was actually pretty mundane stuff when you get down to it.

In The Next Installment

We’ll discuss the GDOS Printer Driver Kit that Atari sent out to some developers.  We’ll go over how one used it to create new drivers and why it’s not really that suitable as a general-purpose driver kit.

Related Articles