Zephyr v3.7.0 · Flash / Flash map / Disk Access

플래시에는 두 개의 좌표가 있다

플래시 드라이버는 장치 시작점에서 센 오프셋을 받고, flash map은 파티션 시작점에서 센 오프셋을 받는다. 어느 좌표를 쓰는지와 누가 그 영역을 소유하는지 먼저 정해야 지우기·쓰기 실패를 해석할 수 있다.

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

기준은 Zephyr v3.7.0이다. 아래 주소·크기는 구조를 보이기 위한 가상 보드의 예시이며 실제 보드에 복사할 파티션 배치가 아니다. 이 글은 raw flash와 flash map의 경계를 다룬다. 키/값의 회복 절차는 Settings·NVS, 파일 이름과 마운트는 파일 시스템 글의 주제다. 그림과 표는 이 글을 위해 새로 그렸다.

1. 같은 플래시라도 호출자가 보는 단위가 다르다

Zephyr 3.7.0의 Flash API는 struct device에 대해 flash_read(), flash_write(), flash_erase()를 제공한다. Flash map은 devicetree의 fixed-partitions에서 만들어진 flash_area를 ID로 찾아, 영역 안의 상대 오프셋을 확인한 다음 하위 플래시 드라이버로 보낸다. 범위 검사는 추가되지만 지우기 페이지 크기와 쓰기 블록 크기가 사라지는 것은 아니다.

그림 1. 저장소 인터페이스를 내려갈 때 바뀌는 주소 단위

↔ 좁은 화면에서 오른쪽으로 이동해 각 계층의 입력 단위를 비교하세요.

① 호출자flash_area_read(fa, rel, ...)자기 영역의 시작을 0으로 센다.
② Flash mapfa_off + relfa_size 바깥 요청을 막고 backing device를 고른다.
③ Flash APIflash_read(dev, absolute, ...)장치 시작점 기준 오프셋으로 드라이버를 부른다.
④ 실제 매체지우기 페이지 · 최소 쓰기 블록정렬과 프로그래밍 제약은 드라이버·하드웨어가 결정한다.
표 1. Flash 장치와 파티션 API의 좌표
호출오프셋의 원점제한 범위주의점
flash_read/write/erase(dev,...)플래시 장치 시작장치·드라이버가 허용한 범위호출자가 파티션 경계를 별도로 지켜야 한다.
flash_area_read/write/erase(fa,...)해당 파티션 시작fa_size 이내하위 드라이버의 정렬·상태 조건은 그대로 적용된다.
FIXED_PARTITION_OFFSET(label)플래시 장치 시작DTS가 선언한 값flash_area_*에 넘길 상대 오프셋으로 혼동하면 안 된다.

근거: 3.7.0 Flash API의 장치 기준 오프셋, 3.7.0 Flash map의 영역 범위 검사와 상대 오프셋.

2. DTS 파티션은 주소 약속이지 독점 잠금이 아니다

fixed-partitions 아래의 각 reg는 플래시 장치 시작점에서 잰 오프셋과 크기다. 아래 가상 장치의 raw 파티션이 0x30000에서 시작한다면 영역 안의 0x100은 장치 기준 0x30100이다. FIXED_PARTITION_ID(app_raw_partition)로 ID를 얻어 flash_area_open()에 넘긴다. 구획 선언만으로 다른 코드가 같은 파티션을 열지 못하게 되거나 지우기 단위에 자동 정렬되는 것은 아니다.

다음은 플래시 장치 노드 아래에 놓일 파티션 선언의 축약 예시다. 이미 파티션 노드가 있는 보드라면 중복 선언하지 않고 기존 지도에 통합해야 한다. 0x30000과 0x10000이 실제 장치의 용량·페이지 경계·부트 영역에 맞는지는 이 조각만으로 알 수 없다.

partitions {                     /* flash device 노드의 자식 */
    compatible = "fixed-partitions";
    #address-cells = <1>;
    #size-cells = <1>;
    app_raw_partition: partition@30000 {
        label = "app-raw";
        reg = <0x30000 0x10000>;
    };
};
그림 2. 파티션 시작점에서 좌표를 다시 세기

↔ 가상 숫자 예시입니다. 장치 전체와 선택한 영역의 서로 다른 원점을 확인하세요.

플래시 장치 시작 0x00000 →
다른 소유자부트·이미지 등의 예약 영역
app_raw_partition장치 기준 시작 0x30000 · 크기 0x10000영역 기준 rel = 0x100 → 장치 기준 0x30100
다른 소유자파일 시스템·NVS 등의 별도 영역
직접 Flash API
flash_read(dev, 0x30100, ...)
Flash map API
flash_area_read(fa, 0x100, ...)
표 2. DTS 파티션과 flash area 필드
선언·필드뜻검토할 조건
reg = <offset size>장치 기준 시작과 바이트 크기실제 용량, 이웃 파티션, 지우기 페이지 경계가 맞는가?
FIXED_PARTITION_ID(label)v3.7.0의 고정 파티션 노드 라벨 → flash map ID해당 라벨이 빌드된 DTS에 존재하고 활성화됐는가?
fa->fa_off / fa->fa_size장치 기준 영역 시작 / 영역 크기호출자는 fa_off를 상대 오프셋에 다시 더하지 않는다.
read-only 속성DTS가 영역을 읽기 전용으로 기술별도 정책·드라이버 제약까지 확인한다. 공유 영역 동시 사용을 중재하지 않는다.

근거: 3.7.0 fixed-partitions 예시·매크로, v3.7.0 공개 헤더.

3. 영역 범위와 지우기·쓰기 정렬은 별도로 검사한다

명시적 지우기가 필요한 플래시에서 새 값을 쓰려면 그 매체의 지우기 조건을 먼저 만족해야 한다. flash_area_align(fa)은 최소 쓰기 블록 크기를 알려 주지만 지우기 페이지 크기를 알려 주는 함수는 아니다. 지우기는 드라이버가 지원하는 페이지 정보(CONFIG_FLASH_PAGE_LAYOUT 및 flash_get_page_info_by_offs() 등)와 실제 장치 사양으로 따로 확인한다. 읽기·쓰기·지우기 호출의 성공은 0, 실패는 음수 오류값이며, 쓰기와 지우기는 한 번의 논리적 트랜잭션이 아니다.

그림 3. raw flash 변경의 네 검사 지점

↔ 각 칸은 서로 다른 조건입니다. 앞 칸 성공이 뒤 칸 성공을 보장하지 않습니다.

① 소유권·범위이 영역을 다른 서브시스템이 쓰지 않는지 확인. 상대 오프셋과 길이가 fa_size 안인가?
② 지우기명시적 지우기 장치라면 지원하는 페이지 시작·길이에 맞게 지운다. 주변 데이터도 같은 페이지에 있는지 확인.
③ 쓰기드라이버의 최소 쓰기 블록에 맞춘 오프셋·길이로 쓴다. 영역 경계를 검사했다고 정렬까지 맞는 것은 아니다.
④ 검증·복구필요하면 읽어 비교한다. 전원 중단·부분 변경 뒤 어떤 버전을 선택할지는 상위 포맷이 설계해야 한다.
표 3. 읽기·쓰기·지우기의 별도 제약
동작v3.7.0 API 조건이것만으로 보장되지 않는 것
flash_area_read영역 내 상대 범위. 하위 드라이버의 읽기 계약을 따른다.데이터의 형식·CRC·최신 버전의 유효성
flash_area_write영역 범위 + 드라이버의 최소 쓰기 블록 크기·오프셋 정렬지우지 않은 비트를 임의 값으로 변경하는 능력, 원자성
flash_area_erase영역 범위 + 장치별 지우기 페이지 시작·크기 제약다른 데이터가 같은 페이지에 없다는 보증, 마모 수명
flash_area_erased_val장치가 보고하는 지워진 바이트 값모든 장치가 항상 0xFF라는 가정

지우기와 쓰기를 한 함수처럼 취급하지 않는다. flash_area_*는 영역 밖 요청을 막는 래퍼이며 원자적 레코드 갱신, 쓰기 횟수 분산, 전원 복구 절차는 제공한다고 약속하지 않는다. 이 요구가 있으면 레코드 포맷과 복구 규칙을 설계하거나 적합한 상위 저장소를 선택한다.

근거: 3.7.0 flash area API의 범위·정렬 계약, 3.7.0 flash_write·flash_erase·page info.

4. 먼저 읽기만으로 파티션 좌표를 확인한다

아래 코드는 보드의 DTS에 app_raw_partition 라벨이 존재하고 Flash map이 활성화됐다는 조건의 읽기 전용 예시다. 실제 쓰기 실험 전에는 해당 보드의 파티션 소유권과 지우기 페이지 경계를 따로 확인한다. 범위를 열었다고 곧바로 지우거나 쓰지 않는다.

#include <errno.h>
#include <stdint.h>
#include <zephyr/storage/flash_map.h>

int read_raw_header(uint8_t header[16])
{
    const struct flash_area *area;
    int rc = flash_area_open(FIXED_PARTITION_ID(app_raw_partition), &area);
    if (rc != 0) {
        return rc;                  /* ID 없음, map/driver 문제 등 */
    }
    if (area->fa_size < 16U) {
        flash_area_close(area);
        return -ERANGE;
    }
    rc = flash_area_read(area, 0, header, 16);
    flash_area_close(area);
    return rc;                       /* 0 또는 음수 오류값 */
}

flash_area_close()는 v3.7.0 헤더에서 현재 아무 작업도 하지 않는 함수지만, open/close API의 쌍을 코드에 남기면 수명 경계가 분명해진다. flash_area_read()의 성공 반환값은 읽은 바이트 수가 아니라 0이다. 위 함수는 데이터 내용의 유효성을 판정하지 않는다.

근거: v3.7.0 flash_map.h의 ID·open/close·read 선언, 3.7.0 공식 사용 예시와 오류 계약.

5. 파티션의 주인은 한 저장 포맷으로 정한다

같은 영역을 raw 코드와 NVS, LittleFS, MCUboot, flash-disk가 동시에 소유하면 각자의 메타데이터·지우기 단위가 충돌한다. flash_area_open()은 이름에서 영역을 찾는 동작이지 독점 잠금이 아니다. DTS, 빌드 설정, 부트로더 파티션 계획을 함께 검토해야 한다.

표 4. 저장 영역별 소유권과 충돌
영역의 주인주로 다루는 단위raw 코드가 같은 영역을 쓰면
MCUboot 이미지 슬롯이미지·업데이트 상태부팅 가능한 이미지와 업데이트 절차가 깨질 수 있다.
NVS / Settings 백엔드ID·레코드 / 설정 키메타데이터와 회복 규칙 밖에서 페이지를 지우게 된다.
LittleFS 등 파일 시스템파일·디렉터리·내부 블록파일 시스템이 소유한 블록을 덮어쓴다.
flash-disk에뮬레이션된 디스크 섹터캐시·섹터 이미지와 원시 바이트 쓰기가 불일치한다.
애플리케이션 전용 raw 영역애플리케이션이 정의한 레코드다른 주인이 없다면 자체 유효성·복구·마모 정책이 필요하다.

보드를 바꿀 때 검사할 것: 부트로더와 애플리케이션이 같은 파티션 지도를 보는가, 예약 영역이 겹치지 않는가, 외부 플래시 장치가 초기화됐는가, 선택한 영역의 시작·끝이 지우기 페이지 경계와 맞는가. DTS의 read-only 속성을 애플리케이션 간 동시 접근을 조정하는 잠금으로 해석하지 않는다.

근거: 3.7.0 boot/storage 파티션 용도, 3.7.0 저장소 서브시스템 구분.

6. flash-disk는 파티션을 블록 장치로 다시 보여준다

v3.7.0 Disk Access 문서는 zephyr,flash-disk 노드의 partition이 flash map 파티션을 가리키고, disk-name과 cache-size를 설정하는 예시를 보여준다. 이 드라이버는 플래시를 디스크 섹터 인터페이스로 바꿔 상위 소비자에게 제공한다. 따라서 disk_access_read/write()의 start_sector는 섹터 번호이며 flash_area_read/write()의 off 바이트와 같은 값이 아니다.

표 5. Raw flash에서 파일 경로까지의 단위
경로호출자가 주는 단위그 경로가 맡는 일
Flash API장치 기준 바이트 오프셋드라이버의 물리적 read/write/erase
Flash map영역 기준 바이트 오프셋정해진 파티션 안으로 요청을 제한
flash-disk / Disk Access디스크 이름·섹터 번호·섹터 수flash 파티션을 block device로 변환
파일 시스템마운트된 경로·파일 오프셋파일 이름·내용·메타데이터 관리

문서의 cache-size = <4096>는 특정 예시 값이다. v3.7.0 Disk Access 문서는 이 캐시 크기를 backing 파티션의 최소 지우기 블록 크기와 같게 잡도록 안내한다. 실제 장치의 페이지 구성과 해당 버전 드라이버·DTS 바인딩 조건을 확인해야 한다. flash-disk를 붙였다고 해서 전원 중단 복구나 플래시 수명이 자동으로 보장되지는 않는다.

근거: 3.7.0 flash-disk DTS 예시와 Disk Access API, v3.7.0 flashdisk 드라이버.

7. 오류값보다 먼저 어느 경계에서 실패했는지 본다

표 6. 실패 관측별 첫 진단 경계
관측먼저 볼 경계다음 확인
flash_area_open 실패flash map·ID·드라이버생성된 DTS 라벨과 설정, backing 장치 존재 여부
영역 바깥 offset/lenflash map 범위 검사상대 오프셋에 fa_off를 중복 가산했는가?
범위 안이지만 write/erase 실패드라이버·장치 정렬쓰기 블록과 지우기 페이지를 혼동했는가?
재부팅 뒤 레코드 불일치애플리케이션 포맷·복구전원이 끊겼을 때의 유효성 표시, 복제본, checksum 규칙이 있는가?
다른 서브시스템 데이터 손상영역 소유권부트/FS/NVS/flash-disk와 파티션이 겹쳤는가?

정확한 음수 errno는 경계와 드라이버에 따라 달라진다. flash_area_open() 문서는 map 부재, 알 수 없는 ID, 드라이버 부재를 구분한다. 이후 I/O 실패는 하위 플래시 드라이버도 조사해야 한다. 반환값만 보고 데이터가 안전하게 롤백됐다고 가정하지 않는다.

근거: 3.7.0 Flash map 반환값, 3.7.0 Flash driver I/O 계약.

8. 소스와 재현 범위

이 글은 Zephyr 릴리스 v3.7.0의 공식 문서와 같은 태그의 공개 헤더·드라이버를 기준으로 썼다. 숫자 그림은 설명용 가상 주소이고, 실제 보드의 DTS·플래시 지우기 크기·서브시스템 구성은 독자가 고정해야 한다. 보드에서의 소거/쓰기를 실행해 검증했다는 뜻이 아니다.

표 7. Zephyr 3.7.0 저장소 근거
고정 근거확인할 부분
Flash API장치 기준 오프셋, 최소 쓰기 블록, 지우기 페이지 제약
Flash map · 공개 헤더고정 파티션 ID, 영역 상대 오프셋, 범위·반환값
Disk Access · flashdiskflash-disk 노드와 섹터 인터페이스의 경계