You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dell XPS 13 9350 (Lunar Lake, OV02C10 센서) 웹캠 비작동 문제를 해결하기까지의 기록.
3/6~4/10, 약 1개월간 7+ 세션.
Phase 1: 증상 파악 (3/6)
Dell XPS 13 9350 (Lunar Lake)에서 웹캠 비작동. /dev/video0은 v4l2loopback 가상 디바이스일 뿐, 실제 카메라 없음. dmesg 에러 체인:
int3472-discrete: GPIO type 0x02 unknown
ov02c10: failed to find sensor: -6
intel_cvs: set_host_identifier cmd failed
Phase 2: 잘못된 가설 추적 (3/6 ~ 3/8 세션 5-6)
INT3472 GPIO type 0x02를 원인으로 보고, discrete.c에 OVTI02C1 + 0x02 → DOVDD 매핑을 추가하는 DKMS 패치(int3472-ov02c10-fix/1.0)를 작성. 빌드·설치까지 했으나 재부팅 후에도 동일 증상. DKMS 빌드/설치 불일치 문제도 겪음.
Phase 3: Root Cause 번복 (3/8 세션 7)
ACPI NVS 메모리를 직접 읽어 핵심 반전 발견:
GPIO type 0x02 unknown은 HIMX1092(다른 센서)용이지 OVTI02C1 무관
LCHS=1 (Connected Camera 모드)에서 OVTI02C1은 INT3472가 아닌 Intel CVS 경로로 전원 공급
진짜 원인: CVS 드라이버가 프로토콜 1.0 미지원 → SET_HOST_IDENTIFIER 무조건 호출 → -EIO → probe 실패 → 센서 전원 미공급
INT3472 DKMS 패치 폐기, 방향 전환.
Phase 4: 새 커널 대기 및 CVS 소스 분석 (3/16)
커널 6.17.0-1014 확인했으나 CVS 관련 수정 미포함. CVS 드라이버 소스 패치 작업으로 전환.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INT3472 컨트롤러가 GPIO를 통해 센서에 전원을 공급하고, 센서는 MIPI CSI-2 레인으로 ISP에 직접 연결된다. 드라이버 스택이 성숙하고 대부분의 Linux 배포판에서 잘 동작한다.
Lunar Lake의 Connected Camera (CVS) 모드
Intel Core Ultra 200V (Lunar Lake)는 Computer Vision Sensing (CVS) 이라는 새로운 카메라 서브시스템을 도입했다. AI 비전 처리를 위한 저전력 전용 컨트롤러가 추가된 구조다:
센서(OV02C10) ──MIPI CSI-2──→ IPU7 ISP ──→ Camera HAL ──→ 앱
↑ ↑
CVS 컨트롤러(INTC10DE) │
↑ 소유권 이전
USBIO 브리지(INTC10B5) (GPIO 핸드셰이크)
↑
USB I2C
CVS 모드에서는:
전원 경로가 다르다. 센서는 INT3472가 아닌 CVS 컨트롤러 경로로 전원을 받는다.
소유권 협상이 필요하다. CVS 컨트롤러가 먼저 센서를 초기화하고, I2C 프로토콜 협상을 마친 뒤, GPIO 핸드셰이크로 IPU7에 센서 소유권을 넘긴다.
프로토콜 버전 협상이 있다. CVS 컨트롤러와 센서 펌웨어 간에 매직 넘버(0xCAFEB0BA)로 프로토콜 버전(1.0 vs 2.0+)을 식별한다.
이 머신의 구성
ACPI 테이블을 직접 읽어서 확인한 이 머신의 카메라 구성:
ACPI 속성
값
의미
LCHS
1
Connected Camera 모드 활성
L1CL
0xFF
OVTI02C1은 INT3472에 의존하지 않음
LNK1._DEP
{CVSS, VIC1}
CVS 컨트롤러 + USB I2C 브리지에 의존
이 설정에서 OVTI02C1(OV02C10 센서)은 전원과 초기화를 전적으로 CVS 경로에 의존한다. CVS probe가 실패하면 센서에 전원이 공급되지 않고, 모든 것이 멈춘다.
2. 무엇이 실패했는가
증상
Intel CVS driver i2c-INTC10DE:00: magic number in dev response not supported
Intel CVS driver i2c-INTC10DE:00: cvs_find_magic_num_support:Device protocol is 1.0
Intel CVS driver i2c-INTC10DE:00: cvs_common_probe:set_host_identifier cmd failed
Intel CVS driver i2c-INTC10DE:00: probe with driver Intel CVS driver failed with error -5
ov02c10 i2c-OVTI02C1:00: failed to find sensor: -6
실패 체인
CVS probe 시작
→ cvs_find_magic_num_support(): "프로토콜 1.0" 확인 (magic_num_support = false)
→ cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0): 무조건 호출
→ 프로토콜 1.0 펌웨어가 이 명령을 이해하지 못함 → -EIO
→ CVS probe 실패
→ 센서 소유권 이전 안 됨
→ ov02c10 드라이버가 I2C로 센서 접근 시도 → 전원 없음 → -ENXIO
→ 웹캠 사용 불가
근본 원인은 한 줄이다: 프로토콜 1.0 장치에 프로토콜 2.0+ 전용 명령(SET_HOST_IDENTIFIER)을 무조건 보내는 것.
3. 왜 이런 버그가 생겼는가 — 업스트림 커밋 이력 추적
Intel vision-drivers 리포의 커밋 이력을 추적하면 버그가 만들어진 과정이 보인다:
- if (!icvs->i2c_shared) {- ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);- ...- }+ ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
"Remove LJCA WA | Code Cleanup" 커밋에서 코드 정리를 하면서 i2c_shared 조건 분기를 제거했다. 원래 i2c_shared는 완벽한 가드는 아니었지만, 일부 장치에서 SET_HOST_IDENTIFIER 호출을 건너뛸 수 있게 해줬다. 이 제거로 SET_HOST_IDENTIFIER가 모든 장치에서 무조건 실행되게 바뀌었다.
+ ret = cvs_find_magic_num_support(icvs);+ if (ret)+ goto exit;++ if (icvs->magic_num_support) {+ ret = cvs_get_device_cap(&icvs->cv_fw_capability);+ if (ret)+ goto exit;+ }+
ret = cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0); // ← 가드 없음!
"Fix I2C read for protocol 1.0 firmware"라는 제목으로 프로토콜 1.0 지원을 추가했다. cvs_find_magic_num_support() 함수를 만들어 프로토콜 버전을 감지하고, cvs_get_device_cap()은 magic_num_support 조건으로 감쌌다. 하지만 바로 아래의 SET_HOST_IDENTIFIER 호출은 같은 조건으로 감싸는 것을 빠뜨렸다.
같은 함수 안에서, 같은 패턴의 가드가 필요한 두 호출 중 하나만 수정한 전형적인 누락이다.
4. 왜 자체 패치가 필요했는가
Ubuntu 패키지의 한계
Ubuntu의 linux-modules-vision-*-oem 패키지는 Intel 업스트림의 특정 시점 스냅샷을 빌드한다. 현재 설치된 6.17.0-1017.17 패키지는 업스트림 HEAD(a8d772f, 2025-11-12)를 기반으로 하지만, 업스트림 자체에 버그가 있으므로 패키지를 아무리 업데이트해도 수정되지 않는다.
바이너리 역어셈블(objdump -d)로 두 모듈의 cvs_common_probe 함수를 비교하여 확인했다:
cvs_get_device_cap만 가드, SET_HOST_IDENTIFIER는 여전히 무조건 → 실패
Dell 출하 시점에 설치된 Ubuntu는 SET_HOST_IDENTIFIER가 추가되기 전(93244d7, 2024-10-22 이전)의 vision-drivers를 사용했거나, i2c_shared 가드가 있던 버전이었을 가능성이 높다. 이후 OEM 커널 업데이트로 새 스냅샷이 적용되면서 무조건 호출 버전이 들어온 것이다.
추정 2: 센서 펌웨어 업데이트
CVS 컨트롤러의 펌웨어가 업데이트되면서 프로토콜 협상 동작이 바뀌었을 가능성도 있다. 하지만 이 머신에서 CVS 펌웨어를 명시적으로 업데이트한 적이 없고, LVFS(Linux Vendor Firmware Service)를 통한 자동 업데이트도 확인되지 않았으므로 가능성은 낮다.
커널 업데이트로 모듈 로드 순서나 ACPI 의존성 해석이 미묘하게 바뀌면, 이전에는 CVS probe가 타이밍상 성공하던 것이 실패할 수도 있다. 다만 이 경우는 -EIO가 아닌 타임아웃이나 다른 에러가 나올 가능성이 높아, 직접적 원인으로 보기는 어렵다.
종합 판단
추정 1이 가장 유력하다. Ubuntu OEM 커널 패키지가 업스트림 vision-drivers의 새 스냅샷을 가져오면서, SET_HOST_IDENTIFIER 무조건 호출 코드가 포함된 것이 직접적 원인이다.
구체적으로는 69d20a1 (2025-03-12, "Remove LJCA WA | Code Cleanup")에서 i2c_shared 가드가 제거된 시점 이후의 스냅샷이 Ubuntu 패키지에 반영된 것이 breaking change다. 그리고 14d0181 (2025-06-04, "Fix I2C read for protocol 1.0 firmware")이 이를 수정하려 했으나 불완전했다.
6. 왜 이 문제가 흔하지 않은가
이 버그가 광범위하게 보고되지 않는 이유:
Lunar Lake 자체가 새로운 플랫폼이다. 2024년 하반기 출시로 Linux 사용자 기반이 아직 작다.
CVS 모드는 일부 구성에서만 활성화된다. 같은 Lunar Lake라도 LCHS=0 (전통 모드)이면 CVS 드라이버를 거치지 않는다.
프로토콜 1.0이 구형이다. 최신 CVS 펌웨어는 프로토콜 2.0+을 지원하므로 SET_HOST_IDENTIFIER가 성공한다. Dell XPS 13 9350의 OV02C10은 프로토콜 1.0 펌웨어를 사용하는, 상대적으로 드문 조합이다.
Windows에서는 드라이버 스택이 다르다. Intel은 Windows용 드라이버를 별도로 관리하므로, Linux 전용 업스트림 리포의 버그가 Windows에서는 나타나지 않는다.
7. 해결 방법
패치만으로는 부족하고 3가지를 모두 적용해야 동작한다.
1. CVS DKMS 패치
DKMS vision-driver/1.0.0으로 빌드하여 패키지 모듈을 오버라이드:
# 설치 스크립트~/command/web-cam/install-vision-dkms.sh
2. 권한 설정
# video 그룹 추가 (재부팅 필요)
sudo usermod -aG video $USER# IPU7 PSys 장치 권한 (udev 규칙)echo'SUBSYSTEM=="intel-ipu7-psys", MODE="0660", GROUP="video"' \
| sudo tee /etc/udev/rules.d/50-ipu7.rules
3. 캡처 (GStreamer icamerasrc)
IPU7는 libcamera pipeline handler가 없어 cam --list에 표시되지 않는다. Intel Camera HAL + GStreamer icamerasrc로 동작:
이 문서는 소스 코드 없이 두 커널 모듈(.ko)의 차이를 역어셈블로 비교하는 방법을 다룬다. 2026-04-10 세션에서 Ubuntu 패키지의 intel_cvs.ko와 DKMS 패치 버전을 비교할 때 사용한 기법을 정리했다.
1. 왜 바이너리 비교가 필요한가
커널 모듈 패키지는 대부분 소스를 포함하지 않는다. Ubuntu의 linux-modules-vision-*-oem 패키지는 Intel 업스트림의 특정 시점 스냅샷을 빌드한 결과물이다. 어떤 커밋까지 포함되었는지 changelog에 명시되지 않는 경우가 많아, "이 수정이 반영되었는가?"를 확인하려면 바이너리를 직접 들여다봐야 한다.
2. 도구와 기본 개념
2.1 핵심 도구
도구
용도
설치
zstd
.ko.zst 압축 해제
apt install zstd
objdump
ELF 바이너리 역어셈블
apt install binutils
nm
심볼 테이블 조회
binutils에 포함
modinfo
모듈 메타데이터 확인
kmod에 포함
strings
바이너리 내 문자열 추출
binutils에 포함
md5sum
바이너리 동일성 확인
coreutils에 포함
2.2 ELF과 .ko
커널 모듈(.ko)은 ELF(Executable and Linkable Format) relocatable object 파일이다. 일반 실행 파일과 달리:
재배치 가능(relocatable): 커널에 로드될 때 주소가 결정된다. 역어셈블에서 call 대상이 0x0000으로 보이는 건 빌드 시 주소가 확정되지 않았기 때문이다.
섹션 구조: .text(코드), .rodata(읽기 전용 데이터/문자열), .data(전역 변수), .symtab(심볼 테이블) 등으로 구성된다.
최근 Ubuntu OEM 커널은 모듈을 zstd로 압축하므로 먼저 해제가 필요하다.
2.3 x86-64 호출 규약 (System V AMD64 ABI)
Linux 커널은 System V AMD64 ABI를 따른다. 함수 호출 시 인자가 레지스터에 순서대로 들어간다:
인자 순서
레지스터
예시
1번째
rdi (32비트: edi)
cvs_write_i2c(cmd, buf, len)
2번째
rsi
cvs_write_i2c(cmd,buf, len)
3번째
rdx (32비트: edx)
cvs_write_i2c(cmd, buf,len)
4번째
rcx
5번째
r8
6번째
r9
반환값
rax
ret = cvs_write_i2c(...)
따라서 mov edi, 0x805 다음에 call이 오면, 첫 번째 인자로 0x805를 넘기는 함수 호출이다. 소스에서 SET_HOST_IDENTIFIER = 0x0805를 알고 있으면 이 패턴으로 해당 호출을 찾을 수 있다.
2.4 자주 보이는 x86-64 명령어
역어셈블 출력을 읽을 때 자주 등장하는 명령어:
명령어
의미
예시
mov
값 복사
mov edi, 0x805 → edi에 0x805 저장
movzx
작은 값을 0 확장하여 복사
movzx eax, BYTE PTR [r12+0x4c] → 1바이트 읽어서 32비트로
test
비트 AND 후 플래그만 설정 (값 안 바뀜)
test eax, eax → eax가 0인지 검사
cmp
뺄셈 후 플래그만 설정
cmp al, 0x1 → al과 1 비교
je / jne
조건 점프 (equal / not equal)
je 12c7 → 같으면 0x12c7로 점프
ja / jle
조건 점프 (above / less-or-equal)
ja 153d → 크면 0x153d로
call
함수 호출
call 1284
xor reg, reg
레지스터를 0으로 초기화
xor edx, edx → edx = 0
push / pop
스택 저장/복원
함수 프롤로그/에필로그
lea
주소 계산 (실제 메모리 접근 안 함)
lea rdi, [r12+0x4d] → 포인터 전달
2.5 C 코드와 어셈블리 대응
아래는 이번 분석에서 실제로 나타난 패턴이다.
C 코드:
if (icvs->magic_num_support) { // [r12+0x4c]가 magic_num_support 필드ret=cvs_write_i2c(SET_HOST_IDENTIFIER, NULL, 0);
}
가드가 있는 어셈블리 (DKMS 버전):
movzxeax, BYTE PTR [r12+0x4c] ; icvs->magic_num_support 로드 (1바이트)andebx,0x1 ; bool 변환testbl,bl ; 0인지 검사je 12c7 ; 0이면 → SET_HOST_IDENTIFIER 건너뜀xoredx,edx ; 3번째 인자: len = 0xoresi,esi ; 2번째 인자: buf = NULLmovedi,0x805 ; 1번째 인자: SET_HOST_IDENTIFIERcall cvs_write_i2c
가드가 없는 어셈블리 (패키지 버전):
; (magic_num_support 체크가 위에서 cvs_get_device_cap만 가드); je로 여기까지 점프해 옴 — SET_HOST_IDENTIFIER 앞에 추가 분기 없음xoredx,edx ; len = 0xoresi,esi ; buf = NULLmovedi,0x805 ; SET_HOST_IDENTIFIERcall cvs_write_i2c ; 무조건 실행됨
핵심 차이: mov edi, 0x805 직전에 test + je(조건 점프)가 있느냐 없느냐.
3. 실전 절차
3.1 모듈 추출 및 기본 비교
# 압축 해제
zstd -d -c /lib/modules/$(uname -r)/ubuntu/vision/intel_cvs.ko.zst > /tmp/pkg.ko
zstd -d -c /lib/modules/$(uname -r)/updates/dkms/intel_cvs.ko.zst > /tmp/dkms.ko
# 동일성 확인
md5sum /tmp/pkg.ko /tmp/dkms.ko
# → 체크섬이 다르면 바이너리가 다른 것# 메타데이터 확인
modinfo /tmp/pkg.ko
modinfo /tmp/dkms.ko
# 문자열 추출로 빠르게 힌트 얻기
strings /tmp/pkg.ko | grep -i "magic_num\|set_host"