안 붙는 줄 알았던 PLC 모뎀 — QCA7005 호스트 부팅_AI작성글
SC6_0 보드에는 차량과 통신하기 위한 PLC 모뎀 QCA7005가 붙어 있다. 충전 케이블의 CP 라인 위로 HomePlug Green PHY 신호를 실어서 ISO 15118 / DIN 70121 통신을 하는 칩이다. MPU와는 SPI로 연결되고, 리눅스에서는 qcaspi 드라이버가 이 칩을 이더넷 인터페이스로 보여준다.
며칠 전 드라이버를 모듈로 빌드해서 올렸는데, 결과가 이상해 보였다.
root@myir:~# lsmod | grep qca
qcaspi 24576 0
qca_7k_common 16384 1 qcaspi
사용 카운트 0. SPI 핀도 analog로 나왔다. “드라이버는 올라왔는데 디바이스가 안 붙었다”고 판단하고 PLC 건을 막힌 항목으로 적어뒀다.
이 판단부터 틀렸다.
붙어 있었다
부트로더 핀 문제를 정리하고 돌아와서 제대로 확인했다.
root@myir:~# ls /sys/bus/spi/devices/
spi0.0
root@myir:~# dmesg | grep -iE "spi|qca"
[ 14.448930] spi_stm32 44004000.spi: driver initialized
[ 17.007248] qcaspi spi0.0: ver=0.2.7-i, clkspeed=8000000, burst_len=5000, pluggable=0
[ 17.037554] qcaspi spi0.0: Using random MAC address: 62:d3:bd:18:1a:f3
[ 20.758514] qcaspi spi0.0 eth1: SPI thread created
eth1까지 만들어져 있었다. 처음부터 붙어 있었던 거다.
오진의 원인은 두 가지였다.
| 본 것 | 내가 한 해석 | 실제 의미 |
|---|---|---|
lsmod 사용 카운트 0 |
디바이스가 안 붙음 | 다른 모듈이 이 모듈을 참조하는 수. 네트워크 드라이버는 디바이스가 붙어 있어도 0인 게 보통이다 |
SPI 핀 analog |
SPI가 동작 안 함 | SPI가 쉬는 동안 runtime PM이 핀을 sleep 상태로 내려둔 것. 정상 |
숫자 하나를 보고 결론 내기 전에, 그 숫자가 무엇을 세는지부터 확인했어야 했다. 지난번
gpioinfo의input때와 같은 실수다.
붙었다는 것 이상을 증명하는 한 줄
dmesg의 pluggable=0이 사실 더 많은 걸 말해주고 있었다. 드라이버 소스를 보면 이 모드에서는 probe할 때 칩의 시그니처 레지스터를 읽는다.
/* drivers/net/ethernet/qualcomm/qca_spi.c */
if (!qcaspi_pluggable) {
qcaspi_read_register(qca, SPI_REG_SIGNATURE, &signature);
qcaspi_read_register(qca, SPI_REG_SIGNATURE, &signature);
if (signature != QCASPI_GOOD_SIGNATURE) { /* 0xAA55 */
dev_err(&spi->dev, "Invalid signature (0x%04X)\n", signature);
free_netdev(qcaspi_devs);
return -EFAULT;
}
}
if (register_netdev(qcaspi_devs)) { ... } /* 여기까지 왔으니 eth1이 생겼다 */
eth1이 생겼다는 건 칩에서 0xAA55를 제대로 읽었다는 뜻이다. 이걸로 보류해뒀던 항목 네 개가 한 번에 닫혔다.
- SPI 배선 (SCK/MOSI/MISO/CS)
- SPI 모드 — DTS의
spi-cpol; spi-cpha;(mode 3) - 8MHz 클럭
- 리셋 핀(Q_RST) 극성 — 칩이 리셋 상태였으면 아무것도 읽히지 않았을 것이다
인터럽트까지 확인
시그니처 읽기는 폴링이라 인터럽트 없이도 된다. 실제 데이터 송수신은 칩이 보내는 인터럽트(PG7)가 살아 있어야 한다. 드라이버가 debugfs에 상태를 내놓는다.
root@myir:~# cat /sys/kernel/debug/eth1/info
Sync state : 2 (QCASPI_SYNC_READY)
IRQ : 120
INTR REQ : 982
INTR SVC : 982
SPI mode : 3
root@myir:~# grep eth1 /proc/interrupts
120: 981 0 stm32gpio 7 Edge eth1
root@myir:~# grep "(PG7)" /sys/kernel/debug/pinctrl/*50002000*/pinconf-pins
pin 103 (PG7): input - low - floating
인터럽트가 들어오고, 들어온 만큼 처리되고 있다.
숫자가 하나 차이 나서(982 vs 981) 잠깐 멈칫했는데 소스에 답이 있었다. 인터페이스를 올릴 때 드라이버가 초기 동기화를 시작하려고 가짜 요청 하나를 넣고 시작한다.
qca->intr_req = 1;
qca->intr_svc = 0;
하드웨어 인터럽트 981 + 1 = 982. 정확히 맞다.
PG7이 input인 것도 반가웠다. 이 핀은 벤더 LTDC 핀맵에서 LCD_CLK였던 핀이라, 지난번 U-Boot 수정 전이었다면 부팅 구간에 클럭이 새어 나갔을 수도 있다.
그런데 칩이 부트로더였다
SPI도, 인터럽트도 된다. 그럼 칩 펌웨어는 제대로 돌고 있을까.
보드에 PLC 관리 도구가 없어서, 파이썬으로 직접 물어봤다. HomePlug 장치는 관리 메시지(MME) 라는 이더넷 프레임으로 제어한다.
목적지 MAC 00:B0:52:00:00:01 "로컬 Qualcomm 칩" 전용 주소
Ethertype 0x88E1 HomePlug AV 관리 메시지
MMTYPE 0xA000 VS_SW_VER.REQ (버전 요청), 응답은 0xA001
import socket, struct
IFACE = "eth1"
ETH_P_HPAV = 0x88E1
LOCAL_DEV = bytes.fromhex("00b052000001")
OUI = bytes.fromhex("00b052")
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_HPAV))
s.bind((IFACE, 0))
src = s.getsockname()[4]
req = LOCAL_DEV + src + struct.pack("!H", ETH_P_HPAV)
req += bytes([0x00]) + struct.pack("<H", 0xA000) + OUI
s.send(req.ljust(60, b"\x00"))
s.settimeout(2.0)
d = s.recv(1518)
vlen = d[22]
print(d[23:23+vlen].split(b"\x00")[0].decode())
응답이 왔다.
version : BootLoader
PLC 펌웨어가 아니라 부트로더가 대답했다. 통신은 완벽한데, 칩이 모뎀으로 동작할 준비가 안 된 상태였다.
넷리스트가 답을 줬다
QCA7005에는 SPI가 두 벌 있다. 하나는 MPU와 연결되는 호스트 인터페이스, 다른 하나는 펌웨어를 저장하는 외부 플래시용이다. 넷리스트에서 플래시 쪽 핀을 찾아봤다.
<net name="unconnected-(U8-SPI_CLK-Pad67)"> ... pintype="passive+no_connect"
<net name="unconnected-(U8-SPI_CS-Pad65)"> ... pintype="passive+no_connect"
<net name="unconnected-(U8-SPI_DI-Pad3)"> ... pintype="passive+no_connect"
<net name="unconnected-(U8-SPI_DO-Pad66)"> ... pintype="passive+no_connect"
| 핀 | 용도 | SC6_0 |
|---|---|---|
| SIO0~SIO4 | 호스트 인터페이스 (MPU와 통신) | 연결됨 |
| SPI_CLK/CS/DI/DO | 플래시 인터페이스 (펌웨어 저장) | 전부 미연결 |
부품 목록에도 플래시 칩이 없었다. 처음부터 플래시 없이 설계된 보드였다.
이런 구조를 호스트 부팅(host boot) 이라고 한다. 칩은 전원이 들어오면 부트로더만 띄워놓고, MPU가 SPI로 펌웨어를 내려주기를 기다린다. 펌웨어 버전을 MPU 쪽에서 관리할 수 있고 플래시 부품값도 빠진다.
고장이 아니라 설계였다. 할 일이 “고치기”에서 “펌웨어 내려주기”로 바뀌었다.
도구 크로스 빌드 — 이번엔 쉬웠다
Qualcomm이 공개한 open-plc-utils에 펌웨어를 내려주는 도구(plcboot)가 있다. ARM용으로 빌드해야 한다.
지난번 U-Boot 빌드에서 에러 네 개를 연달아 맞았던 터라 긴장했는데, 빌드 설정을 열어보니 구조가 단순했다.
# make.def
CC=$(CROSS)gcc
AR=$(CROSS)ar
...
ROOTFS ?=
BIN ?= $(DESTDIR)$(bindir)
호스트에서 실행하는 빌드 도구가 없으니 GLIBC 충돌이나 sysroot 오염이 생길 여지가 없다. 핵심은 두 가지뿐이었다.
#!/bin/bash
set -e
. /opt/st/myir/3.1-snapshot/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
cd ~/work/_Open_PLC_Utils/open-plc-utils-master
exec make CROSS=arm-ostl-linux-gnueabi- CC="$CC" ROOTFS="$HOME/work/plc_out" "${@:-install}"
CROSS—ar,strip등을 ARM용으로CC="$CC"— SDK의$CC에는-mfloat-abi=hard -mcpu=cortex-a7 --sysroot=...가 들어 있다. 이게 빠지면 보드 glibc와 ABI가 안 맞는다. 커맨드라인 값이make.def의CC=$(CROSS)gcc를 덮어쓴다ROOTFS— 설치 경로를 따로 빼서 호스트/usr/local을 건드리지 않게
한 번에 통과했다. 로그에서 error가 108번 잡혀서 놀랐는데, 전부 error.o라는 파일 이름이었다.
$ file plcboot
plcboot: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux-armhf.so.3, ...
디버그 정보를 빼니 109개 도구가 14MB에서 2.3MB로 줄었다. 보드에서 공식 도구로 다시 물어봤다.
root@myir:~# plctool -r
eth1 00:B0:52:00:00:01 QCA7005 BootLoader
파이썬으로 본 것과 같은 결과다. BOM에는 QCA7005/7006으로 적혀 있었는데, 실제 실장된 칩이 QCA7005라는 것도 여기서 확정됐다.
펌웨어 고르기
펌웨어 파일은 인터넷에서 받을 수 있는 게 아니다. Qualcomm이 NDA 고객에게만 주는 파일인데, 다행히 사내 PLC 모뎀 개발 프로젝트에 패키지가 있었다. 문제는 파일이 너무 많았다는 것.
FW\MAC-QCA7000-QCA7005-GP-v3.3.0.0031-00-X-CS.nvm
FW\NvmSoftloader.nvm
PIB\AUTO\QCA7000-EVSE-SpiSlave-Automotive.pib
PIB\AUTO\QCA7000-PEV-SpiSlave-Automotive.pib
PIB\AUTO\QCA7005-EVSE-SpiSlave-Automotive.pib
PIB\AUTO\QCA7005-PEV-SpiSlave-Automotive.pib
PIB\AUTO\QCA7005-SpiSlave-Automotive.pib
PIB\AUTO\QCA7005-UartSerialHost-Automotive.pib
PIB\EN50561\QCA7005-SpiSlave-HomePlugGP-EN50561-1.pib
PIB\IOT\QCA7005-SpiSlave-HomePlugGP_NorthAmerica.pib
... (총 20여 개)
펌웨어(.nvm)는 이름에 QCA7000-QCA7005가 같이 들어 있는 공용 파일이라 고민할 게 없었다. 설정 파일(.pib)은 이름을 조각내서 보드 조건과 맞춰봤다.
QCA7005 - EVSE - SpiSlave - Automotive
칩 역할 연결 방식 용도
| 조각 | 의미 | 우리 보드 |
|---|---|---|
QCA7005 |
칩 | plctool이 QCA7005라고 응답 |
EVSE |
충전기 역할 (PEV는 차량) |
완속충전기 |
SpiSlave |
MPU와 SPI 연결 (Uart*는 UART) |
SPI |
Automotive |
전기차 충전용 (EN50561, IOT는 가정용 전력선) |
전기차 충전 |
가정용 PIB는 사용 주파수 설정(tone mask)과 송신 출력이 다르다. 충전기에 넣으면 통신이 안 되는 건 물론이고 전자파 인증에서 걸릴 수 있다. 이름만 보고 대충 고를 파일이 아니었다.
올렸다
root@myir:~# plcboot -i eth1 -N MAC-QCA7000-QCA7005-GP-v3.3.0.0031-00-X-CS.nvm \
-P QCA7005-EVSE-SpiSlave-Automotive.pib
root@myir:~# plctool -r
eth1 00:B0:52:00:00:01 QCA7005 MAC-QCA7005-3.3.0.31-00-20231004-CS
BootLoader가 펌웨어 버전 문자열로 바뀌었다. 소프트로더 없이 한 번에 올라갔으니 NvmSoftloader.nvm(외부 플래시에 굽는 용도)은 필요 없는 파일로 확정됐다.
설정이 제대로 들어갔는지도 칩에서 다시 읽어봤다.
root@myir:~# plctool -I
PIB 0-0 8836 bytes
MAC 00:B0:52:00:00:01
...
Security level 0
CCo Always
CCo Always 가 결정적이다. CCo(Central Coordinator)는 PLC 네트워크의 관리자 역할인데, ISO 15118-3에서는 충전기가 항상 이 역할을 맡는다. 차량용 PIB였으면 이 값이 달랐을 거다. EVSE 설정이 맞게 들어갔다.
리눅스 드라이버도 칩이 부트로더에서 펌웨어로 넘어가는 동안 끊기지 않고 따라왔다.
Sync state : 2 (QCASPI_SYNC_READY)
INTR REQ : 6205
INTR SVC : 6205
인터럽트가 982에서 6205로 늘었다. 펌웨어가 돌면서 활동이 많아졌고, 여전히 빠짐없이 처리하고 있다.
아직 남은 것
1. 펌웨어가 사라진다
플래시가 없으니 칩 RAM에만 올라가 있다. 전원을 끄면 당연히 사라지고, 운영 중에도 사라질 수 있다. qcaspi 드라이버는 칩과 동기화를 잃으면 SPI로 칩을 리셋하는데, 그러면 칩이 부트로더로 돌아간다. 충전 도중에 이런 일이 생기면 PLC가 죽는다.
부팅 스크립트에서 plcboot를 한 번 돌리는 걸로는 부족하다. open-plc-utils의 plchostd가 상주하면서, 칩의 부트로더가 펌웨어를 요청할 때마다 다시 내려주도록 만들어야 한다.
2. 모든 보드가 같은 MAC을 갖는다
샘플 PIB라서 MAC이 Qualcomm 공장 기본값 00:B0:52:00:00:01이다. 충전소에 충전기가 여러 대 있으면 옆 충전기 케이블로 신호가 새어 들어가는 일(crosstalk)이 있어서, MAC이 같으면 차량이 어느 충전기와 연결됐는지 구분하지 못한다. 장치별 키(DAK)도 마찬가지다. 양산 때는 modpib로 보드마다 고유값을 넣어야 한다.
3. 실제 PLC 링크
지금 확인한 건 MPU와 QCA7005 사이까지다. 차량과의 연결은 CP 라인에 상대가 있어야 볼 수 있다. 다행히 같은 패키지에 차량용 PIB가 있고, open-plc-utils에 SLAC 테스트용 evse / pev 프로그램도 들어 있다. 보드 한 대에 차량 역할을 시키면 책상 위에서 충전기-차량 연결을 재현할 수 있다.
정리
오늘은 “막혀 있다”고 적어둔 항목이 사실은 막혀 있지 않았다는 걸 확인하는 데서 시작했다.
lsmod사용 카운트는 바인딩 여부가 아니다. 숫자를 해석하기 전에 무엇을 세는지 확인해야 한다- probe 성공이 곧 검증이다.
pluggable=0모드는 시그니처를 읽어야 probe가 끝나므로,eth1이 생긴 순간 SPI 배선·모드·리셋 극성이 전부 확인된다 - “동작 안 함”과 “펌웨어 대기 중”은 다르다. 통신이 되는지와 칩이 준비됐는지는 따로 물어봐야 한다
- 넷리스트의
no_connect가 설계 의도를 말해준다. 플래시 핀이 비어 있으면 호스트 부팅 설계다 - 펌웨어 패키지는 이름을 조각내서 고른다. 칩·역할·연결 방식·용도가 전부 맞아야 한다
며칠 동안 “PLC 안 붙음”으로 남아 있던 항목이, 오늘 하루 만에 “펌웨어 동작, EVSE 설정 확인”까지 왔다. 다음은 plchostd 자동화와 SLAC 벤치 테스트다.
댓글남기기