6 분 소요

어제(11/03)까지 Linux 6.6 위에서 보드 주변장치 기능 시험(M5)을 마쳤다. 그런데 그 결과는 지금 보드 한 대에서만 재현된다. 시험하면서 펌웨어와 스크립트를 보드에 손으로 복사했기 때문이다. SD 카드를 새로 구우면 WiFi도, PLC 모뎀도, 블루투스 시리얼도 동작하지 않는다.

이번 단계(M6)의 목표는 SD 카드 하나만 구우면 손으로 아무것도 하지 않아도 전부 동작하는 이미지다. 오늘은 그 첫날로, 손으로 설치한 것을 레이어의 레시피로 옮겼다.

손으로 설치한 것 레이어에서
AP6212 WiFi/BT 펌웨어 (ST 패키지에서 꺼냄) 머신 설정 WIFI_LIST, BLUETOOTH_LIST
AP6212 nvram, BT 패치 (MYiR OS에서 복사) 새 레시피 sc6-firmware
rfcomm.ko (빌드 PC에서 복사) 머신 설정 MACHINE_EXTRA_RRECOMMENDS
bluetoothd -C drop-in bluez5 bbappend
QCA7005 펌웨어, 적재 스크립트, 서비스 새 레시피 qca7005-fw
PLC 도구 (1차 때 SDK로 빌드한 바이너리) 새 레시피 open-plc-utils

0. 레시피는 어디에 넣나

넣을 것은 크게 두 종류다.

  • 보드 하드웨어에 딸린 것: 펌웨어, 커널 모듈, 칩 적재 서비스. 이 보드가 아니면 필요 없다.
  • 제품 기능에 딸린 것: EVerest, MQTT 브로커 같은 응용 프로그램.

Yocto에서는 앞의 것을 머신 설정의 MACHINE_EXTRA_RRECOMMENDS에 넣는 게 일반적이다. 어떤 이미지를 만들든 이 보드용이면 따라 들어간다. 뒤의 것은 이미지 레시피에 넣는다. 오늘은 앞의 것만 다뤘다.

이미지를 구워 보드에서 시험하는 건 3일차(11/06)에 한 번에 한다. 오늘은 레시피를 빌드하고 만들어진 deb 패키지 안을 열어 파일 경로, 권한, 내용(md5)을 확인하는 데까지 했다. ST 배포판은 ipk가 아니라 deb를 만든다.

dpkg-deb -c sc6-firmware_1.0-r0.0_all.deb       # 파일 목록
dpkg-deb -f qca7005-fw_1.0-r0.0_armhf.deb Depends  # 의존성
dpkg-deb -x ... /tmp/x && md5sum /tmp/x/...      # 내용

1. WiFi/BT 펌웨어 — ST가 이미 만들어 둔 길

머신 설정을 열어 보니 10/30에 주석으로 남겨 둔 줄이 있었다.

#MACHINE_FEATURES += "bluetooth wifi"
#BLUETOOTH_LIST += "linux-firmware-addons-bcm43xx"
#WIFI_LIST += "linux-firmware-addons-bcm43xx"

ST 공통 설정(st-machine-features-stm32mp.inc)을 따라가 보면, 머신 기능에 wifi나 bluetooth가 있을 때 *_LIST의 패키지를 MACHINE_EXTRA_RRECOMMENDS로 넣어 준다. ST의 DK2 보드가 쓰는 방식이다. 주석만 풀면 됐다.

MACHINE_FEATURES += "bluetooth wifi"
BLUETOOTH_LIST += "linux-firmware-addons-bcm43xx"
WIFI_LIST += "linux-firmware-addons-bcm43xx"
MACHINE_EXTRA_RRECOMMENDS += "kernel-module-rfcomm"

kernel-module-rfcomm은 어제 커널 설정에 CONFIG_BT_RFCOMM=m을 넣고 다시 빌드했을 때 이미 패키지로 만들어져 있었다. 커널 레시피는 모듈마다 kernel-module-<이름> 패키지를 자동으로 만든다. 목록에 이름만 넣으면 된다.


2. AP6212 전용 파일 — sc6-firmware

2-1. 왜 따로 필요한가

ST 펌웨어 패키지에는 BCM43430용 nvram이 들어 있지만 Murata 모듈용이다(크리스털 37.4MHz). 우리 AP6212는 26MHz다. 그래서 어제 MYiR OS 카드에서 AP6212용 nvram과 BT 패치를 꺼내 보드에 넣었다. 이 두 파일은 보드에만 있었다. 오늘 아침 보드에서 빌드 PC로 보내 레이어에 넣었다.

boardtype=0x0726
xtalfreq=26000
ccode=ALL

2-2. 레이어 안에서는 쉼표 없는 이름으로

설치될 파일 이름에는 보드 compatible이 붙는다. brcmfmac와 hci_bcm이 이 이름을 먼저 찾기 때문이다.

brcmfmac43430-sdio.knv,stm32mp157a-sc6.txt
BCM43430A1.knv,stm32mp157a-sc6.hcd

이름에 쉼표가 들어 있다. SRC_URI는 공백으로 항목을 나누고 ;로 옵션을 붙이는 형식이라, 특수문자가 든 파일 이름은 피하는 게 안전하다. 그래서 레이어에는 ap6212-nvram.txt, ap6212-bt.hcd로 두고 설치할 때 이름을 붙였다.

BOARD_COMPAT = "knv,stm32mp157a-sc6"

do_install() {
	install -d ${D}${nonarch_base_libdir}/firmware/brcm
	install -m 0644 ${S}/ap6212-nvram.txt \
		${D}${nonarch_base_libdir}/firmware/brcm/brcmfmac43430-sdio.${BOARD_COMPAT}.txt
	install -m 0644 ${S}/ap6212-bt.hcd \
		${D}${nonarch_base_libdir}/firmware/brcm/BCM43430A1.${BOARD_COMPAT}.hcd
}

2-3. /lib/firmware인가 /usr/lib/firmware인가

ST 패키지를 열어 보니 펌웨어가 /usr/lib/firmware/에 있었다. 커널은 /lib/firmware를 찾는데 괜찮을까? 배포판 기능을 보니 usrmerge가 켜져 있었다. /lib가 /usr/lib를 가리키는 구성이라 같은 곳이다. 그래서 경로를 직접 쓰지 않고 ${nonarch_base_libdir}를 썼다. 배포판 설정이 바뀌어도 맞는 곳에 들어간다.

라이선스는 CLOSED로 했다. MYiR BSP에 들어 있던 모듈 업체(AMPAK) 파일이라 배포 조건을 아직 확인하지 못했다.


3. bluetoothd -C — 원본은 두고 drop-in만

블루투스 시리얼(SPP)을 sdptool로 등록하려면 bluetoothd가 호환 모드(-C)로 돌아야 한다. 어제는 보드의 /etc/systemd/system/bluetooth.service.d/에 drop-in을 손으로 만들었다. 레이어에서는 bluez5 bbappend로 패키지에 넣었다.

do_install:append() {
	install -d ${D}${systemd_system_unitdir}/bluetooth.service.d
	cat > ${D}${systemd_system_unitdir}/bluetooth.service.d/sc6-compat.conf << EOF
[Service]
ExecStart=
ExecStart=${libexecdir}/bluetooth/bluetoothd -C
EOF
}

FILES:${PN} += "${systemd_system_unitdir}/bluetooth.service.d"
  • 서비스 파일 원본을 패치하지 않는다. bluez5 버전이 올라가도 깨질 일이 적다.
  • ExecStart= 빈 줄로 원래 명령을 지운 뒤 새 명령을 넣는 것이 systemd drop-in의 규칙이다.
  • bluetoothd 경로도 ${libexecdir}로 써서 레시피 변수와 맞췄다.

빌드된 패키지에는 /usr/lib/systemd/system/bluetooth.service.d/sc6-compat.conf로 들어갔다. 다만 sdptool과 -C는 BlueZ에서 오래된 방식이다. 블루투스를 제품에서 무엇에 쓸지(점검용 시리얼인지, 전용 앱용 BLE인지) 정해지면 다시 본다.


4. QCA7005 — /usr/local의 함정

4-1. 구성

QCA7005는 플래시가 없어서 부팅할 때마다 호스트가 펌웨어를 올려야 한다. 그 일을 하는 셸 스크립트와 oneshot 서비스, 그리고 Qualcomm 펌웨어 두 개를 한 레시피(qca7005-fw)에 묶었다.

inherit systemd

RDEPENDS:${PN} = "open-plc-utils libgpiod-tools"

SYSTEMD_SERVICE:${PN} = "qca7005-fw.service"
SYSTEMD_AUTO_ENABLE = "enable"
  • 스크립트가 plcboot, plctool(open-plc-utils)과 gpioset(libgpiod v2)을 쓰므로 실행 의존성으로 걸었다. 이 레시피만 넣어도 도구가 따라 들어간다.
  • SYSTEMD_AUTO_ENABLE = "enable"이면 패키지에 98-qca7005-fw.preset 파일이 생긴다. 첫 부팅 때 systemd가 이걸 보고 서비스를 켠다. 어제처럼 systemctl enable을 손으로 할 필요가 없다.

4-2. 1차 때 경로를 그대로 쓰면 안 된다

1차(MYiR 5.4) 때는 스크립트를 /usr/local/sbin/, 도구를 /usr/local/bin/에 두었다. 손으로 설치하는 파일은 /usr/local에 두는 게 관례라서다. 그런데 ST의 SD 카드 레이아웃을 보면 이렇다.

bootfs   → /boot
vendorfs → /vendor
rootfs   → /
userfs   → /usr/local     ← 별도 파티션

/usr/local이 별도 파티션(userfs)이다. 이미지 패키지가 rootfs의 /usr/local/sbin/에 파일을 넣어도, 부팅하면 userfs가 그 위에 마운트되면서 파일이 가려진다. 빌드도 되고 패키지에도 들어 있는데 보드에서는 “파일 없음”이 나오는, 찾기 어려운 문제가 됐을 것이다. 그래서 스크립트는 /usr/sbin/으로 옮기고, 서비스 파일의 실행 경로는 레시피가 채우게 했다.

ExecStart=@SBINDIR@/qca7005-fwload.sh --reset
sed -i -e 's#@SBINDIR@#${sbindir}#g' ${D}${systemd_system_unitdir}/qca7005-fw.service

4-3. 이 레이어는 공개하지 않는다

.nvm과 .pib는 Qualcomm 독점 파일이다. 라이선스를 CLOSED로 두고, 이 레이어는 사내 저장소에만 두기로 했다.


5. open-plc-utils — 컴파일러가 덮어써진다

5-1. 소스와 라이선스

1차 때는 GitHub에서 받은 소스를 MYiR SDK로 직접 빌드했다. 레시피에서는 같은 저장소를 받되 커밋을 고정했다. 빌드할 때마다 같은 결과가 나오게 하려는 것이다.

LICENSE = "BSD-3-Clause-Clear"
LIC_FILES_CHKSUM = "file://LICENSE;md5=7d83a9e9a9788beb9357262af385f6c7"

SRC_URI = "git://github.com/qca/open-plc-utils.git;protocol=https;branch=master"
SRCREV = "5e0295719d70058221273ac0e780fcd82e28ea0f"
PV = "0.0+git"

저장소는 지금도 관리되고 있었다(마지막 커밋 2026-09-08). 라이선스 본문에는 일반 BSD-3에 없는 “특허권은 이 라이선스로 허락하지 않는다”는 조항이 있다. 이 조항이 있으면 Clear BSD(BSD-3-Clause-Clear)다. 라이선스 파일 md5는 1차 때 쓴 소스와 같았다.

5-2. make.def가 CC를 일반 대입으로 정한다

이 프로젝트의 공통 make 설정(make.def)은 이렇게 되어 있다.

AR=$(CROSS)ar
CC=$(CROSS)gcc
...
CFLAGS+=-Wall -Wextra ...
LDFLAGS+=$(EXTRA_LDFLAGS)

Yocto는 컴파일러를 환경 변수 CC로 넘긴다. 그 값에는 --sysroot, -mfloat-abi=hard, -mcpu=cortex-a7 같은 중요한 옵션이 다 들어 있다. 그런데 Makefile 안의 일반 대입(CC=...)은 환경 변수를 이긴다. 그대로 두면 arm-...-gcc만 쓰고 옵션은 사라진다. 운이 좋으면 빌드 오류가 나고, 나쁘면 ABI가 다른 바이너리가 조용히 만들어진다.

명령행 변수는 Makefile의 대입을 이긴다. 그래서 도구들을 명령행으로 넘겼다. 반대로 CFLAGS와 LDFLAGS는 +=로 받으니 환경 변수 그대로 두면 된다.

EXTRA_OEMAKE = " \
	CROSS=${TARGET_PREFIX} \
	CC='${CC}' CXX='${CXX}' LD='${LD}' AR='${AR}' RANLIB='${RANLIB}' STRIP='${STRIP}' \
"

1차 빌드 스크립트에서도 CC="$CC"를 명령행으로 넘겼었다. 그때 주석으로 남긴 이유(“make.def의 CC는 명령행 값이 이기므로 덮어써진다”)가 오늘 레시피에 그대로 쓰였다.

5-3. 설치 경로

기본값은 prefix=/usr/local이다. 4절과 같은 이유로 /usr로 바꿨다.

do_install() {
	oe_runmake install DESTDIR=${D} prefix=${prefix} bindir=${bindir}
}
/usr/bin/plcboot: ELF 32-bit LSB executable, ARM, EABI5 ...
    interpreter /usr/lib/ld-linux-armhf.so.3, for GNU/Linux 5.4.0, stripped

하드플로트 로더(ld-linux-armhf.so.3)를 가리키니 컴파일러 옵션이 제대로 들어갔다. 도구는 110개가 설치됐다. for GNU/Linux 5.4.0은 10/29에 MYiR 5.4 커널 위에서 시험하려고 빌드 PC에 OLDEST_KERNEL = "5.4"를 넣어 둔 흔적이다. 6.6에서 동작하는 데는 문제가 없다.


6. 정리

커밋

9f1ebb7 Linux 6.6 M6: 보드 펌웨어/서비스/PLC 도구 레시피 추가
meta-sc6/
├── conf/machine/stm32mp15-sc6.conf      WiFi/BT 기능, 보드 패키지 목록
├── recipes-bsp/
│   ├── sc6-firmware/                    AP6212 nvram, BT 패치
│   └── qca7005-fw/                      PLC 펌웨어, 적재 스크립트, 서비스
├── recipes-connectivity/
│   ├── open-plc-utils/                  plcboot, plctool 외
│   └── bluez5/                          bluetoothd -C
├── recipes-kernel/linux/                커널 설정 조각
└── mx/sc6/                              보드 DTS 4종

오늘 배운 것

  1. BSP 업체가 만들어 둔 길부터 찾아라. WiFi/BT 펌웨어는 ST 공통 설정의 WIFI_LIST, BLUETOOTH_LIST로 주석 세 줄만 풀면 됐다.
  2. 손으로 설치할 때의 관례가 이미지에서는 함정이 된다. /usr/local은 손으로 설치하기 좋은 곳이지만, ST 레이아웃에서는 별도 파티션이라 이미지에 넣은 파일이 가려진다.
  3. 오래된 Makefile은 크로스 컴파일 변수를 덮어쓴다. CC=가 일반 대입이면 명령행으로 넘겨야 Yocto의 컴파일러 옵션이 살아남는다. 확인은 결과 바이너리의 로더 경로(file)로 한다.
  4. 보드에 올리기 전에 패키지를 열어 보라. dpkg-deb -c, -f Depends, md5만으로 경로, 권한, 의존성, 내용을 확인할 수 있다. SD 카드를 굽고 부팅하는 것보다 훨씬 빠르다.
  5. 경로는 변수로 써라. ${nonarch_base_libdir}, ${sbindir}, ${libexecdir}를 쓰면 usrmerge 같은 배포판 설정이 바뀌어도 맞는 곳에 들어간다.

다음 할 일

  • 2일차 (11/05) 시스템 설정: 시간대, 호스트 이름, WiFi 자동 접속(networkd), 유선·무선 경로 우선순위, PLC eth0를 DHCP에서 빼기, 블루투스 첫 명령 실패 대책, dwc2 전원 경고, MAC 주소 방식
  • 3일차 (11/06) 이미지 완성: 충전기용 이미지 레시피(EVerest를 빌드 PC local.conf에서 레이어로), 브링업 흔적 정리(TF-A 초기 콘솔, U-Boot 경고, 쓰지 않는 기능), 새 카드로 M5 시험 전부 다시

댓글남기기