home *** CD-ROM | disk | FTP | other *** search
/ Frostbyte's 1980s DOS Shareware Collection / floppyshareware.zip / floppyshareware / APOG / ASM2.ZIP / CPUID.ASM < prev    next >
Encoding:
Assembly Source File  |  1987-11-24  |  35.4 KB  |  1,238 lines

  1.      title     CPUID -- Determine CPU & NDP Type
  2.      page     58,122
  3.      name     CPUID
  4.  
  5. COMMENT|
  6.  
  7. CPUID purports to uniquely identify each Intel CPU & NDP used in IBM
  8. PCs and compatibles.
  9.  
  10. Notes on Program Structure
  11. --------------------------
  12.  
  13.    This program uses four segments, two classes, and one group.  It
  14. demonstrates a useful technique for programmers who generate .COM
  15. programs.  In particular, it shows how to use segment classes to
  16. re-order segments, and how to eliminate the linker's warning message
  17. about the absence of a stack segment.
  18.  
  19.    The correspondence between segments and classes is as follows:
  20.  
  21.      Segment    Class
  22.      -------    -----
  23.      STACK        prog
  24.      DATA        data
  25.      MDATA        data
  26.      CODE        prog
  27.  
  28.    The segments appear in the above order in the program source to
  29. avoid forward references in the CODE segment to labels in the
  30. DATA/MDATA segments.  However, because the STACK segment appears first
  31. in the file, it and all segments in the same class are made contiguous
  32. by the linker.    Thus they precede the DATA/MDATA segments in the
  33. resulting .COM file because the latter are in a different class.  In
  34. this manner, although DATA and MDATA precede CODE in the source file,
  35. their order is swapped in the .COM file.  That way there is no need
  36. for an initial skip over the data areas to get to the CODE segment.
  37. As a side benefit, declaring a STACK segment (as the first segment in
  38. the source) also eliminates the linker's warning about that segment
  39. being missing.    Finally, all segments are declared to be in the same
  40. group so the linker can properly resolve offsets.
  41.  
  42.    Note that if you re-assemble the code for any reason, it is
  43. important to use an assembler later than the IBM version 1.0.  That
  44. version has a number of bugs including an annoying habit of
  45. alphabetizing segment names in the .OBJ file.  Such gratuitous
  46. behavior defeats the above technique as well as exhibits generally bad
  47. manners.  If you use IBM MASM 2.0, be sure to specify /S to order the
  48. segments properly.
  49.  
  50.    If the program reports results at variance with your knowledge of
  51. the system, please contact the author.
  52.  
  53. Environments tested in:
  54.  
  55.          CPU Speed
  56.   System      in MHz    CPU         NDP
  57.   ----------------------------------------------------------
  58.   IBM PC        4.77     Intel 8088     Intel 8087-3
  59.   IBM PC        4.77     Intel 8088*    Intel 8087-3
  60.   IBM PC XT        4.77     Intel 8088     none
  61.   IBM PC XT        4.77     Intel 8088     Intel 8087-3
  62.   IBM PC XT/286     6         Intel 80286    none
  63.   IBM PC AT        6         Intel 80286    Intel 80287
  64.   IBM PC AT        9         Intel 80286    Intel 80287-8
  65.   IBM PC AT        6         Intel 80286    none
  66.   IBM PC AT        8.5      Intel 80286    none
  67.   IBM 3270 PC AT    6         Intel 80286    none
  68.   COMPAQ Portable   4.77     Intel 8088     none
  69.   COMPAQ Portable   4.77     NEC V20        none
  70.   COMPAQ 286        8         Intel 80286    none
  71.   COMPAQ 386       16         Intel 80386    Intel 80827-8
  72.   AT&T PC 6300        8         Intel 8086     Intel 8087-2
  73.   AT&T PC 6300        8         NEC V30        Intel 8087-2
  74.   TANDY 2000        8         Intel 80186    none
  75.   HP 150        4.77     Intel 8088     none
  76.   Zenith Z-160        4.77     NEC V20        none
  77.   HP Vectra RS/20  20         Intel 80386    Intel 80387, Weitek 1167
  78.  
  79.   * = with faulty CPU
  80.  
  81. Program structure:
  82.   Group PGROUP:
  83.   Stack   segment STACK, byte-aligned, stack,  class 'prog'
  84.   Program segment CODE,  word-aligned, public, class 'prog'
  85.   Data      segment DATA,  byte-aligned, public, class 'data'
  86.   Data      segment MDATA, byte-aligned, public, class 'data'
  87.  
  88. Assembly requirements:
  89.  
  90.   Use MASM 1.25 or later.
  91.   With IBM's MASM 2.0 only, use /S to avoid
  92.     alphabetizing the segment names.
  93.   Use /r option to generate real NDP code.
  94.  
  95.   MASM CPUID/r;           to convert .ASM to .OBJ
  96.   LINK CPUID;              to convert .OBJ to .EXE
  97.   EXE2BIN CPUID CPUID.COM      to convert .EXE to .COM
  98.   ERASE CPUID.EXE          to avoid executing .EXE
  99.  
  100.   Note that the linker doesn't warn about a missing stack segment.
  101.  
  102. Copyright free.
  103.  
  104. Original code by:
  105.  
  106.   Bob Smith           May 1985.
  107.   Qualitas, Inc.
  108.   8314 Thoreau Dr.
  109.   Bethesda, MD    20817-3164
  110.   301-469-8848
  111.  
  112.   Arthur Zachai suggested the technique to distinguish within the 808x
  113.   and 8018x families by exploiting the difference in the length of
  114.   their pre-fetch instruction queues.
  115.  
  116. Modifications by:
  117.  
  118. Who            When            Why
  119. ----------------------------------------------------------------------
  120. Bob Smith        21 Jan 86        Distinguish NEC V20/V30s
  121.                         from Intel 8086/8088s
  122.  
  123. David E. Michener    28 Jun 86        Use /z to avoid INT 11h if
  124.                         on a machine which doesn't
  125.                         support interrupts below 20h
  126.  
  127. Bob Smith        15 Sep 86        Distinguish 286 from 386
  128.  
  129. Bob Smith         6 Oct 86        Return length of PIQ
  130.  
  131. Dan Lewis        15 Dec 86        Use /r to allow NDP instructions
  132.                         on 286/386 machines w/o NDP
  133.  
  134. Bob Smith        17 Jun 87        Distinguish 287 from 387
  135.  
  136. Bob Smith        24 Nov 87        Detect Weitek 1167
  137.  
  138. |
  139.  
  140.      subttl  Structures, Records, Equates, & Macros
  141.      page
  142. ARG_STR  struc
  143.  
  144.      dw     ?        ; Caller's BP
  145. ARG_OFF  dw     ?        ; Caller's offset
  146. ARG_SEG  dw     ?        ;       segment
  147. ARG_FLG  dw     ?        ;       flags
  148.  
  149. ARG_STR  ends
  150.  
  151. ; Record to define bits in the CPU's & NDP's flags' registers
  152.  
  153. CPUFLAGS record  $R0:1,$NT:1,$IOPL:2,$OF:1,$DF:1,$IF:1,$TF:1,$SF:1,$ZF:1,$R1:1,$AF:1,$R2:1,$PF:1,$R3:1,$CF:1
  154. NDPFLAGS record  $R4:3,$IC:1,$RC:2,$PC:2,$IEM:1,$R5:1,$PM:1,$UM:1,$OM:1,$ZM:1,$DM:1,$IM:1
  155.  
  156. COMMENT|
  157.  
  158. FLG_PIQL     Pre-fetch instruction queue length, 0 => 4-byte, 1 => 6-byte
  159. FLG_08         Intel 808x
  160. FLG_NEC      NEC V20 or V30
  161. FLG_18         Intel 8018x
  162. FLG_28         Intel 8028x
  163. FLG_38         Intel 8038x
  164.  
  165. FLG_87         Intel 8087
  166. FLG_287      Intel 80287
  167. FLG_387      Intel 80387
  168.  
  169. FLG_1167     Weitek 1167
  170.  
  171. $FLG_CERR     Faulty CPU
  172. $FLG_NERR     Faulty NDP switch setting
  173.  
  174. |
  175.  
  176. FLG     record  $RSVD:6,$FLG_NERR:1,$FLG_CERR:1,$FLG_WTK:1,$FLG_NDP:3,$FLG_CPU:4
  177.  
  178. ; CPU-related flags
  179.  
  180. FLG_PIQL equ     0001b shl $FLG_CPU
  181. FLG_08     equ     0000b shl $FLG_CPU
  182. FLG_NEC  equ     0010b shl $FLG_CPU
  183. FLG_18     equ     0100b shl $FLG_CPU
  184. FLG_28     equ     0110b shl $FLG_CPU
  185. FLG_38     equ     1000b shl $FLG_CPU
  186.  
  187. FLG_8088 equ     FLG_08
  188. FLG_8086 equ     FLG_08 or FLG_PIQL
  189. FLG_V20  equ     FLG_NEC
  190. FLG_V30  equ     FLG_NEC or FLG_PIQL
  191. FLG_80188 equ     FLG_18
  192. FLG_80186 equ     FLG_18 or FLG_PIQL
  193. FLG_80286 equ     FLG_28 or FLG_PIQL
  194. FLG_80386 equ     FLG_38 or FLG_PIQL
  195.  
  196. ; NDP-related flags
  197.  
  198. FLG_NDPX equ     000b shl $FLG_NDP    ; Not present
  199. FLG_NDPU equ     001b shl $FLG_NDP    ; Untested
  200. FLG_87     equ     010b shl $FLG_NDP
  201. FLG_287  equ     011b shl $FLG_NDP
  202. FLG_387  equ     100b shl $FLG_NDP
  203.  
  204. FLG_1167 equ     mask $FLG_WTK
  205.  
  206. BEL     equ     07h
  207. LF     equ     0Ah
  208. CR     equ     0Dh
  209. EOS     equ     '$'
  210.  
  211. POPFF     macro
  212.      local     L1,L2
  213.  
  214.      jmp     short L2    ; Skip over IRET
  215. L1:
  216.      iret            ; Pop the CS & IP pushed below along
  217.                 ; with the flags, our original purpose
  218. L2:
  219.      push     cs        ; Prepare for IRET by pushing current CS
  220.      call     L1        ; Push IP, jump to IRET
  221.  
  222.      endm            ; POPFF macro
  223.  
  224. TAB     macro     TYP
  225.  
  226.      push     bx        ; Save for a moment
  227.      and     bx,mask $FLG_&TYP ; Isolate flags
  228.      mov     cl,$FLG_&TYP    ; Shift amount
  229.      shr     bx,cl        ; Shift to low-order
  230.      shl     bx,1        ; Times two to index table of words
  231.      mov     dx,TYP&MSG_TAB[bx] ; DS:DX ==> descriptive message
  232.      pop     bx        ; Restore
  233.  
  234.      mov     ah,09h     ; Function code to display string at DS:DX
  235.      int     21h        ; Request DOS service
  236.  
  237.      endm            ; TAB macro
  238.  
  239. REGSAVE  macro     LIST        ; Register save macro
  240.  
  241.      irp     XX,<LIST>
  242.      push     XX
  243.      endm
  244.  
  245.      endm            ; REGSAVE
  246.  
  247. REGREST  macro     LIST        ; Register restore macro
  248.  
  249.      irp     XX,<LIST>
  250.      pop     XX
  251.      endm
  252.  
  253.      endm            ; REGREST
  254.      page
  255. INT_VEC  segment at 0        ; Start INT_VEC segment
  256.  
  257.      org     4*01h
  258. INT01_OFF dw     ?        ; Pointer to INT 01h
  259. INT01_SEG dw     ?
  260.  
  261. INT_VEC  ends            ; End INT_VEC segment
  262.  
  263. PGROUP     group     STACK,CODE,DATA,MDATA
  264.  
  265.      include VERSION.INC
  266.  
  267. ; The following segment both positions class 'prog' segments lower in
  268. ; memory than others so the first byte of the resulting .COM file is
  269. ; in the CODE segment, as well as satisfies the LINKer's need to have
  270. ; a stack segment.
  271.  
  272. STACK     segment byte stack 'prog' ; Start STACK segment
  273. STACK     ends            ; End STACK segment
  274.  
  275. I11_REC  record  $I11_PRN:2,$I11_RSV1:2,$I11_COM:3,$I11_RSV2:1,$I11_DISK:2,$I11_VID:2,$I11_RSV3:2,$I11_NDP:1,$I11_IPL:1
  276.  
  277. DATA     segment byte public 'data' ; Start DATA segment
  278.      assume  ds:PGROUP
  279.  
  280. OLDINT01_VEC label dword    ; Save area for original INT 01h handler
  281. OLDINT01_OFF dw  ?
  282. OLDINT01_SEG dw  ?
  283.  
  284. NDP_ENV  dw     7 dup (?)    ; Save area for NDP environment
  285.  
  286. NDP_CW     label     word        ; Save area for NDP control word
  287.      db     ?
  288. NDP_CW_HI db     0        ; High byte of control word
  289.      db     15 dup (?)    ; Room for the CPU to step on
  290.  
  291. DATA     ends            ; End DATA segment
  292.      subttl  Message Data Area
  293.      page
  294. MDATA     segment byte public 'data' ; Start MDATA segment
  295.      assume  ds:PGROUP
  296.  
  297. MSG_START db     'CPUID    -- Version '
  298.      db     VERS_H,'.',VERS_T,VERS_U
  299.      db     CR,LF,EOS
  300.  
  301. LCL_FLAGS dw     0        ; Local flags
  302. @LCL_REAL equ     8000h        ; Use real code to detect 287 or 387 coprocessors
  303. @LCL_I11H equ     4000h        ; Avoid INT 11h on machines which don't support it
  304.  
  305. LCL_OPTS db     'rb'           ; Table of valid option letters
  306. NLCL_OPTS equ     $-LCL_OPTS    ; # valid ...
  307.  
  308. LCL_ACTS dw     PGROUP:CHECK_ARGS_REAL ; Table of corresponding actions
  309.      dw     PGROUP:CHECK_ARGS_I11H
  310.  
  311. PIQL     dw     0        ; Length of prefetch instruction queue
  312. PIQL_CNT dw     10        ; Retry count to ensure valid PIQL
  313.  
  314. MSG_PIQL db     'The length of the pre-fetch instruction queue is '
  315. MSG_PIQL1 db     '__.',CR,LF,EOS
  316.  
  317. MSG_8088 db     'CPU is an Intel 8088.',CR,LF,EOS
  318. MSG_8086 db     'CPU is an Intel 8086.',CR,LF,EOS
  319. MSG_V20  db     'CPU is an NEC V20.',CR,LF,EOS
  320. MSG_V30  db     'CPU is an NEC V30.',CR,LF,EOS
  321. MSG_80188 db     'CPU is an Intel 80188.',CR,LF,EOS
  322. MSG_80186 db     'CPU is an Intel 80186.',CR,LF,EOS
  323. MSG_UNK2 db     'CPU is an Intel 80286',CR,LF,EOS
  324. MSG_80286 db     'CPU is an Intel 80286.',CR,LF,EOS
  325. MSG_UNK3 db     'CPU is an Intel 80386',CR,LF,EOS
  326. MSG_80386 db     'CPU is an Intel 80386.',CR,LF,EOS
  327.  
  328. CPUMSG_TAB label word
  329.      dw     PGROUP:MSG_8088    ; 0000 = Intel 8088
  330.      dw     PGROUP:MSG_8086    ; 0001 = Intel 8086
  331.      dw     PGROUP:MSG_V20     ; 0010 = NEC V20
  332.      dw     PGROUP:MSG_V30     ; 0011 = NEC V30
  333.      dw     PGROUP:MSG_80188    ; 0100 = Intel 80188
  334.      dw     PGROUP:MSG_80186    ; 0101 = Intel 80186
  335.      dw     PGROUP:MSG_UNK2    ; 0110 = ?
  336.      dw     PGROUP:MSG_80286    ; 0111 = Intel 80286
  337.      dw     PGROUP:MSG_UNK3    ; 1000 = ?
  338.      dw     PGROUP:MSG_80386    ; 1001 = Intel 80386
  339.  
  340. NDPMSG_TAB label word
  341.      dw     PGROUP:MSG_NDPX    ; 000 = No NDP
  342.      dw     PGROUP:MSG_NDPU    ; 001 = NDP untested
  343.      dw     PGROUP:MSG_8087    ; 010 = Intel 8087
  344.      dw     PGROUP:MSG_80287    ; 011 = Intel 80287
  345.      dw     PGROUP:MSG_80387    ; 100 = Intel 80387
  346.  
  347.  
  348. WTKMSG_TAB label word
  349.      dw     PGROUP:MSG_EMPTY    ; 0 = No Weitek 1167
  350.      dw     PGROUP:MSG_1167    ; 1 = Weitek 1167
  351.  
  352. MSG_NDPX db     'NDP is not present.',CR,LF,EOS
  353. MSG_NDPU db     'NDP is untested.',CR,LF,EOS
  354. MSG_8087 db     'NDP is an Intel 8087.',CR,LF,EOS
  355. MSG_80287 db     'NDP is an Intel 80287.',CR,LF,EOS
  356. MSG_80387 db     'NDP is an Intel 80387.',CR,LF,EOS
  357. MSG_1167 db     'NDP is a Weitek 1167.',CR,LF,EOS
  358. MSG_EMPTY db     EOS
  359.  
  360. CERRMSG_TAB label word
  361.      dw     PGROUP:MSG_CPUOK    ; 0 = CPU healthy
  362.      dw     PGROUP:MSG_CPUBAD    ; 1 = CPU faulty
  363.  
  364. MSG_CPUOK db     EOS            ; No message
  365. MSG_CPUBAD label byte
  366. db BEL,'*** CPU incorrectly allows interrupts after a change to SS ***',CR,LF
  367. db 'It should be replaced with a more recent version as it could crash the',CR,LF
  368. db 'system at seemingly random times.',CR,LF,EOS
  369.  
  370. NERRMSG_TAB label word
  371.      dw     PGROUP:MSG_NDPSWOK    ; 0 = NDP switch set correctly
  372.      dw     PGROUP:MSG_NDPSWERR    ; 1 = NDP switch set incorrectly
  373.  
  374. MSG_NDPSWOK db     EOS            ; No message
  375. MSG_NDPSWERR label  byte
  376. db '*** The system board switch which indicates whether or not there is',CR,LF
  377. db 'an NDP installed in the system is not properly set.  To correct this,',CR,LF
  378. db 'flip switch 2 of switch block 1 on the system board.',CR,LF,EOS
  379.  
  380. MSG_REAL db     '══> Using real code to detect math coprocessor...',CR,LF,EOS
  381. MSG_I11H db     '══> Bypassing INT 11h test of system board switch...',CR,LF,EOS
  382.  
  383. MSG_HELP db     "CPUID.COM determines the CPU and NDP models in use.  In particular, it can",CR,LF
  384.      db     "distinguish from each other any of the following CPUs and NDPs:",CR,LF
  385.      db     CR,LF
  386.      db     "   Intel 8086, 8087, 8088, 80186, 80188, 80286, 80287, 80386, 80387",CR,LF
  387.      db     "   NEC V20, V30",CR,LF
  388.      db     CR,LF
  389.      db     "It also reports on faulty 8086/8088s.  Such a CPU is faulty if it allows an",CR,LF
  390.      db     "interrupt immediately after a change to the SS register.  If the program",CR,LF
  391.      db     "indicates you have a faulty CPU, you should replace it.  The program also",CR,LF
  392.      db     "checks on the system board's NDP switch in case there is an NDP installed.",CR,LF
  393.      db     CR,LF
  394.      db     "The program's syntax is as follows:",CR,LF
  395.      db     "     CPUID     to run the program",CR,LF
  396.      db     "     CPUID /r  to run the program using real code to detect a 287 or 387",CR,LF
  397.      db     "     CPUID /b  to run the program but bypass the INT 11h test for correctly",CR,LF
  398.      db     "                  set system board NDP switch",CR,LF
  399.      db     CR,LF
  400.      db     "If the program reports results at variance with your knowledge of the system,",CR,LF
  401.      db     "please contact the author:",CR,LF
  402.      db     "     Bob Smith     301-469-8848",CR,LF
  403.      db     "     Qualitas, Inc.",CR,LF
  404.      db     "     8314 Thoreau Dr.",CR,LF
  405.      db     "     Bethesda, MD  20817-3164",CR,LF
  406.      db     EOS
  407.  
  408. MDATA     ends            ; End MDATA segment
  409.      subttl  Main Routine
  410.      page
  411. CODE     segment word public 'prog' ; Start CODE segment
  412.      assume  cs:PGROUP
  413.  
  414.      org     100h        ; Skip over PSP
  415. INITIAL  proc     near
  416.      assume  ds:PGROUP,es:PGROUP
  417.  
  418.      lea     dx,MSG_START    ; Starting message
  419.      mov     ah,09h     ; Function code to display string at DS:DX
  420.      int     21h        ; Request DOS service
  421.  
  422.      call     CHECK_ARGS    ; Check command line for parameters
  423.  
  424.      call     CPUID        ; Check the CPU's identity
  425.  
  426.      TAB     CPU        ; Display CPU results
  427.      TAB     NDP        ; Display NDP results
  428.      TAB     WTK        ; Display Weitek results
  429.      TAB     CERR        ; Display CPU ERR results
  430.      TAB     NERR        ; Display NDP ERR results
  431.  
  432.      mov     ax,PIQL    ; Get length of PIQ
  433.      aam            ; Convert to decimal digits
  434.      add     ax,'00'        ; Convert to ASCII
  435.      mov     MSG_PIQL1,ah    ; Save high-order byte
  436.      mov     MSG_PIQL1+1,al ;    low-order
  437.      lea     dx,MSG_PIQL    ; DS:DX ==> string to display
  438.      mov     ah,09h     ; Function code to display string at DS:DX
  439.      int     21h        ; Request DOS service
  440.  
  441.      mov     ax,PIQL    ; Get length of PIQ
  442.      mov     ah,4Ch     ; Function call to exit with return code
  443.      int     21h        ; Return to DOS
  444.  
  445.      assume  ds:nothing,es:nothing
  446.  
  447. INITIAL  endp            ; End INITIAL procedure
  448.      subttl  CHECK_ARGS Procedure
  449.      page
  450. CHECK_ARGS proc  near        ; Start CHECK_ARGS procedure
  451.      assume  ds:PGROUP,es:PGROUP
  452. COMMENT|
  453.  
  454. Check the command line for parameters.
  455.  
  456. The following parameters are allowed:
  457.  
  458. /r     Use real code to detect 287 or 387 math coprocessor
  459.  
  460. |
  461.  
  462.      REGSAVE <ax,dx,si>    ; Save registers
  463.  
  464.      mov     si,81h     ; DS:SI ==> command line parameters
  465. CHECK_ARGS_NEXT:
  466.      call     SKIP_WHITE    ; Skip over white space
  467.  
  468.      lodsb            ; Get next character
  469.  
  470.      cmp     al,CR        ; Check for end-of-line
  471.      je     CHECK_ARGS_EXIT ; That's all folks
  472.  
  473.      cmp     al,'/'         ; Option separator?
  474.      jne     CHECK_ARGS_ERR ; No, give 'em help
  475.  
  476.      lodsb            ; Get option letter
  477.      or     al,20h     ; Convert to lowercase
  478.  
  479.      lea     di,LCL_OPTS    ; ES:DI ==> string of valid option letters
  480.      mov     cx,NLCL_OPTS    ; Length of above string
  481.    repne scasb            ; Search for it
  482.      jne     CHECK_ARGS_ERR ; Not present, give 'em help
  483.  
  484.      sub     di,1+offset es:LCL_OPTS ; Convert to origin-0
  485.      shl     di,1        ; Times two to index table of words
  486.      jmp     LCL_ACTS[di]    ; Take appropriate action
  487.  
  488. CHECK_ARGS_REAL:
  489.      lea     dx,MSG_REAL    ; DS:DX ==> message for this flag
  490.      mov     ah,09h     ; Function code to display string at DS:DX
  491.      int     21h        ; Request DOS service
  492.  
  493.      or     LCL_FLAGS,@LCL_REAL ; Mark as present
  494.      jmp     CHECK_ARGS_NEXT ; Go around again
  495.  
  496. CHECK_ARGS_I11H:
  497.      lea     dx,MSG_I11H    ; DS:DX ==> message for this flag
  498.      mov     ah,09h     ; Function code to display string at DS:DX
  499.      int     21h        ; Request DOS service
  500.  
  501.      or     LCL_FLAGS,@LCL_I11H ; Mark as present
  502.      jmp     CHECK_ARGS_NEXT ; Go around again
  503.  
  504. CHECK_ARGS_ERR:
  505.      lea     dx,MSG_HELP    ; DS:DX ==> help message
  506.      mov     ah,09h     ; Function code to display string at DS:DX
  507.      int     21h        ; Request DOS service
  508.  
  509.      mov     ax,4CFFh    ; Function call to exit with return code
  510.      int     21h        ; Return to DOS
  511.  
  512. CHECK_ARGS_EXIT:
  513.      REGREST <si,dx,ax>    ; Restore
  514.  
  515.      ret            ; Return to caller
  516.  
  517.      assume  ds:nothing,es:nothing
  518.  
  519. CHECK_ARGS endp         ; End CHECK_ARGS procedure
  520.      subttl  SKIP_WHITE Procedure
  521.      page
  522. SKIP_WHITE proc  near        ; Start SKIP_WHITE procedure
  523.      assume  ds:PGROUP,es:PGROUP
  524. COMMENT|
  525.  
  526. Skip over white space.
  527.  
  528. On entry:
  529.  
  530. DS:SI     ==>     next character (possibly white space)
  531.  
  532. On exit:
  533.  
  534. DS:SI     ==>     next character not white space
  535.  
  536. |
  537.  
  538.      lodsb            ; Get next character
  539.  
  540.      cmp     al,' '         ; Check for blanks
  541.      je     SKIP_WHITE    ; Go around again
  542.  
  543.      cmp     al,TAB     ; Check for TABs
  544.      je     SKIP_WHITE    ; Go around again
  545.  
  546.      dec     si        ; Back off to non-white character
  547.  
  548.      ret            ; Return to caller
  549.  
  550.      assume  ds:nothing,es:nothing
  551.  
  552. SKIP_WHITE endp         ; End SKIP_WHITE procedure
  553.      subttl  CPUID Procedure
  554.      page
  555. CPUID     proc     near        ; Start CPUID procedure
  556.      assume  ds:PGROUP,es:PGROUP
  557. COMMENT|
  558.  
  559. This procedure determines the type of CPU and NDP (if any) in use.
  560.  
  561. The possibilities include:
  562.  
  563. Intel 8086
  564. Intel 8088
  565. NEC   V20
  566. NEC   V30
  567. Intel 80186
  568. Intel 80188
  569. Intel 80286
  570. Intel 80386
  571. Intel 8087
  572. Intel 80287
  573. Intel 80387
  574.  
  575.    Also checked is whether or not the CPU allows interrupts after
  576. changing the SS segment register.  If the CPU does, it is faulty and
  577. should be replaced.
  578.  
  579.    Further, if an NDP is installed, non-AT machines should have a
  580. system board switch set correspondingly.  Such a discrepancy is
  581. reported upon.
  582.  
  583.    On exit, BX contains flag settings (as defined in $FLG record) which
  584. the caller can check.  For example, to test for an Intel 80286, use
  585.  
  586.      and     bx,mask $FLAG_CPU
  587.  
  588.      cmp     bx,FLG_80286
  589.      je     ITSA286
  590.  
  591. |
  592.  
  593.      REGSAVE <ax,cx,di,ds,es> ; Save registers
  594.  
  595. ; Test for 80286/386 -- these CPUs execute PUSH SP by first storing SP on stack,
  596. ; then decrementing it.  Earlier CPUs first decrement then store.
  597.  
  598.      push     sp        ; Only 286 pushes pre-push SP
  599.      pop     ax        ; Get it back
  600.  
  601.      cmp     ax,sp        ; Check for same
  602.      jne     CHECK_18x    ; They aren't, try next class
  603.  
  604.      call     DIST_286or386    ; Distinguish a 286 from 386
  605.      jmp     short CHECK_PIQL ; Join common code
  606.  
  607. ; Test for 80186/80188 -- 18x and later CPUs mask shift/rotate operations
  608. ; mod 32; earlier CPUs use all 8 bits of CL.
  609.  
  610. CHECK_18x:
  611.      mov     bx,FLG_18    ; Assume it's an 8018x
  612.      mov     cl,32+1    ; 18x masks shift counts mod 32
  613.                 ; Note we can't use just 32 in CL
  614.      mov     al,0FFh    ; Start with all bits set
  615.  
  616.      shl     al,cl        ; Shift one position if 18x
  617.      jnz     CHECK_PIQL    ; Some bits still on, so it's a 18x or later;
  618.                 ; check PIQL
  619.  
  620.      mov     bx,FLG_NEC    ; Assume it's an NEC V-series CPU
  621.      call     CHECK_NEC    ; See if it's an NEC chip
  622.      jcxz     CHECK_PIQL    ; Good guess, check PIQL
  623.  
  624.      mov     bx,FLG_08    ; It's an 808x
  625.      subttl  Check Length Of Pre-fetch Instruction Queue
  626.      page
  627. COMMENT|
  628.  
  629. Check the length of the pre-fetch instruction queue (PIQ).
  630.  
  631. xxxx6 CPUs have a PIQ length of 6 bytes,
  632. xxxx8 CPUs   "     "     "      4   "
  633.  
  634. Self-modifying code is used to distinguish the two PIQ lengths.
  635.  
  636. To overcome write pipelining in 286/386 chips, the largest value
  637. over several executions of the subroutine is used.
  638.  
  639. |
  640.  
  641. CHECK_PIQL:
  642.      call     PIQL_SUB    ; Handled via subroutine
  643.  
  644.      cmp     cx,PIQL    ; Use the larger of the two
  645.      jbe     CHECK_PIQL1    ; CX is smaller
  646.  
  647.      mov     PIQL,cx    ; Save to report on later
  648. CHECK_PIQL1:
  649.      dec     PIQL_CNT    ; One fewer times through the loop
  650.      jnz     CHECK_PIQL    ; Jump if
  651.  
  652.      cmp     PIQL,4     ; Check PIQL
  653.      jbe     CHECK_ERR    ; Jump if xxxx8
  654.  
  655.      or     bx,FLG_PIQL    ; PIQ length is 5 or longer
  656.      subttl  Check For Allowing Interrupts After POP SS
  657.      page
  658.  
  659. ; Test for faulty chip (allows interrupts after change to SS register)
  660.  
  661. CHECK_ERR:
  662.      call     ERR_SUB    ; Handled via subroutine
  663.      jcxz     CHECK_NDP    ; If CX is 0, the DEC was executed,
  664.                 ; and the CPU is OK
  665.      or     bx,mask $FLG_CERR ; It's a faulty chip
  666.      subttl  Check For Numeric Data Processor
  667.      page
  668. CHECK_NDP:
  669.      call     NDP_SUB    ; Handled via subroutine
  670.  
  671.      REGREST <es,ds,di,cx,ax> ; Restore registers
  672.      assume  ds:nothing,es:nothing
  673.  
  674.      ret            ; Return to caller
  675.  
  676.      assume  ds:nothing,es:nothing
  677.  
  678. CPUID     endp            ; End CPUID procedure
  679.      subttl  Distinguish A 286 From 386
  680.      page
  681. DIST_286or386 proc   near
  682.      assume  ds:PGROUP,es:PGROUP
  683. COMMENT|
  684.  
  685. The test for 286 vs. 386 is done by attempting to set flag bits in
  686. the high-order nibble of the flag word.  If that's successful, it's a
  687. 386; otherwise it's a 286.
  688.  
  689. |
  690.  
  691.      REGSAVE <ax>        ; Save register
  692.  
  693.      pushf            ; Save flags for a moment
  694.  
  695.      mov     ax,0F000h    ; Try to set high bits in flag register
  696.  
  697.      push     ax        ; Move into flag register
  698.      popf
  699.  
  700.      pushf            ; Get flags back into AX
  701.      pop     ax
  702.  
  703.      popf            ; Restore original flags
  704.  
  705.      test     ax,0F000h    ; Any bits set?
  706.      jz     ITSA286    ; No, so it's a 286
  707.  
  708.      or     bx,FLG_38    ; It's a 38x
  709.      jmp     short DIST_286or386_EXIT ; Join common exit code
  710. ITSA286:
  711.      or     bx,FLG_28    ; It's a 28x
  712. DIST_286or386_EXIT:
  713.      REGREST <ax>        ; Restore
  714.  
  715.      ret            ; Return to caller
  716.  
  717.      assume  ds:nothing,es:nothing
  718.  
  719. DIST_286or386 endp        ; End DIST_286or386 procedure
  720.      subttl  Check For NEC V20/V30
  721.      page
  722. CHECK_NEC proc     near
  723.      assume  ds:PGROUP,es:PGROUP
  724. COMMENT|
  725.  
  726.    The NEC V20/V30 CPUs are very compatible with the Intel 8086/8088.
  727. The only point of "incompatiblity" is that they do not contain a bug
  728. found in the Intel CPUs.  Specifically, the NEC CPUs correctly restart
  729. an interrupted multi-prefix string instruction at the start of the
  730. instruction.  The Intel CPUs incorrectly restart it in the middle of
  731. the instruction.  This routine tests for that situation by executing
  732. such an instruction for a sufficiently long period of time for a timer
  733. interrupt to occur.  If at the end of the instruction, CX is zero,
  734. it must be an NEC CPU; if not, it's an Intel CPU.
  735.  
  736.    Note that we're counting on the timer interrupt to do its thing
  737. every 18.2 times per second.
  738.  
  739.    Here's a worst case analysis:  An Intel 8086/8088 executes 65535
  740. iterations of LODSB ES:[SI] in 2+9+13*65535 = 851,966 clock ticks.  If
  741. the Intel 8086/8088 is running at 15 MHz, each clock tick is 66.67
  742. nanoseconds, hence the entire operation takes 56.8 milliseconds.  If the
  743. timer is running at normal speed, it interrupts the CPU every 55
  744. millseconds and so should interrupt the repeated string instruction at
  745. least once.
  746.  
  747. |
  748.  
  749.      mov     cx,0FFFFh    ; Move a lot of data
  750.      sti            ; Ensure timer enabled
  751.  
  752. ; Execute multi-prefix instruction.  Note that the value of ES as
  753. ; well as the direction flag setting is irrelevant.
  754.  
  755.      push     ax        ; Save registers
  756.      push     si
  757.      rep lods     byte ptr es:[si]
  758.      pop     si        ; Restore
  759.      pop     ax
  760.  
  761. ; On exit, if CX is zero, it's an NEC CPU, otherwise it's an Intel CPU
  762.  
  763.      ret            ; Return to caller
  764.  
  765.      assume  ds:nothing,es:nothing
  766.  
  767. CHECK_NEC endp
  768.      subttl  Pre-fetch Instruction Queue Subroutine
  769.      page
  770. PIQL_SUB proc     near
  771.      assume  ds:PGROUP,es:PGROUP
  772. COMMENT|
  773.  
  774.    This subroutine attempts to discern the length of the CPU's
  775. pre-fetch instruction queue (PIQ).
  776.  
  777.    It stores a new instruction into the instruction stream
  778. following the STOSB.  The loop proceeds backwards from the end
  779. of the stream to the beginning.  At the point the inserted
  780. instruction is not executed, we have found the last byte in
  781. the PIQ.  The value in CX at that time is then the PIQ length.
  782.  
  783. |
  784.  
  785.      REGSAVE <ax,bx,dx,si,di> ; Save registers
  786.  
  787. @REP     equ     64        ; Maximum length of PIQ
  788.                 ; we can handle
  789.  
  790.      std            ; Store backwards
  791.  
  792.      mov     cx,@REP    ; Loop counter
  793.      lea     di,LAB_NOP+@REP-1 ; ES:DI ==> PIQL last byte
  794.                 ; in fill area
  795.  
  796.      mov     al,ds:LAB_INC    ; Change to INC BX
  797.      mov     ah,ds:LAB_NOP    ; Save a NOP here to restore
  798.      mov     si,1        ; Divisor
  799.      xor     dx,dx        ; Zero high-order word for divide
  800.      cli            ; Ensure interrupts are disabled, otherwise
  801.                 ; a timer tick could disturb the PIQ filling
  802.      even            ; Ensure word alignment for LAB_FILL
  803.      nop
  804. PIQL_SUB_NEXT:
  805.      xor     bx,bx        ; Initialize flag
  806.      div     si        ; Take up some time and
  807.                 ; refill the queue
  808.      stosb            ; Change the instruction
  809.  
  810. ; The PIQ begins filling here
  811.  
  812. LAB_NOP  label     byte
  813.      rept     @REP
  814.      nop            ;; Fill byte
  815.      endm
  816.  
  817.      mov     es:[di+1],ah    ; Restore the NOP
  818.  
  819.      and     bx,bx        ; Did we execute it?
  820.      loopnz  PIQL_SUB_NEXT    ; Go around again if we did
  821.                 ; and loop not finished
  822.      inc     cx        ; Count in last byte
  823.  
  824.      sti            ; Restore interrupts
  825.      cld            ; Restore direction flag
  826.  
  827.      REGREST <di,si,dx,bx,ax> ; Restore
  828.  
  829. ; At the end, CX has the length of the PIQ
  830.  
  831.      ret            ; Return to caller
  832.  
  833. LAB_INC  label     byte
  834.      inc     bx        ; Increment counter
  835.  
  836.      assume  ds:nothing,es:nothing
  837.  
  838. PIQL_SUB endp            ; End PIQL_SUB procedure
  839.      subttl  Check For Faulty Interrupts
  840.      page
  841. ERR_SUB  proc     near
  842.      assume  ds:PGROUP,es:PGROUP
  843. COMMENT|
  844.  
  845. Test for faulty chip (allows interrupts after change to SS register).
  846. Setup a handler for INT 01h (single-step interrupt) and turn on that
  847. flag just before executing a POP SS.  If the CPU allows a single-step
  848. interrupt after the POP SS, it's faulty.
  849.  
  850. On exit:
  851.  
  852. CX     =     1 if CPU is faulty
  853.      =     0 if OK
  854.  
  855. |
  856.  
  857.      REGSAVE <ax,ds>    ; Save registers
  858.  
  859.      xor     ax,ax        ; Prepare to address interrupt vector segment
  860.      mov     ds,ax        ; DS points to segment 0
  861.      assume  ds:INT_VEC    ; Tell the assembler
  862.  
  863.      cli            ; Nobody move while we swap
  864.  
  865.      lea     ax,INT01    ; Point to our own handler
  866.      xchg     ax,INT01_OFF    ; Get and swap offset
  867.      mov     OLDINT01_OFF,ax ; Save to restore later
  868.  
  869.      mov     ax,cs        ; Our handler's segment
  870.      xchg     ax,INT01_SEG    ; Get and swap segment
  871.      mov     OLDINT01_SEG,ax ; Save to restore later
  872.  
  873. ; Note we continue with interrupts disabled to avoid an external interrupt
  874. ; occurring during this test.
  875.  
  876.      mov     cx,1        ; Initialize a register
  877.      push     ss        ; Save SS to store back into itself
  878.  
  879.      pushf            ; Move flags
  880.      pop     ax        ; ...into AX
  881.      or     ax,mask $TF    ; Set trap flag
  882.      push     ax        ; Place onto stack
  883.      POPFF            ; ...and then into effect
  884.                 ; Some CPUs effect the trap flag immediately,
  885.                 ;   some wait one instruction.
  886.      nop            ; Allow interrupt to take effect
  887. POST_NOP:
  888.      pop     ss        ; Change the stack segment register (to itself)
  889.      dec     cx        ; Normal CPUs execute this instruction before
  890.                 ; recognizing the single-step interrupt
  891.      hlt            ; We never get here
  892. INT01:
  893.  
  894. ; Note IF=TF=0
  895.  
  896. ; If we're stopped at or before POST_NOP, continue on
  897.  
  898.      push     bp        ; Prepare to address the stack
  899.      mov     bp,sp        ; Hello, Mr. Stack
  900.  
  901.      cmp     [bp].ARG_OFF,offset cs:POST_NOP ; Check offset
  902.      pop     bp        ; Restore
  903.      ja     INT01_DONE    ; We're done
  904.  
  905.      iret            ; Return to caller
  906. INT01_DONE:
  907.  
  908. ; Restore old INT 01h handler
  909.  
  910.      push     es        ; Save for a moment
  911.      les     ax,OLDINT01_VEC ; ES:AX ==> old INT 01h handler
  912.      assume  es:nothing    ; Tell the assembler
  913.      mov     INT01_OFF,ax    ; Restore offset
  914.      mov     INT01_SEG,es    ; ...and segment
  915.      pop     es        ; Restore
  916.      assume  es:PGROUP    ; Tell the assembler
  917.  
  918.      sti            ; Allow interrupts again (IF=1)
  919.  
  920.      add     sp,3*2     ; Strip IP, CS, and Flags from stack
  921.  
  922.      REGREST <ds,ax>    ; Restore
  923.      assume  ds:PGROUP    ; Tell the assembler
  924.  
  925.      ret            ; Return to caller
  926.  
  927.      assume  ds:nothing,es:nothing
  928.  
  929. ERR_SUB  endp            ; End ERR_SUB procedure
  930.      subttl  Check For Numeric Data Processor
  931.      page
  932. NDP_SUB  proc     near
  933.      assume  ds:PGROUP,es:PGROUP
  934. COMMENT|
  935.  
  936.    Test for a Numeric Data Processor -- Intel 8087, 80287, or 80387.
  937. An 8087 allows FDISI, an 80287/80387 ignores it.  The 80287 and 80387
  938. can be distinguished through their different treatment of the infinity
  939. closure setting.
  940.  
  941.    In general, the technique used is passive -- it leaves the NDP in
  942. the same state in which it is found.
  943.  
  944.    Unfortunately, some IBM PC/ATs and 3270/ATs without an NDP don't
  945. handle floating-point instructions correctly.  In particular, when
  946. no-WAIT NDP instruction is executed on those systems, they wipe out
  947. the memory location and all bytes following in the same segment.  To
  948. overcome this bug, Dan Lewis has suggested a technique which computes
  949. the segment and offset of the location into which the store is made
  950. such that the offset is in the last paragraph of the segment.  This
  951. way, the wipe out is harmless.
  952.  
  953. On exit:
  954.  
  955. BX     =     $FLG_NDP & $FLG_NERR bits set as appropriate.
  956.  
  957. |
  958.  
  959.      REGSAVE <ax,cx,di>    ; Save registers
  960.  
  961.      call     CHECK_1167    ; See if there's a Weitek 1167 in the system
  962.  
  963. ; Because some IBM PC/ATs and 3270/ATs without an NDP don't handle
  964. ; floating-point instructions correctly, we check for a 286 explicitly
  965. ; and rely upon the equipment flags to tell us if there's an NDP installed.
  966. ; This behavior is also present on some 386s.
  967.  
  968.      test     LCL_FLAGS,@LCL_REAL ; Use real code or not?
  969.      jnz     NDP_SUB1    ; It's real
  970.  
  971.      mov     ax,bx        ; Copy CPUID bits for destructive testing
  972.      and     ax,(mask $FLG_CPU) and not FLG_PIQL ; Isolate CPU bits
  973.  
  974.      cmp     ax,FLG_28    ; Izit a 28x?
  975.      je     NDP_SUB0    ; Yes, skip NDP instructions
  976.  
  977.      cmp     ax,FLG_38    ; Izit a 38x?
  978.      jne     NDP_SUB1    ; Not this time
  979. NDP_SUB0:
  980.      test     LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
  981.      jnz     NDP_SUB_UN    ; Yes
  982.  
  983.      int     11h        ; Get equipment flags into AX
  984.  
  985.      test     ax,mask $I11_NDP ; Check NDP-installed bit
  986.      jz     NDP_SUB_EXIT0    ; Not installed
  987.  
  988.      call     DIST_287or387    ; Distinguish a 287 from a 387
  989. NDP_SUB_EXIT0:
  990.      jmp     NDP_SUB_EXIT    ; Join common exit code
  991.  
  992. NDP_SUB_UN:
  993.      or     bx,FLG_NDPU    ; Mark as untested
  994.  
  995.      jmp     NDP_SUB_EXIT    ; Join common exit code
  996.  
  997. NDP_SUB1:
  998.      push     es        ; Save for a moment
  999.  
  1000.      lea     di,NDP_ENV+(size NDP_ENV)-1 ; Offset of end of environment
  1001.      call     MAX_OFFSET    ; Return with ES:DI ==> NDP_ENV and DI largest
  1002.      assume  es:nothing
  1003.      sub     di,(size NDP_ENV)-1 ; Back off to start of NDP_ENV
  1004.  
  1005.      cli            ; Protect FNSTENV
  1006.      fnstenv es:[di]    ; If NDP present, save current environment,
  1007.                 ; otherwise, this instruction is ignored
  1008.      sti            ; Allow interrupts
  1009.  
  1010.      pop     es        ; Restore
  1011.      assume  es:PGROUP
  1012.  
  1013.      mov     cx,50/7    ; Cycle this many times
  1014.      loop     $        ; Wait for result to be stored
  1015.  
  1016.      fninit         ; Initialize processor to known state
  1017.      jmp     short $+2    ; Wait for initialization
  1018.  
  1019.      push     es        ; Save for a moment
  1020.  
  1021.      lea     di,NDP_CW+(size NDP_CW)-1 ; Offset of end of control word
  1022.      call     MAX_OFFSET    ; Return with ES:DI ==> NDP_CW and DI largest
  1023.      assume  es:nothing
  1024.      sub     di,(size NDP_CW)-1 ; Back off to start of control word
  1025.  
  1026.      fnstcw  es:[di]    ; Save control word
  1027.  
  1028.      pop     es        ; Restore
  1029.      assume  es:PGROUP
  1030.  
  1031.      jmp     short $+2    ; Wait for result to be stored
  1032.      jmp     short $+2
  1033.  
  1034.      and     NDP_CW,not mask $IC ; Turn off infinity control in case of 387
  1035.  
  1036.      cmp     NDP_CW_HI,03h    ; Check for NDP initial control word
  1037.      jne     NDP_SUB_NONE    ; No NDP installed
  1038.  
  1039.      test     LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
  1040.      jnz     NDP_SUB2    ; Yes
  1041.  
  1042.      int     11h        ; Get equipment flags into AX
  1043.  
  1044.      test     ax,mask $I11_NDP ; Check NDP-installed bit
  1045.      jnz     NDP_SUB2    ; It's correctly set
  1046.  
  1047.      or     bx,mask $FLG_NERR ; Mark as in error
  1048. NDP_SUB2:
  1049.      and     NDP_CW,not mask $IEM ; Enable interrupts (IEM=0, 8087 only)
  1050.      fldcw     NDP_CW     ; Reload control word
  1051.      fdisi            ; Disable interrupts (IEM=1) on 8087,
  1052.                 ; ignored by 80287/80387
  1053.      fstcw     NDP_CW     ; Save control word
  1054.      fldenv  NDP_ENV    ; Restore original NDP environment
  1055.                 ; No need to wait for environment to be loaded
  1056.  
  1057.      test     NDP_CW,mask $IEM ; Check Interrupt Enable Mask (8087 only)
  1058.      jnz     NDP_SUB_8087    ; It changed, hence NDP is an 8087
  1059.  
  1060.      call     DIST_287or387    ; Distinguish a 287 from a 387
  1061.  
  1062.      jmp     short NDP_SUB_EXIT ; Exit with flags in BX
  1063. NDP_SUB_8087:
  1064.      or     bx,FLG_87    ; NDP is an 8087
  1065.  
  1066.      jmp     short NDP_SUB_EXIT ; Join common exit code
  1067. NDP_SUB_NONE:
  1068.      test     LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
  1069.      jnz     NDP_SUB_EXIT    ; Yes
  1070.  
  1071.      int     11h        ; Get equipment flags into AX
  1072.  
  1073.      test     ax,mask $I11_NDP ; Check NDP-installed bit
  1074.      jz     NDP_SUB_EXIT    ; It's correctly set
  1075.  
  1076.      or     bx,mask $FLG_NERR ; Mark as in error
  1077. NDP_SUB_EXIT:
  1078.      REGREST <di,cx,ax>    ; Restore
  1079.  
  1080.      ret            ; Return to caller
  1081.  
  1082.      assume  ds:nothing,es:nothing
  1083.  
  1084. NDP_SUB  endp            ; End NDP_SUB procedure
  1085.      subttl  Check For Weitek 1167
  1086.      page
  1087. CHECK_1167 proc near
  1088.      assume  ds:PGROUP,es:PGROUP
  1089. COMMENT|
  1090.  
  1091. See if there's a Weitek 1167 in the system.
  1092.  
  1093. To determine that, clear EAX, call INT 11h, and test bit 24
  1094. in EAX.  If set, the coprocessor is present; if not, then
  1095. it's not.  Obviously, we must be running on a 386.
  1096.  
  1097. |
  1098.  
  1099.      test     LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
  1100.      jnz     CHECK_1167_EXIT ; Yes, 1167 untested
  1101.  
  1102.      test     bx,FLG_38    ; Are we on a 38x?
  1103.      jz     CHECK_1167_EXIT ; No, thus no 1167
  1104.  
  1105.      db     66h        ; Use EAX
  1106.      push     ax        ; Save for a moment
  1107.  
  1108.      db     66h        ; Use EAX
  1109.      xor     ax,ax        ; Clear entire register
  1110.  
  1111.      int     11h        ; Get equipment flags
  1112.  
  1113.      db     66h        ; Use EAX
  1114.      test     ax,0000h
  1115.      dw     0100h        ; Test bit 24
  1116.      jz     CHECK_1167_EXIT0 ; Not present
  1117.  
  1118.      or     bx,FLG_1167    ; Mark as present
  1119. CHECK_1167_EXIT0:
  1120.      db     66h        ; Use EAX
  1121.      pop     ax        ; Restore
  1122. CHECK_1167_EXIT:
  1123.      ret            ; Return to caller
  1124.  
  1125.      assume  ds:nothing,es:nothing
  1126.  
  1127. CHECK_1167 endp         ; End CHECK_1167 procedure
  1128.      subttl  Distinguish A 287 From 387
  1129.      page
  1130. DIST_287or387 proc near
  1131.      assume  ds:PGROUP,es:PGROUP
  1132. COMMENT|
  1133.  
  1134.    Distinguish a 287 from a 387.
  1135.  
  1136.    Both the 80287 and 80387 are initialized with the infinity closure
  1137. bit set to one.  However, only the 80287 is sensitive to the value of
  1138. this bit.  Ordinarily when this bit is set to one, the chip uses
  1139. projective closure; when it is cleared to zero, the chip uses affine
  1140. closure.  Thus the 80287 is initialized to use projective closure, but
  1141. that state can be changed through the infinity closure bit in the
  1142. control word.  On the other hand, the 80387 is initialized to use
  1143. affine closure and remains in that state independent of the setting of
  1144. the infinity closure bit.  The two NDPs can be distinguished by
  1145. executing code which is sensitive to the setting of the infinity
  1146. closure bit.
  1147.  
  1148.    The algorithm used is based upon one published by Intel on how to
  1149. detect the 80387.
  1150.  
  1151. On exit:
  1152.  
  1153. BX     =     $FLG_NDP bits set as appropriate.
  1154.  
  1155. |
  1156.  
  1157. .287
  1158.      fstenv  NDP_ENV    ; Save current environment
  1159.      finit            ; Initialize processor to known state
  1160.                 ; The 80287 is using projective
  1161.                 ; closure for arithmetic, the 80387 is
  1162.                 ; using affine closure
  1163.  
  1164.      fld1            ; Generate infinity
  1165.      fldz            ;  by dividing zero into one
  1166.      fdiv            ; ST0 = +infinity
  1167.      fld     st(0)        ; Copy it
  1168.      fchs            ; ST0 = -infinity, ST1 = +infinity
  1169.      fcompp         ; Compare them and pop both from stack
  1170.      fstsw     ax        ; Get status word
  1171.      fldenv  NDP_ENV    ; Restore original NDP environment
  1172.      sahf            ; Copy into flags
  1173.      jz     DIST_287or387_PROJ ; Jump if the two are equal
  1174.                 ; (projective closure)
  1175.  
  1176.      or     bx,FLG_387    ; NDP is an 80387
  1177.  
  1178.      jmp     short DIST_287or387_EXIT ; Join common exit code
  1179.  
  1180. DIST_287or387_PROJ:
  1181.      or     bx,FLG_287    ; NDP is an 80287
  1182. DIST_287or387_EXIT:
  1183.      ret            ; Return to caller
  1184. .8087
  1185.      assume  ds:nothing,es:nothing
  1186.  
  1187. DIST_287or387 endp        ; End DIST_287or387 procedure
  1188.      subttl  Calculate Maximum Offset
  1189.      page
  1190. MAX_OFFSET proc  near
  1191.      assume  ds:nothing,es:nothing
  1192. COMMENT|
  1193.  
  1194. On entry:
  1195.  
  1196. ES:DI     ==>     memory offset
  1197.  
  1198. On exit:
  1199.  
  1200. ES:DI     ==>     same location but with DI as large as possible
  1201.  
  1202. |
  1203.  
  1204. MAXOFF     equ     0FFF0h
  1205.  
  1206.      REGSAVE <ax,cx>    ; Save registers
  1207.  
  1208.      push     di
  1209.  
  1210.      mov     cl,4        ; Shift amount
  1211.      shr     di,cl        ; Isolate para of control word
  1212.      mov     ax,es        ; Get current segment
  1213.      add     ax,di        ; Add in segment of control word
  1214.      sub     ax,MAXOFF shr 4 ; Less a lot of paras
  1215.      mov     es,ax        ; Save as segment of control word
  1216.  
  1217.      pop     di        ; Restore
  1218.  
  1219.      or     di,MAXOFF    ; Include paras subtracted out above
  1220.  
  1221.      REGREST <cx,ax>    ; Restore
  1222.  
  1223.      ret            ; Return to caller
  1224.  
  1225.      assume  ds:nothing,es:nothing
  1226.  
  1227. MAX_OFFSET endp         ; End MAX_OFFSET procedure
  1228.  
  1229. CODE     ends            ; End CODE segment
  1230.  
  1231.      if1
  1232. %OUT Pass 1 complete
  1233.      else
  1234. %OUT Pass 2 complete
  1235.      endif
  1236.  
  1237.      end     INITIAL    ; End CPUID module
  1238.