[comp.os.msdos.programmer] Accessing CMOS RAM

joe@acmis.cmis.co.at (Johannes Rupp) (08/31/90)

I would like to know how to access the contents of the CMOS RAM where the
PC configuration (like disk drive type, amount of memory, etc.) is
stored.

Can anybody tell me at which memory address the CMOS starts, or
what other mechanism I could use to access these information by
an assembler or C program.

Thanks for any information,


Johannes
-- 
+------------------------------------------------------------------------+
| CMIS Austria                |      Voice:  (++43-222) 98 109  Ext. 110 |
| NIELSEN Austria             |      Voice:  (++43-222) 98 110  Ext. 110 |
| Moeringgasse 20             |      Fax:    (++43-222) 98 110 77        |
| A-1150 Vienna, Austria      |      E-Mail: joe@acmis.cmis.co.at        | 
|                             |              ...!mcsun!tuvie!acmis!joe   |
+------------------------------------------------------------------------+
-- 
+------------------------------------------------------------------------+
| CMIS Austria                |      Voice:  (++43-222) 98 109  Ext. 110 |
| NIELSEN Austria             |      Voice:  (++43-222) 98 110  Ext. 110 |
| Moeringgasse 20             |      Fax:    (++43-222) 98 110 77        |

phys169@canterbury.ac.nz (09/18/90)

In article <1990Aug31.055038.2326@acmis.cmis.co.at>, joe@acmis.cmis.co.at (Johannes Rupp) writes:
> I would like to know how to access the contents of the CMOS RAM where the
> PC configuration (like disk drive type, amount of memory, etc.) is
> stored.
> 
> Can anybody tell me at which memory address the CMOS starts, or
> what other mechanism I could use to access this information by
> an assembler or C program.
> 
For The IBM AT and PS2, you write a number (0-63) to port 70hex, and quickly
read from port 71h (or write to it). Don't write to port 70h without reading
from it in the next few instructions, and don't give it an address over 127.
As with all port accesses, you'll probably need a few time-wasting jumps
between any pairs of IO instructions to let the hardware catch up to the CPU.
The assignment of locations is difficult to summarise, so I've included a Turbo
Pascal program that accesses the CMOS clock & RAM. The executeable of a later
and much nicer version (with automatic determination of disk types, etc) will
be available soon (anyone with an ftp site wanting it can e-mail me now).

For the PC & XT, the base address varies from board to board, and most programs
test a bunch of memory locations until one seems reasonable; successive cells
are at successive addresses, unlike the 2-port method on the AT/PS2, and the
RAM doesn't carry information like disk types.

Hope this helps, 
Mark Aitchison, Physics, University of Canterbury, New Zealand.

---snip here----[CMOS.PAS v2.1]---------------------------
program CMOS; {show/edit AT CMOS RAM contents}
              {feel free to use & distribute this program non-commercially}
uses DOS,CRT;

const HighlightedAddr : byte = $3F;
      HelpInterrupt = $4E;
      SaveNibbles : longint = 0; {to implement DEL key}
      CMOSport    : word = $70;  {start by assuming AT or PS2 CMOS RAM}
      Unknown     = $FFFF; {probably a PC/XT Cmos card, need to look for the port}
      NewContents : byte = 0;
      NeedToChange : boolean = false;
      Addresses  : string ='';
      Globals      : string ='';
      SwitchChar : char = '/';

var st  : string;
    Model : string[9];
    reg : registers;
    OldCursor,
    n,er  : integer;
    i,j : word;
    Contents,
    adr : byte;
    Keystroke : char;
    MachineID : word absolute $F000:$FFFE;

function binary(w : byte) : string;
const
   digit : array[0..$F] of char = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
var b : byte;
   st : string[8];
begin
st:='00000000';
for b:=0 to 7 do
    if odd(w shr b) then st[8-b]:='1';
binary:=st;
end;

function hex(w : byte) : string;
const
   digit : array[0..$F] of char = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
begin
hex:=digit[w shr 4]+digit[w and $0F];
end;

procedure Beep(freq : word);
var i : integer; reg : registers;
begin
sound(freq);
for i:=1 to 10 do begin
                  intr($28,reg);
                  delay(15);
                  end;
nosound;
end;

function HelpInstalled : boolean;
type TSRheader = record Version : word; Name : array[1..3] of char; end;
var HelpPointer : ^TSRheader;
begin
GetIntVec(HelpInterrupt,pointer(HelpPointer));
if ofs(HelpPointer^)<5 then begin HelpInstalled:=false; exit; end;
dec(word(HelpPointer),5);
HelpInstalled:=HelpPointer^.Name='HLP';
end;

procedure Help(topic : string);
var reg : registers;
begin
if not HelpInstalled then begin beep(440); exit; end;
with reg do begin
            DS:=seg(topic);
            DX:=ofs(topic);
            intr(HelpInterrupt,reg);
            end;
end;

procedure CursorOff;
begin
with reg do begin
            AH:=3; BH:=0;
            intr($10,reg); {get present cursor}
            OldCursor:=CX;
            CX:=$2020;
            AH:=1;
            intr($10,reg); {set cursor to nothing}
            end;
end;

procedure CursorOn;
begin
with reg do begin
            CX:=OldCursor;
            AH:=1;
            intr($10,reg); {set cursor to what it was}
            end;
end;

function CmosRam(adr : byte ): byte;
begin
if CmosPort=$70
   then begin {AT or PS/2 CMOS}
        inline($FA);        { cli        ;DisableInterrupts }
        Port[CmosPort]:=adr;     { out 70h,adr                   }
        CmosRam:=Port[$71]; { in  CmosRam,71h               }
        inline($FB);        { sti        ;EnableInterrupts  }
        end
   else begin
        inline($FA);        { cli        ;DisableInterrupts }
        i:=Port[CmosPort+adr]; {read twice in case port strange}
        if i<>Port[CmosPort+adr] then {worry};
        inline($FB);        { sti        ;EnableInterrupts  }
        CmosRam:=i;
        end;
end;

procedure SetCmosRam(adr,NewContents : byte );
var i : byte; CheckSum : word;
begin
if CmosPort=$70
   then begin {AT or PS/2 CMOS}
        inline($FA);        { cli        ;DisableInterrupts }
        Port[$70]:=adr;         { out 70h,adr                   }
        Port[$71]:=NewContents; { out 71h,NewContents           }
        inline($FB);        { sti        ;EnableInterrupts  }
        end
   else begin
        Port[CmosPort+adr]:=NewContents;
        if NewContents<>Port[CmosPort+adr] then {worry};
        end;
if adr in [$10..$2D]
   then begin
        CheckSum:=0;
        for i:=$10 to $2D do inc(CheckSum,CmosRam(i));
        SetCmosRam($2E,hi(CheckSum));
        SetCmosRam($2F,lo(CheckSum));
        end;
end;

procedure DisplayClockData;
var adr : byte;
begin
for adr:=$00 to $05 do
    begin
    gotoXY(6,2+adr);
    if adr=HighlightedAddr then TextAttr:=$78 else TextAttr:=$0F;
    write(hex(CmosRam(adr)));
    end;
end;

procedure DisplayOtherData(adr : byte);
const Col : array [0..3] of byte = (6,30,52,68);
      DayName : array[0..7] of string[9] = ('Sunday   ','Monday   ','Tuesday  ','WednesDay',
                                            'Thursday ','Friday   ','Saturday ','Sunday   ');
      ScreenTypeName : array[0..3] of string[10] = ('EGA/VGA/etc','CGA 40col','CGA 80col','monochrome');
      DisketteTypeName : array[0..4] of string[4] = ('none','360k','1.2M','720k','1.4M');
      HardDiskTypeName : array[0..15] of string[5]= ('none','306/4','615/4','615/6','940/8',
                                                    '940/6','615/4','462/8','733/5','900/F',
                                                    '820/3','855/5','855/7','306/8','733/7','other');
      MonthName : array[0..12] of string[3] = ('???','Jan','Feb','Mar','Apr','May','Jun',
                                               'Jul','Aug','Sep','Oct','Nov','Dec');
var a,b,c,d : byte;
begin
if adr=HighlightedAddr then TextAttr:=$78 else TextAttr:=$0F;
gotoXY(Col[adr shr 4],2+(adr and $0F));
Contents:=CmosRam(adr);
write(hex(Contents));
if CmosPort<>$70 then write(hex(Contents),'h =',Contents:3,'   ')
   else case adr of
     $06 : begin
           gotoXY(Col[0]+7,8);
           if Contents<7 then write(DayName[Contents])
                         else write('??? =',Contents:3);
           end;
     $08 : begin
           gotoXY(Col[0]+9,10);
           if Contents>9 then if (CmosRam($0B) and 4)=0 then dec(Contents,6);
           if Contents in [1..12] then write(MonthName[Contents])
                                  else write('???');
           end;
     $0A : begin
           gotoXY(16,2+$0A);
           write(Binary(Contents));
           gotoXY(1,22);
           a:=(Contents shr 4) and 7;
           b:=(Contents and $0F);
           if a=2 then write('32.768kHz time base, ')
                  else write('strange DV=',a,'!       ');
           if b=6 then write('1024Hz P.I. rate, ')
                  else write('P.I. rate sel=',b,'?  ');
           end;
     $0B : begin
           gotoXY(16,2+$0B);
           write(Binary(Contents));
           gotoXY(40,22);
           if boolean(Contents and $80) then write('FROZEN,   ')
                                        else write('counting, ');
           if boolean(Contents and $40) then write('P.I. on,  ')
                                        else write('P.I. off, ');
           if boolean(Contents and $20) then write('Alarm on,  ')
                                        else write('Alarm off, ');
           writeln('UIE=',boolean(Contents and $10));
           if boolean(Contents and $08) then write('Square wave on,  ')
                                        else write('Square wave off, ');
           if boolean(Contents and $04)
              then write('Binary mode; time=',CmosRam(4):2,':',CmosRam(2),':',CmosRam(0),', date:',
                    CmosRam(7),'/',MonthName[CmosRam(8) mod 13],'/',
                    (19+(CmosRam($32) and 1)*100+CmosRam(9)))
              else write('   BCD mode; time=',hex(CmosRam(4)),':',hex(CmosRam(2)),':',hex(CmosRam(0)),', date:',
                    hex(CmosRam(7)),'/',MonthName[((CmosRam(8) and $F)+10*(CmosRam(8) shr 4)) mod 13],'/',
                    hex(CmosRam($32))+hex(CmosRam(9)));
           write(', Dayl. Sav.=',odd(Contents));
           end;
     $0C..$0F: begin gotoXY(16,2+adr); write(Binary(Contents)); end;
     $1B..$2D,                    {"reserved", normally 0}
     $11,$13 : write(' =',Contents:3);
     $34..$3F: if Contents in [32..126]           {"reserved", often junk}
                  then write(' =',Contents:3,' "',chr(Contents),'"')
                  else write(' =',Contents:3,'    ');
     $10     : begin
               a:=Contents shr 4;
               if a<=4 then write(' A:'+DisketteTypeName[a])
                       else write(' A:',a:2,'??');
               b:=Contents and $0F;
               if b<=4 then write(' B:'+DisketteTypeName[b])
                       else write(' B:',b:2,'??');
               end;
     $12     : begin
               c:=Contents shr 4;
               d:=Contents and $0F;
               st:=' C:'+HardDiskTypeName[c]+',D:'+HardDiskTypeName[d]+'   ';
               st[0]:=#15;
               write(st);
               end;
     $14     : begin
               write(' equip=',binary(Contents));
               intr($11,reg);
               gotoXY(1,19);
               if (reg.AL and $F3)<>(Contents and $F3)
                  then write('Equipment flags are ',binary(Contents),'; they should be ',binary(reg.AL))
                  else write('Equipment: ',succ(Contents shr 6)*(Contents and 1),' diskettes, ',
                                           ScreenTypeName[(Contents shr 4) and 3],' screen'+
                                           ', Coprocessor=',boolean(Contents and 2) );
               end;
     $15     : with reg do
                    begin
                    intr($12,reg);
                    if HighlightedAddr=$16 then TextAttr:=$78;
                    gotoXY(29,21);
                    i:=Contents+256*CmosRam($16);
                    if i=reg.AX
                       then write('Base: okay           ')
                       else write('Base: should be ',reg.AX,'!');
                    end;
     $16,$18 : begin
               if HighlightedAddr=adr-1 then TextAttr:=$78;
               write('  =',Contents*256+CmosRam(adr-1):5);
               end;
     $19     : begin
               if HighlightedAddr=$12 then TextAttr:=$78;
               if Contents>0 then write(' C: type ',Contents:3)
                             else write('            ');
               end;
     $1A     : begin
               if HighlightedAddr=$12 then TextAttr:=$78;
               if Contents>0 then write(' D: type ',Contents:3)
                             else write('            ');
               end;
     $33     : begin
               gotoXY(6,20);
               if boolean(Contents and $80) then write('Info: top 128K of base mem installed.    ')
                                            else write('Info: top 128K of base mem not installed.');
               write(' "First User"=',boolean(Contents and $40));
               if (Contents and $3F)<>0 then write(' STRANGE?')
                                        else write('         ');
               end;
     $2E     : begin
               gotoXY(2,21);
               if HighlightedAddr=$2F then TextAttr:=$78;
               j:=0;
               for i:=$10 to $2D do inc(j,CmosRam(i));
               i:=Contents*256+CmosRam($2F);
               if i<>j then write('Checksum: should be ',j,'!')
                       else write('Checksum: okay.           ');
               end;
     $31     : begin
               gotoXY(51,21);
               if HighlightedAddr in [$30,$17,$18] then TextAttr:=$78;
               i:=Contents*256+CmosRam($30);
               j:=CmosRam($18)*256+CmosRam($17);
               if i<>j then write('Ext-mem: should be ',i,'!')
                       else write('Ext-mem: okay          ');
               end;
     end {of case};
if boolean(adr and 7) then exit;
Contents:=CmosRam(HighlightedAddr);
gotoXY(64,19);
TextAttr:=$78;
write(hex(HighlightedAddr),': ',hex(Contents),'h =',Contents:3);
if Contents in [32..126] then write(' "',char(Contents),'"')
                         else write('    ');
end;

procedure ProcessKeystroke;
begin
if Keystroke=#0
      then case ord(ReadKey) of
           00 : Keystroke:=^C;  {Break}
           59 : Help(MODEL+' CMOS ADDRESS '+hex(HighlightedAddr));
           68 : Keystroke:=#27; {F10 also escapes from the program}
           72 : dec(HighlightedAddr);     {Up arrow}
           80 : inc(HighlightedAddr);     {Down Arrow}
           71 : HighlightedAddr:=0;       {Home}
           79 : HighlightedAddr:=$3F;     {End}
           75 : dec(HighlightedAddr,$10); {Left Arrow}
           77 : inc(HighlightedAddr,$10); {Right arrow}
           else Beep(1234);
           end {of case}
      else case Keystroke of
           '+' : SetCmosRam(HighlightedAddr,CmosRam(HighlightedAddr) +1);
           '-' : SetCmosRam(HighlightedAddr,CmosRam(HighlightedAddr) -1);
           '$' : {ignore, default is hex};
           '=' : begin
                 gotoXY(64,19); TextAttr:=$86; write('New value=');
                 TextAttr:=LightRed;
                 readln(i);
                 SaveNibbles:=(SaveNibbles shl 8)+CmosRam(HighlightedAddr);
                 SetCmosRam(HighlightedAddr,i);
                 end;
           '0'..'9' : if HighlightedAddr>0  {cannot easily set seconds this way!}
                         then begin
                              i:=CmosRam(HighlightedAddr);
                              SaveNibbles:=(SaveNibbles shl 4)+(i shr 4);
                              SetCmosRam(HighlightedAddr,ord(Keystroke)-ord('0')+(i*16) and $FF);
                              end;
           'A'..'F',
           'a'..'f' : if HighlightedAddr>0
                         then begin
                              i:=CmosRam(HighlightedAddr);
                              SaveNibbles:=(SaveNibbles shl 4)+(i shr 4);
                              SetCmosRam(HighlightedAddr,ord(upcase(Keystroke))-ord('A')+10+(i*16) and $FF);
                              end;
           '"',#39 : begin
                     gotoXY(64,19); write('type character: ');
                     i:=ord(ReadKey);
                     if i=0 then i:=ord(ReadKey);
                     SaveNibbles:=(SaveNibbles shl 8)+CmosRam(HighlightedAddr);
                     SetCmosRam(HighlightedAddr,i);
                     inc(HighlightedAddr);
                     end;
           #127,^H : begin
                     j:=(SaveNibbles and $000F)*16; SaveNibbles:=SaveNibbles shr 4;
                     i:=CmosRam(HighlightedAddr);
                     SetCmosRam(HighlightedAddr,j+(i shr 4));
                     end;
           end {of case};
end;

function ATorBetter : boolean;  {also set Model name}
begin
ATorBetter:=false; Model:='PC '; {assume PC/XT/Clone/PCjr/etc}
case lo(MachineID) of
     $FA,$F8,
     $FC     : with reg do begin
                           ATorBetter:=true;
                           AH:=$C0; intr($15,reg);
                           if not odd(Flags)
                              then Model:='PS/2'
                              else Model:='AT';
                           end;
          end { of case};
end;

procedure CheckGivenParameters;
type string127 = string[127];
var i : byte;
    GivenParameters : ^string127;
begin
reg.AX:=$3700; MsDos(reg); SwitchChar:=chr(reg.DL);
GivenParameters:=ptr(PrefixSeg,$80);
for i:=1 to length(GivenParameters^) do
    if GivenParameters^[i] in [',',';',^I]
       then GivenParameters^[i]:=' '
       else GivenParameters^[i]:=upcase(GivenParameters^[i]);
i:=1; n:=-1;
repeat st:=paramstr(i);
       if (pos('=',st)>1) or (pos('.',st)>1)
          then begin
               j:=pos(st,GivenParameters^); if st[1]='.' then inc(j,2);
               repeat case GivenParameters^[j] of
                           '=' : begin
                                 if st[1]<>'=' then insert(' ',GivenParameters^,j);
                                 repeat inc(j) until GivenParameters^[j]<>'=';
                                 if GivenParameters^[j]<>' ' then insert(' ',GivenParameters^,j);
                                 end;
                           '.' : begin
                                 if st[1]<>'.' then insert(' ',GivenParameters^,j);
                                 repeat inc(j) until GivenParameters^[j]<>'.';
                                 if GivenParameters^[j]<>' ' then insert(' ',GivenParameters^,j);
                                 end;
                           end;
                      inc(j);
                      until (j>length(GivenParameters^)) or (GivenParameters^[j]=' ');
               st:=paramstr(i);
               end;
       if st[1]=SwitchChar then st[1]:='/';
       case st[1] of
            '=' : begin NeedToChange:=true; delete(st,1,1); end;
        '/','-' : Globals:=Globals+'/'+upcase(st[2])+copy(st,3,99);
        '.','T' : begin
                  inc(i); j:=n;
                  val(paramstr(i),n,er);
                  for j:=j+1 to n do Addresses:=Addresses+chr(j);
                  end;
          '0'..'9','$' : begin
                         if st[length(st)]='H' then begin dec(st[0]); insert('$',st,1); end;
                         val(st,n,er);
                         if NeedToChange then NewContents:=n
                                         else Addresses:=Addresses+chr(n);
                         end;
          end {of case};
       inc(i);
       until (i>paramcount);
end;

procedure InteractiveMode;
begin
ClrScr;
DirectVideo:=true;
TextAttr:=Cyan;
writeln('ZDDDDDDDDDDDDDDDDDDDDDDDBDDDCMOS CLOCK/RAMDDDDBDDDDDDDDDDDDDDDBDDDDDDDDDDDDDDD?');
writeln('300:    Seconds         310:                  320:            330:    ex.mem. 3');
writeln('301:    Seconds alarm   311:                  321:            331:    found.  3');
writeln('302:    Minutes         312:                  322:            332:    Century 3');
writeln('303:    Minutes alarm   313:                  323:            333:    "Info"  3');
writeln('304:    Hours           314:                  324:            334:            3');
writeln('305:    Hours alarm     315:    Base memory   325:            335:            3');
writeln('306:    Day=xxxxxxxxx   316:           Kbytes 326:            336:            3');
writeln('307:    Day of month    317:    Extended mem. 327:            337:            3');
writeln('308:    Month           318:           Kbytes 328:            338:            3');
writeln('309:    Year            319:                  329:            339:            3');
writeln('30A:    stat.A=xxxxxxxx 31A:                  32A:            33A:            3');
writeln('30B:    stat.B=xxxxxxxx 31B:                  32B:            33B:            3');
writeln('30C:    stat.C=xxxxxxxx 31C:                  32C:            33C:            3');
writeln('30D:    stat.D=xxxxxxxx 31D:                  32D:            33D:            3');
writeln('30E:    diagn.=xxxxxxxx 31E:                  32E:    Checksum33E:            3');
writeln('30F:    shutd.=xxxxxxxx 31F:                  32F:    of 10-2D33F:            3');
writeln('@DDDDDDDDDDDDDDDDDDDDDDDADDDDDDDDDDDDDDDDDDDDDADDDDDDDDDDDDDDDADDDDDDDDDDDDDDDY');
gotoXY(1,24);
TextAttr:=$70; {reverse video, dim}
st:='(ESC=quit)('#24','#25','#26','#27' to move)(+,-,=," to alter)';
if HelpInstalled then st:=st+'(F1=help)';
write(st:48,' CMOS v2.1 (C)1990 M.Aitchison');
TextAttr:=Yellow;
DisplayClockData;
for adr:=$06 to $3F do DisplayOtherData(adr);
repeat
   repeat CursorOff;
          for adr:=$06 to $3F do
                        begin
                        DisplayOtherData(adr);
                        DisplayClockData;
                        end;
          CursorOn;
          until keypressed;
   KeyStroke:=ReadKey;
   ProcessKeystroke;
   until KeyStroke in [#27,^C];
gotoXY(1,24);
TextAttr:=White;
ClrEOL;
end;

procedure FindXtCmosPortAddresses;
var i : byte;
begin
CmosPort:=$200;
(* insert stuff here *)
end;

{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}
{{                                             {}
{{      MAIN PROGRAM: CMOS                     {}
{{                                             {}
{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}

begin
if not ATorBetter then CmosPort:=Unknown;
if paramcount>0 then CheckGivenParameters;
if pos('/A',Globals)>0 then begin CmosPort:=$70; Model:='AT'; end;
if pos('/X',Globals)>0 then begin CmosPort:=Unknown; Model:='PC/XT'; end;
if pos('/P',Globals)>0 then begin
                            i:=pos('/P',Globals)+2; j:=i;
                            repeat if Globals[i]='=' then j:=i+1;
                                   inc(i);
                                   until (i>length(Globals)) or (Globals[i]='/');
                            st:=copy(Globals,j,i-j);
                            if st[1]<>'$' then if (st[length(st)] in ['h','H','0'])
                               then val('$'+st,CmosPort,j)
                               else val(st,CmosPort,j);
                            if j<>0 then begin writeln('Invalid Cmos port: ',st); CmosPort:=Unknown; end;
                            if CmosPort<>$70 then Model:='PC/XT';
                            end;
if CmosPort=Unknown then FindXTCmosPortAddresses;
if (Addresses<>'') or (Addresses<>'')
   then begin
        for i:=1 to length(Addresses) do
            begin
            Assign(output,''); Rewrite(output); {allow standard output}
            j:=ord(Addresses[i]);
            if NeedToChange
               then begin
                    SetCmosRam(j,NewContents);
                    if pos('/B',Globals)=0
                       then writeln('Cmos RAM address ',hex(j),' now contains ',CmosRam(j),
                             ' (decimal, = ',hex(CmosRam(j)),' hex)');
                    end
               else if pos('/B',Globals)>0
                       then writeln(CmosRam(j))
                       else writeln(CmosRam(j),' (decimal, = ',hex(CmosRam(j)),' hex) at Cmos RAM addr ',
                                                 hex(j),' hex.');
            end;
        end
   else InteractiveMode;
end.