임베디드 MCU 애플리케이션의 OTA 업데이트 설계 시 고려사항
상태바
임베디드 MCU 애플리케이션의 OTA 업데이트 설계 시 고려사항
  • 신동윤 기자
  • 승인 2019.03.22 09:33
  • 댓글 0
이 기사를 공유합니다

벤자민 버클린 브라운(Benjamin Bucklin Brown) | 아나로그디바이스

[테크월드=신동윤 기자] 많은 임베디드 시스템들이 관리자가 접근하기 어려운 위치에 설치되곤 한다. 특히 사람이나 각종 기기의 상태를 모니터링하는 IoT 관련 임베디드 시스템의 경우 이런 경향이 크다. 게다가 소프트웨어 수명주기가 빠르게 변화하면서 많은 시스템들이 OTA(Over-The-Air, 무선) 업데이트 지원을 필요로 하고 있다.

OTA 업데이트란 임베디드 시스템의 MCU(MicroController Unit)나 마이크로프로세서에서 실행되는 소프트웨어를 새로운 소프트웨어로 교체하는 것을 말한다. 이미 많은 사람들이 모바일 기기에서 이뤄지는 OTA 업데이트에는 익숙한 상황이다. 하지만 자원이 한정적인 시스템에 OTA 업데이트 솔루션을 설계, 구현하기 위해서는 많은 것을 고려해야 한다. 이 글에서는 OTA 업데이트 소프트웨어를 설계할 때 고려해야 할 사항은 무엇인지 살펴본다. 또 OTA 업데이트 소프트웨어와 관련해, 초저전력 MCU 2종의 하드웨어 특성들을 활용하는 방법에 대해서도 알아본다.


서버와 클라이언트
OTA 업데이트는 새로운 소프트웨어를 무선으로 다운로드해 이를 기존 소프트웨어와 교체하는 것을 말한다. 임베디드 시스템에서 이 업데이트 소프트웨어를 실행하는 것은 주로 MCU다. MCU는 메모리, 속도, 전력 소모가 제한적인 컴퓨팅 디바이스다. MCU는 통상적으로 마이크로프로세서(코어)와 특정 용도의 디지털 하드웨어 블록(주변장치)을 포함한다.
임베디드 시스템용으로는 동적 모드일 때 30~40μA/MHz를 소모하는 초저전력 MCU가 이상적이다. 이런 MCU의 하드웨어 주변장치 기능을 필요 시에만 활용하고 그렇지 않을 때는 저전력 모드로 전환하는 것이 OTA 업데이트 소프트웨어 설계 시 가장 중요한 부분이다.
[그림 1]은 OTA 업데이트를 수행하는 임베디드 시스템의 예다. MCU가 무선 장치, 센서와 연결돼 있으며, 센서로 주변 환경에 대한 데이터를 수집하고 이를 무선 장치를 사용해서 주기적으로 전송할 수 있다. 시스템에서 이 부분을 에지 노드(Edge node) 또는 클라이언트라고 하며, OTA 업데이트의 대상이 바로 이 부분이다. 또 다른 부분은 클라우드 또는 서버라고 하며, 새로운 소프트웨어를 제공하는 측이다. 서버와 클라이언트는 트랜시버(무선 장치)를 사용해서 무선으로 통신한다.

[그림 1] 임베디드 시스템의 클라이언트/서버 아키텍처

소프트웨어 애플리케이션
OTA 업데이트 프로세스는 거의 대부분이 서버에서 클라이언트로 새로운 소프트웨어를 전송하는 방식으로 이뤄진다. 이 소프트웨어는 일련의 바이트로 전송되는데, 전송하기 전에 먼저 소스 형식에서 바이너리 형식으로 변환을 거친다.
이런 변환을 위해서는 소스 코드 파일(.c, .cpp 등)을 컴파일하고, 이를 실행가능 파일(.exe, .elf 등)로 링크 연결한 다음, 이 실행 파일을 포팅할 수 있는 바이너리 파일 형식(.bin, .hex 등)으로 변환해야 한다. 이런 상위 수준의 파일 형식은 일련의 바이트를 포함하며, 이들 바이트는 MCU의 특정한 메모리 어드레스에 저장된다.
흔히 무선 링크를 통해서는 시스템의 상태를 변경하기 위한 명령이나 시스템이 수집한 센서 데이터 같은 것들만 전송될 것으로 생각하기 쉽다. OTA 업데이트의 경우에는 이 데이터가 바이너리 형식으로 된 새로운 소프트웨어다. 많은 경우, 이 바이너리 파일은 서버에서 클라이언트로 한 번에 전송하기에 크기가 지나치게 클 수 있다. 따라서 바이너리 파일을 패킷화(Packetizing)라고 하는 과정을 거쳐서 각각의 패킷에 집어넣어야 한다.
[그림 2]는 이 과정을 이해하기 쉽게 도식화한 것으로, 어떻게 바이너리 파일을 생성하고 OTA 업데이트 시에 각각의 패킷으로 전송하는지를 보여준다. 이 예시에서는, 각각의 패킷이 8바이트의 데이터를 포함한다. 앞의 4바이트는 클라이언트 메모리에서의 어드레스를 지정하는 것이고, 뒤의 4바이트가 저장 공간이다.

[그림 2] 소프트웨어 애플리케이션의 바이너리 변환과 패킷화 과정

OTA 업데이트의 주요 문제점
이처럼 높은 수준의 OTA 업데이트 기술을 구현하기 위해, OTA 업데이트 솔루션은 3가지 과제를 해결해야 한다.
첫 번째는 메모리다. 새로운 소프트웨어 애플리케이션을 클라이언트 디바이스의 휘발성/비휘발성 메모리에 집어넣어야 한다. 그래야 업데이트 프로세스가 완료되었을 때 실행이 가능하다. 또한 새로운 소프트웨어에 문제가 생겼을 때를 대비한 폴백(Fallback) 기능으로 이전 버전의 소프트웨어를 유지해야 한다. 또한 리셋부터 전원 사이클까지 사이의 클라이언트 디바이스 상태를 보존해야 한다. 여기에는 현재 실행되고 있는 소프트웨어 버전과 메모리 위치 등이 포함된다.
두 번째 과제는 통신이다. 새로운 소프트웨어를 서버에서 클라이언트로 디스크리트 패킷으로 전송해야 하고, 각각의 패킷은 클라이언트 메모리의 특정 어드레스로 지정된다. 따라서 업데이트 소프트웨어를 설계할 때 패킷화방법, 패킷 구조, 데이터 전송에 사용할 프로토콜 등의 사항을 모두 고려해야 한다.
세 번째 과제는 보안이다. 새로운 소프트웨어를 서버에서 클라이언트로 무선으로 전송하기 때문에 서버가 신뢰할 수 있는 당사자라는 것을 확인할 수 있어야 한다. 이를 위한 절차가 인증이다. 또한 새로운 소프트웨어에 민감한 정보가 담길 수도 있으므로 새로운 소프트웨어가 엿보고자 하는 누군가에게 노출되지 않도록 해야 한다. 이런 보안 과제를 기밀유지라고 한다. 보안과 관련한 마지막 요소는 무결성이다. 이것은 새로운 소프트웨어가 무선으로 전송될 때 훼손되지 않도록 하는 것이다.

부트 시퀀스에 대한 이해
1단계 부트로더(Primary bootloader)는 MCU의 ROM(Read-Only Memory)에 영구 상주하는 소프트웨어 애플리케이션이다. 1단계 부트로더가 상주하는 메모리 영역은 정보 공간으로서, 종종 사용자의 접근을 차단하기도 한다. 이 애플리케이션은 리셋할 때마다 실행되는데, 필수적인 하드웨어 초기화를 수행하고 사용자 소프트웨어를 메모리에 로드한다. 만약 MCU가 플래시 메모리 같이 비휘발성 메모리를 내장하고 있다면 부트로더가 로딩할 필요 없이 플래시 메모리에 있는 프로그램으로 제어를 넘길 수 있다.
만약 1단계 부트로더가 OTA 업데이트를 지원하지 않으면 SSBL(Second Stage Boot Loader)이 필요하다. 1단계 부트로더와 마찬가지로 SSBL 역시 리셋할 때마다 실행된다. 다만 OTA 업데이트 프로세스 부분만 실행한다. [그림 3]은 이같은 부트 시퀀스를 보여준다. 그러면 왜 SSBL이 필요하고, 이것이 어떤 역할을 하는지 살펴보자.

[그림 3] SSBL을 사용할 때의 메모리 맵과 부트 플로우

SSBL이 반드시 필요한 이유
언뜻 생각하면 SSBL을 생략하고 모든 OTA 업데이트 기능을 사용자 애플리케이션에 집어넣는 것이 더 간단해 보일 수 있다. 그러면 기존의 소프트웨어 프레임워크, 운영체제, 장치 드라이버를 매끄럽게 활용할 수 있기 때문이다. [그림 4]는 이렇게 SSBL을 사용하지 않을 때의 메모리 맵과 부트 시퀀스를 보여준다.
애플리케이션 A는 MCU에 설치돼 있는 기본 애플리케이션이다. 이 애플리케이션은 OTA 업데이트 관련 소프트웨어를 포함하며, 이 소프트웨어를 사용해 서버의 요청이 있을 때 애플리케이션 B를 다운로드할 수 있다. 다운로드가 완료되고 애플리케이션 B를 검사한 후에는 애플리케이션 A가 애플리케이션 B의 리셋 핸들러로 분기 명령을 실행해서 애플리케이션 B로 제어를 넘긴다.
리셋 핸들러는 소프트웨어 애플리케이션의 진입 지점이 되는 작은 코드 조각으로서, 리셋할 때 실행된다. 이 경우는 분기를 실행해서 리셋이 일어난 것처럼 흉내 낸다. 분기는 함수 호출에 해당된다. 이 방법은 다음 두 가지 측면에서 문제가 된다.
많은 임베디드 소프트웨어 애플리케이션은 실시간 운영체제(RTOS)를 사용한다. 그러므로 소프트웨어를 각자 임무를 처리하는 동시적인 작업들로 분할할 수 있다. 예를 들어 [그림 1]의 애플리케이션은 센서를 읽고, 센서 데이터에 대해 알고리즘을 실행하고, 무선 장치와 인터페이싱하는 작업들로 나눌 수 있다. RTOS 자체는 항상 동작 상태에 있으며 비동기 이벤트나 특정한 시간 간격으로 이런 작업 사이를 전환한다. 따라서 RTOS 작업에서 새로운 프로그램으로 분기하는 것은 안전하지 않다. 다른 작업들이 계속해서 백그라운드로 실행되기 때문이다. RTOS를 사용하면서 프로그램을 종료하기 위한 안전한 방법은 리셋을 하는 것뿐이다.
[그림 4]에 따르면, 앞선 문제에 대한 해결책은 1단계 부트로더 분기를 애플리케이션 A가 아니라 애플리케이션 B로 하는 것이다. 그런데 일부 MCU는 1단계 부트로더가 항상 인터럽트 벡터 테이블(IVT)을 포함하는 프로그램을 실행한다. IVT는 어드레스 0에 위치한 인터럽트 처리 기능을 기술하는 애플리케이션의 중요한 부분이다. 다시 말해, 애플리케이션 B로 리셋하기 위해서 일정한 형태의 IVT 재배치가 필요하다는 뜻이다. 이 IVT 재배치 시에 전원 사이클이 일어나면 시스템이 영구적인 손상을 입을 수 있다.
[그림 3]에서 본 것처럼, 이 문제는 SSBL을 어드레스 0으로 고정함으로써 완화할 수 있다. SSBL은 RTOS 프로그램이 아니므로 안전하게 새로운 애플리케이션으로 분기할 수 있다. 따라서 전원 사이클링으로 인해 시스템에 심각한 손상이 일어날 우려가 없다. 어드레스 0으로 고정된 SSBL의 IVT가 절대로 재배치되지 않기 때문이다.

[그림 4] SSBL을 사용하지 않을 때의 메모리 맵과 부트 플로우

SSBL의 역할
SSBL은 현재 애플리케이션이 어디서 시작되는지 판단하고 그 어드레스로 분기하는 일을 한다. MCU 메모리 내에서 다양한 애플리케이션의 위치는 [그림 3]에서 보듯이 ToC(Table of Contents)에 저장된다. 이것은 SSBL과 소프트웨어 애플리케이션이 소통하기 위해서 사용하는 영구 메모리의 공유 영역이다. OTA 업데이트 프로세스가 완료되면 ToC가 새로운 애플리케이션 정보로 업데이트된다.
일부 OTA 업데이트 기능들을 SSBL에 넣을 수도 있다. 어떤 기능들을 넣느냐가 OTA 업데이트 소프트웨어를 개발할 때 결정해야 할 중요한 사항 중 하나다. 앞서 설명한 SSBL은 극단적으로 단순화한 것으로서 쉽게 검증할 수 있다. 하지만 실제로는 매번 다음 애플리케이션을 다운로드할 때마다 검증해야 한다. 이는 무선 스택, 디바이스 펌웨어, OTA 업데이트 소프트웨어에 있어서 코드 중복성으로 이어진다.
이렇게 하지 않고, 전체적인 OTA 업데이트 프로세스를 SSBL로 집어 넣을 수도 있다. 이 경우에는 애플리케이션이 간단히 ToC로 업데이트를 요청하고 리셋을 실행하기 위한 플래그를 설정한다. 그러면 SSBL이 다운로드 시퀀스와 검증 프로세스를 수행한다. 이 방식은 코드 중복성을 최소화하고 소프트웨어 애플리케이션을 단순화할 수 있다. 하지만 SSBL 자체를 업데이트해야 하는 새로운 문제를 야기한다. 다시 말해 업데이트 코드를 업데이트해야 하는 것이다.
결국, SSBL에 어떤 기능들을 넣을지 결정하는 것은 클라이언트 디바이스의 메모리 제약, 다운로드된 애플리케이션의 유사성, OTA 업데이트 소프트웨어의 이식성에 따라서 결정될 것이다.

캐싱과 압축
OTA 업데이트 소프트웨어를 설계할 때 또 다른 고려사항은 OTA 업데이트를 하면서 들어오는 애플리케이션을 메모리에 어떻게 체계적으로 넣을 것이냐에 대한 것이다. MCU에 주로 사용되는 메모리 유형은 비휘발성 메모리(플래시 메모리)와 휘발성 메모리(SRAM), 크게 두 가지다. 플래시 메모리를 사용하면 애플리케이션의 프로그램 코드와 읽기 전용 데이터뿐만 아니라 ToC와 이벤트 로그 같은 시스템 차원의 여러 데이터를 저장할 수 있다. SRAM을 사용하면 글로벌 변수와 스택 같이 소프트웨어 애플리케이션의 수정 가능한 부분들을 저장할 수 있다. [그림 2]에서 설명한 소프트웨어 애플리케이션 바이너리는 비휘발성 메모리에 들어가는 부분만을 포함한다. 스타트업 루틴을 실행하면 휘발성 메모리에 들어 있는 부분을 초기화한다.
OTA 업데이트 과정에서는 클라이언트 디바이스가 서버로부터 바이너리 부분을 포함하는 패킷을 수신할 때마다 이것을 SRAM에다 저장한다. 패킷은 압축을 할 수도, 하지 않을 수도 있다. 애플리케이션 바이너리를 압축하면 크기를 작게 할 수 있으므로 다운로드 과정에서 더 적은 수의 패킷을 전송할 수 있고 SRAM 저장 공간도 줄일 수 있다. 대신 단점은, 압축과 압축해제를 하기 위해서 업데이트 프로세스에서 처리 시간이 늘어나며, OTA 업데이트 소프트웨어에 압축 관련 코드를 포함해야 한다는 것이다.
새로운 소프트웨어 애플리케이션은 플래시 메모리에 저장해야 하지만 업데이트를 할 때 SRAM에 도착하기 때문에, 업데이트 프로세스의 어느 시점인가에 OTA 업데이트 소프트웨어가 플래시 메모리에 쓰기 작업을 수행할 필요가 있다. 새로운 애플리케이션을 임시로 SRAM에 저장하는 것을 캐싱(Caching)이라고 한다. OTA 업데이트는 캐싱에 대해서 다음과 같은 세 가지 접근법을 취할 수 있다.

- 캐싱하지 않기
새로운 애플리케이션의 일부를 포함한 패킷이 도착할 때마다 이를 플래시 메모리의 지정된 위치에다 저장한다. 이 방법은 매우 간단하며, OTA 업데이트 소프트웨어에 필요한 로직의 양을 최소화할 수 있다. 하지만 새로운 애플리케이션을 저장할 플래시 메모리 영역을 완벽하게 소거해야 한다. 이 방법은 플래시 메모리의 내구성을 저하시키고 처리 부담을 늘린다.

- 부분적 캐싱
SRAM에 일정 영역을 캐싱용으로 할당하고 새로운 패킷이 도착하면 이 영역에 저장한다. 이 영역이 가득 차면 해당 데이터를 플래시 메모리에 기록해 저장 공간을 비운다. 그런데 패킷이 순서가 어긋나게 도착하거나 새로운 애플리케이션 바이너리에 빈 틈이 있으면 일이 복잡해진다. SRAM 어드레스를 플래시 어드레스로 맵핑하는 것이 필요하기 때문이다. 한 가지 방법은 캐시가 플래시 메모리를 미러링하도록 하는 것이다. 플래시 메모리는 페이지(Page)라고 하는 작은 영역들로 나뉜다. 소거할 수 있는 가장 작은 단위다. 이는 자연적인 분할이기 때문에, 플래시 메모리의 한 페이지를 SRAM에 캐싱하고, 이 영역이 가득 차면 해당 페이지를 플래시 메모리에 저장해 캐시를 비우는 것이 좋은 방법이다.

- 완전 캐싱
새로운 애플리케이션 전체를 SRAM에 저장하고 서버로부터 다운로드가 완전히 끝나야만 플래시 메모리에 쓰기를 한다. 이 방법은 플래시 메모리의 쓰기 횟수를 최소화하고 OTA 업데이트 소프트웨어에 복잡한 캐싱 로직을 필요로 하지 않기 때문에 앞서 소개한 두 가지 방법의 단점을 극복할 수 있다. 하지만 다운로드할 수 있는 애플리케이션의 크기가 제한된다. 통상적인 시스템에서는 SRAM의 크기가 플래시 메모리보다 훨씬 작기 때문이다.
[그림 5]는 두 번째 방법인 부분적인 캐싱을 보여준다. [그림 3]과 [그림 4]에서 보았던 애플리케이션 A의 플래시 메모리 부분과 SSBL의 SRAM 메모리 맵을 볼 수 있다. 여기서 플래시 페이지 크기는 2kB이다. 이 크기를 결정하는 것은 새로운 애플리케이션의 크기와 OTA 업데이트 소프트웨어의 허용 가능한 복잡성에 따라 달라진다.

[그림 5] SRAM을 사용해서 캐시의 한 페이지를 플래시 메모리에 쓰기

소프트웨어 vs. 프로토콜
OTA 업데이트 솔루션은 보안과 통신 또한 고려해야 한다. [그림 1]에서 보는 것 같은 많은 시스템들이 센서 데이터를 교환하는 등의 통상적인 시스템 동작을 위해 하드웨어와 소프트웨어로 통신 프로토콜을 구현하고 있다. 즉, 서버와 클라이언트 사이에 무선 통신이 이미 구축돼 있는 것이다. [그림 1]과 같은 임베디드 시스템에 사용할 수 있는 통신 프로토콜로는 저전력 블루투스(Bluetooth Low Energy)나 6LoWPAN을 들 수 있다. 보안과 데이터 교환을 지원하는 이런 프로토콜을 OTA 업데이트에 활용할 수 있다.
OTA 업데이트 소프트웨어에 필요한 통신 기능의 양은 이미 구축된 통신 프로토콜이 제공할 수 있는 추상화 수준이 어느 정도인가에 따라 결정된다. 기존 통신 프로토콜이 서버와 클라이언트 사이에 파일을 전송하고 수신할 수 있기 때문에, 간단히 이를 활용해서 OTA 업데이트 소프트웨어가 다운로드를 할 수 있다. 하지만 통신 프로토콜이 좀더 소박하고 원시 데이터 전송만 가능하다면, OTA 업데이트 소프트웨어가 패킷화를 하고 새로운 애플리케이션 바이너리와 함께 메타데이터를 제공해야 한다. 보안 문제도 마찬가지다. 통신 프로토콜에서 적절히 지원하지 않는다면 기밀유지를 위해서 OTA 업데이트 소프트웨어 측에서 무선으로 전송되는 데이터를 암호화해야 한다.
요컨대, 시스템의 통신 프로토콜이 어떤 기능을 제공하고 보안과 견고성을 위해서 어떤 것들이 필요한지에 따라서 OTA 업데이트 소프트웨어에 적절한 패킷 구조, 클라이언트/서버 동기화, 암호화, 키 교환 등의 사항을 결정해야 한다.

보안 관련 문제의 해결방안
무선으로 전송되는 새로운 애플리케이션의 기밀성을 유지하고, 새로운 애플리케이션이 훼손되지 않았는지 감지하고, 새로운 애플리케이션이 악의적인 누군가가 아니라 신뢰할 수 있는 서버로부터 전송됐다는 것을 확인하기 위해서는 암호화(Cryptographic)된 작업을 수행하는 보안 솔루션이 필요하다. 이를 위한 보안 솔루션에는 암호화(Encryption)와 해싱(Hashing)이라고 알려진 두 가지 방식이 주로 사용된다.
암호화는 클라이언트와 서버 사이에 공유키(패스워드)를 사용해서 무선으로 전송되는 데이터를 노출되지 않도록 한다. 이 MCU의 암호화 하드웨어 가속기는 키 크기에 따라서 AES-128이나 AES-256을 지원한다. 암호화된 데이터와 함께, 서버가 데이터가 훼손되지 않았다는 것을 확인할 수 있는 다이제스트를 전송할 수 있다. 이 다이제스트는 데이터 패킷을 해싱해서 생성된다. 해싱은 고유의 코드를 생성하는 불가역적 수학 함수이다. 서버가 이를 생성한 후에 메시지나 다이제스트의 어느 부분이든 훼손되면 클라이언트가 데이터 패킷에 대해서 동일한 해시 함수를 수행하고 다이제스트를 비교함으로써 어떠한 변경이 일어났다는 것을 알아차린다. 이 MCU의 암호화 하드웨어 가속기가 지원하는 해싱은 SHA-256이다.
[그림 6]은 이 MCU의 암호화 하드웨어 주변장치 기능을 포함한 블록 다이어그램을 보여준다. OTA 업데이트 소프트웨어는 코어텍스-M4(Cortex-M4) 애플리케이션 레이어에 담긴다. [그림 6]에서는 키가 주변장치에 안전하게 저장된다는 것을 알 수 있다. 그러므로 이 기능을 활용해서 OTA 업데이트 소프트웨어 솔루션으로 클라이언트의 키를 안전하게 저장할 수 있다.

[그림 6] ADuCM4050의 하드웨어 암호화 가속기의 블록 다이어그램

또 다른 과제인 인증을 해결하기 위해 주로 사용되는 기법은 비대칭 암호화다. 이 기법은 서버가 공용-비밀 키 쌍을 생성한다. 비밀 키는 서버만 알고 있고 공용 키는 클라이언트에게도 알려준다. 서버가 비밀 키를 사용해 특정 데이터 블록에 대한 시그니처를 생성한다. 무선으로 전송되는 패킷의 다이제스트와 같은 것이다. 시그니처를 클라이언트로 전송하면, 클라이언트는 공용 키를 사용해서 시그니처를 검사한다. 이 방법을 통해 클라이언트는 해당 메시지가 악의적인 제3자가 아닌 서버로부터 전송되었다는 것을 확인할 수 있다. [그림 7]은 이 과정을 보여주는 것으로서, 실선 화살표는 기능 입력/출력을 나타내고, 점선 화살표는 무선으로 전송되는 정보를 나타낸다.

[그림 7] 비대칭 암호화를 사용한 메시지 인증

대부분의 MCU는 이런 비대칭 암호화 동작을 지원하기 위한 하드웨어 가속기를 제공하지 않는다. 대신 Micro-ECC와 같은 소프트웨어 라이브러리를 사용해 구현할 수 있다. 이 방법은 자원이 제한적인 디바이스에 특히 적합하다.
이 라이브러리를 사용하려면 사용자 정의 난수 생성 함수가 필요한데, 이는 MCU의 트루 난수 생성기(True random number generator)를 사용해 구현할 수 있다. 이러한 비동기 암호화 기법은 OTA 업데이트 과정에서의 신뢰성 문제를 해결하지만, 대신 프로세싱 시간을 늘리며 데이터와 함께 시그니처를 전송해야 하므로 패킷 크기도 증가시킨다. 이 검사를 다운로드가 끝났을 때 최종 패킷의 다이제스트나 새로운 소프트웨어 애플리케이션 전체의 다이제스트를 사용해서 한 번만 할 수도 있다. 하지만 이럴 경우 제3자가 클라이언트로 의심스러운 소프트웨어를 전송할 수도 있다. 따라서 매번 시그니처를 검사하는 부담은 피하면서 수신되는 모든 패킷에 대해서 신뢰할 수 있는 서버로부터 전송되었다는 것을 확인할 수 있다면 이상적일 것이다. 이것을 할 수 있는 것이 해시 체인이다.
해시 체인은 앞서 설명한 암호화 개념을 일련의 패킷들에 구현하고, 이 패킷들을 수학적으로 묶어놓은 것이다. [그림 8]과 같이 첫 번째 패킷(0번)은 그 다음 패킷의 다이제스트를 포함한다.
첫 번째 패킷의 페이로드는 실제 소프트웨어 애플리케이션 데이터가 아니라 시그니처다. 두번째 패킷(1번)의 페이로드는 바이너리 부분과 세 번째 패킷(2번)의 다이제스트를 포함한다. 클라이언트는 첫 번째 패킷의 시그니처를 검사하고 다이제스트 H0을 캐시에 저장한다. 두번째 패킷이 도착하면 페이로드를 해싱하고 이것을 H0과 비교한다. 그래서 일치하면 이패킷이 신뢰할 수 있는 서버로부터 전송되었다는 것을 확인할 수 있다.

[그림 8] 패킷 시퀀스에 해시 체인 적용

이 방법으로 매번 시그니처 검사를 해야 하는 부담을 피할 수 있다. 이 체인을 생성하는 부담은 서버가 맡고, 클라이언트는 패킷이 매번 도착할 때마다 캐시에 저장하고 해싱해 패킷이 신뢰할 수 있는 서버로부터 훼손되지 않고 전송되었다는 것을 확인할 수 있다.

시험 셋업
앞서 말한 메모리, 통신, 보안 요구를 충족하는 초저전력 MCU가 ADuCM3029와 ADuCM4050이다. 이들 MCU는 플래시 메모리, SRAM, 암호화 가속기, 트루 난수 생성기를 비롯해, OTA 업데이트에 활용할 수 있는 하드웨어 주변장치 기능들을 포함하고 있다.
또한 MCU용 DFP(Device Family Pack)는 이 디바이스로 OTA 업데이트 솔루션을 구현하기 위한 소프트웨어를 지원한다. 이 DFP는 이 하드웨어를 사용할 수 있도록 간단하고 유연한 인터페이스를 제공하는 주변장치 드라이버도 포함하고 있다.

하드웨어 구성
앞서 논의한 것들을 검증 및 확인할 수 있도록, ADuCM4050을 사용한 OTA 업데이트 소프트웨어 레퍼런스 디자인이 개발됐다. 클라이언트 측의 경우, ADuCM4050 EZ-KIT를 트랜시버 도터 보드 말굽 커넥터를 사용해서 ADF7242에 연결한다. [그림 9]에서 왼쪽이 클라이언트 디바이스다. 서버 측의 경우는 윈도우 PC 환경에서 실행되는 파이썬(Python) 애플리케이션이 개발됐다. 이 파이썬 애플리케이션은 시리얼 포트를 통해 또 다른 ADuCM4050 EZ-KIT와 통신한다. 이 역시 클라이언트와 마찬가지로 ADF7242를 탑재하고 있다. [그림 9]에서 오른쪽의 EZ-KIT는 OTA 업데이트 로직을 실행하는 것이 아니고, ADF7242에서 파이썬 애플리케이션으로 패킷들을 단순히 전달만 한다.

[그림 9] 시험 하드웨어 셋업

소프트웨어 구성
소프트웨어 레퍼런스 디자인은 클라이언트 디바이스의 플래시 메모리를 [그림 3]과 같이 파티셔닝했다. 하지만 다른 구성이나 다른 하드웨어 플랫폼으로도 손쉽게 활용할 수 있도록 메인 클라이언트 애플리케이션을 이식성과 구성가능성을 높이도록 설계했다. [그림 10]은 클라이언트 디바이스의 소프트웨어 아키텍처를 보여준다. 때로는 이 전체 애플리케이션을 SSBL이라고 하기도 하는데, [그림 10]에서 주황색으로 표시한 OTA 업데이트 부분은 앞서 설명한 것처럼 이와 동일한 애플리케이션에서 전체적으로 구현될 때 꼭 필요한 부분은 아니기 때문에, 이제부터는 진정한 SSBL 부분(보라색)과 OTA 업데이트 부분(주황색)을 구분하기로 한다. [그림 10]에서 하드웨어 추상화 레이어(HAL)는 OTA 클라이언트 소프트웨어를 이식할 수 있으며, 어떠한 기본 라이브러리(주황색)로부터도 독립적이도록 만든다.
이 소프트웨어 애플리케이션은 서버로부터 새로운 애플리케이션을 다운로드하기 위한 간단한 통신 프로토콜인 [그림 3]의 부트 시퀀스와, 해시 체인을 구현한다. 통신 프로토콜의 각 패킷은 12바이트 메타데이터 헤더, 64바이트 페이로드, 32바이트 다이제스트를 비롯해, 다음과 같은 기능을 포함한다.

[그림 10] 클라이언트 소프트웨어 아키텍처

캐싱
사용자 구성에 따라, 캐싱을 하지 않거나 플래시 메모리의 한 페이지를 캐싱할 수 있다.

ToC(Table of Contents)
ToC는 두 가지 애플리케이션만 유지하도록 설계돼으며, 새로운 애플리케이션은 항상 가장 오래된 위치로 다운로드됨으로써 폴백 애플리케이션을 유지한다. 이것을 A/B 업데이트 회로라고 한다.

메시징
메시징을 위해서는 사용자 구성에 따라서 ADF7242 또는 UART를 지원한다. UART를 사용하면 [그림 9]에서 왼쪽의 EZ-KIT는 없어지고 클라이언트 용으로 오른쪽의 키트만 남는다. 이 유선 업데이트는 초기 시스템 브링업(Bring-up)이나 디버깅에 유용하다.

시험 결과
기능적 요건을 충족하는 것 외에, 소프트웨어의 성능 역시 프로젝트의 성공 여부를 판단하는 데 있어 중요하다. 임베디드 소프트웨어의 성능을 평가하기 위해서 널리 사용되는 두 가지 지표가 풋프린트와 사이클이다. 풋프린트는 소프트웨어 애플리케이션이 휘발성(SRAM)이나 비휘발성(플래시) 메모리에서 얼마나 많은 공간을 차지하는지를 의미한다.
사이클은 소프트웨어가 특정 작업을 수행하기 위해 사용하는 마이크로프로세서 클록 사이클 수를 말한다. 이는 소프트웨어 실행 시간과도 비슷한데, 다만 OTA 업데이트를 하는 도중 저전력 모드로 들어가 사이클을 소모하지 않을 수도 있다는 점을 반영하기 위한 것이다. 이 소프트웨어 레퍼런스 디자인은 이런 지표에 대해서 최적화된 것은 아니지만, 프로그램을 시험하고 결과를 비교해 보기위한 용도로 유용하다.
[그림 11]과 [그림 12]는 ADuCM4050으로 캐싱을 사용하지 않고 구현된 OTA 업데이트 소프트웨어 레퍼런스 디자인의 풋프린트를 보여준다. 수치는 [그림 10]에서 설명한 요소별로 나눈 것이다. [그림 11]과 같이 전체 애플리케이션이 약 15kB의 플래시 메모리를 사용한다는 것을 알 수 있다. ADuCM4050은 512kB의 플래시 메모리를 제공하기 때문에, 이에 비하면 매우 적은 양이라고 할 수 있다.

[그림 11] 플래시 풋프린트(바이트)

실제 애플리케이션 소프트웨어(OTA 업데이트 프로세스를 위해 개발된 소프트웨어)는 1.5kB에 불과하며, 나머지는 DFP, Micro-ECC, ADF7242 스택 같은 라이브러리에 사용되고 있다. 이 결과를 보면 SSBL이 시스템에서 어떤 역할을 하는지 이해할 수 있다. 15kB 풋프린트의 대부분이 업데이트 프로세스를 위한 것이다. SSBL 자체는 500바이트의 풋프린트 밖에 차지하지 않으며, 플래시 드라이버 같은 장치 액세스를 위해서 추가적인 1~2kB의 DFP 코드가 사용된다.
소프트웨어로 인한 오버헤드를 평가하기 위해서 데이터 패킷을 수신할 때마다 사이클 횟수를 집계하고 패킷당 소모되는 평균 사이클 횟수를 계산했다. 매 데이터 패킷마다 AES-128 암호해독, SHA-256 해싱, 플래시 메모리에 대한 쓰기, 일부 패킷 메타데이터 확인이 필요하다. 64바이트의 패킷 페이로드 크기에 캐싱을 하지 않을 때, 하나의 데이터 패킷을 처리하기 위한 오버헤드가 7409 사이클인 것으로 나타났다. 26MHz 코어 클록을 사용한다고 가정하면 이는 약 285마이크로초의 프로세싱 시간에 해당된다. 이 계산은 ADuCM4050 DFP에 들어 있는 사이클 카운팅 드라이버를 사용해서 했으며, 100kB 바이너리 다운로드(약 1500 패킷)를 할 때의 평균이다.
이처럼 패킷당 최소한의 오버헤드를 달성할 수 있는 것은 버스 트랜잭션을 처리할 때 DFP의 드라이버가 ADuCM4050의 DMA(Direct Memory Access) 하드웨어를 활용하고, 매 트랜잭션을 처리할 때마다 프로세서를 저전력 슬립 상태로 전환하기 때문이다. DFP의 저전력 슬립 모드를 정지시키고 버스 트랜잭션을 DMA를 사용하지 않도록 변경하면 데이터 패킷당 오버헤드는 1만 7297사이클로 늘어난다. 따라서 장치 드라이버를 효율적으로 사용하는 것이 임베디드 소프트웨어 애플리케이션에 얼마나 영향을 미치는지 알 수 있다.
패킷당 데이터 바이트가 얼마되지 않기 때문에 오버헤드가 낮기도 하지만, 패킷당 데이터 바이트를 128바이트로 두 배로 늘리더라도 사이클은 조금밖에 늘어나지 않는다. 동일한 시험일 경우, 8362 사이클인 것으로 나타났다.
앞서 설명한 것처럼, 패킷 데이터를 매번 플래시 메모리에 쓰는 대신 캐싱을 하는 것 역시 사이클과 풋프린트에 영향을 미친다. 플래시 메모리의 한 페이지를 캐싱하면 데이터 패킷당 오버헤드는 7409 사이클에서 5904 사이클로 줄어든다. 이처럼 약 20% 줄어든 것은 매번 플래시 메모리에 쓰기를 하는 것이 아니라 캐시가 가득 찼을 때에만 플래시에 쓰기를 하기 때문이다. 그 대신, SRAM 풋프린트는 증가한다. 캐싱을 하지 않으면 [그림 12]의 HAL에 336바이트의 SRAM만을 필요로 한다. 하지만 캐싱을 사용할 때에는 플래시 메모리에 한 페이지만큼의 공간을 할당해야 한다. 그러므로 SRAM 사용량이 2388바이트로 늘어난다. 또한 캐시를 언제 비워야 할 지 판단하는데 필요한 추가적인 코드로 인해서 HAL에 필요한 플래시 메모리 양이 조금 더 늘어난다.

[그림 12] SRAM 풋프린트(바이트)

이런 결과는 설계를 어떻게 결정하느냐가 소프트웨어 성능에 실질적인 영향을 미친다는 것을 보여준다. 어떤 경우에나 적합한 만능 솔루션은 있을 수 없다. 각각의 시스템마다 요건이나 제약이 다르기 때문이다. 따라서 OTA 업데이트 소프트웨어 역시 각 상황마다 그에 맞춰 설계해야만 한다.

 

글 | 벤자민 버클린 브라운(Benjamin Bucklin Brown)

제공 | 아나로그디바이스