0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CMOS時間取得をBIOSのソースから読み解く ~自力でINT 0x1Aを実装してみた~

0
Last updated at Posted at 2025-12-07

INT0x1Aとは

INT 0x1AはBIOSが提供する「今の時間や日付を教えてくれる仕組み」です。
この仕組みを使うと、アセンブリ言語から簡単に時計の情報を取得できます。

主な使い方

AHレジスタの値 できること
02H 今の「時・分・秒」を取得
04H 今の「年・月・日」を取得

返り値の形式
時間や日付は BCD(Binary Coded Decimal) という形式で返されます。

1バイトで10進数2桁を表す形式

  • 上位4ビット(ビット7〜4) → 10の位
  • 下位4ビット(ビット3〜0) → 1の位

例:00110110

  • 上位4ビット 0011 → 3
  • 下位4ビット 0110 → 6
    → よって 36 となります

まずはINT0x1Aを使ってみる

INT 0x1Aがどんな動作をするのかを理解するため、実際に呼び出してみます。

test@test-fujitsu:~/kaihatsu/1a$ nasm -f bin boot.asm -o boot.bin
boot.asm
[org 0x7C00]
bits 16

start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    sti

    ; VGA テキストモード
    mov ah, 0
    mov al, 3
    int 0x10

    ; -------------------------------
    ; 時刻取得
    mov ah, 2
    int 0x1A       ; CH=時間, CL=分, DH=秒
    mov al, ch
    call print_bcd2
    mov al, cl
    call print_bcd2
    mov al, dh
    call print_bcd2

    ; 改行
    mov ah, 0x0E
    mov al, 13
    int 0x10
    mov al, 10
    int 0x10

    ; -------------------------------
    ; 日付取得
    mov ah, 4
    int 0x1A       ; CH=世紀, CL=年(2025年ならCH=20,CL=25), DH=月, DL=日
    mov al, ch
    call print_bcd2
    mov al, cl
    call print_bcd2
    mov al, dh
    call print_bcd2
    mov al, dl
    call print_bcd2

hang:
    hlt
    jmp hang

; -------------------------------
; BCD (AL) → 2桁数字表示
; -------------------------------
print_bcd2:
    push ax
    mov ah, al
    shr ah, 4
    add ah, '0'
    mov al, ah
    call print_char
    pop ax
    and al, 0x0F
    add al, '0'
    call print_char
    ret

; -------------------------------
; 1文字表示(AL)
; -------------------------------
print_char:
    mov ah, 0x0E
    int 0x10
    ret

times 510-($-$$) db 0
dw 0xAA55

QEMU

test@test-fujitsu:~/kaihatsu/1a$ qemu-system-x86_64 -drive format=raw,file=boot.bin

图片.png

ThinkPad X280

lsblk
sudo dd if=boot.bin of=/dev/sdb bs=512 count=1 conv=notrunc
sync

06626660999fb.jpg
2d338670da294.jpg

8086互換CPU(V30)

f76d5f60a6209.jpg

IBM BIOS上での実装

参考 BIOS ソース:
IBM-PC-BIOS/IBM PC AT/AT BIOS V3/

INT 1AHには複数の機能がありますが、ここでは 時間取得(AH=02H) と 日付取得(AH=04H) に絞って BIOS の実装を読み解きます。

以下は注釈から抜粋した仕様です。
実行すると、結果は複数のレジスタに格納されます。

※筆者は NASM を使用していますが、当時の BIOS は別のアセンブラで作られているため、文法が多少異なる場合があります。

BIOS2.ASM
;     (AH) = 02H  READ THE REAL TIME CLOCK AND RETURN WITH,		       :
;		       (CH) = HOURS IN BCD (00-23)			       :
;		       (CL) = MINUTES IN BCD (00-59)			       :
;		       (DH) = SECONDS IN BCD (00-59)			       :
;		       (DL) = DAYLIGHT SAVINGS ENABLE (00-01).		       :

;     (AH) = 04H  READ THE DATE FROM THE REAL TIME CLOCK AND RETURN WITH,      :
;		       (CH) = CENTURY IN BCD (19 OR 20) 		       :
;		       (CL) = YEAR IN BCD (00-99)			       :
;		       (DH) = MONTH IN BCD (01-12)			       :
;		       (DL) = DAY IN BCD (01-31).			       :

AHの機能番号が存在するかの確認処理

INT 1AH では、AH に入れた値によって実行する機能を切り替えます。
例:
AH = 02H → 時刻取得
AH = 04H → 日付取得

BIOS では、まず 指定されたAHが有効な番号か を確認します。

BIOS2.ASM
	CMP	AH,(RTC_TBE-RTC_TB)/2	; CHECK IF COMMAND IN VALID RANGE (0-7)
	CMC				; COMPLEMENT CARRY FOR ERROR EXIT
	JC	TIME_9			; EXIT WITH CARRY = 1 IF NOT VALID

この処理を分解すると:

  • CMP op1, op2
    op1 < op2 → CF=1
    op1 >= op2 → CF=0

  • CMC
    CFを反転します。
    CF = 0 → CF = 1
    CF = 1 → CF = 0

  • JC TIME_9
    CF=1 の場合にTIME_9へ跳ぶ
    つまりこの場合はAH が範囲外(8H以上)の場合にTIME_9(該当する機能番号無し)へ跳びます。

BIOS では、各 AH の値に対応する処理を 表 として登録しています。
表は DW(ワード=2バイト)で書かれているので、各項目は 2 バイトです。

BIOS2.ASM
RTC_TB	DW	RTC_00			; 0 = READ CURRENT CLOCK COUNT
	DW	RTC_10			; 1 = SET CLOCK COUNT
	DW	RTC_20			; 2 = READ THE REAL TIME CLOCK TIME
	DW	RTC_30			; 3 = SET REAL TIME CLOCK TIME
	DW	RTC_40			; 4 = READ THE REAL TIME CLOCK DATE
	DW	RTC_50			; 5 = SET REAL TIME CLOCK DATE
	DW	RTC_60			; 6 = SET THE REAL TIME CLOCK ALARM
	DW	RTC_70			; 7 = RESET ALARM
RTC_TBE EQU	$

表の大きさ = RTC_TBE - RTC_TB(バイト数)
登録されている機能数 = サイズ ÷ 2

AHに登録された機能番号による分岐処理

AH の値が有効(1〜7)であることが確認できた後、BIOS は以下の処理で対応する関数を呼び出します。

BIOS2.ASM
	SHR	AX,8			; CONVERT FUNCTION TO BYTE OFFSET
	ADD	AX,AX			; CONVERT FUNCTION TO WORD OFFSET (CY=0)
	MOV	SI,AX			; PLACE INTO ADDRESSING REGISTER
	CLI				; NO INTERRUPTS DURING TIME FUNCTIONS
	CALL	CS:[SI]+OFFSET RTC_TB	; VECTOR TO FUNCTION REQUESTED WITH CY=0
					;  RETURN WITH CARRY FLAG SET FOR RESULT

SHR AX,8同AHの内容を右に8ビットずらしてALに格納します。
→ AX の上位バイト(AH)が AL 側に下りて、AH は 0 になります。
分かり易く言うとmov al, ah mov ah, 0と同義です。

ADD AX,AXはAXの値を2倍しています。
RTC_TBは各項目を2バイトで登録している為、オフセット位置は2の倍数になります。

  • 0 : 0バイト目
  • 1 : 2バイト目
  • 2 : 4バイト目
  • 3 : 6バイト目

CALL CS:[SI]+OFFSET RTC_TB
RTC_TBのアドレス + オフセットの位置にある関数を呼び出します。

例えばAH=04Hの場合、RTC_TB+オフセット8バイトの位置に登録されているRTC_TBが呼び出されます。

時間取得

RTC_20は実際に時間そのものを取得しているわけではなく、CMOSから値を読み込む CMOS_READを呼び出しています。

BIOS2.ASM
RTC_20: 				;	GET RTC TIME
	CALL	UPD_IPR 		; CHECK FOR UPDATE IN PROCESS
	JC	RTC_29			; EXIT IF ERROR (CY= 1)

	MOV	AL,CMOS_SECONDS 	; SET ADDRESS OF SECONDS
	CALL	CMOS_READ		; GET SECONDS
	MOV	DH,AL			; SAVE
	MOV	AL,CMOS_REG_B		; ADDRESS ALARM REGISTER
	CALL	CMOS_READ		; READ CURRENT VALUE OF DSE BIT
	AND	AL,00000001B		; MASK FOR VALID DSE BIT
	MOV	DL,AL			; SET [DL] TO ZERO FOR NO DSE BIT
	MOV	AL,CMOS_MINUTES 	; SET ADDRESS OF MINUTES
	CALL	CMOS_READ		; GET MINUTES
	MOV	CL,AL			; SAVE
	MOV	AL,CMOS_HOURS		; SET ADDRESS OF HOURS
	CALL	CMOS_READ		; GET HOURS
	MOV	CH,AL			; SAVE
	CLC				; SET CY= 0

日時取得

RTC_40も同様にCMOS_READを使って日時を取得します。

BIOS2.ASM
RTC_40: 				;	GET RTC DATE
	CALL	UPD_IPR 		; CHECK FOR UPDATE IN PROCESS
	JC	RTC_49			; EXIT IF ERROR (CY= 1)

	MOV	AL,CMOS_DAY_MONTH	; ADDRESS DAY OF MONTH
	CALL	CMOS_READ		; READ DAY OF MONTH
	MOV	DL,AL			; SAVE
	MOV	AL,CMOS_MONTH		; ADDRESS MONTH
	CALL	CMOS_READ		; READ MONTH
	MOV	DH,AL			; SAVE
	MOV	AL,CMOS_YEAR		; ADDRESS YEAR
	CALL	CMOS_READ		; READ YEAR
	MOV	CL,AL			; SAVE
	MOV	AL,CMOS_CENTURY 	; ADDRESS CENTURY LOCATION
	CALL	CMOS_READ		; GET CENTURY BYTE
	MOV	CH,AL			; SAVE
	CLC				; SET CY=0

CMOS_READ

実際にCMOSから日時及び時間を呼び出す処理を知りたかったらCMOS_READの中身を読む必要があります。

ALに設定する値。今回呼び出すもののみ抜粋。呼び出し順に載せたので前後しています。

POSTEQU.INC
;---------- CMOS TABLE LOCATION ADDRESS'S ## -----------------------------------
CMOS_SECONDS	EQU	000H		; SECONDS
CMOS_REG_B	EQU	00BH		; STATUS REGISTER B  ALARM
CMOS_MINUTES	EQU	002H		; MINUTES
CMOS_HOURS	EQU	004H		; HOURS

CMOS_DAY_MONTH	EQU	007H		; DAY OF THE MONTH
CMOS_MONTH	EQU	008H		; MONTH
CMOS_YEAR	EQU	009H		; YEAR (TWO DIGITS)
CMOS_CENTURY	EQU	032H		; DATE CENTURY BYTE (BCD)


;----------------------------------------
;	CMOS EQUATES FOR THIS SYSTEM	:
;-------------------------------------------------------------------------------
CMOS_PORT	EQU	070H		; I/O ADDRESS OF CMOS ADDRESS PORT
CMOS_DATA	EQU	071H		; I/O ADDRESS OF CMOS DATA PORT
TEST4.ASM
;--- CMOS_READ -----------------------------------------------------------------
;		READ BYTE FROM CMOS_SYSTEM CLOCK CONFIGURATION TABLE	       :
;									       :
; INPUT: (AL)=	CMOS_TABLE ADDRESS TO BE READ				       :
;		BIT    7 = 0 FOR NMI ENABLED AND 1 FOR NMI DISABLED ON EXIT    :
;		BITS 6-0 = ADDRESS OF TABLE LOCATION TO READ		       :
;									       :
; OUTPUT: (AL)	VALUE AT LOCATION (AL) MOVED INTO (AL). IF BIT 7 OF (AL) WAS   :
;		ON THEN NMI LEFT DISABLED, DURING THE CMOS READ BOTH NMI AND   :
;		NORMAL INTERRUPTS ARE DISABLED TO PROTECT CMOS DATA INTEGRITY. :
;		THE CMOS ADDRESS REGISTER IS POINTED TO A DEFAULT VALUE AND    :
;		THE INTERRUPT FLAG RESTORED TO THE ENTRY STATE ON RETURN.      :
;		ONLY THE (AL) REGISTER AND THE NMI STATE IS CHANGED.	       :
;-------------------------------------------------------------------------------

CMOS_READ	PROC	NEAR		;	READ LOCATION (AL) INTO (AL)
	PUSHF				; SAVE INTERRUPT ENABLE STATUS AND FLAGS
	ROL	AL,1			; MOVE NMI BIT TO LOW POSITION
	STC				; FORCE NMI BIT ON IN CARRY FLAG
	RCR	AL,1			; HIGH BIT ON TO DISABLE NMI - OLD IN CY
	CLI				; DISABLE INTERRUPTS
	OUT	CMOS_PORT,AL		; ADDRESS LOCATION AND DISABLE NMI
	NOP				; I/O DELAY
	IN	AL,CMOS_DATA		; READ THE REQUESTED CMOS LOCATION
	PUSH	AX			; SAVE (AH) REGISTER VALUE AND CMOS BYTE
	MOV	AL,CMOS_REG_D*2 	; GET ADDRESS OF DEFAULT LOCATION
	RCR	AL,1			; PUT ORIGINAL NMI MASK BIT INTO ADDRESS
	OUT	CMOS_PORT,AL		; SET DEFAULT TO READ ONLY REGISTER
	POP	AX			; RESTORE (AH) AND (AL), CMOS BYTE
	PUSH	CS			; *PLACE CODE SEGMENT IN STACK AND
	CALL	CMOS_POPF		; *HANDLE POPF FOR B- LEVEL 80286
	RET				; RETURN WITH FLAGS RESTORED

CMOS_READ	ENDP

ALの構成

AL = 7 6 5 4 3 2 1 0
     | NMI | CMOS_ADDR  |

ビット7: NMI 制御(0 = NMI有効、1 = NMI無効)
ビット6-0: CMOS のアドレス

今回使用するALの設定値は以下の通り

ラベル CMOS アドレス (16進) AL (ビット列, NMI=0) 説明
CMOS_SECONDS 00H 00000000 現在の秒
CMOS_MINUTES 02H 00000010 現在の分
CMOS_HOURS 04H 00000100 現在の時
CMOS_DAY_MONTH 07H 00000111 現在の日
CMOS_MONTH 08H 00001000 現在の月
CMOS_YEAR 09H 00001001 現在の年下2桁
CMOS_CENTURY 32H 00110010 世紀 (BCD)

NMI情報をCFへ入れる

CMOS_CENTURY EQU 032Hを例とします。
ビット列にすると0 0 1 1 0 0 1 0となります。

  • 操作1:ROL AL,1
    ビット列を左に1ビット回転させます。
    この時ビット7(NMI)は 回転してビット0へ入ります。
AL 元: 0 0 1 1 0 0 1 0
ROL 1: 0 1 1 0 0 1 0 0
  • 操作2:STC
    CARRYを1にする

  • 操作3: RCR AL,1
    ビット列を右に1ビット回転させます。
    するとALは元に戻り、右端のビット0(元々のNMI)はCFに入ります。

AL 元: 0 1 1 0 0 1 0 0
RCR 1: 0 0 1 1 0 0 1 0

呼び出し後に戻すためにCFへ入れていますが、今回はビット7が全て0でありNMIは有効のままなのでこの処理は必要ありません。

CMOS から値を読み出す処理

実際にCMOSとやりとりをしているのは以下の2行です。
NOPは数クロック待機しCMOSの処理が終わるのを待つために置いてあります。
結果はBCD形式でALに格納されます。

OUT 070H,AL
NOP 
IN AL,071H

2025年 5月 18日 17時 45分 36秒の例

ラベル CMOS アドレス (16進) AL (ビット列, NMI=0) 説明 CMOS データ読み出し例 (IN 71H → AL)
CMOS_SECONDS 00H 00000000 現在の秒 00110110 (BCD 36 → 36 秒)
CMOS_MINUTES 02H 00000010 現在の分 01000101 (BCD 45 → 45 分)
CMOS_HOURS 04H 00000100 現在の時 00010111 (BCD 17 → 17 時)
CMOS_DAY_MONTH 07H 00000111 現在の日 00010010 (BCD 18 → 18 日)
CMOS_MONTH 08H 00001000 現在の月 00000101 (BCD 05 → 5 月)
CMOS_YEAR 09H 00001001 現在の年下2桁 00100101 (BCD 25 → 25 年)
CMOS_CENTURY 32H 00110010 世紀 (BCD) 00100000 (BCD 20 → 2000 年台)

NMIの設定を戻す

今回、ALレジスタに設定した CMOS_SECONDSCMOS_MINUTES などは ビット7が0 となっており、NMI は有効のままです。
もし仮にビット7が 1 の値を ALに設定して OUT 070H,ALを実行すると、NMI が無効になり、後続の割り込み処理に影響が出る可能性があります。
CMOS_READ では NMI の設定を復元する処理も用意されていますが、今回使用したラベルではビット7=0 なので、復元処理は 特に必要ありません。

BIOSを使わずに日時を取得

BIOSは安全や汎用性を考慮するため、コードが長くなりますが、簡単に言えばこの3行を使えばINT 1AHを使わずとも同じように日時及び時間が取得できます。

OUT 070H,AL
NOP 
IN AL,071H

冒頭で実装したCMOS読み込みのソースを、INT 1AHを使わないように書き換えると以下のようになります。

boot.asm
[org 0x7C00]
bits 16

start:
    cli
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00
    sti

    ; VGA テキストモード
    mov ah, 0
    mov al, 3
    int 0x10

    ; -------------------------------
    ; 時刻取得
    mov al,0x04 ; 時間
    out 0x70,al
    nop
    in al,0x71
    call print_bcd2
    
    mov al,0x02 ; 分
    out 0x70,al
    nop
    in al,0x71
    call print_bcd2
    
    mov al,0x00 ; 秒
    out 0x70,al
    nop
    in al,0x71
    call print_bcd2

    ; 改行
    mov ah, 0x0E
    mov al, 13
    int 0x10
    mov al, 10
    int 0x10

    ; -------------------------------
    ; 日付取得
    mov al,0x32 ; 世紀
    out 0x70,al
    nop
    in al,0x71
    call print_bcd2
    
    mov al,0x09 ; 年
    out 0x70,al
    nop
    in al,0x71
    call print_bcd2

    mov al,0x08 ; 月
    out 0x70,al
    nop
    in al,0x71
    call print_bcd2
    
    mov al,0x07 ; 日
    out 0x70,al
    nop
    in al,0x71
    call print_bcd2

hang:
    hlt
    jmp hang

; -------------------------------
; BCD (AL) → 2桁数字表示
; -------------------------------
print_bcd2:
    push ax
    mov ah, al
    shr ah, 4
    add ah, '0'
    mov al, ah
    call print_char
    pop ax
    and al, 0x0F
    add al, '0'
    call print_char
    ret

; -------------------------------
; 1文字表示(AL)
; -------------------------------
print_char:
    mov ah, 0x0E
    int 0x10
    ret

times 510-($-$$) db 0
dw 0xAA55

QEMU

图片.png

ThinkPad X280

729ea6d9bb4898.jpg

8086互換CPU(V30)

e489befbb6d958.jpg

最後に

今回、INT 0x1Aを使った時刻・日付の取得について解説しました。まとめると以下の通りです。

時刻取得は AH=02H、日付取得は AH=04H で呼び出します。
返り値は BCD 形式なので、表示する際には変換が必要です。
BIOS を経由しない場合でも、CMOS の I/O ポート 0x70/0x71 を直接操作すれば同じ値を取得できます。

今回参考にしたBIOSはかなり古いものですが、2018年発売のThinkPadから同じ方法で日時及び時間を取得できたことから、この仕組みは現代のパソコンにも残っていることが分かります。

CMOSを搭載していない8086復刻機で実験するとBIOSとOUT/INで表示される値に差が出ました。
例外処理の違いかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?