0から作るソフトウェア開発

日々勉強中。。。

0から作るOS開発

環境準備

環境設定

ブートローダ

カーネルローダ

GRUB

カーネル

ドライバーその他

0から作るOS開発 割り込みその3 PICのまとめとPIT

前回までの内容

これまでで、 ことがわかりました。それではカーネルを0から開発していきましょう!

今回はPICのまとめとPITについて説明します

PICの初期化コマンドをプログラムする

前回のICWコマンドをPICに書き込むプログラムを作ります


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

	Description : 8259A programmable interrupt master controller 

_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
#define	PORT_MASTER_PIC_COMMAND		0x0020
#define	PORT_MASTER_PIC_STATUS		0x0020
#define	PORT_MASTER_PIC_DATA		0x0021
#define	PORT_MASTER_PIC_IMR		0x0021

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

	Description :8259A programmable interrupt slave controller 

_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
#define	PORT_SLAVE_PIC_COMMAND		0x00A0
#define	PORT_SLAVE_PIC_STATUS		0x00A0
#define	PORT_SLAVE_PIC_DATA		0x00A1
#define	PORT_SLAVE_PIC_IMR		0x00A1

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

	PIC Initialization control word
	
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
/*
==============================================================================

	Description :initialization control word 1
	
	bit number	value	description
	0		IC4	0:PIC does not expect ICW4 command
				1:PIC expects ICW4 command
	1		SNGL	0:There are some PICs cascaded with each other
				1:There is a one PIC on a system
	2		ADI	0:Call address interval is 8
				1:Call address interval is 4( ignored by x86 system )
	3		LTIM	0:PIC operates in edge trigger mode
				1:PIC operates in level trigger mode
	4		1	reserved. must be set to 1
	5...7		0	Must be 0 in x86 system

==============================================================================
*/
#define	PIC_ICW1			0x11

/*
==============================================================================

	Description :initialization control word 2
	
	bit number	value	description
	0-2		IVA	Address bits for IVT when in MCS-80/85 mode
			(MCS-80/85)
	3-7		IVA	specifies the interrupt vector address in x86
			(x86)

==============================================================================
*/
#define	PIC_MASTER_ICW2		0x20	/* IRQ0 is mapped to IVT 0x20	*/
#define	PIC_SLAVE_ICW2		0x28	/* IRQ8 is mapped to IVT 0x28	*/

/*
===============================================================================

	Description :initialization control word 3
	
	bit number	value		description
	0-7		0-7		IRQ0-IRQ7
							
	Master IRQ2 is conected to Slave INT
===============================================================================
*/
#define	PIC_MASTER_ICW3		0x04
#define	PIC_SLAVE_ICW3		0x02

/*
===============================================================================

	Description :initialization control word 4
	
	bit number	value	description
	0		uPM	0:x86 mode
				1:MCS-80/85 mode
	1		AEOI	0:auto EOI mode
				1:manual EOI mode
	2		M/S	0:buffered slave
				1:buffered master
	3		BUF	0:normal mode
				1:buffered mode
	4		SFNM	0:non nested mode
				1:nested mode
	5...7		0	Must be 0

===============================================================================
*/
#define	PIC_MASTER_ICW4		0x01
#define	PIC_SLAVE_ICW4		0x01


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :initPIC
	Input       :void
	Output      :void
	Return      :void

	Description :initialize pics
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
void initPIC( void )
{
	/* --------------------------------------------------------------------- */
	/* 	send ICW1 ot PIC						 */
	/* --------------------------------------------------------------------- */
	outPortByte( PORT_MASTER_PIC_COMMAND,	PIC_ICW1	);
	outPortByte( PORT_SLAVE_PIC_COMMAND,	PIC_ICW1	);

	/* --------------------------------------------------------------------- */
	/* 	send ICW2 ot PIC						 */
	/* --------------------------------------------------------------------- */
	outPortByte( PORT_MASTER_PIC_DATA,	PIC_MASTER_ICW2	);
	outPortByte( PORT_SLAVE_PIC_DATA,	PIC_SLAVE_ICW2	);

	/* --------------------------------------------------------------------- */
	/* 	send ICW3 ot PIC						 */
	/* --------------------------------------------------------------------- */
	outPortByte( PORT_MASTER_PIC_DATA,	PIC_MASTER_ICW3	);
	outPortByte( PORT_SLAVE_PIC_DATA,	PIC_SLAVE_ICW3	);

	/* --------------------------------------------------------------------- */
	/* 	send ICW4 ot PIC						 */
	/* --------------------------------------------------------------------- */
	outPortByte( PORT_MASTER_PIC_DATA,	PIC_MASTER_ICW4	);
	outPortByte( PORT_SLAVE_PIC_DATA,	PIC_SLAVE_ICW4	);
}



IRQの割り込みベクタ

PICの初期化(ICW2)が完了すると割り込みベクタとIRQは下記のように対応付けされます

IRQと割り込みベクタ
ベクタ番号 IRQ 説明
32(0x20) IRQ0 タイマ
33(0x21) IRQ1 キーボード
34(0x22) IRQ2 スレーブPIC
35(0x23) IRQ3 シリアルポート2
36(0x24) IRQ4 シリアルポート1
37(0x25) IRQ5 ATシステム:パラレルポート2
PS/2システム:予約
38(0x26) IRQ6 FDD
39(0x27) IRQ7 パラレルポート1
40(0x28) IRQ8(IRQ0) CMOS RTC(リアルタイムクロック)
41(0x29) IRQ9(IRQ1) CGA 垂直トレース
42(0x2A) IRQ10(IRQ2) 予約
43(0x2B) IRQ11(IRQ3) 予約
44(0x2C) IRQ12(IRQ4) ATシステム:予約
PS/2システム:AUXデバイス
45(0x2D) IRQ13(IRQ5) FPU(浮動小数点演算装置)
46(0x2E) IRQ14(IRQ6) HDD
47(0x2F) IRQ15(IRQ7) 予約


IRQをマスクする

初期化コマンドICWを書き込み終わった後に割り込みマスクレジスタIMRの

値を書き換えて、目的の割り込みが入るようにしておきます

その他のIRQはマスクしておきます

例えばIR0の割り込みを有効にするためマスクを解除する例は

(スレーブPICの割り込みも有効にしています)


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

	PIC Operation Control Word
	
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
/*
==============================================================================

	Description :interrupt mask register
	
	bit number	value	description
	0...7		IMR0-7	Interrupt Mask Register bit

==============================================================================
*/
#define	PIC_IMR_MASK_IRQ0		0x01
#define	PIC_IMR_MASK_IRQ1		0x02
#define	PIC_IMR_MASK_IRQ2		0x04
#define	PIC_IMR_MASK_IRQ3		0x08
#define	PIC_IMR_MASK_IRQ4		0x10
#define	PIC_IMR_MASK_IRQ5		0x20
#define	PIC_IMR_MASK_IRQ6		0x40
#define	PIC_IMR_MASK_IRQ7		0x80
#define	PIC_IMR_MASK_IRQ_ALL		0xFF

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :initPIC
	Input       :void
	Output      :void
	Return      :void

	Description :initialize pics
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
void initPIC( void )
{
	/* PIC 初期化関数続き	*/
	/* --------------------------------------------------------------------- */
	/* 	send IMR ot PIC							 */
	/* --------------------------------------------------------------------- */
	outPortByte( PORT_MASTER_PIC_IMR, (~PIC_IMR_MASK_IRQ0) & (~PIC_IMR_MASK_IRQ2 )	);
	outPortByte( PORT_SLAVE_PIC_IMR, PIC_IMR_MASK_IRQ_ALL				);
}



後はIRQ0に対応するIDTの割り込みゲートディスクリプタをセットアップし

割り込みハンドラでEOIコマンドを書き込むようにすればよいだけとなります

これでIRQ割り込みが入ってから割り込み終了処理までの一連の処理ができるようになりました

次にIRQ0の割り込みとして使用するタイマのPITについて見ていき、

簡単な割り込みハンドラを作っていきます

PIT(Programmable Interval Timer)

PITは名前の通りタイマ/カウンタとして機能するICで、

Intel CPU周辺デバイスとして開発された 8253 があります

8253は8259Aと同様にCPUの周辺デバイスとしてプロセッサに組み込まれています

8253のハードウェア

ハードウェアに興味のない方は読み飛ばして行っても問題ありません

このピンアサイン図が8253のICです

8253のピンアサイン図

各ピンについては下記表を参照してください

8259Aのピンアサイン
シンボル ピン番号 名称 説明
D0-D7 1-8 双方向データバス 8ビットのデータバスです
CPUは8259Aと8ビットのデータを読み書きできます
CLK0、CLK1、CLK2 9、15、18 クロック入力 入力されたクロックの周波数に基づいて
カウンタをカウントしていきます
カウンタは3つありますので、入力クロック数も3つあります
OUT0、OUT1、OUT2 10、13、17 データアウトプット データを出力するピンです
PITをプログラムすることで出力を変えることができます
GATE0、GATE1、GATE2 11、14、16 ゲートデータ クロック入力のゲートとして機能します
GND 12 グラウンド グラウンド
VCC 24 電源 +5Vを入力します
CS 1 チップセレクト
(Lowアクティブ)
CPUとデータをやり取りするICを決定する信号です
この信号がLowのとき8253はデータを入出力できます
WR 2 ライト
(Lowアクティブ)
CSピンがLowで、WRもLowの場合CPUが8253に
データを書き込むことができます
OUT命令を使用した場合にCSとWRがLowになります
RD 3 リード
(Lowアクティブ)
CSピンがLowで、RDもLowの場合CPUは8253から
データを読み込むことができます
IN命令を使用したときにCSとRDがLowになります
A0、A1 19、20 アドレスライン システムアドレスバスに接続されていて

どのカウンタにアクセスするか選択できます


8259Aと同様に、D0-D7はシステムデータバスに接続されて、このバスを通して8253の

レジスタに書き込みを行い制御します。8253にはタイマが3つあります

3つのタイマはそれぞれ、3つカウンタ、3つのCLK、3つのOUT、3つのGATEピンを独立で

持っていますので、それぞれ違う目的に使用することができます

8253のブロック図

8253のタイマ

8253のタイマはカウンタ、CLK、OUT、GATEピンがあります

そしてタイマは合計3つありそれぞれのピン機能も3つずつあります

1つ目のタイマは、パソコンなどのシステムではOUTピンが PIC のIRラインに接続されていて、

IRQ0のシステムタイマの割り込みとして動作します

2つ目のタイマはDRAMをリフレッシュするためのタイミングを計ります

(DRAMのリフレッシュはメモリコントローラが行います)

3つ目のタイマはスピーカの音を出すために使用されたりします

タイマに使用するカウンタ、CLK、OUT、GATEピンは次のような役割があります

カウンタ

カウンタはタイマに設定したカウント値をクロックの周期に合わせて1ずつ値を

減らしていきます。1つ目のタイマのカウンタが0になるとIRQ0のシステムタイマ割り込みが

発生します。カウンタが0になると次のクロックで設定値に戻ります。カウンタは16ビットのレジスタです

CLK

クロックを入力するピンで、このクロックの周期を元にカウンタを1ずつ減らしていきます

OUT

1つ目のタイマのカウンタが0になるとOUTピンをHighからLowにしてPICに割り込みを

要求します。OUTピンはカウンタがカウントの再設定されるか制御コマンドを書き込みするまで

Highのまま(割り込みが入ったまま)となります

GATE

GATEピンはタイマのモードによって8253の動作を制御します

モードとGATEピンの状態(Low、Highエッジ、High)の組み合わせによって

下記表のように8253は動作します

GATEピンの入力と8253の動作
モード Low(Lowエッジ) Highエッジ High
0 カウント無効 - カウント有効
1 - 1.カウンタ初期化
2.次のクロックでOUTをリセット
-
2 1.カウント無効
2.直ぐにOUTをHighにします
1.カウンタを再設定
2.カウンタを初期化
カウント有効
3 1.カウント無効
2.直ぐにOUTをHighにします
カウンタを初期化 カウント有効
4 カウント無効 - カウント有効
5 - カウンタ初期化 -


8253にはタイマが3つあり、それぞれをチャンネルとも言いますので

8253にチャンネルが3つあるとも言えます

チャンネル0

チャンネル0のタイマはPICにつながっています。8253のOUT0ピンが8259AのIR0ピンに

システムタイマ割り込みが発生したことを知らせます。通常のマザーボードではBIOSがチャンネル0の

カウンタを0xFFFF(65535)に設定します。CLKには1193181.666...Hzのクロックが入力されています

この条件で、カウンタが0になる度にIRQ0のシステムタイマ割り込みが発生すると、

その間隔は54.9254ms(18.2065Hz)となります。この周波数はNTSCの周波数の1/3で

CGAとの互換性のために、CLKの周波数と、カウンタの値が設定されています

チャンネル1

ビデオカード、BIOS、メモリコントローラがDRAMのリフレッシュするために使用していましたが、

現在では使用されていません

チャンネル2

PCのスピーカに接続されていて、チャンネル2で2種類の矩形波を生成して音を出力します

この矩形波をPWM(Pulse-Width Modulation)といいます

チャンネル2を使って音を出す方法とは別にスピーカのポートにアクセスして設定する方法があります

8253のモード

各チャンネルは初期化コマンドICW(Initialization Control Word)を書き込んで

1から6までのモードを設定することができます

モード0(タイマ)

このモードでは、最初に設定したカウント値からCLKからの入力クロックの周波数でカウントダウンしていきます

カウントダウンしていきカウンタの値が0になるとOUTピンをHighにしてPICに割り込みを要求します

カウンタが0になった後は最初に設定したカウント値を再設定するか、別の値をカウンタに書き込むか

モードを変更する制御コマンドを書き込むまでOUTピンはHighになったままとなります

カウンタがカウントダウン中にGATEピンをLowにすると、カウントダウンが止まります

下図はカウンタの値とOUTピンの出力の関係図となります

モード0でのPITのカウンタとOUTピンの出力

モード1(ワンショットタイマ)

このモードは、ワンショットのパルス波形を生成するモードとなります

モード1にする制御コマンドを書き込んだ直後からOUTピンはHighになり、カウンタのカウンタ値を設定した後は

勝手にカウントダウンは始まりません。GATEピンの入力がLowからHighになるエッジを検出するとカウントダウンが

始まります。カウントダウン中はOUTピンがLowになり、カウンタが0になるまでLowのままとなります

カウンタが0になるとOUTピンがHighになり、次のGATEピンがLowからHighになるまで止まったままとなります

カウンタの値は出力パルスのLow期間を決定します(1カウントにCLKの1周期の時間がかかります)

下図はカウンタの値とGATEピン入力、OUTピンの出力の関係図となります

モード1でのPITのカウンタとGATEピン入力、OUTピンの出力

モード2(パルス生成モード)

このモードに設定した後はOUTピンはHighのままとなり、カウンタに設定したカウント値が1になると

OUTピンが1クロック期間だけLowになってIRQ0割り込みをPICに要求します

カウンタの値が1になった後は自動的に最初に設定したカウンタの値に戻します

これにより一定周期にパルスが自動的に出力されることになりますので、システムタイマとして

使うことができます。カウンタに設定する値でパルスを出力する周期を調整することができます

CLKに入力されたクロックをカウンタに設定する値nで分周することになりますので、

パルスの周波数は


	パルスの周波数 = CLKの入力クロック周波数 / カウンタの値n



で計算することができます。通常のタイマと違い、カウンタの0がカウントされないことに注意してください

カウンタに設定する値nは分周するカウンタそのままとなります。0カウントしないので-1する必要はありません

設定するカウンタの値nを計算するには


	カウンタの値n = CLKの入力クロック周波数 / パルスの周波数



となります。ここでCLKの入力クロック周波数は1193181.666...Hzで、

割り込みが入る周波数(パルスの周波数)を1000Hz(1ms間隔)にしたいときのカウント値nは


	1ms周期のカウンタの値n = 1193181.666...Hz / 1000Hz 



と計算できます

下図はカウンタの値とOUTピンの出力の関係図となります

モード2でのPITのカウンタとOUTピンの出力

モード3(矩形波生成モード)

このモードはモード2とほとんど同じ動きをしますが、OUTピンの出力が異なります

OUTピンはHighになった後モード2の半分の期間でLowになります

OUTピンのLow期間はカウンタの値が奇数であれば(n+1)/2の期間となります

偶数であれば(n-1)/2の期間となります

モード3でのPITのカウンタとOUTピンの出力

モード4(ソフトウェアトリガモード)

モード4に設定するとOUTピンはHighになります。カウンタにカウンタの値nを

書き込むとカウントダウンが始まり、カウントが0になるとOUTピンは1クロックの期間

Lowになった後Highになります

モード5(ハードウェアトリガモード)

このモードでは、GATEピンをLowからHighにすることでカウントダウンが始まります

カウンタが0になるとモード4同様にOUTピンは1クロックの期間Lowになった後Highになります


8253のレジスタ

PITの8253のレジスタは、ポートアドレス0x40から0x43にあります

ポートアドレス一覧のPITにあたる部分となります

8253のレジスタは下記表となります

8253のレジスタ
ポートアドレス レジスタ 説明
0x40 Counter0 カウンタ0に値を設定するレジスタです
読み込み時はカウンタ0の値を読み込みます
0x41 Counter1 カウンタ1に値を設定するレジスタです
読み込み時はカウンタ1の値を読み込みます
0x42 Counter2 カウンタ2に値を設定するレジスタです
読み込み時はカウンタ2の値を読み込みます
0x43 Control Word 制御コマンドを書き込むレジスタです
読み込みすることはできません(不定値)。


カウンタレジスタ

カウンタレジスタはCounter0レジスタからCounter2レジスタの3つあります

それぞれチャンネル0からチャンネル2に対応しています

カウンタレジスタは16ビットのレジスタで、カウントダウンする値を格納します

8253は8ビットのデータバスしか持っていませんので、16ビットのデータをそのまま書き込む

ことはできません。制御コマンドレジスタにコマンドを書き込んだ後に続けてカウンタの値を

1バイトずつ書き込む必要があります

制御コマンド(Control Word)レジスタ

8253を制御するためのレジスタです。このレジスタにコマンドを書き込むことで、モードを

変更したりすることができます。制御コマンドレジスタのフォーマットは下図のようになります

PITの制御(Control Word)レジスタ

制御コマンド(Control Word)レジスタのフォーマット
ビット シンボル 説明
0 BCD カウンタの値をバイナリとして扱うか、BCD(2進化10進数)として扱うか設定します
0:バイナリ
1:BCD
1-3 Mode モードを設定します
000b:モード0(タイマ)
001b:モード1(ワンショットタイマ)
010b:モード2(パルス生成モード)
011b:モード3(矩形波生成モード)
100b:モード4(ソフトウェアトリガモード)
101b:モード5(ハードウェアトリガモード)
110b:予約
111b:予約
4-5 RL リード/ロードモードを設定します
00b:カウンタの値がカウンタレジスタにロードされます
01b:カウンタの値をLSB(Least Significant Byte)で書き込みます
10b:カウンタの値をMSB(Most Significant Byte)で書き込みます
11b:カウンタの値を最初の1バイトはLSBで、次の1バイトをMSBで書き込みます
6-7 SC カウンタを選択します
00b:カウンタ0
01b:カウンタ1
10b:カウンタ2
11b:無効


PITのまとめ

PITはレジスタが少なく簡単に制御することができます

C言語での制御例を下記します


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	
	Description : 8253 Programmable Interval Timer
	
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
/* Port Address		*/
#define	PIT_REG_COUNTER0	0x0040
#define	PIT_REG_COUNTER1	0x0041
#define	PIT_REG_COUNTER2	0x0042
#define	PIT_REG_CONTROL		0x0043

/* Input CLK0		*/
#define	DEF_PIT_CLOCK		1193181.67	

/*
=================================================================================

	Description :initialization control word
	
	bit number	value	description
	0		BCD	Binary Coded Decimal
				0:Binary
				1:Binary Coded Decimal
	1...3		Mode	000:timer
				001:programmable one-shot
				010:rate generator
				011:square wave generator
				100:software triggered strobe
				101:hardware triggered strobe
				110:undefined
				111:undefined
	4,5		RL	Read/Load Mode
				00:counter value is latched into an internal control
				   register at the time of the i/o write operation
				01:read or load least significant byte only
				10:read or load most significatn byte only
				11:read or load lsb first then msb
	6,7		SC	select counter
				00:counter 0
				01:counter 1
				02:counter 2
				11:ilegal value	

=================================================================================
*/
#define	DEF_PIT_COM_MASK_BINCOUNT	0x01
#define	DEF_PIT_COM_MASK_MODE		0x0E
#define	DEF_PIT_COM_MASK_RL		0x30
#define	DEF_PIT_COM_MASK_COUNTER	0xC0

/* binary count */
#define	DEF_PIT_COM_BINCOUNT_BIN	0x00
#define	DEF_PIT_COM_BINCOUNT_BCD	0x01

/* counter mode */
#define	DEF_PIT_COM_MODE_TERMINAL	0x00
#define	DEF_PIT_COM_MODE_PROGONE	0x02
#define	DEF_PIT_COM_MODE_RATEGEN	0x04
#define	DEF_PIT_COM_MODE_SQUAREWAVE	0x06
#define	DEF_PIT_COM_MODE_SOFTTRIG	0x08
#define	DEF_PIT_COM_MODE_HARDTRIG	0x0A

/* data transfer */
#define	DEF_PIT_COM_RL_LATCH		0x00
#define	DEF_PIT_COM_RL_LSBONLY		0x10
#define	DEF_PIT_COM_RL_MSBONLY		0x20
#define	DEF_PIT_COM_RL_DATA		0x30

/* counter	 */
#define DEF_PIT_COM_COUNTER0		0x00
#define	DEF_PIT_COM_COUNTER1		0x40
#define	DEF_PIT_COM_COUNTER2		0x80

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :setPitCounter
	Input       :int freq
				 < frequency Hz >
				 unsigned char counter
				 < type of counter  >
				 unsigned char mode
				 < pit mode >
	Output      :void
	Return      :int 
				 < status >
	Description :set counter of a pic
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
int setPitCounter( int freq, unsigned char counter, unsigned char mode )
{
	unsigned short	count;
	unsgined char	command;

	/* --------------------------------------------------------------------- */
	/* 	calculate frequency						 */
	/* --------------------------------------------------------------------- */
	count = ( unsigned short )( DEF_PIT_CLOCK / freq );

	/* --------------------------------------------------------------------- */
	/* 	make initial command						 */
	/* --------------------------------------------------------------------- */
	command = mode | DEF_PIT_COM_RL_DATA | counter;

	outPortByte( PIT_REG_CONTROL, command );

	/* --------------------------------------------------------------------- */
	/* 	send counter value						 */
	/* --------------------------------------------------------------------- */
	outPortByte( PIT_REG_COUNTER0, ( unsigned char )( count & 0xFF		) );
	outPortByte( PIT_REG_COUNTER0, ( unsigned char )( ( count >> 8 ) & 0xFF	) );
}

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :initPit
	Input       :void
	Output      :void
	Return      :void 

	Description :initialize pic
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
void initPit( void )
{
	/* --------------------------------------------------------------------- */
	/* 	set counter0 100Hz						 */
	/* --------------------------------------------------------------------- */
	setPitCounter( 100, DEF_PIT_COM_COUNTER0,  DEF_PIT_COM_MODE_SQUAREWAVE );
}



setPitCounter関数でPITのカウンタの設定を行います

初期化関数で周波数100Hz(10msec)で、モード3(矩形波生成モード)として設定します


	setPitCounter( 100, DEF_PIT_COM_COUNTER0,  DEF_PIT_COM_MODE_SQUAREWAVE );



setPitCounter関数ないでカウンタに設定する値を計算します

ここでは約1193182Hz÷100Hzを計算しています


	count = ( unsigned short )( DEF_PIT_CLOCK / freq );



modeビットはモード3(矩形波生成モード)、RLビットはLSB、MSBの順で書き込むモードを、

SCビットはカウンタ0(counter変数)として設定します


	command = mode | DEF_PIT_COM_RL_DATA | counter;
	
	outPortByte( PIT_REG_CONTROL, command )



設定したい値がcount変数に格納されています。countのLSBを最初に書き込み

次にcountのMSBを書き込みしています


	outPortByte( PIT_REG_COUNTER0, ( unsigned char )( count & 0xFF		) );
	outPortByte( PIT_REG_COUNTER0, ( unsigned char )( ( count >> 8 ) & 0xFF	) );



次にタイマ割り込みハンドラを作っていきたいと思います

割り込みハンドラ

ここまででIDT、PIC、PITと見てきましたので、割り込みが入るまでの一連の動作が

分かってきました。ここでは割り込みが入った後の、ハンドラの処理について見ていきたいと思います

割り込み・例外ハンドラの実行

現在実行中のプログラムの特権レベルよりゲートディスクリプタの特権レベルの方が低い場合

この場合はユーザプログラム実行中に割り込み・例外が発生した場合となります

  1. TSSディスクリプタに記述されているセグメントセレクタとスタックポインタに切り替えます
    CPUは切り替えたスタック上に、現在実行中(ユーザプログラム)のスタックセグメントレジスタSSと
    スタックポインタSPをPUSHします

  2. 次に、CPUは切り替えたスタック上に現在実行中のEFLAGS、CS、EIPをPUSHします

  3. 例外の場合は、エラーコードがある場合は切り替えられたスタック上にエラーコードが
    PUSHされます

現在実行中のプログラムの特権レベルとゲートディスクリプタの特権レベルが同じ場合

この場合はカーネルコード実行中に割り込み・例外が発生した場合となります

  1. CPUはEFLAGS、CS、EIPを現在の実行中のスタックにPUSHします

  2. 例外の場合は、エラーコードがある場合は現在実行中のスタック上にエラーコードが
    PUSHされます

割り込み・例外時のスタック操作

今のところカーネルコードしか作成していませんので、現在実行中の特権レベルと

ゲートディスクリプタの特権レベルが同じ場合を見ていきます

割り込み・例外時にCPUが行うスタック操作としてはEFLAGS、CS、EIPをスタックに積み上げます

CSは現在実行中のセグメントディスクリプタを退避させ、割り込み・例外処理から戻るときにスタックから

POPし、CSを元に戻します(CPUが自動的に行います)。EIPも同様に現在実行中のアドレスを

スタックに退避させ、割り込み・例外処理から戻るときにEIPに戻すことで、元のアドレスに戻ります

そして、例外時にエラーコードがある場合はエラーコードがスタックに積み上げられます

割り込み・例外時のスタック操作

割り込み・例外処理時にCPUは上図のようにスタックに積み上げます

エラーコードが有る場合にはスタックポインタESPはエラーコードの次のスタックアドレスを指しています

エラーコードが無い場合は、ESPは戻り先のアドレスEIPの次のスタックアドレスを指しています

補足として異なる特権モードで割り込みが発生した(ユーザプログラム実行中の割り込みの)場合の

CPUが操作するスタックは下図となります

異なる特権モード実行時に割り込み・例外が入ったときのスタック操作

エラーコード

スタックに積まれるエラーコードは下記フォーマットになります

例外のエラーコード

例外のエラーコード
ビット シンボル 説明
0 EXT 外部イベントフラグ
0:ソフトウェアにとって内部的なイベントによって例外が発生しています
1:ソフトウェアにとって外部的なイベント(IRQなど)によって例外が発生しています
1 IDT ディスクリプタ位置フラグ。参照するディスクリプタの場所を示すフラグです
0:エラーコードの”セグメントセレクタインデックス”はIDTのインデックスとなります
1:エラーコードの”セグメントセレクタインデックス”はGDTまたは現行のLDTのインデックスとなります
2 TI GDT/LDTフラグ。ビット1のIDTが0の場合のみ使用されます
0:エラーコードの”セグメントセレクタインデックス”はLDTのセグメントディスクリプタまたは
    ゲートディスクリプタのインデックスとなります
1:エラーコードの”セグメントセレクタインデックス”はGDTのインデックスとなります
3-15 Segment Selector Index セグメントセレクタインデックス
エラーコードが参照するIDT、GDTまたは現在のLDTのインデックスとなります
16-31 Reserved 予約領域です


エラーコードは外部割り込み(INTR、LINT0、LINT1ピン)やINT命令ではスタックに積まれることはありません

例外とエラーコードの対応表は下記となります

例外の割り込みベクタ
ベクタ番号 ニーモニック 説明 原因 エラーコード
0 #DE 除算エラー
Divided by 0
0で割り算を行った
DIV命令、IDIV命令
無し
1 #DB シングルステップ(デバッグ)
Single Step(Debug)
デバッガによるコード、データの参照 無し
2 - マスク不可割り込み
NMI(Non-Maskable Interrupt)
マスク不可能な外部割り込み 無し
3 #BP ブレークポイント(デバッグ)
Break Point(Debug)
INT 3命令 無し
4 #OF オーバフロー
Overflow
INTO命令 無し
5 #BR バウンド範囲外
Bounds Check
BOUND命令 無し
6 #UD 無効なオペコード(未定義なオペコード)
Undifined Operation Code Instruction
UD2命令または無効なオペコード 無し
7 #NM デバイス使用不可能(数値演算コプロセッサ無し)
No Coprocessor
浮動小数点命令、WAIT/FWAIT命令 無し
8 #DF ダブルフォルト
Double Fault
例外、NMI、INTRを生成できる命令 常に0
9 #MF コプセッサセグメントオーバーラン
Coprocessor Segment Overrun
浮動小数点命令
Intel386プロセッサ以降のプロセッサでは生成されません
無し
10 #TS 無効なTSS(Task State Segments)
Invalid TSS
タスクスイッチ、TSSアクセス 例外を発生したセグメントディスクリプタのインデックス
11 #NP セグメントが不在
Segment Not Present
セグメントレジスタのロード、システムセグメントへのアクセス 例外を発生したセグメントディスクリプタのインデックス
12 #SS スタックセグメントフォルト
Stack Segment Fault
スタック操作、SSレジスタのロード 例外を発生したセグメントセレクタ
13 #GP 一般保護例外
General Protection Fault(GPF)
メモリ参照、保護チェック セグメントディスクリプタへのロード中にフォルトが発生した場合
そのディスクリプタを指すセグメントセレクタ
そうでない場合は0
14 #PF ページフォルト
Page Fault
メモリ参照 ・スタック上のエラーコード
・制御レジスタCR2の値
15 - 予約 - -
16 #MF 浮動小数点エラー(数値演算フォルト)
Coprocessor Error
浮動小数点命令、WAIT/FWAIT命令 無し
17 #AC アライメントチェック
Alignment Check
メモリ参照
Intel486プロセッサ以降のプロセッサで発生します
常に0
18 #MC マシンチェック
Machine Check
エラーコード、ソースがモデルに依存
インテルPentinumプロセッサ以降
無し
19 #XF SIMD浮動小数点例外
SIMD FPU Exception
SIMD浮動小数点命令 無し
20-31 - 予約 - -
32-255 - マスク可能割り込み
IRQやソフトウェア割り込みの割り込みベクタとして使用します
INTRピンによる外部割り込みまたはINT命令 無し


割り込み番号14のページフォルトのみエラーコードのフォーマットではありません

エラーコードにアクセスするにはスタックポインタESPから-4した(32ビット引いた)アドレスに

アクセスします

割り込み・例外処理からの復帰

割り込み・例外処理から元の処理に復帰するときにはIRET(16ビット命令)命令または

IRED(32ビット命令)命令で復帰します

IRET命令はRET命令と似ていますが、スタック上に積まれたEFLAGをPOPするという点だけが

が違います。注意事項としてEFLAGレジスタのIOPLビットはCPLが0(リング0)の場合だけ

元の値に戻ります

タイマ割り込みハンドラ

割り込みの一連の流れが分かってきましたので、システムタイマとして動作させるPITの

タイマ割り込みハンドラを作成していきます

割り込みハンドラの入り口

割り込みハンドラの入り口でまず最初に実行するのが汎用レジスタの退避命令PUSHAD命令です

割り込みハンドラの出口でPOPすれば汎用レジスタの値が破壊されても問題無しとなります

割り込みゲートディスクリプタではEFLAGSのIFフラグが0クリアされその他の割り込みは自動的に

禁止されます


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :enter_interrupt
	Input       :void
	Output      :void
	Return      :void

	Description :call for entering interrupt function
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
__inline__ void enter_interrupt( void )
{
	__asm__ __volatile__  ("pushad"			);
}



割り込みハンドラ(IRQ)の終了通知

PICによる割り込みハンドラはPICにEOIコマンドを書き込んで割り込みが終了したことを

通知します

IRQ以外の例外ハンドラにはこの処理は必要ありません


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :interrupt_done
	Input       :void
	Output      :void
	Return      :void
	Description :inform end of interrupt to pics
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
__inline__ void interrupt_done( void )
{
	/* send end of interrupt(EOI) command to pic */
	sendCommandPic( 0x20, 0x20 );
	sendCommandPic( 0xA0, 0x20 );
}



割り込みハンドラの出口

割り込みの最後ではスタックに積んだ汎用レジスタの値を復帰させ

IRED命令で元の処理に戻ります


/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :exit_interrupt
	Input       :void
	Output      :void
	Return      :void

	Description :call for exiting interrupt function
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
__inline__ void exit_interrupt( void )
{
	__asm__ __volatile__  (" popad"	);
	__asm__ __volatile__  (" iretd"	);
}



タイマ割り込みハンドラのまとめ

割り込みハンドラでやるべき処理をタイマ割り込みで行います


static volatile int timer_tick;

/*
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
	Funtion     :timer_interrupt
	Input       :void
	Output      :void
	Return      :void

	Description :handling timer interrupt
_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
*/
void timer_interrupt( void )
{
	/* --------------------------------------------------------------------- */
	/*  entering interrupt							 */
	/* --------------------------------------------------------------------- */
	enter_interrupt( );

	/* --------------------------------------------------------------------- */
	/*  count tick								 */
	/* --------------------------------------------------------------------- */
	timer_tick++;

	/* --------------------------------------------------------------------- */
	/*  inform end of interrupt to pics					 */
	/* --------------------------------------------------------------------- */
	interrupt_done( );

	/* --------------------------------------------------------------------- */
	/*  exit interrupt							 */
	/* --------------------------------------------------------------------- */
	exit_interrupt( );
}



この関数をするIDTの割り込みゲートディスクリプタに登録しておくことで、この割り込みハンドラが

呼び出されるようになります

実際にはユーザプログラム実行中に割り込みが入った場合とカーネルコード実行中に

割り込みが入った場合の処理で異なった処理をする必要がります。今のところ

ユーザプログラムは実行されていませんので、この回のハンドラの処理で十分です

次回は物理メモリの管理方法についてです

inserted by FC2 system