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의 모든 것을 심층 분석합니다.

전제 조건: 블록 I/O 문서를 먼저 읽으세요. eMMC는 블록 디바이스로서 blk-mq를 통해 I/O 요청을 처리합니다. 디바이스 트리 지식이 있으면 플랫폼 바인딩 섹션을 더 쉽게 이해할 수 있습니다.
일상 비유: eMMC는 올인원 USB 메모리와 비슷합니다. 일반 SSD/HDD가 별도의 케이블과 커넥터로 연결되는 반면, eMMC는 NAND 칩과 컨트롤러가 하나의 작은 칩 안에 들어 있어 보드에 직접 납땜됩니다. 호스트 컨트롤러(SDHCI)는 우체국 창구처럼 정해진 프로토콜로 eMMC와 커맨드/데이터를 주고받습니다.

핵심 요약

eMMC(embedded MultiMediaCard)는 JEDEC 표준(JESD84-B51A)에 의해 정의된 임베디드 스토리지 솔루션입니다. NAND 플래시 메모리, 내장 플래시 컨트롤러(FTL, ECC, Wear Leveling), 그리고 호스트 인터페이스를 하나의 153-pin 또는 169-pin BGA 패키지에 통합합니다. 호스트는 8비트 병렬 버스(CMD + DAT0~7 + CLK)를 통해 MMC 프로토콜 커맨드를 전송하여 eMMC와 통신합니다.
  • 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 vs SD Card vs UFS vs NVMe 비교
항목eMMCSD CardUFSNVMe
인터페이스병렬 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-bit1/4-bit1~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 SDBGA (11.5×13mm)M.2 / U.2 / AIC
JEDEC eMMC 스펙 버전 변천
스펙 버전발표 연도최대 버스 모드최대 전송 속도주요 특징
eMMC 4.32007High Speed (52 MHz)52 MB/sBoot 파티션, RPMB 도입
eMMC 4.412010DDR52 (52 MHz DDR)104 MB/sDDR 모드, Enhanced Area 지원
eMMC 4.52011DDR52104 MB/sTRIM/Sanitize, HPI, BKOPS, 캐시
eMMC 5.02013HS200 (200 MHz SDR)200 MB/sHS200, 슬립 알림(Sleep Notification)
eMMC 5.12015HS400 (200 MHz DDR)400 MB/sHS400, CMDQ(Command Queuing), Secure Write Protect
eMMC 5.1A2016HS400ES (Enhanced Strobe)400 MB/sHS400ES(Data Strobe), FFU(Field Firmware Update)
/dev/mmcblk 네이밍 규칙: eMMC 디바이스는 /dev/mmcblk0으로 나타나며, 사용자 데이터 파티션은 mmcblk0p1, mmcblk0p2, ... 로 표시됩니다. 하드웨어 부트 파티션은 mmcblk0boot0, mmcblk0boot1이 되고, RPMB(Replay Protected Memory Block) 파티션은 mmcblk0rpmb로 나타납니다. SD 카드가 추가로 있으면 mmcblk1이 됩니다. 호스트 컨트롤러 인덱스와 탐지 순서에 따라 번호가 결정됩니다.

단계별 이해

비유: eMMC 통신을 우체국에 비유하면 다음과 같습니다. 호스트(사용자)가 창구(SDHCI)를 통해 편지(커맨드)를 보내고, eMMC(우체국 직원)가 편지를 읽고 소포(데이터)를 돌려보내는 구조입니다.
  1. 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. 2단계 — eMMC가 응답(Response) 반환
    eMMC는 같은 CMD 라인을 통해 응답을 돌려보냅니다. 응답 타입에 따라 48비트(R1, R3, R6 등) 또는 136비트(R2 — CID/CSD 전체)가 전송됩니다. 응답에는 카드 상태 비트(ready, error 플래그 등)가 포함되어 호스트가 후속 동작을 결정할 수 있습니다.
  3. 3단계 — DAT0~7 라인으로 데이터 전송
    데이터 전송이 필요한 커맨드(ADTC 타입)의 경우, 데이터는 DAT0~DAT7의 8비트 병렬 버스를 통해 전송됩니다. eMMC 5.1의 HS400 모드에서는 200MHz DDR로 동작하여 클럭 엣지마다 8비트 × 2(더블 데이터 레이트) = 16비트를 전송, 이론적 최대 400 MB/s를 달성합니다. 각 블록(512바이트) 전송 후 CRC16 체크가 수행됩니다.
  4. 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. 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가지로 분류됩니다.

MMC 커맨드 타입 분류
타입약어응답데이터설명예시
Broadcast CommandBC없음없음모든 카드에 전송, 응답 없음CMD0 (GO_IDLE_STATE)
Broadcast Command with ResponseBCR있음없음모든 카드에 전송, 응답 있음CMD1 (SEND_OP_COND)
Addressed CommandAC있음없음특정 카드 주소 지정, 데이터 없음CMD7 (SELECT_CARD)
Addressed Data Transfer CommandADTC있음있음특정 카드 주소 지정, 데이터 전송CMD17 (READ_SINGLE_BLOCK)

응답 타입

MMC 응답 타입
응답 타입길이내용CRC사용 커맨드
R148비트카드 상태(Card Status) 32비트CRC7대부분의 AC/ADTC 커맨드
R1b48비트R1과 동일 + DAT0 busy 신호CRC7CMD6 (SWITCH), CMD12 (STOP)
R2136비트CID 또는 CSD 레지스터 (128비트)CRC7CMD2 (ALL_SEND_CID), CMD9 (SEND_CSD)
R348비트OCR 레지스터 (32비트)없음CMD1 (SEND_OP_COND)

주요 MMC 커맨드

eMMC 핵심 커맨드 목록
커맨드이름타입응답설명
CMD0GO_IDLE_STATEBC없음카드를 Idle 상태로 리셋
CMD1SEND_OP_CONDBCRR3동작 전압 협상 및 카드 초기화
CMD2ALL_SEND_CIDBCRR2카드 식별 정보(CID) 요청
CMD3SET_RELATIVE_ADDRACR1상대 주소(RCA) 할당
CMD6SWITCHACR1bEXT_CSD 레지스터 필드 변경 (버스 모드, 파티션 등)
CMD7SELECT_CARDACR1bRCA로 카드 선택/해제 (Transfer State 진입)
CMD8SEND_EXT_CSDADTCR1EXT_CSD 512바이트 읽기
CMD13SEND_STATUSACR1카드 상태(Card Status) 조회
CMD17READ_SINGLE_BLOCKADTCR1단일 블록(512B) 읽기
CMD18READ_MULTIPLE_BLOCKADTCR1다중 블록 연속 읽기
CMD23SET_BLOCK_COUNTACR1다음 다중 블록 전송의 블록 수 설정
CMD24WRITE_BLOCKADTCR1단일 블록(512B) 쓰기
CMD25WRITE_MULTIPLE_BLOCKADTCR1다중 블록 연속 쓰기

버스 폭 모드

eMMC는 초기화 시 1-bit 모드(DAT0만 사용)로 시작하며, 호스트가 CMD6(SWITCH)를 통해 EXT_CSD[183] BUS_WIDTH 필드를 변경하여 4-bit 또는 8-bit 모드로 전환합니다. DDR 모드에서는 클럭의 상승/하강 엣지 모두에서 데이터를 전송하여 동일 클럭 주파수에서 2배의 대역폭을 얻습니다.

eMMC 버스 폭 설정 (EXT_CSD[183])
BUS_WIDTH 값모드데이터 라인DDR비고
01-bitDAT0SDR초기화 기본값
14-bitDAT0~3SDRSD 카드 호환 모드
28-bitDAT0~7SDReMMC 전용 최대 폭
54-bit DDRDAT0~3DDRDDR52 4-bit
68-bit DDRDAT0~7DDRDDR52 / HS400 / HS400ES
eMMC 커맨드/응답 시퀀스 (Single Block Read) Host (SDHCI) eMMC Device CMD17 (READ_SINGLE_BLOCK) + Block Addr CMD 라인 → 48-bit 커맨드 프레임 R1 Response (Card Status) CMD 라인 ← 48-bit 응답 프레임 512-byte Data Block DAT0~7 라인 ← 8-bit 병렬 데이터 + CRC16 CMD23 (SET_BLOCK_COUNT) + N R1 Response CMD18 (READ_MULTIPLE_BLOCK) R1 + N blocks on DAT0~7 Block 1 | Block 2 | ... | Block N (연속 전송) CMD23+CMD18 = Pre-defined Multi-Block (Auto CMD12 불필요)
Open-ended vs Pre-defined 전송: 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개의 주요 블록으로 구성됩니다:

BGA 핀 배치

eMMC BGA 주요 핀 (JEDEC 153-pin / 169-pin)
핀 이름방향설명
CLK1Host → eMMC클럭 신호. 모든 버스 동작의 동기 기준 (최대 200 MHz)
CMD1양방향커맨드/응답 전송 라인 (OD/PP 모드)
DAT0~DAT78양방향8-bit 데이터 버스. 1/4/8-bit 모드 선택 가능
DS (Data Strobe)1eMMC → HostHS400 모드에서 데이터 샘플링 스트로브 (eMMC 5.1+)
RST_n1Host → eMMC하드웨어 리셋 핀 (Active Low)
VCC다수전원NAND 플래시 전원 (2.7V~3.6V)
VCCQ다수전원I/O 인터페이스 전원 (1.2V / 1.8V / 3.3V)
VSS다수GND접지

전압 모드

eMMC 전압 모드 및 시그널링
VCCQ 전압시그널링지원 버스 모드비고
3.3V3.3V LVCMOSLegacy, High Speed, DDR52하위 호환 모드
1.8V1.8V LVCMOSHS200, DDR52저전력 시그널링
1.2V1.2V LVCMOSHS200, HS400, HS400ES최고 속도 모드 필수

속도 모드별 클럭 및 대역폭

eMMC 속도 모드 상세
모드클럭 주파수버스 폭전송 방식최대 대역폭VCCQ
Legacy0~26 MHz1/4/8-bitSDR26 MB/s3.3V
High Speed (HS)0~52 MHz1/4/8-bitSDR52 MB/s3.3V
DDR520~52 MHz4/8-bitDDR104 MB/s3.3V / 1.8V
HS2000~200 MHz4/8-bitSDR200 MB/s1.8V / 1.2V
HS4000~200 MHz8-bitDDR400 MB/s1.8V / 1.2V
HS400ES0~200 MHz8-bitDDR + Strobe400 MB/s1.8V / 1.2V
HS400 vs HS400ES: HS400은 호스트가 튜닝(tuning) 과정을 통해 데이터 샘플링 지점을 조정해야 합니다. 반면 HS400ES(Enhanced Strobe)는 eMMC가 제공하는 DS(Data Strobe) 신호를 사용하므로 튜닝이 불필요하여 더 안정적이고 초기화가 빠릅니다. HS400ES를 지원하는 eMMC는 EXT_CSD[184] STROBE_SUPPORT 비트가 설정되어 있습니다.
eMMC 하드웨어 버스 토폴로지 Host SoC CPU / AP MMC Core (blk-mq) SDHCI Controller CMD Engine | DMA | Clock Gen Registers: 0x00~0xFF I/O Pad + PHY (드라이브 강도, 딜레이) Voltage Regulator (VCC 3.3V / VCCQ 1.8V) CLK (→) CMD (↔) DAT0~DAT7 (↔ 8-bit) DS (Data Strobe, HS400 only ←) RST_n (→) eMMC Device (BGA) Host Interface CMD/DAT Logic Flash Controller FTL + ECC + WL SRAM Buffer / Write Cache NAND Flash Array Boot0 Boot1 RPMB User Data GP1~4 CID | CSD | EXT_CSD | OCR | DSR RCA: 0x0001 (eMMC 고정)
eMMC 점대점(Point-to-Point) 연결: SD 카드와 달리 eMMC는 보드에 직접 납땜되므로 항상 호스트와 1:1로 연결됩니다. 따라서 RCA(Relative Card Address)는 항상 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             # 빌드 설정

핵심 구조체

MMC 서브시스템 핵심 구조체
구조체헤더역할
struct mmc_hostinclude/linux/mmc/host.h호스트 컨트롤러 인스턴스. 클럭, 전압, 버스 폭, 타이밍, capability 플래그를 관리. mmc_alloc_host()로 할당
struct mmc_cardinclude/linux/mmc/card.h탐지된 카드 디바이스 하나. CID, CSD, EXT_CSD 정보, 카드 타입(MMC/SD/SDIO)을 보유
struct mmc_iosinclude/linux/mmc/host.h현재 I/O 설정: 클럭 주파수, 버스 폭, 전압, 타이밍 모드, 드라이브 강도
struct mmc_requestinclude/linux/mmc/core.h하나의 MMC 트랜잭션. mmc_command(CMD) + mmc_command(STOP) + mmc_data를 담는 컨테이너
struct mmc_commandinclude/linux/mmc/core.h단일 커맨드: opcode, arg, flags(응답 타입), resp[4] 배열
struct mmc_datainclude/linux/mmc/core.h데이터 전송 정보: blksz, blocks, sg(scatterlist), flags(읽기/쓰기)
struct mmc_host_opsinclude/linux/mmc/host.h호스트 드라이버 콜백 집합. 요청 처리, I/O 설정, 카드 탐지, 튜닝 등

mmc_host_ops 콜백

mmc_host_ops 주요 콜백 함수
콜백프로토타입설명
requestvoid (*)(struct mmc_host *, struct mmc_request *)MMC 요청을 하드웨어에 전달. 비동기 방식으로 완료 시 mmc_request_done() 호출
set_iosvoid (*)(struct mmc_host *, struct mmc_ios *)클럭 주파수, 버스 폭, 전압, 타이밍 등 I/O 파라미터 적용
get_cdint (*)(struct mmc_host *)카드 삽입 상태 반환 (eMMC는 항상 1)
get_roint (*)(struct mmc_host *)쓰기 보호 상태 반환
enable_sdio_irqvoid (*)(struct mmc_host *, int enable)SDIO 인터럽트 활성화/비활성화
execute_tuningint (*)(struct mmc_host *, u32 opcode)HS200/HS400 버스 튜닝 수행 (CMD21 사용)
prepare_hs400_tuningvoid (*)(struct mmc_host *, struct mmc_ios *)HS400 전환 전 튜닝 준비
hs400_enhanced_strobevoid (*)(struct mmc_host *, struct mmc_ios *)HS400ES 모드 설정
card_busyint (*)(struct mmc_host *)DAT0 라인의 busy 상태 확인
hw_resetvoid (*)(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는 다음 순서로 초기화됩니다:

  1. CMD0 — GO_IDLE_STATE: 카드를 Idle 상태로 리셋합니다.
  2. CMD1 — SEND_OP_COND (반복): 동작 전압을 협상합니다. OCR의 busy 비트가 설정될 때까지 반복합니다.
  3. CMD2 — ALL_SEND_CID: CID(Card Identification) 레지스터를 읽어 제조사, 제품명, 시리얼 번호를 확인합니다.
  4. CMD3 — SET_RELATIVE_ADDR: RCA를 0x0001로 설정합니다 (eMMC는 호스트가 지정).
  5. CMD9 — SEND_CSD: CSD 레지스터를 읽어 용량, 최대 전송 속도 등을 확인합니다.
  6. CMD7 — SELECT_CARD: 카드를 Transfer State로 전환합니다.
  7. CMD8 — SEND_EXT_CSD: 512바이트 EXT_CSD 레지스터를 읽어 상세 설정을 확인합니다.
  8. CMD6 — SWITCH (반복): 버스 폭(8-bit), 타이밍(HS200/HS400), 파워 클래스 등을 설정합니다.
  9. Tuning: HS200 모드에서 CMD21로 튜닝을 수행합니다. HS400 전환 시 HS200 → DDR52 → HS400 순서를 따릅니다.
Linux MMC 서브시스템 레이어 구조 User Space: /dev/mmcblk0pN, /sys/class/mmc_host/, mmc-utils, fio ─── 커널 경계 (syscall) ─── VFS + 파일시스템 (ext4, f2fs ...) sysfs / debugfs / ioctl Block Layer (blk-mq) — I/O 스케줄링, 요청 병합 mmc_block (block.c) — blk-mq 콜백, 파티션 관리, RPMB, Boot 파티션 MMC Core (core.c, mmc.c, mmc_ops.c) 초기화, CMD 전송, 전압/클럭/타이밍 관리 CQE (cqhci.c) Command Queue Engine SDHCI Framework (sdhci.c) — 표준 레지스터 맵, IRQ, DMA sdhci-tegra sdhci-msm sdhci-esdhc-imx dw_mmc / mtk-sd ─── 하드웨어 경계 (SDHCI 레지스터 → eMMC BGA) ─── eMMC 초기화 커맨드 시퀀스 (HS400 전환 포함) Idle State Ready State Ident State Stand-by State Transfer State CMD0 (GO_IDLE_STATE) — 400 kHz, 1-bit, 3.3V CMD1 (SEND_OP_COND) — 반복, OCR busy 확인 (섹터 주소 모드) CMD2 (ALL_SEND_CID) — 카드 식별 (R2 응답, 128-bit CID) CMD3 (SET_RELATIVE_ADDR) — RCA=0x0001 할당 CMD9 (SEND_CSD) — CSD 레지스터 읽기 CMD7 (SELECT_CARD) — Transfer State 진입 CMD8 (SEND_EXT_CSD) — EXT_CSD 512B 읽기 (ADTC, DAT) CMD6 (SWITCH) — BUS_WIDTH=8-bit, TIMING=HS200, POWER_CLASS 설정 CMD21 (SEND_TUNING_BLOCK) — HS200 튜닝 수행 (128B 패턴) CMD6 (SWITCH) — TIMING=HS400 (HS200→DDR52→HS400 순서) HS400ES: CMD6으로 STROBE_SUPPORT 활성화 후 HS400 전환 (튜닝 불필요)
HS400 전환 시퀀스의 특수성: HS400 모드로 전환하려면 반드시 HS200 → HS (DDR52) → HS400 순서를 따라야 합니다. 직접 HS400으로 전환할 수 없으며, 이 순서를 위반하면 eMMC가 응답하지 않을 수 있습니다. 커널 코드에서 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 콜백

sdhci_ops 주요 콜백 (벤더 드라이버가 구현)
콜백설명구현 예시
set_clock클럭 주파수 설정. 벤더별 PLL/분주기 제어sdhci_tegra_set_clock
set_bus_width버스 폭(1/4/8-bit) 설정sdhci_set_bus_width (공통)
set_uhs_signalingUHS/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_descADMA descriptor 엔트리 작성(벤더별 ADMA 포맷 대응)
voltage_switch1.8V ↔ 3.3V 시그널링 전압 전환sdhci_start_signal_voltage_switch
irq벤더별 추가 인터럽트 처리sdhci_msm_cqe_irq

SDHCI 레지스터 맵 (주요 레지스터)

SDHCI 표준 레지스터 맵 (오프셋 기준)
오프셋크기이름접근설명
0x004BSDMA System AddressR/WSDMA 전송용 시스템 메모리 주소
0x042BBlock SizeR/W전송 블록 크기 (보통 512)
0x062BBlock CountR/W전송 블록 수
0x084BArgumentR/W커맨드 인자 (32-bit)
0x0C2BTransfer ModeR/WDMA 사용, 블록 수 카운트, 방향, Auto CMD12/23
0x0E2BCommandR/W커맨드 인덱스 + 응답 타입. 쓰기 시 커맨드 발행
0x10~0x1F16BResponseR응답 레지스터 (R1: [0]만, R2: [0]~[3] 사용)
0x204BBuffer Data PortR/WPIO 모드 데이터 전송 포트
0x244BPresent StateRCMD/DAT 라인 상태, 카드 삽입, 쓰기 보호 등
0x281BHost Control 1R/W버스 폭(4/8-bit), High Speed 활성화, DMA 선택
0x291BPower ControlR/W버스 전원 ON/OFF, 전압 선택
0x2C2BClock ControlR/W내부 클럭 활성화, SD 클럭 출력, 분주기 설정
0x2E1BTimeout ControlR/W데이터 타임아웃 카운터 값
0x2F1BSoftware ResetR/WAll/CMD/DAT 리셋
0x302BNormal Interrupt StatusR/W1C커맨드 완료, 전송 완료, 카드 삽입/제거 등
0x322BError Interrupt StatusR/W1CCMD CRC 에러, 데이터 CRC 에러, 타임아웃 등
0x342BNormal Interrupt Status EnableR/W인터럽트 상태 레지스터 활성화 마스크
0x382BNormal Interrupt Signal EnableR/W인터럽트 신호(IRQ 라인) 활성화 마스크
0x3E2BHost Control 2R/WUHS 모드 선택, 1.8V 시그널링, 튜닝 제어
0x404BCapabilitiesR지원 전압, 최대 클럭, DMA 지원 등
0x444BCapabilities 2RSDR50/SDR104/DDR50/HS400 지원, 튜닝 정보
0x584BADMA2 Address LowR/WADMA2 descriptor 테이블 시스템 주소 (하위)
0x5C4BADMA2 Address HighR/WADMA2 descriptor 테이블 시스템 주소 (상위, 64-bit)
0xFE2BHost Controller VersionRSDHCI 스펙 버전 번호

주요 플랫폼 드라이버

Linux 커널 주요 SDHCI 플랫폼 드라이버
드라이버소스 파일SoC / 벤더비고
sdhci-tegrahost/sdhci-tegra.cNVIDIA TegraHS400/HS400ES, 자체 튜닝 알고리즘
sdhci-msmhost/sdhci-msm.cQualcomm MSM/SDMICE(Inline Crypto), CQE 지원
sdhci-esdhc-imxhost/sdhci-esdhc-imx.cNXP i.MXEnhanced SD Host Controller
sdhci-of-arasanhost/sdhci-of-arasan.cXilinx Zynq, Arasan IPDT 기반, PHY 분리
sdhci-s3chost/sdhci-s3c.cSamsung Exynos레거시, 신규는 dw_mmc 사용
dw_mmchost/dw_mmc.cSynopsys DesignWare비SDHCI 독자 IP. Samsung, Rockchip 등
mtk-sdhost/mtk-sd.cMediaTek비SDHCI 독자 IP. MT8183/MT6797 등
sdhci-brcmstbhost/sdhci-brcmstb.cBroadcom STB셋톱박스 SoC
sdhci-pcihost/sdhci-pci-core.cIntel/AMD (PCI)x86 PCI 장치로 노출
SDHCI 호스트 컨트롤러 내부 블록 다이어그램 SDHCI Host Controller (Register Map: 0x00~0xFF) System Bus I/F AHB / AXI / PCIe Control Registers CMD, Arg, BlkSize, BlkCnt, IntSts, Caps Interrupt Controller Normal IRQ + Error IRQ → GIC DMA Engine SDMA ADMA2 ADMA3 Command Engine CMD 발행 → 응답 수신 Auto CMD12/23 Clock Gen Base CLK → 분주기 → SD CLK 출력 Data Buffer (FIFO) PIO / DMA 전송 버퍼 Tuning Circuit HS200 튜닝 + 샘플링 딜레이 CQE (Command Queue Engine) JEDEC CQHCI: 최대 32 슬롯 CLK Pad CMD Pad DAT0~7 Pad DS Pad (HS400) → eMMC BGA 핀 연결
SDHCI quirks 활용: 실제 SoC의 SDHCI 구현은 표준과 미묘하게 다른 동작을 보이는 경우가 많습니다. 커널은 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가 비활성화된 상태에서의 요청 경로입니다:

  1. submit_bio() → blk-mq 소프트웨어 큐에 bio 삽입
  2. blk-mq 스케줄러가 request를 디스패치 → mmc_blk_mq_issue_rq() 호출
  3. mmc_blk_mq_issue_rw_rq()에서 mmc_start_request()host->ops->request()
  4. SDHCI: sdhci_request() → 레지스터에 CMD/ARG/블록 정보 기록 → 커맨드 발행
  5. 하드웨어가 CMD 전송 → 응답 수신 → DMA로 데이터 전송
  6. 인터럽트 발생 → sdhci_irq()mmc_request_done()
  7. 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) 스펙을 구현합니다.

일반 vs CQE 모드 비교
항목일반 모드CQE/CMDQ 모드
동시 요청1개 (직렬 처리)최대 32개 (병렬 처리)
커맨드CMD17/18/24/25CMD44 (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를 사용합니다.

SDHCI DMA 모드 비교
DMA 모드주소 크기설명장점단점
SDMA32-bit단일 버퍼, 4KB 경계마다 인터럽트단순한 구현scatter-gather 미지원, 4KB마다 인터럽트 오버헤드
ADMA232/64-bitDescriptor 테이블 기반 scatter-gather비연속 물리 메모리 직접 전송descriptor 테이블 메모리 할당 필요
ADMA364-bitADMA2 + 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 */

블록 디바이스 핵심 구조체

mmc_block 관련 구조체
구조체소스역할
struct mmc_blk_datacore/block.c블록 디바이스 인스턴스. gendisk, mmc_queue, 파티션 타입(main/boot/rpmb/gp) 보유
struct mmc_queuecore/queue.cblk-mq 요청 큐 래퍼. blk_mq_tag_set, recovery 워크 관리
struct mmc_queue_reqcore/queue.h개별 요청 컨텍스트. mmc_blk_request(mrq + brq), bounce 버퍼, sg_list 포함
struct mmc_blk_requestcore/block.cmmc_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;
    }
}
eMMC 요청 처리 파이프라인 (일반 vs CQE) User: read()/write() VFS ext4 / f2fs submit_bio() blk-mq: I/O 스케줄링 → dispatch → mmc_blk_mq_issue_rq() CQE? No mmc_blk_mq_issue_rw_rq() mmc_start_request() sdhci_request() → 레지스터 기록 Yes mmc_blk_cqe_issue_rw_rq() cqhci_request() → Task Desc CQE HW: Doorbell → Auto CMD eMMC Hardware CMD + DAT 전송 → NAND R/W → 응답/데이터 반환 IRQ → sdhci_irq() / cqhci_irq() → mmc_request_done() → blk-mq 완료
CQE 비활성화 구간: CMDQ 모드에서 RPMB 접근, eMMC sleep, 파티션 전환 등의 특수 작업을 수행할 때는 일시적으로 CQE를 비활성화(cqhci_suspend())하고 일반 CMD 경로로 전환해야 합니다. 커널은 mmc_blk_cqe_recovery()에서 이를 자동으로 처리하지만, 이 과정에서 일시적인 성능 저하가 발생할 수 있습니다.
Packed Command (eMMC 4.5+): CMDQ 이전의 eMMC 버전에서는 Packed Command 기능으로 여러 읽기/쓰기 요청을 하나의 다중 블록 전송에 패킹하여 커맨드 오버헤드를 줄일 수 있었습니다. 그러나 CMDQ가 이를 완전히 대체하므로, 커널에서 Packed Command 지원은 CONFIG_MMC_BLOCK_PACKED로 별도 활성화해야 하며 기본 비활성입니다.

버스 모드 튜닝

eMMC는 세대가 발전하면서 버스 인터페이스 속도가 비약적으로 향상되었습니다. 호스트 컨트롤러는 카드의 CARD_TYPE(EXT_CSD[196]) 필드를 읽어 지원 가능한 최고 속도 모드를 협상하고, 해당 모드에 맞는 타이밍 튜닝을 수행합니다.

eMMC 버스 속도 모드 비교
모드 클럭 버스 폭 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)을 결정합니다.

  1. 호스트가 CMD21을 전송하면 카드는 미리 정의된 튜닝 데이터 패턴(4-bit: 64바이트, 8-bit: 128바이트)을 응답합니다.
  2. 호스트 컨트롤러가 내부 딜레이 라인의 위상을 0°부터 360° 범위로 순차적으로 변경하면서 CMD21을 반복 전송합니다.
  3. 각 위상에서 수신 데이터를 기대 패턴과 비교하여 성공/실패를 기록합니다.
  4. 연속 성공 구간(pass window)의 중앙점을 최적 샘플링 포인트로 선택합니다.
  5. 선택된 위상을 호스트 컨트롤러에 설정하여 이후 모든 데이터 전송에 사용합니다.
/* 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 기반 튜닝이 불필요합니다. 이는 부팅 시간 단축과 안정성 향상에 크게 기여합니다.

HS400 전환 시퀀스: HS400 모드로 전환하려면 반드시 HS200에서 먼저 튜닝을 완료한 후, HS200 → High Speed → HS400 순서로 단계적으로 전환해야 합니다. 이는 JEDEC 스펙에서 요구하는 필수 절차이며, 커널의 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);  /* 즉시 재튜닝 요청 */
HS200 (SDR) vs HS400 (DDR + Data Strobe) 타이밍 비교 HS200 (SDR, 200 MHz) — 상승 에지에서만 샘플링 CLK DAT D0 D1 D2 D3 D4 D5 D6 D7 ● = 샘플링 포인트 (상승 에지) HS400 (DDR, 200 MHz) — DS(Data Strobe) 에지에서 샘플링 CLK DAT D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 D10 D11 D12 D13 D14 D15 DS ● = DS 에지 샘플링 (양쪽 에지) ━ = Data Strobe 신호
재튜닝(Re-tuning): 온도 변화, 전압 변동 등으로 인해 최적 샘플링 포인트가 변할 수 있습니다. SDHCI 스펙은 Timer Count 레지스터를 통해 주기적 재튜닝을 지원하며, 커널은 mmc_retune_timer를 사용하여 자동으로 재튜닝을 수행합니다. 또한 CRC 에러 발생 시 즉시 재튜닝을 트리거할 수 있습니다(mmc_retune_needed()).

EXT_CSD 레지스터

EXT_CSD(Extended CSD)는 eMMC 카드의 확장 설정 레지스터로, 총 512바이트 크기입니다. 기존 CSD 레지스터(128비트)로는 표현할 수 없는 eMMC 고유 기능들의 설정과 상태 정보를 담고 있으며, CMD8 (SEND_EXT_CSD)을 통해 읽을 수 있습니다.

레지스터 구조

EXT_CSD는 두 영역으로 나뉩니다:

EXT_CSD 주요 필드 요약
바이트 오프셋 필드명 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];
}
EXT_CSD 레지스터 맵 (512 바이트) Properties 영역 (바이트 192 ~ 511) — 읽기 전용 [192] EXT_CSD_REV [194] CSD_STRUCTURE [196] CARD_TYPE [212-215] SEC_COUNT [307] CMDQ_DEPTH [249-252] CACHE_SIZE [254-261] FW_VERSION [267] PRE_EOL_INFO [268-269] LIFE_TIME_EST A/B [168] RPMB_SIZE_MULT [503] SUPPORTED_MODES ... 기타 읽기 전용 필드 ── 바이트 191/192 경계 ── Modes 영역 (바이트 0 ~ 191) — 읽기/쓰기 (CMD6 SWITCH) [185] HS_TIMING [183] BUS_WIDTH [179] PARTITION_CONFIG [177] BOOT_BUS_CONDITIONS [162] RST_N_FUNCTION [163] BKOPS_EN [33] CACHE_CTRL [15] CMDQ_MODE_EN 읽기 전용 (Properties) 읽기/쓰기 (Modes) 수명/건강 정보 보안/특수 기능 바이트 0 바이트 511 192 (Properties/Modes 경계)
sysfs를 통한 EXT_CSD 정보: 커널은 주요 EXT_CSD 정보를 sysfs로 노출합니다. /sys/class/mmc_host/mmc0/mmc0:0001/ 디렉토리에서 life_time, pre_eol_info, fwrev, hwrev, csd 등의 파일을 직접 읽을 수 있어, mmc-utils 없이도 기본 정보를 확인할 수 있습니다.

eMMC 파티션 관리

eMMC는 소프트웨어 파티션 테이블(GPT/MBR)과 별개로, 하드웨어 수준의 물리 파티션을 내장하고 있습니다. 각 파티션은 독립적인 주소 공간과 접근 제어를 가지며, Linux 커널은 이를 별도의 블록 디바이스로 노출합니다.

eMMC 하드웨어 파티션 유형
파티션 유형 기본 크기 접근 방법 디바이스 노드 설명
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 레지스터는 부트 파티션 활성화와 접근 대상 파티션을 제어합니다:

# 부트 파티션 설정: 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)을 방지합니다.

# 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 -
주의: RPMB 키는 한 번 프로그래밍하면 변경이나 삭제가 불가능합니다. 잘못된 키를 설정하면 해당 RPMB 파티션은 영구적으로 사용 불가 상태가 됩니다. 프로덕션 환경에서는 키 관리에 특별히 주의해야 합니다.

부트 파티션 쓰기

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
eMMC 하드웨어 파티션 레이아웃 eMMC 칩 (JEDEC e.MMC 5.1) User Data Area (UDA) /dev/mmcblk0 GPT/MBR 파티션 테이블 → /dev/mmcblk0p1, p2, ... Enhanced Area (SLC, 선택적) Boot Area 1 /dev/mmcblk0boot0 Boot Area 2 /dev/mmcblk0boot1 RPMB (Replay Protected Memory Block) /dev/mmcblk0rpmb — HMAC-SHA256 인증 GP1 /dev/mmcblk0gp0 GP2 /dev/mmcblk0gp1 GP3 /dev/mmcblk0gp2 GP4 /dev/mmcblk0gp3 GP 파티션은 선택적이며, 공장 설정 시 1회만 생성 가능 EXT_CSD[179] PARTITION_CONFIG: BOOT_ACK[6] | BOOT_PARTITION_ENABLE[5:3] | PARTITION_ACCESS[2:0] 접근 대상 파티션 전환 시 CMD6 SWITCH 사용 → 커널 자동 관리
디바이스 노드 네이밍: eMMC가 시스템 첫 번째 MMC 장치인 경우 mmcblk0이지만, SD 카드 슬롯이 먼저 probe되면 mmcblk1이 될 수 있습니다. 안정적인 참조를 위해 /dev/disk/by-path/ 또는 /dev/disk/by-partuuid/ 심볼릭 링크를 사용하는 것이 권장됩니다.

데이터 무결성

eMMC 프로토콜은 여러 계층에서 데이터 무결성을 보장하는 메커니즘을 제공합니다. CRC 보호부터 안정적인 쓰기, 캐시 관리, 백그라운드 작업까지 다양한 기능이 데이터 손실을 방지하고 스토리지 성능을 유지합니다.

CRC 보호

eMMC 프로토콜은 모든 전송에 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는 세 가지 수준의 쓰기 보호를 제공합니다:

/* 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-on Write Protection을 사용하고, Permanent Write Protection은 절대적으로 필요한 경우에만 사용하세요. 또한 RPMB를 활용하여 보안이 필요한 키, 인증서 등을 저장하면 소프트웨어 수준의 쓰기 보호보다 강력한 보안을 확보할 수 있습니다.

전원 관리

eMMC의 전원 관리는 카드 상태 머신, 전압 제어, Linux PM 프레임워크가 통합되어 동작합니다. 임베디드 시스템에서 전력 소비 최적화는 배터리 수명에 직결되므로, eMMC의 전원 상태를 적절히 관리하는 것이 중요합니다.

eMMC 카드 상태

eMMC 카드는 MMC 프로토콜에 따라 다음 상태들을 갖습니다:

eMMC 카드 상태 머신
상태 약자 설명 전환 커맨드
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의 전원 관련 전환 시퀀스는 다음과 같습니다:

  1. Power-off → Pre-idle: VCC 인가 후 CMD0 전송
  2. Pre-idle → Idle: CMD0 수신, 내부 레지스터 초기화
  3. 활성 → Sleep: CMD7 deselect → CMD5 (Sleep) — VCC 유지, VCCQ 차단 가능
  4. Sleep → Power-off: VCC 차단 (VCCQ는 이미 차단되었거나 함께 차단)
  5. Sleep → 활성: CMD5 (Awake) → CMD7 select

전압 제어

eMMC는 두 가지 전원 레일을 사용합니다:

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); */
Power-off Notification과 데이터 안전: 시스템 셧다운 시 mmc_poweroff_notify()로 카드에 전원 차단을 사전 통지하면, 카드가 내부 캐시를 플러시하고 메타데이터를 안전하게 저장합니다. 급격한 전원 차단(unexpected power loss)은 데이터 손실의 주요 원인이므로, 배터리 기반 시스템에서는 저전압 감지 시 즉시 POWER_OFF_SHORT를 발행하는 것이 권장됩니다.
eMMC 전원 상태 및 카드 상태 전이 Power Off VCC 인가 Pre-idle CMD0 Idle CMD1 Ready CMD2 Ident CMD3 Standby CMD7 Transfer (tran) CMD17/18 Data (읽기) CMD24/25 Rcv (쓰기) Prg (NAND 기록) 완료 Sleep CMD5 (Standby→Sleep) CMD5 (Awake) VCC 차단 Inactive CMD15 전원 레일 VCC (vmmc): NAND 전원 3.3V VCCQ (vqmmc): I/O 전원 1.8V/3.3V Sleep: VCC 유지, VCCQ 차단 가능 mmc_power_up() → 초기화 → mmc_sleep() ↔ mmc_power_off() — Linux 커널 전원 관리 경로

인라인 암호화

최신 eMMC 호스트 컨트롤러는 ICE(Inline Crypto Engine)를 내장하여, 호스트 컨트롤러와 eMMC NAND 사이의 데이터 경로에서 하드웨어 암호화/복호화를 수행합니다. 이를 통해 CPU 부하 없이 투명한 디스크 암호화를 실현할 수 있으며, Android의 FBE(File-Based Encryption)와 메타데이터 암호화에 핵심적으로 활용됩니다.

Inline Crypto Engine (ICE) 아키텍처

ICE는 호스트 컨트롤러 내부에 위치하며, 블록 I/O가 eMMC에 도달하기 전에 암호화/복호화를 수행합니다. 핵심 구성 요소:

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
Android FBE/메타데이터 암호화: Android 기기에서는 /data 파티션에 FBE(fscrypt)를, /metadata에 dm-default-key를 사용한 메타데이터 암호화를 적용합니다. ICE 지원 eMMC 호스트에서는 이 모든 암호화가 하드웨어에서 투명하게 처리되어 CPU 오버헤드가 거의 없습니다.
인라인 암호화(ICE) 데이터 경로 Filesystem ext4 / f2fs + fscrypt Block Layer bio + blk_crypto_key blk-crypto 키슬롯 관리 HW 지원 확인 → 키 할당 HW 지원 → CQHCI crypto SW Fallback (미지원 시) blk-crypto-fallback (CPU) eMMC 호스트 컨트롤러 (SDHCI / CQHCI) ICE (Inline Crypto Engine) 키슬롯 테이블 Slot 0: [AES-256-XTS key₀] Slot 1: [AES-256-XTS key₁] Slot N: [미사용] 암호화된 데이터 eMMC NAND Flash 암호문(Ciphertext) 저장 쓰기: 평문 → ICE 암호화 → NAND 읽기: NAND → ICE 복호화 → 평문 Task Descriptor crypto_enable = 1 keyslot + DUN (IV)
키 관리 주의: ICE 키슬롯에 프로그래밍된 키는 DRAM에 평문으로 존재하는 시간을 최소화해야 합니다. 키 사용이 끝나면 즉시 keyslot_evict()를 호출하여 하드웨어에서 제거하고, memzero_explicit()으로 메모리에서 안전하게 삭제해야 합니다. TrustZone/TEE를 통한 키 생성 및 주입이 가장 안전한 방식입니다.

디바이스 트리 바인딩

ARM/ARM64 기반 임베디드 시스템에서 eMMC 호스트 컨트롤러는 디바이스 트리(Device Tree)를 통해 하드웨어 설정을 기술합니다. 올바른 DT 바인딩은 eMMC 초기화, 속도 모드 협상, 전원 관리의 기반이 됩니다.

표준 mmc-controller 바인딩 속성

eMMC 디바이스 트리 바인딩 속성
속성 타입 필수/선택 설명
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>;
        };
    };
};
흔한 DT 실수:
  • 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 프로빙으로 초기화 지연이 발생합니다.
DT 디버깅: 부팅 후 실제 적용된 속도 모드를 확인하려면 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 호스트 컨트롤러의 런타임 속성을 제어하는 경로입니다. 호스트 컨트롤러 드라이버에 따라 제공되는 속성이 다를 수 있습니다.

MMC 호스트 sysfs 속성
속성 파일권한설명
rescanW1을 쓰면 카드 재탐지를 트리거합니다. eMMC(non-removable)에서는 일반적으로 불필요하나, 디버깅 시 초기화를 재시도할 때 유용합니다.
clk_gate_delayR/W클럭 게이팅 지연 시간(ms). 마지막 요청 후 이 시간이 지나면 호스트 클럭을 꺼서 전력을 절약합니다. 값이 너무 작으면 빈번한 on/off로 오히려 오버헤드가 증가합니다.
clkgate_enableR/W클럭 게이팅 활성화 여부(0/1). 디버깅 시 0으로 설정하여 클럭 관련 문제를 배제할 수 있습니다.

MMC 디바이스 속성: /sys/bus/mmc/devices/mmc0:0001/

개별 eMMC 카드의 고유 정보를 읽을 수 있는 경로입니다. CID, CSD, EXT_CSD에서 추출한 정보가 사람이 읽을 수 있는 형태로 제공됩니다.

MMC 디바이스 sysfs 속성 (mmc0:0001)
속성 파일타입설명
type문자열장치 타입 (예: MMC)
name문자열제품 이름 (CID의 PNM 필드, 6바이트 ASCII)
serial16진수시리얼 번호 (CID의 PSN 필드, 32-bit)
dateMM/YYYY제조 날짜 (CID의 MDT 필드)
manfid16진수제조사 ID (예: 0x000015 = Samsung, 0x000011 = Toshiba, 0x000045 = SanDisk)
oemid16진수OEM/Application ID
hwrev16진수하드웨어 리비전 (CID의 PRV 상위 4비트)
fwrev16진수펌웨어 리비전 (CID의 PRV 하위 4비트)
cid16진수CID 레지스터 원시값 (128-bit)
csd16진수CSD 레지스터 원시값 (128-bit)
eol_info16진수PRE_EOL_INFO — 장치 수명 잔여 상태 (EXT_CSD[267])
life_time16진수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 값의 의미는 다음과 같습니다.

life_time 값 해석 (DEVICE_LIFE_TIME_EST)
수명 소진 비율상태
0x00정보 없음미지원 또는 미정의
0x010% ~ 10%정상 (신품)
0x0210% ~ 20%정상
0x0320% ~ 30%정상
0x0430% ~ 40%정상
0x0540% ~ 50%주의 시작
0x0650% ~ 60%주의
0x0760% ~ 70%주의
0x0870% ~ 80%경고
0x0980% ~ 90%경고 — 교체 계획 수립 필요
0x0A90% ~ 100%위험 — 즉시 교체 권장
0x0B수명 초과수명 완전 소진, 데이터 손실 위험
pre_eol_info 값 해석 (PRE_EOL_INFO)
의미
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)"
자동화 모니터링: 위 스크립트를 cron에 등록하거나 systemd timer로 주기적 실행하면, eMMC 수명이 임계치(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

커널 로그 메시지 해석

주요 MMC 커널 로그 메시지 및 해결 방법
메시지 패턴의미해결 방법
mmc0: error -110 whilst initialising MMC card초기화 타임아웃 (-ETIMEDOUT)클럭 주파수 낮추기, 전원 공급 확인, DT 설정 검증
mmc0: tuning execution failedHS200/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 cardHigh Speed(52MHz)로 초기화됨HS200/HS400이 기대였다면 DT 속성과 vqmmc 확인
mmc0: switch to bus width 8 failed8-bit 버스 전환 실패DAT4-DAT7 라인 연결 확인, DT bus-width 설정
mmc0: unrecognised SCR structure versionSD 카드용 명령을 eMMC에 전송DT에 no-sd 속성 추가
mmcblk0: error -84 sending status commandEILSEQ — 데이터 무결성 오류튜닝 파라미터 조정, 속도 모드 다운그레이드
mmc0: Timeout waiting for hardware interruptSDHCI 인터럽트 미수신인터럽트 라인 연결 확인, 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개까지 묶기 가능

성능 튜닝 체크리스트

eMMC 성능 튜닝 체크리스트
항목명령/설정기대 효과
최대 속도 모드 협상cat /sys/kernel/debug/mmc0/iosHS400/HS400ES 확인 → 최대 대역폭
8-bit 버스 폭DT: bus-width = <8>1-bit 대비 8배 대역폭
CMDQ 활성화dmesg | grep -i cmdq랜덤 I/O 성능 2~3배 향상
I/O 스케줄러echo none > .../schedulerCMDQ 환경에서 오버헤드 제거
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-suspendresume 시 재초기화 방지

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 vs UFS 아키텍처 비교 eMMC (병렬 버스, 반이중) AP (Host) SDHCI Controller 8-bit 병렬 버스 CLK + CMD + DAT[7:0] eMMC Device eMMC Controller + NAND Flash (단일 패키지) 최대 400MB/s (HS400) Half-duplex JEDEC eMMC 표준 UFS (직렬 레인, 전이중) AP (Host) UFS Host Controller MIPI M-PHY 2 Lanes (TX+RX) UniPro 전송 계층 UFS Device UFS Controller (SCSI) LU 0 LU 1 LU 2 최대 4.6GB/s (UFS 4.0) Full-duplex / Multi-LU JEDEC UFS + SCSI 호스트 장치 인터페이스 논리 유닛

성능 비교

eMMC vs UFS 성능 비교
항목eMMC 5.1UFS 2.1UFS 3.1UFS 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 vs UFS 기능 비교
기능eMMC 5.1UFS 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)
RPMBv2 (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 notificationActive/Idle/Sleep/PowerDown + Hibernate

선택 기준

eMMC vs UFS 선택 가이드
기준eMMC 선택UFS 선택
비용저비용 우선 (IoT, 저가형 기기)성능이 비용보다 중요한 경우
용량4GB ~ 128GB 범위32GB ~ 1TB 이상
성능 요구순차 250MB/s 이하면 충분고성능 앱 로딩, 4K 영상 녹화
전력단순 전력 관리로 충분세밀한 전력 상태 제어 필요
응용웨어러블, IoT, 셋톱박스, 저가 스마트폰플래그십 스마트폰, 태블릿, 자동차

마이그레이션 고려사항

eMMC에서 UFS로 전환할 때 주요 변경 사항:

UFS 심화 문서: UFS 아키텍처, MIPI M-PHY/UniPro, UPIU 프로토콜, UFSHCI, ufshcd 드라이버, MCQ, Write Booster, HPB, 인라인 암호화, 전원 관리 등을 자세히 알고 싶다면 UFS (Universal Flash Storage) 심화 문서를 참조하세요.

자주 겪는 문제와 해결

eMMC 개발 및 운영에서 자주 겪는 문제와 체계적인 해결 방법을 정리합니다. 각 문제에 대해 증상, 원인, 해결 방법, 진단 명령을 포함합니다.

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/OeMMC 위의 블록 레이어, I/O 스케줄러, bio/request 처리
SATA/AHCISATA/AHCI또 다른 블록 스토리지 서브시스템 비교 참고
NVMeNVMePCIe 기반 플래시 스토리지, 커맨드 큐 비교
F2FS 파일시스템F2FSeMMC/UFS에 최적화된 플래시 전용 파일시스템
디바이스 트리디바이스 트리eMMC DT 바인딩, 핀 설정, 전원 시퀀스
DMADMAADMA2 디스크립터, DMA 매핑, scatter-gather
Android 커널AndroidAndroid 환경에서의 eMMC 활용, 보안 부팅
MTD/플래시MTDRaw NAND와의 비교, FTL 개념 이해

eMMC 서브시스템을 소스 코드 레벨에서 이해하려면 다음 순서로 읽는 것을 권장합니다.

  1. include/linux/mmc/host.hstruct mmc_host 정의, 호스트 ops
  2. include/linux/mmc/card.hstruct mmc_card 정의, 카드 속성
  3. include/linux/mmc/mmc.h — MMC 커맨드/레지스터 상수 정의
  4. drivers/mmc/core/core.c — MMC 코어 초기화, 전원/클럭 관리
  5. drivers/mmc/core/mmc.c — eMMC 카드 초기화 시퀀스
  6. drivers/mmc/core/mmc_ops.c — 개별 MMC 커맨드 실행 함수
  7. drivers/mmc/core/block.c — 블록 디바이스 인터페이스 (mmcblk)
  8. drivers/mmc/core/queue.c — 블록 요청 큐 처리
  9. drivers/mmc/host/sdhci.c — SDHCI 공통 드라이버
  10. drivers/mmc/host/sdhci-of-arasan.c — 플랫폼별 SDHCI 예시 (Arasan)
  11. drivers/mmc/host/cqhci.c — CMDQ 호스트 컨트롤러 인터페이스
학습 시작점: eMMC를 처음 접한다면 drivers/mmc/core/mmc.cmmc_init_card() 함수부터 읽으세요. 카드 초기화의 전체 흐름 — 전원 인가 → OCR 협상 → CID/CSD/EXT_CSD 읽기 → 버스 폭/속도 모드 전환 —을 하나의 함수에서 순차적으로 따라갈 수 있습니다.