Statistics

Members: 1927
News: 293
Web Links: 1
Visitors: 3962218

Who's Online

We have 2 guests online
Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Home arrow About/Disclaimer
Using the RTC
User Rating: / 0
PoorBest 
Written by Jan Verhoeven   


Here are some routines to use the RTC/CMOS chip for serious timing. It's an introductory tutorial, so you'll be given more than enough opportunity to experiment with timing via this method.

 

 

About the hardware.

The RTC chip used to be a Motorola MC 146818A chip, but nowadays you will either find a Dallas 1287 or 1387 style chip, or it is embedded in the chipset. So far for romance... :o)

I will describe the Dallas DS 1287 since this is the configuration which is most common for many years now, and the majority of the features are the same as for the other chips.

The DS 1287 is a clock/RAM with a Lithium battery inside the package. That's why it stays so big: the battery needs space. If the system is powered on, the RTC gets its power from the powersupply. When the PC is off, the RTC goes into power-down mode and slowly drains the Lithium cell. Expected life for the battery is around 10 years.

The DS 1287 has 64 storage locations, 14 of which are clock and control registers and the remaining 50 are battery-backed general purpose RAM cells. This is were the CMOS setup of your PC stores it's system setup data.

The programmable clock can issue an interrupt, which can be triggered by three independent events: time of day, periodic signal or end of clockupdate.

The 14 registers inside the DS 1287 are:

    address         purpose
-------         ---------------------------------------
0            current value of seconds
1            alarm setting for seconds
2            current value of minutes
3            alarm setting for minutes
4            current value of hours
5            alarm setting for hours
6            Day of the week [Sunday = 1]
7            Day of the month
8            month                   [0..12]
9            year of this century    [0..99]
10            Control register A
11            Control register B
12            Control register C      [read-only]
13            Control register D      [read-only]

If you want to know the time of day, or any other date related data, just select the RTC chip and request the contents of the desired register.

The alarm registers can be set to generate long-time periodical interrupts, or for having the chip give a signal when it's time for your nap. The alarm rate ranges from seconds to weeks. And since these alarm registers are almost never used, they can also be used for storing some data for your own software. PTS Partition Manager for example uses these registers to keep track of where it was, while reformatting the hard disk. If there is a power-fail, it will just continue where it left off.

In the PC, the RTC chip is hidden from the programmer. It can only be accessed in an indirect way. The trick is to first select a register location and then access that one register as follows:

        mov     al, <register number>
out     70h, al         ; select <register number>
in      al, 71h         ; for a READ operation
out     71h, ah         ; for a WRITE operation

So, we use port 70h for selecting a register or storage location and use port 71h for doing the actual access to that register. A bit tedious, but that's how the PC was designed in the first place.

In "old style" RTC chips the century is maintained in software. It resides in a RAM cell, offset 32h/50d, so it will not be affected by a year-rollover from 99 to 00. If you update it with a short piece of code on January first 2000, your PC will be ready for many, many, moons to come.

The control registers.


Registers A, B, C and D are the registers that control the working of the RTC clock. They have various functions and register D uses just a singe bit, which is also read-only....
But this chip is well engineered and all registers have a significant (although not always logical) influence on the operation of it.

Register A: Timing control.


Register A is layed out as follows:
    bit     function
---     ------------------------------------------------------------
7      UIP bit: Update In Progress. When there's a ONE in this flag
the timing registers are being updated and it is not safe to
read them. Better to wait until this flag is cleared.
This one bit is read-only!
4-6     DV0-DV2: these three bits control the on-chip oscillator. Do
not experiment too much with this setting. There is only ONE
valid combination for these three bits: 010.
0-3     RS0 - RS3: These are the four Rate Selector bits. They
determine how often the IRQ pin is activated. The following
table shows the meaning of the different values.
RS3  RS2  RS1  RS0    Frequency [Hz]    Period [ms]
---  ---  ---  ---    --------------    -----------
0    0    0    0           ---             ----
0    0    0    1           256             3.906
0    0    1    0           128             7.813
0    0    1    1          8192             0.122
0    1    0    0          4096             0.244
0    1    0    1          2048             0.488
0    1    1    0          1024             0.977
0    1    1    1           512             1.953
1    0    0    0           256             3.906
1    0    0    1           128             7.813
1    0    1    0            64            15.625
1    0    1    1            32            31.25
1    1    0    0            16            62.5
1    1    0    1             8           125.0
1    1    1    0             4           250.0
1    1    1    1             2           500.0
The default value in the average IBM PC is 0110 or 1024 Hz.
Since no IRQ is enabled, you will not notice any difference
if you change the value.

Register B: Internal operation control.


This is the most important register for controling operation of the RTC chip. Register A determines timing and oscillator parameters, but the Bregister determines how the system will notice these conditions. In a normal PC, only bit 1 (24/12) is set. All other bits are cleared.
    bit     function
---     ------------------------------------------------------------
7      SET : If you determine to write a ONE in this bit position,
the clockregisters will not be updated anymore. Only when
this bit is ZERO, the clockregisters will be updated.
6      PIE : The Periodic Interrupt Enable bit controls the IRQ
pin. If this bit is ZERO, no IRQ will be given when the
programmable frequency source (selected by RS0 - RS3) times
out.
You need to set this bit to a ONE to enable a periodic IRQ
operation.
5      AIE : Alarm Interrupt Enable. When this bit is ONE, the IRQ
pin is activated when the alarm-time equals the actual time.
4      UIE : "Update Ended" Interrupt Enable. When this bit is set
to ONE, the IRQ line is asserted when the timing registers
have changed contents.
3      SQWE : Put a ONE in this bit to have the programmable
interval timer (which is controlled by RS0 - RS3) output a
square wave on pin 23 of the chip.
Unfortunately this pin 23 is not connected in a PC so for us
this bit has no meaning. But if you are man enough to bring
pin 23 of the DS 1287 to the outside world, you can use it
at will.
2      DM : Data Mode. The timing registers can display their data
in two different modes: binary and BCD. In the PC, this bit
is always ZERO, meaning that BCD is the desired format.
1      24/12 : Controls if hours are shown in 12 or 24 hours mode.
Put a ONE inhere and you have 24 hours in a day. Clear this
bit and you end up with two half days of 12 hours each. In
the 12-hour mode, bit 7 acts as an AM or PM flag.
0      DSE : Daylight Saving Enabled. Always leave this bit cleared
to ZERO. Daylight saving time periods vary worldwide and the
dates of change are determined by politicians and not by
chipmakers. Unfortunately.

Register C: Interrupt sources.


Register C is a status-word only. The bits in this register are readonly and only have menaing AFTER an IRQ was received. Since there is just one IRQ pin on the RTC chip, the IRQ can have three different sources and there's no way to know which one triggered it, unless there was only one source enabled. The bits mean the following:
    bit     function
---     ------------------------------------------------------------
7      IRQF : If this bit is ONE, one of the actual interrupt
conditions was enabled and the interrupt condition was met.
6      PF : Periodic interrupt Flag. If this bit is set, the source
of this IRQ source was the programmable interval timer.
5      AF : Alarm interrupt Flag. If this bit is set, the alarm
condition was the same as the actual date/time.
4      UF : The "Update Ended" interrupt Flag. If this bit is set,
the IRQ was issued by an update of the timing registers.

Bits 0 - 3 are meaningless and will always be ZERO.

Register D: Battery status.


On the chip, there is a voltage reference that is constantly being compared to the battery voltage. If the battery voltage drops below the reference voltage, the battery is considered empty and bit 7 will be SET.
If bit 7 is a ONE, the battery has been empty for some period of time and hence the data in the timing registers and in the RAM locations MAY have lost their meaning.

Bits 0 - 6 have no meaning in this register and will always return a ZERO value.

Using the RTC internals.


This, in a nutshell, is what the RTC chip is from the inside. I already explained some lines above how to access the storage locations and the timing registers of the DS 1287. This does not mean that everything will also work the first time.

If you need to change a timing value, you must always first disable register updates, even if you make sure that the changes you make to the timing registers will well fit in an RTC timeslot. This means:

  • access register B and set the SET flag
  • change the timing registers
  • access register B and clear the SET flag

Remember, there's not much intelligence inside a DS 1287. More recent chips might do more tricks for the programmer, but the old beasties just do as they were told.

In order to set the periodic interrupt rate, we use the following code:

--- Begin ------------------------------------------- SetPIRate -----

  SetPIRate:                    ; Set Periodic Interrupt Rate
mov   al, 0A           ; ah = rate to set
out   070, al
mov   al, ah
out   071, al          ; and set it in register A
ret

---- End -------------------------------------------- SetPIRate -----

This code is very straightforward. It relies on the fact that (in the IBM PC) the contents of register A are always the same:

     bit  7      = read-only
bits 4 - 6  = 010
bits 0 - 3  = rate selector

So, it can set the value of bits 4 - 7 in the calling code. It is not good programming, since we should:

  • read in the contents of Register A
  • clear bits 0 - 3
  • OR in the new value
  • write it back to register A

Inside the IBM PC.


The IRQ pin of the RTC is connected to the Intel 8259 PIC (Programmable Interrupt Controller, although "programmable" is too much honour for this dumbo). In non-XT machines there are two of them, cascaded. This means that the second one is connected to what used to be IRQ2. This gives us a rather stupid PC IRQ priority list:
        IRQ     Priority        IRQ     Priority
---     --------        ---     --------
0         0             8          2
1         1             9          3
2        10            10          4
3        11            11          5
4        12            12          6
5        13            13          7
6        14            14          8
7        15            15          9

A lower number means a higher priority....

The RTC interrupt line is connected to PC-IRQ8. So it comes in third place for being serviced. When enabled!

Normally IRQ8 is NOT enabled, so you will first have to settle that with the PIC, which is far from easy to understand. I use the following code to enable and disable the IRQ8 processing. Disabling this interrupt is necessary after your program is unloaded from memory. If you don't do this, the IRQ service routine vector might point to some random code or data in the next program loaded (like Command.Com).

----------------------------------------------------- EnableIRQ8 ----

  EnableIRQ8:                   ; enable IRQ 8 in 8259
push  ax
in    al, 0A1          ; get IRQ mask word
and   al, not bit 0
out   0A1, al          ; enable IRQ 8
pop   ax
ret

----------------------------------------------------- EnableIRQ8 ----

Easy, isn't it? It took some nights to figure this out, 'cause the Intel databooks are not that clear. I was glad to find some NEC databooks since these shed some more light. In general, for older chips, NEC is a good choice of databooks. They used to second source 80x86 chips for Intel and are still known for their innovations they put into their V20 and V30 chips. The V25, a vastly improved 8088, was contaminated by 8 full banks of 14 registers. Luckily Intel did not copy this. What would a 386 have been with 250 GP registers?

Here's the code for disabling IRQ8:

--- Begin ------------------------------------------ DisableIRQ8 ----

  DisableIRQ8:                  ; disable IRQ 8 in 8259
push  ax
in    al, 0A1          ; get IRQ mask word
or    al, bit 0
out   0A1, al          ; disable IRQ 8
pop   ax
ret

---- End ------------------------------------------- DisableIRQ8 ----

Asserting IRQ8 will make the PC generate an INT 70h. So, we need to have an INT 70h handler ready:

--- Begin ------------------------------------------ NewIRQ8 --------

  L0:      mov   [IrqCount], ax           ; and store it
L1:      mov   al, 020                  ; tell stupid PC that IRQ ends here
out   020, al                  ; EOI to original PIC
out   0A0, al                  ; EOI to cascaded PIC
pop   ds, ax                   ; restore registers
iret                           ; and get out

NewIRQ8: push ax, ds

        cs mov   ds, [DataSeg]            ; restore DS
mov   al, 0C
out   070, al
in    al, 071                  ; clear interrupt flags
test  [Flags], Running         ; are we running?
jz    L1                       ; if not, get out
test  [Flags], FastMode        ; Samplerate over 128 Sps?
jz    >L2                      ; if not, scram
or    [Flags], TimeOut         ; else set TimeOut flag
jmp   L1
L2:      mov   ax, [IrqCount]           ; medium to slow samplerates
dec   ax                       ; are we at correct value?
jnz   L0                       ; ... if not, wait some more
or    [Flags], TimeOut         ; ... if so, set TimeOut flag,
mov   ax, [MaxCount]           ; ... reload time constant register
jmp   L0

---- End ------------------------------------------- NewIRQ8 --------

I like to do as little as possible in this kind of routines. In this case I set a flag and rely on the abillities of the background program to fork execution based on the state of that flag.

I hate the idea of having an INT routine that actually DOES things, but which, for some obscure reason, cannot complete before the next INT comes in. You'll be able to figure out what will happen in most cases.

If this routine sets a flag twice, I don't care too much. OK, I loose a sample, but the program keeps running and it will still terminate when I ask it to.

This routine:

  • saves registers on the user-stack
  • restores correct DS
  • accesses the FLAGS register in memory
  • consults these flags and acts upon them
  • eventually reaches L1 and here an EOI is sent to the PIC's
  • pops the stored registers from the userstack
  • returns with an IRET.

The PIC needs an EOI to enable lower priority interrupts. And since there are two PIC's in modern PC's, there also must be two EOI's.

The following routine will enable the new IRQ8 handler:

--- Begin ----------------------------------- EnableNewIRQ8 ---------

  EnableNewIRQ8:                  ; program the RTC chip to 1 kSps
push  ax               ; and enable the 8259 PIC, channel 8
mov   al, 0C
out   070, al
in    al, 071          ; check register C first
mov   ah, 00100110xB
call  SetPIRate        ; set PI rate to 1 kSps
mov   al, 0B
out   070, al
mov   al, 01000010xB   ; enable the RTC interrupt pin
out   071, al          ; and store it in RTC register B
call  EnableIRQ8       ; enable the 8259 PIController
pop   ax
ret

---- End ------------------------------------ EnableNewIRQ8 ---------

And before going back to the OS of your choice, make sure there will be no IRQ8's anymore coming this way:

--- Begin ----------------------------------- ResetNewIRQ8 ----------

  ResetNewIRQ8:                   ; restore default values in RTC
push  ax               ; and disable 8259 PIC, channel 8
mov   al, 0A
out   070, al          ; select register A
mov   al, 00100110xB
out   071, al          ; and set it back to PC default
mov   al, 0B
out   070, al
mov   al, 00000010xB   ; disable interruptions from RTC chip
out   071, al          ; via register B
call  DisableIRQ8      ; handle the PIC
pop   ax
ret

---- End ------------------------------------ ResetNewIRQ8 ----------

In the big program these code fragments are from, I use two timer interrupts:

  • the RTC timer is used for trigger-timing. When the RTC has set the right flag, the main program will sample the ADC and store the result in a buffer for later processing.
  • the internal PC klok which generates the 55 ms timing signals is used to set another flag. When this is set, the (DMM style) display is updated. The digital readout is updated about 3 times per second and the bargraph display is updated 18 times per second.

Therefore I also need a new IRQ0 handler:

---------------------------------------------------- NewIRQ0 --------

  L0:      pop   ds                       ; restore register
jmp   [cs:OldIRQ0]             ; and update DOS clock
NewIRQ0: push  ds                       ; new timer routine (18,2 Hz)
cs mov   ds, [DataSeg]            ; restore DS
test  [Flags], Running
jz    L0                       ; if not running, eject!
inc   [Counter]                ; else increment counter,
or    [Flags], RefrshBar       ;  indicate "bargraph refresh"
test  [Counter], 07            ; twice per second,
IF  Z or  [Flags], RefrshDig   ;  indicate "digits update"
jmp   L0                       ; and get out

---------------------------------------------------- NewIRQ0 --------

This new routine does the following:

  • check if the DMM is running,
  • if not, it makes no sense to set any flags,
  • if running, set the "update bargraph display" flag,
  • if running, check if it is time to update the digital readout,
  • restore DS register,
  • branch to previous IRQ0 handler.

In the initialisation routine, common to all my programs, I make sure the right interrupt vectors are stolen:

--- Begin ------------------------------------------ Init -----------

  init:    call  SetVars          ; init most import variables
call  PowDown          ; make sure ADC is OFF
call  ClkLo            ; prepare ADC for power-up
call  ChkTime          ; measure minimum sample time
call  MaxSps           ; determine maximum sample speed
mov   ah, 0F
int   010              ; determine existing video mode
mov   [VidMode], al    ; store it
mov   ax, 012
int   010              ; set 640 x 480 graphics mode
push  es
mov   ax, 0351C        ; get old timervector
int   021
mov   w [OldIRQ0], bx
mov   w [OldIRQ0+2], es
mov   dx, offset NewIRQ0
mov   ax, 0251C
int   021              ; install new TIMER routine
mov   ax, 03570
int   021
mov   w [OldClock], bx
mov   w [OldClock+2], es
mov   dx, offset NewIRQ8
mov   ax, 02570
int   021              ; install NewIRQ8 routine
call  EnableNewIRQ8    ; and get it to work
pop   es
mov   ax, 0
int   033              ; init mouse
ShowMouse              ; this is a macro....
call  FillScreen
or    [Flags], RfrshBar + RefrPara + Upd8Digs
call  ShowDig
call  BrScale
call  Update
ret

---- End ------------------------------------------- Init -----------

Not much to explain about this INIT routine I guess.

So, on to the EXIT part of the software. Forget this, and the computer will hang on random times afterwards....

--- Begin ------------------------------------------ Exit -----------

exit: call PowDown

           call  ResetNewIRQ8
push  ds
lds   dx, [OldIRQ0]
mov   ax, 0251C
int   021              ; restore timer vector
pop   ds
push  ds
lds   dx, [OldClock]
mov   ax, 02570
int   021              ; restore realtime clock vector
pop   ds
mov   ah, 0
mov   al, [VidMode]
int   010              ; back to previous screenmode
mov   ax, 0
int   033              ; reset mouse and -driver
mov   ax, 04C00
int   021              ; and exit to DOS

---- End ------------------------------------------- Exit -----------

That's all you need to know to get started. The RTC chip has some nice other possibillities. It can be programmed to interrupt each second. Or any other number of seconds. It is a truly versatile chip with many timing functions directly available to systems level programmers.

It might be a good idea to seacrh the web for a datasheet. A good starting point will be www.dalsemi.com where PDF files will be available for all DS 1287 style chips. Or else from ftp ftp.dalsemi.com. The latest versions of this chip that I know of is the DS 17887. This has a Y2K compliant clock and over 8K of NV (=Non Volatile) RAM.

In the USA Dallas have an Automatic Datasheet FaxBack number:

972 - 371 4441

Have fun exploiting the RTC chip, but be prepared to hit the reset button now and then. Also, make a backup of the CMOS battery-backup RAM onto a floppy disk! You'll have corrupted or erased these data before you know it and it's always a bit of a shock if the system cannot even find the C: drive anymore....