Page 1 of 1

RTC timing issue

Posted: Wed Feb 10, 2010 9:08 am
by assainator
Heey all,

At the moment I want my OS to show the time, so i do this:
1. first check if the CMOS is making a update
2. if so, go to step 1
3. if not, read the 'b register' and print it (so i can see it)
4. read the current day of the week and print it
5. read the current hour, if the CMOS uses 12 hour mode, add 12 to the current hour and print it

the only strange thing i'm facing is the following. the result is always the following and i've tested it today and yesterday:

format: 00000010b
day of the week: 00000100b
hour: 0010000b

from what I understand this means:
binary format is enabled
24 hour mode is disabled

i'm using bochs 2.4.2

Code: Select all

;time.asm
; time functions
%DEFINE _CMOS_RTC_OUT 0x70
%DEFINE _CMOS_RTC_IN 0x71

get_time_format:
  mov al, 10
  out _CMOS_RTC_OUT,al
  in al, _CMOS_RTC_IN
  test al,0x80          ;if a update is in progress redo it all
	jne get_time_format

  mov al, 0x0b
  out _CMOS_RTC_OUT, al
  in al, _CMOS_RTC_IN
  mov ah, 0x0
  
  cmp al, 00000100b
  je .no24_isbin
  
  cmp al, 00000110b
  je .is24_isbin
  
  cmp al, 00000010b
  je .is24_nobin
  
  
  mov byte [__time__binary_mode_enabled], 0x0
  mov byte [__time__24hour_mode_enabled], 0x0
  ret
  
  
  .no24_isbin:
    mov byte [__time__binary_mode_enabled], 0x1
    mov byte [__time__24hour_mode_enabled], 0x0
    ret
  
  .is24_isbin:
    mov byte [__time__binary_mode_enabled], 0x1
    mov byte [__time__24hour_mode_enabled], 0x1
    ret
    
  .is24_nobin:
    mov byte [__time__binary_mode_enabled], 0x0
    mov byte [__time__24hour_mode_enabled], 0x1
    ret


get_time_year:
  mov al, 10
  out _CMOS_RTC_OUT,al
  in al, _CMOS_RTC_IN
  test al,0x80          ;if a update is in progress redo it all
	jne get_time_year
	
	
	mov al, 0x09
  out _CMOS_RTC_OUT,al
  in al, _CMOS_RTC_IN
  
  mov ah, 0x0
  
  ret


get_time_weekday:
  mov al, 10
  out _CMOS_RTC_OUT,al
  in al, _CMOS_RTC_IN
  test al,0x80          ;if a update is in progress redo it all
	jne get_time_hour
	
	mov al, 0x06
  out _CMOS_RTC_OUT,al
  in al, _CMOS_RTC_IN
  
  mov ah, 0x0
  ret




get_time_hour:
  mov al, 10
  out _CMOS_RTC_OUT,al
  in al, _CMOS_RTC_IN
  test al,0x80          ;if a update is in progress redo it all
	jne get_time_hour
	
	mov al, 0x09
  out _CMOS_RTC_OUT,al
  in al, _CMOS_RTC_IN
  
  mov ah, 0x0
  cmp [__time__24hour_mode_enabled], 0x0
  je .done
  
  add al, 0x0c
  
  .done:
    ret
  

__time__binary_mode_enabled db 0xFF
__time__24hour_mode_enabled db 0xFF

Re: RTC timing issue

Posted: Wed Feb 10, 2010 9:39 am
by Creature
Why don't you just use 24-hour mode if that's what you want instead of adding the time? In the format, bit 0 stands for Daylight Savings Time, bit 1 for 24-hour mode and bit 2 for binary format. If bit 2 is not set, you need to read the time, date, etc. as binary decimals (also called BCD).

Also, if you are using interrupts (IRQs), I believe it is required that you read from index 0x0C every interrupt (but I'm not sure).

Re: RTC timing issue

Posted: Wed Feb 10, 2010 12:13 pm
by Brendan
Hi,
assainator wrote:At the moment I want my OS to show the time, so i do this:
1. first check if the CMOS is making a update
2. if so, go to step 1
3. if not, read the 'b register' and print it (so i can see it)
4. read the current day of the week and print it
5. read the current hour, if the CMOS uses 12 hour mode, add 12 to the current hour and print it
There's 2 race conditions there - if an update begins at step 3 (or step 4 or 5), then you'll be reading from the RTC while an update is in progress; and an IRQ can occur that changes RTC_OUT after you've set RTC_OUT but before you've read from RTC_IN. You need to do something like:

1. disable interrupts
2. check if the CMOS is making a update
3. if not, enable interrupts, do a NOP, then go to step 1
4. check if the CMOS is making a update again
5. if so, enable interrupts, do a NOP, disable interrupts again, then go to step 4
6. if not, read all the registers as quick as you can, then enable interrupts (and print them after they're all read)

The CMP instruction compares values (it's exactly like SUB except the result is discarded and only the flags are effected). The TEST instruction tests individual bits (it's exactly like AND except the result is discarded and only the flags are effected). You should use TEST (and not CMP) to check if the "24 hr" and "binary mode" flags are set.

I'm also wondering why your assembler doesn't complain about "cmp [__time__24hour_mode_enabled], 0x0". Is it meant to compare the byte, the word or the dword at this address?
Creature wrote:Why don't you just use 24-hour mode if that's what you want instead of adding the time?
That would screw up the time (you'd need to read the time, then switch to 24-hour mode, then write a 24-hour time back to the RTC), and then it could screw up other OSs and/or the BIOS (which might assume the CMOS is in 12-hour mode).

To convert between 12-hour and 24-hour, you can't just add 12. In 12-hour mode there's an AM/PM flag in bit 7, and you have to be very careful to handle 12 AM and 12 PM correctly:

12 AM = 00:00
1 AM = 01:00
2 AM = 02:00
3 AM = 03:00
4 AM = 04:00
5 AM = 05:00
6 AM = 06:00
7 AM = 07:00
8 AM = 08:00
9 AM = 09:00
10 AM = 10:00
11 AM = 11:00
12 PM = 12:00
1 PM = 13:00
2 PM = 14:00
3 PM = 15:00
4 PM = 16:00
5 PM = 17:00
6 PM = 18:00
7 PM = 19:00
8 PM = 20:00
9 PM = 21:00
10 PM = 22:00
11 PM = 23:00

Basically, if hour = 12 then set the hour to zero; then, if the PM flag is set add 12.


Cheers,

Brendan

Re: RTC timing issue

Posted: Wed Feb 10, 2010 1:24 pm
by assainator
Brendan wrote: I'm also wondering why your assembler doesn't complain about "cmp [__time__24hour_mode_enabled], 0x0". Is it meant to compare the byte, the word or the dword at this address?
acctually it does I just forgot to add that in my post. It should have been: "cmp byte [__time__24hour_mode_enabled], 0x0"

Both thanks for the help, now I got some new stuff to dig into!

Re: RTC timing issue

Posted: Wed Feb 10, 2010 3:54 pm
by Creature
Brendan wrote:
Creature wrote:Why don't you just use 24-hour mode if that's what you want instead of adding the time?
That would screw up the time (you'd need to read the time, then switch to 24-hour mode, then write a 24-hour time back to the RTC), and then it could screw up other OSs and/or the BIOS (which might assume the CMOS is in 12-hour mode).
Brendan wrote:To convert between 12-hour and 24-hour, you can't just add 12. In 12-hour mode there's an AM/PM flag in bit 7, and you have to be very careful to handle 12 AM and 12 PM correctly:
I had the same idea, that's why I suggested 24-hour mode. I didn't know you could screw over the BIOS by simply adjusting the mode, but I'll take your advice and change my code also. The fact that other OS' rely on whether it's 12 hour mode or 24 hour mode is total bullshit (I don't mean it's not true). If we should handle it properly, so should they (but we all know this doesn't always happen, I guess).

Re: RTC timing issue

Posted: Wed Feb 10, 2010 10:37 pm
by Brendan
Hi,
Creature wrote:I had the same idea, that's why I suggested 24-hour mode. I didn't know you could screw over the BIOS by simply adjusting the mode, but I'll take your advice and change my code also. The fact that other OS' rely on whether it's 12 hour mode or 24 hour mode is total bullshit (I don't mean it's not true). If we should handle it properly, so should they (but we all know this doesn't always happen, I guess).
I learnt the hard way: once upon a time a version of my OS switched the RTC to binary, 24-hour - it completely screwed up the BIOS and screwed up Windows. :)

An OS should only read the RTC once during boot anyway, so it's easier to support all RTC modes than to worry about setting your own RTC mode. Most of the pain comes from the next step - attempting to write code that converts the "RTC time" into some sort of "UTC time in ticks since the epoch" integer format (especially when "RTC time" is local time in some country with strange daylight savings rules).


Cheers,

Brendan

Re: RTC timing issue

Posted: Fri Feb 12, 2010 10:22 am
by ~
Can somebody confirm if the following is correct?:

- The best and most stable way of reading CMOS time/date is to properly set up IRQ8 to wait for it (when it triggers we have 1 second of machine time -a huge lot of time- to read date/time).

- The time/date can be considered read/write, so our OS can modify/update the date (I have tried it and it's possible to modify those time values). It seems that it can be written to at any arbitrary time, but read best when IRQ8 triggers.

- All other CMOS data should be considered read only even when it can be written to, so not to break the PC stability.

- It would be a good idea reading all 128 bytes of standard CMOS memory at system startup. In that way we have a full copy of CMOS memory somewhere in kernel/application space and we won't need anymore I/O instructions to access almost all CMOS fields (except for date).

Re: RTC timing issue

Posted: Sat Feb 13, 2010 12:40 am
by Brendan
~ wrote:Can somebody confirm if the following is correct?:

- The best and most stable way of reading CMOS time/date is to properly set up IRQ8 to wait for it (when it triggers we have 1 second of machine time -a huge lot of time- to read date/time).
Um, no...

During boot, you read the RTC time once, then convert that into something like "microseconds since the beginning of the year 2000, in UTC". Then you use a more precise timer (e.g. HPET, PIT, etc) to update this counter 100 times per second (or more frequently, depending on how good the timer is and how fast the CPU/s are). Then you write conversion routines to convert "microseconds since 2000" back into the current time and date (hour:minute:second:microsecond, day:month:year). Then you write a routine to convert "ticks since 2000 UTC" into "ticks since 2000 local time" (for any/all time zones).

After that you add drift adjustment (and maybe support for NTP), and the ability to set the RTC from your (hopefully much more accurate) "microseconds since 2000" counter when the OS shuts down.
~ wrote:- It would be a good idea reading all 128 bytes of standard CMOS memory at system startup. In that way we have a full copy of CMOS memory somewhere in kernel/application space and we won't need anymore I/O instructions to access almost all CMOS fields (except for date).
I can't think of any reason for your OS (kernel, drivers, etc) to read anything from the CMOS (except for the time and date once during boot), so there's probably no point caching data that you don't need... ;)


Cheers,

Brendan

Re: RTC timing issue

Posted: Sat Feb 13, 2010 1:49 pm
by Selenic
Brendan wrote:
~ wrote:Can somebody confirm if the following is correct?:

- The best and most stable way of reading CMOS time/date is to properly set up IRQ8 to wait for it (when it triggers we have 1 second of machine time -a huge lot of time- to read date/time).
Um, no...

During boot, you read the RTC time once, then convert that into something like "microseconds since the beginning of the year 2000, in UTC"
What you've quoted and your reply are largely independent: yes, you should only read the RTC once, but you need a way of reliably retrieving the real time, which is best done by a one-shot update interrupt.

With respect to the rest of the previous post, though, I agree that non-RTC timers are likely to be more accurate, with NTP able to (to some extent) provide both.
~ wrote:- All other CMOS data should be considered read only even when it can be written to, so not to break the PC stability.

- It would be a good idea reading all 128 bytes of standard CMOS memory at system startup. In that way we have a full copy of CMOS memory somewhere in kernel/application space and we won't need anymore I/O instructions to access almost all CMOS fields (except for date).
Usually that memory is largely used for BIOS settings, and any OS-related non-volatile stuff would be much better on the disk (because there's much more space and it can be swapped across machines, among other things)

Re: RTC timing issue

Posted: Sat Feb 13, 2010 11:03 pm
by Brendan
Hi,
Selenic wrote:
Brendan wrote:
~ wrote:Can somebody confirm if the following is correct?:

- The best and most stable way of reading CMOS time/date is to properly set up IRQ8 to wait for it (when it triggers we have 1 second of machine time -a huge lot of time- to read date/time).
Um, no...

During boot, you read the RTC time once, then convert that into something like "microseconds since the beginning of the year 2000, in UTC"
What you've quoted and your reply are largely independent: yes, you should only read the RTC once, but you need a way of reliably retrieving the real time, which is best done by a one-shot update interrupt.
Perhaps.

If you are reading the RTC time and date once, then you could setup the "once per second" update IRQ to avoid polling the "update in progress" flag; but this would be a little more hassle, and the OS would still need to spend up to 1 second waiting for the update (before reading the time and date) either way. Mostly, setting up the RTC IRQ handler is only really worth the trouble if you're going to use it after boot (but there's usually no real reason to use the RTC IRQ after boot).

As an alternative, it should be safe to read the RTC seconds, then read the rest of the date and time (starting from the century and working towards the minute), then read the seconds again. If the seconds changed then you'd discard the results and try again, and if the seconds didn't change you should have a valid time and date. This would avoid the need to care about the update in progress flag (and the update IRQ), but more importantly it'd avoid the need for an (up to 1 second) delay waiting for an update to occur.

For timing used after boot; the main thing to consider is that the OS needs to keep track of time with a precision that is at least high enough to handle file system time stamps; and hopefully enough precision to use for small delays used in device drivers.

This means the "once per second" update IRQ from the RTC isn't adequate. The RTC also has a periodic IRQ that does have adequate precision; however the RTC in general has higher overhead than (for e.g.) the PIT because you have to read from RTC register C each IRQ (and accessing "ISA" I/O ports is slow - typically 1 us, or about 2000 cycles for a 2 GHz CPU). The PIT has lower overhead, and can therefore be run at higher frequencies (resulting in better time keeping precision) before the overhead becomes a major problem.


Cheers,

Brendan