본문 바로가기

MINT64 OS 개발

14장. 키보드 디바이스 드라이버 업그레이드

Ⅰ. 인터럽트 핸들러와 큐

1. 범용 큐 구현과 사용 방법

  • 큐에 대한 정보를 관리하는 자료구조
    • bLastOperationPut : 삽입 위치와 제거 위치가 같아지는 경우를 처리하기 위한 필드 (삽입 시 TRUE, 제거 시 False로 설정된다.)
    typef struck kQueueManagerStruct
    {
        // 큐를 구성하는 데이터 하나의 크기와 최대 개수
        int iDataSize;
        int iMaxDataCount;
    
        // 큐 버퍼의 포인트와 삽입/제거 인덱스
        void pvQueueArray;
        int iPutIndex;
        int iGetIndex;
    
        // 큐에 수행된 마지막 명령이 삽입인지를 저장
        // 큐의 버퍼가 비어있는지, 가득 찼는지 확인하는 용도
        BOOL bLastOperationPut; 
    } QUEUE;
    
  • 큐를 초기화하는 함수의 코드
    • 큐 버퍼 어드레스와 데이터의 크기를 설정하고 나머지 필드를 기본값(0)으로 설정한다.
    void kInitializeQueue( QUEUE* pstQueue, void* pvQueueBuffer, int iMaxDataCount, int iDataSize )
    {
    		// 큐의 최대 개수와 크기, 그리고 버퍼 어드레스를 저장
    		pstQueue->iMaxDataCount = iMaxDataCount;
    		pstQueue->iDataSize = iDataSize;
    		pstQueue->pvQueueArray = pvQueueBuffer;
    
    		// 큐의 삽입 위치와 제거 위치를 초기화하고 마지막으로 수행된 명령을 제거로 설정하여 큐를 빈 상태로 만듦
    		pstQueue->iPutIndex = 0;
    		pstQueue->iGetIndex = 0;
    		pstQueue->bLastOperationPut = FALSE;
    }
    
  • 큐에 데이터를 삽입하고 제거하는 함수의 코드
// 큐가 가득 찼는지 여부를 반환
BOOL kIsQueueFull( const QUEUE* pstQueue )
{
		// 큐의 삽입 인덱스와 제거 인덱스가 같고, 마지막으로 수행된 명령이 삽입이면
		// 큐가 가득 찼으므로 삽입할 수 없음
		if( (pstQueue->iGetIndex == pstQueue->iPutIndex) && 
				(pstQueue->bLastOperationPut == TRUE ) )
		{
				return TRUE;
		}
		return FALSE;
}

// 큐가 비었는지 여부를 반환
BOOL kIsQueueEmpty( const QUEUE* pstQueue )
{
		// 큐의 삽입 인덱스와 제거 인덱스가 같고 마지막으로 수행된 명령이 제거이면
		// 큐가 비었으므로 제거할 수 없음
		if( ( pstQueue->iGetIndex == pstQueue->iPutIndex ) &&
				( pstQueue->bLastOperationPut == FALSE ) )
		{
				return TRUE;
		}
		return FALSE;
}

// 큐에 데이터를 삽입
BOOL kPutQueue( QUEUE* pstQueue, const void* pvData )
{
		// 큐가 가득 찼으면, 삽입할 수 없음
		if( kIsQueueFull( pstQueue ) == TRUE )
		{
				return FALSE;
		}

		// 삽입 인덱스가 가리키는 위치에서 데이터의 크기만큼을 복사
		kMemCpy( ( char* ) pstQueue->pvQueueArray + ( pstQueue->iDataSize * 
						pstQueue->iPutIndex ), pvData, pstQueue->iDataSize );

		// 삽입 인덱스를 변경하고 삽입 동작을 수행했음을 기록
		pstQueue->iPutIndex = ( pstQueue->iPutIndex + 1 ) % pstQueue->iMaxDataCount;
		pstQueue->bLastOperationPut = TRUE;
		return TRUE;
}

// 큐에서 데이터를 제거
BOOL kGetQueue( QUEUE* pstQueue, void* pvData )
{
		// 큐가 비었으면, 제거할 수 없음
		if( kIsQueueEmpty( pstQueue ) == TRUE ){
				return FALSE;
		}

		// 제거 인덱스가 가리키는 위치에서 데이터의 크기만큼을 복사
		kMemCpy( pvData, ( char* ) pstQueue->pvQueueARray + ( pstQueue->iDataSize *
						 pstQueue->iGetIndex ), pstQueue->iDataSize );

		// 제거 인덱스 변경하고 제거 동작을 수행했음을 기록
		pstQueue->iGetIndex = ( pstQueue->iGetIndex + 1 ) % pstQueue->iMaxDataCount;
		pstQueue->bLastOperationPut = FALSE;
		return TRUE;
}

 

  • 범용 큐를 사용하는 예
    • 임의의 크기를 가지는 자료구조인 TEMPDATA를 최대 100개까지 관리할 수 있는 큐를 생성하고 사용하는 예시의 코드이다.
    // 데이터 정의
    typedef struct kTempDataStruct
    {
    	int a;
      ``` 생략 ```
    } TEMPDATA;
    
    // 큐와 큐 버퍼 정의
    QUEUE g_stQueue;
    TEMPDATA g_vstQueueBuffer[ 100 ];
    
    void main( void )
    {
    		TEMPDATA stData;
    
    		// 큐 초기화
    		kInitializeQueue( &g_stQueue, g_vstQueueBuffer, 100, sizeof( TEMPDATA ) );
    
    		// 데이터 삽입
    		if( kPutQueue( &g_stQueue, &stData ) == TRUE )
    		{
    				// 성공
    		}
    		else
    		{
    				// 실패
    		}
    
    		// 데이터 제거
    		if( kGetQueue( &g_StQueue, &stData ) == TRUE )
    		{
    				// 성공
    		}
    		else
    		{
    				// 실패
    		}
    }
    

Ⅱ. 키보드 디바이스 드라이버 업그레이드

1. 키 정보를 저장하는 자료구조와 큐 생성

  • 키 정보를 저장하는 자료구조
typedef struct kKeyDataStruct
{
		// 키보드에서 전달된 스캔 코드
		BYTE bScanCode;
		// 스캔 코드를 변환한 ASCII 코드
		BYTE bASCIICode;
		// 키 상태를 저장하는 플래그(눌림/떨어짐/확장키 여부)
		BYTE bFlags;
} KEYDATA;
  • 키보드를 활성화하고 큐를 초기화하는 함수의 코드
    • 기존 키보드를 활성화했던 kActiviteKeyboard( )함수와 키 큐를 생성하는 기능을 묶어서 kInitalizeKeyboard( ) 함수로 통합하였다.
    • 키 큐의 크기는 최대 100개로 넉넉히 설정한다.
    // 키 큐의 최대 크기
    #define KEY_MAXQUEUECOUNT 100
    
    // 키를 저장하는 큐와 버퍼 정의
    static QUEUE gs_stKeyQueue;
    static KEYDATA gs_vstKeyQueueBuffer[ KEY_MAXQUEUECOUNT ];
    
    BOOL kInitializeKeyboard( void )
    {
    		// 큐 초기화
    		kInitializeQueue( &gs_stKeyQueue, gs_vstKeyQueueBuffer, KEY_MAXQUEUECOUNT,
    											sizeof( KEYDATA ) );
    
    		// 키보드 활성화
    		return kActivateKeyboard();
    }
    

 

2. 키보드 핸들러 수정

  • 키 처리 기능이 추가된 키보드 핸들러 코드
// 스캔 코드를 내부적으로 사용하는 키 데이터를 바꾼 후, 키 큐에 삽입
BOOL kConvertScanCodeAndPutQueue( BYTE bScanCode )
{
		KEYDATA stData;

		// 스캔 코드를 키 데이터에 삽입 
		stData.bScanCode = bScanCode;
		
		// 스캔 코드를 ASCII 코드와 키 상태로 변환하여 키 데이터에 삽입
		if( kConvertScanCodeToASCIICode( bScanCode, &( stData.bASCIICode ),
						&( stData.bFlags ) ) == TRUE )
		{
				// 키 큐에 삽입
				return kPutQueue( &gs_stKeyQueue, &stData );
		}
		return FALSE;
}

// 키보드 인터럽트의 핸들러
void kKeyboardHandler( int iVectorNumber )
{
		char vcBuffer[] = "[INT:  , ]";
		static int g_iKeyboardInterruptCount = 0;
		BYTE bTemp;

		//====================================================================
		// 인터럽트가 발생했음을 알리려고 메시지를 출력하는 부분
		// 인터럽트 벡터가 화면 왼쪽 위에 2자리 정수를 출력
		vcBuffer[ 5 ] = '0' + iVectorNumber / 10;
		vcBuffer[ 6 ] = '0' + iVectorNumber % 10;
		// 발생한 횟수 출력
		vcBuffer[ 8 ] = '0' + g_iCommonInterruptCount;
		g_iKeyboardInterruptCount = ( g_iKeyboardIntteruptCount + 1 ) % 10;
		//====================================================================

		// 키보드 컨트롤러에서 데이터를 읽어서 ASCII로 변환하여 큐에 삽입
		if( kIsOutputBufferFull() == TRUE )
		{
				bTemp = kGetKeyboardScanCode();
				kConvertScanCodeAndPutQueue( bTemp );
		}

		// EOI 전송
		kSendEOIToPIC( iVectorNumber - PIC_IRQSTARTVECTOR );
}

 

3. 셸 코드 수정

  • 큐를 사용하도록 수정된 셸 코드
// 키 큐에서 키 데이터를 제거
BOOL kGetKeyFromKeyQueue( KEYDATA* pstData )
{
		// 큐가 비었으면, 키 데이터를 꺼낼 수 없음
		if( kIsQueueEmpty( &gs_stKeyQueue ) == TRUE )
		{
				return FALSE;
		}

		// 키 큐에서 키 데이터를 제거
		return kGetQueue( &gs_stKeyQueue, pstData );
}

// C언어 커널 엔트리 포인트
void Main( void )
{
		char vcTemp[ 2 ] = { 0, };
		BYTE bTemp;
		int i = 0;
		KEYDATA stData;

		``` 생략 ```
		
		while(1)
		{
				if( kGetKeyFromKeyQueue( &stData ) == TRUE )
				{
						if( stData.bFlags & KEY_FLAGS_DOWN )
						{
							// 키 데이터의 ASCII 코드 값을 저장
							vcTemp[ 0 ] = stData.bASCIICode;
							kPrintString( i++, 17, vcTemp );

							// 0이 입력되면 변수를 0으로 나누어 Divide Error 예외(벡터 0번)을 발생시킴
							if( vcTemp[ 0 ] = '0' )
							{
									// 아래 코드를 수행하면, Divide Error 예외가 발생하여, 커널의 임시 핸들러가 수행됨
									bTemp = bTemp / 0;
							}
						}
				}
		}
}

 

4. 인터럽트로 인한 문제와 인터럽트 제어

  • 인터럽트 플래그를 제어하는 함수 코드
BOOL kSetIntteruptFlag( BOOL bEnableInterrupt )
{
		QWORD qwRFLAGS;

		// 이전의 RFLAGS 레지스터 값을 읽은 뒤에 인터럽트 가능/불가 처리
		qwRFLAGS = kReadRFLAGS();
		if( bEnableInterrupt == TRUE )
		{
				kEnableInterrupt();
		}
		else
		{
				kDisableInterrupt();
		}

		// 이전 RFLAGS 레지스터의 IF 비트(비트9)를 확인하여, 이전의 인터럽트 상태를 반환
		if(qwRFLAGS & 0x0200)
		{
				return TRUE;
		}
		return FALSE;
}
  • 수정된 큐 관련 함수 코드
    • 큐에 데이터를 삽입 또는 삭제할 때, 인터럽트 플래그를 조작하여 작업을 수행하는 동안 인터럽트가 발생하지 않게 하였다.
    // 스캔 코드를 내부적으로 사용하는 키 데이터로 바꾼 후, 키 큐에 삽입
    BOOL kConvertScanCodeAndPutQueue( BYTE bScanCode )
    {
    		KEYDATA stData;
    		BOOL bResult = FALSE;
    		BOOL bPreviousInterrupt;
    
    		// 스캔 코드를 키 데이터에 삽입
    		stData.bScanCode = bScanCode;
    
    		// 스캔 코드를 ASCII 코드와 키 상대로 변환하여 키 데이터를 삽입
    		if( kConvertScanCodeToASCIICode( bScanCode, &( stData.bASCIICode ), 
    						&( stData.bFlags ) ) == TRUE )
    		{
    				// 인터럽트 불가
    				bPreviousInterrupt = kSetInterruptFlag( FALSE );
    				
    				// 키 큐에 삽입
    				bResult = kPutQueue( &gs_stKeyQueue, &stData );
    
    				// 이전 인터럽트 플래그 복원
    				kSetInterruptFlag( bPreviousInterrupt );
    		}
    		return bResult;
    }
    
    // 키 큐에서 키 데이터를 제거
    BOOL kGetKeyFromKeyQueue( KEYDATA* pstData )
    {
    		BOOL bResult;
    		BOOL bPreviousInterrupt;
    
    		// 큐가 비었으면, 키 데이터를 꺼낼 수 없음
    		if( kIsQueueEmpty( &gs_stKeyQueue ) == TRUE )
    		{
    				return FALSE;
    		}
    
    		// 인터럽트 불가
    		bPreviousInterrupt = kSetInterruptFlag( FALSE );
    
    		// 키 큐에서 키 데이터를 제어
    		bResult = kGetQueue( &gs_stKeyQueue, pstData );
    
    		// 이전 인터럽트 플래그 복원
    		kSetInterruptFlag( bPreviousInterrupt );
    		return bResult;
    }
    
  • ACK가 수신될 때까지 대기하는 코드 (ACK외에 스캔 코드는 변환해서 큐에 삽입)
BOOL kWaitForACKAndPutOtherScnaCode( void )
{
		int i, j;
		BYTE bData;
		BOOL bResult = FALSE;

		// ACK가 오기 전에 키보드 출력 버퍼(포트 0x60)에 키 데이터가 저장될 수 있으므로
		// 키보드에서 전달된 데이터를 최대 100개까지 수신하여 ACK를 확인
		for (j = 0; j < 100 ; j++ )
		{
				// 0xFFFF만큼 루프를 수행할 시간이면, 충분히 커맨드의 응답이 올 수 있음
				// 0xFFFF 루프를 수행한 이후에도 출력 버퍼(포트 0x60)가 차있지 않으면 무시하고 읽음
				for( i = 0; i < 0xFFFF ; i++)
				{
					// 출력 버퍼(포트 0x60)가 차있으면, 데이터를 읽을 수 있음
					if( kIsOutputBufferFull() == TRUE )
					{
							break;
					}
				}
			// 출력 버퍼(포트 0x60)에서 읽은 데이터가 ACK(0xFA)이면 성공
			bData = kInPortByte( 0x60 );
			if( bData == 0xFA )
			{
					bResult = TRUE;
					break;
			}
			// ACK(0xFA)가 아니면, ASCII 코드로 변환하여 키 큐에 삽입
			else{
					kConvertScanCodeAndPutQueue( bData );
			}
	  }
		return bResult;
}
  • kActivateKeyboard()
BOOL kActivateKeyboard( void )
{
		int i, j;
		BOOL bPreviousInterrupt;
		BOOL bResult;

		// 인터럽트 불가
		bPreviousInterrupt = kSetInterruptFlag( FALSE );
		
		// 컨트롤 레지스터(포트 0x64)에 키보드 활성화 커맨드(0xAE)를 전달하여 키보드 디바이스 활성화
		kOutPortByte( 0x64, 0xAE );

		// 입력 버퍼(포트 0x60)가 빌 때까지 기다렸다가 키보드에 활성화 커맨드를 전송
		// 0xFFFF만큼 루프를 수행할 시간이면, 충분히 커맨드가 전송될 수 있음
		// 0xFFFF 루프를 수행한 이후에도 입력 버퍼(포트 0x60)가 비지 않으면 무시하고 전송
		for( i = 9; i < 0xFFFF ; i++ )
		{
				// 입력 버퍼(포트 0x60)로 키보드 활성화(0xF4) 커맨드를 전달하여 키보드로 전송
				if( kIsInputBufferFull() == FALSE )
				{
						break;
				}
		}
		// 입력 버퍼(포트 0x60)로 키보드 활성화(0xF4) 커맨드를 전달하여 키보드로 전송
		kOutPortByte( 0x60, 0xF4 );

		// ACK가 올 때까지 대기함
		bResult = kWaitForACKAndPutOtherScanCode();

		// 이전 인터럽트 상태 복원
		kSetInterruptFlag( bPreviousInterrupt );
		return bResult;

}

Ⅲ. 키보드 디바이스 드라이버의 업그레이드와 빌드