eMMC/MMC 서브시스템 — Embedded MultiMediaCard & Linux MMC Framework
eMMC(embedded MultiMediaCard)는 NAND 플래시와 컨트롤러를 하나의 BGA 패키지에 통합한 임베디드 스토리지 솔루션으로,
스마트폰, 태블릿, IoT 디바이스, 자동차 인포테인먼트 등에 널리 사용됩니다.
이 문서에서는 JEDEC eMMC 스펙의 프로토콜 기초부터 Linux 커널의 MMC 서브시스템(drivers/mmc/) 구조,
SDHCI 호스트 컨트롤러 프레임워크, HS200/HS400 버스(Bus) 튜닝, EXT_CSD 레지스터(Register), 하드웨어 파티션(Boot/RPMB/GP),
인라인 암호화(ICE), 디바이스 트리(Device Tree) 바인딩, sysfs 인터페이스, 디버깅(Debugging), 성능 튜닝,
UFS와의 비교까지 eMMC/MMC의 모든 것을 심층 분석합니다.
핵심 요약
- mmc_host — 호스트 컨트롤러 하나를 대표하는 최상위 구조체(Struct). 클럭, 버스 폭, 전압, 타이밍 정보를 관리합니다.
- mmc_card — 탐지된 eMMC/SD 카드 하나를 나타내며, CID/CSD/EXT_CSD 레지스터 정보를 담고 있습니다.
- SDHCI — SD Host Controller Interface. 대부분의 eMMC 호스트 컨트롤러가 구현하는 표준 레지스터 맵입니다.
- EXT_CSD — 512바이트 Extended CSD 레지스터. eMMC 고유의 설정(버스 모드, 파티션, RPMB 등)을 담고 있습니다.
- /dev/mmcblk0 — eMMC 블록 디바이스 노드.
mmcblk0p1,mmcblk0boot0,mmcblk0rpmb등 하위 파티션이 생성됩니다.
| 항목 | eMMC | SD Card | UFS | NVMe |
|---|---|---|---|---|
| 인터페이스 | 병렬 8-bit MMC | 병렬 4-bit SD | 직렬 MIPI M-PHY (2 레인) | 직렬 PCIe (x1~x4) |
| 최대 속도 | 400 MB/s (HS400) | 985 MB/s (SD Express) | 2.9 GB/s (UFS 4.0) | ~14 GB/s (PCIe 5.0 x4) |
| 버스 폭 | 1/4/8-bit | 1/4-bit | 1~2 레인 (각 방향) | x1/x2/x4 레인 |
| 프로토콜 | MMC (half-duplex) | SD (half-duplex) | SCSI (full-duplex) | NVMe (full-duplex) |
| 커맨드 큐 | CMDQ (최대 32개) | 미지원 | 최대 32개 | 최대 65535개 |
| 전형적 용도 | 스마트폰, IoT, 자동차 | 카메라, 확장 스토리지 | 플래그십 스마트폰 | PC, 서버, 워크스테이션 |
| 디바이스 노드 | /dev/mmcblk0 | /dev/mmcblk1 | /dev/sda | /dev/nvme0n1 |
| 폼 팩터 | BGA (11.5×13mm) | Full/Mini/Micro SD | BGA (11.5×13mm) | M.2 / U.2 / AIC |
| 스펙 버전 | 발표 연도 | 최대 버스 모드 | 최대 전송 속도 | 주요 특징 |
|---|---|---|---|---|
| eMMC 4.3 | 2007 | High Speed (52 MHz) | 52 MB/s | Boot 파티션, RPMB 도입 |
| eMMC 4.41 | 2010 | DDR52 (52 MHz DDR) | 104 MB/s | DDR 모드, Enhanced Area 지원 |
| eMMC 4.5 | 2011 | DDR52 | 104 MB/s | TRIM/Sanitize, HPI, BKOPS, 캐시(Cache) |
| eMMC 5.0 | 2013 | HS200 (200 MHz SDR) | 200 MB/s | HS200, 슬립(Sleep) 알림(Sleep Notification) |
| eMMC 5.1 | 2015 | HS400 (200 MHz DDR) | 400 MB/s | HS400, CMDQ(Command Queuing), Secure Write Protect |
| eMMC 5.1A | 2016 | HS400ES (Enhanced Strobe) | 400 MB/s | HS400ES(Data Strobe), FFU(Field Firmware Update) |
/dev/mmcblk0으로 나타나며, 사용자 데이터 파티션은 mmcblk0p1, mmcblk0p2, ... 로 표시됩니다.
하드웨어 부트 파티션은 mmcblk0boot0, mmcblk0boot1이 되고,
RPMB(Replay Protected Memory Block) 파티션은 mmcblk0rpmb로 나타납니다.
SD 카드가 추가로 있으면 mmcblk1이 됩니다. 호스트 컨트롤러 인덱스와 탐지 순서에 따라 번호가 결정됩니다.
단계별 이해
-
1단계 — 호스트가 CMD 라인으로 커맨드 전송
호스트 컨트롤러(SDHCI)는 CMD 라인(1-bit 양방향)을 통해 48비트 커맨드 프레임을 전송합니다. 커맨드 프레임은 시작 비트(0), 전송 비트(1=호스트→카드), 6비트 커맨드 인덱스, 32비트 인자(argument), 7비트 CRC, 끝 비트(1)로 구성됩니다. 예를 들어CMD17(READ_SINGLE_BLOCK)을 보내면 인자 필드에 읽고자 하는 블록 주소가 들어갑니다.커맨드 프레임 구조 (48비트):
[Start:1][Tx:1][Cmd Index:6][Argument:32][CRC7:7][End:1] -
2단계 — eMMC가 응답(Response) 반환
eMMC는 같은 CMD 라인을 통해 응답을 돌려보냅니다. 응답 타입에 따라 48비트(R1, R3, R6 등) 또는 136비트(R2 — CID/CSD 전체)가 전송됩니다. 응답에는 카드 상태 비트(ready, error 플래그 등)가 포함되어 호스트가 후속 동작을 결정할 수 있습니다. -
3단계 — DAT0~7 라인으로 데이터 전송
데이터 전송이 필요한 커맨드(ADTC 타입)의 경우, 데이터는 DAT0~DAT7의 8비트 병렬 버스를 통해 전송됩니다. eMMC 5.1의 HS400 모드에서는 200MHz DDR로 동작하여 클럭 엣지마다 8비트 × 2(더블 데이터 레이트) = 16비트를 전송, 이론적 최대 400 MB/s를 달성합니다. 각 블록(512바이트) 전송 후 CRC16 체크가 수행됩니다. -
4단계 — 블록 레이어와 mmc_block 통합
Linux 커널에서 사용자 공간(User Space)의read()/write()요청은 VFS → 파일시스템(Filesystem) → 블록 레이어(blk-mq)를 거쳐mmc_blk_mq_issue_rq()함수에 도달합니다. 이 함수가mmc_start_request()를 호출하여 실제 MMC 커맨드를 호스트 컨트롤러에 전달합니다. CMDQ(Command Queuing)가 활성화된 경우 최대 32개의 요청을 동시에 큐에 넣을 수 있습니다.blk-mq 연결: eMMC는mmc_init_queue()에서 blk-mq 하드웨어 큐를 1개(hw_queue = 1)로 설정합니다. CMDQ 모드에서는 CQE(Command Queue Engine) 드라이버가 하드웨어 레벨에서 다중 요청을 병렬 처리합니다. -
5단계 — 사용자 공간에서 /dev/mmcblk0pN 접근
사용자는/dev/mmcblk0p1같은 블록 디바이스 노드를 통해 일반 블록 디바이스처럼 eMMC에 접근합니다. 파일시스템(ext4, f2fs 등)을 마운트(Mount)하거나,dd/fio로 직접 블록 I/O를 수행할 수 있습니다. 부트 파티션(mmcblk0boot0)은 기본적으로 쓰기 보호(Write Protection)되어 있으며,echo 0 > /sys/block/mmcblk0boot0/force_ro로 해제해야 쓸 수 있습니다.# eMMC 디바이스 확인 lsblk | grep mmcblk # 출력 예시: # mmcblk0 179:0 0 29.1G 0 disk # ├─mmcblk0p1 179:1 0 256M 0 part /boot # ├─mmcblk0p2 179:2 0 28.8G 0 part / # mmcblk0boot0 179:32 0 4M 1 disk # mmcblk0boot1 179:64 0 4M 1 disk # eMMC 정보 확인 cat /sys/class/mmc_host/mmc0/mmc0:0001/name cat /sys/class/mmc_host/mmc0/mmc0:0001/fwrev cat /sys/class/mmc_host/mmc0/mmc0:0001/hwrev
eMMC 프로토콜 기초
eMMC는 JEDEC 표준에 정의된 MMC 프로토콜을 사용합니다. 모든 통신은 호스트가 CMD 라인으로 커맨드를 보내고, 카드가 응답을 반환하는 마스터-슬레이브 구조입니다. 데이터 전송은 DAT 라인을 통해 이루어지며, 호스트가 제공하는 CLK 신호에 동기화됩니다.
커맨드 타입
MMC 커맨드는 응답 유무와 데이터 전송 여부에 따라 4가지로 분류됩니다.
| 타입 | 약어 | 응답 | 데이터 | 설명 | 예시 |
|---|---|---|---|---|---|
| Broadcast Command | BC | 없음 | 없음 | 모든 카드에 전송, 응답 없음 | CMD0 (GO_IDLE_STATE) |
| Broadcast Command with Response | BCR | 있음 | 없음 | 모든 카드에 전송, 응답 있음 | CMD1 (SEND_OP_COND) |
| Addressed Command | AC | 있음 | 없음 | 특정 카드 주소 지정, 데이터 없음 | CMD7 (SELECT_CARD) |
| Addressed Data Transfer Command | ADTC | 있음 | 있음 | 특정 카드 주소 지정, 데이터 전송 | CMD17 (READ_SINGLE_BLOCK) |
응답 타입
| 응답 타입 | 길이 | 내용 | CRC | 사용 커맨드 |
|---|---|---|---|---|
| R1 | 48비트 | 카드 상태(Card Status) 32비트 | CRC7 | 대부분의 AC/ADTC 커맨드 |
| R1b | 48비트 | R1과 동일 + DAT0 busy 신호 | CRC7 | CMD6 (SWITCH), CMD12 (STOP) |
| R2 | 136비트 | CID 또는 CSD 레지스터 (128비트) | CRC7 | CMD2 (ALL_SEND_CID), CMD9 (SEND_CSD) |
| R3 | 48비트 | OCR 레지스터 (32비트) | 없음 | CMD1 (SEND_OP_COND) |
주요 MMC 커맨드
| 커맨드 | 이름 | 타입 | 응답 | 설명 |
|---|---|---|---|---|
| CMD0 | GO_IDLE_STATE | BC | 없음 | 카드를 Idle 상태로 리셋 |
| CMD1 | SEND_OP_COND | BCR | R3 | 동작 전압 협상 및 카드 초기화 |
| CMD2 | ALL_SEND_CID | BCR | R2 | 카드 식별 정보(CID) 요청 |
| CMD3 | SET_RELATIVE_ADDR | AC | R1 | 상대 주소(RCA) 할당 |
| CMD6 | SWITCH | AC | R1b | EXT_CSD 레지스터 필드 변경 (버스 모드, 파티션 등) |
| CMD7 | SELECT_CARD | AC | R1b | RCA로 카드 선택/해제 (Transfer State 진입) |
| CMD8 | SEND_EXT_CSD | ADTC | R1 | EXT_CSD 512바이트 읽기 |
| CMD13 | SEND_STATUS | AC | R1 | 카드 상태(Card Status) 조회 |
| CMD17 | READ_SINGLE_BLOCK | ADTC | R1 | 단일 블록(512B) 읽기 |
| CMD18 | READ_MULTIPLE_BLOCK | ADTC | R1 | 다중 블록 연속 읽기 |
| CMD23 | SET_BLOCK_COUNT | AC | R1 | 다음 다중 블록 전송의 블록 수 설정 |
| CMD24 | WRITE_BLOCK | ADTC | R1 | 단일 블록(512B) 쓰기 |
| CMD25 | WRITE_MULTIPLE_BLOCK | ADTC | R1 | 다중 블록 연속 쓰기 |
버스 폭 모드
eMMC는 초기화 시 1-bit 모드(DAT0만 사용)로 시작하며, 호스트가 CMD6(SWITCH)를 통해
EXT_CSD[183] BUS_WIDTH 필드를 변경하여 4-bit 또는 8-bit 모드로 전환합니다.
DDR 모드에서는 클럭의 상승/하강 엣지 모두에서 데이터를 전송하여 동일 클럭 주파수에서 2배의 대역폭(Bandwidth)을 얻습니다.
| BUS_WIDTH 값 | 모드 | 데이터 라인 | DDR | 비고 |
|---|---|---|---|---|
| 0 | 1-bit | DAT0 | SDR | 초기화 기본값 |
| 1 | 4-bit | DAT0~3 | SDR | SD 카드 호환 모드 |
| 2 | 8-bit | DAT0~7 | SDR | eMMC 전용 최대 폭 |
| 5 | 4-bit DDR | DAT0~3 | DDR | DDR52 4-bit |
| 6 | 8-bit DDR | DAT0~7 | DDR | DDR52 / HS400 / HS400ES |
CMD23으로 블록 수를 미리 설정한 뒤 CMD18/CMD25를 보내면 "pre-defined" 전송이 되어
마지막 블록 전송 후 자동으로 종료됩니다.
CMD23 없이 CMD18/CMD25를 보내면 "open-ended" 전송이 되어
호스트가 명시적으로 CMD12(STOP_TRANSMISSION)를 보내야 합니다.
리눅스 커널은 CMD23을 지원하는 eMMC에서는 항상 pre-defined 방식을 사용합니다.
하드웨어 아키텍처
eMMC는 하나의 BGA 패키지 안에 NAND 플래시 어레이, 플래시 컨트롤러(FTL/ECC/WL), 호스트 인터페이스 로직을 통합한 관리형(Managed) NAND 솔루션입니다. 호스트 SoC에는 SDHCI 호환 컨트롤러가 내장되어 있으며, eMMC와 직접 연결됩니다.
eMMC 내부 블록 구성
eMMC 디바이스 내부는 크게 4개의 주요 블록으로 구성됩니다:
- NAND Flash Array: MLC/TLC/QLC NAND 셀로 구성된 실제 저장 영역. 다이(Die) 수에 따라 용량이 결정됩니다.
- Flash Controller: FTL(Flash Translation Layer), ECC(Error Correction Code), Wear Leveling, Bad Block Management를 수행하는 내장 컨트롤러입니다.
- Host Interface: MMC 프로토콜을 처리하는 버스 인터페이스 로직. CMD/DAT 라인 구동, CRC 생성/검증을 담당합니다.
- SRAM Buffer: 읽기/쓰기 캐시 및 EXT_CSD/CSD/CID 레지스터를 저장하는 내부 버퍼(Buffer)입니다.
BGA 핀 배치
| 핀 이름 | 수 | 방향 | 설명 |
|---|---|---|---|
| CLK | 1 | Host → eMMC | 클럭 신호. 모든 버스 동작의 동기 기준 (최대 200 MHz) |
| CMD | 1 | 양방향 | 커맨드/응답 전송 라인 (OD/PP 모드) |
| DAT0~DAT7 | 8 | 양방향 | 8-bit 데이터 버스. 1/4/8-bit 모드 선택 가능 |
| DS (Data Strobe) | 1 | eMMC → Host | HS400 모드에서 데이터 샘플링 스트로브 (eMMC 5.1+) |
| RST_n | 1 | Host → eMMC | 하드웨어 리셋 핀 (Active Low) |
| VCC | 다수 | 전원 | NAND 플래시 전원 (2.7V~3.6V) |
| VCCQ | 다수 | 전원 | I/O 인터페이스 전원 (1.2V / 1.8V / 3.3V) |
| VSS | 다수 | GND | 접지 |
전압 모드
| VCCQ 전압 | 시그널링 | 지원 버스 모드 | 비고 |
|---|---|---|---|
| 3.3V | 3.3V LVCMOS | Legacy, High Speed, DDR52 | 하위 호환 모드 |
| 1.8V | 1.8V LVCMOS | HS200, DDR52 | 저전력 시그널링 |
| 1.2V | 1.2V LVCMOS | HS200, HS400, HS400ES | 최고 속도 모드 필수 |
속도 모드별 클럭 및 대역폭
| 모드 | 클럭 주파수 | 버스 폭 | 전송 방식 | 최대 대역폭 | VCCQ |
|---|---|---|---|---|---|
| Legacy | 0~26 MHz | 1/4/8-bit | SDR | 26 MB/s | 3.3V |
| High Speed (HS) | 0~52 MHz | 1/4/8-bit | SDR | 52 MB/s | 3.3V |
| DDR52 | 0~52 MHz | 4/8-bit | DDR | 104 MB/s | 3.3V / 1.8V |
| HS200 | 0~200 MHz | 4/8-bit | SDR | 200 MB/s | 1.8V / 1.2V |
| HS400 | 0~200 MHz | 8-bit | DDR | 400 MB/s | 1.8V / 1.2V |
| HS400ES | 0~200 MHz | 8-bit | DDR + Strobe | 400 MB/s | 1.8V / 1.2V |
0x0001로 고정되며,
카드 탐지(Card Detection) 핀이 필요 없습니다.
커널에서는 MMC_CAP_NONREMOVABLE 플래그로 이를 표시합니다.
Linux MMC 서브시스템 구조
Linux 커널의 MMC 서브시스템은 drivers/mmc/ 디렉토리에 위치하며,
eMMC, SD 카드, SDIO 디바이스를 통합적으로 관리합니다. 계층적 구조로 설계되어 있어
호스트 컨트롤러 드라이버와 카드 디바이스 드라이버를 분리합니다.
디렉토리 구조
drivers/mmc/
├── core/ # MMC 코어 프레임워크
│ ├── bus.c # MMC 버스 드라이버 (mmc_bus_type)
│ ├── core.c # 코어 기능: 커맨드 전송, 클럭/전압 설정
│ ├── host.c # mmc_host 등록/해제
│ ├── mmc.c # eMMC 카드 초기화 (CMD1→CMD2→CMD3→CMD7→EXT_CSD)
│ ├── sd.c # SD 카드 초기화
│ ├── sdio.c # SDIO 디바이스 초기화
│ ├── block.c # mmc_blk: 블록 디바이스 레이어 (blk-mq 연동)
│ ├── queue.c # mmc_queue: 요청 큐 관리
│ ├── mmc_ops.c # eMMC 전용 명령 (CMD6/CMD8/CMD14/CMD21 등)
│ ├── sd_ops.c # SD 전용 명령 (ACMD41/ACMD6 등)
│ ├── crypto.c # 인라인 암호화(ICE) 프레임워크
│ └── regulator.c # 전압 레귤레이터 관리
├── host/ # 호스트 컨트롤러 드라이버
│ ├── sdhci.c # SDHCI 공통 프레임워크
│ ├── sdhci-pltfm.c # SDHCI 플랫폼 헬퍼
│ ├── sdhci-of-arasan.c # Arasan/Xilinx SDHCI
│ ├── sdhci-tegra.c # NVIDIA Tegra SDHCI
│ ├── sdhci-esdhc-imx.c # NXP i.MX SDHCI
│ ├── sdhci-msm.c # Qualcomm MSM SDHCI
│ ├── sdhci-s3c.c # Samsung Exynos SDHCI
│ ├── dw_mmc.c # Synopsys DesignWare MMC 호스트
│ ├── mtk-sd.c # MediaTek SD/eMMC 호스트
│ ├── cqhci.c # CQE(Command Queue Engine) 프레임워크
│ └── sdhci-cqhci.c # SDHCI + CQE 통합
└── Kconfig # 빌드 설정
핵심 구조체
| 구조체 | 헤더 | 역할 |
|---|---|---|
struct mmc_host | include/linux/mmc/host.h | 호스트 컨트롤러 인스턴스. 클럭, 전압, 버스 폭, 타이밍, capability 플래그를 관리. mmc_alloc_host()로 할당 |
struct mmc_card | include/linux/mmc/card.h | 탐지된 카드 디바이스 하나. CID, CSD, EXT_CSD 정보, 카드 타입(MMC/SD/SDIO)을 보유 |
struct mmc_ios | include/linux/mmc/host.h | 현재 I/O 설정: 클럭 주파수, 버스 폭, 전압, 타이밍 모드, 드라이브 강도 |
struct mmc_request | include/linux/mmc/core.h | 하나의 MMC 트랜잭션(Transaction). mmc_command(CMD) + mmc_command(STOP) + mmc_data를 담는 컨테이너(Container) |
struct mmc_command | include/linux/mmc/core.h | 단일 커맨드: opcode, arg, flags(응답 타입), resp[4] 배열 |
struct mmc_data | include/linux/mmc/core.h | 데이터 전송 정보: blksz, blocks, sg(scatterlist), flags(읽기/쓰기) |
struct mmc_host_ops | include/linux/mmc/host.h | 호스트 드라이버 콜백(Callback) 집합. 요청 처리, I/O 설정, 카드 탐지, 튜닝 등 |
mmc_host_ops 콜백
| 콜백 | 프로토타입 | 설명 |
|---|---|---|
request | void (*)(struct mmc_host *, struct mmc_request *) | MMC 요청을 하드웨어에 전달. 비동기 방식으로 완료 시 mmc_request_done() 호출 |
set_ios | void (*)(struct mmc_host *, struct mmc_ios *) | 클럭 주파수, 버스 폭, 전압, 타이밍 등 I/O 파라미터 적용 |
get_cd | int (*)(struct mmc_host *) | 카드 삽입 상태 반환 (eMMC는 항상 1) |
get_ro | int (*)(struct mmc_host *) | 쓰기 보호 상태 반환 |
enable_sdio_irq | void (*)(struct mmc_host *, int enable) | SDIO 인터럽트(Interrupt) 활성화/비활성화 |
execute_tuning | int (*)(struct mmc_host *, u32 opcode) | HS200/HS400 버스 튜닝 수행 (CMD21 사용) |
prepare_hs400_tuning | void (*)(struct mmc_host *, struct mmc_ios *) | HS400 전환 전 튜닝 준비 |
hs400_enhanced_strobe | void (*)(struct mmc_host *, struct mmc_ios *) | HS400ES 모드 설정 |
card_busy | int (*)(struct mmc_host *) | DAT0 라인의 busy 상태 확인 |
hw_reset | void (*)(struct mmc_host *) | 호스트 하드웨어 리셋 수행 |
호스트 등록 흐름
/* 플랫폼 드라이버의 probe 함수 예시 */
static int my_mmc_probe(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct my_host *host;
/* 1. mmc_host 할당 (private 데이터 포함) */
mmc = mmc_alloc_host(sizeof(struct my_host), &pdev->dev);
if (!mmc)
return -ENOMEM;
host = mmc_priv(mmc); /* private 데이터 접근 */
host->mmc = mmc;
/* 2. 호스트 capability 설정 */
mmc->ops = &my_mmc_ops;
mmc->f_min = 400000; /* 400 kHz (초기화용) */
mmc->f_max = 200000000; /* 200 MHz (HS200/HS400) */
mmc->ocr_avail = MMC_VDD_27_36 | MMC_VDD_165_195;
/* eMMC 관련 capability 플래그 */
mmc->caps = MMC_CAP_8_BIT_DATA /* 8-bit 버스 지원 */
| MMC_CAP_4_BIT_DATA /* 4-bit 버스 지원 */
| MMC_CAP_NONREMOVABLE /* eMMC: 분리 불가 */
| MMC_CAP_MMC_HIGHSPEED /* High Speed 52 MHz */
| MMC_CAP_CMD23; /* CMD23 사전 블록 수 설정 */
mmc->caps2 = MMC_CAP2_HS200_1_8V_SDR /* HS200 1.8V */
| MMC_CAP2_HS400_1_8V /* HS400 1.8V */
| MMC_CAP2_HS400_ES /* HS400ES */
| MMC_CAP2_NO_SD /* SD 카드 미지원 */
| MMC_CAP2_NO_SDIO /* SDIO 미지원 */
| MMC_CAP2_CQE; /* CQE 지원 */
mmc->max_segs = 128; /* 최대 scatter 세그먼트 수 */
mmc->max_seg_size = 65536; /* 세그먼트당 최대 크기 */
mmc->max_req_size = 524288; /* 요청당 최대 크기 (512 KB) */
mmc->max_blk_size = 512; /* 블록 크기 */
mmc->max_blk_count = 65535; /* 최대 블록 수 */
/* 3. 하드웨어 초기화 (클럭 게이팅, 리셋 등) */
my_hw_init(host);
/* 4. mmc_host 등록 → 카드 탐지 시작 */
return mmc_add_host(mmc);
}
static void my_mmc_remove(struct platform_device *pdev)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
mmc_remove_host(mmc);
mmc_free_host(mmc);
}
eMMC 카드 탐지 및 초기화
mmc_add_host() 호출 후 커널은 mmc_rescan() 워크를 통해 카드 탐지를 시작합니다.
eMMC는 다음 순서로 초기화됩니다:
- CMD0 — GO_IDLE_STATE: 카드를 Idle 상태로 리셋합니다.
- CMD1 — SEND_OP_COND (반복): 동작 전압을 협상합니다. OCR의 busy 비트가 설정될 때까지 반복합니다.
- CMD2 — ALL_SEND_CID: CID(Card Identification) 레지스터를 읽어 제조사, 제품명, 시리얼 번호를 확인합니다.
- CMD3 — SET_RELATIVE_ADDR: RCA를
0x0001로 설정합니다 (eMMC는 호스트가 지정). - CMD9 — SEND_CSD: CSD 레지스터를 읽어 용량, 최대 전송 속도 등을 확인합니다.
- CMD7 — SELECT_CARD: 카드를 Transfer State로 전환합니다.
- CMD8 — SEND_EXT_CSD: 512바이트 EXT_CSD 레지스터를 읽어 상세 설정을 확인합니다.
- CMD6 — SWITCH (반복): 버스 폭(8-bit), 타이밍(HS200/HS400), 파워 클래스 등을 설정합니다.
- Tuning: HS200 모드에서 CMD21로 튜닝을 수행합니다. HS400 전환 시 HS200 → DDR52 → HS400 순서를 따릅니다.
mmc_select_hs400() 함수가 이 복잡한 시퀀스를 자동으로 처리합니다.
HS400ES의 경우 mmc_select_hs400es()에서 Data Strobe를 활성화하는 별도 경로를 사용합니다.
SDHCI 프레임워크
SDHCI(SD Host Controller Interface)는 SD Association이 정의한 호스트 컨트롤러 레지스터 맵 표준(현재 v4.20)으로,
대부분의 SoC에 내장된 eMMC/SD 호스트 컨트롤러가 이 스펙을 따릅니다.
Linux 커널의 sdhci.c는 SDHCI 공통 프레임워크를 제공하여,
플랫폼별 드라이버는 최소한의 코드만으로 호스트 컨트롤러를 등록할 수 있습니다.
sdhci_host 구조체 주요 필드
struct sdhci_host {
/* 부모: mmc_host */
struct mmc_host *mmc;
/* SDHCI 레지스터 베이스 주소 (MMIO) */
void __iomem *ioaddr;
/* 인터럽트 번호 */
int irq;
/* SDHCI capability (CAPS 레지스터에서 읽은 값) */
unsigned int caps; /* SDHCI_CAPABILITIES */
unsigned int caps1; /* SDHCI_CAPABILITIES_1 */
/* 하드웨어 quirks (비표준 동작 보정) */
unsigned int quirks; /* SDHCI_QUIRK_* */
unsigned int quirks2; /* SDHCI_QUIRK2_* */
/* 타이머/워크 */
struct timer_list timer; /* 커맨드/데이터 타임아웃 */
struct work_struct complete_work; /* 완료 처리 워크 */
/* DMA 관련 */
dma_addr_t adma_addr; /* ADMA2 descriptor 테이블 물리 주소 */
void *adma_table; /* ADMA2 descriptor 테이블 가상 주소 */
unsigned int adma_table_sz; /* descriptor 테이블 크기 */
/* 벤더별 operations */
const struct sdhci_ops *ops;
/* CQE 연동 */
struct cqhci_host *cqe_host;
...
};
sdhci_ops 콜백
| 콜백 | 설명 | 구현 예시 |
|---|---|---|
set_clock | 클럭 주파수 설정. 벤더별 PLL/분주기 제어 | sdhci_tegra_set_clock |
set_bus_width | 버스 폭(1/4/8-bit) 설정 | sdhci_set_bus_width (공통) |
set_uhs_signaling | UHS/HS200/HS400 시그널링 모드 설정 | sdhci_msm_set_uhs_signaling |
get_max_clock | 지원 가능한 최대 클럭 반환 | sdhci_pltfm_clk_get_max_clock |
get_min_clock | 초기화용 최소 클럭 반환 | (기본값: max_clock/256) |
platform_execute_tuning | 플랫폼별 튜닝 수행 | sdhci_tegra_execute_tuning |
reset | 호스트 리셋 (CMD/DAT/ALL) | sdhci_reset (공통) |
adma_write_desc | ADMA descriptor 엔트리 작성 | (벤더별 ADMA 포맷 대응) |
voltage_switch | 1.8V ↔ 3.3V 시그널링 전압 전환 | sdhci_start_signal_voltage_switch |
irq | 벤더별 추가 인터럽트 처리 | sdhci_msm_cqe_irq |
SDHCI 레지스터 맵 (주요 레지스터)
| 오프셋 | 크기 | 이름 | 접근 | 설명 |
|---|---|---|---|---|
| 0x00 | 4B | SDMA System Address | R/W | SDMA 전송용 시스템 메모리 주소 |
| 0x04 | 2B | Block Size | R/W | 전송 블록 크기 (보통 512) |
| 0x06 | 2B | Block Count | R/W | 전송 블록 수 |
| 0x08 | 4B | Argument | R/W | 커맨드 인자 (32-bit) |
| 0x0C | 2B | Transfer Mode | R/W | DMA 사용, 블록 수 카운트, 방향, Auto CMD12/23 |
| 0x0E | 2B | Command | R/W | 커맨드 인덱스 + 응답 타입. 쓰기 시 커맨드 발행 |
| 0x10~0x1F | 16B | Response | R | 응답 레지스터 (R1: [0]만, R2: [0]~[3] 사용) |
| 0x20 | 4B | Buffer Data Port | R/W | PIO 모드 데이터 전송 포트 |
| 0x24 | 4B | Present State | R | CMD/DAT 라인 상태, 카드 삽입, 쓰기 보호 등 |
| 0x28 | 1B | Host Control 1 | R/W | 버스 폭(4/8-bit), High Speed 활성화, DMA 선택 |
| 0x29 | 1B | Power Control | R/W | 버스 전원 ON/OFF, 전압 선택 |
| 0x2C | 2B | Clock Control | R/W | 내부 클럭 활성화, SD 클럭 출력, 분주기 설정 |
| 0x2E | 1B | Timeout Control | R/W | 데이터 타임아웃 카운터 값 |
| 0x2F | 1B | Software Reset | R/W | All/CMD/DAT 리셋 |
| 0x30 | 2B | Normal Interrupt Status | R/W1C | 커맨드 완료, 전송 완료, 카드 삽입/제거 등 |
| 0x32 | 2B | Error Interrupt Status | R/W1C | CMD CRC 에러, 데이터 CRC 에러, 타임아웃 등 |
| 0x34 | 2B | Normal Interrupt Status Enable | R/W | 인터럽트 상태 레지스터 활성화 마스크 |
| 0x38 | 2B | Normal Interrupt Signal Enable | R/W | 인터럽트 신호(IRQ 라인) 활성화 마스크 |
| 0x3E | 2B | Host Control 2 | R/W | UHS 모드 선택, 1.8V 시그널링, 튜닝 제어 |
| 0x40 | 4B | Capabilities | R | 지원 전압, 최대 클럭, DMA 지원 등 |
| 0x44 | 4B | Capabilities 2 | R | SDR50/SDR104/DDR50/HS400 지원, 튜닝 정보 |
| 0x58 | 4B | ADMA2 Address Low | R/W | ADMA2 descriptor 테이블 시스템 주소 (하위) |
| 0x5C | 4B | ADMA2 Address High | R/W | ADMA2 descriptor 테이블 시스템 주소 (상위, 64-bit) |
| 0xFE | 2B | Host Controller Version | R | SDHCI 스펙 버전 번호 |
주요 플랫폼 드라이버
| 드라이버 | 소스 파일 | SoC / 벤더 | 비고 |
|---|---|---|---|
| sdhci-tegra | host/sdhci-tegra.c | NVIDIA Tegra | HS400/HS400ES, 자체 튜닝 알고리즘 |
| sdhci-msm | host/sdhci-msm.c | Qualcomm MSM/SDM | ICE(Inline Crypto), CQE 지원 |
| sdhci-esdhc-imx | host/sdhci-esdhc-imx.c | NXP i.MX | Enhanced SD Host Controller |
| sdhci-of-arasan | host/sdhci-of-arasan.c | Xilinx Zynq, Arasan IP | DT 기반, PHY 분리 |
| sdhci-s3c | host/sdhci-s3c.c | Samsung Exynos | 레거시, 신규는 dw_mmc 사용 |
| dw_mmc | host/dw_mmc.c | Synopsys DesignWare | 비SDHCI 독자 IP. Samsung, Rockchip 등 |
| mtk-sd | host/mtk-sd.c | MediaTek | 비SDHCI 독자 IP. MT8183/MT6797 등 |
| sdhci-brcmstb | host/sdhci-brcmstb.c | Broadcom STB | 셋톱박스 SoC |
| sdhci-pci | host/sdhci-pci-core.c | Intel/AMD (PCI) | x86 PCI 장치로 노출 |
SDHCI_QUIRK_* / SDHCI_QUIRK2_* 플래그로 이를 보정합니다.
예: SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC(ADMA NOP descriptor에 End 속성 누락),
SDHCI_QUIRK2_PRESET_VALUE_BROKEN(프리셋 레지스터 무시) 등.
플랫폼 드라이버에서 host->quirks, host->quirks2에 해당 플래그를 설정합니다.
호스트 컨트롤러 드라이버 비교
SDHCI 표준은 공통 레지스터 인터페이스를 정의하지만, 각 SoC 제조사는 자체 PHY, DMA 엔진, 클럭 게이팅, 인라인 암호화 블록 등을 추가합니다. Linux 커널은 이들을 플랫폼별 드라이버로 구현하며, 각 드라이버는 고유한 튜닝 시퀀스와 quirk 세트를 가집니다.
Qualcomm — sdhci-msm
Qualcomm Snapdragon SoC에 탑재된 SD/eMMC 호스트 컨트롤러입니다. HS400 Enhanced Strobe, ICE(Inline Crypto Engine), CQHCI를 모두 지원하며, Android 디바이스에서 가장 널리 사용됩니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/sdhci-msm.c |
| IP 기반 | Qualcomm 자체 설계 (SDHCI 호환 래퍼) |
| DMA | ADMA2 64-bit, System DMA (BAM/GPI) |
| 튜닝 방식 | CDR(Clock Data Recovery) 기반, DLL(Delay-Locked Loop) Phase 조정 |
| 인라인 암호화 | ICE(Inline Crypto Engine) — AES-256-XTS 하드웨어 가속 |
| CMDQ | CQHCI 지원 (별도 cqhci.c 모듈 사용) |
| 클럭 관리 | clk_core, clk_iface, clk_ice 3개 클럭 독립 제어 |
| 주요 quirk | SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12, 자체 MSM_QUIRK_* 플래그 |
/* drivers/mmc/host/sdhci-msm.c — Qualcomm HS400 튜닝 핵심 흐름 */
static int sdhci_msm_hs400_dll_calibration(struct sdhci_host *host)
{
struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
struct sdhci_msm_host *msm_host = sdhci_pltfm_priv(pltfm_host);
/* 1단계: CDR을 비활성화하고 DLL 리셋 */
writel_relaxed(0, host->ioaddr + msm_host->regs->core_dll_config);
writel_relaxed(DLL_RST_EN, host->ioaddr + msm_host->regs->core_dll_config);
/* 2단계: DDR_CAL_EN으로 HS400 캘리브레이션 시작 */
writel_relaxed(DDR_CAL_EN | DLL_EN,
host->ioaddr + msm_host->regs->core_dll_config_2);
/* 3단계: DLL 잠금 완료 대기 */
return msm_dll_lock_status(host);
}
/* ICE(Inline Crypto Engine) 키 프로그래밍 */
static int sdhci_msm_program_key(struct cqhci_host *cq_host,
const union cqhci_crypto_cfg_entry *cfg,
int slot)
{
/* ICE 레지스터에 AES-256-XTS 키를 직접 프로그래밍 */
qcom_ice_program_key(msm_host->ice, cfg->crypto_cap_idx,
QCOM_ICE_CRYPTO_ALG_AES_XTS,
cfg->crypto_key, cfg->data_unit_size, slot);
return 0;
}
코드 설명
- 3–5행
SDHCI 플랫폼 호스트에서 Qualcomm 전용
sdhci_msm_host구조체를 추출합니다. - 7–8행 CDR을 끄고 DLL을 리셋합니다. HS400 모드는 Data Strobe 신호를 사용하므로 CDR이 불필요합니다.
- 11–12행
DDR_CAL_EN비트로 HS400 전용 DDR 캘리브레이션을 시작합니다. - 19–24행 ICE 블록에 암호화 키를 프로그래밍합니다. 블록 I/O가 eMMC에 도달하기 전에 하드웨어가 자동으로 AES-XTS 암복호화를 수행합니다.
Synopsys DesignWare — dw_mmc
Synopsys DesignWare Mobile Storage Host Controller IP는 다수의 SoC 제조사(Samsung Exynos, Rockchip, Allwinner, HiSilicon 등)가
라이선스하여 사용하는 공통 IP 블록입니다. 커널에서 drivers/mmc/host/dw_mmc.c가 코어 드라이버이며,
SoC별 글루 드라이버(dw_mmc-exynos.c, dw_mmc-rockchip.c 등)가 PHY 튜닝과 클럭 관리를 담당합니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/dw_mmc*.c |
| IP 기반 | Synopsys DesignWare (비-SDHCI, 자체 레지스터 맵) |
| DMA | IDMAC(Internal DMA Controller) — Descriptor 기반 체인 |
| 튜닝 방식 | SoC별 Phase Shift (0°~360° 스캔), 최적 윈도우 중심점 선택 |
| 특징 | SDHCI와 다른 레지스터 맵, 자체 인터럽트 체계, FIFO 모드 지원 |
| CMDQ 지원 | IP 자체는 미지원 — CQHCI 별도 연결 필요 (일부 SoC만 가능) |
/* drivers/mmc/host/dw_mmc.c — IDMAC Descriptor 설정 */
struct idmac_desc {
__le32 des0; /* Control: OWN, CH, DIC, LD, FS 비트 */
__le32 des1; /* Buffer Size (최대 4KB per descriptor) */
__le32 des2; /* Buffer Address (물리 주소) */
__le32 des3; /* Next Descriptor Address (링 버퍼 또는 체인) */
};
/* Exynos 글루 드라이버: Phase Shift 튜닝 */
static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot,
u32 opcode)
{
struct dw_mci *host = slot->host;
struct dw_mci_exynos_priv_data *priv = host->priv;
/* 0°부터 360°까지 Phase를 스캔하며 CMD21 전송 */
for (phase = 0; phase < TUNING_PHASE_MAX; phase++) {
dw_mci_exynos_set_sample_phase(host, phase);
if (mmc_send_tuning(mmc, opcode, NULL) == 0)
pass_window |= (1 << phase); /* 성공한 Phase 기록 */
}
/* 가장 넓은 연속 통과 윈도우의 중심을 최적 Phase로 선택 */
best_phase = find_best_window_center(pass_window);
dw_mci_exynos_set_sample_phase(host, best_phase);
return 0;
}
코드 설명
- 2–7행
IDMAC descriptor는 4개의 32비트 필드로 구성됩니다.
des0의 OWN 비트가 1이면 DMA 엔진이 소유, 0이면 CPU가 소유합니다. - 17–20행 Phase를 0°~360° 범위에서 스캔하며 CMD21(튜닝 블록 읽기)을 전송합니다. CRC 에러 없이 성공한 Phase를 비트마스크에 기록합니다.
- 22–23행 가장 넓은 연속 통과 구간의 중심점을 최적 샘플링 Phase로 설정합니다. 온도 변동에 대한 마진을 최대화하는 전략입니다.
MediaTek — mtk-sd (MSDC)
MediaTek SoC는 SDHCI 표준을 따르지 않는 독자적인 MSDC(MT SD/MMC Controller)를 사용합니다. 자체 레지스터 맵, 자체 튜닝 알고리즘, DVFS(Dynamic Voltage and Frequency Scaling) 연동이 특징이며, Android 스마트폰과 Chromebook에서 많이 사용됩니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/mtk-sd.c |
| IP 기반 | MediaTek 자체 설계 (비-SDHCI) |
| DMA | GPDDMA(General Purpose Descriptor DMA) — 최대 65535 BD 지원 |
| 튜닝 방식 | PAD 딜레이 튜닝 — 내부/외부 딜레이 라인 독립 조정 |
| CMDQ | CQHCI 지원 (MT6779+) |
| 특징 | Data Tune / CMD Tune 분리, 자동 CRC 상태 모니터링 |
| 주요 quirk | Source Clock CG(Clock Gating) 이슈, 특정 칩에서 HS400 모드 진입 시 추가 딜레이 필요 |
/* drivers/mmc/host/mtk-sd.c — PAD 딜레이 튜닝 핵심 */
static int msdc_tune_together(struct mmc_host *mmc, u32 opcode)
{
struct msdc_host *host = mmc_priv(mmc);
u32 rise_delay = 0, fall_delay = 0;
/* 상승 에지(Rising Edge) 딜레이 스캔 */
sdr_set_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
for (i = 0; i < PAD_DELAY_MAX; i++) {
msdc_set_data_delay(host, i);
if (mmc_send_tuning(mmc, opcode, NULL) == 0)
rise_delay |= (1 << i);
}
/* 하강 에지(Falling Edge) 딜레이 스캔 */
sdr_clr_bits(host->base + MSDC_IOCON, MSDC_IOCON_RSPL);
for (i = 0; i < PAD_DELAY_MAX; i++) {
msdc_set_data_delay(host, i);
if (mmc_send_tuning(mmc, opcode, NULL) == 0)
fall_delay |= (1 << i);
}
/* 상승/하강 중 더 넓은 통과 윈도우를 가진 에지를 선택 */
final_delay = get_best_delay(host, rise_delay, fall_delay);
msdc_set_data_delay(host, final_delay);
return 0;
}
코드 설명
- 7–12행 상승 에지에서 PAD 딜레이를 0부터 최대값까지 스캔합니다. 각 딜레이에서 CMD21을 보내 CRC 통과 여부를 비트마스크에 기록합니다.
- 14–19행 하강 에지에서도 동일한 스캔을 수행합니다. MSDC는 상승/하강 에지를 독립적으로 제어할 수 있어 더 정밀한 튜닝이 가능합니다.
- 21–22행 두 에지 중 통과 윈도우가 넓은 쪽을 최종 에지로 선택하고, 해당 윈도우의 중심을 최적 딜레이로 설정합니다.
NXP i.MX — sdhci-esdhc-imx
NXP(구 Freescale) i.MX 시리즈에 탑재된 uSDHC(Ultra Secured Digital Host Controller)입니다. SDHCI 호환이지만 레지스터 오프셋과 비트 배치가 표준과 다르며, 자체 Strobe DLL과 Auto-Tuning 회로를 갖추고 있습니다. 산업용/자동차 등급 i.MX8/i.MX9에서 eMMC 5.1을 주로 사용합니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/sdhci-esdhc-imx.c |
| IP 기반 | NXP uSDHC (SDHCI 호환, 비표준 레지스터 매핑) |
| DMA | ADMA2, 일부 SoC에서 SDMA 전용 |
| 튜닝 방식 | Auto-Tuning Circuit + Strobe DLL (HS400) |
| CMDQ | i.MX8MM+ CQHCI 지원 |
| 특징 | Watermark Level 레지스터로 FIFO 임계값 세밀 제어, 레지스터 엔디안 변환 |
| 주요 quirk | SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC, SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
Renesas — renesas_sdhi / tmio
Renesas R-Car 시리즈(자동차 SoC)의 SDHI 컨트롤러는 TMIO(Toshiba Mobile I/O) IP를 기반으로 합니다. SCC(Sampling Clock Controller)로 TAP(Timing Adjustment Point) 딜레이를 조정하며, 차량용 eMMC의 넓은 온도 범위(-40°C ~ +125°C)에서 안정적인 동작을 보장합니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/renesas_sdhi_core.c, tmio_mmc_core.c |
| IP 기반 | TMIO + Renesas SCC 확장 (비-SDHCI) |
| DMA | DMA Engine(SYS-DMAC) 또는 내장 DMAC |
| 튜닝 방식 | SCC TAP 딜레이 — 8비트 탭 포지션 스캔, 최적 윈도우 선택 |
| CMDQ | 미지원 (TMIO IP 한계) |
| 특징 | 자동차 등급(AEC-Q100), 넓은 온도 범위, 주기적 재튜닝 내장 |
Intel — sdhci-pci
Intel Bay Trail, Apollo Lake, Elkhart Lake 등의 플랫폼에 내장된 eMMC 호스트 컨트롤러입니다. PCI 디바이스로 열거되며 ACPI를 통해 설정됩니다. 주로 IoT 게이트웨이, 산업용 PC, 일부 Chromebook에서 사용됩니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/sdhci-pci-core.c, sdhci-pci-gli.c |
| IP 기반 | SDHCI 표준 준수 (PCI 열거) |
| DMA | ADMA2 64-bit |
| 튜닝 방식 | HW Auto-Tuning (CMD21) |
| CMDQ | CQHCI 지원 (Apollo Lake+) |
| 특징 | ACPI 바인딩, LTR(Latency Tolerance Reporting), D3cold 지원 |
| 주요 quirk | SDHCI_QUIRK2_STOP_WITH_TC, 플랫폼별 PCI Vendor/Device ID 기반 fixup |
Allwinner — sunxi-mmc
Allwinner A-시리즈/H-시리즈(A64, H6, H616 등) SoC의 독자 MMC 컨트롤러입니다. 저가형 SBC(Single Board Computer)와 미디어 플레이어에서 주로 사용되며, 자동 튜닝 회로가 없어 수동 Phase 캘리브레이션이 필요합니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/sunxi-mmc.c |
| IP 기반 | Allwinner 자체 설계 (비-SDHCI) |
| DMA | 내장 DMA — Descriptor 체인 방식 |
| 튜닝 방식 | 수동 Phase 캘리브레이션 (DT sample-phase, output-phase 속성) |
| CMDQ | 미지원 |
| 특징 | 2x Clock Mode(DDR), New Timing Mode(HS200 일부 지원), DT 기반 타이밍 파라미터 |
Broadcom — sdhci-bcm2835 / sdhci-iproc
Raspberry Pi(BCM2835/BCM2711)에 사용되는 Broadcom의 eMMC/SD 호스트 컨트롤러입니다. BCM2711(RPi 4)은 EMMC2 컨트롤러를 통해 HS200까지 지원하지만, BCM2835(RPi 1~3)는 Arasan SDHCI IP의 비표준 구현으로 다양한 quirk를 필요로 합니다.
| 항목 | 설명 |
|---|---|
| 커널 드라이버 | drivers/mmc/host/sdhci-bcm2835.c, sdhci-iproc.c |
| DMA | SDMA (DMA 채널 제약, 최대 전송 크기 제한) |
| 최대 속도 | BCM2835: SDR25 / BCM2711: HS200 |
| 주요 quirk | SDHCI_QUIRK_BROKEN_CARD_DETECTION, SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
호스트 컨트롤러 드라이버 종합 비교
| SoC 제조사 | 커널 드라이버 | IP 유형 | 최대 속도 | CMDQ | 인라인 암호화 | DMA |
|---|---|---|---|---|---|---|
| Qualcomm | sdhci-msm | SDHCI 호환 | HS400ES | CQHCI | ICE | ADMA2 64-bit |
| Samsung Exynos | dw_mmc-exynos | DesignWare | HS400 | 미지원 | FMP | IDMAC |
| MediaTek | mtk-sd | MSDC 독자 | HS400 | CQHCI | 미지원 | GPDDMA |
| NXP i.MX | sdhci-esdhc-imx | uSDHC | HS400ES | CQHCI | CAAM 연동 | ADMA2 |
| Renesas R-Car | renesas_sdhi | TMIO | HS400 | 미지원 | 미지원 | SYS-DMAC |
| Intel | sdhci-pci | SDHCI 표준 | HS400ES | CQHCI | 미지원 | ADMA2 64-bit |
| Allwinner | sunxi-mmc | 독자 | HS200 | 미지원 | 미지원 | 내장 DMA |
| Broadcom | sdhci-bcm2835 | Arasan SDHCI | HS200 | 미지원 | 미지원 | SDMA |
| TI OMAP/AM | sdhci-omap | SDHCI 호환 | HS200 | 미지원 | 미지원 | ADMA2 |
| Amlogic | meson-gx-mmc | 독자 | HS400 | 미지원 | 미지원 | 내장 DMA |
dmesg | grep "new HS"로 실제 협상된 속도 모드를 확인하세요.
요청 처리 파이프라인(Pipeline)
사용자 공간의 I/O 요청이 eMMC 하드웨어까지 도달하는 전체 경로를 추적합니다. 일반(non-CQE) 경로와 CMDQ(CQE) 경로를 모두 살펴봅니다.
일반 요청 흐름
CQE가 비활성화된 상태에서의 요청 경로입니다:
submit_bio()→ blk-mq 소프트웨어 큐에 bio 삽입- blk-mq 스케줄러(Scheduler)가 request를 디스패치(Dispatch) →
mmc_blk_mq_issue_rq()호출 mmc_blk_mq_issue_rw_rq()에서mmc_start_request()→host->ops->request()- SDHCI:
sdhci_request()→ 레지스터에 CMD/ARG/블록 정보 기록 → 커맨드 발행 - 하드웨어가 CMD 전송 → 응답 수신 → DMA로 데이터 전송
- 인터럽트 발생 →
sdhci_irq()→mmc_request_done() mmc_blk_mq_complete_rq()→ blk-mq 완료 처리
CQE/CMDQ 경로
eMMC 5.1의 CMDQ(Command Queuing)는 호스트와 eMMC 사이에 최대 32개의 태스크(Task)를 동시에 큐에 넣을 수 있는 기능입니다.
커널의 CQE 프레임워크(cqhci.c)는 JEDEC의 CQHCI(Command Queue Host Controller Interface) 스펙을 구현합니다.
| 항목 | 일반 모드 | CQE/CMDQ 모드 |
|---|---|---|
| 동시 요청 | 1개 (직렬 처리) | 최대 32개 (병렬 처리) |
| 커맨드 | CMD17/18/24/25 | CMD44 (Task Descriptor) + CMD45 (Task Address) + CMD46/47 |
| 커맨드 발행 | 호스트가 매 요청마다 직접 CMD 발행 | CQE HW가 Task Descriptor에서 자동 CMD 발행 |
| 완료 통지 | Transfer Complete 인터럽트 | Task Complete 인터럽트 (비트맵) |
| Random 4K 성능 | 낮음 (직렬 병목(Bottleneck)) | 높음 (eMMC 내부 병렬 처리) |
| 활성화 | 기본 | EXT_CSD[15] CMDQ_MODE_EN=1 |
DMA 모드
SDHCI는 3가지 DMA 모드를 지원합니다. 현대 드라이버는 대부분 ADMA2를 사용합니다.
| DMA 모드 | 주소 크기 | 설명 | 장점 | 단점 |
|---|---|---|---|---|
| SDMA | 32-bit | 단일 버퍼, 4KB 경계마다 인터럽트 | 단순한 구현 | scatter-gather 미지원, 4KB마다 인터럽트 오버헤드(Overhead) |
| ADMA2 | 32/64-bit | Descriptor 테이블 기반 scatter-gather | 비연속 물리 메모리(Physical Memory) 직접 전송 | descriptor 테이블 메모리 할당 필요 |
| ADMA3 | 64-bit | ADMA2 + Command descriptor 통합 | CMD+데이터를 하나의 descriptor chain에 | SDHCI v4.10+, 아직 제한적 지원 |
ADMA2 Descriptor 구조
/* ADMA2 descriptor (64-bit 엔트리) */
struct sdhci_adma2_64_desc {
__le16 cmd; /* 속성: Valid, End, Int, Act(Tran/Link/Nop) */
__le16 len; /* 전송 길이 (0 = 65536) */
__le32 addr_lo; /* 시스템 메모리 주소 (하위 32-bit) */
__le32 addr_hi; /* 시스템 메모리 주소 (상위 32-bit, v4 모드) */
};
/* ADMA2 속성 비트 */
#define SDHCI_ADMA2_DESC_VALID (1 << 0) /* 이 descriptor가 유효함 */
#define SDHCI_ADMA2_DESC_END (1 << 1) /* 마지막 descriptor */
#define SDHCI_ADMA2_DESC_INT (1 << 2) /* 이 descriptor 완료 시 인터럽트 */
#define SDHCI_ADMA2_DESC_ACT_TRAN (2 << 4) /* 데이터 전송 */
#define SDHCI_ADMA2_DESC_ACT_LINK (3 << 4) /* 다음 descriptor 테이블 링크 */
#define SDHCI_ADMA2_DESC_ACT_NOP (0 << 4) /* No-operation */
블록 디바이스 핵심 구조체
| 구조체 | 소스 | 역할 |
|---|---|---|
struct mmc_blk_data | core/block.c | 블록 디바이스 인스턴스. gendisk, mmc_queue, 파티션 타입(main/boot/rpmb/gp) 보유 |
struct mmc_queue | core/queue.c | blk-mq 요청 큐 래퍼. blk_mq_tag_set, recovery 워크 관리 |
struct mmc_queue_req | core/queue.h | 개별 요청 컨텍스트. mmc_blk_request(mrq + brq), bounce 버퍼, sg_list 포함 |
struct mmc_blk_request | core/block.c | mmc_request + mmc_command(sbc/cmd/stop) + mmc_data를 하나로 묶는 컨테이너 |
요청 처리 코드 흐름
/* core/block.c — blk-mq 요청 진입점 */
static blk_status_t mmc_blk_mq_issue_rq(struct mmc_queue *mq,
struct request *req)
{
struct mmc_blk_data *md = mq->blkdata;
struct mmc_card *card = md->queue.card;
switch (mmc_issue_type(mq, req)) {
case MMC_ISSUE_SYNC:
/* 특수 요청 (DISCARD, FLUSH 등) */
return mmc_blk_mq_issue_rw_rq(mq, req);
case MMC_ISSUE_DCMD:
/* CQE Direct Command (비데이터 커맨드) */
return mmc_blk_cqe_issue_rw_rq(mq, req);
case MMC_ISSUE_ASYNC:
/* CQE 비동기 데이터 요청 */
return mmc_blk_cqe_issue_rw_rq(mq, req);
default:
/* 일반 읽기/쓰기 */
return mmc_blk_mq_issue_rw_rq(mq, req);
}
}
/* 일반 읽기/쓰기 요청 → MMC 커맨드 변환 */
static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
struct mmc_card *card,
int disable_multi,
struct mmc_queue *mq)
{
struct mmc_blk_request *brq = &mqrq->brq;
struct request *req = mmc_queue_req_to_req(mqrq);
brq->cmd.arg = blk_rq_pos(req); /* 블록 주소 */
if (!mmc_card_blockaddr(card))
brq->cmd.arg <<= 9; /* 바이트 주소 변환 */
brq->data.blksz = 512;
brq->data.blocks = blk_rq_sectors(req);
if (rq_data_dir(req) == READ) {
brq->cmd.opcode = brq->data.blocks > 1
? MMC_READ_MULTIPLE_BLOCK /* CMD18 */
: MMC_READ_SINGLE_BLOCK; /* CMD17 */
brq->data.flags = MMC_DATA_READ;
} else {
brq->cmd.opcode = brq->data.blocks > 1
? MMC_WRITE_MULTIPLE_BLOCK /* CMD25 */
: MMC_WRITE_BLOCK; /* CMD24 */
brq->data.flags = MMC_DATA_WRITE;
}
/* CMD23 (SET_BLOCK_COUNT) 사전 설정 */
if (brq->data.blocks > 1 && mmc_card_cmd23(card)) {
brq->sbc.opcode = MMC_SET_BLOCK_COUNT; /* CMD23 */
brq->sbc.arg = brq->data.blocks;
brq->mrq.sbc = &brq->sbc;
}
}
cqhci_suspend())하고 일반 CMD 경로로 전환해야 합니다.
커널은 mmc_blk_cqe_recovery()에서 이를 자동으로 처리하지만,
이 과정에서 일시적인 성능 저하가 발생할 수 있습니다.
CONFIG_MMC_BLOCK_PACKED로 별도 활성화해야 하며 기본 비활성입니다.
CQHCI 심화 — Command Queue Host Controller Interface
CQHCI(Command Queue Host Controller Interface)는 JEDEC에서 정의한 eMMC 커맨드 큐잉의 호스트 측 표준입니다.
eMMC 5.1의 CMDQ(CMD44/CMD45/CMD46/CMD47) 프로토콜을 하드웨어 레벨에서 가속하여,
최대 32개의 태스크를 동시에 관리하고 호스트 CPU 개입 없이 자동으로 디스패치합니다.
커널에서는 drivers/mmc/host/cqhci.c가 공통 구현을 제공하며,
Qualcomm, MediaTek, Intel, NXP 등의 호스트 드라이버가 이를 사용합니다.
CQHCI 아키텍처
CQHCI는 호스트 메모리에 위치한 Task Descriptor List와 Transfer Descriptor List를 통해 eMMC와 통신합니다. 소프트웨어는 Task Descriptor에 커맨드 정보를 기록하고 Doorbell 레지스터를 쓰면, 하드웨어가 자동으로 CMD44/CMD45(Task Descriptor 전송) → CMD46/CMD47(데이터 전송)을 수행합니다.
CQHCI 주요 레지스터
| 레지스터 | 오프셋 | 크기 | 설명 |
|---|---|---|---|
CQVER | 0x00 | 4B | CQHCI 버전 (5.10 = eMMC 5.1 지원) |
CQCAP | 0x04 | 4B | Capability — Crypto 지원 여부, ITCFMUL(인터럽트 타이머 배율) |
CQCFG | 0x08 | 4B | Configuration — CQE 활성화(bit 0), Task Descriptor 크기(bit 8: 128-bit) |
CQCTL | 0x0C | 4B | Control — Halt(bit 0), Clear All Tasks(bit 8) |
CQIS | 0x10 | 4B | Interrupt Status — HAC(Halt), TCC(Task Complete), RED(Response Error) |
CQISTE | 0x14 | 4B | Interrupt Status Enable |
CQISGE | 0x18 | 4B | Interrupt Signal Enable |
CQIC | 0x1C | 4B | Interrupt Coalescing — 타이머/카운터 기반 인터럽트 병합 |
CQTDLBA | 0x20 | 4B | Task Descriptor List Base Address (하위 32비트) |
CQTDLBAU | 0x24 | 4B | Task Descriptor List Base Address (상위 32비트, 64-bit 주소) |
CQTDBR | 0x28 | 4B | Task Doorbell — 각 비트가 슬롯 0~31에 대응, 1 기록 시 태스크 실행 시작 |
CQTCN | 0x2C | 4B | Task Completion Notification — 완료된 태스크 슬롯 비트맵 |
CQDQS | 0x30 | 4B | Device Queue Status — eMMC가 보고한 큐 상태 |
CQDPT | 0x34 | 4B | Device Pending Tasks — eMMC에서 대기 중인 태스크 |
CQTCLR | 0x38 | 4B | Task Clear — 특정 슬롯의 태스크 취소 |
CQSSC1 | 0x40 | 4B | Send Status Config 1 — CMD13 폴링 간격 설정 |
CQSSC2 | 0x44 | 4B | Send Status Config 2 — RCA(Relative Card Address) |
CQCRDCT | 0x48 | 4B | Command Response for Direct CMD Task |
CQTERRI | 0x50 | 4B | Task Error Information — 에러 발생 태스크/CMD 인덱스 |
CQCRI | 0x54 | 4B | Command Response Index |
커널 CQHCI 구현
/* drivers/mmc/host/cqhci.c — 핵심 함수 흐름 */
/* 1. CQHCI 초기화: 호스트 드라이버가 probe 시 호출 */
int cqhci_init(struct cqhci_host *cq_host, struct mmc_host *mmc,
bool dma64)
{
/* Task Descriptor List 할당 (32 슬롯 × 128-bit each) */
cq_host->desc_size = dma64 ? 16 : 8; /* 64-bit: 16B, 32-bit: 8B */
cq_host->data_size = dma64 ? 16 : 8; /* Transfer Desc도 동일 */
/* DMA 일관성 메모리에 Descriptor 목록 할당 */
cq_host->trans_desc_base = dmam_alloc_coherent(
mmc_dev(mmc), cq_host->trans_desc_len,
&cq_host->trans_desc_dma_base, GFP_KERNEL);
/* CQCFG: CQE 활성화, 128-bit Task Descriptor 모드 */
cqhci_writel(cq_host, CQHCI_TASK_DESC_SZ_128 | CQHCI_ENABLE,
CQHCI_CFG);
/* CQTDLBA: Descriptor 기본 주소 설정 */
cqhci_writel(cq_host, lower_32_bits(cq_host->desc_dma_base),
CQHCI_TDLBA);
cqhci_writel(cq_host, upper_32_bits(cq_host->desc_dma_base),
CQHCI_TDLBAU);
return 0;
}
/* 2. 요청 제출: blk-mq에서 새 I/O 요청이 도착하면 호출 */
static blk_status_t cqhci_request(struct mmc_host *mmc,
struct mmc_request *mrq)
{
struct cqhci_host *cq_host = mmc->cqe_private;
int tag = mrq->tag; /* blk-mq 태그 = CQHCI 슬롯 번호 */
/* Task Descriptor 작성: 방향, LBA, 블록 수, 우선순위 */
cqhci_prep_task_desc(mrq, &cq_host->slot[tag].task_desc);
/* Transfer Descriptor(Scatter-Gather List) 작성 */
cqhci_prep_tran_desc(mrq, cq_host, tag);
/* Doorbell 레지스터에 해당 슬롯 비트를 SET → HW가 자동 디스패치 */
cqhci_writel(cq_host, 1 << tag, CQHCI_TDBR);
return BLK_STS_OK;
}
/* 3. 완료 인터럽트 핸들러 */
irqreturn_t cqhci_irq(struct mmc_host *mmc, u32 intmask,
int cmd_error, int data_error)
{
struct cqhci_host *cq_host = mmc->cqe_private;
/* CQIS(Interrupt Status) 읽기 */
status = cqhci_readl(cq_host, CQHCI_IS);
if (status & CQHCI_IS_TCC) {
/* Task Complete: CQTCN에서 완료 비트맵 읽기 */
comp_status = cqhci_readl(cq_host, CQHCI_TCN);
cqhci_writel(cq_host, comp_status, CQHCI_TCN); /* W1C */
for_each_set_bit(tag, &comp_status, 32) {
mrq = cq_host->slot[tag].mrq;
mmc_cqe_request_done(mmc, mrq); /* blk-mq 완료 콜백 */
}
}
if (status & CQHCI_IS_RED) {
/* Response Error: 에러 복구 — CQE Halt → 재발행 */
cqhci_error_handler(mmc);
}
return IRQ_HANDLED;
}
코드 설명
- 5–22행
cqhci_init은 DMA 일관성 메모리에 32개 슬롯의 Task/Transfer Descriptor를 할당하고, CQCFG 레지스터에 CQE를 활성화합니다. - 25–39행
cqhci_request는 blk-mq 태그를 CQHCI 슬롯 번호로 직접 매핑합니다. Descriptor를 채우고 Doorbell 비트를 세우면 하드웨어가 CMD44~47을 자동 전송합니다. - 42–62행
인터럽트 핸들러는 CQTCN 비트맵에서 완료된 태스크를 읽어 각각
mmc_cqe_request_done으로 blk-mq에 완료를 알립니다. 에러 시 CQE를 Halt하고 복구합니다.
인터럽트 병합(Interrupt Coalescing)
매 태스크 완료마다 인터럽트를 발생시키면 CPU 오버헤드가 큽니다.
CQHCI는 CQIC 레지스터를 통해 타이머/카운터 기반의 인터럽트 병합을 지원합니다.
| CQIC 필드 | 비트 | 설명 |
|---|---|---|
| ICTOVAL | [6:0] | Timeout Value — 첫 번째 완료 후 타이머 만료까지 대기 (단위: ITCFMUL 기반) |
| ICTOVALWEN | [7] | Timeout Value Write Enable |
| ICCTH | [12:8] | Counter Threshold — 이 수만큼 태스크가 완료되면 즉시 인터럽트 |
| ICCTHWEN | [15] | Counter Threshold Write Enable |
| ICSB | [16] | Counter/Timer Reset |
| ICCEN | [31] | Interrupt Coalescing Enable |
/* 인터럽트 병합 설정 예시: 8개 태스크 또는 1ms 중 먼저 도달 시 인터럽트 */
u32 ic_val = CQHCI_IC_ENABLE |
CQHCI_IC_ICCTHWEN | (8 << CQHCI_IC_ICCTH_SHIFT) | /* 카운터 = 8 */
CQHCI_IC_ICTOVALWEN | (0x14 << CQHCI_IC_ICTOVAL_SHIFT); /* 타이머 ≈ 1ms */
cqhci_writel(cq_host, ic_val, CQHCI_IC);
에러 복구 절차
CQHCI에서 에러가 발생하면(CRC 에러, 타임아웃 등) 다음 절차로 복구합니다.
이 과정은 cqhci.c의 cqhci_recovery_start()와 cqhci_recovery_finish()에 구현되어 있습니다.
- CQE Halt —
CQCTL에 Halt 비트를 설정하여 새 태스크 디스패치를 중지합니다. - 에러 태스크 식별 —
CQTERRI레지스터에서 에러가 발생한 태스크 번호와 CMD 인덱스를 확인합니다. - 태스크 클리어 —
CQTCLR로 해당 슬롯을 클리어하거나, 심각한 에러 시CQCTL의 Clear All Tasks로 전체 초기화합니다. - SDHCI 리셋 — 호스트 컨트롤러의 CMD/DAT 라인을 소프트 리셋합니다.
- CQE 재시작 — Halt를 해제하고, 실패한 태스크와 미완료 태스크를 blk-mq에 재큐잉합니다.
CQTDBR의 슬롯 31은 DCMD(Direct Command Task)로 예약되어 있으며,
캐시 플러시(CMD6 FLUSH_CACHE)나 BKOPS 같은 관리 커맨드는 DCMD를 통해 전송합니다.
DCMD가 불가능한 커맨드(예: CMD0 리셋)는 반드시 CQE를 Halt한 후 전송해야 합니다.
CQHCI 인라인 암호화 (Crypto Extension)
CQHCI Crypto Extension은 128-bit Task Descriptor의 상위 64비트에 암호화 설정을 포함합니다. 각 태스크마다 독립적인 암호화 키 슬롯과 DUN(Data Unit Number)을 지정할 수 있어, 파일 단위 암호화(fscrypt)와 자연스럽게 통합됩니다.
| Task Descriptor 비트 | 필드 | 설명 |
|---|---|---|
| [127:96] | Crypto Key Index | 키 슬롯 번호 (최대 32개 키) |
| [95:64] | DUN (Data Unit Number) | 암호화 IV(Initialization Vector)의 일부, 섹터 번호 기반 |
| [0] | Crypto Enable | 이 태스크에 대해 인라인 암호화 활성화 |
cqhci_host_ops의 .program_key 콜백을 통해 호스트별 키 프로그래밍 로직을 주입합니다.
CONFIG_MMC_CRYPTO가 활성화되어야 하며, blk-crypto 프레임워크와 연동됩니다.
버스 모드 튜닝
eMMC는 세대가 발전하면서 버스 인터페이스 속도가 비약적으로 향상되었습니다.
호스트 컨트롤러는 카드의 CARD_TYPE(EXT_CSD[196]) 필드를 읽어 지원 가능한
최고 속도 모드를 협상하고, 해당 모드에 맞는 타이밍 튜닝을 수행합니다.
| 모드 | 클럭 | 버스 폭 | SDR/DDR | 최대 처리량(Throughput) | eMMC 스펙 |
|---|---|---|---|---|---|
| Legacy | 26 MHz | 1/4/8-bit | SDR | 26 MB/s | 4.3 |
| High Speed | 52 MHz | 1/4/8-bit | SDR | 52 MB/s | 4.3 |
| High Speed DDR (DDR52) | 52 MHz | 4/8-bit | DDR | 104 MB/s | 4.4 |
| HS200 | 200 MHz | 4/8-bit | SDR | 200 MB/s | 4.5 |
| HS400 | 200 MHz | 8-bit | DDR | 400 MB/s | 5.0 |
| HS400ES | 200 MHz | 8-bit | DDR + Strobe | 400 MB/s | 5.1 |
CMD21 튜닝 절차 (HS200)
HS200 모드에서는 200 MHz의 높은 클럭 주파수로 인해 데이터 샘플링 포인트를 정확히 조정해야 합니다. 호스트 컨트롤러는 CMD21 (SEND_TUNING_BLOCK)을 사용하여 튜닝 블록을 읽고, 수신된 데이터 패턴을 기대값과 비교하여 최적의 샘플링 위상(phase)을 결정합니다.
- 호스트가 CMD21을 전송하면 카드는 미리 정의된 튜닝 데이터 패턴(4-bit: 64바이트, 8-bit: 128바이트)을 응답합니다.
- 호스트 컨트롤러가 내부 딜레이 라인의 위상을 0°부터 360° 범위로 순차적으로 변경하면서 CMD21을 반복 전송합니다.
- 각 위상에서 수신 데이터를 기대 패턴과 비교하여 성공/실패를 기록합니다.
- 연속 성공 구간(pass window)의 중앙점을 최적 샘플링 포인트로 선택합니다.
- 선택된 위상을 호스트 컨트롤러에 설정하여 이후 모든 데이터 전송에 사용합니다.
/* drivers/mmc/host/sdhci.c — 튜닝 구현 핵심 */
static int sdhci_execute_tuning(struct mmc_host *mmc, u32 opcode)
{
struct sdhci_host *host = mmc_priv(mmc);
int tuning_count = 0;
bool hs400_tuning;
hs400_tuning = host->flags & SDHCI_HS400_TUNING;
if (host->tuning_mode == SDHCI_TUNING_MODE_1)
tuning_count = host->tuning_count;
/*
* HS200 → CMD21 (SEND_TUNING_BLOCK_HS200)
* SD UHS-I → CMD19 (SEND_TUNING_BLOCK)
*/
sdhci_writew(host, SDHCI_TRNS_READ, SDHCI_TRANSFER_MODE);
/* Execute HW Auto Tuning 또는 SW 루프 */
sdhci_start_tuning(host);
host->tuning_err = __sdhci_execute_tuning(host, opcode);
sdhci_end_tuning(host);
/* HS400 전환 시: HS200 튜닝 후 HS400으로 모드 전환 */
if (hs400_tuning)
return host->ops->hs400_downgrade(host);
/* 재튜닝 타이머 설정 (온도/전압 변화 대응) */
if (!host->tuning_err && tuning_count)
mod_timer(&host->tuning_timer,
jiffies + tuning_count * HZ);
return host->tuning_err;
}
HS400 Enhanced Strobe (DS 핀)
HS400 모드에서는 DDR(Double Data Rate)로 동작하므로 CLK의 상승/하강 에지 모두에서 데이터를 샘플링합니다. 그러나 200 MHz DDR에서 정확한 타이밍 마진 확보가 어렵기 때문에, eMMC 5.1에서는 Enhanced Strobe (HS400ES)를 도입했습니다.
HS400ES에서는 카드가 DS(Data Strobe) 핀을 통해 데이터와 동기화된 스트로브 신호를 출력합니다. 호스트는 CLK 기반 샘플링 대신 이 DS 신호의 에지에서 데이터를 캡처하므로, CMD21 기반 튜닝이 불필요합니다. 이는 부팅 시간 단축과 안정성 향상에 크게 기여합니다.
mmc_select_hs400()이 이 과정을 처리합니다.
HS400ES는 이 과정이 생략되어 HS → HS400ES로 직접 전환합니다.
커널 튜닝 API
/* include/linux/mmc/host.h — 튜닝 관련 API */
/* 호스트 드라이버가 구현하는 ops */
struct mmc_host_ops {
/* 튜닝 실행: opcode = CMD19(SD) 또는 CMD21(eMMC) */
int (*execute_tuning)(struct mmc_host *host, u32 opcode);
/* 튜닝 준비/해제 */
int (*prepare_hs400_tuning)(struct mmc_host *host,
struct mmc_ios *ios);
/* HS400 Enhanced Strobe 설정 */
void (*hs400_enhanced_strobe)(struct mmc_host *host,
struct mmc_ios *ios);
};
/* core 레이어 튜닝 함수 */
int mmc_execute_tuning(struct mmc_host *host); /* 튜닝 실행 */
void mmc_retune_enable(struct mmc_host *host); /* 재튜닝 활성화 */
void mmc_retune_disable(struct mmc_host *host); /* 재튜닝 비활성화 */
int mmc_retune(struct mmc_host *host); /* 재튜닝 실행 */
void mmc_retune_hold(struct mmc_host *host); /* 재튜닝 일시 중지 */
void mmc_retune_release(struct mmc_host *host); /* 재튜닝 재개 */
/* 재튜닝 타이머: 온도/전압 변동 시 주기적 재튜닝 */
void mmc_retune_timer_stop(struct mmc_host *host);
void mmc_retune_needed(struct mmc_host *host); /* 즉시 재튜닝 요청 */
mmc_retune_timer를 사용하여 자동으로 재튜닝을 수행합니다.
또한 CRC 에러 발생 시 즉시 재튜닝을 트리거할 수 있습니다(mmc_retune_needed()).
제조사별 Quirk 및 호환성
eMMC 표준은 JEDEC에서 정의하지만, 각 제조사의 펌웨어(Firmware) 구현에는 미묘한 차이와 버그가 존재합니다.
Linux 커널은 CID(Card Identification) 레지스터의 manfid(제조사 ID)와 name 필드를 읽어
특정 제조사/모델에 대한 quirk(보정 플래그)를 적용합니다.
이 메커니즘은 drivers/mmc/core/quirks.h와 drivers/mmc/core/card.h에 정의되어 있습니다.
CID 기반 제조사 판별
eMMC의 CID 레지스터(128비트)에서 제조사를 판별하는 핵심 필드는 다음과 같습니다.
| CID 필드 | 비트 위치 | 설명 |
|---|---|---|
MID (Manufacturer ID) | [127:120] | JEDEC 등록 제조사 코드 |
OID (OEM/Application ID) | [119:104] | OEM 식별자 (2바이트 ASCII) |
PNM (Product Name) | [103:56] | 제품명 (6바이트 ASCII) |
PRV (Product Revision) | [55:48] | 펌웨어 버전 (BCD 형식: 상위4=메이저, 하위4=마이너) |
PSN (Product Serial) | [47:16] | 제품 시리얼 번호 (32비트) |
MDT (Manufacturing Date) | [15:8] | 제조 연월 (년:상위4비트+1997, 월:하위4비트) |
# CID 정보 확인 (sysfs 경로)
cat /sys/class/mmc_host/mmc0/mmc0:0001/cid
# 출력 예: 150100514e423136100264f1c700e801
# ──── ──── ──────── ── ──────── ────
# MID OID PNM PRV PSN MDT
# 개별 필드 확인
cat /sys/class/mmc_host/mmc0/mmc0:0001/manfid # 0x000015 (Samsung)
cat /sys/class/mmc_host/mmc0/mmc0:0001/name # QNB16
cat /sys/class/mmc_host/mmc0/mmc0:0001/fwrev # 0x10
cat /sys/class/mmc_host/mmc0/mmc0:0001/hwrev # 0x0
cat /sys/class/mmc_host/mmc0/mmc0:0001/oemid # 0x0100
주요 제조사 ID(MID) 목록
| MID | 제조사 | 주요 제품 라인 | 커널 내 상수 |
|---|---|---|---|
0x15 | Samsung | KLMAG/KLMBG/KLMCG/KLMDG 시리즈 | CID_MANFID_SAMSUNG |
0x90 | SK Hynix | H26M/H28U 시리즈 | CID_MANFID_HYNIX |
0x13 | Micron | MTFC 시리즈 | CID_MANFID_MICRON |
0x45 | SanDisk/WD | iNAND 7x50/8x51 시리즈 | CID_MANFID_SANDISK |
0x11 | Toshiba/Kioxia | THGBMHG/THGBMJG 시리즈 | CID_MANFID_TOSHIBA |
0x70 | Kingston | EMMC04G/08G/16G 시리즈 | CID_MANFID_KINGSTON |
0xFE | Biwin/Foresee | NCard/FORESEE 시리즈 | — |
0x9B | YMTC(장강메모리) | YMTC eMMC | — |
Samsung Quirk
Samsung은 eMMC 시장 점유율이 가장 높으며, Linux 커널에서도 가장 많은 quirk가 등록되어 있습니다.
| Quirk 플래그 | 적용 조건 | 문제 및 보정 |
|---|---|---|
MMC_QUIRK_TRIM_BROKEN |
특정 Samsung eMMC (rev < 0x07) | TRIM/DISCARD 커맨드 발행 시 데이터 손상 가능. 커널이 TRIM을 비활성화합니다. |
MMC_FIXUP_INAND_CMD38 |
iNAND (SanDisk/Samsung OEM) | CMD38(ERASE) 전에 CMD38 arg를 특수 값으로 설정해야 올바른 Secure Erase가 동작합니다. |
| Cache Flush 타임아웃 | Samsung eMMC 5.0 일부 모델 | CMD6(FLUSH_CACHE) 응답 시간이 스펙보다 길어 타임아웃 발생. cache_ctrl을 비활성화하거나 타임아웃을 연장합니다. |
| HS400 CMD13 상태 오류 | 특정 FW 버전 | HS400 전환 직후 CMD13(SEND_STATUS)의 응답에 잘못된 상태 비트가 포함됩니다. 전환 후 딜레이를 추가합니다. |
/* drivers/mmc/core/quirks.h — Samsung eMMC quirk 등록 예시 */
MMC_FIXUP("VYL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY,
add_quirk_mmc, MMC_QUIRK_TRIM_BROKEN),
MMC_FIXUP("KYL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY,
add_quirk_mmc, MMC_QUIRK_TRIM_BROKEN),
/* CID의 name 필드("VYL00M")와 manfid(0x15)가 일치하면
MMC_QUIRK_TRIM_BROKEN 플래그를 설정하여 TRIM 커맨드를 비활성화 */
/* 커널 내부: quirk 적용 로직 */
static void mmc_fixup_device(struct mmc_card *card,
const struct mmc_fixup *table)
{
for (f = table; f->vendor_fixup; f++) {
if ((f->manfid == CID_MANFID_ANY ||
f->manfid == card->cid.manfid) &&
(f->oemid == CID_OEMID_ANY ||
f->oemid == card->cid.oemid) &&
(!f->name[0] ||
!strncmp(f->name, card->cid.prod_name, sizeof(f->name))))
f->vendor_fixup(card, f->data);
}
}
코드 설명
- 2–5행
MMC_FIXUP매크로로 Samsung "VYL00M", "KYL00M" 모델에 TRIM 깨짐 quirk를 등록합니다. CID의 prod_name이 일치할 때 적용됩니다. - 11–21행
mmc_fixup_device는 카드 초기화 시 fixup 테이블을 순회하며, CID의 manfid, oemid, prod_name이 일치하는 항목의vendor_fixup콜백을 호출합니다.
Micron Quirk
| Quirk 플래그 | 적용 조건 | 문제 및 보정 |
|---|---|---|
MMC_QUIRK_TRIM_BROKEN |
Micron eMMC (rev < 0x26, 일부 모델) | TRIM 후 해당 영역 읽기 시 stale 데이터가 반환됩니다. DISCARD만 사용하도록 전환합니다. |
MMC_QUIRK_CMDQ_BROKEN |
특정 Micron FW 버전 | CMDQ 활성화 시 간헐적 데이터 손상. CQE를 비활성화합니다. |
| BKOPS 자동/수동 전환 | Micron MTFC 시리즈 | EXT_CSD BKOPS_EN=1로 자동 BKOPS를 활성화하면 일부 모델에서 예상치 못한 지연이 발생합니다. 수동 BKOPS(BKOPS_START)를 유지하는 것이 안전합니다. |
| Cache 크기 보고 오류 | 일부 MTFC 초기 모델 | EXT_CSD[252-249](CACHE_SIZE)가 실제보다 큰 값을 보고합니다. 커널은 이를 신뢰하되, 캐시 플러시 타임아웃이 부족할 수 있습니다. |
SK Hynix Quirk
| Quirk 플래그 | 적용 조건 | 문제 및 보정 |
|---|---|---|
MMC_QUIRK_BROKEN_HPI |
SK Hynix H26M 일부 모델 | HPI(High Priority Interrupt, CMD12/CMD1) 발행 시 카드가 비정상 상태에 빠집니다. HPI를 비활성화합니다. |
| TRIM 완료 지연 | H26M 초기 버전 | TRIM 커맨드의 R1b 응답이 스펙 최대값(~6초)에 근접합니다. 대량 TRIM은 분할 전송이 필요합니다. |
| HS400 모드 진입 실패 | H26M41208HPR 등 구형 모델 | 특정 호스트 컨트롤러와 조합 시 HS400 전환 CMD6에서 타임아웃 발생합니다. HS200으로 폴백합니다. |
SanDisk/Western Digital Quirk
| Quirk 플래그 | 적용 조건 | 문제 및 보정 |
|---|---|---|
MMC_FIXUP_INAND_CMD38 |
iNAND 시리즈 전체 | CMD38(ERASE) 실행 전에 CMD13으로 특수 arg를 전달해야 합니다. 그렇지 않으면 Secure Erase가 불완전하게 동작합니다. |
MMC_QUIRK_BLK_NO_CMD23 |
iNAND 구형 모델 | CMD23(SET_BLOCK_COUNT) 다중 블록 전송에서 간헐적 CRC 에러. CMD12(STOP) 기반 전송으로 전환합니다. |
| Vendor CMD56 Health Report | iNAND 7x50+ | CMD56(GEN_CMD)으로 512바이트 Health Report를 읽을 수 있습니다(벤더 전용 프로토콜). NAND 배드블록 수, 프로그램/이레이즈 카운트, 초기화 횟수 등을 포함합니다. |
Toshiba/Kioxia Quirk
| Quirk 플래그 | 적용 조건 | 문제 및 보정 |
|---|---|---|
| Packed Command 비호환 | THGBMHG 일부 모델 | Packed Write에서 하위 바이트 순서가 뒤바뀌는 버그. Packed Command를 비활성화합니다. |
| DDR52 CRC 에러 | 특정 FW 버전 | DDR52 모드에서 온도 45°C 이상 시 CRC 에러율 증가. 재튜닝 주기를 단축합니다. |
커널 Quirk 전체 플래그 정리
| Quirk 플래그 | 헤더 | 동작 |
|---|---|---|
MMC_QUIRK_TRIM_BROKEN | card.h | TRIM/DISCARD 커맨드 비활성화 |
MMC_QUIRK_NONSTD_FUNC_IF | card.h | 비표준 SDIO 기능 인터페이스 처리 |
MMC_QUIRK_BROKEN_CLK_GATING | card.h | Clock Gating 비활성화 (Power Save 모드 금지) |
MMC_QUIRK_BLK_NO_CMD23 | card.h | CMD23 대신 CMD12 방식의 다중 블록 전송 사용 |
MMC_QUIRK_BROKEN_HPI | card.h | HPI(High Priority Interrupt) 비활성화 |
MMC_QUIRK_BROKEN_IRQ_POLLING | card.h | 인터럽트 기반 카드 탐지 대신 폴링 사용 |
MMC_QUIRK_CMDQ_BROKEN | card.h | CMDQ(CQE) 비활성화 |
MMC_QUIRK_BROKEN_CACHE_FLUSH | card.h | Cache Flush 비활성화 (Write-through 강제) |
/* drivers/mmc/core/quirks.h — 주요 제조사별 fixup 테이블 (발췌) */
/* Samsung: 여러 모델에서 TRIM 깨짐 */
MMC_FIXUP("VYL00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY,
add_quirk_mmc, MMC_QUIRK_TRIM_BROKEN),
MMC_FIXUP("VTU00M", CID_MANFID_SAMSUNG, CID_OEMID_ANY,
add_quirk_mmc, MMC_QUIRK_TRIM_BROKEN),
/* Micron: 특정 리비전 이하에서 TRIM 깨짐 */
MMC_FIXUP(CID_NAME_ANY, CID_MANFID_MICRON, 0x0200,
add_quirk_mmc_rev, MMC_QUIRK_TRIM_BROKEN, 0, 0x25),
/* SK Hynix: HPI 깨짐 */
MMC_FIXUP(CID_NAME_ANY, CID_MANFID_HYNIX, CID_OEMID_ANY,
add_quirk_mmc, MMC_QUIRK_BROKEN_HPI),
/* Kingston: Cache Flush 이슈 */
MMC_FIXUP("V10016", CID_MANFID_KINGSTON, CID_OEMID_ANY,
add_quirk_mmc, MMC_QUIRK_BROKEN_CACHE_FLUSH),
/* SanDisk iNAND: 특수 CMD38 처리 */
MMC_FIXUP(CID_NAME_ANY, CID_MANFID_SANDISK, CID_OEMID_ANY,
add_quirk_mmc, MMC_FIXUP_INAND_CMD38),
코드 설명
- 3–7행 Samsung의 특정 모델명("VYL00M", "VTU00M")에 대해 TRIM 깨짐 quirk를 적용합니다. 이 모델들은 TRIM 후 데이터가 손상될 수 있습니다.
- 10–11행
Micron은 모델명 대신 OEM ID와 리비전 범위(0x00~0x25)로 조건을 지정합니다.
add_quirk_mmc_rev는 PRV 필드를 추가로 검사합니다. - 14–15행
SK Hynix는 전 모델(
CID_NAME_ANY)에 대해 HPI를 비활성화합니다. - 22–23행 SanDisk iNAND 전 모델에 CMD38 fixup을 적용합니다. Secure Erase/Trim 전에 특수 시퀀스가 필요합니다.
벤더 전용 커맨드(Vendor Command)
JEDEC 표준은 CMD56(GEN_CMD)과 CMD62를 벤더 전용 용도로 예약합니다.
각 제조사는 이 커맨드를 통해 표준에 없는 진단, 모니터링, 테스트 기능을 제공합니다.
| 제조사 | 커맨드 | 기능 | 데이터 크기 |
|---|---|---|---|
| SanDisk | CMD56 (arg=0x110005F9) | Health Report — 배드블록 수, P/E 카운트, 초기화 횟수, 리프레시 횟수 | 512B |
| Samsung | CMD56 (arg=벤더 고유) | Smart Report — 프로그램/이레이즈 카운트, 온도, 초기 배드블록/런타임 배드블록 | 512B |
| Micron | CMD56 (arg=0x110005F9) | Health Status — SanDisk와 유사한 포맷(일부 호환) | 512B |
| Toshiba | CMD62 (arg=벤더 고유) | 테스트 모드 진입 — FTL 정보, 내부 NAND 상태 조회 | 가변 |
| SK Hynix | CMD56 (arg=벤더 고유) | Device Report — 수명 예측, 이레이즈 카운트, 전원 사이클 수 | 512B |
/* SanDisk iNAND Health Report 읽기 예시 (커널 모듈 또는 유저스페이스 ioctl) */
struct mmc_command cmd = {
.opcode = MMC_GEN_CMD, /* CMD56 */
.arg = 0x110005F9, /* SanDisk Health Report arg */
.flags = MMC_RSP_R1 | MMC_CMD_ADTC,
};
struct mmc_data data = {
.blksz = 512,
.blocks = 1,
.flags = MMC_DATA_READ,
.sg = &sg,
.sg_len = 1,
};
u8 health_buf[512];
sg_init_one(&sg, health_buf, 512);
mmc_claim_host(card->host);
mmc_wait_for_req(card->host, &mrq);
mmc_release_host(card->host);
/* health_buf 파싱 예시 (SanDisk iNAND 포맷) */
u32 initial_bad = health_buf[8] | (health_buf[9] << 8); /* 초기 배드블록 */
u32 runtime_bad = health_buf[10] | (health_buf[11] << 8); /* 런타임 배드블록 */
u32 pe_count = health_buf[12] | (health_buf[13] << 8)
| (health_buf[14] << 16); /* 최대 P/E 카운트 */
u32 power_cycles = health_buf[16] | (health_buf[17] << 8)
| (health_buf[18] << 16); /* 전원 사이클 횟수 */
코드 설명
- 2–5행
CMD56에 SanDisk Health Report 전용 argument(
0x110005F9)를 설정합니다. 이 값은 SanDisk 기술 문서에서 정의된 것입니다. - 17–19행
mmc_claim_host로 호스트를 점유한 후 요청을 전송하고, 완료 후 해제합니다. 다른 I/O와의 경합을 방지합니다. - 22–27행 Health Report의 특정 오프셋에서 초기 배드블록 수, 런타임 배드블록 수, P/E 카운트, 전원 사이클 횟수를 추출합니다.
SDHCI 호스트 Quirk 주요 플래그
디바이스(eMMC 카드) 측 quirk 외에, 호스트 컨트롤러 측에도 SDHCI_QUIRK_* 플래그가 있습니다.
이들은 SoC별 SDHCI 구현의 비표준 동작을 보정합니다.
| Quirk 플래그 | 설명 | 적용 SoC 예시 |
|---|---|---|
SDHCI_QUIRK_BROKEN_CARD_DETECTION |
카드 탐지 레지스터가 동작하지 않음. GPIO 또는 non-removable 사용 | BCM2835, 일부 Intel BYT |
SDHCI_QUIRK_BROKEN_TIMEOUT_VAL |
타임아웃 레지스터 값이 부정확함. 최대값으로 고정 | Ricoh, JMicron |
SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC |
ADMA NOP descriptor에서 End 속성 미인식 | NXP i.MX, Intel BYT |
SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
Preset Value 레지스터가 잘못된 값을 보고하여 수동 설정 필요 | NXP i.MX, Qualcomm MSM |
SDHCI_QUIRK2_STOP_WITH_TC |
CMD12(STOP) 응답에 TC(Transfer Complete) 비트가 없음 | Intel SPT |
SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12 |
다중 블록 읽기에 Auto CMD12가 필요함 | Qualcomm MSM |
SDHCI_QUIRK2_BROKEN_DDR50 |
DDR50 모드가 불안정하여 SDR50으로 폴백 | 일부 Marvell |
SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK |
데이터 타임아웃이 SD 클럭 기반 (TMCLK 아님) | BCM2835 |
cat /sys/class/mmc_host/mmc0/mmc0:0001/quirks로 확인할 수 없지만,
dmesg | grep -i quirk으로 커널 부팅 시 적용된 quirk 로그를 볼 수 있습니다.
호스트 quirk는 cat /sys/kernel/debug/mmc0/ios의 quirks 필드에서 확인합니다.
새로운 eMMC 모델에서 문제가 발생하면, CID를 확인하고 quirks.h에 fixup을 추가하는 것이 표준 해결 방법입니다.
EXT_CSD 레지스터
EXT_CSD(Extended CSD)는 eMMC 카드의 확장 설정 레지스터로, 총 512바이트 크기입니다. 기존 CSD 레지스터(128비트)로는 표현할 수 없는 eMMC 고유 기능들의 설정과 상태 정보를 담고 있으며, CMD8 (SEND_EXT_CSD)을 통해 읽을 수 있습니다.
레지스터 구조
EXT_CSD는 두 영역으로 나뉩니다:
- Properties 영역 (바이트 192~511): 읽기 전용(Read-Only). 카드의 하드웨어 능력 및 스펙 정보를 제공합니다.
- Modes 영역 (바이트 0~191): 읽기/쓰기 가능. 카드의 동작 모드를 설정합니다. CMD6 (SWITCH)로 변경합니다.
| 바이트 오프셋 | 필드명 | R/W | 설명 |
|---|---|---|---|
| 192 | EXT_CSD_REV | R | EXT_CSD 리비전 (8=v5.1, 7=v5.0, 6=v4.5) |
| 194 | CSD_STRUCTURE | R | CSD 구조 버전 |
| 196 | CARD_TYPE | R | 지원 속도 모드 비트마스크 (HS/DDR/HS200/HS400) |
| 212~215 | SEC_COUNT | R | 사용자 영역 섹터 수 (512바이트 단위, 최대 2TB) |
| 183 | BUS_WIDTH | W | 버스 폭 설정 (0=1-bit, 1=4-bit, 2=8-bit, 5=4-bit DDR, 6=8-bit DDR) |
| 185 | HS_TIMING | W | 속도 모드 설정 (0=Legacy, 1=HS, 2=HS200, 3=HS400) |
| 179 | PARTITION_CONFIG | R/W | 부트 파티션 활성화 및 접근 파티션 선택 |
| 177 | BOOT_BUS_CONDITIONS | R/W | 부트 모드 버스 폭 및 리셋 후 버스 조건 |
| 168 | RPMB_SIZE_MULT | R | RPMB 파티션 크기 (128KB 단위) |
| 162 | RST_N_FUNCTION | R/W | 하드웨어 리셋 핀(RST_n) 기능 활성화 (0=비활성, 1=활성, 2=영구 활성) |
| 163 | BKOPS_EN | R/W | 백그라운드 작업 활성화 (자동/수동) |
| 33 | CACHE_CTRL | R/W | 캐시 활성화/비활성화 (1=활성) |
| 249~252 | CACHE_SIZE | R | 쓰기 캐시 크기 (KB 단위) |
| 15 | CMDQ_MODE_EN | R/W | Command Queue 모드 활성화 |
| 307 | CMDQ_DEPTH | R | Command Queue 깊이 (최대 32 슬롯) |
| 267 | PRE_EOL_INFO | R | 수명 종료 사전 경고 (0=정상, 1=소비 80% 미만, 2=소비 80% 초과, 3=긴급) |
| 268 | DEVICE_LIFE_TIME_EST_TYP_A | R | 수명 추정 Type A (SLC, 0x01=0~10% 사용, ... 0x0B=100% 사용) |
| 269 | DEVICE_LIFE_TIME_EST_TYP_B | R | 수명 추정 Type B (MLC/TLC) |
| 254~261 | FIRMWARE_VERSION | R | 카드 펌웨어(Firmware) 버전 (8바이트 ASCII 또는 HEX) |
| 503 | SUPPORTED_MODES | R | FFU 지원 모드 (필드 펌웨어 업데이트) |
mmc-utils로 EXT_CSD 읽기
# 전체 EXT_CSD 덤프
sudo mmc extcsd read /dev/mmcblk0
# 특정 필드 확인 (사람이 읽기 편한 형태)
sudo mmc extcsd read /dev/mmcblk0 | grep -i "card type"
sudo mmc extcsd read /dev/mmcblk0 | grep -i "life time"
# EXT_CSD 원시 데이터를 파일로 덤프
sudo mmc extcsd dump /dev/mmcblk0 > extcsd_dump.bin
# 수명 정보 확인 (eMMC 건강 상태)
sudo mmc extcsd read /dev/mmcblk0 | grep -E "EOL|LIFE_TIME"
# 펌웨어 버전 확인
sudo mmc extcsd read /dev/mmcblk0 | grep -i firmware
커널에서 EXT_CSD 접근
/* drivers/mmc/core/mmc_ops.c — EXT_CSD 읽기 */
int mmc_get_ext_csd(struct mmc_card *card, u8 **new_ext_csd)
{
int err;
u8 *ext_csd;
if (!card || !new_ext_csd)
return -EINVAL;
if (!mmc_can_ext_csd(card))
return -EOPNOTSUPP;
ext_csd = kzalloc(512, GFP_KERNEL);
if (!ext_csd)
return -ENOMEM;
/* CMD8 (SEND_EXT_CSD) 전송 */
err = mmc_send_ext_csd(card, ext_csd);
if (err) {
kfree(ext_csd);
return err;
}
*new_ext_csd = ext_csd;
return 0;
}
/* EXT_CSD 특정 필드 읽기 예시 */
static void mmc_decode_ext_csd(struct mmc_card *card, u8 *ext_csd)
{
/* Properties 영역 (읽기 전용) */
card->ext_csd.rev = ext_csd[EXT_CSD_REV]; /* [192] */
card->ext_csd.card_type = ext_csd[EXT_CSD_CARD_TYPE]; /* [196] */
card->ext_csd.sectors =
ext_csd[EXT_CSD_SEC_CNT + 0] << 0 |
ext_csd[EXT_CSD_SEC_CNT + 1] << 8 |
ext_csd[EXT_CSD_SEC_CNT + 2] << 16 |
ext_csd[EXT_CSD_SEC_CNT + 3] << 24; /* [212-215] */
/* 수명 정보 */
card->ext_csd.pre_eol_info = ext_csd[267];
card->ext_csd.device_life_time_est_typ_a = ext_csd[268];
card->ext_csd.device_life_time_est_typ_b = ext_csd[269];
/* Modes 영역 (읽기/쓰기 가능) — CMD6 SWITCH로 변경 */
card->ext_csd.part_config = ext_csd[EXT_CSD_PART_CONFIG]; /* [179] */
card->ext_csd.cache_size =
ext_csd[249] | ext_csd[250] << 8 |
ext_csd[251] << 16 | ext_csd[252] << 24; /* [249-252] */
card->ext_csd.cache_ctrl = ext_csd[33];
}
/sys/class/mmc_host/mmc0/mmc0:0001/ 디렉토리에서
life_time, pre_eol_info, fwrev,
hwrev, csd 등의 파일을 직접 읽을 수 있어,
mmc-utils 없이도 기본 정보를 확인할 수 있습니다.
eMMC 파티션 관리
eMMC는 소프트웨어 파티션 테이블(GPT/MBR)과 별개로, 하드웨어 수준의 물리 파티션을 내장하고 있습니다. 각 파티션은 독립적인 주소 공간(Address Space)과 접근 제어(Access Control)를 가지며, Linux 커널은 이를 별도의 블록 디바이스로 노출합니다.
| 파티션 유형 | 기본 크기 | 접근 방법 | 디바이스 노드 | 설명 |
|---|---|---|---|---|
| User Data Area (UDA) | 전체 용량 - 기타 파티션 | 일반 R/W | /dev/mmcblk0 |
기본 데이터 저장 영역 (GPT/MBR 생성 가능) |
| Boot Partition 1 | 128KB ~ 32MB | PARTITION_CONFIG | /dev/mmcblk0boot0 |
부트로더(Bootloader) 저장, 전용 erase group |
| Boot Partition 2 | 128KB ~ 32MB | PARTITION_CONFIG | /dev/mmcblk0boot1 |
백업 부트로더 저장 |
| RPMB | 128KB ~ 16MB | 인증된 R/W | /dev/mmcblk0rpmb |
Replay Protected Memory Block (보안 저장소) |
| GP1 ~ GP4 | 0 (미설정 시) | PARTITION_CONFIG | /dev/mmcblk0gp0 ~ gp3 |
범용 파티션 (Enhanced/SLC 가능, 공장 설정 시 1회만 생성) |
PARTITION_CONFIG 레지스터
EXT_CSD[179] PARTITION_CONFIG 레지스터는 부트 파티션 활성화와 접근 대상 파티션을 제어합니다:
- 비트 [5:3] BOOT_PARTITION_ENABLE: 부팅 시 읽을 파티션 지정 (0=미사용, 1=Boot1, 2=Boot2, 7=UDA)
- 비트 6 BOOT_ACK: 부트 ACK 응답 활성화
- 비트 [2:0] PARTITION_ACCESS: 현재 접근 대상 파티션 (0=UDA, 1=Boot1, 2=Boot2, 3=RPMB, 4=GP1, ...)
# 부트 파티션 설정: Boot Partition 1에서 부팅, ACK 활성화
sudo mmc bootpart enable 1 1 /dev/mmcblk0
# 현재 부트 설정 확인
sudo mmc extcsd read /dev/mmcblk0 | grep "PARTITION_CONFIG"
# 부트 버스 조건 설정 (8-bit, HS 모드)
sudo mmc bootbus set single_backward retain x8 /dev/mmcblk0
RPMB (Replay Protected Memory Block)
RPMB는 인증된 접근만 허용하는 보안 파티션입니다. 일반적인 read/write 대신 인증 프로토콜을 사용하며, 재전송(Retransmission) 공격(replay attack)을 방지합니다.
- 인증 키: 256비트 HMAC-SHA256 키를 1회 프로그래밍 (변경 불가)
- 쓰기 카운터: 매 인증 쓰기마다 단조 증가 (재전송 방지)
- MAC: 모든 접근 프레임에 HMAC-SHA256 MAC 첨부
- 프레임 크기: 512바이트 고정 (256바이트 데이터 + 메타데이터)
# RPMB 키 프로그래밍 (최초 1회, 복구 불가!)
sudo mmc rpmb write-key /dev/mmcblk0rpmb /path/to/key_file
# RPMB 쓰기 카운터 읽기
sudo mmc rpmb read-counter /dev/mmcblk0rpmb
# RPMB 데이터 쓰기 (인증 필요)
sudo mmc rpmb write-block /dev/mmcblk0rpmb 0 /path/to/data /path/to/key
# RPMB 데이터 읽기
sudo mmc rpmb read-block /dev/mmcblk0rpmb 0 1 -
부트 파티션 쓰기
Linux 커널은 부트 파티션을 기본적으로 읽기 전용으로 마운트합니다.
쓰기를 위해서는 force_ro sysfs 파라미터를 해제해야 합니다.
# 부트 파티션 쓰기 보호 해제
echo 0 > /sys/block/mmcblk0boot0/force_ro
# 부트로더 이미지 기록
sudo dd if=u-boot.bin of=/dev/mmcblk0boot0 bs=512 seek=0 conv=fsync
# 쓰기 보호 재활성화
echo 1 > /sys/block/mmcblk0boot0/force_ro
# Boot Partition 1에서 부팅하도록 설정
sudo mmc bootpart enable 1 1 /dev/mmcblk0
Enhanced User Data Area 및 GP 파티션
eMMC 4.4 이상에서는 User Data Area의 일부를 Enhanced(SLC 모드)로 설정하거나, GP(General Purpose) 파티션을 생성할 수 있습니다. 이 작업은 공장 설정 시 1회만 가능하며 (PARTITION_SETTING_COMPLETED[155] = 1), 이후에는 변경할 수 없습니다.
# Enhanced user data area 생성 (SLC 영역)
# 주의: 1회성 설정이며 전체 용량이 줄어듦
sudo mmc enh_area set -y 0 524288 /dev/mmcblk0 # 시작 0, 512MB
# GP 파티션 생성 (Enhanced 속성 포함)
sudo mmc gp create -y 1 1048576 enh /dev/mmcblk0 # GP1, 1GB, Enhanced
# 설정 완료 (복구 불가!)
sudo mmc write_reliability set -y 1 /dev/mmcblk0
mmcblk0이지만,
SD 카드 슬롯이 먼저 probe되면 mmcblk1이 될 수 있습니다.
안정적인 참조를 위해 /dev/disk/by-path/ 또는
/dev/disk/by-partuuid/ 심볼릭 링크를 사용하는 것이 권장됩니다.
데이터 무결성(Integrity)
eMMC 프로토콜은 여러 계층에서 데이터 무결성을 보장하는 메커니즘을 제공합니다. CRC 보호부터 안정적인 쓰기, 캐시 관리, 백그라운드 작업까지 다양한 기능이 데이터 손실을 방지하고 스토리지 성능을 유지합니다.
CRC 보호
eMMC 프로토콜은 모든 전송에 CRC(순환 중복 검사)를 적용합니다:
- CMD 라인: CRC7 — 48비트 커맨드/응답 프레임의 마지막 7비트
- DAT 라인: CRC16 — 각 DAT 라인별로 독립적인 16비트 CRC
- CRC 에러 발생 시 호스트 컨트롤러가 인터럽트를 생성하고, 커널은 해당 요청을 재시도합니다
Reliable Write
일반 쓰기는 갑작스러운 전원 차단 시 데이터가 일부만 기록될 수 있습니다. Reliable Write는 이를 방지하여 원자적(atomic) 쓰기를 보장합니다.
/* CMD23 (SET_BLOCK_COUNT)의 bit 31을 설정하여 Reliable Write 활성화 */
struct mmc_command cmd = {
.opcode = MMC_SET_BLOCK_COUNT,
.arg = (1 << 31) | block_count, /* bit 31 = Reliable Write */
.flags = MMC_RSP_R1 | MMC_CMD_AC,
};
/*
* Reliable Write 동작 방식:
* - 전원 차단 시 이전 상태 또는 완전히 새 상태 중 하나가 보장됨
* - 부분 기록(torn write) 방지
* - 512바이트 단위 원자성 (Reliable Write Size에 따라 다름)
* - EXT_CSD[166] REL_WR_SEC_C: 최대 Reliable Write 섹터 수
*/
/* 커널에서의 활용: mmc_blk_data_prep() */
if (brq->data.blocks > card->ext_csd.rel_sectors)
brq->data.blocks = card->ext_csd.rel_sectors;
if (req_op(req) == REQ_OP_WRITE &&
(req->cmd_flags & REQ_META) &&
(card->ext_csd.rel_param & EXT_CSD_WR_REL_PARAM_EN))
brq->sbc.arg |= (1 << 31); /* Reliable Write flag */
Power-off Notification
eMMC 4.5에서 도입된 POWER_OFF_NOTIFICATION(EXT_CSD[34])은 카드에 전원 차단을 사전 통지하여 내부 캐시를 안전하게 플러시(Flush)할 시간을 제공합니다.
/* drivers/mmc/core/mmc.c — 전원 차단 통지 */
static void mmc_poweroff_notify(struct mmc_card *card, unsigned int notify_type)
{
unsigned int timeout;
/* EXT_CSD[34] POWER_OFF_NOTIFICATION 설정 */
/* notify_type: EXT_CSD_POWER_OFF_SHORT (1) 또는 LONG (2) */
mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_POWER_OFF_NOTIFICATION,
notify_type, card->ext_csd.generic_cmd6_time);
if (notify_type == EXT_CSD_POWER_OFF_SHORT)
timeout = card->ext_csd.power_off_longtime; /* 수백 ms */
else
timeout = card->ext_csd.power_off_longtime; /* 최대 수 초 */
/* 카드가 내부 캐시를 플러시할 때까지 대기 */
/* 이후 안전하게 VCC/VCCQ 차단 가능 */
}
캐시 관리
eMMC 4.5 이상의 캐시 기능은 쓰기 성능을 크게 향상시키지만, 갑작스러운 전원 차단 시 캐시의 데이터가 손실될 수 있습니다.
/* 캐시 활성화: EXT_CSD[33] CACHE_CTRL = 1 */
int mmc_cache_ctrl(struct mmc_host *host, u8 enable)
{
struct mmc_card *card = host->card;
if (!(card->ext_csd.cache_size > 0))
return 0; /* 캐시 미지원 */
return mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_CACHE_CTRL, enable,
card->ext_csd.generic_cmd6_time);
}
/* 캐시 플러시: CMD6 → FLUSH_CACHE (EXT_CSD[32] = 1) */
int mmc_flush_cache(struct mmc_card *card)
{
if (!(card->ext_csd.cache_ctrl & 1))
return 0; /* 캐시 비활성 */
return mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_FLUSH_CACHE, 1,
card->ext_csd.cache_flush_timeout);
}
/* barrier flush: REQ_FUA + REQ_PREFLUSH 처리 */
/* blk-mq에서 REQ_FUA 요청 시 mmc_blk_issue_flush()가 캐시 플러시 수행 */
Background Operations (BKOPS)
eMMC 내부의 가비지 컬렉션, 웨어 레벨링 등의 유지보수 작업을 BKOPS(Background Operations)라 합니다. 호스트가 이를 적절히 관리하지 않으면 쓰기 성능이 시간이 지남에 따라 급격히 저하될 수 있습니다.
/* BKOPS 긴급도 수준 (EXT_CSD[246] BKOPS_STATUS) */
#define BKOPS_STATUS_NO_OP 0 /* 유지보수 불필요 */
#define BKOPS_STATUS_NON_CRITICAL 1 /* 비긴급: 여유 시간에 수행 */
#define BKOPS_STATUS_PERF_IMPACT 2 /* 성능 영향: 곧 수행 필요 */
#define BKOPS_STATUS_CRITICAL 3 /* 긴급: 즉시 수행 필요 */
/* 수동 BKOPS 시작: CMD6 → BKOPS_START (EXT_CSD[164] = 1) */
int mmc_start_bkops(struct mmc_card *card)
{
int err;
if (!card->ext_csd.man_bkops_en)
return 0;
err = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_BKOPS_START, 1,
MMC_BKOPS_TIMEOUT_MS);
if (!err)
card->doing_bkops = true;
return err;
}
/* 자동 BKOPS: EXT_CSD[163] BKOPS_EN bit 1 설정 */
/* eMMC 5.0+에서 자동 BKOPS가 권장됨 — 카드가 유휴 시 자동 수행 */
int mmc_set_auto_bkops(struct mmc_card *card, bool enable)
{
return mmc_switch(card, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_BKOPS_EN,
enable ? card->ext_csd.bkops_en | 0x2
: card->ext_csd.bkops_en & ~0x2,
card->ext_csd.generic_cmd6_time);
}
Write Protection
eMMC는 세 가지 수준의 쓰기 보호를 제공합니다:
- 임시 보호 (Temporary): CMD28/CMD29로 설정/해제, 전원 차단 시 초기화
- 전원 보호 (Power-on): CMD28로 설정, 전원 차단 시 초기화되지만 런타임 중 해제 불가
- 영구 보호 (Permanent): CMD28로 설정, 해제 불가 — 프로덕션 환경에서 주의
/* Write Protection Group Size:
* WP_GRP_SIZE (CSD) × ERASE_GRP_SIZE 단위로 보호 영역 설정
* 일반적으로 수 MB 단위
*/
/* 쓰기 보호 설정 */
struct mmc_command cmd = {
.opcode = MMC_CMD_SET_WRITE_PROT, /* CMD28 */
.arg = sector_addr, /* WP 그룹 시작 섹터 */
.flags = MMC_RSP_R1B | MMC_CMD_AC,
};
/* 쓰기 보호 해제 (임시 보호만 가능) */
struct mmc_command cmd = {
.opcode = MMC_CMD_CLR_WRITE_PROT, /* CMD29 */
.arg = sector_addr,
.flags = MMC_RSP_R1B | MMC_CMD_AC,
};
전원 관리(Power Management)
eMMC의 전원 관리는 카드 상태 머신, 전압 제어, Linux PM 프레임워크가 통합되어 동작합니다. 임베디드 시스템에서 전력 소비 최적화는 배터리 수명에 직결되므로, eMMC의 전원 상태를 적절히 관리하는 것이 중요합니다.
eMMC 카드 상태
eMMC 카드는 MMC 프로토콜에 따라 다음 상태들을 갖습니다:
| 상태 | 약자 | 설명 | 전환 커맨드 |
|---|---|---|---|
| Idle | idle | 초기화 시작 전 상태 | CMD0 (GO_IDLE_STATE) |
| Ready | ready | 식별 준비 완료 | CMD1 (SEND_OP_COND) |
| Identification | ident | CID 전송 대기 | CMD2 (ALL_SEND_CID) |
| Standby | stby | RCA 할당됨, 비활성 | CMD3 (SET_RELATIVE_ADDR) |
| Transfer | tran | 데이터 전송 준비 완료 | CMD7 (SELECT_CARD) |
| Sending-data | data | 읽기 데이터 전송 중 | CMD17/18 (READ) |
| Receive-data | rcv | 쓰기 데이터 수신 중 | CMD24/25 (WRITE) |
| Programming | prg | 내부 쓰기(NAND 프로그래밍) 진행 중 | 데이터 전송 완료 후 자동 |
| Disconnect | dis | CMD7 deselect로 연결 해제 | CMD7 (DESELECT) |
| Bus Test | btst | 버스 테스트 모드 | CMD19/CMD14 |
| Sleep | slp | 저전력 수면 모드 | CMD5 (SLEEP_AWAKE) |
| Inactive | ina | 비활성 (전원 사이클 필요) | CMD15 (GO_INACTIVE_STATE) |
전원 상태 전환
eMMC의 전원 관련 전환 시퀀스는 다음과 같습니다:
- Power-off → Pre-idle: VCC 인가 후 CMD0 전송
- Pre-idle → Idle: CMD0 수신, 내부 레지스터 초기화
- 활성 → Sleep: CMD7 deselect → CMD5 (Sleep) — VCC 유지, VCCQ 차단 가능
- Sleep → Power-off: VCC 차단 (VCCQ는 이미 차단되었거나 함께 차단)
- Sleep → 활성: CMD5 (Awake) → CMD7 select
전압 제어
eMMC는 두 가지 전원 레일을 사용합니다:
- VCC (vmmc-supply): NAND 플래시 전원, 2.7~3.6V (일반적으로 3.3V)
- VCCQ (vqmmc-supply): I/O 인터페이스 전원, 1.7~1.95V 또는 2.7~3.6V
HS200/HS400 모드에서는 1.8V VCCQ가 필수입니다. CMD11 (VOLTAGE_SWITCH)을 통해 3.3V에서 1.8V로 전환하며, Linux에서는 regulator 프레임워크로 관리합니다.
/* drivers/mmc/core/core.c — 전원 관리 */
void mmc_power_up(struct mmc_host *host, u32 ocr)
{
/* 1단계: VCC(vmmc) 전원 인가 */
mmc_set_initial_state(host);
if (!IS_ERR(host->supply.vmmc))
mmc_regulator_set_ocr(host, host->supply.vmmc, ocr);
/* 2단계: VCCQ(vqmmc) 전원 인가 */
if (!IS_ERR(host->supply.vqmmc))
regulator_enable(host->supply.vqmmc);
/* 3단계: 클럭 활성화 및 초기 주파수 설정 */
mmc_set_ios(host);
mmc_delay(host->ios.power_delay_ms);
/* 4단계: 클럭 주파수를 초기화 속도(400kHz 이하)로 설정 */
mmc_set_clock(host, host->f_init);
mmc_delay(host->ios.power_delay_ms);
}
void mmc_power_off(struct mmc_host *host)
{
/* 클럭 중지 */
mmc_set_clock(host, 0);
/* VCC 차단 */
if (!IS_ERR(host->supply.vmmc))
mmc_regulator_set_ocr(host, host->supply.vmmc, 0);
/* VCCQ 차단 */
if (!IS_ERR(host->supply.vqmmc))
regulator_disable(host->supply.vqmmc);
mmc_set_ios(host);
}
/* Sleep 상태 전환 */
static int mmc_sleep(struct mmc_host *host)
{
struct mmc_card *card = host->card;
int err;
/* CMD7 deselect */
mmc_deselect_cards(host);
/* CMD5 with Sleep bit */
err = mmc_card_sleepawake(host, 1);
if (err)
return err;
/* Sleep 상태에서는 VCCQ 차단 가능 (전력 절감) */
mmc_set_ios(host);
return 0;
}
Runtime PM 통합
/* drivers/mmc/core/block.c — Runtime PM */
static int mmc_blk_runtime_suspend(struct mmc_host *host)
{
/* 큐 중지 + 캐시 플러시 + Sleep 모드 진입 */
return mmc_power_save_host(host);
}
static int mmc_blk_runtime_resume(struct mmc_host *host)
{
/* 전원 복구 + 카드 초기화 + 큐 재개 */
return mmc_power_restore_host(host);
}
/* 호스트 드라이버에서의 사용 */
static int sdhci_runtime_suspend_host(struct sdhci_host *host)
{
unsigned long flags;
/* 미완료 요청 대기 */
mmc_retune_timer_stop(host->mmc);
spin_lock_irqsave(&host->lock, flags);
host->ier &= SDHCI_INT_CARD_INT;
sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
spin_unlock_irqrestore(&host->lock, flags);
synchronize_hardirq(host->irq);
/* 클럭 게이팅 */
sdhci_set_clock(host, 0);
return 0;
}
/* Autosuspend 설정 (sysfs) */
/* /sys/devices/.../power/autosuspend_delay_ms (기본 3000ms) */
/* pm_runtime_set_autosuspend_delay(dev, 3000); */
mmc_poweroff_notify()로 카드에 전원 차단을 사전 통지하면,
카드가 내부 캐시를 플러시하고 메타데이터를 안전하게 저장합니다.
급격한 전원 차단(unexpected power loss)은 데이터 손실의 주요 원인이므로,
배터리 기반 시스템에서는 저전압 감지 시 즉시 POWER_OFF_SHORT를 발행하는 것이 권장됩니다.
인라인 암호화
blk-crypto 프레임워크는 eMMC/UFS 컨트롤러의 하드웨어 인라인 암호화 엔진(ICE)을 활용하여 스토리지 I/O를 투명하게 암호화합니다. fscrypt와 통합되어 파일 단위 암호화를 지원하며, 하드웨어 미지원 시 소프트웨어 폴백을 자동 적용합니다.
디바이스 트리 바인딩
ARM/ARM64 기반 임베디드 시스템에서 eMMC 호스트 컨트롤러는 디바이스 트리(Device Tree)를 통해 하드웨어 설정을 기술합니다. 올바른 DT 바인딩은 eMMC 초기화, 속도 모드 협상, 전원 관리의 기반이 됩니다.
표준 mmc-controller 바인딩 속성
| 속성 | 타입 | 필수/선택 | 설명 |
|---|---|---|---|
compatible |
string | 필수 | 호스트 컨트롤러 드라이버 매칭 문자열 |
reg |
u32 array | 필수 | 레지스터 베이스 주소 및 크기 |
interrupts |
u32 array | 필수 | 인터럽트 번호 및 타입 |
bus-width |
u32 | 선택 | 데이터 버스 폭: 1, 4, 또는 8 (기본 1) |
max-frequency |
u32 | 선택 | 최대 클럭 주파수 (Hz) |
non-removable |
boolean | 선택 | eMMC 필수 — 카드 탈착 불가 표시 |
cap-mmc-highspeed |
boolean | 선택 | High Speed (52MHz) 모드 지원 |
mmc-hs200-1_8v |
boolean | 선택 | HS200 (1.8V VCCQ) 모드 지원 |
mmc-hs400-1_8v |
boolean | 선택 | HS400 (1.8V VCCQ) 모드 지원 |
mmc-hs400-enhanced-strobe |
boolean | 선택 | HS400ES (Data Strobe) 모드 지원 |
mmc-ddr-1_8v |
boolean | 선택 | DDR52 (1.8V VCCQ) 모드 지원 |
no-sdio |
boolean | 선택 | SDIO 프로빙 비활성화 |
no-sd |
boolean | 선택 | SD 카드 프로빙 비활성화 |
vmmc-supply |
phandle | 선택 | VCC 전원 레귤레이터 (3.3V) |
vqmmc-supply |
phandle | 선택 | VCCQ I/O 전원 레귤레이터 (1.8V/3.3V) |
pinctrl-names |
string list | 선택 | 핀 컨트롤 상태 이름 ("default", "state_uhs" 등) |
pinctrl-0 |
phandle | 선택 | 기본 상태 핀 설정 |
Qualcomm (sdhci-msm) DT 예시
/* Qualcomm SDM845 — sdhci-msm with ICE crypto */
sdhc_2: sdhci@8804000 {
compatible = "qcom,sdm845-sdhci", "qcom,sdhci-msm-v5";
reg = <0x08804000 0x1000>, /* HC 레지스터 */
<0x08805000 0x1000>; /* CQE 레지스터 */
reg-names = "hc", "cqhci";
interrupts = <GIC_SPI 204 IRQ_TYPE_LEVEL_HIGH>;
interrupt-names = "hc_irq";
clocks = <&gcc GCC_SDCC2_AHB_CLK>,
<&gcc GCC_SDCC2_APPS_CLK>,
<&xo_board>;
clock-names = "iface", "core", "xo";
bus-width = <8>;
max-frequency = <200000000>; /* 200 MHz */
non-removable;
/* 속도 모드 */
cap-mmc-highspeed;
mmc-ddr-1_8v;
mmc-hs200-1_8v;
mmc-hs400-1_8v;
mmc-hs400-enhanced-strobe;
/* 전원 */
vmmc-supply = <&vreg_l21a_2p95>; /* VCC 2.95V */
vqmmc-supply = <&vreg_l13a_1p8>; /* VCCQ 1.8V */
/* 핀 컨트롤 */
pinctrl-names = "default", "sleep";
pinctrl-0 = <&sdc2_default>;
pinctrl-1 = <&sdc2_sleep>;
/* SD/SDIO 프로빙 비활성화 (eMMC 전용) */
no-sdio;
no-sd;
/* Inline Crypto Engine */
supports-cqe;
supports-crypto;
/* QoS */
qcom,ice-clk-rates = <300000000 150000000>;
status = "okay";
};
Samsung Exynos (dw_mmc-exynos) DT 예시
/* Samsung Exynos7420 — Synopsys DesignWare MMC */
dwmmc0: mmc@15740000 {
compatible = "samsung,exynos7-dw-mshc-smu";
reg = <0x15740000 0x2000>;
interrupts = <GIC_SPI 245 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cmu_fsys CLK_ACLK_MMC0>,
<&cmu_fsys CLK_SCLK_MMC0>;
clock-names = "biu", "ciu";
fifo-depth = <64>;
card-detect-delay = <200>;
bus-width = <8>;
cap-mmc-highspeed;
mmc-ddr-1_8v;
mmc-hs200-1_8v;
mmc-hs400-1_8v;
non-removable;
broken-cd;
vmmc-supply = <&ldo18_reg>;
vqmmc-supply = <&ldo3_reg>;
samsung,dw-mshc-ciu-div = <3>;
samsung,dw-mshc-sdr-timing = <0 4>;
samsung,dw-mshc-ddr-timing = <2 4>;
samsung,dw-mshc-hs400-timing = <0 2>;
pinctrl-names = "default";
pinctrl-0 = <&sd0_clk &sd0_cmd &sd0_rdqs &sd0_bus1 &sd0_bus4 &sd0_bus8>;
status = "okay";
};
Rockchip (dwcmshc / sdhci-of-arasan) DT 예시
/* Rockchip RK3588 — Synopsys DWC MSHC */
sdhci: mmc@fe2e0000 {
compatible = "rockchip,rk3588-dwcmshc";
reg = <0x0 0xfe2e0000 0x0 0x10000>;
interrupts = <GIC_SPI 219 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru CCLK_EMMC>,
<&cru HCLK_EMMC>,
<&cru ACLK_EMMC>,
<&cru BCLK_EMMC>,
<&cru TMCLK_EMMC>;
clock-names = "core", "bus", "axi", "block", "timer";
bus-width = <8>;
max-frequency = <200000000>;
non-removable;
cap-mmc-highspeed;
mmc-ddr-1_8v;
mmc-hs200-1_8v;
mmc-hs400-1_8v;
mmc-hs400-enhanced-strobe;
vmmc-supply = <&vcc_3v3_s3>;
vqmmc-supply = <&vcc_1v8_s3>;
pinctrl-names = "default";
pinctrl-0 = <&emmc_bus8 &emmc_clk &emmc_cmd
&emmc_data_strobe &emmc_rstnout>;
/* DLL 튜닝 파라미터 (Rockchip 전용) */
rockchip,txclk-tapnum = <0x6>;
status = "okay";
};
핀 컨트롤 (pinctrl) 설정
/* Rockchip 핀 컨트롤 예시 */
&pinctrl {
emmc {
emmc_bus8: emmc-bus8 {
rockchip,pins =
/* DAT0 ~ DAT7 */
<2 RK_PA0 1 &pcfg_pull_up_drv_level_2>,
<2 RK_PA1 1 &pcfg_pull_up_drv_level_2>,
<2 RK_PA2 1 &pcfg_pull_up_drv_level_2>,
<2 RK_PA3 1 &pcfg_pull_up_drv_level_2>,
<2 RK_PA4 1 &pcfg_pull_up_drv_level_2>,
<2 RK_PA5 1 &pcfg_pull_up_drv_level_2>,
<2 RK_PA6 1 &pcfg_pull_up_drv_level_2>,
<2 RK_PA7 1 &pcfg_pull_up_drv_level_2>;
};
emmc_clk: emmc-clk {
rockchip,pins =
<2 RK_PB0 1 &pcfg_pull_up_drv_level_2>;
};
emmc_cmd: emmc-cmd {
rockchip,pins =
<2 RK_PB1 1 &pcfg_pull_up_drv_level_2>;
};
emmc_data_strobe: emmc-data-strobe {
rockchip,pins =
<2 RK_PB2 1 &pcfg_pull_none>;
};
emmc_rstnout: emmc-rstnout {
rockchip,pins =
<2 RK_PB3 1 &pcfg_pull_none>;
};
};
};
non-removable누락: eMMC인데 이 속성이 없으면 커널이 카드 삽입/제거를 감지하려 시도하여 초기화가 실패하거나 불안정해집니다.bus-width = <8>미설정: 기본값이 1-bit이므로 성능이 1/8로 저하됩니다.vqmmc-supply미설정: HS200/HS400 모드에서 1.8V 전환이 불가하여 High Speed까지만 동작합니다.- 속도 모드 속성(
mmc-hs200-1_8v등) 누락: 호스트가 해당 모드를 시도하지 않아 최대 성능을 활용하지 못합니다. no-sdio,no-sd미설정: eMMC 전용 슬롯에서 불필요한 SD/SDIO 프로빙으로 초기화 지연(Latency)이 발생합니다.
cat /sys/kernel/debug/mmc0/ios에서 timing 필드를 확인하세요.
mmc hs400 enhanced strobe가 표시되면 HS400ES로 정상 동작 중입니다.
DT 속성이 올바른데도 원하는 모드가 활성화되지 않으면,
카드의 EXT_CSD CARD_TYPE 필드를 확인하여 카드 자체가 해당 모드를 지원하는지 검증하세요.
Sysfs 인터페이스
리눅스 커널은 eMMC/MMC 장치의 상태와 속성을 사용자 공간에서 읽고 제어할 수 있도록 다양한 sysfs 인터페이스를 제공합니다. 이 인터페이스를 활용하면 장치 정보 조회, 건강 상태 모니터링, 동적 설정 변경 등을 쉘 스크립트만으로 수행할 수 있습니다.
MMC 호스트 속성: /sys/class/mmc_host/mmc0/
MMC 호스트 컨트롤러의 런타임 속성을 제어하는 경로입니다. 호스트 컨트롤러 드라이버에 따라 제공되는 속성이 다를 수 있습니다.
| 속성 파일 | 권한 | 설명 |
|---|---|---|
rescan | W | 1을 쓰면 카드 재탐지를 트리거합니다. eMMC(non-removable)에서는 일반적으로 불필요하나, 디버깅 시 초기화를 재시도할 때 유용합니다. |
clk_gate_delay | R/W | 클럭 게이팅 지연 시간(ms). 마지막 요청 후 이 시간이 지나면 호스트 클럭을 꺼서 전력을 절약합니다. 값이 너무 작으면 빈번한 on/off로 오히려 오버헤드가 증가합니다. |
clkgate_enable | R/W | 클럭 게이팅 활성화 여부(0/1). 디버깅 시 0으로 설정하여 클럭 관련 문제를 배제할 수 있습니다. |
MMC 디바이스 속성: /sys/bus/mmc/devices/mmc0:0001/
개별 eMMC 카드의 고유 정보를 읽을 수 있는 경로입니다. CID, CSD, EXT_CSD에서 추출한 정보가 사람이 읽을 수 있는 형태로 제공됩니다.
| 속성 파일 | 타입 | 설명 |
|---|---|---|
type | 문자열 | 장치 타입 (예: MMC) |
name | 문자열 | 제품 이름 (CID의 PNM 필드, 6바이트 ASCII) |
serial | 16진수 | 시리얼 번호 (CID의 PSN 필드, 32-bit) |
date | MM/YYYY | 제조 날짜 (CID의 MDT 필드) |
manfid | 16진수 | 제조사 ID (예: 0x000015 = Samsung, 0x000011 = Toshiba, 0x000045 = SanDisk) |
oemid | 16진수 | OEM/Application ID |
hwrev | 16진수 | 하드웨어 리비전 (CID의 PRV 상위 4비트) |
fwrev | 16진수 | 펌웨어 리비전 (CID의 PRV 하위 4비트) |
cid | 16진수 | CID 레지스터 원시값 (128-bit) |
csd | 16진수 | CSD 레지스터 원시값 (128-bit) |
eol_info | 16진수 | PRE_EOL_INFO — 장치 수명 잔여 상태 (EXT_CSD[267]) |
life_time | 16진수 | DEVICE_LIFE_TIME_EST_TYP_A/B — SLC/MLC 영역별 수명 추정치 (EXT_CSD[268-269]) |
enhanced_area_offset | 바이트 | Enhanced 영역 시작 오프셋 (EXT_CSD에서 계산) |
enhanced_area_size | 바이트 | Enhanced 영역 크기 |
raw_rpmb_size_mult | 정수 | RPMB 파티션 크기 (128KB 단위, EXT_CSD[168]) |
블록 디바이스 속성: /sys/block/mmcblk0/
eMMC 블록 디바이스의 I/O 관련 속성을 제어하는 표준 블록 레이어 sysfs입니다.
# 장치 기본 정보
cat /sys/block/mmcblk0/size # 512-byte 섹터 수
cat /sys/block/mmcblk0/ro # 읽기 전용 여부 (0/1)
cat /sys/block/mmcblk0/removable # 제거 가능 여부 (eMMC는 0)
# I/O 큐 속성
cat /sys/block/mmcblk0/queue/scheduler # 현재 I/O 스케줄러
cat /sys/block/mmcblk0/queue/read_ahead_kb # 미리 읽기 크기
cat /sys/block/mmcblk0/queue/nr_requests # 최대 대기 요청 수
cat /sys/block/mmcblk0/queue/max_sectors_kb # 단일 요청 최대 크기
cat /sys/block/mmcblk0/queue/rotational # 회전형 미디어 (eMMC는 0)
# 부트 파티션 쓰기 보호 해제
echo 0 > /sys/block/mmcblk0boot0/force_ro # boot0 쓰기 허용
echo 0 > /sys/block/mmcblk0boot1/force_ro # boot1 쓰기 허용
건강 상태 모니터링
eMMC 5.0 이상에서 제공하는 수명 관련 sysfs 값의 의미는 다음과 같습니다.
| 값 | 수명 소진 비율 | 상태 |
|---|---|---|
0x00 | 정보 없음 | 미지원 또는 미정의 |
0x01 | 0% ~ 10% | 정상 (신품) |
0x02 | 10% ~ 20% | 정상 |
0x03 | 20% ~ 30% | 정상 |
0x04 | 30% ~ 40% | 정상 |
0x05 | 40% ~ 50% | 주의 시작 |
0x06 | 50% ~ 60% | 주의 |
0x07 | 60% ~ 70% | 주의 |
0x08 | 70% ~ 80% | 경고 |
0x09 | 80% ~ 90% | 경고 — 교체 계획 수립 필요 |
0x0A | 90% ~ 100% | 위험 — 즉시 교체 권장 |
0x0B | 수명 초과 | 수명 완전 소진, 데이터 손실 위험 |
| 값 | 의미 |
|---|---|
0x00 | 미정의 |
0x01 | 정상 — 예비 블록 충분 |
0x02 | 경고 — 예비 블록 80% 이상 소진 |
0x03 | 긴급 — 예비 블록 거의 소진, 즉시 교체 필요 |
#!/bin/bash
# eMMC 건강 상태 모니터링 스크립트
DEVICE="/sys/bus/mmc/devices/mmc0:0001"
if [ ! -d "$DEVICE" ]; then
echo "eMMC 장치를 찾을 수 없습니다."
exit 1
fi
echo "=== eMMC 장치 정보 ==="
echo "이름: $(cat $DEVICE/name)"
echo "제조사: $(cat $DEVICE/manfid)"
echo "시리얼: $(cat $DEVICE/serial)"
echo "날짜: $(cat $DEVICE/date)"
echo ""
echo "=== 수명 정보 ==="
EOL=$(cat $DEVICE/eol_info 2>/dev/null)
LIFE=$(cat $DEVICE/life_time 2>/dev/null)
case "$EOL" in
0x01) echo "PRE_EOL_INFO: 정상 (예비 블록 충분)" ;;
0x02) echo "PRE_EOL_INFO: ⚠ 경고 (예비 블록 부족)" ;;
0x03) echo "PRE_EOL_INFO: ✗ 긴급 (즉시 교체 필요)" ;;
*) echo "PRE_EOL_INFO: $EOL (알 수 없음)" ;;
esac
echo "LIFE_TIME: $LIFE"
echo ""
echo "=== 블록 장치 정보 ==="
SIZE_SECTORS=$(cat /sys/block/mmcblk0/size)
SIZE_GB=$(echo "scale=2; $SIZE_SECTORS * 512 / 1073741824" | bc)
echo "용량: ${SIZE_GB}GB ($SIZE_SECTORS sectors)"
echo "읽기전용: $(cat /sys/block/mmcblk0/ro)"
echo "스케줄러: $(cat /sys/block/mmcblk0/queue/scheduler)"
0x08 이상)에 도달했을 때 알림을 받을 수 있습니다.
산업용/차량용 장비에서는 이 모니터링이 유지보수 계획의 핵심 지표가 됩니다.
eMMC 수명 및 건강 관리 심화
eMMC는 NAND 플래시 기반이므로 P/E(Program/Erase) 사이클 수명이 유한합니다.
JEDEC 표준은 EXT_CSD의 DEVICE_LIFE_TIME_EST_TYP_A/B와 PRE_EOL_INFO 필드를 통해
기본적인 수명 지표를 제공하지만, 제조사별 벤더 커맨드를 활용하면 훨씬 상세한 진단이 가능합니다.
이 섹션에서는 표준 수명 지표와 제조사별 Health Report를 심화 분석합니다.
JEDEC 표준 수명 지표
eMMC 5.0+ 표준이 정의하는 3가지 핵심 수명 필드입니다.
| EXT_CSD 오프셋 | 필드 | 크기 | 설명 |
|---|---|---|---|
| [268] | DEVICE_LIFE_TIME_EST_TYP_A | 1B | SLC/향상(Enhanced) 영역의 수명 추정. 0x01(0~10%)~0x0B(사용 초과). 0x00=정보 없음 |
| [269] | DEVICE_LIFE_TIME_EST_TYP_B | 1B | MLC/TLC 사용자 영역의 수명 추정. Type A와 동일한 스케일 |
| [267] | PRE_EOL_INFO | 1B | EOL 사전 경고: 0x01=정상, 0x02=소비 80% (경고), 0x03=긴급 교체 필요 |
| [252-249] | CACHE_SIZE | 4B | 캐시 크기(KB). 캐시 크기 감소는 내부 NAND 영역 축소를 의미할 수 있습니다 |
| [246] | BKOPS_STATUS | 1B | 백그라운드 작업 긴급도: 0=불필요, 1=보통, 2=성능 저하, 3=치명적 |
Device Life Time 해석 가이드
| 값 | 수명 소모율 | 권장 조치 | 예상 남은 수명 |
|---|---|---|---|
0x01 | 0% ~ 10% | 정상 운영 | 충분 |
0x02 | 10% ~ 20% | 정상 운영 | 충분 |
0x03 | 20% ~ 30% | 정상 운영, 모니터링 시작 권장 | 양호 |
0x04 | 30% ~ 40% | 정기 백업 강화 | 양호 |
0x05 | 40% ~ 50% | 쓰기 부하 분석, 불필요한 쓰기 제거 | 보통 |
0x06 | 50% ~ 60% | 교체 계획 수립 | 보통 |
0x07 | 60% ~ 70% | 교체 준비 | 주의 |
0x08 | 70% ~ 80% | 교체 일정 확정, 백업 빈도 증가 | 경고 |
0x09 | 80% ~ 90% | 즉시 교체 권장 | 위험 |
0x0A | 90% ~ 100% | 긴급 교체 | 임박 |
0x0B | 사용 초과 | 데이터 손실 위험, 즉시 교체 | 수명 초과 |
Samsung Health Report
Samsung eMMC는 CMD56(GEN_CMD)을 통해 512바이트의 Smart Report를 제공합니다. 표준 EXT_CSD 수명 지표보다 훨씬 상세한 NAND 레벨 정보를 포함합니다.
| 오프셋 | 크기 | 필드 | 설명 |
|---|---|---|---|
| 0x00 | 2B | 초기 배드블록 수 | 공장 출하 시 마킹된 배드블록 |
| 0x02 | 2B | 런타임 배드블록 수 | 운영 중 발생한 배드블록. 급증 시 수명 임박 |
| 0x04 | 4B | Spare Block 잔여 수 | 교체용 여유 블록. 0에 가까워지면 교체 불가 |
| 0x08 | 4B | SLC 최대 P/E 카운트 | SLC 블록의 최대 프로그램/이레이즈 횟수 |
| 0x0C | 4B | SLC 평균 P/E 카운트 | SLC 블록 평균 — 최대와의 차이가 크면 Wear Leveling 불균형 |
| 0x10 | 4B | MLC/TLC 최대 P/E 카운트 | MLC/TLC 블록의 최대 프로그램/이레이즈 횟수 |
| 0x14 | 4B | MLC/TLC 평균 P/E 카운트 | MLC/TLC 블록 평균 |
| 0x18 | 4B | 전원 사이클 횟수 | Power On/Off 사이클 누적 |
| 0x1C | 4B | 비정상 전원 종료 횟수 | Power Loss 발생 횟수. 높으면 FTL 무결성 점검 필요 |
| 0x20 | 4B | 총 쓰기량(Write Amplification 포함) | NAND 레벨 실제 쓰기량 (호스트 쓰기량의 WA배) |
| 0x24 | 1B | 온도(°C) | 현재 다이 온도 |
SanDisk/WD iNAND Health Report
SanDisk iNAND 시리즈는 CMD56 arg=0x110005F9로 Health Report를 읽습니다.
이 포맷은 Micron의 일부 모델에서도 호환됩니다.
| 오프셋 | 크기 | 필드 | 설명 |
|---|---|---|---|
| 0x00 | 2B | Health Report 버전 | 리포트 포맷 버전 |
| 0x02 | 2B | 초기화 횟수 | eMMC 초기화(CMD0→CMD1) 수행 횟수 |
| 0x04 | 4B | 읽기 재시도(Read Reclaim) 횟수 | ECC 임계치 초과로 데이터 이동이 발생한 횟수 |
| 0x08 | 2B | 초기 배드블록 수 | 공장 배드블록 |
| 0x0A | 2B | 런타임 배드블록 수 | 운영 중 발생 배드블록 |
| 0x0C | 4B | 최대 P/E 카운트 | 전체 블록 중 최대 이레이즈 카운트 |
| 0x10 | 4B | 전원 사이클 횟수 | Power Cycle 누적 |
| 0x14 | 4B | 최소 P/E 카운트 | 전체 블록 중 최소 이레이즈 카운트 |
| 0x18 | 4B | 예비 블록 잔여 비율(%) | Over-Provisioning 영역 남은 비율 |
| 0x1C | 4B | 리프레시(Refresh) 횟수 | 데이터 무결성 유지를 위한 블록 리프레시 수행 횟수 |
# mmc-utils를 이용한 Health Report 읽기 (SanDisk/Micron 호환)
# mmc-utils에 vendor 확장이 포함된 경우:
mmc gen_cmd read /dev/mmcblk0 0x110005F9 /tmp/health.bin
# 원시 데이터 확인
xxd /tmp/health.bin | head -20
# Python으로 파싱하는 예시 스크립트
python3 -c "
import struct, sys
with open('/tmp/health.bin', 'rb') as f:
data = f.read(512)
ver = struct.unpack_from('<H', data, 0)[0]
init_cnt = struct.unpack_from('<H', data, 2)[0]
read_reclaim = struct.unpack_from('<I', data, 4)[0]
init_bad = struct.unpack_from('<H', data, 8)[0]
runtime_bad = struct.unpack_from('<H', data, 10)[0]
max_pe = struct.unpack_from('<I', data, 12)[0]
power_cycles = struct.unpack_from('<I', data, 16)[0]
min_pe = struct.unpack_from('<I', data, 20)[0]
spare_pct = struct.unpack_from('<I', data, 24)[0]
print(f'Health Report Version: {ver}')
print(f'Init Count: {init_cnt}')
print(f'Read Reclaim: {read_reclaim}')
print(f'Initial Bad Blocks: {init_bad}')
print(f'Runtime Bad Blocks: {runtime_bad}')
print(f'Max P/E Count: {max_pe}')
print(f'Min P/E Count: {min_pe}')
print(f'Power Cycles: {power_cycles}')
print(f'Spare Block Remaining: {spare_pct}%')
"
Micron Health 모니터링
Micron MTFC 시리즈는 CMD56과 더불어 EXT_CSD의 벤더 전용 영역(바이트 270~301)에 추가 Health 정보를 포함하는 경우가 있습니다.
| EXT_CSD 오프셋 | 필드 | 설명 |
|---|---|---|
| [270] | Optimal Write Size | 최적 쓰기 블록 크기(MB 단위). 이 크기로 정렬된 쓰기가 WA(Write Amplification)를 최소화합니다 |
| [271] | Optimal Read Size | 최적 읽기 블록 크기 |
| [301] | Firmware Version | 추가 FW 버전 정보(PRV 필드보다 상세) |
emmc_health 오픈소스 유틸리티를 제공합니다.
ioctl(fd, MMC_IOC_CMD, &cmd)를 사용하여 CMD56으로 Health 데이터를 읽고 파싱합니다.
GitHub에서 micron-emmc-health로 검색하면 참고 구현을 찾을 수 있습니다.
Write Amplification Factor (WAF) 분석
WAF(Write Amplification Factor)는 호스트가 쓴 데이터량 대비 NAND에 실제 기록된 데이터량의 비율입니다. FTL의 Garbage Collection, Wear Leveling 등으로 인해 항상 1.0보다 크며, WAF가 높을수록 eMMC 수명이 빠르게 소모됩니다.
| 항목 | 설명 | 확인 방법 |
|---|---|---|
| 호스트 쓰기량 | /sys/block/mmcblk0/stat의 write sectors |
cat /sys/block/mmcblk0/stat | awk '{print $7}' (512B 섹터 단위) |
| NAND 쓰기량 | Vendor Health Report의 총 NAND 쓰기량 또는 P/E 카운트 × 블록 크기 | CMD56 Health Report 파싱 |
| WAF 계산 | NAND 쓰기량 ÷ 호스트 쓰기량 | 이상적: 1.0~2.0, 문제: 3.0 이상 |
WAF를 줄이는 방법:
- TRIM/DISCARD 활성화: 파일 삭제 시 FTL에 무효 블록을 알려 GC 효율을 높입니다 (
mount -o discard또는fstrim) - 쓰기 크기 정렬: eMMC의 Optimal Write Size(EXT_CSD[270])에 맞춰 I/O를 정렬합니다
- Random Write 감소: 저널링 파일시스템(ext4)의 저널 크기를 줄이거나 F2FS를 사용합니다
- tmpfs 활용:
/tmp,/var/log등 빈번한 쓰기 경로를 RAM 기반 tmpfs로 마운트합니다 - Swap 비활성화: eMMC에 Swap을 배치하면 수명이 급격히 단축됩니다
종합 건강 모니터링 스크립트
#!/bin/bash
# eMMC 종합 건강 상태 점검 스크립트
DEVICE="mmc0:0001"
SYSFS="/sys/class/mmc_host/mmc0/${DEVICE}"
BLOCK="/sys/block/mmcblk0"
echo "===== eMMC Health Report ====="
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
echo
# 1. 기본 디바이스 정보
echo "--- 디바이스 정보 ---"
MANFID=$(cat ${SYSFS}/manfid)
NAME=$(cat ${SYSFS}/name)
FWREV=$(cat ${SYSFS}/fwrev)
echo "제조사 ID: ${MANFID}"
echo "모델명: ${NAME}"
echo "펌웨어: ${FWREV}"
# 제조사 이름 변환
case ${MANFID} in
0x000015) VENDOR="Samsung" ;;
0x000090) VENDOR="SK Hynix" ;;
0x000013) VENDOR="Micron" ;;
0x000045) VENDOR="SanDisk/WD" ;;
0x000011) VENDOR="Toshiba/Kioxia" ;;
0x000070) VENDOR="Kingston" ;;
*) VENDOR="Unknown(${MANFID})" ;;
esac
echo "제조사: ${VENDOR}"
echo
# 2. 수명 지표
echo "--- 수명 지표 ---"
LIFE_A=$(cat ${SYSFS}/life_time 2>/dev/null | awk '{print $1}')
LIFE_B=$(cat ${SYSFS}/life_time 2>/dev/null | awk '{print $2}')
PRE_EOL=$(cat ${SYSFS}/pre_eol_info 2>/dev/null)
# 수치 해석
interpret_life() {
case $1 in
0x01) echo "0-10% (양호)" ;;
0x02) echo "10-20% (양호)" ;;
0x03) echo "20-30% (양호)" ;;
0x04) echo "30-40% (보통)" ;;
0x05) echo "40-50% (보통)" ;;
0x06) echo "50-60% (주의)" ;;
0x07) echo "60-70% (주의)" ;;
0x08) echo "70-80% (경고)" ;;
0x09) echo "80-90% (위험)" ;;
0x0a) echo "90-100% (위험)" ;;
0x0b) echo "초과 (즉시 교체)" ;;
*) echo "정보 없음" ;;
esac
}
echo "Type A (SLC/Enhanced): ${LIFE_A} - $(interpret_life ${LIFE_A})"
echo "Type B (MLC/TLC): ${LIFE_B} - $(interpret_life ${LIFE_B})"
case ${PRE_EOL} in
0x01) EOL_STATUS="정상" ;;
0x02) EOL_STATUS="경고 (80% 소모)" ;;
0x03) EOL_STATUS="긴급 (즉시 교체)" ;;
*) EOL_STATUS="정보 없음" ;;
esac
echo "Pre-EOL: ${PRE_EOL} - ${EOL_STATUS}"
echo
# 3. I/O 통계
echo "--- I/O 통계 ---"
STAT=$(cat ${BLOCK}/stat)
READ_SECTORS=$(echo ${STAT} | awk '{print $3}')
WRITE_SECTORS=$(echo ${STAT} | awk '{print $7}')
READ_GB=$(echo "scale=2; ${READ_SECTORS} * 512 / 1073741824" | bc)
WRITE_GB=$(echo "scale=2; ${WRITE_SECTORS} * 512 / 1073741824" | bc)
echo "누적 읽기: ${READ_GB} GB"
echo "누적 쓰기: ${WRITE_GB} GB (호스트 기준, 부팅 후)"
echo
# 4. 경고 판단
if [[ "${PRE_EOL}" == "0x03" ]] || [[ "${LIFE_A}" > "0x08" ]] || [[ "${LIFE_B}" > "0x08" ]]; then
echo "⚠ 경고: eMMC 수명이 위험 수준입니다. 즉시 교체를 계획하세요."
echo " - 데이터 백업을 우선 수행하세요."
echo " - 쓰기 부하를 최소화하세요 (swap 비활성화, 로그 축소)."
elif [[ "${PRE_EOL}" == "0x02" ]] || [[ "${LIFE_A}" > "0x05" ]] || [[ "${LIFE_B}" > "0x05" ]]; then
echo "⚠ 주의: eMMC 수명 50% 이상 소모. 교체 계획을 수립하세요."
else
echo "✓ eMMC 수명 상태 양호."
fi
코드 설명
- 14–19행 sysfs에서 CID의 manfid, name, fwrev를 읽어 기본 디바이스 정보를 출력합니다.
- 22–31행 manfid 값을 제조사 이름으로 변환합니다. 0x15=Samsung, 0x90=SK Hynix 등 JEDEC 등록 코드입니다.
- 35–37행
sysfs의
life_time(Type A/B)과pre_eol_info를 읽습니다. 커널 4.10+에서 이 파일이 제공됩니다. - 79–86행
/sys/block/mmcblk0/stat에서 부팅 후 누적된 읽기/쓰기 섹터 수를 GB로 변환합니다. - 89–95행 Pre-EOL이 긴급(0x03)이거나 Life Time이 0x08(70~80%) 이상이면 경고를 출력합니다.
산업용/자동차 eMMC 수명 관리
산업용(Industrial Grade)과 자동차용(Automotive Grade, AEC-Q100) eMMC는 일반 소비자용과 다른 수명 관리 전략이 필요합니다.
| 항목 | 소비자용 | 산업용 | 자동차용 |
|---|---|---|---|
| 온도 범위 | 0°C ~ +70°C | -40°C ~ +85°C | -40°C ~ +125°C |
| P/E 사이클 | 3,000 (TLC) | 3,000~10,000 (pSLC 가능) | 10,000~100,000 (SLC/pSLC) |
| 데이터 보존 | 1년 (비전원) | 3~10년 | 10~15년 |
| 수명 모니터링 | 선택적 | 필수 | 필수 + 실시간 알림 |
| pSLC 모드 | 미지원 | Enhanced 영역으로 구현 | 일반적 적용 |
| 전원 손실 보호 | 기본 | 강화 (Power Loss Protection) | 이중화 (Capacitor 백업) |
EXT_CSD[136] ENH_SIZE_MULT와
EXT_CSD[140] ENH_START_ADDR로 설정하며, 한 번 설정하면 되돌릴 수 없습니다.
디버깅 및 진단
eMMC/MMC 서브시스템 문제를 진단할 때는 커널이 제공하는 다양한 디버깅 도구를 계층별로 활용합니다. 하드웨어 레벨(오실로스코프/로직 분석기)부터 소프트웨어 레벨(커널 로그, tracepoint, debugfs)까지 단계적으로 접근하면 효율적으로 원인을 좁힐 수 있습니다.
mmc_test 커널 모듈(Kernel Module)
mmc_test는 커널에 내장된 MMC 테스트 모듈로, 데이터 무결성, 전송 성능, 에러 처리 등을
하드웨어 레벨에서 검증합니다. 프로덕션이 아닌 개발/디버깅 환경에서만 사용해야 합니다.
# mmc_test 모듈 로드 (해당 eMMC의 블록 드라이버를 unbind 후 사용)
echo mmc0:0001 > /sys/bus/mmc/drivers/mmcblk/unbind
modprobe mmc_test
# 테스트 실행 (test 파일에 테스트 번호 쓰기)
echo mmc0:0001 > /sys/bus/mmc/drivers/mmc_test/bind
# 사용 가능한 테스트 목록 (dmesg에서 확인)
dmesg | grep "mmc_test"
# 특정 테스트 실행
echo 1 > /sys/bus/mmc/drivers/mmc_test/mmc0:0001/test
# 결과 확인
dmesg | tail -20
# 복구: mmc_test unbind 후 mmcblk rebind
echo mmc0:0001 > /sys/bus/mmc/drivers/mmc_test/unbind
echo mmc0:0001 > /sys/bus/mmc/drivers/mmcblk/bind
debugfs 인터페이스
MMC 호스트 컨트롤러는 debugfs에 런타임 상태를 노출합니다.
# debugfs 마운트 확인
mount | grep debugfs
# 없으면: mount -t debugfs none /sys/kernel/debug
# MMC 호스트 debugfs 조회
ls /sys/kernel/debug/mmc0/
# 현재 I/O 설정 확인 (클럭, 버스 폭, 타이밍 모드)
cat /sys/kernel/debug/mmc0/ios
# 출력 예시:
# clock: 200000000 Hz
# actual clock: 200000000 Hz
# vdd: 21 (3.3 ~ 3.4 V)
# bus mode: 2 (push-pull)
# chip select: 0 (don't care)
# power mode: 2 (on)
# bus width: 3 (8 bits)
# timing spec: 10 (mmc hs400 enhanced strobe)
# signal voltage: 1 (1.80 V)
# driver type: 0 (driver type B)
# 클럭 정보
cat /sys/kernel/debug/mmc0/clock
ftrace Tracepoint
커널의 ftrace 프레임워크는 MMC 요청의 시작과 완료를 추적하는 tracepoint를 제공합니다. 성능 분석이나 타이밍 문제 진단에 매우 유용합니다.
# 사용 가능한 MMC tracepoint 확인
ls /sys/kernel/debug/tracing/events/mmc/
# trace-cmd로 MMC 요청 추적
trace-cmd record -e mmc:mmc_request_start -e mmc:mmc_request_done \
dd if=/dev/mmcblk0 of=/dev/null bs=4k count=1000
trace-cmd report | head -50
# 수동 ftrace 설정
echo 1 > /sys/kernel/debug/tracing/events/mmc/mmc_request_start/enable
echo 1 > /sys/kernel/debug/tracing/events/mmc/mmc_request_done/enable
# 워크로드 실행 후 결과 확인
cat /sys/kernel/debug/tracing/trace | head -100
# 추적 끄기
echo 0 > /sys/kernel/debug/tracing/events/mmc/enable
mmc-utils 상세 명령
mmc-utils는 eMMC 전용 관리 유틸리티로, EXT_CSD 읽기/쓰기, 쓰기 보호, RPMB,
펌웨어 업데이트 등 다양한 관리 작업을 지원합니다.
# 설치 (소스에서 빌드)
git clone https://git.kernel.org/pub/scm/utils/mmc/mmc-utils.git
cd mmc-utils && make && sudo make install
# EXT_CSD 전체 읽기
mmc extcsd read /dev/mmcblk0
# EXT_CSD 특정 바이트 쓰기 (주의: 잘못된 값은 장치 손상 유발)
# 예: BKOPS_EN (EXT_CSD[163]) 활성화
mmc extcsd write 163 1 /dev/mmcblk0
# 쓰기 보호 상태 확인
mmc writeprotect get /dev/mmcblk0
mmc writeprotect boot get /dev/mmcblk0
# 쓰기 보호 설정
mmc writeprotect boot set /dev/mmcblk0
# RPMB 카운터 읽기
mmc rpmb read-counter /dev/mmcblk0rpmb
# 장치 상태 조회
mmc status get /dev/mmcblk0
# sanitize 실행 (삭제 데이터 물리적 소거)
mmc sanitize /dev/mmcblk0
# FFU (Field Firmware Update) — 펌웨어 파일 필요
mmc ffu <firmware.bin> /dev/mmcblk0
# 캐시 활성화/비활성화
mmc cache enable /dev/mmcblk0
mmc cache disable /dev/mmcblk0
# eMMC 하드웨어 리셋
mmc hwreset enable /dev/mmcblk0
커널 로그 메시지 해석
| 메시지 패턴 | 의미 | 해결 방법 |
|---|---|---|
mmc0: error -110 whilst initialising MMC card | 초기화 타임아웃 (-ETIMEDOUT) | 클럭 주파수 낮추기, 전원 공급 확인, DT 설정 검증 |
mmc0: tuning execution failed | HS200/HS400 튜닝 실패 | 신호 무결성 확인, 속도 모드 다운그레이드, PCB 배선 검증 |
mmc0: retune needed | 런타임 리튜닝 요청 | 정상적 동작이나 빈번하면 온도/전압 변동 확인 |
mmc0: CRC error | 데이터 전송 CRC 오류 | 신호 무결성 문제 — 슬루레이트, 임피던스 매칭 확인 |
mmc0: CMD6 timeout | 스위치 커맨드 타임아웃 | eMMC 펌웨어 이슈 가능, 벤더 quirk 확인 |
mmcblk0: timed out sending r/w cmd | 읽기/쓰기 커맨드 타임아웃 | BKOPS 지연, 배드 블록, 또는 CMDQ 이슈 확인 |
mmc0: card never left busy state | 카드가 busy 상태에서 복귀하지 않음 | 전원 리셋 필요, 하드웨어 리셋 핀 활성화 확인 |
mmc0: new high speed MMC card | High Speed(52MHz)로 초기화됨 | HS200/HS400이 기대였다면 DT 속성과 vqmmc 확인 |
mmc0: switch to bus width 8 failed | 8-bit 버스 전환 실패 | DAT4-DAT7 라인 연결 확인, DT bus-width 설정 |
mmc0: unrecognised SCR structure version | SD 카드용 명령을 eMMC에 전송 | DT에 no-sd 속성 추가 |
mmcblk0: error -84 sending status command | EILSEQ — 데이터 무결성 오류 | 튜닝 파라미터 조정, 속도 모드 다운그레이드 |
mmc0: Timeout waiting for hardware interrupt | SDHCI 인터럽트 미수신 | 인터럽트 라인 연결 확인, DT IRQ 설정 검증 |
Dynamic Debug 및 blktrace
# mmc_core 동적 디버그 메시지 활성화
echo "module mmc_core +p" > /sys/kernel/debug/dynamic_debug/control
echo "module sdhci +p" > /sys/kernel/debug/dynamic_debug/control
# 특정 파일의 디버그 활성화
echo "file mmc_ops.c +p" > /sys/kernel/debug/dynamic_debug/control
echo "file sdhci.c +p" > /sys/kernel/debug/dynamic_debug/control
# 활성화된 디버그 포인트 확인
grep "mmc" /sys/kernel/debug/dynamic_debug/control | grep "=p"
# 디버그 메시지 확인
dmesg -w | grep mmc
# blktrace로 블록 I/O 추적
blktrace -d /dev/mmcblk0 -o trace_emmc &
# 워크로드 실행
dd if=/dev/mmcblk0 of=/dev/null bs=1M count=100
# 추적 중지
kill %1
# 분석
blkparse -i trace_emmc -o trace_emmc.txt
# I/O 패턴 시각화
btt -i trace_emmc.blktrace.0 -o btt_result
성능 튜닝
eMMC 성능을 최대한 끌어내려면 하드웨어 설정(버스 폭, 클럭, 타이밍)과 소프트웨어 설정(스케줄러, 큐 깊이, 미리 읽기) 양쪽을 모두 최적화해야 합니다. 아래에서 각 튜닝 포인트를 구체적으로 설명합니다.
클럭 및 버스 폭 최적화
실제 협상된 모드가 기대한 모드인지 먼저 확인해야 합니다. DT에 HS400을 설정했더라도 카드나 호스트 제약으로 HS200이나 High Speed로 폴백될 수 있습니다.
# 현재 협상된 모드 확인
cat /sys/kernel/debug/mmc0/ios
# 핵심 필드:
# clock: 200000000 (200MHz = HS400)
# bus width: 3 (8 bits)
# timing: 10 (mmc hs400 enhanced strobe)
# 타이밍 모드 번호 해석:
# 0=legacy, 1=mmc HS, 2=SD HS, 3=SDR12, 4=SDR25
# 5=SDR50, 6=SDR104, 7=DDR50, 8=DDR52
# 9=HS200, 10=HS400, 11=HS400ES
CMDQ (Command Queue) 활성화
eMMC 5.1의 CMDQ는 최대 32개의 명령을 큐에 넣어 병렬 I/O를 수행합니다. 카드 내부 컨트롤러가 명령 순서를 최적화하여 랜덤 I/O 성능이 크게 향상됩니다.
# CMDQ 지원 여부 확인
mmc extcsd read /dev/mmcblk0 | grep CMDQ
# CMDQ_SUPPORT [308]: 0x01 → 지원함
# CMDQ_DEPTH [307]: 0x20 → 32 슬롯
# CMDQ 활성화 확인
mmc extcsd read /dev/mmcblk0 | grep CMDQ_MODE_EN
# CMDQ_MODE_EN [15]: 0x01 → 활성화됨
# 커널 로그에서 CMDQ 확인
dmesg | grep -i cmdq
# mmc0: Command Queue Engine enabled
DMA 모드 최적화
SDHCI 호스트는 SDMA, ADMA1, ADMA2 세 가지 DMA 모드를 지원합니다. ADMA2가 scatter-gather를 지원하여 가장 효율적이며, 대부분의 현대 SoC에서 기본 선택됩니다.
# 현재 DMA 모드 확인 (커널 로그)
dmesg | grep -i "sdhci.*dma"
# sdhci-pltfm: SDHCI platform driver (ADMA 64-bit)
I/O 스케줄러 설정
eMMC는 비회전형 플래시 스토리지이므로, 탐색 시간(seek time) 최적화를 수행하는
bfq 스케줄러는 불필요한 오버헤드를 추가합니다.
mq-deadline 또는 none이 적합합니다.
# 현재 스케줄러 확인
cat /sys/block/mmcblk0/queue/scheduler
# [mq-deadline] kyber bfq none
# mq-deadline으로 변경
echo mq-deadline > /sys/block/mmcblk0/queue/scheduler
# CMDQ 활성 상태에서는 none이 최적 (카드 내부에서 스케줄링)
echo none > /sys/block/mmcblk0/queue/scheduler
Read-ahead 튜닝
# 현재 미리 읽기 크기 확인 (기본 128KB)
cat /sys/block/mmcblk0/queue/read_ahead_kb
# 순차 읽기 워크로드: 미리 읽기 증가
echo 2048 > /sys/block/mmcblk0/queue/read_ahead_kb
# 랜덤 읽기 워크로드: 미리 읽기 감소 (불필요한 I/O 방지)
echo 16 > /sys/block/mmcblk0/queue/read_ahead_kb
fio 벤치마크
# 1. 순차 읽기 테스트
fio --name=seq_read --filename=/dev/mmcblk0 --direct=1 \
--rw=read --bs=512k --numjobs=1 --iodepth=32 \
--runtime=30 --time_based --group_reporting
# 2. 순차 쓰기 테스트
fio --name=seq_write --filename=/dev/mmcblk0 --direct=1 \
--rw=write --bs=512k --numjobs=1 --iodepth=32 \
--runtime=30 --time_based --group_reporting
# 3. 랜덤 4K 읽기 테스트
fio --name=rand_read_4k --filename=/dev/mmcblk0 --direct=1 \
--rw=randread --bs=4k --numjobs=4 --iodepth=32 \
--runtime=30 --time_based --group_reporting
# 4. 랜덤 4K 쓰기 테스트
fio --name=rand_write_4k --filename=/dev/mmcblk0 --direct=1 \
--rw=randwrite --bs=4k --numjobs=4 --iodepth=32 \
--runtime=30 --time_based --group_reporting
--filename=/dev/mmcblk0에 직접 쓰기 테스트를 수행하면
파일시스템과 데이터가 완전히 파괴됩니다. 반드시 별도의 테스트 파티션이나
데이터가 없는 장치에서만 실행하세요.
BKOPS (Background Operations)
BKOPS는 eMMC 내부의 가비지 컬렉션, 웨어 레벨링 등 유지보수 작업입니다.
자동 BKOPS(BKOPS_EN)를 활성화하면 카드가 유휴 시 자동으로 수행하지만,
시간 민감한 워크로드에서는 수동 트리거가 유리할 수 있습니다.
# BKOPS 상태 확인
mmc extcsd read /dev/mmcblk0 | grep BKOPS
# BKOPS_EN [163]: 0x01 → 자동 BKOPS 활성화됨
# BKOPS_STATUS [246]: 0x00 → 미수행 필요
# BKOPS_STATUS 값:
# 0x00 = 작업 불필요
# 0x01 = 비긴급 (성능 영향 없음)
# 0x02 = 성능 영향 가능 (조만간 수행 권장)
# 0x03 = 긴급 (즉시 수행 필요, 성능 저하 발생 중)
# 수동 BKOPS 트리거 (EXT_CSD BKOPS_START[164] = 1)
mmc extcsd write 164 1 /dev/mmcblk0
Pack Command
Pack Command는 여러 개의 작은 쓰기 요청을 하나의 큰 전송으로 묶어 프로토콜 오버헤드를 줄이는 기능입니다. eMMC 4.5 이상에서 지원되며, 커널의 MMC 블록 드라이버가 자동으로 활용합니다.
# Pack Command 지원 확인
mmc extcsd read /dev/mmcblk0 | grep PACKED
# PACKED_COMMAND_MAX_SIZE [166]: 0x08 → 8개까지 묶기 가능
성능 튜닝 체크리스트
| 항목 | 명령/설정 | 기대 효과 |
|---|---|---|
| 최대 속도 모드 협상 | cat /sys/kernel/debug/mmc0/ios | HS400/HS400ES 확인 → 최대 대역폭 |
| 8-bit 버스 폭 | DT: bus-width = <8> | 1-bit 대비 8배 대역폭 |
| CMDQ 활성화 | dmesg | grep -i cmdq | 랜덤 I/O 성능 2~3배 향상 |
| I/O 스케줄러 | echo none > .../scheduler | CMDQ 환경에서 오버헤드 제거 |
| Read-ahead 조정 | echo 2048 > .../read_ahead_kb | 순차 읽기 처리량 향상 |
| BKOPS 활성화 | mmc extcsd write 163 1 ... | 지속적 쓰기 성능 유지 |
| 캐시 활성화 | mmc cache enable ... | 쓰기 응답 시간 감소 |
| DMA 모드 확인 | dmesg | grep -i "adma" | ADMA2로 scatter-gather 활용 |
| 전원 관리 최적화 | DT: keep-power-in-suspend | resume 시 재초기화 방지 |
UFS 비교
UFS(Universal Flash Storage)는 eMMC의 후속으로 개발된 플래시 스토리지 표준입니다. 두 기술은 동일한 NAND 플래시 기반이지만 인터페이스 아키텍처가 근본적으로 다릅니다. eMMC는 병렬 버스, UFS는 직렬 고속 인터페이스를 사용합니다.
아키텍처 비교
eMMC는 8-bit 병렬 데이터 버스에 반이중(half-duplex) 통신을 사용합니다. 반면 UFS는 MIPI M-PHY 물리 계층과 UniPro 전송 계층 위에 SCSI 기반 UFS 프로토콜을 실행하며, 2개의 차동 레인으로 전이중(full-duplex) 통신을 합니다.
성능 비교
| 항목 | eMMC 5.1 | UFS 2.1 | UFS 3.1 | UFS 4.0 |
|---|---|---|---|---|
| 순차 읽기 | ~250 MB/s | ~850 MB/s | ~2,100 MB/s | ~4,200 MB/s |
| 순차 쓰기 | ~125 MB/s | ~250 MB/s | ~1,200 MB/s | ~2,800 MB/s |
| 랜덤 읽기 (IOPS) | ~7K | ~45K | ~100K | ~200K |
| 랜덤 쓰기 (IOPS) | ~5K | ~35K | ~70K | ~100K |
| 읽기 지연 시간 | ~200 us | ~100 us | ~50 us | ~30 us |
기능 비교
| 기능 | eMMC 5.1 | UFS 3.1 / 4.0 |
|---|---|---|
| 통신 방식 | 반이중 (Half-duplex) | 전이중 (Full-duplex) |
| 인터페이스 | 병렬 8-bit (CLK+CMD+DAT[7:0]) | 직렬 MIPI M-PHY (2 lanes) |
| 명령 큐 깊이 | CMDQ: 32 (eMMC 5.1) | 32 (UFS 2.1+), MCQ: 최대 32 큐 |
| 인라인 암호화 | 미지원 (일부 벤더 확장) | 표준 지원 (UFS 2.1+) |
| 부트 지원 | Boot 파티션 (boot0/boot1) | Boot LU (LU 0/1) |
| RPMB | v2 (eMMC 5.1) | v3 (UFS 3.1+, 더 큰 프레임) |
| Write Booster | 미지원 | SLC 캐시 기반 쓰기 가속 (UFS 3.1+) |
| HPB (Host Performance Booster) | 미지원 | 호스트 메모리 활용 L2P 캐싱 (UFS 3.1+) |
| Multi-LU (논리 유닛) | 미지원 (파티션으로 분리) | 최대 32개 LU 독립 관리 |
| 전원 관리 | Sleep, Power-off notification | Active/Idle/Sleep/PowerDown + Hibernate |
선택 기준
| 기준 | eMMC 선택 | UFS 선택 |
|---|---|---|
| 비용 | 저비용 우선 (IoT, 저가형 기기) | 성능이 비용보다 중요한 경우 |
| 용량 | 4GB ~ 128GB 범위 | 32GB ~ 1TB 이상 |
| 성능 요구 | 순차 250MB/s 이하면 충분 | 고성능 앱 로딩, 4K 영상 녹화 |
| 전력 | 단순 전력 관리로 충분 | 세밀한 전력 상태 제어 필요 |
| 응용 | 웨어러블, IoT, 셋톱박스, 저가 스마트폰 | 플래그십 스마트폰, 태블릿, 자동차 |
마이그레이션 고려사항
eMMC에서 UFS로 전환할 때 주요 변경 사항:
- 드라이버 변경: MMC 서브시스템(
drivers/mmc/) 대신 SCSI + UFS 서브시스템(drivers/ufs/)을 사용합니다. 블록 장치(Block Device) 이름이mmcblk0에서sda로 변경됩니다. - DT 변경:
sdhci호환 노드 대신ufs-hci호환 노드를 사용합니다. 레인 수, 기어(Gear) 설정 등 M-PHY 관련 속성이 추가됩니다. - 파티션 구조: eMMC의 하드웨어 파티션(boot0/boot1/RPMB/User) 대신 UFS의 LU(Logical Unit) 개념으로 전환합니다. 부트로더 저장 위치와 RPMB 접근 방식이 달라집니다.
- 유저스페이스 도구:
mmc-utils대신ufs-utils를 사용합니다.
자주 겪는 문제와 해결
eMMC 개발 및 운영에서 자주 겪는 문제와 체계적인 해결 방법을 정리합니다. 각 문제에 대해 증상, 원인, 해결 방법, 진단 명령을 포함합니다.
| 문제 | 증상 | 원인 | 해결 방법 |
|---|---|---|---|
| HS200/HS400 튜닝 실패 | tuning execution failed, High Speed로 폴백 |
PCB 신호 무결성 불량, 전압 불안정, 온도 변동 | DT에서 속도 모드 다운그레이드, PCB 배선 길이 균등화, 바이패스 커패시터 추가 |
| 열에 의한 성능 저하 | 지속 쓰기 시 속도가 점진적으로 감소, BKOPS_STATUS 증가 | eMMC 내부 온도 상승으로 컨트롤러가 보호 모드 진입 | 방열 설계 개선, 쓰기 버스트 크기 제한, 유휴 구간 확보 |
| PRE_EOL_INFO 경고 | eol_info가 0x02 또는 0x03 |
예비 블록 소진 — 장치 수명 한계 도달 | 장치 교체 계획 수립, 쓰기 부하 감소 (저널링(Journaling) 최소화, tmpfs 활용) |
| RPMB 인증 실패 | RPMB authentication error, MAC 검증 실패 |
잘못된 인증 키, Write Counter 불일치, 키 미프로그래밍 | 키 프로그래밍 상태 확인, Write Counter 읽기로 검증, 키는 한 번만 쓸 수 있으므로 재프로그래밍 불가 |
| 부트 파티션 쓰기 불가 | write error: Read-only file system |
boot 파티션 기본 force_ro=1 설정 |
echo 0 > /sys/block/mmcblk0boot0/force_ro로 해제 후 쓰기 |
| CRC 오류 빈발 | CRC error 반복, 리튜닝 빈번 발생 |
DAT/CMD 라인 신호 무결성 문제 (크로스토크, 임피던스 부정합) | PCB 리뷰: 트레이스 길이 매칭, 임피던스 제어, 드라이브 강도 DT 조정 |
| 카드 미인식 | error -110 whilst initialising, mmc 장치 미생성 |
전원 공급 불량, 클럭 미출력, DT 설정 오류 | vmmc/vqmmc regulator 확인, DT compatible 문자열 검증, 오실로스코프로 CLK/CMD 확인 |
| BKOPS 지연으로 레이턴시 스파이크 | 주기적으로 수십~수백 ms 쓰기 지연 발생 | 긴급 BKOPS 수행 중 새 요청 대기 | BKOPS_EN 활성화로 유휴 시 자동 수행, 또는 시스템 유휴 시 수동 트리거 |
| suspend/resume 시 타임아웃 | CMD timeout, resume 후 카드 미인식 |
전원 차단 후 재초기화 실패, Power-off Notification 미발행 | DT에 keep-power-in-suspend 추가, HW 리셋 핀 연결 확인, mmc-pwrseq 설정 |
| eMMC 펌웨어 버그 (quirk 필요) | 특정 제조사/모델에서만 발생하는 비정상 동작 | eMMC 펌웨어의 사양 미준수 또는 구현 차이 | 커널 quirk 매칭 확인 (drivers/mmc/core/quirks.h), 필요 시 벤더별 quirk 추가 |
PRE_EOL_INFO가 0x03(긴급)인 상태에서 계속 사용하면
예고 없이 읽기 전용 모드로 전환되거나, 최악의 경우
데이터가 손상될 수 있습니다. 0x02(경고) 단계에서 백업 및 교체 계획을 수립하세요.
진단 명령 모음
# 1. 기본 상태 확인
dmesg | grep -i mmc # 커널 로그에서 MMC 관련 메시지
lsblk -o NAME,SIZE,TYPE,MOUNTPOINT,TRAN # 블록 장치 목록
cat /sys/kernel/debug/mmc0/ios # 현재 I/O 설정
# 2. 카드 정보 확인
cat /sys/bus/mmc/devices/mmc0:0001/name # 제품 이름
cat /sys/bus/mmc/devices/mmc0:0001/cid # CID 원시값
mmc extcsd read /dev/mmcblk0 | head -50 # EXT_CSD 주요 필드
# 3. 수명/건강 확인
cat /sys/bus/mmc/devices/mmc0:0001/life_time # 수명 추정치
cat /sys/bus/mmc/devices/mmc0:0001/eol_info # EOL 상태
# 4. 에러 카운트 확인
cat /sys/block/mmcblk0/stat # I/O 통계
# 필드: read_ios read_merges read_sectors read_ticks
# write_ios write_merges write_sectors write_ticks
# in_flight io_ticks time_in_queue
# 5. 튜닝 상태 확인
dmesg | grep -i "tuning\|retune" # 튜닝 관련 메시지
# 6. 전원 상태 확인
cat /sys/bus/mmc/devices/mmc0:0001/power/runtime_status
cat /sys/bus/mmc/devices/mmc0:0001/power/runtime_active_time
# 7. 호스트 컨트롤러 정보
cat /sys/class/mmc_host/mmc0/device/uevent
관련 문서
eMMC/MMC 서브시스템을 더 깊이 이해하거나 관련 기술을 학습할 때 참고할 문서를 정리합니다.
내부 문서 (사이트 내)
| 페이지 | 링크 | 관련성 |
|---|---|---|
| 블록 I/O 레이어 | 블록 I/O | eMMC 위의 블록 레이어, blk-mq, bio/request 처리 — eMMC의 필수 선행 지식 |
| UFS | UFS | eMMC 후속 스토리지 기술, SCSI 프로토콜 기반, 인라인 암호화/RPMB 비교 |
| NVMe | NVMe | PCIe 기반 플래시 스토리지, SQ/CQ 커맨드 큐잉 — CQHCI와 비교 |
| SATA/AHCI | SATA/AHCI | 블록 스토리지 서브시스템, AHCI 레지스터/NCQ — SDHCI와 구조적 비교 |
| MTD/플래시 | MTD | Raw NAND/NOR 플래시 직접 접근 — eMMC 내장 FTL과의 차이 이해 |
| F2FS 파일시스템 | F2FS | eMMC/UFS에 최적화된 플래시 전용 파일시스템, WAF 최소화 전략 |
| 디스크 파티션 | 디스크 파티션 | GPT/MBR 파티션 — eMMC 하드웨어 파티션(Boot/RPMB/GP)과의 차이 |
| Direct I/O & Buffered I/O | Direct I/O & Buffered I/O | eMMC 캐시/BKOPS와 커널 I/O 경로의 상호작용 |
| Readahead & Prefetch | Readahead & Prefetch | eMMC 순차 읽기 성능 최적화 — Read-ahead 튜닝과 직결 |
| io_uring | io_uring | 비동기 I/O 프레임워크 — eMMC CMDQ와의 blk-mq 연계 |
| Device Mapper / LVM | Device Mapper / LVM | eMMC 위의 논리 볼륨 관리, dm-linear/dm-snapshot |
| 페이지 | 링크 | 관련성 |
|---|---|---|
| dm-crypt | dm-crypt | 블록 계층 암호화 — eMMC 인라인 암호화(ICE/FMP)의 소프트웨어 대안 |
| dm-verity | dm-verity | Merkle Tree 블록 검증 — Android Verified Boot에서 eMMC와 함께 사용 |
| dm-integrity | dm-integrity | 블록 무결성 보호 — eMMC CRC/Reliable Write와 보완적 역할 |
| 암호화 프레임워크 | Crypto API | 커널 암호화 프레임워크 — blk-crypto, fscrypt 연동 기반 |
| 암호화 하드웨어 가속 | Crypto HW 가속 | Qualcomm ICE, Samsung FMP 등 인라인 암호화 엔진의 HW 가속 원리 |
| 키링 | 키링 | fscrypt 키 관리 — eMMC 인라인 암호화 키 프로그래밍과 연계 |
| ARM TrustZone & OP-TEE | TrustZone & OP-TEE | RPMB 키 프로비저닝, Secure Boot에서 eMMC 부트 파티션 활용 |
| TPM 2.0 | TPM 2.0 | Measured Boot 체인에서 eMMC 부트 파티션 무결성 측정 |
| 페이지 | 링크 | 관련성 |
|---|---|---|
| 디바이스 트리 | 디바이스 트리 | eMMC DT 바인딩, 핀 설정, 전원 시퀀스 — 호스트 컨트롤러 드라이버 설정 핵심 |
| DMA | DMA | ADMA2/IDMAC 디스크립터, DMA 매핑, Scatter-Gather 리스트 |
| DMA Engine | DMA Engine | SYS-DMAC 등 외부 DMA 엔진 — Renesas SDHI, DesignWare 등에서 사용 |
| GPIO / pinctrl | GPIO / pinctrl | eMMC 데이터/클럭 핀 멀티플렉싱, 전압 전환용 GPIO 설정 |
| Common Clock 프레임워크 | CCF | eMMC 호스트 클럭(clk_core/clk_iface) 관리, 클럭 게이팅 |
| Regulator 프레임워크 | Regulator | eMMC 전원(VMMC/VQMMC) 레귤레이터 제어, 전압 전환(1.8V/3.3V) |
| Reset Controller | Reset Controller | eMMC 호스트 컨트롤러 리셋 시퀀스 |
| 인터럽트 | 인터럽트 | SDHCI/CQHCI 인터럽트 처리, MSI 매핑 |
| 디바이스 드라이버 | 디바이스 드라이버 | 플랫폼 드라이버 모델 — SDHCI/DesignWare 드라이버 등록 구조 |
| 전원 관리 | 전원 관리 | Runtime PM, System Suspend/Resume — eMMC 전원 상태 전이와 연계 |
| 페이지 | 링크 | 관련성 |
|---|---|---|
| ftrace / Tracepoints | ftrace | MMC tracepoint(mmc:mmc_request_start/done) 활용, 요청 레이턴시 분석 |
| perf 서브시스템 | perf | 블록 I/O 이벤트 프로파일링, eMMC 성능 병목 분석 |
| 디버깅 & 트러블슈팅 | 디버깅 | 커널 디버깅 기법 전반 — eMMC 드라이버 디버깅에 활용 |
| Android 커널 | Android | Android Verified Boot, fscrypt, 인라인 암호화 — eMMC 주요 적용 환경 |
외부 참고 자료
JEDEC 표준 및 사양
- JEDEC JESD84-B51A: eMMC 5.1 표준 사양서 — 커맨드, 레지스터, 타이밍, 파티션, RPMB 등 전체 프로토콜 정의
- JEDEC JESD84-B50A: eMMC 5.0 표준 — CMDQ 최초 정의, BKOPS, Cache 기능 추가
- SD Host Controller Simplified Specification v4.20: SDHCI 레지스터 맵, ADMA 디스크립터, 인터럽트 처리, CQE 확장 정의
- JEDEC UFS (JESD220): UFS 표준 사양서 — eMMC 후속 기술 비교 학습에 유용
- JEDEC eMMC 표준 페이지 — eMMC 관련 JEDEC 표준 문서 목록 및 정보를 제공합니다
커널 공식 문서
- 커널 공식 MMC 서브시스템 문서 — 리눅스 커널 MMC/SD/SDIO 프레임워크의 공식 문서입니다
- 커널 Documentation/mmc/:
mmc-dev-attrs.rst(sysfs 속성),mmc-dev-parts.rst(파티션),mmc-tools.rst(유틸리티) - 커널 Documentation/devicetree/bindings/mmc/: 호스트 컨트롤러별 DT 바인딩 사양 —
sdhci-msm.yaml,synopsys-dw-mshc.yaml,mtk-sd.yaml등
커널 소스 (Bootlin Elixir)
- drivers/mmc/ — MMC 코어, 호스트 드라이버 소스 코드를 온라인에서 탐색할 수 있습니다
- drivers/mmc/core/mmc.c — eMMC 카드 초기화 시퀀스(
mmc_init_card)의 핵심 코드입니다 - drivers/mmc/core/quirks.h — 제조사별 eMMC quirk/fixup 테이블 정의입니다
- drivers/mmc/host/cqhci.c — CQHCI(Command Queue Host Controller Interface) 공통 드라이버입니다
- drivers/mmc/host/sdhci.c — SD Host Controller 공통 드라이버의 핵심 구현입니다
- drivers/mmc/host/sdhci-msm.c — Qualcomm SDHCI 호스트 드라이버 (ICE, HS400 DLL 캘리브레이션)
- drivers/mmc/host/dw_mmc.c — Synopsys DesignWare MMC 호스트 드라이버 (IDMAC, Phase 튜닝)
- drivers/mmc/host/mtk-sd.c — MediaTek MSDC 호스트 드라이버 (PAD 딜레이 튜닝)
기술 문서 및 참고 자료
- LWN: Multi-queue for MMC (2016) — MMC 서브시스템의 blk-mq 전환 과정을 다루는 기사입니다
- LWN: Command queueing for eMMC (2017) — eMMC CMDQ 기능의 커널 지원 구현을 설명합니다
- mmc(1) man page — mmc-utils 도구의 사용법 레퍼런스입니다
- Samsung eMMC Data Sheet: KLMAG/KLMBG 시리즈 — Smart Report CMD56 포맷, 전기적 특성, P/E 사이클 보증
- SanDisk iNAND Application Note: Health Report CMD56 arg(0x110005F9) 포맷 정의, 수명 추정 알고리즘
- Micron TN-FC-32: eMMC Health Status Report 기술 노트 — CMD56 포맷, BKOPS 최적화 가이드
커널 소스 읽기 순서
eMMC 서브시스템을 소스 코드 레벨에서 이해하려면 다음 순서로 읽는 것을 권장합니다.
1단계: 핵심 자료구조와 초기화
include/linux/mmc/host.h—struct mmc_host정의,mmc_host_ops콜백 인터페이스include/linux/mmc/card.h—struct mmc_card정의, 카드 속성, quirk 플래그include/linux/mmc/mmc.h— MMC 커맨드/레지스터 상수, EXT_CSD 오프셋 정의drivers/mmc/core/core.c— MMC 코어 초기화, 전원/클럭 관리, 재튜닝drivers/mmc/core/mmc.c—mmc_init_card()— eMMC 카드 초기화 시퀀스 전체drivers/mmc/core/mmc_ops.c— 개별 MMC 커맨드 실행 함수 (mmc_send_ext_csd등)
2단계: 블록 디바이스와 요청 처리
drivers/mmc/core/block.c— 블록 디바이스 인터페이스 (mmcblk), TRIM/DISCARD 처리drivers/mmc/core/queue.c— blk-mq 큐 설정, 요청 디스패치drivers/mmc/core/quirks.h— 제조사별 quirk/fixup 테이블 — CID 매칭 로직
3단계: SDHCI 프레임워크
drivers/mmc/host/sdhci.c— SDHCI 공통 드라이버 (레지스터 I/O, ADMA, 인터럽트)drivers/mmc/host/sdhci.h— SDHCI 레지스터 오프셋, quirk 플래그 정의drivers/mmc/host/sdhci-pltfm.c— SDHCI 플랫폼 드라이버 공통 유틸리티
4단계: CQHCI와 호스트 드라이버
drivers/mmc/host/cqhci.c/cqhci.h— CMDQ 호스트 컨트롤러 인터페이스 공통 구현drivers/mmc/host/cqhci-crypto.c— CQHCI 인라인 암호화 확장drivers/mmc/host/sdhci-msm.c— Qualcomm 호스트 드라이버 (ICE, DLL 캘리브레이션)drivers/mmc/host/dw_mmc.c— Synopsys DesignWare 코어 (IDMAC, FIFO 관리)drivers/mmc/host/mtk-sd.c— MediaTek MSDC (PAD 딜레이, 독자 DMA)drivers/mmc/host/sdhci-esdhc-imx.c— NXP i.MX uSDHC (Strobe DLL, Auto-Tuning)drivers/mmc/host/renesas_sdhi_core.c— Renesas SDHI (SCC TAP 튜닝)
drivers/mmc/core/mmc.c의 mmc_init_card() 함수부터 읽으세요.
카드 초기화의 전체 흐름 — 전원 인가 → OCR 협상 → CID/CSD/EXT_CSD 읽기 → 버스 폭/속도 모드 전환 —을
하나의 함수에서 순차적으로 따라갈 수 있습니다.
호스트 드라이버 비교가 목적이라면 sdhci-msm.c(SDHCI 호환)와 dw_mmc.c(비-SDHCI)를
나란히 읽으며 초기화/튜닝/DMA 처리의 구조적 차이를 파악하는 것이 효과적입니다.