HSMS 세션은 SELECTED다. Linktest도 잘 오간다. 그런데 host가 처음 던진 S1F3에 돌아온 답이 S1F4가 아니라 S1F0이다. 본문 없이 헤더 10바이트만 온다. 설비 없이 코드만 짜는 입장이면 여기서 한나절이 날아간다.
설비가 고장 난 게 아니다. 층이 다른 두 상태를 하나로 본 것뿐이다. SELECTED는 SEMI E37이 정의하는 HSMS 연결 상태고, COMMUNICATING은 SEMI E30의 communication state model이 정의하는 GEM 통신 상태다. E37이 끝났다고 E30이 열리지 않는다. S1F13/S1F14가 COMMACK 0으로 끝나기 전까지 설비는 NOT COMMUNICATING이고, 그 창에서는 S1F13을 뺀 모든 primary message가 거절된다.
아래 frame은 전부 SECS/GEM 시뮬레이터의 passive listener(127.0.0.1:5501, SessionID 11)에 TCP client를 직접 붙여서 주고받은 것이다.
Select가 끝나면 E37은 거기서 끝이다
먼저 세션을 세운다.
TX Select.req 00 00 00 0A 00 0B 00 00 00 01 00 00 20 01
RX Select.rsp 00 00 00 0A 00 0B 00 00 00 02 00 00 20 01
SessionID 00 0B은 11이다. 바이트 5의 SType이 요청에서 01(Select.req), 응답에서 02(Select.rsp)다. 데이터 message라면 Function이 들어갈 헤더 바이트 3 자리에 Select.rsp는 Select Status를 싣는데, 여기가 00 — Select Status 0이니 수락이다(SEMI E37). 이제 HSMS 상태는 SELECTED다.
E37이 요구하는 건 여기까지다. 이 시점에 host 드라이버가 "연결 완료"라고 로그를 찍고 정상 운전 코드로 넘어가는 게 이 버그의 시작점이다.
S1F13 전에 보낸 primary는 S1F0으로 돌아온다
SELECTED가 되자마자 S1F3(Selected Equipment Status Request)을 던진다. SVID 하나를 담은 L[1]{U4 101}이다.
TX S1F3 W 00 00 00 12 00 0B 81 03 00 00 00 00 20 02 01 01 B1 04 00 00 00 65
| Bytes | Field | Value |
|---|---|---|
00 00 00 12 | Length | 18 — 헤더 10 + body 8 |
00 0B | SessionID | 11 |
81 | 헤더 바이트 2 | W-bit 1, Stream 1 |
03 | 헤더 바이트 3 | Function 3 |
00 00 | PType, SType | 0, 0 — 데이터 message |
00 00 20 02 | SystemBytes | 0x00002002 |
01 01 B1 04 00 00 00 65 | Body | L[1]{U4 101} |
돌아온 것:
RX S1F0 00 00 00 0A 00 0B 01 00 00 00 00 00 20 02
길이가 00 00 00 0A, 10이다. 헤더뿐이고 body가 없다. 바이트 2는 01 — W-bit가 꺼졌고 Stream은 여전히 1. 바이트 3은 00, 즉 Function 0이다. SystemBytes 00 00 20 02는 요청 값 그대로다.
SEMI E5에서 SxF0은 abort transaction이다. 응답은 요청이 쓴 stream을 그대로 쓰고 function만 0으로, body는 비운다. 그래서 이건 에러 리포트가 아니라 abort다. S9 message가 아니고, ERRCODE도 없다. 무엇이 틀렸는지 알려주는 필드가 message 안에 하나도 없다는 뜻이다. SxF0과 S9의 차이는 장비가 S9를 보내줄 거라 기대하고 호스트를 짜면 안 된다에서 따로 다뤘다.
abort된 요청은 설비 쪽 상태를 아무것도 바꾸지 않는다. abort 응답은 요청이 쓴 stream을 그대로 쓰니, 이렇게 답하는 설비라면 S1F13 전에 보낸 S2F41은 HCACK이 아니라 S2F0으로 돌아올 것이다. E5의 규칙에서 끈 추론이지 캡처가 아니다 — 이번에 S1F13 전에 던져본 primary는 S1F3 하나뿐이다.
S1F13/S1F14, 그리고 COMMACK 0
이제 게이트를 연다. Establish Communications Request다.
TX S1F13 W 00 00 00 17 00 0B 81 0D 00 00 00 00 20 03
01 02 41 04 48 4F 53 54 41 03 31 2E 30
body는 L[2]{A "HOST", A "1.0"}다. 41 04는 4바이트 ASCII item, 48 4F 53 54가 HOST다. 41 03 31 2E 30은 1.0. host의 MDLN과 SOFTREV 자리다. Host가 자기를 뭐라고 부르는지는 설비가 대개 신경 쓰지 않지만, 비워두면 파서가 까다로운 설비에서 걸린다.
RX S1F14 00 00 00 37 00 0B 01 0E 00 00 00 00 20 03
01 02
21 01 00
01 02 41 15 56 58 2D 39 30 30 30 20 50 6C 61 73 6D 61 20 45 74 63 68 65 72
41 0D 53 45 43 53 47 45 4D 2D 31 2E 34 2E 31
body는 L[2]{B 0, L[2]{MDLN, SOFTREV}}다. 21 01 00이 1바이트 Binary item, 값 0 — COMMACK 0, 수락이다. 이어지는 L[2]가 설비 쪽 MDLN VX-9000 Plasma Etcher와 SOFTREV SECSGEM-1.4.1이다. 이 순간 E30 상태가 COMMUNICATING으로 넘어간다.
COMMACK이 0이 아니면 게이트는 열리지 않는다. 그때 host가 할 일은 재시도지 다음 message가 아니다. 전체 기동 순서는 E30 기동 핸드셰이크를 명령 하나로 증명하기에 순서대로 있다.
S1F13은 설비가 먼저 보낼 수도 있다. E30은 어느 쪽이든 보낼 수 있게 두었으니, host도 들어온 S1F13에 S1F14를 답할 수 있어야 한다. 이번 캡처에는 그 방향이 없어서 여기까지만 적는다.
같은 바이트, 다른 답
2단계에서 거절당한 S1F3을 SystemBytes만 바꿔서 그대로 다시 보낸다.
TX S1F3 W 00 00 00 12 00 0B 81 03 00 00 00 00 20 04 01 01 B1 04 00 00 00 65
RX S1F4 00 00 00 31 00 0B 01 04 00 00 00 00 20 04
01 04
41 04 49 64 6C 65
81 08 40 38 3A E1 47 AE 14 7B
81 08 3F F1 F7 CE D9 16 87 2B
41 09 45 54 43 48 5F 42 41 53 45
요청 바이트는 20 02가 20 04로 바뀐 것 말고 step 2와 한 바이트도 다르지 않다. 같은 소켓, 같은 SessionID, 같은 바이트다. 이번엔 S1F0이 아니라 S1F4가 온다. 바뀐 건 E30 상태 하나뿐이다.
여기서 확인된 건 primary가 abort되지 않고 답을 받는다는 사실 하나다. body 내용은 그 이상을 말해주지 않는다. 이 simulator는 요청한 SVID와 무관하게 자기가 가진 변수 네 개를 전부 돌려주고, 내부적으로 변수를 이름으로 들고 있어서 요청에 실린 U4 101은 해석되지도 않았다. E5가 정한 S1F4의 모양 — 요청한 SVID마다 값 하나씩, 요청 순서대로 — 은 이 도구로 검증되지 않았다. 그러니 위 L[4]{A "Idle", F8 24.23, F8 1.123, A "ETCH_BASE"}를 SVID 대응표로 읽으면 안 된다.
마지막으로 세션을 접는다.
TX Separate.req 00 00 00 0A 00 0B 00 00 00 09 00 00 20 05
SType 09. 응답은 설계상 없다.
Host 드라이버에 넣을 것
- S1F13을 다른 어떤 primary보다 먼저 보낸다. Select.rsp를 받았다고 큐를 열지 말고, COMMACK 0을 받고 열어라.
- 증상 두 가지를 같은 원인으로 묶어라. (a) SxF0 abort가 즉시 돌아오는 경우, (b) 아무것도 안 오고 T3가 만료되는 경우. 둘 다 "통신이 아직 안 열렸다"로 처리하고, 재시도 경로는 S1F13이다.
- abort를 프로토콜 에러로 로그하지 마라. SxF0에는 진단 정보가 없다. SystemBytes와 그 시점의 E30 상태를 같이 남겨두지 않으면 나중에 원인을 못 가린다.
2번의 (a)는 짚어둘 게 있다. SxF0을 돌려주는 건 이 설비가 그렇게 하는 것이다. SEMI E30은 NOT COMMUNICATING 상태에서 abort 응답을 보내라고 요구하지 않는다. 실제 설비는 그냥 침묵하는 쪽이 흔하고, 그러면 host는 T3 타임아웃을 그대로 먹는다. 증상 하나만 보고 분기하는 코드는 반대쪽 설비에서 다시 깨진다. 침묵 쪽 증상은 SELECTED는 통신 중이라는 뜻이 아니다에, Linktest만 살아 있고 Select가 안 되는 더 앞 단계는 Linktest는 오가는데 SELECTED가 안 된다에 있다.
위 frame은 전부 실제로 소켓을 타고 오갔다. raw export는 content/demos/e30-communication-gate-s1f0-before-s1f13.json에 있고, 이 글의 실행은 SystemBytes 0x2001–0x2005 구간이다(파일에는 그 앞의 다른 실행도 같이 들어 있다). 같은 바이트를 직접 던져보려면 SECS/GEM 시뮬레이터에 소켓을 붙이면 된다.