Input/Output Device Driver Reference/Joystick Device Driver

The Advanced OS/2 Joystick Device Driver has two main purposes:
 * To provide a reliable joystick device driver for DOS games running under OS/2.
 * To provide a standard device driver for OS/2 game programmers that they can use as an interface between the PC game adapter port and their products.

Understanding How the IBM PC Game-Port Adapter Works
The IBM PC game-port adapter provides a simple interface to joysticks and other similar analog devices. The status of up to four analog and four digital signals is read through I/O port address 201h. Two analog and two digital signals are commonly grouped together in the form of a dual-axis joystick with two "fire" buttons. Some modern joysticks use two axes for movement and an additional axis for throttle.

Each analog and digital signal is assigned one bit in the game adapter port. A low (binary zero) value for a bit connected to a joystick button is returned when that button is depressed. Bits representing analog signals are implemented as one-shot outputs. All one-shots are fired when the I/O port is written to and remain high until an RC timing circuit decays. The variable position of each joystick axis provides the necessary resistance for this delay circuit. If a joystick axis is not connected, the infinite resistance across that signal holds the one-shot high indefinitely.

Determining the position of each joystick axis is done by measuring the duration of the high state for each one-shot. Two methods are commonly used to measure this duration: counting loop iterations and reading the hardware timer. Although the iterative method is dependant on the speed of the processor, this limitation becomes irrelevant with proper calibration of each axis. This is the method chosen to sample the game-port adapter in this device driver.

Understanding How the Joystick Device Driver Works
The pre-emptive multi-tasking nature of OS/2 can cause DOS applications reading the game adapter port to deduce incorrect positions for the joysticks connected. The solution implemented in this device driver to fix this is to regularly sample the game adapter port inside the Physical Device Driver (PDD) and then return fabricated values to the DOS application that reflect that state. With the logic for regularly sampling the port already in the PDD, all that must be done in that regard is to activate that procedure.

Before any of this can be initiated, the first thing that must be done is to trap any access to the game port by the DOS application. Any time I/O ports are to be trapped or emulated, an OS/2 Virtual Device Driver (VDD) is required. The VDD works in conjunction with the PDD to provide the device emulation by capturing port reads and returning values that reflect the current state as determined by the PDD. The VDD is also responsible for determining whether or not a given virtual DOS machine (VDM) is to have direct access to the port and, if so, for obtaining ownership of it.

At device initialization, the VDD first opens the PDD and then retrieves a pointer to the data area that will be used to store the current state of the device. The next job for the VDD comes when a DOS session is started - at which time, it hooks the I/O port for the game adapter. Knowing that the device must be written to by the CPU to fire the one-shots before the read sequence can begin, the hook handler for the write operation is the function where device ownership or emulation is resolved.

If the GAME_DIRECT_ACCESS DOS property is ON for that session, this specifies that the device is to be directly accessed by the VDM. In this case, the VDD must first obtain ownership of the device before allowing this operation to proceed. Device ownership is one of the more complex issues handled by this device driver. It first involves getting permission from the PDD, which can only be granted if the device is presently unused either by an OS/2 application or by another VDM through the emulation system. If not available, the user is given the choice to kill the session, wait for the device to become free, or ignore that error and use the device in emulation mode. If the user chooses to wait for the device to become free, the DOS session process will be blocked on an event semaphore to be posted when the device is available.

Once the VDD is granted ownership of the device by the PDD, it then must arbitrate final ownership amongst all VDMs competing for it. A mutual exclusion (MUTEX) semaphore is used as the final decision maker in this process and the VDM that is granted ownership of it is given the same status with game adapter port. When the DOS application that owns the device terminates, this MUTEX is released-allowing any other VDMs waiting for it to compete for ownership once again. If no VDMs are waiting, the ownership of the device is returned to none through a call to the PDD.

When the GAME_DIRECT_ACCESS property for a VDM is in its default state of OFF and the game adapter port is written to, the emulation system is activated. First the VDD informs the PDD that it wishes to become a client of the sampling procedure. If no other clients are already active, this request forces an initial sampling so that valid data is immediately available to the VDD. Next the VDD copies the game-port data from the PDD space to its own VDM instance data area using the pointer retrieved at initialization. This copy of the data will be used when the DOS application reads the game port.

Next, the emulation system uses the other DOS property registered at initialization: GAME_DIGITAL_RESPONSE If set to ON, the joysticks connected are emulated as fixed-resistance style joysticks. In this case, the values for each axis are selected from a field in the PDD data area that corresponds to the range the actual value is presently within. That is, if the current value for an axis is less than the upper limit for the lower range, a given fixed value for the lower range is chosen.

With the given value for each axis in hand, the VDD is ready to emulate the game-port adapter when read by the DOS application. First the count on the number of reads is zeroed as the last operation in the port write handler. When the port read handler is called, the value for each axis is compared against the number of reads by this VDM and if greater, a one for that bit is generated. The state of each position bit mirrors the value returned by the actual hardware when the PDD last sampled it on that iteration. The bits for each of the buttons is retrieved from the PDD data area and used in generating the byte returned. This information is transferred on each read to maintain a "freshness" in the button data.

Adjusting the Device Driver's Settings
This section describes how to adjust the device driver's settings.

DOS-Settings Adjustment
The joystick device-driver installation adds two options to your DOS-Settings notebook. To change these options, simply bring up the settings notebook for a particular DOS-session icon, choose the Session tab, and click on the DOS-Settings button.


 * GAME_DIRECT_ACCESS
 * ON
 * This setting essentially disables the device driver by giving the DOS program direct access to the game port. You should only enable this setting if you are having problems with control of a DOS game and suspect that the joystick device driver may be at fault. This is also useful for testing the difference between having the device driver enabled and disabled for a particular game.
 * OFF
 * The default setting. This prevents the DOS program from talking directly to the game port and thus allows the joystick device driver to do its work.


 * GAME_DIGITAL_RESPONSE
 * ON
 * The default setting. When this option is enabled, the device driver reports information back to the DOS program in such a way as to make your joystick look digital even if it is analog.
 * OFF
 * Analog joysticks will look like analog joysticks.

Manual Response-Settings Adjustment
The values that the device driver returns to the application using it can be manually adjusted in case of unusual behavior. For example, some games may act as if the joystick is being held constantly toward one direction. If this happens, switch out of the running DOS application (e.g., by using CTRL+ESC) and execute the JOYTUNE.EXE program found in the MPCA subdirectory.

The program displays the values that the joystick device driver currently responds with when in Digital-Response mode. If you choose to change these values, you are prompted first for X-axis values then for Y-axis values. Enter three numbers for each axis representing the values that should be returned to the game or application.

Modifying these values may improve or resolve odd joystick-response problems. For example, if your program acts as if the joystick is being held constantly towards the upper left, then values the joystick device driver is returning for the centered position are too low for each axis. Try raising the middle value for each axis using JOYTUNE. You will probably need to adjust the other values as well. In most cases, you will be able to simply switch back and forth between the game that you are trying to use and the JOYTUNE program until the adjustment is refined.

Limitations
There seems to be a limitation in the number of option settings that can be added to the DOS-Settings notebook. If nothing happens when you click on the DOS-Settings button, then you have most likely run into this limitation. Our only suggestion at this point is that you remove other virtual device drivers that add options to this notebook from your CONFIG.SYS.

Using the Interface
Defining and implementing an API for OS/2 applications is one of the two primary functions of this device driver. After opening the device, applications communicate with the device driver via the OS/2 device IOCtl interface. Functions are provided for getting and setting device parameters, reading the state of the devices connected, and calibrating those devices.

The device driver samples the game-port adapter inside the realtime clock timer handler. This handler is registered with the operating system at device initialization and is called every 32 milliseconds thereafter. It determines whether or not the game-adapter port requires sampling based on client demand and the current sampling rate. Client demand for the sampling procedure only exists if an OS/2 application has the device open or if a VDM is emulating the port.

The joysticks connected are determined and calibrated as part of the device initialization. These parameters can be modified by an OS/2 application or refreshed through a calibration IOCtl request.

In addition to the standard device-read function, functions are also defined for returning the state of the device connected at the next button press or at the next timed read. These functions block the calling process until the given event occurs when the process is run from inside the timer handler.

Usage Examples
Interfacing with the device driver is accomplished rather straightforwardly through IOCtl functions after opening the new GAME$ device.


 * Note:Versions of the joystick device driver prior to 0.2a only have stub open, read, and close routines enabled. Do your testing with device driver version 0.2a or later.

1.Open the device: HFILE  hGame; ULONG  action; APIRET rc;

rc = DosOpen( GAMEPDDNAME,                       // "GAME$"              &hGame,              &action,              0,              FILE_READONLY,              FILE_OPEN,              OPEN_ACCESS_READONLY | OPEN_SHARE_DENYNONE,              NULL );

if( rc != 0 ) { // ERROR opening device: result code in rc } 2.Get the version number of the device driver: HFILE  hGame; ULONG  version; ULONG  dataLen; APIRET rc;

dataLen = sizeof( version ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_GET_VERSION,                // 0x80, 0x01                  NULL,                  0,                  NULL,                  &version,                  dataLen,                  &dataLen );

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } 3.Get device-driver parameters: // Below defined in JOYOS2.H // In use bitmasks originating in 1.0
 * 1) define GAME_USE_BOTH_OLDMASK    0x01   // For backward compatibility with Boolean
 * 2) define GAME_USE_X_NEWMASK       0x02
 * 3) define GAME_USE_Y_NEWMASK       0x04
 * 4) define GAME_USE_X_EITHERMASK    ( GAME_USE_X_NEWMASK | GAME_USE_BOTH_OLDMASK )
 * 5) define GAME_USE_Y_EITHERMASK    ( GAME_USE_Y_NEWMASK | GAME_USE_BOTH_OLDMASK )
 * 6) define GAME_USE_BOTH_NEWMASK    ( GAME_USE_X_NEWMASK | GAME_USE_Y_NEWMASK )

// Only timed sampling implemented in version 1.0
 * 1) define GAME_MODE_TIMED          1                // Timed sampling
 * 2) define GAME_MODE_REQUEST        2                // Request-driven sampling

// Only raw implemented in version 1.0
 * 1) define GAME_DATA_FORMAT_RAW     1                // [l,c,r]
 * 2) define GAME_DATA_FORMAT_SIGNED  2                // [-l,0,+r]
 * 3) define GAME_DATA_FORMAT_BINARY  3                // {-1,0,+1}
 * 4) define GAME_DATA_FORMAT_SCALED  4                // [-10,+10]

// Parameters defining the operation of the device driver typedef struct {               USHORT  useA;                      // New bitmasks:  see above USHORT useB; USHORT mode;                      // See constructs above USHORT format;                    // See constructs above USHORT sampDiv;                   // Sample frequency = 32 / n                USHORT  scale;                     // Scaling factor USHORT res1;                      // Must be 0 USHORT res2;                      // Must be 0 }             GAME_PARM_STRUCT;

//-- HFILE            hGame; GAME_PARM_STRUCT gameParms; ULONG            dataLen; APIRET           rc;

dataLen = sizeof( gameParms ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_GET_PARMS,                 // 0x80, 0x02                  NULL,                  0,                  NULL,                  &gameParms,                  dataLen,                  &dataLen);

if( rc != 0 ) { // ERROR in IOCtl: result code in rc } 4.Set device-driver parameters: // See section above from JOYOS2.H HFILE            hGame; GAME_PARM_STRUCT gameParms; ULONG            parmLen; APIRET           rc;

parmLen = sizeof( gameParms ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_SET_PARMS,                 // 0x80, 0x03                  &gameParms,                  parmLen,                  &parmLen,                  NULL,                  0,                  NULL );

if( rc != 0 ) { // ERROR in IOCtl: result code in rc } 5.Get calibration values for joystick(s): // Below defined in JOYOS2.H // 1-D position struct used for each axis typedef SHORT GAME_POS;                // Some data formats require signed values

// Struct to be used for calibration and digital response on each axis typedef struct {               GAME_POS  lower; GAME_POS centre; GAME_POS upper; }             GAME_3POS_STRUCT;

// Calibration values for each axis: //    upper limit on value to be considered in lower range //    centre value //    lower limit on value to be considered in upper range typedef struct {               GAME_3POS_STRUCT  Ax; GAME_3POS_STRUCT Ay; GAME_3POS_STRUCT Bx; GAME_3POS_STRUCT By; }             GAME_CALIB_STRUCT;

//-- HFILE             hGame; GAME_CALIB_STRUCT gameCalib; ULONG             dataLen; APIRET            rc;

dataLen = sizeof( gameCalib ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_GET_CALIB,                 // 0x80, 0x04                  NULL,                  0,                  NULL,                  &gameCalib,                  dataLen,                  &dataLen);

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } 6.Calibrate joystick(s)-set calibration values: // See section above from JOYOS2.H

// For each stick: //   tell user to centre joystick and press button //   call get status with wait //   tell user to move to upper left and press button //   call get status with wait //   tell user to move to lower right and press button //   call get status with wait // Then call set calibration IOCTL with these values

HFILE             hGame; GAME_CALIB_STRUCT gameCalib; ULONG             parmLen; APIRET            rc;

parmLen = sizeof( gameCalib ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_SET_CALIB,                     // 0x80, 0x05                  &gameCalib,                  parmLen,                  &parmLen,                  NULL,                  0,                  NULL );

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } 7.Get VDM digital response values for joystick(s): // Below defined in JOYOS2.H

// 1-D position struct used for each axis typedef SHORT GAME_POS;                 // Some data formats require signed values

// Struct to be used for calibration and digital response on each axis typedef struct {               GAME_POS  lower; GAME_POS centre; GAME_POS upper; }             GAME_3POS_STRUCT;

// Struct defining the digital response values for all axes typedef  struct {                  GAME_3POS_STRUCT    Ax ; GAME_3POS_STRUCT   Ay ; GAME_3POS_STRUCT   Bx ; GAME_3POS_STRUCT   By ; }                GAME_DIGSET_STRUCT ;

//-- HFILE              hGame; GAME_DIGSET_STRUCT gameDigset; ULONG              dataLen; APIRET             rc;

dataLen = sizeof( gameDigset ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_GET_DIGSET,                // 0x80, 0x06                  NULL,                  0,                  NULL,                  &gameDigset,                  dataLen,                  &dataLen );

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } 8.Set VDM digital response values for joystick(s): // See section above from JOYOS2.H

HFILE              hGame; GAME_DIGSET_STRUCT gameDigset; ULONG              parmLen; APIRET             rc;

parmLen = sizeof( gameDigset ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_SET_DIGSET,                // 0x80, 0x07                  &gameDigset,                  parmLen,                  &parmLen,                  NULL,                  0,                  NULL );

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } 9.Get the status of the joystick(s): // Below defined in JOYOS2.H // 1-D position struct used for each axis

typedef SHORT GAME_POS;                   // Some data formats require signed values

// Simple 2-D position for each joystick

typedef struct {               GAME_POS  x;                GAME_POS  y;              } GAME_2DPOS_STRUCT;

// Struct defining the instantaneous state of both sticks and all buttons

typedef struct {               GAME_2DPOS_STRUCT  A;                GAME_2DPOS_STRUCT  B;                USHORT             butMask; }             GAME_DATA_STRUCT;

// Status struct returned to OS/2 applications: // Current data for all sticks as well as button counts since last read

typedef struct {               GAME_DATA_STRUCT  curdata; USHORT           b1cnt; USHORT           b2cnt; USHORT           b3cnt; USHORT           b4cnt; }             GAME_STATUS_STRUCT;

//--

HFILE              hGame; GAME_STATUS_STRUCT gameStatus; ULONG              dataLen; APIRET             rc;

dataLen = sizeof( gameStatus ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_GET_STATUS,                // 0x80, 0x10                  NULL,                  0,                  NULL,                  &gameStatus,                  dataLen,                  &dataLen );

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } 10.Get the status of the joystick(s) at next button press: // See section above from JOYOS2.H // NOTE: this call will block

HFILE              hGame; GAME_STATUS_STRUCT gameStatus; ULONG              dataLen; APIRET             rc;

dataLen = sizeof( gameStatus ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_GET_STATUS_BUTWAIT,        // 0x80, 0x11                  NULL,                  0,                  NULL,                  &gameStatus,                  dataLen,                  &dataLen );

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } 11.Get the status of the joystick(s) at next sample (depends on mode): // See section above from JOYOS2.H // NOTE: this call will block

HFILE              hGame; GAME_STATUS_STRUCT gameStatus; ULONG              dataLen; APIRET             rc;

dataLen = sizeof( gameStatus ); rc = DosDevIOCtl( hGame,                 IOCTL_CAT_USER,                  GAME_GET_STATUS_SAMPWAIT,       // 0x80, 0x12                  NULL,                  0,                  NULL,                  &gameStatus,                  dataLen,                  &dataLen );

if( rc != 0 ) { // ERROR in IOCtl:  result code in rc } Close the device: HFILE  hGame; APIRET rc;

rc = DosClose( hGame );

if( rc != 0 ) { // ERROR closing device:  result code in rc }