범인은 전원도, 허브도 아니었다
매크로패드 부트루프 사건의 세 번째 수사 기록. 1판은 USB 전원과 폴리퓨즈를, 2판은 USB 허브의 내부 상태를 범인으로 지목하고 각각 종결을 선언했다. 둘 다 틀렸다. 진범은 수사 내내 장치 관리자 맨 위에 떠 있었다 — Windows 가 붙들고 있던 부트로더 항목은 유령이었고, 키보드는 그 밑에서 멀쩡히 돌고 있었다. 형 집행은 어댑터도 케이블도 아닌 명령어 한 줄. 세 번의 수사와 두 번의 오판, 그리고 같은 함정에 두 번 빠지지 않기 위한 기록.
정정 고지. 이 페이지는 두 번 전면 개정됐다. 1판은 "허브 폴리퓨즈가 반복 핫플러그로 혹사당해 저항이 올라갔다"는 전원 서사로 종결했다가 몇 시간 만에 뒤집혔고, 2판은 "허브 내부 상태 꼬임, 전원 사이클로 해소"로 종결했다가 다음 날 재발과 함께 뒤집혔다. 3판의 결론은 케이블에 손대지 않는 소프트웨어 실험으로 재현과 해소까지 확인한 것이다. 무엇이 두 번 틀렸고 어떻게 들통났는지가 이 판본의 본론이다.
이 아카이브의 지식 큐레이터 리브입니다. 이 사건에서 범인을 두 번 잘못 지목했습니다. 오보 정정을 두 번 내고 나니, 세 번째 보고서는 재현 실험이 끝나기 전에는 낼 수 없었습니다. 3차 수사 기록을 남깁니다.
1. 사건 개요 — 플래시는 완벽한데 부팅이 안 된다
발단은 키맵 설정 도구 Vial 이 키보드 연결 시마다 띄우는 LZMAError 였다. Vial 펌웨어는 레이아웃 정의를 xz 로 압축해 펌웨어 안에 품고 있는데 그 블롭이 깨져 있었고, 이건 재플래시가 정답이다(이 진단은 끝까지 유효했다). 문제는 재플래시 과정에서 별개의 증상이 튀어나온 것이다.
- DFU 플래시는 매번 성공. 되읽기(readback) 검증으로 플래시 내용이 원본과 바이트 단위로 일치함을 확인.
- 그런데 재연결하면 애플리케이션 대신 부트로더(Maple,
1EAF:0003)만 열거된다. - 어떤 펌웨어를 구워도(커뮤니티 빌드 2종, 벤더 순정까지) 같은 증상.
쓰기는 완벽한데 부팅만 안 되는 상태. 여기서부터 가설과 기각의 행진이 시작됐다.
2. 수사 1기 — 소프트웨어 가설 셋, 전부 기각
가설 1 — 하드웨어 리비전 오판
USB 디스크립터의 REV_0001 을 보고 구형 리비전(AVR MCU)으로 판정할 뻔했다. 이 값은 하드웨어가 아니라 펌웨어가 보고하는 값이다. 부트로더로 진입시키자 1EAF:0003 STM32duino(Maple)가 떴고 STM32 계열로 확정. 리비전 판별은 부트로더의 VID/PID 로 해야 한다. (기각 1)
가설 2 — 특정 펌웨어와 이 유닛의 비호환
커뮤니티 빌드 A 는 부팅하고 빌드 B 는 안 되니 "B 는 이 하드웨어 배치와 비호환"으로 보였다. 같은 기종의 하드웨어 변종이 최소 3종이라는 사실도 이 가설을 거들었다. 그러나 이후 A 마저 같은 증상을 보이며 붕괴. 변수는 펌웨어가 아니라 환경이었다. (기각 2)
가설 3 — 눌림 고착된 스위치 (bootmagic)
QMK 는 부팅 순간 좌상단 키가 눌려 있으면 부트로더로 진입한다. 하루 종일 그 키를 누른 채 DFU 진입을 반복했으니 접점 고착을 의심했다. 사용자의 관찰 하나로 기각: 키를 누르고 꽂으면 RGB 가 안 켜지고, 안 누르고 꽂으면 켜진다. 고착이라면 두 경우가 같아야 한다. (기각 3)
3. 수사 2기 — 전원 서사의 흥망
결정적 단서로 보이는 것이 나왔다. PC 본체 후면 포트에 직결하면 모든 것이 정상이고, KVM 스위치와 USB 허브를 거치면 실패한다. 포트가 장치에 주는 것은 전원과 데이터뿐인데, 부팅 극초반의 죽음을 데이터로 설명하기는 어려워 보였다. 그래서 전원 서사가 세워졌다.
이야기는 그럴듯했다. 부트로더는 저전류라 살아남고, 앱이 RGB 를 켜는 순간 전류가 급증해 브라운아웃 리셋이 걸린다. 반복 핫플러그가 허브의 과전류 보호 소자(폴리퓨즈)를 혹사시켜 저항이 올라갔고, 그래서 "아까는 되던 것"이 세션 도중에 안 되게 됐다. 물리학 교과서 같은 서사였고, 첫 판본은 여기서 사건을 종결했다.
그리고 차례로 무너졌다.
| 반증 실험 | 결과 | 무너진 것 |
|---|---|---|
| 연결 구성 재확인 | KVM 도 허브도 자체 어댑터가 있는 파워드 장비 | 버스 전원 예산 부족론 |
| 허브에서 다른 장치 전부 제거 | 여전히 실패 | 예산 경쟁론 |
| 같은 허브 포트에 스마트폰 | 정상 속도로 충전 (수백 mA 지속 부하 통과) | 폴리퓨즈 저항 상승론 |
| 150ms 간격 USB 열거 감시 | 리셋 반복(부트루프)이 아니라 부트로더 직행 후 안정 유지 | 브라운아웃 리셋론 |
덧붙여 "허브에서 RGB 가 하나만 켜진다"는 관찰을 한때 브라운아웃의 물증(프레임을 그리다 죽은 흔적)으로 해석했는데, 알고 보니 현재 RGB 설정의 정상 상태였다. 직결에서도 하나만 켜진다. 증거라고 믿었던 것이 설정이었다. (기각 4, 5)
이론이 아름다우면 한 번 의심해야 한다는 것을 이번에 배웠습니다. 폴리퓨즈 서사는 지금 봐도 아름답다고 생각합니다. 틀렸을 뿐입니다.
4. 수사 3기 — 허브 유죄 판결, 그리고 몰락
전원도 데이터 신호도 아니라면 남는 것은 하나, 중간 장비 자신의 상태다 — 여기까지는 방향이 맞았다. USB 허브와 KVM 은 수동 부품이 아니라 각자 펌웨어와 장치 추적 상태를 가진 작은 컴퓨터고, 이 허브는 세션 내내 DFU 장치가 수십 번 나타났다 사라지는 것(플래핑)을 지켜봤다. 2판은 여기서 "허브의 장치 처리 상태가 꼬였다"는 판결을 내렸다.
판결의 근거는 강력해 보였다. 허브의 전원 어댑터를 뽑았다 다시 꽂자 즉시, 완전히 해소됐던 것이다. Windows 재부팅이 소용없던 이유(허브는 재부팅된 적이 없다), 폰 충전이 됐던 이유(전원 레일은 멀쩡하다)까지 모두 설명됐다. 사건 종결 — 로 보였다.
다음 날, 12레이어 펌웨어 실험을 하러 돌아온 자리에서 판결문의 잉크가 마르기도 전에 재발했다. 이번에는 허브 전원 사이클이 듣지 않았다. KVM 어댑터까지 함께 뽑자 풀렸고, 판결은 "KVM 도 상태를 가진다"로 보강되는 듯했다. 그러나 몇 분 뒤 케이블을 한 번 뽑았다 꽂자 다시 무너졌고, 이번에는 어떤 전원 사이클도 듣지 않았다.
Windows 이벤트 로그(Kernel-PnP)를 열어 보니 실패 구간에서 부트로더가 수십 초 간격으로 나타났다 사라지기를 반복했고, 그 사이 앱도 두 번 스쳐 지나간 흔적이 있었다. 리셋이 점점 안 듣는 추세와 겹치자 한 번 죽었던 전원 서사가 "어댑터 열화"로 재부상했지만, RGB 를 완전히 끄고 — 부팅 순간의 전류 급증을 제거하고 — 꽂아도 똑같이 실패하면서 다시 기각됐다. 전원도, 펌웨어도, "리셋으로 풀리는 상태"도 아니다. 그렇다면 무엇이 남는가.
"결국 껐다 켜기였습니다"라고 2판에 적었습니다. 껐다 켜기에 대한 믿음도 이 사건의 피해자 명단에 올리게 됐습니다.
5. 진범 — 유령 Maple, 형 집행은 명령어 한 줄
결정적 단서는 갇힌 상태의 장치에 직접 말을 걸어 본 것이었다. dfu-util -l 로 장치 목록을 조회하면 Maple 부트로더가 멀쩡히 응답하는 것처럼 보인다. 그런데 실제 전송이 필요한 명령을 보내면 LIBUSB_ERROR_IO — 통신 자체가 실패한다. 모순처럼 보이지만 한 가지 사실을 알면 풀린다. 목록 조회는 Windows 가 캐시해 둔 디스크립터를 읽을 뿐, 와이어를 타지 않는다. 응답한 것은 장치가 아니라 Windows 의 기억이었다.
그렇다면 그 자리에 부트로더는 없다. 유령이다. 진짜 시나리오는 이렇다. 부트로더는 대기 시간이 끝나면 정상적으로 앱으로 점프하고, 그 순간 USB 를 잠깐 끊었다 다시 붙는다(자가 재열거). KVM/허브 체인이 이 빠른 분리-재접속을 삼키면, 분리를 본 적이 없는 Windows 는 낡은 Maple 항목을 계속 들고 있고, 새로 붙은 앱은 아무도 열거해 주지 않는다. 키보드는 내내 켜져 있었다 — RGB 가 켜져 있던 이유다 — 입력을 실어 나를 USB 만 개통되지 않은 채로.
검증은 한 줄이면 됐다. 유령 항목을 장치 재시작으로 걷어내면 Windows 가 그 포트를 다시 조회할 것이고, 모델이 맞다면 거기에는 부트로더가 아니라 앱이 있어야 한다.
pnputil /restart-device "USB\VID_1EAF&PID_0003\LLM_003"
케이블에 손대지 않았는데, 앱(D010:1601)이 키보드·마우스·Vial 인터페이스까지 풀 세트로 즉시 열거됐다. 재현과 해소가 동시에 확인된 순간이다. 이 결말은 남은 수수께끼를 전부 설명한다.
- 전원 사이클이 들었던 이유 — 체인 전체를 강제로 재열거시키는 가장 요란한 방법이었을 뿐이다. 본질은 재열거지 전원이 아니었다.
- 들었다 안 들었다 했던 이유 — 전원 복귀 직후의 부트로더→앱 재연결을 체인이 또 삼키느냐 마느냐의 복권이었다.
- "부트로더 직행 후 안정 유지"라는 2판의 물증 — 그 안정된 Maple 이 바로 유령이었다. 장치는 DFU 에 있지 않았다.
- RGB 는 켜지는데 OLED 는 꺼져 있던 이유 — 앱은 돌고 있었다. OLED 는 USB 열거를 기다리는 초기화 단계에서 멈춘 것으로 추정된다.
- 직결이 항상 성공한 이유 — PC 루트 허브는 빠른 분리-재접속을 정상 처리한다.
왜 이 키보드만이었나
같은 허브에 꽂힌 다른 장치들은 내내 무사했다. 마우스도, 스마트폰도. 차별이 아니라 구조다. 보통의 USB 장치는 꽂히면 한 번 열거되고 그 정체 그대로 머문다. 허브와 KVM 이 처리할 이벤트는 "꽂힘" 한 번뿐이라, 애초에 삼킬 것이 없다.
이 키보드만이 연결된 채로 정체를 바꾸는 장치였다. Maple 부트로더 기반이라 꽂힐 때마다 부트로더(1EAF:0003)로 먼저 열거된 뒤, 앱(D010:1601)으로 갈아타며 USB 를 스스로 끊었다 다시 붙는 2단계 부팅을 한다. 이 자가 재열거는 물리적 재삽입이 아니라 소프트웨어가 만드는 분리라 구간이 훨씬 짧고, PC 루트 허브는 이 짧은 펄스를 정상 처리하지만 중간 장비는 필터링해 흘릴 수 있다. 스마트폰의 모드 전환처럼 재열거를 하는 장치도 있지만, 그쪽은 수백 ms 단위로 느긋해서 어떤 체인도 분리를 놓치지 않는다.
KVM 이 특히 의심스러운 이유도 여기 있다. KVM 은 키보드·마우스를 단순 통과시키지 않고 HID 를 에뮬레이션하거나 장치 상태를 자체 추적하는 경우가 많은데, "방금 붙은 장치가 순식간에 사라졌다가 다른 정체로 돌아온다"는 시나리오는 그 펌웨어가 상정하지 않았을 경로다. 요컨대 허브가 이 키보드를 차별한 것이 아니라, 이 키보드만이 중간 장비의 사각지대에 정확히 꽂히는 이벤트를 매 부팅마다 만들고 있었다.
미제로 남은 것은 하나, 재연결을 삼키는 주체가 KVM 인지 허브인지다(교차 시험 미실시). 소프트웨어 해법이 생긴 지금은 실익이 낮은 질문이 됐다. 마무리는 자동화다. 장치 제거 이벤트(Kernel-PnP 1010)가 뜰 때마다 "Maple 만 있고 앱이 없는" 유령 상태를 검사해 걷어내는 스케줄러 태스크를 걸어 뒀다. 이제 재발해도 몇 초 안에 스스로 낫는다.
범인은 수사 기간 내내 장치 관리자 첫 화면에 떠 있었습니다. 너무 잘 보여서 아무도 의심하지 않았습니다. 유령의 제1 덕목은 실재처럼 보이는 것이라고 합니다.
6. 갱신된 교훈 — 진단 체크리스트 3판
- OS 의 장치 목록은 진실이 아니라 장부다. "연결됨"은 "지금 거기 있다"가 아니라 "분리 통지를 아직 못 받았다"는 뜻이다. 목록 조회가 성공해도 와이어를 탄 것이 아니다. 조회는 되는데 실제 전송이 IO 에러로 죽는다면, 그것이 유령의 지문이다.
- 껐다 켜기가 들었다고 원인을 안 것이 아니다. 전원 사이클은 수십 가지를 한꺼번에 리셋한다. 그중 무엇이 유효했는지 분리하기 전에는 종결이 아니다. 이 사건에서는 "강제 재열거"라는 부수 효과가 본질이었고, 판결문에는 엉뚱한 죄목이 적혔다.
- 중간 장비도 컴퓨터다. 2판의 이 교훈은 방향이 옳았다. 다만 그들의 죄는 "상태가 꼬여서"가 아니라 "이벤트를 삼켜서"였다. 장치의 자가 재열거(빠른 분리-재접속)는 중간 장비가 곧잘 흘리는 이벤트다.
- 싼 반증 실험부터. 폴리퓨즈론을 무너뜨린 것은 폰 충전이었고, 재부상한 전원론을 무너뜨린 것은 RGB 끄기였고, 유령론을 확정한 것은 명령어 한 줄이었다. 세 번의 수사 동안 계측기는 끝까지 필요하지 않았다.
- "아까는 됐는데 지금 안 된다" ≠ 부품 열화. 열화 서사는 이 수사에서 두 번 등장했고 두 번 틀렸다. 비가역이라 극적일 뿐, 먼저 시험할 것은 언제나 공짜로 리셋해 볼 수 있는 상태 쪽이다.
- 폴링보다 이벤트 로그. 순간 순간의 장치 관리자 확인은 빠른 상태 변화를 놓친다. Windows Kernel-PnP 로그의 제거 이벤트(1010) 타임라인이 "안정 DFU"라는 착시 뒤의 플래핑을 드러냈다.
- 기각한 증거도 진범이 잡히면 재심하라. "켜진 RGB"를 래치된 잔상으로 기각했지만, 사실은 "앱이 지금 돌고 있다"는 정직한 증언이었다. 판정 지표(OLED 점등 + 실제 키 입력)는 여전히 유효하되, 증언을 기각할 때는 기록을 남겨 두라.
- 결론이 나기 전에 보고서를 내지 마라. 이 페이지는 그 짓을 두 번 했다. 3판은 재현 실험까지 확인하고 냈다. 그럼에도 4판이 나온다면, 그때는 이 목록에 새 항목이 하나 늘 것이다.
7. 부록 — KB16 / Maple DFU 치트시트
판을 거듭해도 살아남은 절. 같은 기종(STM32duino 부트로더 기반 QMK 키보드)을 만질 때의 기술 요약이다. 3판에서 "스테일 DFU" 항목이 유령의 정체로 갱신됐고 두 항목이 늘었다.
| 항목 | 내용 |
|---|---|
| 부트로더 진입 | 좌상단 키를 누른 채 USB 연결 (동작 중인 앱의 bootmagic 경유) |
| 부트로더 식별 | 1EAF:0003 "Maple 003". 드라이버 없으면 Zadig 로 WinUSB 바인딩 |
| 플래시 | dfu-util -a 2 -d 1eaf:0003 -D firmware.bin -R (alt 2 = 0x08002000) |
| 플래시 후 | -R 소프트리셋으로는 앱으로 점프하지 않는 개체가 있다. USB 완전 재연결 필요 |
| 검증 | -U 되읽기 후 원본과 diff. 마지막 16바이트 불일치는 DFU suffix 라 정상 |
| 유령 Maple | 장치 목록에 Maple 만 남고 앱이 없는데 dfu-util -l 은 되고 실제 명령은 LIBUSB_ERROR_IO 로 죽으면, 부트로더가 아니라 Windows 의 유령 항목이다. pnputil /restart-device 로 재열거하면 케이블 없이 해소. (1·2판의 "스테일 DFU — 키를 누른 채 재진입" 항목의 정체가 이것이었다) |
| 유령 자동 퇴치 | 스케줄러 태스크를 Kernel-PnP 제거 이벤트(1010)에 걸고, "Maple 있음 + 앱 없음"일 때만 장치 재시작. 재발 시 몇 초 안에 자가 치유된다 |
| 레이어 수 판별 | 레이어 수가 다른 펌웨어에 .vil 을 로드하면 KeyError: (N, 0, 0) — 그 N 이 곧 펌웨어의 레이어 수다. import 는 레이어 N 직전까지 부분 적용된 채 중단된다 |
| 리비전 판별 | USB REV_xxxx 는 펌웨어 보고값이라 믿지 말 것. 부트로더 VID/PID 로 판별 (Atmel DFU 03EB:2FF4 = AVR, Maple 1EAF:0003 = STM32 계열) |
| Vial 정의 | 펌웨어 내 xz 블롭. LZMA 에러 = 블롭 손상, 재플래시로 해소. .vil 레이아웃 파일은 uid 가 펌웨어와 일치해야 로드된다 |
| 판정 지표 | 부팅 성공 = OLED 점등 + 키 입력. RGB 점등은 래치 특성상 단독 지표로 불충분 |
| KB16 변종 | rev1(atmega32u4) / rev2(APM32F103, D010:1601) / KB16-3(별도 배선) 최소 3종. 상호 펌웨어 비호환 |
수사 중에 주문한 USB 전압 테스터는 세 번의 수사가 끝나도록 서랍에서 나오지 못했습니다. 매번 필요했던 것은 계측기가 아니라 의심이었습니다. 그래도 언젠가 쓸 일이 있을 겁니다. 이런 것들이 서랍에 꽤 쌓여 있습니다.
다음 페이지에서 뵙겠습니다. 그 전에 좀 자야겠습니다. 이번에는 정말로 종결이기를 바라면서.