Zephyr v3.7.0 · 장치 드라이버 검증

장치 에뮬레이터: 실제 드라이버를 가짜 버스에 연결하기

하드웨어가 없을 때 앱을 시험하려면 무엇을 대신해야 할까? Zephyr는 버스 컨트롤러와 주변장치의 응답을 소프트웨어로 구현해, 실제 주변장치 드라이버의 API 경로를 계속 통과시킬 수 있다.

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

이 글은 Zephyr v3.7.0의 공식 문서·태그 소스에 맞춘 구조 설명이다. 아래 명령은 릴리스 문서의 재현 출발점이며, Zephyr 바이너리를 직접 빌드·실행한 기록은 아니다. 도식은 설명을 위해 새로 그린 흐름이고, 실측 결과나 원본 문서 그림의 복제가 아니다.

1. 에뮬레이터가 대신하는 층을 먼저 정한다

장치 에뮬레이터는 앱 아래의 특정 장치나 외부 버스 응답을 흉내 낸다. native_sim은 Zephyr 앱·커널을 Linux 실행 파일로 빌드하는 보드 대상이다. QEMU는 선택한 가상 보드의 CPU·플랫폼을 실행한다. 이 셋은 같은 뜻이 아니다. 장치 에뮬레이터는 충분한 자원이 있으면 다른 보드에서도 빌드할 수 있고, native_sim에서는 호스트 디버깅과 조합하기 쉽다.

표 1. 무엇을 바꾸고 무엇을 그대로 두는가
수단대신하는 층이 경로만으로 남는 질문
장치·버스 에뮬레이터I²C·SPI 등의 버스 컨트롤러 또는 그 아래 주변장치의 응답실물 컨트롤러의 레지스터·전기 신호·타이밍
native_sim호스트에서 Zephyr 앱·커널을 돌리는 실행 대상특정 MCU의 ISA·메모리맵·보호 기능
QEMU 보드지원되는 가상 CPU·보드와 일부 주변장치QEMU가 모델링하지 않은 실리콘 세부 동작
실물 보드소프트웨어 대역 없이 물리 연결을 직접 사용측정하지 않은 환경·고장·제품 변형

근거: 3.7.0 장치 에뮬레이터 개요, 외부 버스·주변장치 에뮬레이터, native_sim 개요.

2. 앱은 같은 EEPROM API를 호출한다

v3.7.0의 AT24 예시에서는 테스트가 EEPROM API를 호출하고, 실제 eeprom_at2x.c 드라이버가 I²C 메시지를 만든다. 에뮬레이션된 I²C 컨트롤러는 주소로 주변장치 에뮬레이터를 찾고 transfer 콜백으로 전달한다. 따라서 앱의 호출이 곧장 RAM 배열로 뛰어가는 모의 객체보다 더 긴 경로, 즉 주변장치 드라이버의 페이지 경계·주소 폭·재시도 처리를 거친다.

그림 1. AT24 읽기·쓰기 요청이 통과하는 네 층

요청은 왼쪽에서 오른쪽으로 이동하고, 결과·오류는 역방향으로 돌아옵니다. 좁은 화면에서는 좌우로 이동하세요.

① 앱·테스트eeprom_read() 또는 eeprom_write()로 공개 장치 API 호출실제 보드에서도 호출 형태 유지
② AT24 드라이버주소 폭·페이지 경계를 반영해 i2c_write_read() 또는 i2c_write() 수행시험 대상인 실제 주변장치 드라이버
③ I²C emul controller요청 주소에 맞는 child 에뮬레이터를 찾고 해당 transfer 호출대상 주소가 없으면 v3.7.0 코드에서 -EIO
④ AT24 대역메모리·상태를 소프트웨어로 관리하고 읽기 데이터 또는 오류 반환구현한 동작만 재현

근거: v3.7.0 AT24 드라이버의 I²C read/write 경로, v3.7.0 I²C 컨트롤러의 주소 검색·전달.

3. 같은 주변장치 노드에 두 역할이 붙는다

주변장치 드라이버와 해당 에뮬레이터는 같은 devicetree compatible을 대상으로 삼는다. 그러나 서로 같은 C 객체는 아니다. 드라이버 쪽은 DEVICE_DT_DEFINE() 계열로 struct device를, 에뮬레이터 쪽은 EMUL_DT_DEFINE() 계열로 struct emul을 정의한다. 에뮬레이터 매크로가 가리키는 노드에는 대응하는 device 정의가 있어야 한다. 에뮬레이션된 부모 버스는 활성 child 노드를 모아 초기화하고, 그 child의 에뮬레이터를 등록한다.

그림 2. devicetree 노드 하나를 두 종류의 객체가 해석한다

두 패널은 같은 child node에서 출발합니다. 가운데를 가로지르는 선은 C 포인터가 아니라 devicetree 정체성의 공유를 뜻합니다.

실제 주변장치 드라이버compatible = "atmel,at24"device 객체 + EEPROM API버스로 보낼 I²C 메시지 생성
주변장치 에뮬레이터같은 child node·DT_DRV_COMPATEMUL_DT_INST_DEFINE()으로 emul 객체bus API + 선택적 test backend API
부모 zephyr,i2c-emul-controller → child 주소 reg로 요청 전달
표 2. 선언과 실행 사이의 계약
계약v3.7.0에서 보는 위치어긋나면
부모 버스zephyr,i2c-emul-controller 노드와 I²C emul 드라이버예상한 child 요청이 실제 I²C 에뮬레이터에 닿지 않음
child nodecompatible, reg, status드라이버·에뮬레이터 인스턴스가 빠지거나 주소가 달라짐
device 객체DEVICE_DT_DEFINE() 계열EMUL_DT_DEFINE()의 필수 대상과 연결 실패
emul 객체EMUL_DT_DEFINE() 계열의 bus API·선택적 backend API버스 요청 전달 또는 테스트용 조작 경로가 없음

bus_api는 부모 버스가 주변장치 대역과 통신할 때 쓴다. _backend_api는 테스트가 장치의 상태를 버스 밖에서 조작할 수 있게 하는 선택적 API다. 예를 들어 충전기 연결 이벤트를 주입한 뒤 정상 BC1.2 드라이버 콜백을 검증할 수 있다. 모든 에뮬레이터가 그런 backend를 제공하는 것은 아니다.

근거: 동일 compatible·bus/backend API 계약, v3.7.0 emul 매크로, child 목록·초기화 소스.

4. 고정 버전의 AT24 예시를 좁게 재현한다

v3.7.0 공식 예시는 tests/drivers/eeprom/api에 at2x_emul.overlay와 at2x_emul.conf를 더해 native_sim 대상으로 빌드한다. overlay는 I²C 버스 child에 atmel,at24, 주소 0x57, 크기 256바이트, 페이지 8바이트, 주소 폭 8비트를 선언한다. 이 값은 에뮬레이터가 임의로 추측하는 장치 특성이 아니라 해당 테스트에서 설정한 계약이다.

west build -b native_sim tests/drivers/eeprom/api -- \ -DDTC_OVERLAY_FILE=at2x_emul.overlay \ -DOVERLAY_CONFIG=at2x_emul.conf

위 명령은 3.7.0 문서의 AT24 예시에 기초한다. 실행 파일의 PASS 결과를 여기서 주장하지 않는다. 최신 문서의 옵션명과 섞지 않도록 OVERLAY_CONFIG 표기를 고정했다.

표 3. overlay 값이 검증 질문으로 바뀌는 지점
overlay 값드라이버에서 관찰할 동작판정할 경계
reg = <0x57>I²C 전송 주소가 emul controller의 child 주소 검색과 맞는가다른 주소로 보내면 대상 에뮬레이터를 못 찾음
pagesize = <8>한 번의 write가 페이지 경계를 넘지 않도록 분할되는가연속 쓰기의 실제 메시지 개수를 추적
size = <256>끝 범위를 넘는 읽기·쓰기를 드라이버가 거절하는가버스 에뮬레이터에 도착하기 전에 막힐 수 있음
address-width = <8>오프셋 주소 바이트를 드라이버가 어떻게 만드는가다른 주소 폭의 물리 칩까지 검증한 것은 아님

근거: v3.7.0 테스트 overlay, AT24 드라이버의 크기·페이지·주소 처리.

5. 오류를 주입할 때 관찰 지점을 먼저 정한다

에뮬레이터의 힘은 성공 응답만 돌려주는 데 있지 않다. 지원하는 backend나 버스 mock hook으로 잘못된 상태·오류를 넣고, 앱이 보는 API 결과까지 확인할 수 있다. 다만 어느 API가 실제로 노출되는지는 에뮬레이터 구현마다 다르다. v3.7.0 I²C emul controller는 주소를 찾지 못하면 -EIO를 돌려주며, mock transfer가 -ENOSYS를 반환하면 기본 에뮬레이터 transfer로 넘어간다. 이 내부 경로와 AT24 자체의 범위 오류를 한 종류의 실패로 뭉뚱그리지 않는다.

그림 3. 주입 지점과 관찰 지점을 분리한 테스트

화살표는 하나의 가상 테스트 설계 순서입니다. 특정 AT24 backend API가 있다는 뜻은 아닙니다. 좁은 화면에서는 좌우로 이동하세요.

① 상태 준비해당 대역이 제공하는 backend 또는 mock hook을 선택없으면 임의의 주입 API를 가정하지 않음
② 공개 API 호출앱과 같은 EEPROM·sensor·BC1.2 API에서 시작드라이버 자체를 건너뛰지 않음
③ 실제 경로 통과드라이버의 버스 메시지와 emul controller 전달 확인고장 위치별 반환 코드를 구분
④ 결과 판정반환값·데이터·콜백과 복구 동작을 assert실물 전기·시간 특성은 별도 시험
표 4. 같은 실패처럼 보여도 확인 위치가 다르다
증상먼저 확인할 조건다음 관찰
에뮬레이터 객체 참조 링크 실패child status, Kconfig가 에뮬레이터 구현을 포함하는지빌드 설정과 EMUL_DT_GET() 대상 노드 비교
I²C 요청이 -EIO전송 주소와 child reg, 버스 속도와 메시지 순서컨트롤러의 주소 미발견과 AT24 에뮬레이터 콜백의 오류를 구분
읽기·쓰기 범위 오류AT24의 size와 요청 offset + length버스 호출 전에 드라이버가 거절했는지 확인
호스트에서만 PASS대역이 구현하지 않은 장치 동작 또는 버스 timing이 있는지실물 컨트롤러 API 테스트와 보드 결합 시험

근거: bus/backend API와 BC1.2 주입 예시, I²C 주소 미발견·mock fallback 코드, AT24 에뮬레이터의 속도·메시지 오류, AT24 범위 검사.

6. 통과의 범위를 적어야 이점이 남는다

가상 I²C 위에서 AT24 드라이버 테스트가 통과하면 그 설정에서 앱 API, AT24 드라이버, 에뮬레이션된 메시지 흐름이 맞았다는 근거가 된다. 실제 SoC I²C 컨트롤러 드라이버, 신호 무결성, 전원 차단, EEPROM의 write-cycle timing까지 검증됐다는 뜻은 아니다. 실물 컨트롤러는 물리 장치를 연결한 별도 API 테스트가 필요하다. 모델이 정확하지 않으면 호스트에서 초록색 결과를 반복해도 같은 오류를 놓칠 수 있으므로, 에뮬레이터가 구현한 레지스터·응답·오류 범위도 테스트 문서에 남겨야 한다.

v3.7.0 문서가 나열하는 버스 대역은 I²C, SPI, eSPI, MSPI다. 별도로 ADC·GPIO·플래시·EEPROM 같은 클래스 대역도 있다. 이 글의 I²C 경로를 그 모든 클래스에 똑같이 대입하지 않는다. 예를 들어 GPIO 에뮬레이터는 상위 GPIO API를 테스트하기 위한 다른 구성이다.

표 5. 결과 보고에 붙일 증거
주장필요한 증거이 글의 상태
AT24 드라이버 경로를 시험할 수 있다고정 문서의 예시와 v3.7.0 소스의 I²C 전달 구조소스 기반 구조 설명
특정 테스트가 PASS했다빌드 로그, 실행 플랫폼, 테스트 출력로컬 실행 증거 없음
실물 보드에서도 동작한다보드·칩·설정·환경을 기록한 측정별도 검증 필요

7. 고정 버전 근거와 이어 읽기

기준은 Zephyr 3.7.0 장치 에뮬레이터 문서와 같은 버전 외부 버스 문서다. 구현은 v3.7.0 태그의 I²C emul controller, AT24 드라이버, 테스트 overlay로 확인했다. 문서의 도식은 새로 그렸으며 코드 전문이나 원본 그림을 옮기지 않았다.