Detecting Video Adapters At Runtime

Marcus Johnson


Marcus Johnson received his B.S. in math from the University of Florida in 1978 and is currently Senior System Engineer for Prericision Software in Clearwater, Florida. You may contact him at 6258 99th Circle, Pinellas Park, Florida.

To self-configure for a user's hardware, your video code must first determine which video adapter is installed. Armed with this knowledge, the video code can determine whether or not color is appropriate (or even possible), how to best execute graphics, whether or not snow is a factor to be dealt with, whether or not the hardware can handle text modes with more than 25 lines, and so on.

The code presented here detects these display adaptor types: Monochrome Display Adaptor (MDA), Color Graphics Adaptor (CGA), Enhanced Graphics Adaptor (EGA), Multi-Color Graphics Array (MCGA), Video Graphics Array (VGA), Hercules Graphics Card, Hercules Graphics Card Plus, and Hercules In Color Card

The code also identifies these display types: MDA-compatible (monochrome), CGA-compatible (color), EGA-compatible (color), PS/2-compatible monochrome, and PS/2-compatible color.

The code presented also detects up to two video hardware systems and distinguishes which is active, and which is not.

Some of these characteristics can't be guaranteed. The assumption of color in the case of CGA and EGA compatible displays is just that, an assumption. The CGA video system has no idea what kind of monitor is attached, and EGA systems can be fooled. Also, there is no way to distinguish for all CGA cards and their clones whether or not the card is prey to the display interference problem of snow. Having discovered that the CGA is present, the programmer must either make an arbitrary decision to allow for snow or ignore its possibility, or have the user make that decision.

The Method

The identification process begins with calls to the enhanced video BIOS; first a call that only a VGA or MCGA equipped system will support, then one that an EGA card should support. These calls will only function with a video BIOS that supports these more sophisticated video systems; CGA, MDA and Hercules cards do not have enhanced video BIOS ROMs, and the calls should fail harmlessly, with all registers preserved. The enhanced BIOS calls will succeed, modifying the registers to identify the installed hardware. If the calls succeed, the job is done, or nearly so; it may be necessary to hunt for a CGA or MDA card of which the more sophisticated video system is unaware.

Next the routines attempt to identify the CRT controller (CTRC) status port's address in the PC's I/O space. The MDA and Hercules cards' CRTC status port is usually found at 3B4H, and the CGA cards' CRTC status port at 3D4H. The routines write an arbitrary value to the Cursor Location Low register (0Fh) and then, after a reasonable delay, attempt to read that value back from one of the status port addresses. If the value is recovered, the routines assume they have found the CTRC of the associated card.

In the case of the CGA cards and their clones, there is nothing left to do — that's all you can learn. If an MDA card is detected, however, you can also distinguish between an MDA card and the Hercules cards. To do this, the vertical sync bit is sampled and then for a certain time interval, is continually and frequently re-sampled. If no change is observed, the video card is assumed to be an MDA card. If the vertical sync bit changes, the video card is one of the Hercules graphics cards. The specific card is identified by examining bits 4-6 of the status port; a pattern of 001 indicates a Hercules Graphics Card Plus, a pattern of 101 indicates a Hercules In-Color Card, and any other pattern is a Hercules Graphics Card.

Distinguishing CGA from MDA and MDA from Hercules cards are both time-dependent tasks. How much delay is enough to insure the CGA card has had time to update the status port? How frequently must the code sample the vertical sync to ensure it hasn't just missed a transition? Will compiler generated code be fast enough to catch the sync transition at all? If the distinction between the MDA and Hercules cards is important enough, the vertical sync test might best be written in assembly language. In my software, because of these factors, and because I don't usually need to know the difference, I often use a modified version of this code that simply identifies whether or not the system has EGA graphics or better.

In any case, the identification of the video hardware, as with any code that attempts to exploit the hardware environment to its benefit, is not a risk-free undertaking. Even the seemingly straightforward matter of making BIOS calls assumes that the video BIOS will behave in the expected fashion, and in particular that undocumented calls will return with the caller's register values intact. These techniques are very specific to true IBMs. Nevertheless, this and other routines based on [1], have worked on every "100% PC-compatible" I've been able to test.

References

[1] Wilton, Richard, Programmer's Guide to PC and PS/2 Video Systems.

This article, and the C source code presented here, are based on Wilton's assembly language version. This excellent reference provides a reasonably complete answer to the problem of detecting and exploiting the video hardware and should be a standard reference for any programmer who wants or needs to know how to milk the PC's video hardware for all it's worth.

main.c

/*------------------------------------------------------------\
|       Marcus W. Johnson 1990                                |
|                                                             |
|       Code to demonstrate IdentifyVideo()                   |
\------------------------------------------------------------*/

#include     <stdio.h>
#include     "video.h"

/*  Print the name of the adaptor and the display device */
static void      Name(struct video *v)
       {
       switch (v->VideoAdaptor)
              {
              case   UnknownAdaptor:
                    puts("No Video Adaptor Detected");
                    break;
              case   MDA:
                    puts("Monochrome Display Adaptor");
                    break;
              case   CGA:
                    puts("Color Graphics Adaptor");
                    break;
              case   EGA:
                    puts("Enhanced Graphics Adaptor");
                    break;
              case   MCGA:
                    puts("Multi-Color Graphics Array");
                    break;
              case   VGA:
                    puts("Video Graphics Array");
                    break;
              case   HGC:
                    puts("Hercules Graphics Card");
                    break;
              case   HGCPlus:
                    puts("Hercules Graphics Card Plus");
                    break;
              case   HerculesInColor:
                    puts("Hercules InColor Card");
                    break;
              default:
                    puts("Program Error: Unidentified Video Adaptor");
                    break;
              }
       switch (v->VideoMonitor)
              {
              case   UnknownMonitor:
                    puts("No Monitor Detected");
                    break;
              case   MDAMonochrome:
                    puts("Monochrome Monitor");
                    break;
              case   CGAColor:
                    puts("CGA Color Monitor");
                    break;
              case   EGAColor:
                    puts("EGA Color Monitor");
                    break;
              case   PS2Monochrome:
                    puts("PS/2 Monochrome Monitor");
                    break;
              case   PS2Color:
                    puts("PS/2 Color Monitor");
                    break;
              default:
                    puts("Program Error: Unidentified Video Monitor");
                    break;
              }
       }
/*  Demonstates use of IdentifyVideo() */
void     main()
       {
       struct video    *v;
       
       v = IdentifyVideo();
       if (v->VideoAdaptor != UnknownAdaptor)
              {
              Name(v++);
              if (v->VideoAdaptor != UnknownAdaptor)
                     Name(v);
              
              }
       else
              puts("No known video adaptor or monitor");
       }

video.h

/*----------------------------------------------------\
|  Marcus W. Johnson 1990                             |
|                                                     |
|  Definitions of detected video systems and displays |
|                                                     |
|  adapted from:   Programmer's Guide to PC & PS/2    |
|                  Video Systems                      |
|                  Richard Wilton                     |
|                  Microsoft Press                    |
|                  Redmond, Washington                |
\-----------------------------------------------------*/
   
   enum    adaptor
          {
          UnknownAdaptor,
          MDA,
          CGA,
          EGA,
          MCGA,
          VGA,
          HGC,
          HGCPlus,
          HerculesInColor
          };
   
   enum    monitor
          {
          UnknownMonitor,
          MDAMonochrome,
          CGAColor,
          EGAColor,
          PS2Monochrome,
          PS2Color
          };
   
   struct video
          {
          enum adaptor   VideoAdaptor;
          enum monitor   VideoMonitor;
          };
 
   extern struct video    *IdentifyVideo(void);

video.c

/*--------------------------------------------------------------------\
|       Marcus W. Johnson 1990.                                       |
|                                                                     |
|       Code to identify video display adaptors and display devices   |
|                                                                     |
|       Adapted from:  Programmer's Guide to PC and PS/2 Video Systems|
|                      Richard Wilton                                 |
|                      Microsoft Press                                |
|                      Redmond, Washington                            |
\--------------------------------------------------------------------*/

#include       <dos.h>
#include       "video.h"

enum    boolean
       {
       NO,
       YES
       };

#define N_SYSTEMS    (2)

static struct video  Device[ N_SYSTEMS ];

/*---------------------------------------------------------------------\
|       Detects whether or not a given I/O address is that of a CRT    |
|       Controller; the Cursor Location Low register of the alleged    |
|       CRTC is written with an arbitrary value (I used the one Wilton |
|       uses in his Find6845 procedure), we wait an arbitrary period of|
|       time (I waited a millisecond), and we see if the value is still|
|       there, then this is probably the CRTC.                         |
\---------------------------------------------------------------------*/

static int      FindCRTC(int Port)
       {
       unsigned char  CursorLow;
       unsigned char  NewCursorLow;
       
       outportb(Port++, 0x0F);
       CursorLow = inportb(Port);
       outportb(Port, 0x66);
       delay(1);
       NewCursorLow = inportb(Port);
       outportb(Port, CursorLow);
       return (NewCursorLow == 0x66);
       }

/*---------------------------------------------------------------------\
|       Places the specified adaptor and monitor data in the next      |
|       unused element of the Device array.                            |
\---------------------------------------------------------------------*/

static void      FoundDevice(enum adaptor AType, enum monitor MType)
       {
       if (Device[ 0 ].VideoAdaptor == UnknownAdaptor)
              {
              Device[ 0 ].VideoAdaptor = AType;
              Device[ 0 ].VideoMonitor = MType;
              }
       else
              {
              Device[ 1 ].VideoAdaptor = AType;
              Device[ 1 ].VideoMonitor = MType;
              }
       }

/*---------------------------------------------------------------------\
|       Attempt to find a monochrome adaptor; attempts to detect a CRTC|
|       at I/O address at 0x3B4. If this is successful, we read the    |
|       vertical sync bit, and wait a while to see a transition; if it |
|       occurs, it's a plain monochrome display adaptor, otherwise it's|
|       a Hercules card of some sort. What type is decided by bits 4-6 |
|       of the CRTC port.                                              |
\---------------------------------------------------------------------*/

static void  DetectMono(void)
      {
      if {FindCRTC{0x3B4))
            {
            auto unsigned char    VSync;
            auto unsigned int     k;
            auto enum boolean     FoundIt;
            
            VSync = inportb(0x3BA)& 0x80;
            FoundIt = NO;
            for (k = 0; k < 0x8000; k++)
                  {
                  if (VSync != (inportb(0x3BA) a 0x80))
                        {
                        switch (inportb(0x3BA) & 0x70)
                              {
                              case 0x10:
                                  FoundDevice(HGCPlus, MDA);
                                  break;
                              case 0x50:
                                  FoundDevice(HerculesInColor,
                                        EGAColor);
                                  break;
                              default:
                                  FoundDevice(HGC, MDA);
                                  break;
                              }
                        FoundIt = YES;
                        break;
                        }
                  }
            if (FoundIt == NO)
                  FoundDevice(MDA, MDAMonochrome);
            }
      }

/*---------------------------------------------------------------------\
|       Attempt to find a CGA adaptor; if a CRTC is detected at I/O    |
|       address 3D4, must be CGA...                                    |
\---------------------------------------------------------------------*/

static void     DetectCGA(void)
       {
       if (FindCRTC(0x3D4))
              FoundDevice(CGA, CGAColor);
       }

/*---------------------------------------------------------------------\
|       Fills in the Device array and returns its address to the       |
|       caller, who can then examine its contents.                     |
\---------------------------------------------------------------------*/

struct video    *IdentifyVideo(void)
       {
       int             k;
       union REGS      r;
       
       for (k = 0; k < N_SYSTEMS; k++)
              {
              Device[ k ].VideoAdaptor = UnknownAdaptor;
              Device[ k ].VideoMonitor = UnknownMonitor;
              }
       
       /*------------------------------------------------------------\
       |      Attempt to detect PS/2-type systems by making a BIOS   |
       |      call to get the video display combination from the     |
       |      video BIOS. On return, the AL register is set to 1A,   |
       |      BL will contain the display code for the active display|
       |      and BH will contain the display code for the inactive  |
       |      display. The BL and BH registers are used to index     |
       |      arrays containing codes for the appropriate display and|
       |      display adaptor.                                       |
       \------------------------------------------------------------*/

r.x.ax = 0x1A00;
int86(0x10, &r, &r);
if (r.h.al == 0x1A)
       {
       static struct video     DeviceList[ ] =
              {
              {      UnknownAdaptor, UnknownMonitor  },
              {      MDA,            MDAMonochrome   },
              {      CGA,            CGAColor        },
              {      UnknownAdaptor, UnknownMonitor  },
              {      EGA,            EGAColor        },
              {      EGA,            MDAMonochrome   },
              {      UnknownAdaptor, UnknownMonitor  },
              {      VGA,            PS2Monochrome   },
              {      VGA,            PS2Color        },
              {      UnknownAdaptor, UnknownMonitor  },
              {      MCGA,           EGAColor        },
              {      MCGA,           PS2Monochrome   },
              {      MCGA,           PS2Color        },
              };
       if (r.h.bh != 0)
              {
              Device[ 1 ].VideoAdaptor =
                     DeviceList[ r.h.bh ].VideoAdaptor;
              Device[ 1 ].VideoMonitor =
                     DeviceList[ r.h.bh ].VideoMonitor;
              }
       Device[ 0 ].VideoAdaptor = DeviceList[ r.h.bl ].VideoAdaptor;
       Device[ 0 ].VideoMonitor = DeviceList[ r.h.bl ].VideoMonitor;
       if (Device[ 0 ].VideoAdaptor == MDA ||
              Device[ 1 ].VideoAdaptor == MDA)
              {
              
              /*----------------------------------------------\
              |       If either the active display or the     |
              |       inactive display is identified as MDA,  |
              |       we clear the system and display         |
              |       information for that display; we need   |
              |       to further identify the system as       |
              |       possibly a Hercules card.               |
              \----------------------------------------------*/
              
              if (Device[ 0 ].VideoAdaptor == MDA)
                     {
                     Device[ 0 ].VideoAdaptor = UnknownAdaptor;
                     Device[ 0 ].VideoMonitor = UnknownMonitor;
                     }
              else
                     {
                     Device[ 1 ].VideoAdaptor = UnknownAdaptor;
                     Device[ 1 ].VideoMonitor = UnknownMonitor;
                     }
              DetectMono();
              }
       }
else
       {
       /*------------------------------------------------------\
       |       detect an EGA card; make a call to the BIOS to  |
       |       get the video subsystem configuration. On       |
       |       return, BL will be set to 0, 1, 2 or 3 (which   |
       |       is the number of 64K blocks of RAM in addition  |
       |       to the default 64K block on the video card),    |
       |       and the least 4 bits of CL contain the          |
       |       configuration switch settings for the card.     |
       |       This includes information as to the display     |
       |       type.                                           |
       \------------------------------------------------------*/
       r.h.bl = 0x10;
       r.h.ah = 0x12;
       int86(0x10, &r, &r);
       if (r.h.bl != 0x10)
              {
              auto enum monitor    Display;
              static enum monitor  EGADisplay[ ] =
                     {
                     CGAColor,    EGAColor,      MDAMonochrome
                     };
              
              Display = EGADisplay[ (r.h.cl % 6) >> 1 ];
              FoundDevice(EGA, Display);
              
              /*-----------------------------------------------\
              |       If a monochrome display is found, any    |
              |       other system must be color, and          |
              |       vice-versa.                              |
              \-----------------------------------------------*/
              
              if (Display == MDAMonochrome)
                     DetectCGA();
              else
                     DetectMono();
              }
       else
              {
              DetectCGA();
              DetectMono();
              }
       }
/*---------------------------------------------------------------\
|       Resolve discrepancies between systems with multiple      |
|       display types and the current video state; not a         |
|       problem if only one type was found, or one of the types  |
|       found was MCGA or VGA. Basically, the active display,    |
|       which is in Device[ 0 ], should match the color          |
|       specification of the current video mode. Incidently,     |
|       the device swap is performed by the trick of using the   |
|       mathematical properties of the exclusive or operator to  |
|       avoid having to declare a temporary holding variable.    |
\---------------------------------------------------------------*/

if (Device[ 1 ].VideoAdaptor != UnknownAdaptor &&
       Device[ 0 ].VideoAdaptor != VGA &&
       Device[ 0 ].VideoAdaptor != MCGA &&
       Device[ 1 ].VideoAdaptor != VGA &&
       Device[ 1 ].VideoAdaptor != MCGA)
       {
       r.h.ah = 0x0F;
       int86(0x10, &r, &r);
       if ((r.h.al & 7) == 7)
              {
              if (Device[ 0 ].VideoMonitor != MDAMonochrome)
                     {
                     Device[ 0 ].VideoMonitor ^=
                            Device[ 1 ].VideoMonitor;
                     Device[ 1 ].VideoMonitor ^=
                            Device[ 0 ].VideoMonitor;
                     Device[ 0 ].VideoMonitor ^=
                            Device[ 1 ].VideoMonitor;
                     Device[ 0 ].VideoAdaptor ^=
                            Device[ 1 ].VideoAdaptor;
                     Device[ 1 ].VideoAdaptor ^=
                            Device[ 0 ].VideoAdaptor;
                     Device[ 0 ].VideoAdaptor ^=
                            Device[ 1 ].VideoAdaptor;
                     }
              }
       else
              {
              if (Device[ 0 ].VideoMonitor == MDAMonochrome)
                     {
                     Device[ 0 ].VideoMonitor ^=
                            Device[ 1 ].VideoMonitor;
                     Device[ 1 ].VideoMonitor ^=
                            Device[ 0 ].VideoMonitor;
                     Device[ 0 ].VideoMonitor ^=
                            Device[ 1 ].VideoMonitor;
                     Device[ 0 ].VideoAdaptor ^=
                            Device[ 1 ].VideoAdaptor;
                     Device[ 1 ].VideoAdaptor ^=
                            Device[ 0 ].VideoAdaptor;
                     Device[ 0 ].VideoAdaptor ^=
                            Device[ 1 ].VideoAdaptor;
                     }
              }
       }
return (Device);
}