eMMC/MMC 서브시스템 — Embedded MultiMediaCard & Linux MMC Framework
eMMC(embedded MultiMediaCard)는 NAND 플래시와 컨트롤러를 하나의 BGA 패키지에 통합한 임베디드 스토리지 솔루션으로,
스마트폰, 태블릿, IoT 디바이스, 자동차 인포테인먼트 등에 널리 사용됩니다.
이 문서에서는 JEDEC eMMC 스펙의 프로토콜 기초부터 Linux 커널의 MMC 서브시스템(drivers/mmc/) 구조,
SDHCI 호스트 컨트롤러 프레임워크, HS200/HS400 버스 튜닝, EXT_CSD 레지스터, 하드웨어 파티션(Boot/RPMB/GP),
인라인 암호화(ICE), 디바이스 트리 바인딩, sysfs 인터페이스, 디버깅, 성능 튜닝,
UFS와의 비교까지 eMMC/MMC의 모든 것을 심층 분석합니다.
핵심 요약
- mmc_host — 호스트 컨트롤러 하나를 대표하는 최상위 구조체. 클럭, 버스 폭, 전압, 타이밍 정보를 관리합니다.
- 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, 캐시 |
| eMMC 5.0 | 2013 | HS200 (200 MHz SDR) | 200 MB/s | HS200, 슬립 알림(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 커널에서 사용자 공간의read()/write()요청은 VFS → 파일시스템 → 블록 레이어(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 등)을 마운트하거나,dd/fio로 직접 블록 I/O를 수행할 수 있습니다. 부트 파티션(mmcblk0boot0)은 기본적으로 쓰기 보호되어 있으며,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배의 대역폭을 얻습니다.
| 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 레지스터를 저장하는 내부 버퍼입니다.
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 트랜잭션. mmc_command(CMD) + mmc_command(STOP) + mmc_data를 담는 컨테이너 |
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 | 호스트 드라이버 콜백 집합. 요청 처리, 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 인터럽트 활성화/비활성화 |
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에 해당 플래그를 설정합니다.
요청 처리 파이프라인
사용자 공간의 I/O 요청이 eMMC 하드웨어까지 도달하는 전체 경로를 추적합니다. 일반(non-CQE) 경로와 CMDQ(CQE) 경로를 모두 살펴봅니다.
일반 요청 흐름
CQE가 비활성화된 상태에서의 요청 경로입니다:
submit_bio()→ blk-mq 소프트웨어 큐에 bio 삽입- blk-mq 스케줄러가 request를 디스패치 →
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개의 태스크를 동시에 큐에 넣을 수 있는 기능입니다.
커널의 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 성능 | 낮음 (직렬 병목) | 높음 (eMMC 내부 병렬 처리) |
| 활성화 | 기본 | EXT_CSD[15] CMDQ_MODE_EN=1 |
DMA 모드
SDHCI는 3가지 DMA 모드를 지원합니다. 현대 드라이버는 대부분 ADMA2를 사용합니다.
| DMA 모드 | 주소 크기 | 설명 | 장점 | 단점 |
|---|---|---|---|---|
| SDMA | 32-bit | 단일 버퍼, 4KB 경계마다 인터럽트 | 단순한 구현 | scatter-gather 미지원, 4KB마다 인터럽트 오버헤드 |
| ADMA2 | 32/64-bit | Descriptor 테이블 기반 scatter-gather | 비연속 물리 메모리 직접 전송 | 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로 별도 활성화해야 하며 기본 비활성입니다.
버스 모드 튜닝
eMMC는 세대가 발전하면서 버스 인터페이스 속도가 비약적으로 향상되었습니다.
호스트 컨트롤러는 카드의 CARD_TYPE(EXT_CSD[196]) 필드를 읽어 지원 가능한
최고 속도 모드를 협상하고, 해당 모드에 맞는 타이밍 튜닝을 수행합니다.
| 모드 | 클럭 | 버스 폭 | SDR/DDR | 최대 처리량 | 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()).
EXT_CSD 레지스터
EXT_CSD(Extended CSD)는 eMMC 카드의 확장 설정 레지스터로, 총 512바이트 크기입니다. 기존 CSD 레지스터(128비트)로는 표현할 수 없는 eMMC 고유 기능들의 설정과 상태 정보를 담고 있으며, CMD8 (SEND_EXT_CSD)을 통해 읽을 수 있습니다.
레지스터 구조
EXT_CSD는 두 영역으로 나뉩니다:
- Properties 영역 (바이트 192~511): 읽기 전용. 카드의 하드웨어 능력 및 스펙 정보를 제공합니다.
- 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 | 카드 펌웨어 버전 (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)과 별개로, 하드웨어 수준의 물리 파티션을 내장하고 있습니다. 각 파티션은 독립적인 주소 공간과 접근 제어를 가지며, Linux 커널은 이를 별도의 블록 디바이스로 노출합니다.
| 파티션 유형 | 기본 크기 | 접근 방법 | 디바이스 노드 | 설명 |
|---|---|---|---|---|
| User Data Area (UDA) | 전체 용량 - 기타 파티션 | 일반 R/W | /dev/mmcblk0 |
기본 데이터 저장 영역 (GPT/MBR 생성 가능) |
| Boot Partition 1 | 128KB ~ 32MB | PARTITION_CONFIG | /dev/mmcblk0boot0 |
부트로더 저장, 전용 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 대신 인증 프로토콜을 사용하며, 재전송 공격(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/ 심볼릭 링크를 사용하는 것이 권장됩니다.
데이터 무결성
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])은 카드에 전원 차단을 사전 통지하여 내부 캐시를 안전하게 플러시할 시간을 제공합니다.
/* 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,
};
전원 관리
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를 발행하는 것이 권장됩니다.
인라인 암호화
최신 eMMC 호스트 컨트롤러는 ICE(Inline Crypto Engine)를 내장하여, 호스트 컨트롤러와 eMMC NAND 사이의 데이터 경로에서 하드웨어 암호화/복호화를 수행합니다. 이를 통해 CPU 부하 없이 투명한 디스크 암호화를 실현할 수 있으며, Android의 FBE(File-Based Encryption)와 메타데이터 암호화에 핵심적으로 활용됩니다.
Inline Crypto Engine (ICE) 아키텍처
ICE는 호스트 컨트롤러 내부에 위치하며, 블록 I/O가 eMMC에 도달하기 전에 암호화/복호화를 수행합니다. 핵심 구성 요소:
- Crypto Capabilities Register: 지원 알고리즘(AES-256-XTS 등), 키 크기, 키슬롯 수
- Keyslot Manager: 하드웨어 키슬롯에 암호화 키를 프로그래밍하고 관리
- Task Descriptor: CQHCI 태스크 디스크립터에 crypto 필드(키슬롯 번호, DUN) 포함
blk-crypto 프레임워크
Linux 커널은 blk-crypto 프레임워크를 통해 인라인 암호화를 추상화합니다.
하드웨어가 지원하면 ICE를 사용하고, 미지원 시 소프트웨어 폴백(blk-crypto-fallback)을
자동으로 적용합니다.
/* include/linux/blk-crypto.h — 블록 레이어 암호화 키 */
struct blk_crypto_key {
struct blk_crypto_config crypto_cfg;
unsigned int data_unit_size; /* 암호화 단위 (보통 4096) */
unsigned int size; /* 키 크기 */
u8 raw[BLK_CRYPTO_MAX_KEY_SIZE]; /* 원시 키 데이터 */
};
struct blk_crypto_config {
enum blk_crypto_mode_num crypto_mode; /* AES-256-XTS 등 */
unsigned int data_unit_size;
unsigned int dun_bytes; /* Data Unit Number 바이트 수 */
};
/* 암호화 프로파일: 하드웨어 능력 기술 */
struct blk_crypto_profile {
unsigned int max_dun_bytes_supported;
unsigned int num_slots; /* 하드웨어 키슬롯 수 */
struct blk_crypto_ll_ops *ll_ops; /* 저수준 키슬롯 ops */
};
/* 저수준 ops (호스트 드라이버가 구현) */
struct blk_crypto_ll_ops {
int (*keyslot_program)(struct blk_crypto_profile *profile,
const struct blk_crypto_key *key,
unsigned int slot);
int (*keyslot_evict)(struct blk_crypto_profile *profile,
const struct blk_crypto_key *key,
unsigned int slot);
};
CQHCI Crypto 지원
/* drivers/mmc/host/cqhci-crypto.c — CQHCI 인라인 암호화 */
int cqhci_host_init_crypto(struct cqhci_host *cq_host)
{
struct mmc_host *mmc = cq_host->mmc;
unsigned int num_keyslots;
unsigned int cap_idx;
/* Crypto Capabilities Register 읽기 */
cq_host->crypto_capabilities =
cqhci_readl(cq_host, CQHCI_CCAP);
/* 키슬롯 수 확인 */
num_keyslots = cq_host->crypto_capabilities &
CQHCI_CCAP_NUM_KEYSLOTS;
/* blk-crypto 프로파일 할당 */
return devm_blk_crypto_profile_init(
mmc_dev(mmc), &mmc->crypto_profile, num_keyslots);
}
/* 키슬롯 프로그래밍 */
static int cqhci_crypto_keyslot_program(
struct blk_crypto_profile *profile,
const struct blk_crypto_key *key,
unsigned int slot)
{
struct cqhci_host *cq_host = profile_to_cqhci(profile);
int i;
/* CRYPTO_CFG 레지스터에 키 + 알고리즘 쓰기 */
/* Keyslot은 32바이트 단위로 구성 */
for (i = 0; i < key->size / sizeof(__le32); i++) {
cqhci_writel(cq_host,
le32_to_cpu(((__le32 *)key->raw)[i]),
CQHCI_CRYPTOCFG_BASE +
slot * CQHCI_CRYPTOCFG_SIZE +
i * sizeof(__le32));
}
/* 알고리즘 + DUN 크기 + 활성화 비트 설정 */
cqhci_writel(cq_host,
CQHCI_CRYPTOCFG_ENABLE |
CQHCI_CRYPTOCFG_ALG(key->crypto_cfg.crypto_mode) |
CQHCI_CRYPTOCFG_DU_SIZE(key->crypto_cfg.data_unit_size),
CQHCI_CRYPTOCFG_BASE +
slot * CQHCI_CRYPTOCFG_SIZE + 16);
return 0;
}
/* Task Descriptor에 crypto 정보 설정 */
static void cqhci_set_tran_desc_crypto(struct cqhci_host *cq_host,
u8 slot, u64 dun,
__le64 *task_desc)
{
/* CQHCI Task Descriptor Word 0: crypto enable + keyslot */
task_desc[0] |= CQHCI_TASK_DESC_CRYPTO_ENABLE;
task_desc[0] |= CQHCI_TASK_DESC_CRYPTO_KEYSLOT(slot);
/* Word 2-3: Data Unit Number (IV와 유사) */
task_desc[2] = cpu_to_le64(dun);
}
fscrypt 통합
fscrypt(파일 시스템 수준 암호화)은 blk-crypto와 연동하여
파일별 암호화 키를 하드웨어 ICE에 오프로드할 수 있습니다.
ext4, f2fs에서 지원하며, 각 파일/디렉토리별로 독립적인 키를 사용합니다.
# 커널 설정 옵션
CONFIG_MMC_CRYPTO=y # MMC 인라인 암호화 지원
CONFIG_BLK_INLINE_ENCRYPTION=y # 블록 레이어 인라인 암호화
CONFIG_FS_ENCRYPTION=y # fscrypt 파일 시스템 암호화
CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y # fscrypt → blk-crypto 연동
# fscrypt 정책 설정 (ext4/f2fs)
# 디렉토리에 암호화 정책 적용
fscryptctl set_policy --contents=AES-256-XTS --filenames=AES-256-CTS \
--flags=inline-crypt /mnt/data/encrypted_dir
/data 파티션에 FBE(fscrypt)를, /metadata에
dm-default-key를 사용한 메타데이터 암호화를 적용합니다.
ICE 지원 eMMC 호스트에서는 이 모든 암호화가 하드웨어에서 투명하게 처리되어
CPU 오버헤드가 거의 없습니다.
keyslot_evict()를 호출하여 하드웨어에서 제거하고,
memzero_explicit()으로 메모리에서 안전하게 삭제해야 합니다.
TrustZone/TEE를 통한 키 생성 및 주입이 가장 안전한 방식입니다.
디바이스 트리 바인딩
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 = ;
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 = ;
#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 = ;
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 프로빙으로 초기화 지연이 발생합니다.
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/MMC 서브시스템 문제를 진단할 때는 커널이 제공하는 다양한 디버깅 도구를 계층별로 활용합니다. 하드웨어 레벨(오실로스코프/로직 분석기)부터 소프트웨어 레벨(커널 로그, tracepoint, debugfs)까지 단계적으로 접근하면 효율적으로 원인을 좁힐 수 있습니다.
mmc_test 커널 모듈
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/)을 사용합니다. 블록 장치 이름이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 |
예비 블록 소진 — 장치 수명 한계 도달 | 장치 교체 계획 수립, 쓰기 부하 감소 (저널링 최소화, 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 위의 블록 레이어, I/O 스케줄러, bio/request 처리 |
| SATA/AHCI | SATA/AHCI | 또 다른 블록 스토리지 서브시스템 비교 참고 |
| NVMe | NVMe | PCIe 기반 플래시 스토리지, 커맨드 큐 비교 |
| F2FS 파일시스템 | F2FS | eMMC/UFS에 최적화된 플래시 전용 파일시스템 |
| 디바이스 트리 | 디바이스 트리 | eMMC DT 바인딩, 핀 설정, 전원 시퀀스 |
| DMA | DMA | ADMA2 디스크립터, DMA 매핑, scatter-gather |
| Android 커널 | Android | Android 환경에서의 eMMC 활용, 보안 부팅 |
| MTD/플래시 | MTD | Raw NAND와의 비교, FTL 개념 이해 |
외부 참고 자료
- JEDEC JESD84-B51A: eMMC 5.1 표준 사양서 — 커맨드, 레지스터, 타이밍, 파티션, RPMB 등 전체 프로토콜 정의
- SD Host Controller Simplified Specification: SDHCI 레지스터 맵, ADMA 디스크립터, 인터럽트 처리 정의
- 커널 Documentation/mmc/:
Documentation/mmc/mmc-dev-attrs.rst(sysfs 속성),Documentation/mmc/mmc-dev-parts.rst(파티션) - JEDEC UFS (JESD220): UFS 표준 사양서 — eMMC 후속 기술 비교 학습에 유용
- MIPI M-PHY / UniPro: UFS 물리/전송 계층 이해에 필요한 MIPI Alliance 사양
커널 소스 읽기 순서
eMMC 서브시스템을 소스 코드 레벨에서 이해하려면 다음 순서로 읽는 것을 권장합니다.
include/linux/mmc/host.h—struct mmc_host정의, 호스트 opsinclude/linux/mmc/card.h—struct mmc_card정의, 카드 속성include/linux/mmc/mmc.h— MMC 커맨드/레지스터 상수 정의drivers/mmc/core/core.c— MMC 코어 초기화, 전원/클럭 관리drivers/mmc/core/mmc.c— eMMC 카드 초기화 시퀀스drivers/mmc/core/mmc_ops.c— 개별 MMC 커맨드 실행 함수drivers/mmc/core/block.c— 블록 디바이스 인터페이스 (mmcblk)drivers/mmc/core/queue.c— 블록 요청 큐 처리drivers/mmc/host/sdhci.c— SDHCI 공통 드라이버drivers/mmc/host/sdhci-of-arasan.c— 플랫폼별 SDHCI 예시 (Arasan)drivers/mmc/host/cqhci.c— CMDQ 호스트 컨트롤러 인터페이스
drivers/mmc/core/mmc.c의 mmc_init_card() 함수부터 읽으세요.
카드 초기화의 전체 흐름 — 전원 인가 → OCR 협상 → CID/CSD/EXT_CSD 읽기 → 버스 폭/속도 모드 전환 —을
하나의 함수에서 순차적으로 따라갈 수 있습니다.