38. 사용자 지정 프로토콜을 만들고 사용하기
InstallMultipleProtocolInterfaes와 UninstallMultipleProtocolInterfaces 사용
이전 장까지 우리는 프로토콜만을 사용해왔다.
이제는 실제로 프로토콜을 생성하는 드라이버를 작성할 것이다.
클래스와 유사한 프로토콜로서, SimpleClass라는 프로토콜을 작성할 예정이다.
SimpleClass 프로토콜은 두 가지 기능을 가지게 된다. 내부 클래스변수 mNumber에 대한 간단한 Getter와 Setter가 이에 해당한다. mNumber의 m은 이 모듈에 대해서 해당 변수가 지역 변수임을 의미한다.
$ cat UefiLessonsPkg/SimpleClassProtocol/SimpleClassProtocol.c
---
UINTN mNumber = 0;
EFI_STATUS
EFIAPI
SimpleClassProtocolSetNumber (
UINTN Number
)
{
mNumber = Number;
return EFI_SUCCESS;
}
EFI_STATUS
EFIAPI
SimpleClassProtocolGetNumber (
UINTN* Number
)
{
if (Number == NULL) {
return EFI_INVALID_PARAMETER;
}
*Number = mNumber;
return EFI_SUCCESS;
}
SIMPLE_CLASS_PROTOCOL mSimpleClass = {
SimpleClassProtocolGetNumber,
SimpleClassProtocolSetNumber
};C 언어에는 클래스와 같은 키워드가 없지만, 비슷하게는 만들 수 있다. struct를 이용하여 class 메소드를 함수에 대한 포인터 필드로 사용한다.
이제 SIMPLE_CLASS_PROTOCOL 유형의 정의를 포함하는 헤더 파일 만들어야 한다.
일반적으로 패키지에는 폴더의 프로토콜에 대한 헤더가 포함된다.
따라서 UefiLessonsPkg/Include/Protocol/SimpleClass.h 헤더 파일을 생성한다.
SIMPLE_CLASS_PROTOCOL 유형을 *.c 파일에 포함시킨다.
다른 모듈에서 프로토콜을 이용할 수 있게 하려면, 시스템(Protocol database)에 프로토콜을 설치해 주어야 한다.
이를 위해서 InstallProtocolInterface 함수를 사용한다.
EDKII 코드베이스 전체에서 InstallProtocolInterface를 찾을 수 있지만, 동일한 UEFI 스펙에 따르면, 사실 이건 조금 구식 API이다. 여기서 제안하는 API는 다음과 같다.
우리 예제에서 드라이버를 작성할 때, 드라이버 INF 파일에서 집입점으로 선언할 SimpleClassProtocolDriverEntryPoint 함수에서 InstallMultipleProtocolInterfaces를 사용하겠다.
여기에 mSimpleClasshandle의 주소를 출력하도록 Print문도 추가해준다.
mSimpleClassHandle 및 mSimpleClass는 동일한 *.c 파일(따라서, 앞에 m이 접두사로 쓰인)에서 선언되며, gSimpleClassProtocolGuid(g는 전역을 의미) 패키지 DEC 파일에서 선언하므로, 다른 모듈이 이 파일을 포함하고 프로토콜을 사용할 수 있다.
최종적으로, 이를 INF 파일에 넣어준다.
SimpleClassUser
지금부터는 우리가 프로토콜을 사용할 애플리케이션을 작성할 것이다. 이 프로토콜은 앱 이미지 핸들에설치되지 않았으므로, 먼저 LocateHandleBuffer API로 해당 프로토콜이 있는 모든 핸들을 찾은 다음, 각 핸들에서 OpenProtocol을 호출해야 한다.
이는 다른 EDKII 패키지의 다른 프로토콜과 다르지 않다.
프로토콜을 다음과 같이 사용한다.
현재 숫자 값을 가져와 출력
숫자 값에 5를 더하고 세팅
다시 현재 값을 가져와 출력
프로토콜 유저의 앱 INF 파일은 매우 간단하다.
패키지의 [Components] 섹션의 SimpleClassProtocol과 SimpleClassUser를 모두 추가한다.
Testing
이제 빌드하고 결과를 QEMU 공유 폴더에 복사하여 OVMF를 실행한다.
SimpleClassUser를 먼저 실행하면, 오류가 발생할 것이다.
에러 코드에서도 보이듯이, 아직 우리가 만든 프로토콜이 시스템이 설치되지 않았다. 이를 설치하고 애플리케이션을 실행시키면 된다.
dh를 사용하여 드라이버 및 해당 프로토콜에 대한 핸들을 볼 수 있다.
(핸들 번호는 본인 환경에 맞게 바꿔 생각해주면 된다.)
SimpleClassUser.efi를 다시 실행하면, 숫자가 5에서 10으로 증가한다. 변수가 SimpleClassUser.efi 외부에 저장된다는 것을 의미한다. 예상해보면, 이는 핸들 C7이 있는 프로토콜에 저장된다는 것을 짐작할 수 있다.
load 명령어를 다시 사용하면, SimpleClassProtocol.efi 드라이버의 사본을 로드할 수 있다.
이제 시스템 C7 및 C9에는 두 개의 프로토콜 핸들이 존재한다.
SimpleClassUser.efi 애플리케이션을 다시 실행하면, 시스템 2개의 또 다른 버전이 존재하는 것을 볼 수 있다.
아직 이 드라이버에는 unload 기능을 구현하지 않았으므로 언로드할 수는 없다.
여기까지의 완성 코드
IMAGE_UNLOAD
이제 프로토콜 드라이브에서 언로드 기능을 구현하도록 하자.
SimpleClassProtocol.c에는 다음 함수를 추가해준다.
테스트 해보자. 다시 드라이버를 빌드하여 로드하고 앱을 실행한다.
프로토콜 핸들에 대한 언로드를 수행할 수 없었지만, 드라이버에 대한 unload 기능을 구현해주었으니 언로드를 시도한다.
하지만, 아직 핸들(C7)이 남아있을 것이다.
이제 애플리케이션을 실행하려하면 예외가 발생한다. OpenProtocol 호출은 프로토콜에 대해 동일한 주소를 제공하나, 이제 이 메모리는 해제되고 SimpleClass -> GetNumber와 같은 프로토콜 함수에 대한 호출은 시스템에서 충돌이 발생한다.
이 문제를 해결하려면 드라이버 언로드시 시스템에서 프로토콜을 제거해야 한다.
Protocol Interface 제거하기
프로토콜 인터페이스 설치와 마찬가지로 UEFI API에는 프로토콜 제거를 위한 두 가지 함수가 존재한다. 한 가지는 더 이상 사용되지 않고(UninstallProtocolInterface), 하나는 새로운 함수(UninstallMultipleProtocolInterface)이다.
드라이버 언로드에서 UninstallMultipleProtocolInterfaces를 호출해 보겠다.
이제 예제를 다시 빌드하여 실행해보자.
드라이버를 로드하고 앱을 다시 한 번 실행해본다.
다른 드라이버 복사본을 로드하고 앱을 다시 실행한다.
이제 시스템 두 개의 프로토콜/드라이버가 있다. 첫 번째 프로토콜/드라이버를 언로드하자.
앱을 다시 실행하면, 시스템에 SimpleClass 프로토콜이 있는 핸들 하나만 있고, 유효하며 다른 프로토콜 제거 프로세스에 의해 완전히 방해받지 않는다는 것을 알 수 있다.
Last updated