КаталогИндекс раздела
НазадОглавлениеВперед


5. Проверка состава оборудования

В программе, предназначенной для тиражирования, очень важной характеристикой является переносимость. Поскольку ПЭВМ разных пользователей могут иметь существенные отличия в конфигурации оборудования, важно, чтобы программа при ее запуске могла "произвести рекогносцировку" - проанализировать состав оборудования и выбрать ту ветвь своего алгоритма, которая обеспечивает функционирование на данном составе, или выдать отказ, если конфигурация не соотвутствует спецификациям.
К действиям по рекогносцировке относятся:

5.1. Тип ПЭВМ

В конце ПЗУ BIOS по адресу FF00:0FFE записан байт типа ПЭВМ. Специфицированы следующие 4 значения этого байта для машин фирмы IBM: 0xFF - IBM PC; 0xFE - XT; 0xFD - PCjr; 0xFC - AT. Для ПЭВМ других производителей все наши эксперименты давали один из этих кодов в соответствии с классом компьютера, однако, стопроцентной гарантии этого дать нельзя.

Кроме того, 8 байт ПЗУ начиная с адреса 0xFF00:0x0FF5, содержат дату выпуска данной версии BIOS в символьном виде, например: 01/01/84. Эта информация может быть полезна только для пользователей продукции IBM, в условиях же калейдоскопа фирм-производителей пользу из этих данных извлечь трудно (мы встречали даже ПЭВМ, в которых дата выпуска BIOS отсутствовала вообще). Приведенная ниже программа извлекает из ПЗУ тип ПЭВМ и дату выпуска BIOS (пример 5.2.).


/*==                ПРИМЕР 5.1                         ==*/
/*======== Чтение типа ПЭВМ и даты издания BIOS =========*/
#include <dos.h>
main() {
  unsigned char pc; /* Код типа PC */
  char *PT[]= { "AT", "PCjr", "XT", "IBM PC", "???" };
  unsigned int t; /* Текущее смещение */
  printf("\nТип ПЭВМ = %x = ",pc=peekb(0xf000,0xfffe));
  if ((pc-=0xfc)>4) pc=4;
  printf("%s\n",PT[pc]);
  printf("Дата издания BIOS = ");
  for (t=0xfff5;t<0xfffd;t++) printf("%c",peekb(0xf000,t));
  printf("\n");
  }

5.2. Состав оборудования

Опеределять состав оборудования следует только после того, как мы определили тип ПЭВМ. Это обусловлено тем, что способы получения информации о составе оборудования различны для XT и для AT. Рассмотрим сначала машины класса XT.

Состав оборудования XT (и PC) определяется положением переключателей на специальных колодках. Состояние этих переключателей может быть прочитано из программы обращением к порту 0x60. Но необходимости в этом нет - эту операцию выполняет BIOS при инициализации системы. На основании этой информации BIOS формирует так называемый список оборудования - 2-байтное слово по адресу 0040:0010. Прочитать это слово можно либо обратившись по указанному адресу, либо обратившись к BIOS через прерывание 0x11. Назначения разрядов списка оборудования следующие:

0- установлен в 1, если есть НГМД (см.разряды 6, 7);
1- установлен в 1, если есть сопроцессор;
2,3- число 16-Кбайтных блоков ОЗУ на системной плате;
4,5- код видеоадаптера: 11 - MDA, 10 - CGA, 80 колонок, 01 - CGA, 40 колонок, 00 - другой;
6,7- число НГМД-1 (если в разряде 0 единица);
8- 0, если установлен канал ПДП;
9,10,11- число последовательных портов RS-232;
12- 1, если установлен джойстик;
13- 1, если установлен последовательный принтер;
14,15- число параллельных принтеров.
Для XT и AT биты 8 и 13 всегда будут нулевыми.

Ниже приведен пример получения и интерпретации списка оборудования.


/*=                     ПРИМЕР 5.2                       =*/
/*============= Получение списка оборудования ============*/
#include <dos.h>
main()
{
  union REGS rr;
  unsigned int d;  /* список оборудования */
  int i;

  /*== Чтение байта обоpудования через прерывание 0x11 ==*/
  /* Прерывание 0x11 возвращает его в регистре AX */
  int86(0x11,&rr,&rr);
  /* Побитная распечатка списка оборудования */
  printf
    ("Список активного оборудования из прерывания 11 - ");
  for (i=15; i>=0; printf("%d",(rr.x.ax>>i--)&0x01));
  printf(" (%04x)\n",rr.x.ax);
  /* == Чтение байта обоpудования из памяти BIOS == */
  /* Будет получено то же самое */
  d=peek(0x40,0x10);
  printf
    ("Список активного оборудования из памяти BIOS   - ");
  for (i=15; i>=0; printf("%d",(d>>i--)&0x01));
  printf(" (%04x)\n",d);
  /* == Раскодирование списка обоpудования == */
  printf("       Дисководов ГМД - ");
  if (d&0x0001) printf("%d\n",((d&0x00c0)>>6)+1);
  else printf("нет\n");
  printf("       Сопроцессор 8087 - ");
  if (d&0x0002) printf("есть\n");
  else printf("нет\n");
  printf("       Тип дисплейного адаптера - ");
  switch (d&0x0030) {
    case 0: printf("EGA/VGA"); break;
    case 0x10: printf("CGA,40-кол"); break;
    case 0x20: printf("CGA,80-кол"); break;
    case 0x30: printf("MDA"); break;
    }
  printf(" (неточно)\n");
  printf("       Первичный блок памяти - ");
  switch (d&0x000c) {
    case 0: printf("16 Кбайт\n"); break;
    case 4: printf("32 Кбайт\n"); break;
    case 8: printf("48 Кбайт\n"); break;
    case 12: printf("64 Кбайт или больше\n"); break;
    }
  printf("       Портов RS232 - %d\n",(d&0x0e00)>>9);
  printf("       Джойстик - ");
  if (d&0x1000) printf("есть\n");
  else printf("нет\n");
  printf("       Принтеров - %d\n",(d&0xe000)>>14);
}

Бросается в глаза, что список оборудования содержит явно недостаточно информации по дисплейному адаптеру - видимо, при его проектировании не была учтена возможность появления новых типов адаптеров после CGA. Для уточнения типа адаптера программа должна выполнить еще некоторые действия, рассмотрение которых мы отложим до главы, посвященной адаптерам.

Для AT информация об оборудовании хранится в CMOS (КМОП) памяти и сохраняется при выключении питания. При инициализации BIOS считывает эту информацию и формирует список оборудования, который может быть прочитан теми же средствами. Однако, информация об оборудовании AT более полная, для получения дополнительной информации программа может сама обратиться к CMOS-памяти. CMOS-память состоит из ряда регистров. Для чтения их необходимо выдать в порт 0x70 байт номера регистра, а затем из порта 0x71 прочитать байт содержимого этого регистра. С точки зрания определения конфигурации представляют интерес следующие регистры CMOS-памяти:

0x14- содержит байт оборудования, совпадающий с младшим байтом списка оборудования BIOS;
0x10- типы дисководов НГМД; младший полубайт - для дисковода A, старший - для B; типы кодируются следующим образом: 0 - нет дисковода, 1 - дисковод для дискет 360К, 2 - для дискет высокой плотности;
0x12- типы жестких дисков младший полубайт - для первого диска, старший - для второго; 0 в соответствующем полубайте означает, что этого жесткого диска нет; числа 1-14 - коды типов, число 15 означает, что тип жесткого диска записан в регистре 0x19 (для первого диска) или 0x1A (для второго).
Вот как выглядит получение информации из CMOS-памяти.


/*==                     ПРИМЕР 5.3                     ==*/
/*========= Получение информации из CMOS-памяти AT =======*/
#include <dos.h>
#define byte unsigned char
main() {
  byte f;
  /* Байт оборудования */
  outportb(0x70,0x14);  f=inportb(0x71);
  printf("Байт оборудования = %02x\n",f);
  /* Типы дискет */
  outportb(0x70,0x10);  f=inportb(0x71);
  floppy(1,f&0x0f);
  floppy(2,f>>4);
  /* Типы тв.дисков */
  outportb(0x70,0x12);  f=inportb(0x71);
  hard(1,f>>4);
  hard(2,f&0x0f);
}
/*==== Печать хаpактеpистик floppy-диска. ====*/
floppy(byte drive,byte type) {
  printf("ГМД дисковод %d -  %d  ",drive,type);
  switch (type) {
    case 0: printf("нет\n"); break;
    case 1: printf("DD\n"); break;
    case 2: printf("HD\n"); break;
    }
}
/*==== Печать хаpактеpистик твеpдого диска. ====*/
hard(byte drive,byte type) {
  byte f, cmos_reg;
  switch(type) {
    case 0: break;
    case 15: cmos_reg=0x19+drive-1;
      outportb(0x70,cmos_reg); f=inportb(0x71);
      printf("Твердый диск %d -> %d (расширенный тип)\n",
        drive,f);
      break;
    default: printf("Твердый диск %d -> %d\n",drive,type);
     break;
    }
}

Заметим, что порт 0x71 двунаправленный, то есть, возможно также и записать информацию в CMOS-память, хотя и трудно представить ситуацию, в которой программе понадобится это сделать. Коррекцию содержимого CMOS-памяти (например, при его случайной порче или при изменении конфигурации) обеспечивает программа SETUP, которая может быть запущена во время POST.

5.3. Определение объема оперативной памяти

Методика определения объема памяти - такая же, как и определения списка оборудования. Объем ОЗУ (в Кбайтах) находится в области памяти BIOS по адресу 0040:0013 (2-байтное слово) и может быть получен при помощи прерывания 0x12, как это показано в следующем примере.


/*==                  ПРИМЕР 5.4                        ==*/
/*======== Определение объема оперативной памяти =========*/
#include <dos.h>
main()
  {
  union REGS rr;
  /* Прерывание 0x12 возвращает его в регистре AX */
  int86(0x12,&rr,&rr);
  printf("Объем памяти по прерыванию 12 - %d Кбайт\n",
    rr.x.ax);
  /* То же число можно получить чтением из 0040:0012 */
  printf("Объем памяти из данных BIOS   - %d Кбайт\n",
    peek(0x40,0x13));
}

Для AT объем ОЗУ может быть также прочитан из регистров 0x15 (младший байт) и 0x16 (старший байт) CMOS-памяти. Кроме того, в AT может быть еще и расширенная (extended) память сверх 1 Мбайта (в AT она используется только для виртуальных дисков). Ее объем можно получить из регистров 0x17 (младший байт) и 0x18 (старший байт), или из регистров 0x30 (младший байт) и 0x31 (старший байт) или по функции 0x88 прерывания 0 x15.


/*==                   ПРИМЕР 5.5                       ==*/
/*======= Определение характеристик памяти для AT ========*/
#include <dos.h>
union REGS rr;
main() {
 unsigned char f, f1;
    printf("Объем основной памяти - ");
    outportb(0x70,0x15);  f=inportb(0x71);
    outportb(0x70,0x16);  f1=inportb(0x71);
    printf("%d Кбайт\n",(f1<<8)|f);
    printf("Объем extended памяти - ");
    outportb(0x70,0x17);  f=inportb(0x71);
    outportb(0x70,0x18);  f1=inportb(0x71);
    printf("%d Кбайт\n",(f1<<8)|f);
    printf("Объем extended памяти - ");
    outportb(0x70,0x30);  f=inportb(0x71);
    outportb(0x70,0x31);  f1=inportb(0x71);
    printf("%d Кбайт\n",(f1<<8)|f);
    printf("Объем extended памяти - ");
    rr.h.ah=0x88;
    int86(0x15,&rr,&rr);
    printf("%d Кбайт\n",rr.x.ax);
    }
}

5.4. Определение версии DOS

Эта операция необходима, так как средства и структуры данных DOS могут существенно меняться от версии к версии. Функция DOS 0x30 возвращает в регистре AL старшее число номера версии, а в AH - младшее число.


/*==                     ПРИМЕР 5.6                     ==*/
/*================ Определение версии DOS ================*/
#include <dos.h>
union REGS rr;
main()  {
  rr.h.ah=0x30;
  intdos(&rr,&rr);
  printf("Версия MS-DOS %d.%d\n",rr.h.al,rr.h.ah);
}


КаталогИндекс раздела
НазадОглавлениеВперед