UFS (Universal Flash Storage) — MIPI M-PHY/UniPro & SCSI Protocol

UFS(Universal Flash Storage)는 JEDEC JESD220 표준으로 정의된 고성능 플래시 스토리지 인터페이스입니다. 플래그십 스마트폰, 태블릿, 자동차 인포테인먼트, 임베디드 시스템에서 핵심 저장 장치로 사용되며, MIPI M-PHY 물리 계층과 UniPro 전송 프로토콜 위에 SCSI 명령 체계를 운용합니다. 이 문서에서는 UFS 아키텍처, UFSHCI(UFS Host Controller Interface), ufshcd 호스트 드라이버, MCQ(Multi-Circular Queue), Write Booster, HPB(Host Performance Booster), 인라인 암호화(Encryption)(ICE), RPMB, 전원 관리(Power Management), 디버깅(Debugging), 성능 튜닝까지 Linux 커널의 UFS 서브시스템(drivers/ufs/) 전체를 심층 분석합니다.

전제 조건: 블록 I/O 문서와 SCSI 서브시스템 문서를 먼저 읽으세요. UFS는 SCSI 프로토콜을 사용하므로 SCSI mid-layer 이해가 필수적입니다. 디바이스 트리(Device Tree) 지식이 있으면 플랫폼 바인딩 섹션을 더 쉽게 이해할 수 있습니다.
일상 비유: UFS는 전용 고속도로에 비유할 수 있습니다. eMMC가 반이중(half-duplex) 통신으로 한 번에 한 방향만 데이터를 전송하는 “왕복 1차선 지방도로”라면, UFS는 전이중(full-duplex) 직렬 링크로 양방향 동시 전송이 가능한 “전용 고속도로”입니다. 또한 SCSI 명령 큐잉을 지원하여 여러 I/O 요청을 동시에 처리할 수 있으므로, 마치 고속도로에 여러 차선이 있어 다수의 차량이 동시에 달리는 것과 같습니다.

핵심 요약

  • UFS 스택 — MIPI M-PHY + UniPro + SCSI 조합으로 구성된 전이중(full-duplex) 직렬 스토리지 인터페이스입니다.
  • ufshcd 드라이버 — drivers/ufs/의 ufshcd가 UFSHCI 레지스터, UTRL/UTMRL, 클럭·전원을 관리합니다.
  • SCSI 통합 — 각 UFS Logical Unit이 SCSI 디바이스(/dev/sda)로 노출되고 Well-Known LU는 BSG로 접근합니다.
  • 성능 확장 — UFS 4.0의 MCQ(Multi-Circular Queue)와 Write Booster/HPB로 처리량(Throughput)과 지연을 개선합니다.
  • 보안·전원 — 인라인 암호화(ICE), RPMB, DeepSleep과 런타임 PM이 모바일/차량 시나리오에서 중요합니다.

단계별 이해

  1. 블록 레이어 진입
    VFS·파일시스템을 거친 요청이 blk-mq → SCSI mid-layer → ufshcd_queuecommand()로 이어집니다.
  2. UPIU·UTRD 구성
    SCSI CDB를 Command UPIU로 변환하고 UCD·UTRD·PRDT를 DMA 접근 가능한 버퍼에 배치합니다.
  3. Doorbell 전송
    UTRLDBR에 태그 비트를 세팅해 UFSHCI가 UTRD를 DMA로 가져가도록 지시합니다.
  4. M-PHY/UniPro 전송
    호스트 컨트롤러가 UniPro 프레임으로 캡슐화해 디바이스에 전달하고, 디바이스가 NAND I/O를 수행합니다.
  5. 응답 처리
    디바이스가 Response UPIU를 돌려주면 ufshcd_intr() → ufshcd_transfer_req_compl()가 scsi_done()으로 완료를 올립니다.

UFS 주요 개념과 구조체

UFS(Universal Flash Storage)는 JEDEC JESD220 표준에 의해 정의된 고성능 임베디드 스토리지 인터페이스입니다. MIPI M-PHY 물리 계층(최대 2레인, UFS 4.x 기준 레인 당 최대 23.2Gbps, UFS 5.0에서는 약 46.7Gbps까지 확장)과 UniPro 전송 프로토콜을 기반으로 하며, 상위 응용 계층에서 SCSI 명령 체계를 사용합니다. 전이중(full-duplex) 직렬 통신으로 eMMC 대비 수 배 높은 대역폭(Bandwidth)과 낮은 지연(Latency) 시간을 제공합니다. Linux 커널에서는 drivers/ufs/ 디렉터리의 ufshcd 드라이버가 UFS 호스트 컨트롤러를 관리합니다.
UFS vs eMMC vs NVMe vs SATA 비교
항목UFSeMMCNVMeSATA
인터페이스직렬 MIPI M-PHY (1~2 레인)병렬 8-bit MMC직렬 PCIe (x1~x4)직렬 SATA (1 레인)
최대 속도~4.2 GB/s (UFS 4.x, 2L HS-G5, JEDEC 공칭)400 MB/s (HS400)~14 GB/s (PCIe 5.0 x4)600 MB/s (SATA III)
프로토콜SCSI (full-duplex)MMC (half-duplex)NVMe (full-duplex)ATA/AHCI (half-duplex)
큐 깊이32 (SDB) / MCQ 가변32 (CMDQ)최대 6553532 (NCQ)
폼 팩터BGA (11.5x13mm)BGA (11.5x13mm)M.2 / U.2 / AIC2.5" / M.2
디바이스 노드/dev/sda/dev/mmcblk0/dev/nvme0n1/dev/sda
전형적 용도플래그십 스마트폰, 자동차IoT, 미드레인지 폰PC, 서버데스크톱, NAS
전이중 통신지원미지원지원미지원
JEDEC UFS 스펙 버전 변천
스펙 버전발표표준 문서M-PHY/UniPro최대 Gear최대 라인 레이트 (2L)주요 특징
UFS 1.0 (JESD220)2011JESD220M-PHY v1.x / UniPro v1.40PWM-G1~3 MB/s초기 규격, MIPI M-PHY + UniPro + SCSI 기반 아키텍처 정의
UFS 1.1 (JESD220A)2012JESD220AM-PHY v1.8 / UniPro v1.41HS-G1~364 MB/sHS(High Speed) Gear 도입, Boot LU 지원
UFS 2.0 (JESD220B)2013JESD220BM-PHY v3.0 / UniPro v1.60HS-G3~1.46 GB/sHS-G3(5.83 Gbps/lane), 2레인 지원, RPMB(Replay Protected Memory Block)
UFS 2.1 (JESD220C)2016JESD220CM-PHY v3.1 / UniPro v1.61HS-G3~1.46 GB/s보안 강화, DeepSleep, 인라인 암호화 옵션
UFS 3.0 (JESD220D)2018년 1월JESD220D / JESD223BM-PHY v4.1 / UniPro v1.80HS-G4~2.92 GB/sHS-G4(11.6 Gbps/lane), Write Booster, HPB(Host Performance Booster)
UFS 3.1 (JESD220E)2020년 1월JESD220E / JESD223CM-PHY v4.1 / UniPro v1.80HS-G4~2.92 GB/sWrite Booster 필수화, HPB 표준화, DeepSleep 개선
UFS 4.0 (JESD220F)2022년 8월JESD220F / JESD223EM-PHY v5.0 / UniPro v2.0HS-G5~5.83 GB/sHS-G5(23.2 Gbps/lane), MCQ(Multi-Circular Queue), 향상된 RPMB
UFS 4.1 (JESD220G)2025년 1월JESD220G / JESD223FM-PHY v5.0 / UniPro v2.0HS-G5~5.83 GB/sHost-Initiated Defragmentation, WriteBooster 버퍼 리사이즈·부분 플러시, Permanent Bootable LU, 벤더 명령 RPMB 인증, QLC 정밀도 개선
UFS 5.0 (JESD220H)2026년 초JESD220H / JESD223GM-PHY v6.0 / UniPro v3.0HS-G6~11.7 GB/sHS-G6(~46.7 Gbps/lane), Edge AI 워크로드 대응, 시퀀셜 최대 10.8 GB/s(JEDEC 공칭)

UFS 5.0(JESD220H/JESD223G) — 공표된 다음 세대 표준

JEDEC는 2026년 2월 26일 UFS 5.0(JESD220H)과 UFSHCI 5.0(JESD223G)을 발표했습니다. UFS 5.0은 UFS 4.x 하드웨어와의 호환성을 유지하면서 다음과 같은 확장을 담고 있습니다.

커널 지원 현황: 2026년 8월(커널 6.20 머지 윈도우) 기준 메인라인 ufshcd는 HS-G6/UFS 5.0을 아직 완전히 지원하지 않습니다. 다만 링크 이퀄라이제이션 인프라(적응형 TX EQ 모듈, tx_eq_hs_gearN debugfs, negotiate_pwr_mode() vops)가 먼저 병합되고 있어 향후 Gear 6 지원의 토대가 됩니다.
/dev/sdX 네이밍: UFS 디바이스는 SCSI 레이어를 사용하므로 /dev/sda, /dev/sdb 등으로 나타납니다. 각 UFS Logical Unit(LU)이 별도의 SCSI 디바이스로 등록되며, LU 0이 sda, LU 1이 sdb가 됩니다. Well-Known LU(Boot, RPMB, Device)는 BSG(Block SCSI Generic) 노드를 통해 접근합니다. ufs-bsg 디바이스(/dev/bsg/ufs-bsg0)로 UFS 고유 Query 명령을 전송할 수 있습니다.
/* UFS 핵심 구조체 관계 — drivers/ufs/core/ufshcd.c */
struct ufs_hba {
    struct Scsi_Host *host;          /* SCSI 호스트 어댑터 */
    void __iomem *mmio_base;         /* UFSHCI 레지스터 매핑 */
    struct utp_transfer_req_desc *utrdl_base_addr; /* UTRD 배열 */
    struct utp_task_req_desc *utmrdl_base_addr;    /* UTMRD 배열 */
    /* (커널 ≤6.18) struct ufshcd_lrb *lrb 배열 — 6.19부터 제거,
       LRB는 scsi_cmd_priv(cmd)로 각 scsi_cmnd에 직접 부착 */
    const struct ufs_hba_variant_ops *vops; /* 벤더 콜백 */
    struct ufs_dev_info dev_info;    /* 디바이스 정보 */
    enum ufs_pm_level rpm_lvl;       /* 런타임 PM 레벨 */
    enum ufs_pm_level spm_lvl;       /* 시스템 PM 레벨 */
    u32 capabilities;                /* 호스트 역량 비트맵 */
    unsigned int nutrs;              /* UTP Transfer Request 슬롯 수 */
    unsigned int nutmrs;             /* UTP Task Management 슬롯 수 */
    /* ... */
};
# UFS 디바이스 확인 — 일반 LU는 LUN 0부터, W-LU는 큰 LUN 번호로 보인다
$ lsscsi
[0:0:0:0]     disk SAMSUNG KLUDG4U1EA-B0C1 0200 /dev/sda    ← 일반 LU 0
# 그 외 W-LU도 별도 SCSI 디바이스로 등록된다:
#   [0:0:0:49456] ← Boot W-LU (UPIU W-LUN 0xB0 → SCSI LUN 0xC130)
#   [0:0:0:49472] ← RPMB W-LU (0xC4 → 0xC140)
#   [0:0:0:49488] ← Device W-LU (0xD0 → 0xC150)

# UFS sysfs 정보 확인
$ cat /sys/bus/platform/drivers/ufshcd-qcom/*/device_descriptor/manufacturer_name
SAMSUNG

# UFS 일반 LU의 SCSI 디바이스 속성 확인
$ cat /sys/class/scsi_device/0:0:0:0/device/vendor
SAMSUNG

UFS I/O 처리 흐름 상세

UFS I/O는 Linux 블록 레이어에서 시작하여 SCSI mid-layer, ufshcd 드라이버, UFSHCI 하드웨어, M-PHY/UniPro 링크를 거쳐 UFS 디바이스에 도달합니다. 아래 5단계로 전체 흐름을 요약합니다.
  1. 블록 레이어 → SCSI mid-layer → ufshcd_queuecommand
    사용자 공간(User Space)의 read()/write() 호출이 VFS, 파일시스템(Filesystem)을 거쳐 블록 레이어의 struct request로 변환됩니다. blk-mq가 이를 SCSI mid-layer에 전달하면, scsi_queue_rq()가 struct scsi_cmnd를 생성하고 UFS 호스트 드라이버의 ufshcd_queuecommand()을 호출합니다.
    /* SCSI mid-layer → UFS 호스트 드라이버 진입점 (커널 6.19+ 기준, 단순화) */
    static enum scsi_qc_status ufshcd_queuecommand(struct Scsi_Host *host,
                                                   struct scsi_cmnd *cmd)
    {
        struct ufs_hba *hba = shost_priv(host);
        struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba,
                                            scsi_cmd_to_rq(cmd)); /* SDB면 NULL */
        int tag = scsi_cmd_to_rq(cmd)->tag;
        ufshcd_setup_scsi_cmd(hba, cmd,
                              ufshcd_scsi_to_upiu_lun(cmd->device->lun), tag);
            /* ├─ ufshcd_init_lrb(cmd): LRB 초기화 (lrbp = scsi_cmd_priv(cmd)) */
            /* ├─ ufshcd_prepare_lrbp_crypto(): blk-crypto 컨텍스트 연결 */
            /* └─ ufshcd_comp_scsi_upiu(hba, cmd): Command UPIU 구성 */
        ufshcd_send_command(hba, tag, hwq);   /* doorbell ring (SDB: UTRLDBR) */
        return 0;
    }
  2. UPIU(UFS Protocol Information Unit) 및 UTRD 구성
    ufshcd_comp_scsi_upiu() 내부의 ufshcd_prepare_utp_scsi_cmd_upiu()가 SCSI CDB(Command Descriptor Block)를 Command UPIU 형식으로 변환합니다. Command UPIU(12바이트 헤더 + EDTL + CDB)를 UCD(UTP Command Descriptor) 내에 배치하고, 데이터 전송이 필요한 경우 ufshcd_prepare_req_desc_hdr()이 데이터 방향·암호화 설정을 담은 UTRD(UTP Transfer Request Descriptor) 헤더를 채우고, PRDT(Physical Region Descriptor Table)를 scatter-gather 리스트로부터 구성합니다.
  3. UFSHCI DMA 매핑(Mapping) 및 Doorbell 링
    UTRD와 UCD가 DMA 접근 가능한 메모리에 준비되면, ufshcd_send_command()가 UFSHCI의 UTRLDBR(UTP Transfer Request List DoorBell Register)에 해당 태그 비트를 설정합니다. 호스트 컨트롤러 하드웨어는 이를 감지하여 DMA로 UTRD를 읽어가고, UCD 내의 Command UPIU를 추출합니다.
    /* Doorbell 링 — SDB(Single DoorBell) 모드에서 UFSHCI에 전송 시작 알림 */
    void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag,
                             struct ufs_hw_queue *hwq)
    {
        if (hwq) {
            /* MCQ 모드: 해당 HW 큐의 SQ Tail Doorbell에 직접 기록 */
            ufshcd_writel(hba, task_tag, hwq->sq_sqctr_db);
        } else {
            /* SDB 모드: 공용 UTRLDBR의 태그 비트 설정 */
            ufshcd_writel(hba, 1 << task_tag,
                          REG_UTP_TRANSFER_REQ_DOOR_BELL);
        }
        /* 하드웨어가 해당 슬롯의 UTRD/UCD를 DMA로 fetch */
    }
  4. M-PHY/UniPro 전송 및 디바이스 처리
    UFSHCI 하드웨어는 Command UPIU를 UniPro 프레임으로 캡슐화(Encapsulation)하고 M-PHY 물리 계층을 통해 UFS 디바이스에 전송합니다. 디바이스는 UPIU를 수신하여 SCSI 명령을 해석하고, NAND 플래시에 대한 읽기/쓰기를 수행합니다. 쓰기 요청의 경우, 디바이스는 먼저 RTT(Ready To Transfer) UPIU를 보내 호스트에 데이터 전송을 요청합니다.
  5. 응답 UPIU 수신 및 SCSI 완료
    디바이스가 명령 처리를 완료하면 Response UPIU를 호스트에 전송합니다. UFSHCI가 이를 수신하여 UCD의 Response UPIU 영역에 기록하고, 인터럽트(Interrupt)를 발생시킵니다. ufshcd_intr() → ufshcd_transfer_req_compl()가 호출되어 완료된 태그의 Response UPIU 상태를 확인하고, scsi_done()으로 SCSI 레이어에 완료를 알립니다.
    /* 인터럽트 핸들러 — 전송 완료 처리 */
    static irqreturn_t ufshcd_intr(int irq, void *__hba)
    {
        struct ufs_hba *hba = __hba;
        u32 intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
    
        if (intr_status & UTP_TRANSFER_REQ_COMPL)
            ufshcd_transfer_req_compl(hba);
    
        if (intr_status & UTP_TASK_REQ_COMPL)
            ufshcd_tmc_handler(hba);
    
        return IRQ_HANDLED;
    }
SDB vs MCQ: 전통적 UFS(UFS 3.1 이하)는 SDB(Single DoorBell) 방식으로 하나의 UTRLDBR을 통해 최대 32개 슬롯을 관리합니다. UFS 4.0의 MCQ(Multi-Circular Queue)는 여러 개의 Submission/Completion Queue를 두어 NVMe와 유사한 다중 큐 아키텍처를 지원합니다. 이에 대해서는 후반부 MCQ 섹션에서 상세히 다룹니다.

UFS 아키텍처 개요

UFS 아키텍처는 크게 응용 계층(Application Layer), 전송 계층(UFS Transport Protocol, UTP), 인터커넥트 계층(UniPro), 물리 계층(MIPI M-PHY)의 4단계 프로토콜 스택으로 구성됩니다. 호스트 측과 디바이스 측이 각각 이 스택을 구현하며, M-PHY 직렬 링크를 통해 전이중 통신합니다.
UFS 프로토콜 스택 아키텍처 왼쪽 호스트 · 오른쪽 디바이스 — 아래로 갈수록 캡슐화되고, 실제로 오가는 것은 가운데 링크 하나뿐 UFS 호스트 UFS 디바이스 ① 응용 계층 (Application) SCSI 명령셋 · UFS Query/NOP ① 응용 계층 (Application) SCSI 타겟 · LU/속성 관리 ② 전송 계층 (UTP) UPIU 생성 · 큐잉 · UTRD/UCD ② 전송 계층 (UTP) UPIU 해석 · 응답 생성 ③ UniPro (L1.5~L4) CPort 멀티플렉싱 · AFC 흐름 제어 ③ UniPro (L1.5~L4) 동일 계층 구조 (미러) ④ 물리 계층 (MIPI M-PHY) HS/PWM-Gear · 8b10b 차동 전송 ④ 물리 계층 (MIPI M-PHY) 차동 트랜시버(TX/RX) + UPIU 헤더 + T_PDU (AFC/CRC) + 8b10b 심볼화 SCSI/UFS 명령 (논리 피어) UPIU 교환 T_PDU · 흐름 제어 실제 직렬 링크 (전이중) 점선 = 논리적 피어 통신(실제로는 모두 아래 링크로 캡슐화되어 전달) · 실선 = 유일한 물리 링크 호스트 구현 블록 디바이스 내부 ufshcd 드라이버 (SW) drivers/ufs/ UFSHCI 컨트롤러 (HW) UTRLDBR · DMA · 인터럽트 FTL + ECC 논리↔물리 주소 매핑 NAND 플래시 3D TLC/QLC UFSHCI는 UTP와 UniPro 동작을 하드웨어로 수행하며, ufshcd 드라이버가 레지스터와 DMA로 이를 구동합니다

UFS의 프로토콜 스택은 네트워크 OSI 모델과 유사한 계층 구조를 갖습니다. 최상위 응용 계층에서 SCSI 명령을 생성하고, UTP 계층이 이를 UPIU로 캡슐화하며, UniPro 계층이 전송을 관리하고, M-PHY 물리 계층이 실제 직렬 데이터를 전송합니다.

UFS 프로토콜 스택 각 계층의 역할
계층스펙주요 역할핵심 개념
Application (SAP)JESD220SCSI 명령 처리, UFS 명령(Query, NOP, Task Mgmt)LU, Descriptor, Attribute, Flag
UTP (Transport)JESD220UPIU 생성/파싱, 커맨드 큐잉UPIU Header, CDB, Data Segment, PRDT
UniPro L4 (Transport)MIPI UniProCPort 멀티플렉싱, 세그먼트 전송CPort, T_PDU, Connection ID
UniPro L3 (Network)MIPI UniProDevice ID 기반 라우팅(Routing)DeviceID, N_PDU
UniPro L2 (Data Link)MIPI UniPro흐름 제어(Flow Control), 오류 복구AFC(Adaptive Flow Control), CRC, TC(Traffic Class)
UniPro L1.5 (PHY Adapter)MIPI UniPro레인 병합, 기어/모드 설정PA_GEAR, PA_ActiveTxDataLanes, PA_PWRMode
M-PHY L1 (Physical)MIPI M-PHY직렬 데이터 전송, 클럭 복원HS-Gear, PWM-Gear, 8b10b/10b12b 인코딩

Linux 커널의 UFS 관련 소스 코드는 drivers/ufs/ 디렉터리에 집중되어 있습니다. 최근 메인라인 계열에서는 core, host 서브디렉터리로 구분됩니다.

# UFS 커널 소스 구조 (Linux 6.x)
drivers/ufs/
├── core/
│   ├── ufshcd.c            # UFS 호스트 컨트롤러 드라이버 코어 (메인)
│   ├── ufshcd-crypto.c     # 인라인 암호화 (ICE) 지원
│   ├── ufshcd-priv.h       # 내부 선언
│   ├── ufs-sysfs.c         # sysfs 인터페이스
│   ├── ufs-debugfs.c       # debugfs 인터페이스
│   ├── ufs-mcq.c           # Multi-Circular Queue 지원
│   ├── ufs-hwmon.c         # 하드웨어 모니터 (온도)
│   └── ufs_bsg.c           # BSG(Block SCSI Generic) 인터페이스
├── host/
│   ├── ufs-qcom.c          # Qualcomm UFS PHY/호스트 드라이버
│   ├── ufs-exynos.c        # Samsung Exynos UFS 드라이버
│   ├── ufs-mediatek.c      # MediaTek UFS 드라이버
│   ├── ufs-hisi.c          # HiSilicon UFS 드라이버
│   ├── ufs-renesas.c       # Renesas UFS 드라이버
│   ├── ufshcd-pci.c        # PCI 기반 UFS 호스트 (Intel)
│   ├── ufshcd-pltfrm.c     # 플랫폼 디바이스 공통 코드
│   ├── ufs-qcom.h          # Qualcomm 전용 헤더
│   └── tc-dwc-g210.c       # Synopsys DWC UFS PHY
└── Kconfig / Makefile
/* UFS 관련 주요 헤더 파일 */
#include <ufs/ufshcd.h>       /* struct ufs_hba, ufshcd_* API */
#include <ufs/ufshci.h>       /* UFSHCI 레지스터, UTRD/UTMRD 정의 */
#include <ufs/ufs.h>          /* UFS 스펙 상수, UPIU 정의 */
#include <ufs/unipro.h>       /* UniPro DME 속성 정의 */

/* UFS 버전 매크로 */
#define UFS_SPEC_VER_20   0x0200
#define UFS_SPEC_VER_21   0x0210
#define UFS_SPEC_VER_30   0x0300
#define UFS_SPEC_VER_31   0x0310
#define UFS_SPEC_VER_40   0x0400
UFS 버전 역사 요약: UFS 1.0(2011)은 기초 아키텍처를 정의했지만 실제 제품화는 UFS 2.0(2013)부터 시작되었습니다. UFS 2.1(2016)은 보안을 강화했고, UFS 3.0(2018)은 HS-G4로 대역폭을 크게 늘렸습니다. UFS 3.1(2020)은 Write Booster와 HPB를 표준화했으며, UFS 4.0(2022)은 HS-G5와 MCQ를 도입하여 성능과 확장성을 한층 강화했습니다.

MIPI M-PHY / UniPro 물리·전송 계층

UFS의 물리적 연결은 MIPI Alliance가 표준화한 M-PHY(물리 계층)와 UniPro(Unified Protocol, 전송 계층)를 사용합니다. M-PHY는 SerDes(Serializer/Deserializer) 기반의 고속 직렬 인터페이스로, 각 방향(TX/RX)에 차동(differential) 신호 쌍을 사용합니다. UniPro는 그 위에 계층화된 프로토콜로, 흐름 제어, 오류 복구, 레인 관리 등을 담당합니다.
MIPI M-PHY 레인 구조와 HS-Gear 라인 레이트 위 — 전이중 레인과 차동 신호 쌍 · 아래 — 기어별 라인 레이트 비교 UFS 호스트 PHY UFS 디바이스 PHY TX 레인 0 RX 레인 0 TX 레인 1 (선택) RX 레인 1 (선택) RX 레인 0 TX 레인 0 RX 레인 1 (선택) TX 레인 1 (선택) 레인 0 하나의 레인 = DIF_P(+) / DIF_N(-) 차동 신호 2선 레인 1 실선 = 기본 1레인(모든 제품 공통) · 점선 = 추가 레인(UFS 고대역폭 구성) M-PHY HS-Gear 라인 레이트 (레인당 · 로그 스케일) HS-G1 1.46 Gbps (A 1.25) HS-G2 2.92 Gbps (A 2.50) HS-G3 5.83 Gbps (A 4.99) HS-G4 11.66 Gbps (A 9.98) HS-G5 23.22 Gbps (A 19.97) HS-G6 ≈46.7 Gbps급 — M-PHY v6.0에서 신규 도입 (UFS 5.0) 막대 길이는 로그 스케일 · Rate B ≈ Rate A × 1.1667 (상용 제품은 주로 Rate B) · 최대 2레인 구성 시 레인당 레이트가 합산됩니다
M-PHY HS-Gear 라인 레이트 사양 (레인 당)
HS-GearRate ARate B주요 사용
HS-G11.248 Gbps1.4576 Gbps초기 UFS 고속 모드
HS-G22.496 Gbps2.9152 Gbps일부 UFS 2.x 제품
HS-G34.992 Gbps5.8304 GbpsUFS 2.x~3.x 하위 호환
HS-G49.984 Gbps11.6608 GbpsUFS 3.x 표준
HS-G519.968 Gbps23.2192 GbpsUFS 4.x 표준 (JEDEC 공칭 인터페이스 최대 ~4.2 GB/s)
HS-G6M-PHY v6.0 신규 — 레인 당 약 46.7 Gbps급UFS 5.0

모든 기어에 Rate A/B 두 계열이 존재하며(Rate B ≈ Rate A × 1.1667), 상용 UFS 제품은 주로 Rate B를 사용합니다. 실제 유효 대역폭은 라인 코딩과 UniPro/UPIU 프로토콜 오버헤드로 라인 레이트보다 낮아지므로, 실측 처리량은 라인 레이트를 그대로 MB/s로 환산한 값보다 작습니다.

M-PHY PWM-Gear 속도 사양 (저전력 모드)
PWM-Gear신호율 범위 (per Lane)용도
PWM-G13.4~9.2 Mb/s링크 초기화 시 기본 모드
PWM-G26.8~18.4 Mb/s저전력 데이터 전송
PWM-G313.6~36.8 Mb/s저전력 데이터 전송
PWM-G427.2~73.6 Mb/s저전력 데이터 전송
PWM-G554.4~147.2 Mb/s저전력 유지 통신
PWM-G6108.8~294.4 Mb/s저전력 유지 통신
PWM-G7217.6~588.8 Mb/s저전력 유지 통신

UniPro는 M-PHY 위에 4개 서브 계층(L1.5, L2, L3, L4)을 쌓아 신뢰성 있는 데이터 전송을 보장합니다.

/* UniPro DME 속성 설정 — ufshcd_dme_set() */
int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel,
                         u8 attr_set, u32 mib_val, u8 peer)
{
    struct uic_command uic_cmd = {0};

    uic_cmd.command = peer ?
        UIC_CMD_DME_PEER_SET : UIC_CMD_DME_SET;
    uic_cmd.argument1 = attr_sel;   /* MIB 속성 ID + GenSelector */
    uic_cmd.argument2 = UIC_ARG_ATTR_TYPE(attr_set);
    uic_cmd.argument3 = mib_val;    /* 설정할 값 */

    return ufshcd_send_uic_cmd(hba, &uic_cmd);
}

/* 사용 예: HS-G4, Rate B, 2 Lane 설정 */
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HSGEAR), 4);     /* HS-Gear 4 */
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HSRATESERIRES), 2); /* Rate B */
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVETXDATALANES), 2); /* 2 Lane TX */
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_ACTIVERXDATALANES), 2); /* 2 Lane RX */
ufshcd_dme_set(hba, UIC_ARG_MIB(PA_PWRMODE),
    FAST_MODE << 4 | FAST_MODE);   /* TX/RX 모두 FAST 모드 */
/* UniPro 주요 DME 속성 상수 (include/ufs/unipro.h) */
#define PA_ACTIVETXDATALANES    0x1560
#define PA_ACTIVERXDATALANES    0x1580
#define PA_TXGEAR               0x1568
#define PA_RXGEAR               0x1583
#define PA_HSSERIES             0x156A
#define PA_PWRMODE              0x1571
#define PA_MAXRXHSGEAR          0x1587
#define PA_LOCALVERINFO         0x15A0
#define PA_GRANULARITY          0x15AA
#define PA_PACPREQTIMEOUT       0x1590
#define PA_PACPREQEOBTIMEOUT    0x1591
#define DME_LOCALFC_PROTECTION_TIMEOUT_ATTR  0x1590

/* Power Mode 값 */
#define FAST_MODE       1
#define SLOW_MODE       2
#define FASTAUTO_MODE   4
#define SLOWAUTO_MODE   5
#define UNCHANGED       7
HS-Gear 전환 시퀀스: ufshcd_config_pwr_mode()는 먼저 디바이스의 PA_MAXRXHSGEAR를 읽어 지원 가능한 최대 Gear를 확인한 후, 호스트와 디바이스 양쪽의 Gear/Rate/Lane 수를 일치시켜 DME_SET(PA_PWRMODE) UIC 명령으로 전환합니다. 벤더별 vops->pwr_change_notify() 콜백(Callback)을 통해 플랫폼 특화 PHY 설정을 적용합니다. 전환 실패 시 SLOW_MODE 폴백을 시도합니다.

UPIU (UFS Protocol Information Unit) 프로토콜

UPIU는 UFS 호스트와 디바이스 간에 주고받는 모든 정보의 기본 단위입니다. 12바이트 고정 헤더와 가변 길이 데이터 세그먼트로 구성되며, Command UPIU는 헤더 뒤에 EDTL(Expected Data Transfer Length, 4바이트)과 CDB(16바이트)를 붙여 총 32바이트가 됩니다. SCSI 명령, 데이터 전송, 응답, Query 요청/응답, NOP, Task Management 등 모든 UFS 통신이 UPIU 형식으로 이루어집니다.
UPIU Header Layout (12 Bytes) 3 × DWORD = 12바이트 · 격자 칸 하나 = 정확히 1바이트 · 왼쪽 라벨은 DWORD·바이트 범위 DW0 B0~B3 B0 트랜잭션 타입 예: 0x01 B1 플래그 제어 플래그 B2 LUN LU 번호 B3 Task Tag 요청 태그 DW1 B4~B7 B4 명령셋 타입 예: SCSI B5 펑션 타입 Query·TMF B6 응답 코드 Response B7 SCSI 상태 Status DW2 B8~B11 B8 EHS 길이 추가 헤더 B9 디바이스 정보 Device Info B10~B11 데이터 세그먼트 길이 be16 · 최대 65535 헤더 뒤에 오는 것 (페이로드) Command UPIU — 총 32바이트 Header 12B EDTL CDB 16B EDTL+CDB는 ufshcd_prepare_utp_scsi_cmd_upiu()가 채움 Query Request UPIU — 헤더 + TSF = 32바이트 Header 12B TSF 20B EHS: ehs_length>0이면 헤더 직후 배치되는 추가 세그먼트 Response UPIU의 Status 필드(B7)에 기록되는 대표 SCSI 상태 코드 0x00 GOOD 0x02 CHECK_CONDITION 0x04 BUSY 0x08 RESERVATION_CONFLICT 0x28 TASK_ABORTED Command UPIU = Header 12B + EDTL 4B + CDB 16B = 32B (+ Data Segment)
UPIU Transaction Types
코드이름방향설명
0x00NOP OUTHost → Device링크 활성 확인용. 디바이스는 NOP IN으로 응답
0x20NOP INDevice → HostNOP OUT에 대한 응답
0x01COMMANDHost → DeviceSCSI 명령 전송 (CDB 16바이트 포함)
0x22DATA INDevice → Host읽기 데이터 전송 (디바이스가 호스트에 전달)
0x02DATA OUTHost → Device쓰기 데이터 전송 (호스트가 디바이스에 전달)
0x21RESPONSEDevice → HostCOMMAND에 대한 SCSI 상태 응답
0x31READY TO TRANSFERDevice → Host쓰기 시 디바이스가 데이터 수신 준비 완료 통보
0x16QUERY REQUESTHost → DeviceDescriptor/Attribute/Flag 읽기/쓰기 요청
0x36QUERY RESPONSEDevice → HostQuery 요청에 대한 응답
0x04TASK MANAGEMENT REQHost → DeviceAbort Task, Query Task, Logical Unit Reset 등
0x24TASK MANAGEMENT RESPDevice → HostTask Management 요청에 대한 응답
0x3FREJECT양방향잘못된 UPIU 수신 시 거부 응답
UFS에서 사용하는 주요 SCSI 명령
SCSI 명령Opcode용도비고
READ(10)0x28데이터 읽기 (32-bit LBA)일반 읽기 I/O
WRITE(10)0x2A데이터 쓰기 (32-bit LBA)일반 쓰기 I/O
READ(16)0x88데이터 읽기 (64-bit LBA)HPB 읽기에도 사용 (HPB는 커널 6.6에서 제거)
WRITE(16)0x8A데이터 쓰기 (64-bit LBA)대용량 LU 지원
SYNCHRONIZE_CACHE(10)0x35디바이스 캐시(Cache) 플러시(Flush)fsync() 시 발행
START_STOP_UNIT0x1B전원 상태 전환PowerDown, Sleep, Active 전환
UNMAP0x42TRIM (논리 블록 해제 통보)discard 지원
SECURITY PROTOCOL IN0xA2RPMB 읽기보안 프로토콜 데이터 수신
SECURITY PROTOCOL OUT0xB5RPMB 쓰기보안 프로토콜 데이터 전송
INQUIRY0x12디바이스 정보 조회Vendor, Product, Revision
TEST UNIT READY0x00디바이스 준비 상태 확인정상 여부 확인
REPORT LUNS0xA0사용 가능한 LUN 목록 조회LU 탐지 시
/* UPIU 헤더 구조체 — include/ufs/ufs.h */
struct utp_upiu_header {
    union {
        struct {
            __be32 dword_0;  /* Transaction Type | Flags | LUN | Task Tag */
            __be32 dword_1;  /* Cmd Set Type | Func Type | Response | Status */
            __be32 dword_2;  /* EHS Length | Device Info | Data Segment Length */
        };
        struct {
            /* DWORD 0 */
            u8 transaction_code;
            u8 flags;
            u8 lun;
            u8 task_tag;
            /* DWORD 1 */
            u8 command_set_type;
            u8 function_type;   /* Query Function 또는 TMF 코드 */
            u8 response;
            u8 status;
            /* DWORD 2 */
            u8 ehs_length;
            u8 device_info;
            __be16 data_segment_length;
        };
    };
};
static_assert(sizeof(struct utp_upiu_header) == 12);  /* 헤더 = 3 DWORD = 12바이트 */

/* Transaction Type 추출 매크로 */
#define UPIU_HEADER_TRANSACTION_TYPE(dword0) \
    (((dword0) >> 24) & 0xFF)

/* Command UPIU 구조체 */
struct utp_upiu_cmd {
    struct utp_upiu_header header;
    __be32 exp_data_transfer_len;  /* 예상 데이터 전송 길이 */
    u8     cdb[UFS_CDB_SIZE];     /* SCSI CDB (16바이트) */
};

/* Query UPIU 구조체 */
struct utp_upiu_query {
    struct utp_upiu_header header;
    u8     opcode;     /* Query 종류: Read/Write Descriptor/Attribute/Flag */
    u8     idn;        /* Descriptor/Attribute/Flag IDN */
    u8     index;      /* 인덱스 */
    u8     selector;   /* 선택자 */
    __be16 reserved_osf;
    __be16 length;     /* 데이터 길이 */
    __be32 value;      /* Attribute 값 또는 Flag 값 */
    __be32 reserved[2];
};
/* Command UPIU 구성 — ufshcd_prepare_utp_scsi_cmd_upiu() */
static void ufshcd_prepare_utp_scsi_cmd_upiu(
    struct scsi_cmnd *cmd, u8 upiu_flags)
{
    struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);
    struct utp_upiu_req *ucd_req_ptr = lrbp->ucd_req_ptr;

    /* DW0: Transaction Type | Flags | LUN | Task Tag */
    ucd_req_ptr->header.transaction_code = UPIU_TRANSACTION_COMMAND;
    ucd_req_ptr->header.flags           = upiu_flags;
    ucd_req_ptr->header.lun             = lrbp->lun;
    ucd_req_ptr->header.task_tag        = lrbp->task_tag;

    /* DW1: SCSI Command Set */
    ucd_req_ptr->header.command_set_type = UPIU_COMMAND_SET_TYPE_SCSI;

    /* 예상 데이터 전송 길이 + CDB 복사 (헤더+EDTL+CDB = 32바이트) */
    ucd_req_ptr->sc.exp_data_transfer_len = cpu_to_be32(scsi_bufflen(cmd));
    memcpy(ucd_req_ptr->sc.cdb, cmd->cmnd, UFS_CDB_SIZE);
}
RTT(Ready To Transfer) 흐름: 쓰기 명령의 경우, 호스트가 Command UPIU를 보낸 후 즉시 데이터를 전송하지 않습니다. 디바이스가 RTT UPIU를 보내 “데이터를 보내도 좋다”고 알려주면, 그때 호스트가 DATA OUT UPIU로 데이터를 전송합니다. 이 메커니즘은 디바이스 내부 버퍼(Buffer) 관리를 위한 것으로, eMMC의 busy 시그널(Signal)링과 유사한 역할을 합니다. 읽기 명령은 RTT 없이 디바이스가 바로 DATA IN UPIU를 보냅니다.

UFS 디바이스 아키텍처

UFS 디바이스 내부는 Logical Unit(LU) 기반으로 구성됩니다. 일반 목적의 LU(최대 32개)와 특수 목적의 Well-Known LU(Boot, RPMB, Device)가 있으며, 각 LU는 독립적인 블록 디바이스로 호스트에 노출됩니다. 디바이스의 설정과 상태는 Descriptor, Attribute, Flag 체계를 통해 관리됩니다.
UFS Device Internal Architecture 위에서 아래로 — 호스트와 만나는 링크부터 NAND 플래시까지 데이터가 흐르는 6단계 ① ② ③ ④ ⑤ ⑥ M-PHY / UniPro 인터페이스 (디바이스 측) UPIU 수신 UTP 엔진 (UTP Engine) UPIU 디코딩 · 응답 생성 · 명령 큐잉 (SDB 32슬롯 / MCQ) LUN 기반 라우팅 LU 매니저 (LU Manager) LU 라우팅 · Descriptor/Attribute/Flag 엔진 일반 목적 LU (최대 32개) LU 0 /dev/sda LU 1 /dev/sdb LU 2 /dev/sdc ⋯ LU 29 /dev/sd* LU 30 /dev/sd* LU 31 /dev/sd? 각 LU는 독립 SCSI 디바이스 (LBA 공간·블록 크기별도) Well-Known LU — BSG 노드(/dev/bsg/ufs-bsg0)로 접근 W-LU BOOT LUN=0xB0 (부트 파티션 0) W-LU RPMB LUN=0xC4 (보안 블록) W-LU Device LUN=0xD0 (전원·리셋 관리) Boot LU 1 LUN=0xB1 (부트 백업) 논리 주소→물리 위치 변환 요청 Flash Translation Layer (FTL) L2P 매핑 · 가비지 컬렉션(GC) · 웨어 레벨링(Wear Leveling) · ECC Write Buffer (SLC Cache) HPB L2P Cache Write Booster Buffer NAND I/O — 채널·다이·플레인 병렬 접근 3D NAND 플래시 배열 TLC/QLC · Multi-Plane · Multi-Die · Multi-Channel Write Booster와 HPB의 동작 상세는 각 전용 섹션 참조 — 이 그림은 데이터 경로의 계층 관계를 보여줍니다
Well-Known Logical Unit 종류
Well-Known LUW-LUN 값용도접근 방법
REPORT LUNS0x81사용 가능한 LUN 목록 보고SCSI REPORT LUNS 명령
UFS Device0xD0디바이스 전체 관리 (Power, Reset)START_STOP_UNIT 명령
Boot LU 00xB0부트 파티션 0 (부트로더(Bootloader) 저장)READ/WRITE 명령
Boot LU 10xB1부트 파티션 1 (부트로더 백업)READ/WRITE 명령
RPMB0xC4Replay Protected Memory BlockSECURITY PROTOCOL IN/OUT

UFS 디바이스의 설정과 상태는 Descriptor, Attribute, Flag 세 가지 체계로 관리됩니다. 모두 Query UPIU를 통해 읽기/쓰기가 가능합니다.

UFS 주요 Descriptor 종류
Descriptor IDN이름크기주요 필드
0x00Device Descriptor~64BbNumberLU, bUFSFeaturesSupport, wManufacturerID, bBootEnable, bDeviceClass
0x01Configuration Descriptor~144BbBootEnable, bDescrAccessEn, bInitPowerMode, 각 LU 설정 배열
0x02Unit Descriptor~45BbLUEnable, bBootLunID, bLUWriteProtect, qLogicalBlockCount, dEraseBlockSize
0x04Interconnect Descriptor~6BUniPro/M-PHY 버전 정보
0x05String Descriptor가변제조사명, 제품명, 시리얼 번호 (UTF-16)
0x07Geometry Descriptor~72B총 NAND 용량, 최대 LU 수, 세그먼트 크기, 얼로케이션 단위
0x08Power Parameters Descriptor~98B각 기어/모드별 전력 소비량
0x09Device Health Descriptor~45BbPreEOLInfo, bDeviceLifeTimeEstA/B (수명 잔량 추정)
/* Device Descriptor 읽기 — Query UPIU 사용 */
static int ufshcd_read_device_desc(struct ufs_hba *hba, u8 *buf, u32 size)
{
    return ufshcd_read_desc_param(hba,
        QUERY_DESC_IDN_DEVICE,  /* IDN = 0x00 */
        0,                       /* Index */
        0,                       /* Offset */
        buf, size);
}

/* Device Descriptor 주요 필드 오프셋(Offset) */
enum device_desc_param {
    DEVICE_DESC_PARAM_LEN           = 0x00,
    DEVICE_DESC_PARAM_TYPE          = 0x01,
    DEVICE_DESC_PARAM_DEVICE_TYPE   = 0x02,
    DEVICE_DESC_PARAM_DEVICE_CLASS  = 0x03,
    DEVICE_DESC_PARAM_DEVICE_SUB_CLASS = 0x04,
    DEVICE_DESC_PARAM_PRTCL         = 0x05,
    DEVICE_DESC_PARAM_NUM_LU        = 0x06,
    DEVICE_DESC_PARAM_NUM_WLU       = 0x07,
    DEVICE_DESC_PARAM_BOOT_ENBL     = 0x08,
    DEVICE_DESC_PARAM_MANF_ID       = 0x18, /* 제조사 ID (2바이트) */
    DEVICE_DESC_PARAM_SPEC_VER      = 0x10, /* UFS 스펙 버전 */
    DEVICE_DESC_PARAM_UFS_FEAT      = 0x1F, /* 지원 기능 비트맵 */
    DEVICE_DESC_PARAM_HPB_VER       = 0x40, /* HPB 버전 */
    DEVICE_DESC_PARAM_WB_TYPE       = 0x45, /* Write Booster 타입 */
};
/* Attribute 읽기/쓰기 예시 */
/* bCurrentPowerMode 읽기 */
u32 power_mode;
ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
    QUERY_ATTR_IDN_POWER_MODE, 0, 0, &power_mode);

/* bActiveICCLevel 쓰기 — 전류 소비 레벨 설정 */
u32 icc_level = 0x0F; /* 최대 레벨 */
ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
    QUERY_ATTR_IDN_ACTIVE_ICC_LVL, 0, 0, &icc_level);

/* Flag 설정 예시 — fDeviceInit 플래그로 디바이스 초기화 트리거 */
ufshcd_query_flag(hba, UPIU_QUERY_OPCODE_SET_FLAG,
    QUERY_FLAG_IDN_FDEVICEINIT, 0, NULL);
Descriptor vs Attribute vs Flag 차이: Descriptor는 다수 필드를 포함하는 구조화된 데이터 블록으로, 디바이스의 정적/반정적 설정을 담습니다. Attribute는 단일 32비트 값으로, 실시간(Real-time) 상태나 동적 설정(전원 모드, ICC 레벨 등)을 나타냅니다. Flag는 단일 비트(boolean) 값으로, 디바이스 초기화, 퍼지 활성화 등 on/off 스위치 역할을 합니다. 셋 모두 Query UPIU를 통해 호스트에서 읽고/쓸 수 있습니다.
ufs-utils 도구: 사용자 공간에서 ufs-utils 패키지의 ufs-tool로 Descriptor, Attribute, Flag를 직접 조회할 수 있습니다. ufs-tool desc -t 0 -p /dev/bsg/ufs-bsg0으로 Device Descriptor를 읽고, ufs-tool attr -t 2 -p /dev/bsg/ufs-bsg0으로 bCurrentPowerMode를 확인할 수 있습니다.

UFSHCI (UFS Host Controller Interface) 레지스터 아키텍처

UFSHCI는 JEDEC 표준(JESD223)에 의해 정의된 UFS 호스트 컨트롤러의 하드웨어 인터페이스입니다. SoC에 통합된 UFS 호스트 컨트롤러가 이 레지스터 맵을 구현하며, ufshcd 드라이버가 MMIO를 통해 이 레지스터들을 제어합니다. 핵심은 UTRL(UTP Transfer Request List), UTMRL(UTP Task Management Request List), 그리고 인터럽트/상태 레지스터입니다.
UFSHCI 레지스터와 DMA 구조 (SDB 모드) 왼쪽 레지스터가 오른쪽 DMA 메모리 위치를 가리키고, doorbell 쓰기가 하드웨어 동작을 시작시킵니다 MMIO 레지스터 시스템 / 인터럽트 CAP 0x00 VER 0x08 IS 0x20 IE 0x24 HCE 0x34 (컨트롤러 Enable) 전송 요청 (UTRL) UTRLBA(U) 0x50/0x54 — 배열 주소 UTRLDBR 0x58 — DoorBell UTRLCLR 0x5C — 완료 클리어 태스크 관리 (UTMRL) UTMRLBA(U) 0x70/0x74 — 배열 주소 UTMRLDBR 0x78 — DoorBell UIC UICCMD 0x90 ARG1~3 0x94+ MCQ 설정 0x300+ (UFS 4.0+) 벤더 전용 0xC0~ DMA 메모리 배치 UTRL — Transfer Request List (UTRLBA/UTRLBAU가 가리키는 UTRD 배열) UTRD[0] UTRD[1] UTRD[2] ⋯ UTRD[31] 최대 32 슬롯 · UTRD당 32바이트 · doorbell 비트 = 슬롯 번호 doorbell 링 UCD — Command Descriptor (UTRD가 가리키는 메모리) Command UPIU (32B) Response UPIU (32B) PRDT PRDT 엔트리 = {base_addr, byte_count} × N (Scatter-Gather) 드라이버가 UCD를 채우고 doorbell을 세팅하면 하드웨어가 DMA로 읽어간다 UTMRL — Task Management List (최대 8 슬롯 · Abort/LU Reset/Query Task) UTMRD0 UTMRD1 UTMRD2 ⋯ UTMRD7 TM doorbell UFS 4.0+ MCQ(Multi-Circular Queue)로의 확장 SDB (UFSHCI v3.0+) UTRL — 슬롯 32개 고정 UTRLDBR (하나뿐인 doorbell) MCQ 활성화 MCQ (UFS 4.0+) SQ 0 CQ 0 DB 독립 SQ 1 CQ 1 개수·깊이 가변 UFSHCI v3.0+: UTRL max 32 slots / UTMRL max 8 slots / UFS 4.0 MCQ: variable SQ/CQ
UFSHCI 주요 레지스터
오프셋(Offset)이름접근설명
0x00CAP (Capabilities)RO호스트 역량: NUTRS(슬롯 수), NUTMRS, 64-bit 지원, 오토 히버네이트, MCQ 지원 등
0x08VER (Version)ROUFSHCI 스펙 버전 (예: 0x0300 = v3.0)
0x20IS (Interrupt Status)RW1C인터럽트 상태: UTP Transfer/Task 완료, UIC 명령 완료, 오류 등
0x24IE (Interrupt Enable)RW인터럽트 활성화 마스크
0x30HCS (Host Controller Status)RO컨트롤러 상태: UCRDY, UTRLRDY, UTMRLRDY, DP(Device Present)
0x34HCE (Host Controller Enable)RW컨트롤러 활성화/비활성화 (비트 0: HCE)
0x50UTRLBARWUTRL 베이스 주소 (하위 32비트)
0x54UTRLBAURWUTRL 베이스 주소 (상위 32비트, 64-bit 주소용)
0x58UTRLDBRRWTransfer Request Doorbell: 비트 N 세팅 → UTRD[N] 전송 시작
0x5CUTRLCLRRWTransfer Request Clear: 비트 N 세팅 → UTRD[N] 취소
0x60UTRLRSRRWTransfer Request List Run-Stop Register
0x70UTMRLBARWUTMRL 베이스 주소 (하위 32비트)
0x74UTMRLBAURWUTMRL 베이스 주소 (상위 32비트)
0x78UTMRLDBRRWTask Management Request Doorbell
0x90UICCMDRWUIC Command 레지스터 (DME_GET/SET, DME_PEER_GET/SET 등)
0x94UCMDARG1RWUIC 명령 인자 1 (MIB Attribute ID)
0x98UCMDARG2RWUIC 명령 인자 2 (Attribute Set Type)
0x9CUCMDARG3RWUIC 명령 인자 3 (값)

UTRD(UTP Transfer Request Descriptor)는 UFSHCI 하드웨어가 DMA로 읽어가는 32바이트 구조체로, UCD(UTP Command Descriptor)의 DMA 주소와 데이터 전송 방향을 담습니다.

/* UTRD 구조체 — include/ufs/ufshci.h */
struct utp_transfer_req_desc {
    /* DW 0-3: 공용 디스크립터 헤더 (16바이트) */
    struct request_desc_header header;

    /* DW 4-5: UCD Base Address */
    __le64 command_desc_base_addr;

    /* DW 6: Response UPIU offset/length */
    __le16 response_upiu_length;  /* Response UPIU 길이 (DWord 단위) */
    __le16 response_upiu_offset;  /* UCD 내 Response UPIU 오프셋 */

    /* DW 7: PRDT offset/length */
    __le16 prd_table_length;      /* PRDT 엔트리 수 */
    __le16 prd_table_offset;      /* UCD 내 PRDT 오프셋 */
};  /* sizeof == 32바이트 — reserved 필드는 없음 */
static_assert(sizeof(struct utp_transfer_req_desc) == 32);

/* UTRD/UTMRD 공용 헤더 — include/ufs/ufshci.h */
struct request_desc_header {
    u8 cci;               /* Crypto Config Index (인라인 암호화 시 키 슬롯) */
    u8 ehs_length;
    u8 enable_crypto:1, reserved2:7;      /* CE 비트 */
    u8 interrupt:1, data_direction:2, command_type:4; /* DD, CT, IE */
    __le32 dunl;          /* Data Unit Number 하위 32비트 */
    u8 ocs;               /* Overall Command Status */
    u8 cds;
    __le16 ldbc;
    __le32 dunu;          /* Data Unit Number 상위 32비트 */
};
static_assert(sizeof(struct request_desc_header) == 16);

/* OCS (Overall Command Status) 값 */
enum utp_ocs {
    OCS_SUCCESS                  = 0x00,
    OCS_INVALID_CMD_TABLE_ATTR   = 0x01,
    OCS_INVALID_PRDT_ATTR        = 0x02,
    OCS_MISMATCH_DATA_BUF_SIZE   = 0x03,
    OCS_MISMATCH_RESP_UPIU_SIZE  = 0x04,
    OCS_PEER_COMM_FAILURE        = 0x05,
    OCS_ABORTED                  = 0x06,
    OCS_FATAL_ERROR              = 0x07,
    OCS_DEVICE_FATAL_ERROR       = 0x08,
    OCS_INVALID_CRYPTO_CONFIG    = 0x09,
    OCS_GENERAL_CRYPTO_ERROR     = 0x0A,
    OCS_INVALID_COMMAND_STATUS   = 0x0F,
};
/* LRB(Local Reference Block) 구조체 — 각 태그의 런타임 정보 */
struct ufshcd_lrb {
    struct utp_transfer_req_desc *utr_descriptor_ptr; /* UTRD 포인터 */
    struct utp_upiu_req *ucd_req_ptr;  /* UCD 내 Command UPIU 포인터 */
    struct utp_upiu_rsp *ucd_rsp_ptr;  /* UCD 내 Response UPIU 포인터 */
    struct ufshcd_sg_entry *ucd_prdt_ptr; /* PRDT 포인터 */

    struct scsi_cmnd *cmd;          /* 연결된 SCSI 명령 */
    u8 *sense_buffer;               /* Sense Data 버퍼 */
    unsigned int sense_bufflen;

    int scsi_status;
    int command_type;               /* SCSI / DEV_MANAGE / NOP */
    int task_tag;                   /* 슬롯 번호 (0~31) */
    u8 lun;                         /* 대상 LUN */
    bool req_abort_skip;            /* abort skip 플래그 */
    int crypto_key_slot;            /* 인라인 암호화 키 슬롯 (-1: 비활성) */
};
PRDT(Physical Region Descriptor Table) 엔트리: 각 PRDT 엔트리는 DMA scatter-gather 세그먼트 하나를 나타내며, 베이스 주소(64비트)와 데이터 바이트 수를 담습니다. UFSHCI 스펙은 PRDT 엔트리의 최소 정렬을 4바이트, 최소 크기를 4바이트로 규정합니다. struct ufshcd_sg_entry는 각 엔트리가 16바이트이며, 인라인 암호화(ICE) 활성 시 추가 필드가 포함됩니다. hba->sg_entry_size로 실제 크기를 확인할 수 있습니다.
OCS 확인: 전송 완료 시 ufshcd_transfer_req_compl()은 먼저 UTRD의 OCS(Overall Command Status)를 확인합니다. OCS가 OCS_SUCCESS이면 Response UPIU의 SCSI Status를 파싱하고, 그렇지 않으면 OCS 자체가 에러 원인입니다. OCS_PEER_COMM_FAILURE는 M-PHY/UniPro 링크 에러를, OCS_FATAL_ERROR는 UFSHCI 하드웨어 치명적 오류를 나타냅니다.

ufshcd 호스트 컨트롤러 드라이버

ufshcd(UFS Host Controller Driver)는 Linux 커널의 UFS 핵심 드라이버로, drivers/ufs/core/ufshcd.c에 구현되어 있습니다. SCSI 호스트 어댑터로 등록되며, 벤더별 플랫폼 드라이버(ufs-qcom, ufs-exynos 등)는 ufs_hba_variant_ops(vops) 콜백을 통해 하드웨어 특화 동작을 제공합니다.
ufshcd 드라이버 아키텍처 — 초기화와 I/O 경로 ① 부팅 시 한 번 초기화 · ② 매 I/O마다 반복되는 핫패스 · ③ 두 경로를 지탱하는 서비스 ① 초기화 경로 — probe부터 LU 스캔까지 (부팅 시 1회) 플랫폼 드라이버 ufs-qcom.c 등 probe() ufshcd_pltfrm_init() MMIO·clk·vreg 확보 ufshcd_init() hba 구성·UTRL 할당 SCSI 호스트 등록 LU 스캔 → /dev/sdX 플랫폼 드라이버가 vops 콜백 테이블을 넘기면 코어가 hba->vops에 저장 — 이후 벤더 분기는 모두 이 테이블 경유 ② I/O 처리 경로 — 요청 → 하드웨어 → 완료 / 오류 (매 I/O 반복) blk-mq → scsi_queue_rq SCSI 명령 도착 ufshcd_queuecommand() CDB → Command UPIU·UCD 작성 ufshcd_send_command() UTRLDBR write → HW DMA 시작 UFSHCI MMIO + DMA UTRL / UTMRL UIC (UniPro) IRQ(IS) · MCQ ufshcd_transfer_req_compl() IS 인터럽트 → OCS 확인 → scsi_done() 완료 IRQ (IS) scsi_done() 상위 계층 복귀 ufshcd_err_handler() 오류 복구 — host reset / LUN reset 후 재시도 OCS 오류 · 커맨드 타임아웃 감지 시 ③ 지원 서비스와 벤더 계층 클럭 / 레귤레이터 core_clk·ref_clk / vcc·vccq·vccq2 런타임 PM hibernate ↔ resume · rpm_lvl / spm_lvl sysfs / debugfs / BSG ufs-sysfs.c · ufs-debugfs.c · ufs_bsg.c ufs_hba_variant_ops (vops) init · setup_clocks · pwr_change_notify · hce_enable_notify … 벤더 플랫폼 드라이버 ufs-qcom · ufs-exynos · ufs-mediatek · ufshcd-pci(Intel) vops 등록 벤더 드라이버가 등록한 vops 콜백을 코어가 클럭·전원·활성화 시점에 호출 — 위 ② 경로 안에서 개입 코어 함수 완료 / 응답 HW 디스패치 오류 처리 벤더 연계 (vops)

ufshcd의 핵심은 struct ufs_hba입니다. 이 구조체는 UFS 호스트 컨트롤러의 모든 상태를 관리하며, SCSI 호스트(Scsi_Host), UFSHCI 레지스터 매핑(mmio_base), UTRD/LRB 배열, 벤더 콜백(vops), 클럭/레귤레이터, 전원 관리 상태 등을 포함합니다.

/* ufs_hba 주요 필드 상세 — include/ufs/ufshcd.h */
struct ufs_hba {
    /* === SCSI 호스트 === */
    struct Scsi_Host *host;
    struct device *dev;

    /* === UFSHCI 레지스터 === */
    void __iomem *mmio_base;         /* MMIO 베이스 주소 */
    u32 ahit;                        /* 오토 히버네이트 타이머 값 */

    /* === UTRL / UTMRL DMA 메모리 === */
    struct utp_transfer_req_desc *utrdl_base_addr;  /* UTRD 배열 */
    dma_addr_t utrdl_dma_addr;                      /* UTRD DMA 주소 */
    struct utp_task_req_desc *utmrdl_base_addr;     /* UTMRD 배열 */
    dma_addr_t utmrdl_dma_addr;

    /* === UCD(Command Descriptor) DMA 메모리 === */
    struct utp_transfer_cmd_desc *ucdl_base_addr;   /* UCD 배열 */
    dma_addr_t ucdl_dma_addr;

    /* === LRB (Local Reference Block) === */
    /* (커널 ≤6.18) struct ufshcd_lrb *lrb 배열 존재 — 6.19부터 제거되어
       LRB는 scsi_cmd_priv(cmd)가 반환하는 scsi_cmnd 내부 영역에 부착됨 */
    unsigned long lrb_in_use;        /* 사용 중인 태그 비트맵 */

    /* === 벤더 콜백 === */
    const struct ufs_hba_variant_ops *vops;
    void *priv;                      /* 벤더 전용 데이터 (ufshcd_get_variant) */

    /* === 역량 및 슬롯 수 === */
    u32 capabilities;                /* UFSHCI CAP 레지스터 값 */
    unsigned int nutrs;              /* Transfer Request 슬롯 수 (1~32) */
    unsigned int nutmrs;             /* Task Mgmt 슬롯 수 (1~8) */

    /* === 클럭 및 전원 === */
    struct list_head clk_list_head;  /* 클럭 목록 */
    struct ufs_vreg_info vreg_info;  /* vcc, vccq, vccq2 레귤레이터 */

    /* === 전원 관리 === */
    enum ufs_pm_level rpm_lvl;       /* 런타임 PM 레벨 */
    enum ufs_pm_level spm_lvl;       /* 시스템 PM 레벨 */
    int pm_op_in_progress;

    /* === 디바이스 정보 === */
    struct ufs_dev_info dev_info;    /* LU 수, WB 지원, HPB 지원 등 */
    u16 dev_quirks;                  /* 디바이스 quirk 비트맵 */

    /* === 에러 처리 === */
    struct work_struct eh_work;      /* 에러 핸들러 워크큐 */
    u32 errors;                      /* 마지막 에러 상태 */
    u32 uic_error;                   /* UIC 에러 상태 */

    /* === MCQ (UFS 4.0+) === */
    bool mcq_enabled;
    unsigned int nr_hw_queues;       /* MCQ 하드웨어 큐 수 */
    struct ufs_hw_queue *uhq;        /* MCQ 큐 배열 */
};

벤더별 플랫폼 드라이버는 ufs_hba_variant_ops(vops) 구조체를 통해 하드웨어 특화 초기화, PHY 설정, 전력 모드 전환 등의 콜백을 제공합니다.

/* 벤더 콜백 구조체 — include/ufs/ufshcd.h */
struct ufs_hba_variant_ops {
    const char *name;

    /* 초기화/해제 */
    int  (*init)(struct ufs_hba *);
    void (*exit)(struct ufs_hba *);

    /* 클럭 제어 */
    int  (*setup_clocks)(struct ufs_hba *, bool, enum ufs_notify_change_status);

    /* HCE(Host Controller Enable) 전후 알림 */
    int  (*hce_enable_notify)(struct ufs_hba *, enum ufs_notify_change_status);

    /* 링크 스타트업 전후 알림 */
    int  (*link_startup_notify)(struct ufs_hba *, enum ufs_notify_change_status);

    /* 전력 모드 변경 전후 알림 (Gear/Lane/Rate 변경) */
    int  (*pwr_change_notify)(struct ufs_hba *,
            enum ufs_notify_change_status,
            struct ufs_pa_layer_attr *desired,
            struct ufs_pa_layer_attr *final);

    /* 런타임 suspend/resume */
    int  (*suspend)(struct ufs_hba *, enum ufs_pm_op, enum ufs_notify_change_status);
    int  (*resume)(struct ufs_hba *, enum ufs_pm_op);

    /* PHY 특화 설정 */
    int  (*phy_initialization)(struct ufs_hba *);

    /* 디바이스 리셋 GPIO 제어 */
    int  (*device_reset)(struct ufs_hba *);

    /* MCQ 관련 (UFS 4.0+) */
    int  (*mcq_config_resource)(struct ufs_hba *);
    int  (*get_hba_mac)(struct ufs_hba *);      /* 최대 활성 명령 수 */
    int  (*op_runtime_config)(struct ufs_hba *); /* MCQ 런타임 설정 */

    /* 프로그램 키 — 과거 vops에 존재했으나 최근 메인라인에서 제거됨.
       ICE 키슬롯 프로그래밍은 blk-crypto profile의 ll_ops
       (.keyslot_program 등)로 각 벤더가 직접 구현한다.
       예) ufs-qcom: ufs_qcom_ice_keyslot_program() → qcom_ice_program_key() */
};

/* 벤더 드라이버 등록 예시 (Qualcomm) */
static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
    .name                   = "qcom",
    .init                   = ufs_qcom_init,
    .exit                   = ufs_qcom_exit,
    .setup_clocks           = ufs_qcom_setup_clocks,
    .hce_enable_notify      = ufs_qcom_hce_enable_notify,
    .link_startup_notify    = ufs_qcom_link_startup_notify,
    .pwr_change_notify      = ufs_qcom_pwr_change_notify,
    .suspend                = ufs_qcom_suspend,
    .resume                 = ufs_qcom_resume,
    .device_reset           = ufs_qcom_device_reset,
    .config_scaling_param   = ufs_qcom_config_scaling_param,
    /* .program_key 는 제거됨 — blk-crypto ll_ops로 이동 */
    .mcq_config_resource    = ufs_qcom_mcq_config_resource,
    .get_hba_mac            = ufs_qcom_get_hba_mac,
    .op_runtime_config      = ufs_qcom_op_runtime_config,
};

ufshcd 등록 및 초기화 흐름

플랫폼 드라이버가 ufshcd_pltfrm_init()을 호출하면 다음 순서로 초기화가 진행됩니다:

  1. ufshcd_alloc_host() — Scsi_Host와 ufs_hba 할당, SCSI 호스트 템플릿 설정
  2. ufshcd_init() — UTRL/UTMRL/UCD DMA 메모리 할당, LRB 배열 초기화, 인터럽트 등록, 워크큐 생성
  3. ufshcd_async_scan() (비동기) — HCE 활성화 → 링크 스타트업 → NOP OUT 확인 → Device Descriptor 읽기 → Gear 전환 → SCSI 호스트 스캔
/* 초기화 핵심 흐름 */
int ufshcd_pltfrm_init(struct platform_device *pdev,
                        const struct ufs_hba_variant_ops *vops)
{
    struct ufs_hba *hba;
    void __iomem *mmio_base;
    int irq, err;

    mmio_base = devm_platform_ioremap_resource(pdev, 0);
    irq = platform_get_irq(pdev, 0);

    err = ufshcd_alloc_host(&pdev->dev, &hba);
    hba->vops = vops;

    err = ufshcd_init(hba, mmio_base, irq);
    /* → 내부에서 ufshcd_async_scan() 스케줄링 */

    platform_set_drvdata(pdev, hba);
    return err;
}

/* ufshcd_async_scan() 주요 단계 */
static void ufshcd_async_scan(void *data, async_cookie_t cookie)
{
    struct ufs_hba *hba = data;

    /* 1. HCE 활성화: HCE 레지스터에 1 기록 */
    ufshcd_hba_enable(hba);

    /* 2. 링크 스타트업: UniPro 링크 초기화 (PWM-G1에서 시작) */
    ufshcd_link_startup(hba);

    /* 3. NOP OUT/IN 교환으로 링크 상태 확인 */
    ufshcd_verify_dev_init(hba);

    /* 4. Device Descriptor 읽기 → 디바이스 정보 파싱 */
    ufshcd_probe_hba(hba, false);
    /* → bNumberLU, bUFSFeaturesSupport, wManufacturerID 등 */

    /* 5. 최고 HS-Gear로 전환 */
    ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info);
    /* → vops->pwr_change_notify() → DME_SET(PA_PWRMODE) */

    /* 6. SCSI 호스트 스캔: LU 탐지 → /dev/sdX 생성 */
    scsi_scan_host(hba->host);
}

I/O 경로 상세

일반 I/O 경로의 핵심 함수 호출 순서를 추적합니다.

/* ===== I/O 발행 경로 ===== */

/* 1. SCSI mid-layer가 ufshcd_queuecommand() 호출
 *   (커널 6.19+: LRB는 scsi_cmd_priv(cmd)에 부착, 반환 타입 enum scsi_qc_status) */
static enum scsi_qc_status ufshcd_queuecommand(struct Scsi_Host *host,
                                               struct scsi_cmnd *cmd)
{
    struct ufs_hba *hba = shost_priv(host);
    struct request *rq = scsi_cmd_to_rq(cmd);
    int tag = rq->tag;
    /* SDB 모드면 NULL, MCQ 모드면 요청이 매핑된 HW 큐 */
    struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq);
    /* 커널 6.19+ : hba->lrb 배열 대신 scsi_cmd_priv(cmd) 사용 */
    struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);

    ufshcd_setup_scsi_cmd(hba, cmd,
                          ufshcd_scsi_to_upiu_lun(cmd->device->lun), tag);
        /* ├─ LRB 초기화 (cmd / task_tag / lun / 암호화 컨텍스트) */
        /* └─ 내부에서 Command UPIU + PRDT 구성 */

    /* 2. Doorbell 링으로 HW에 전송 (SDB: UTRLDBR, MCQ: SQ Tail DB) */
    ufshcd_send_command(hba, tag, hwq);
    return 0;
}

/* ===== I/O 완료 경로 ===== */

/* 3. 인터럽트 핸들러 */
static irqreturn_t ufshcd_intr(int irq, void *__hba)
{
    u32 intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS);

    /* Transfer Request 완료 인터럽트 */
    if (intr_status & UTP_TRANSFER_REQ_COMPL) {
        u32 completed = ~ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL)
                        & hba->outstanding_reqs;
        ufshcd_transfer_req_compl(hba, completed);
    }

    /* 에러 인터럽트 */
    if (intr_status & UFSHCD_ERROR_MASK)
        ufshcd_check_errors(hba, intr_status);

    return IRQ_HANDLED;
}

/* 4. 개별 요청 완료 처리 — 커널 6.19+ 기준 (단순화) */
static void ufshcd_transfer_req_compl(struct ufs_hba *hba,
                                      unsigned long completed_reqs)
{
    int tag;

    for_each_set_bit(tag, &completed_reqs, hba->nutrs) {
        struct scsi_cmnd *cmd = scsi_host_find_tag(hba->host, tag);
        struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);

        /* UTRD 헤더의 OCS(Overall Command Status) 확인 */
        if (lrbp->utr_descriptor_ptr->header.ocs == OCS_SUCCESS) {
            /* Response UPIU에서 SCSI Status 추출 */
            cmd->result = ufshcd_transfer_rsp_status(hba, lrbp);
        } else {
            /* OCS 에러 → 에러 핸들러로 위임 */
            cmd->result = DID_ERROR << 16;
        }

        scsi_done(cmd);   /* SCSI 레이어에 완료 통보 */
    }
}
ufshcd 주요 함수 참조
함수명파일역할
ufshcd_alloc_host()ufshcd.cScsi_Host + ufs_hba 할당
ufshcd_init()ufshcd.cDMA 메모리, IRQ, 워크큐 초기화
ufshcd_async_scan()ufshcd.c비동기 HCE→링크→NOP→스캔 시퀀스
ufshcd_hba_enable()ufshcd.cHCE 레지스터 활성화, 컨트롤러 리셋
ufshcd_link_startup()ufshcd.cUniPro 링크 초기화 (DME_LINKSTARTUP)
ufshcd_config_pwr_mode()ufshcd.cHS-Gear/Rate/Lane 전환
ufshcd_queuecommand()ufshcd.cSCSI 명령 → UPIU → doorbell
ufshcd_send_command()ufshcd.cUTRLDBR doorbell 링
ufshcd_intr()ufshcd.c인터럽트 핸들러(Handler) (IS 레지스터 처리)
ufshcd_transfer_req_compl()ufshcd.c전송 완료: OCS 확인 → scsi_done()
ufshcd_err_handler()ufshcd.c에러 복구: abort → reset → full reset
ufshcd_send_uic_cmd()ufshcd.cUIC 명령 전송 (DME_SET/GET 등)
ufshcd_query_attr()ufshcd.cQuery UPIU로 Attribute 읽기/쓰기
ufshcd_query_flag()ufshcd.cQuery UPIU로 Flag 설정/해제/읽기
ufshcd_read_desc_param()ufshcd.cQuery UPIU로 Descriptor 읽기
SCSI 호스트 템플릿: ufshcd는 scsi_host_template을 통해 SCSI mid-layer에 자신을 등록합니다. .queuecommand = ufshcd_queuecommand, .eh_abort_handler = ufshcd_abort, .eh_device_reset_handler = ufshcd_eh_device_reset_handler, .eh_host_reset_handler = ufshcd_eh_host_reset_handler 등이 핵심 콜백입니다. .cmd_per_lun은 기본 32이고, .can_queue는 hba->nutrs 값을 사용합니다.
에러 복구 레벨: ufshcd_err_handler()는 3단계 에러 복구를 수행합니다: (1) Abort — 개별 태그의 UTRLCLR로 명령 취소, (2) LU Reset — Task Management UPIU로 특정 LU 리셋, (3) Host Reset — HCE 비활성화/재활성화로 전체 UFSHCI 리셋 후 링크 재초기화. 각 단계 실패 시 다음 단계로 에스컬레이션됩니다. UIC 에러(M-PHY/UniPro 링크 오류)의 경우 즉시 Host Reset으로 진행할 수 있습니다.
/* 에러 핸들러 개요 */
static void ufshcd_err_handler(struct work_struct *work)
{
    struct ufs_hba *hba = container_of(work, struct ufs_hba, eh_work);

    /* 1단계: 개별 명령 Abort */
    for_each_set_bit(tag, &hba->outstanding_reqs, hba->nutrs) {
        err = ufshcd_try_to_abort_task(hba, tag);
        if (err) goto reset_lu;
    }
    return;

reset_lu:
    /* 2단계: LU 리셋 (Task Management UPIU) */
    err = ufshcd_eh_device_reset_handler(/* ... */);
    if (err) goto reset_host;
    return;

reset_host:
    /* 3단계: 호스트 전체 리셋 */
    ufshcd_reset_and_restore(hba);
    /* → HCE disable → HCE enable → link startup → restore */
}
# ufshcd 디바이스 트리 바인딩 예시 (Qualcomm SM8550)
ufs_mem_hc: ufshc@1d84000 {
    compatible = "qcom,sm8550-ufshc", "qcom,ufshc", "jedec,ufs-2.0";
    reg = <0x01d84000 0x3000>,       /* UFSHCI MMIO */
          <0x01d90000 0x8000>;        /* UFS PHY */
    reg-names = "std", "ice";

    interrupts = <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>;

    clocks = <&gcc GCC_UFS_PHY_AXI_CLK>,
             <&gcc GCC_UFS_PHY_ICE_CORE_CLK>,
             <&gcc GCC_UFS_PHY_UNIPRO_CORE_CLK>,
             <&rpmhcc RPMH_CXO_CLK>;
    clock-names = "core_clk", "ice_core_clk",
                  "unipro_core_clk", "ref_clk";

    freq-table-hz = <75000000 300000000>,   /* core_clk */
                    <75000000 300000000>,   /* ice_core_clk */
                    <75000000 300000000>,   /* unipro_core_clk */
                    <75000000 75000000>;    /* ref_clk */

    vcc-supply = <&vreg_l17b_2p5>;
    vccq-supply = <&vreg_l6b_1p2>;
    vcc-max-microamp = <800000>;
    vccq-max-microamp = <900000>;

    power-domains = <&gcc GCC_UFS_PHY_GDSC>;

    resets = <&gcc GCC_UFS_PHY_BCR>;
    reset-names = "rst";

    phys = <&ufs_mem_phy>;
    phy-names = "ufsphy";

    lanes-per-direction = <2>;  /* 2-Lane 구성 */

    status = "okay";
};
클럭 스케일링(Clock Scaling): ufshcd는 devfreq 프레임워크와 연동하여 런타임에 UFS 클럭 주파수를 동적으로 조절합니다. I/O 부하가 낮을 때 클럭을 낮추어 전력을 절약하고, 부하가 높아지면 최대 클럭으로 올립니다. ufshcd_devfreq_scale()가 기어 변경까지 포함하여 처리하며, vops->setup_clocks() 콜백으로 벤더별 클럭 게이팅/언게이팅을 수행합니다. sysfs의 /sys/class/devfreq/*/에서 현재 주파수와 통계를 확인할 수 있습니다.

커널 소스 분석 — drivers/ufs/ 구조

UFS 호스트 컨트롤러 드라이버의 핵심은 drivers/ufs/ 디렉터리에 위치합니다. Linux 6.x 기준으로 이 디렉터리는 코어 드라이버, 플랫폼별 글루(glue) 드라이버, 그리고 보조 기능 모듈로 구성됩니다. 전체 구조를 파악하면 UFS 스택의 동작 원리를 소스 레벨에서 이해할 수 있습니다.

소스 디렉터리 개요: Linux 커널 6.x에서 UFS 관련 소스는 크게 두 곳에 분산되어 있습니다. 드라이버 구현은 drivers/ufs/(이전에는 drivers/scsi/ufs/)에, 헤더 정의는 include/ufs/에 위치합니다. 커널 6.0부터 SCSI 디렉터리에서 독립적인 서브시스템으로 분리되었으며, drivers/ufs/core/와 drivers/ufs/host/로 세분화됩니다.

소스 파일 맵

경로파일명역할대략적 규모
drivers/ufs/core/ufshcd.cUFS Host Controller Driver 코어 — 초기화, I/O 처리, 에러 핸들링, 전원 관리~10,000줄
drivers/ufs/core/ufshcd-priv.h코어 내부 private 헤더~200줄
drivers/ufs/core/ufs-sysfs.csysfs 인터페이스 — 디바이스 속성 노출, 런타임 설정~1,000줄
drivers/ufs/core/ufs-mcq.cMulti-Circular Queue(MCQ) 지원 — UFS 4.0+~800줄
drivers/ufs/core/ufs-rpmb.c범용 RPMB 프레임워크 연결 — OP-TEE 백엔드(커널 6.19+)~350줄
drivers/ufs/core/ufs-txeq.c적응형 TX 이퀄라이제이션(EQTR) — 커널 6.20 병합 예정~1,300줄
drivers/ufs/core/ufs-hwmon.chwmon 연동 — 온도·수명 센서 노출~200줄
drivers/ufs/core/ufshcd-crypto.c인라인 암호화 — blk-crypto 프레임워크 연동~300줄
drivers/ufs/core/ufs-bsg.cBSG(Block SCSI Generic) — 사용자 공간 UFS 커맨드 통로~400줄
drivers/ufs/core/ufs-fault-inject.cFault injection 프레임워크 연동~100줄
drivers/ufs/core/ufs-debugfs.cdebugfs 인터페이스 — 런타임 디버깅~200줄
include/ufs/ufshci.hUFSHCI 레지스터 정의, UTRD/UTMRD 구조체~600줄
include/ufs/ufshcd.hufs_hba, ufshcd_lrb 등 핵심 구조체~1,200줄
include/ufs/ufs.hUFS 프로토콜 상수, Descriptor/Flag/Attribute 정의~700줄
include/ufs/ufs_quirks.h벤더별 quirk 플래그 정의~100줄
include/ufs/unipro.hUniPro 레이어 상수 (PA, DL, NL, TL 속성)~300줄

핵심 함수 호출 체인

UFS 드라이버의 전체 생명주기를 이해하려면 주요 함수들의 호출 관계를 파악해야 합니다. 아래는 드라이버 초기화부터 I/O 처리까지의 핵심 콜 체인입니다.

/* === 드라이버 초기화 콜 체인 === */
ufshcd_pltfrm_init()            /* 플랫폼 드라이버 진입점 */
  └─ ufshcd_alloc_host()        /* Scsi_Host + ufs_hba 할당 */
  └─ ufshcd_init()              /* 코어 초기화 */
       ├─ ufshcd_hba_init()     /* HBA 하드웨어 초기화 */
       ├─ ufshcd_host_reset_and_restore()
       ├─ scsi_add_host()       /* SCSI 미드 레이어 등록 */
       └─ async_schedule(ufshcd_async_scan, hba)
            └─ ufshcd_probe_hba()   /* 디바이스 탐색 및 설정 */
                 ├─ ufshcd_link_startup()    /* UniPro 링크 수립 */
                 ├─ ufshcd_verify_dev_init()
                 ├─ ufshcd_complete_dev_init()
                 ├─ ufshcd_device_params_init()
                 ├─ ufs_get_device_desc()   /* Device Descriptor 읽기 */
                 ├─ ufshcd_tune_unipro_params()
                 └─ scsi_scan_host()   /* LU 스캔 → /dev/sdX 생성 */

ufshcd_probe_hba() 상세 흐름

ufshcd_probe_hba()는 UFS 디바이스 탐색과 초기 설정의 핵심입니다. 이 함수는 비동기 스캔 콜백으로 호출되며, 링크 수립부터 LU(Logical Unit) 등록까지 전 과정을 담당합니다.

/* drivers/ufs/core/ufshcd.c — ufshcd_probe_hba() 핵심 흐름 */
static int ufshcd_probe_hba(struct ufs_hba *hba, bool init_dev_params)
{
    int ret;

    /* 1단계: UniPro 링크 수립 */
    ret = ufshcd_link_startup(hba);
    if (ret)
        goto out;

    /* 2단계: 디바이스 초기화 확인 */
    ret = ufshcd_verify_dev_init(hba);
    if (ret)
        goto out;

    /* 3단계: UFS 디바이스 파라미터 초기화 */
    if (init_dev_params) {
        ret = ufshcd_device_params_init(hba);
        if (ret)
            goto out;
    }

    /* 4단계: UniPro 파라미터 튜닝 (Gear, Lane, Rate) */
    ufshcd_tune_unipro_params(hba);

    /* 5단계: UFS Power Mode 변경 (HS-G4 등) */
    ret = ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info);
    if (ret)
        goto out;

    /* 6단계: Write Booster 초기화 */
    ufshcd_wb_config(hba);

    /* 7단계: SCSI 호스트 스캔 → /dev/sdX 생성 */
    if (hba->scsi_host_added)
        scsi_scan_host(hba->host);

    ufshcd_set_ufs_dev_active(hba);
out:
    return ret;
}

에러 핸들링: ufshcd_err_handler()

UFS 에러 핸들링은 전용 워크 큐(eh_work)를 통해 비동기적으로 처리됩니다. 인터럽트 컨텍스트에서 에러가 감지되면 워크를 스케줄하고, 별도 스레드(Thread)에서 복구를 수행합니다. 이 구조는 에러 복구 중에도 시스템이 응답성을 유지할 수 있게 합니다.

/* 에러 핸들러 워크 — ufshcd_err_handler() 주요 단계 */
static void ufshcd_err_handler(struct work_struct *work)
{
    struct ufs_hba *hba = container_of(work, struct ufs_hba, eh_work);

    /* 1. 미완료 태스크 정리 */
    ufshcd_clear_pending_tasks(hba);

    /* 2. 에러 유형별 분기 */
    if (hba->saved_err & UIC_ERROR) {
        /* UniPro 에러 → 링크 복구 시도 */
        ufshcd_update_evt_hist(hba, UFS_EVT_LINK_STARTUP_FAIL, 0);
        ret = ufshcd_uic_hibern8_exit(hba);
    }

    if (hba->saved_err & UFSHCD_UIC_DL_TCx_REPLAY_ERROR) {
        /* Data Link 에러 → 호스트 리셋 */
        ret = ufshcd_host_reset_and_restore(hba);
    }

    if (hba->saved_err & INT_FATAL_ERRORS) {
        /* Fatal 에러 → 전체 리셋 */
        ret = ufshcd_reset_and_restore(hba);
    }

    /* 3. 복구 완료 후 I/O 재개 */
    ufshcd_recover_pm_error(hba);
    ufshcd_release(hba);
}
디버깅 팁: UFS 에러 핸들링 과정을 추적하려면 tracepoint를 활용하세요. trace_ufshcd_command, trace_ufshcd_uic_command, trace_ufshcd_upiu 등의 tracepoint가 정의되어 있으며, echo 1 > /sys/kernel/debug/tracing/events/ufs/enable로 활성화할 수 있습니다.

주요 구조체 관계

구조체정의 위치역할주요 필드
struct ufs_hbainclude/ufs/ufshcd.hHBA 인스턴스 — 드라이버 전체 상태 관리host, mmio_base, utrdl_base_addr, lrb, vops
struct ufshcd_lrbinclude/ufs/ufshcd.hLocal Reference Block — 개별 I/O 요청 추적utr_descriptor_ptr, ucd_req_ptr, cmd, task_tag
struct utp_transfer_req_descinclude/ufs/ufshci.hUTP Transfer Request Descriptor (UTRD)header, command_desc_base_addr, prd_table_*
struct ufs_dev_infoinclude/ufs/ufshcd.hUFS 디바이스 정보 캐시wmanufacturerid, model, wspecversion
struct ufs_hba_variant_opsinclude/ufs/ufshcd.h플랫폼별 콜백 (vops)init, setup_clocks, hce_enable_notify, link_startup_notify

플랫폼별 드라이버 — Platform-Specific Glue Drivers

UFS 코어 드라이버(ufshcd.c)는 하드웨어 독립적인 로직을 제공하고, 각 SoC 벤더는 ufs_hba_variant_ops(통칭 vops) 콜백을 구현하여 플랫폼 고유의 PHY 초기화, 클럭 설정, 암호화 엔진 등을 처리합니다. 이 분리 구조 덕분에 새 플랫폼 지원을 추가할 때 코어 드라이버 수정 없이 글루 드라이버만 작성하면 됩니다.

vops 패턴: UFS 플랫폼 드라이버의 핵심은 struct ufs_hba_variant_ops입니다. 이 구조체의 각 함수 포인터가 초기화, 링크 수립, 전원 관리, 암호화 등의 시점별 콜백을 정의합니다. 코어 드라이버는 적절한 시점에 ufshcd_vops_xxx() 래퍼 매크로(Macro)를 통해 이 콜백들을 호출합니다.

플랫폼 드라이버 비교

드라이버파일compatible주요 특징PHY 유형
Qualcommufs-qcom.cqcom,ufshcICE(Inline Crypto Engine), QMP PHY, testbus 디버깅QMP UFS PHY
Samsung Exynosufs-exynos.csamsung,exynos7-ufsFMP(Flash Memory Protector), TXHSLV/RXHSLV 캘리브레이션Exynos UFS PHY
MediaTekufs-mediatek.cmediatek,mt8183-ufshciVA09 레귤레이터, 전용 crypto IP, MPHY 캘리브레이션MediaTek MPHY
HiSiliconufs-hisi.chisilicon,hi3670-ufsKirin SoC용, PHY 초기화 시퀀스HiSilicon MPHY
Renesasufs-renesas.crenesas,r8a779f0-ufsR-Car Gen4용, 자체 PHY setupRenesas MPHY
Intel PCIufshcd-pci.cPCI VID/DID 매칭PCI 기반 UFS, 서버/데스크탑용내장 PHY

vops 콜백 구현 예시

/* Qualcomm UFS vops — ufs-qcom.c */
static const struct ufs_hba_variant_ops ufs_hba_qcom_vops = {
    .name                   = "qcom",
    .init                   = ufs_qcom_init,
    .exit                   = ufs_qcom_exit,
    .set_dma_mask           = ufs_qcom_set_dma_mask,
    .hce_enable_notify      = ufs_qcom_hce_enable_notify,
    .link_startup_notify    = ufs_qcom_link_startup_notify,
    .pwr_change_notify      = ufs_qcom_pwr_change_notify,
    .apply_dev_quirks       = ufs_qcom_apply_dev_quirks,
    .suspend                = ufs_qcom_suspend,
    .resume                 = ufs_qcom_resume,
    .dbg_register_dump      = ufs_qcom_dump_dbg_regs,
    .device_reset           = ufs_qcom_device_reset,
    .config_scaling_param   = ufs_qcom_config_scaling_param,
    .event_notify           = ufs_qcom_event_notify,
    .mcq_config_resource    = ufs_qcom_mcq_config_resource,
};

/* Samsung Exynos UFS vops — ufs-exynos.c */
static const struct ufs_hba_variant_ops ufs_hba_exynos_ops = {
    .name                   = "exynos",
    .init                   = exynos_ufs_init,
    .exit                   = exynos_ufs_exit,
    .hce_enable_notify      = exynos_ufs_hce_enable_notify,
    .link_startup_notify    = exynos_ufs_link_startup_notify,
    .pwr_change_notify      = exynos_ufs_pwr_change_notify,
    .suspend                = exynos_ufs_suspend,
    .resume                 = exynos_ufs_resume,
};

새 플랫폼 드라이버 작성 스켈레톤

/* drivers/ufs/host/ufs-myplatform.c — 새 플랫폼 드라이버 스켈레톤 */
#include <ufs/ufshcd.h>
#include <ufs/ufshcd-pltfrm.h>
#include <ufs/unipro.h>

struct ufs_myplatform_host {
    struct ufs_hba *hba;
    void __iomem *phy_base;
    struct clk *ref_clk;
    struct regulator *vcc_supply;
    /* 플랫폼 고유 필드 */
};

static int ufs_myplatform_init(struct ufs_hba *hba)
{
    struct ufs_myplatform_host *host;
    struct device *dev = hba->dev;

    host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
    if (!host)
        return -ENOMEM;

    host->hba = hba;
    ufshcd_set_variant(hba, host);

    /* PHY 초기화 */
    host->phy_base = devm_platform_ioremap_resource_byname(
        to_platform_device(dev), "phy");
    if (IS_ERR(host->phy_base))
        return PTR_ERR(host->phy_base);

    /* 클럭, 레귤레이터 설정 */
    host->ref_clk = devm_clk_get(dev, "ref_clk");
    clk_prepare_enable(host->ref_clk);

    /* UFS Host Controller 특화 초기화 */
    hba->caps |= UFSHCD_CAP_CLK_GATING |
                  UFSHCD_CAP_HIBERN8_WITH_CLK_GATING;

    return 0;
}

static void ufs_myplatform_exit(struct ufs_hba *hba)
{
    struct ufs_myplatform_host *host = ufshcd_get_variant(hba);
    clk_disable_unprepare(host->ref_clk);
}

static int ufs_myplatform_link_startup_notify(
    struct ufs_hba *hba, enum ufs_notify_change_status status)
{
    if (status == PRE_CHANGE) {
        /* UniPro 파라미터 사전 설정 */
        ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0);
    }
    return 0;
}

static const struct ufs_hba_variant_ops ufs_myplatform_vops = {
    .name                = "myplatform",
    .init                = ufs_myplatform_init,
    .exit                = ufs_myplatform_exit,
    .link_startup_notify = ufs_myplatform_link_startup_notify,
};

static const struct of_device_id ufs_myplatform_of_match[] = {
    { .compatible = "myvendor,my-ufshc", .data = &ufs_myplatform_vops },
    { },
};
MODULE_DEVICE_TABLE(of, ufs_myplatform_of_match);

static int ufs_myplatform_probe(struct platform_device *pdev)
{
    const struct ufs_hba_variant_ops *vops;

    vops = of_device_get_match_data(&pdev->dev);
    return ufshcd_pltfrm_init(pdev, vops);
}

static void ufs_myplatform_remove(struct platform_device *pdev)
{
    ufshcd_pltfrm_remove(pdev);
}

static struct platform_driver ufs_myplatform_driver = {
    .probe  = ufs_myplatform_probe,
    .remove = ufs_myplatform_remove,
    .driver = {
        .name = "ufs-myplatform",
        .of_match_table = ufs_myplatform_of_match,
        .pm = &ufshcd_of_pm_ops,
    },
};
module_platform_driver(ufs_myplatform_driver);
Qualcomm ICE (Inline Crypto Engine): Qualcomm SoC에서는 UFS 컨트롤러에 ICE가 통합되어 있어 하드웨어 수준의 인라인 암호화를 지원합니다. ufs_qcom_ice_program_key()는 blk_crypto_profile의 키슬롯에 AES-256-XTS 키를 프로그래밍합니다. ICE는 CPU 개입 없이 데이터 경로에서 직접 암복호화를 수행하므로 성능 오버헤드(Overhead)가 거의 없습니다.

Kconfig 설정

# UFS 플랫폼 드라이버 Kconfig 예시
CONFIG_SCSI_UFS_QCOM=m          # Qualcomm UFS 호스트
CONFIG_SCSI_UFS_EXYNOS=m        # Samsung Exynos UFS 호스트
CONFIG_SCSI_UFS_MEDIATEK=m      # MediaTek UFS 호스트
CONFIG_SCSI_UFS_HISI=m          # HiSilicon UFS 호스트
CONFIG_SCSI_UFS_RENESAS=m       # Renesas UFS 호스트
CONFIG_SCSI_UFSHCD_PCI=m        # PCI 기반 UFS 호스트
CONFIG_SCSI_UFS_DWC_TC_PCI=m    # Synopsys DesignWare PCI

제조사별 칩셋 심층 분석 (Vendor-Specific Deep Dive)

UFS 코어 드라이버(ufshcd.c)는 벤더 중립적이지만, 실제 SoC에서는 PHY 캘리브레이션, 인라인 암호화 엔진, 전원 도메인, 디버깅 인프라 등에서 제조사마다 상당히 다른 구현을 갖습니다. 이 섹션에서는 주요 SoC 벤더의 UFS 호스트 드라이버가 커널에서 어떻게 구현되어 있는지, 각 벤더의 고유 하드웨어 IP와 그에 대응하는 커널 코드를 심층 분석합니다.

SoC 벤더별 UFS 호스트 아키텍처 비교 동일한 코어(vops 인터페이스) 아래에서 벤더마다 다른 PHY·암호화·전원 인프라를 구현 ufshcd.c — 공통 코어 (drivers/ufs/core/) SCSI 어댑터 · Command UPIU · UTRL/UTMRL 관리 · 오류 복구 — 벤더 차이는 모두 vops로 흡수 Qualcomm (ufs-qcom) ICE v3 암호화 엔진 QMP UFS PHY Testbus 디버깅 MCQ 리소스 매핑 DevFreq 스케일링 ESI (Event Specific IRQ) drivers/ufs/host/ufs-qcom.c Samsung (ufs-exynos) FMP 암호화 엔진 Exynos UFS PHY (PMA) TXHSLV/RXHSLV 캘리브 EOM (Eye Opening Monitor) 다중 Gear 테이블 VS_DEBUGCLOCKENABLE drivers/ufs/host/ufs-exynos.c MediaTek (ufs-mediatek) 전용 Crypto IP MediaTek MPHY VA09 레귤레이터 다단계 클럭 게이팅 MCQ Interrupt 매핑 VCC/VCCQ 시퀀싱 drivers/ufs/host/ufs-mediatek.c HiSilicon / Renesas Kirin PHY 다단계 초기화 Hi3660/Hi3670 Quirks R-Car Gen4 PHY 자동차 등급 온도 범위 최소 vops 구현 PMA 레지스터 직접 제어 ufs-hisi.c / ufs-renesas.c MIPI M-PHY / UniPro → UFS 디바이스 (NAND 플래시) 모든 벤더가 동일한 MIPI 물리 계층을 공유 — 차이는 호스트 컨트롤러 통합 방식 공통 패턴: 모든 벤더가 ufs_hba_variant_ops (vops) 콜백으로 플랫폼 고유 동작을 주입 차이점: PHY IP, 암호화 엔진(ICE vs FMP vs 전용 IP), 디버깅 인프라, 전원 도메인 관리가 벤더마다 상이 ※ Synopsys DesignWare UFS IP를 사용하는 SoC(일부 HiSilicon 등)는 ufshcd-dwc.c 공통 헬퍼를 활용

Qualcomm — ICE, QMP PHY, Testbus

Qualcomm의 UFS 호스트 드라이버(ufs-qcom.c)는 커널 UFS 플랫폼 드라이버 중 가장 기능이 풍부하며, Snapdragon SoC의 주요 스토리지 경로를 담당합니다. ICE(Inline Crypto Engine), QMP PHY, Testbus 디버깅, MCQ 리소스 매핑, DevFreq 기반 성능 스케일링 등 다양한 하드웨어 IP를 커널에서 직접 제어합니다.

ICE (Inline Crypto Engine) 상세

ICE는 UFS 컨트롤러와 M-PHY 사이의 데이터 경로에 위치하여, CPU 개입 없이 읽기/쓰기 데이터를 실시간으로 암복호화합니다. Android의 파일 기반 암호화(FBE, File-Based Encryption)에서 핵심적인 역할을 수행하며, AES-256-XTS 모드를 하드웨어에서 직접 처리하여 소프트웨어 암호화 대비 성능 오버헤드가 거의 없습니다.

ICE 버전별 특성 비교
ICE 버전키슬롯 수지원 알고리즘대표 SoC커널 지원
ICE v132AES-128/256-XTSSDM845Legacy (비표준 API)
ICE v232AES-256-XTSSM8150, SM7150blk-crypto + qcom_ice
ICE v332AES-256-XTS, AES-256-CTSSM8250+, SM8550+blk-crypto + qcom_ice + wrapped keys
/* ICE 키 프로그래밍 흐름 — ufs-qcom.c + qcom-ice.c */

/*
 * 1단계: blk-crypto 프레임워크가 키슬롯 프로그래밍 요청
 *    → blk-crypto profile ll_ops.keyslot_program() → ufs_qcom_ice_program_key()
 */
static int ufs_qcom_ice_program_key(struct ufs_hba *hba,
                                     const union ufs_crypto_cfg_entry *cfg,
                                     int slot)
{
    struct ufs_qcom_host *host = ufshcd_get_variant(hba);
    union ufs_crypto_cap_entry cap;
    union {
        u8 bytes[AES_256_XTS_KEY_SIZE];
        u32 words[AES_256_XTS_KEY_SIZE / sizeof(u32)];
    } key;

    /* crypto capability 확인 */
    cap = hba->crypto_cap_array[cfg->crypto_cap_idx];

    /* 키 데이터를 ICE 레지스터 형식으로 변환 */
    memcpy(key.bytes, cfg->crypto_key, AES_256_XTS_KEY_SIZE);

    /* qcom_ice 헬퍼를 통해 ICE 레지스터에 키 프로그래밍 */
    return qcom_ice_program_key(host->ice, cfg->crypto_cap_idx,
                                 QCOM_ICE_CRYPTO_KEY_SIZE_256,
                                 key.bytes, cfg->data_unit_size,
                                 slot);
}

/*
 * 2단계: ICE 레지스터에 키 쓰기 — qcom-ice.c
 *
 * ICE는 각 키슬롯에 대해 다음 레지스터를 프로그래밍:
 *   QCOM_ICE_REG_CRYPTOCFG      — 알고리즘, 키 크기, DUN(Data Unit Number) 크기
 *   QCOM_ICE_LUT_KEYS_CRYPTOCFG — 키 데이터 (256비트 × 2 = XTS 모드)
 *
 * 키 프로그래밍 순서:
 *   1. CRYPTOCFG.ENABLE = 0 (슬롯 비활성화)
 *   2. 키 데이터 레지스터에 32바이트씩 쓰기
 *   3. CRYPTOCFG = algorithm | key_size | dusize | ENABLE
 */

/*
 * 3단계: I/O 경로에서 ICE 자동 적용
 *   → UTRD의 crypto_enable 비트 설정
 *   → ICE가 DMA 전송 중 실시간 암복호화 수행
 *
 * ufshcd_prepare_req_desc_hdr()에서 (request_desc_header 필드 기준):
 *   req_desc->header.enable_crypto = 1;   // CE 비트
 *   req_desc->header.cci  = lrbp->crypto_key_slot;
 *   req_desc->header.dunl = lower_32_bits(lrbp->data_unit_num);
 *   req_desc->header.dunu = upper_32_bits(lrbp->data_unit_num);
 */
Wrapped Keys (ICE v3+): ICE v3부터 지원되는 wrapped key 기능은 실제 암호화 키가 소프트웨어에 노출되지 않도록 합니다. 키는 하드웨어 보안 모듈(TrustZone)에서 래핑(wrapping)된 상태로 ICE에 프로그래밍되며, ICE 내부의 하드웨어 키 언래퍼(unwrapper)가 실시간으로 언래핑하여 사용합니다. 이를 통해 커널 메모리 덤프(Dump)나 cold boot 공격에서도 실제 암호화 키를 보호할 수 있습니다. CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK=n으로 설정하면 소프트웨어 폴백(fallback)이 비활성화되어 반드시 ICE 하드웨어를 통해서만 암호화가 수행됩니다.

QMP UFS PHY

Qualcomm SoC는 QMP(Qualcomm Multi-Protocol) PHY IP를 사용하여 MIPI M-PHY 호환 직렬 링크를 구현합니다. QMP PHY는 USB, PCIe, UFS 등 여러 프로토콜을 지원하는 범용 SerDes(Serializer/Deserializer) IP이며, UFS 모드에서는 HS-Gear 4/5, Rate-A/B, 1~2 Lane을 지원합니다.

/* QMP UFS PHY 초기화 시퀀스 — 주요 흐름 */

/*
 * PHY 초기화는 link_startup_notify(PRE_CHANGE) 시점에 수행됩니다.
 * 핵심은 SoC별 캘리브레이션 테이블을 PHY 레지스터에 프로그래밍하는 것입니다.
 */
static int ufs_qcom_link_startup_notify(struct ufs_hba *hba,
                                         enum ufs_notify_change_status status)
{
    struct ufs_qcom_host *host = ufshcd_get_variant(hba);

    switch (status) {
    case PRE_CHANGE:
        /* UniPro 로컬 파라미터 설정 */
        ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0);

        /* PHY 전원 On + 캘리브레이션 */
        if (host->phy)
            phy_power_on(host->phy);  /* → qmp_ufs_power_on() */
        break;

    case POST_CHANGE:
        /* 링크 수립 후 Gear/Rate 협상 결과 확인 */
        ufs_qcom_link_startup_post_change(hba);
        break;
    }
    return 0;
}

/*
 * QMP PHY 캘리브레이션 테이블 구조 (SoC별로 다름):
 *
 * static const struct qmp_phy_init_tbl sm8550_ufsphy_serdes[] = {
 *     QMP_PHY_INIT_CFG(QSERDES_UFS_V6_COM_SYSCLK_EN_SEL, 0xd9),
 *     QMP_PHY_INIT_CFG(QSERDES_UFS_V6_COM_CMN_CONFIG, 0x06),
 *     QMP_PHY_INIT_CFG(QSERDES_UFS_V6_COM_VCO_TUNE_MAP, 0x02),
 *     ... // SoC별 수십~수백 개의 레지스터 값
 * };
 *
 * 각 테이블은 TX(송신), RX(수신), PCS(Protocol Convergence Sublayer),
 * SERDES(PLL/CDR) 영역별로 분리되어 있습니다.
 */

Testbus 디버깅 인프라

Qualcomm UFS 컨트롤러는 내부 상태를 외부에서 관찰할 수 있는 Testbus 메커니즘을 제공합니다. 에러 핸들링 시 ufs_qcom_dump_dbg_regs()가 자동 호출되어 컨트롤러 내부 상태를 커널 로그에 덤프합니다.

/* Testbus 디버그 레지스터 덤프 — ufs-qcom.c */
static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba)
{
    struct ufs_qcom_host *host = ufshcd_get_variant(hba);

    /* 1. UFS 호스트 컨트롤러 레지스터 덤프 */
    ufshcd_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16 * 4,
                     "HCI Vendor Specific Regs ");

    /* 2. Testbus 셀렉터별 상태 읽기 */
    ufs_qcom_testbus_read(hba);

    /*
     * Testbus 셀렉터 예시:
     *   TESTBUS_CFG = 0x   → UTP Transfer Request 상태
     *   TESTBUS_CFG = 0x05 → UTP Task Management 상태
     *   TESTBUS_CFG = 0x06 → UTRLCNR (완료 알림 레지스터) 상태
     *   TESTBUS_CFG = 0x07 → UTMRLCNR 상태
     *   TESTBUS_CFG = 0x10 → UFS-PHY 어댑터 레이어 상태
     *   TESTBUS_CFG = 0x11 → UniPro 상태
     *
     * 에러 분석 시 testbus 값의 비트 패턴으로
     * 요청이 어느 단계에서 멈췄는지 판별할 수 있습니다.
     */

    /* 3. PHY 상태 덤프 (선택) */
    if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_PHY_EN)
        ufs_qcom_phy_dbg_register_dump(host->phy);
}

/*
 * sysfs를 통한 testbus 제어:
 *   echo <selector> > /sys/kernel/debug/ufshcd0/testbus
 *   cat /sys/kernel/debug/ufshcd0/testbus
 */

MCQ 리소스 매핑

UFS 4.0의 MCQ(Multi-Circular Queue) 모드에서 Qualcomm SoC는 SQ(Submission Queue)와 CQ(Completion Queue)의 도어벨(doorbell) 레지스터를 별도의 MMIO 영역에 매핑합니다. ufs_qcom_mcq_config_resource()는 디바이스 트리에서 이 영역의 물리 주소(Physical Address)를 읽어와 ioremap합니다.

/* Qualcomm MCQ 리소스 설정 — ufs-qcom.c */
static int ufs_qcom_mcq_config_resource(struct ufs_hba *hba)
{
    struct ufs_qcom_host *host = ufshcd_get_variant(hba);
    struct platform_device *pdev = to_platform_device(hba->dev);
    struct resource *res;

    /* MCQ SQ/CQ 도어벨 전용 MMIO 영역 */
    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mcq");
    if (res) {
        host->mcq_base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(host->mcq_base))
            return PTR_ERR(host->mcq_base);
    }

    /*
     * 각 SQ/CQ 쌍은 고유한 도어벨 레지스터 주소를 가짐:
     *   SQ[n] Tail Doorbell = mcq_base + 0x0 + (n * stride)
     *   CQ[n] Head Doorbell = mcq_base + 0x4 + (n * stride)
     *
     * 이 분리 매핑 덕분에 서로 다른 CPU 코어가
     * 각자의 큐에 독립적으로 접근할 수 있어 lock contention이 감소합니다.
     */

    return 0;
}

/*
 * DevFreq 성능 스케일링 — ufs_qcom_config_scaling_param()
 *
 * Qualcomm SoC는 UFS 컨트롤러의 동작 주파수를 DevFreq 프레임워크로 제어합니다.
 * I/O 부하가 낮을 때 Gear를 낮추고, 높을 때 최대 Gear로 올려
 * 전력-성능 균형을 자동으로 조절합니다.
 *
 * static void ufs_qcom_config_scaling_param(struct ufs_hba *hba,
 *                                   struct devfreq_dev_profile *p,
 *                                   struct devfreq_simple_ondemand_data *d)
 * {
 *     p->polling_ms = 60;       // 60ms마다 부하 샘플링
 *     d->upthreshold = 70;      // 70% 이상 → Gear Up
 *     d->downdifferential = 5;  // 65% 이하 → Gear Down
 * }
 */

Samsung Exynos — FMP, EOM, PHY 캘리브레이션

Samsung Exynos SoC의 UFS 드라이버(ufs-exynos.c)는 FMP(Flash Memory Protector)라는 자체 인라인 암호화 엔진, Exynos UFS PHY의 PMA(Physical Medium Attachment) 레지스터 직접 제어, EOM(Eye Opening Monitor)을 통한 신호 품질 분석 등 고유한 기능을 갖습니다.

FMP (Flash Memory Protector)

FMP는 Qualcomm의 ICE와 유사한 역할을 수행하지만, 구현 방식이 다릅니다. ICE가 별도의 레지스터 블록을 통해 키를 관리하는 반면, FMP는 PRDT(Physical Region Descriptor Table)의 각 엔트리에 암호화 정보를 직접 포함시킵니다.

ICE vs FMP 인라인 암호화 비교
특성Qualcomm ICESamsung FMP
키 저장 위치ICE 전용 레지스터 (키슬롯)PRDT 엔트리 내 임베딩
키 프로그래밍사전에 키슬롯에 프로그래밍매 I/O마다 PRDT에 포함
키슬롯 수 제한32개 (고정)제한 없음 (PRDT 단위)
Wrapped Key지원 (v3+)미지원 (업스트림 기준)
알고리즘AES-256-XTS, AES-256-CTSAES-256-XTS
DUN 크기64비트64비트
커널 통합blk-crypto + qcom_iceblk-crypto + exynos-fmp
/* FMP 키 프로그래밍 — PRDT 기반 방식 */

/*
 * FMP는 PRDT의 각 세그먼트에 암호화 파라미터를 직접 삽입합니다.
 * 표준 PRDT 엔트리(16바이트)가 FMP 확장 필드(추가 16바이트)를 포함하여
 * 총 32바이트로 확장됩니다.
 *
 * struct fmp_sg_entry {
 *     // 표준 PRDT 필드 (16바이트)
 *     __le32  des0;     // base address lower
 *     __le32  des1;     // base address upper
 *     __le32  des2;     // reserved
 *     __le32  des3;     // size (+ ODD/EDD 비트)
 *
 *     // FMP 확장 필드 (16바이트) — 암호화 설정
 *     __le32  file_iv0; // IV (Initialization Vector) [31:0]
 *     __le32  file_iv1; // IV [63:32]
 *     __le32  file_iv2; // IV [95:64]
 *     __le32  file_iv3; // IV [127:96]
 * };
 *
 * FMP 활성화: des3의 FAS(File Algorithm Select) 비트를 설정
 *   FAS = 0b00 : 암호화 비활성화
 *   FAS = 0b10 : AES-256-XTS 파일 암호화
 */

/*
 * 메인라인 ufs-exynos.c의 실제 FMP 연동 (단순화):
 *
 * 1) PRDT 채우기 — ufshcd_prepare_req_desc_hdr() 경로에서
 *    exynos_ufs_fmp_fill_prdt()가 각 SG 세그먼트마다
 *    fmp_sg_entry 확장 필드에 IV·알고리즘(FAS)을 기록한다.
 *
 * 2) 키 프로그래밍 — TrustZone으로 전달되는 SMC 명령
 *    (SMC_CMD_FMP_SECURITY)으로 FMP 키슬롯을 설정한다.
 *    키 관리는 SoC 보안 월드에서 수행된다.
 *
 * 즉, FMP도 blk-crypto 프로파일에 등록되어 동작하며,
 * "PRDT 단위 암호화 + SMC 기반 키 설정"인 점만 ICE와 다르다.
 */

Exynos UFS PHY — PMA 캘리브레이션

Exynos UFS PHY는 PMA(Physical Medium Attachment) 레지스터를 통해 TX/RX 신호 특성을 세밀하게 조절합니다. TXHSLV(TX High-Speed Level)와 RXHSLV(RX High-Speed Level) 값은 PCB 트레이스 길이, 임피던스, 보드 레이아웃에 따라 SoC별로 달라지며, 캘리브레이션이 부정확하면 링크 수립 실패나 CRC 에러가 발생합니다.

/* Exynos UFS PHY 캘리브레이션 — ufs-exynos.c */

/*
 * link_startup_notify(PRE_CHANGE)에서 PHY 파라미터를 설정합니다.
 * 각 SoC(Exynos 990, 2100, 2200 등)별로 최적 값이 다릅니다.
 */
static int exynos_ufs_pre_link(struct exynos_ufs *ufs)
{
    struct ufs_hba *hba = ufs->hba;

    /* UniPro 파라미터 설정 */
    ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0);

    /* TX 캘리브레이션 */
    for_each_ufs_tx_lane(ufs, i) {
        ufshcd_dme_set(hba,
            UIC_ARG_MIB_SEL(TX_HIBERN8TIME_CAPABILITY, i), 0x01);
        ufshcd_dme_set(hba,
            UIC_ARG_MIB_SEL(TX_HS_EQUALIZER_SETTING, i),
            ufs->drv_data->tx_eq_val);  /* SoC별 이퀄라이저 값 */
    }

    /* RX 캘리브레이션 */
    for_each_ufs_rx_lane(ufs, i) {
        ufshcd_dme_set(hba,
            UIC_ARG_MIB_SEL(RX_HS_G1_SYNC_LENGTH_CAPABILITY, i), 0x4F);
        ufshcd_dme_set(hba,
            UIC_ARG_MIB_SEL(RX_HS_G2_SYNC_LENGTH_CAPABILITY, i), 0x4F);
        ufshcd_dme_set(hba,
            UIC_ARG_MIB_SEL(RX_HS_G3_SYNC_LENGTH_CAPABILITY, i), 0x4F);
    }

    /* Vendor-Specific 파라미터 (M-PHY 표준 외 확장) */
    exynos_ufs_config_phy(ufs);

    return 0;
}

/*
 * post_link에서 Gear/Rate 변경 후 추가 캘리브레이션 수행:
 * - HS-Series B (Rate-B)에서는 추가 SYNC 길이 조정 필요
 * - Gear 4에서는 RX 이퀄라이저 부스트 값 증가
 */

EOM (Eye Opening Monitor)

EOM은 Exynos UFS PHY에 내장된 신호 품질 측정 기능으로, 수신 데이터의 아이 다이어그램(Eye Diagram)을 소프트웨어에서 분석할 수 있게 합니다. PHY 캘리브레이션의 적절성을 검증하거나, 보드 설계의 시그널 인테그리티 문제를 진단할 때 사용됩니다.

/* EOM (Eye Opening Monitor) — debugfs를 통한 아이 다이어그램 측정 */

/*
 * 측정 원리:
 *   1. PHY RX 샘플링 포인트를 시간(phase)·전압(voltage) 축으로 이동
 *   2. 각 좌표에서 비트 에러율(BER) 측정
 *   3. BER < 임계값인 영역 = "Eye Opening" (개방 영역)
 *   4. 개방 영역이 클수록 신호 품질이 양호
 *
 * debugfs 인터페이스:
 *   /sys/kernel/debug/ufshcd0/eom
 *
 * 출력 예시 (phase × voltage 격자의 BER 맵):
 *   phase[-32..+32], voltage[-32..+32]
 *   중앙 영역의 BER = 0 → Eye Open
 *   가장자리 BER > 0 → Eye Closing (jitter/noise 영향)
 *
 * 실무 활용:
 *   - 보드 브링업 시 Eye가 충분히 열려 있는지 확인
 *   - HS-Gear 4/5에서 Eye가 닫히면 TX 이퀄라이저 또는 RX DFE 조정
 *   - 온도/전압 변동에 따른 마진 확인
 */

/*
 * Samsung 자체 VS(Vendor-Specific) 레지스터를 통한 EOM 제어:
 *   VS_DEBUGCLOCKENABLE — 디버그 클럭 활성화
 *   VS_SAVECONFIGTIME    — 캘리브레이션 저장 시간
 *   VS_DEBUGCOUNTENABLE  — 에러 카운터 활성화
 */

MediaTek — VA09, MPHY, 클럭 게이팅

MediaTek의 UFS 드라이버(ufs-mediatek.c)는 MT8183, MT8195, Dimensity 시리즈 등의 SoC에서 사용됩니다. VA09 레귤레이터 관리, 자체 MPHY IP, 다단계 클럭 게이팅 등 전력 효율에 초점을 맞춘 구현이 특징입니다.

VA09 레귤레이터 및 전원 시퀀싱

/* MediaTek UFS 전원 관리 — ufs-mediatek.c */

/*
 * MediaTek SoC는 UFS PHY에 VA09(0.9V 아날로그 전원)를 별도 레귤레이터로 공급합니다.
 * 이 전원은 M-PHY 아날로그 프론트엔드에 필수이며,
 * 전원 On/Off 순서가 정확하지 않으면 PHY가 불안정해집니다.
 */
static int ufs_mtk_mphy_power_on(struct ufs_hba *hba, bool on)
{
    struct ufs_mtk_host *host = ufshcd_get_variant(hba);
    int ret = 0;

    if (on) {
        /* 전원 On 순서: VA09 → MPHY → Reference Clock */
        ret = regulator_enable(host->reg_va09);
        if (ret)
            return ret;

        /* PHY 안정화 대기 */
        usleep_range(200, 210);

        /* MPHY 활성화 */
        ufs_mtk_setup_ref_clk(hba, true);

        /* MPHY 캘리브레이션 트리거 */
        ufs_mtk_mphy_ctrl(host, true);
    } else {
        /* 전원 Off 순서: MPHY → Reference Clock → VA09 (역순) */
        ufs_mtk_mphy_ctrl(host, false);
        ufs_mtk_setup_ref_clk(hba, false);
        regulator_disable(host->reg_va09);
    }

    return ret;
}

/*
 * MediaTek 전원 도메인 구조:
 *
 *   VCC (3.3V/1.8V) ──→ UFS 디바이스 (NAND 전원)
 *   VCCQ (1.2V)     ──→ UFS 디바이스 (I/O 전원)
 *   VCCQ2 (1.8V)    ──→ UFS 디바이스 (보조 I/O)
 *   VA09 (0.9V)     ──→ MPHY 아날로그 프론트엔드 (SoC 내부)
 *   VCC_HCI          ──→ UFS 호스트 컨트롤러 (SoC 내부)
 *
 * Suspend 시 VA09를 끄면 ~5mW 추가 절전 효과가 있으나,
 * Resume 시 PHY 재캘리브레이션이 필요하여 ~2ms 지연이 발생합니다.
 */

다단계 클럭 게이팅

/* MediaTek 클럭 관리 — 세분화된 클럭 게이팅 */

/*
 * MediaTek SoC는 UFS 관련 클럭을 5개 이상으로 세분화하여
 * 각 클럭을 독립적으로 게이팅할 수 있습니다.
 *
 * 클럭 구성 예시 (MT8195):
 *   1. ufs_sel       — UFS 코어 클럭 소스 선택 (200MHz/400MHz)
 *   2. ufs_hclk      — AHB 버스 클럭 (호스트 레지스터 접근)
 *   3. ufs_unipro    — UniPro 계층 클럭
 *   4. ufs_mp_sap    — MPHY 서브-어댑터 프로토콜 클럭
 *   5. ufs_encrypt   — 암호화 엔진 클럭
 *
 * 유휴 상태에서는 1~5 모두 게이팅 → 최소 전력 소비
 * Hibernate 상태에서는 2~5만 게이팅 → 빠른 복귀
 * 활성 상태에서는 5만 조건부 게이팅 → 암호화 미사용 시 절전
 */

static int ufs_mtk_setup_clocks(struct ufs_hba *hba, bool on,
                                 enum ufs_notify_change_status status)
{
    struct ufs_mtk_host *host = ufshcd_get_variant(hba);

    if (status == PRE_CHANGE && !on) {
        /* 클럭 Off 전: 진행 중인 트랜잭션 완료 대기 */
        ufs_mtk_wait_idle_state(hba, 5);

        /* Reference 클럭 비활성화 */
        ufs_mtk_setup_ref_clk(hba, false);
    } else if (status == POST_CHANGE && on) {
        /* 클럭 On 후: Reference 클럭 활성화 + PHY 안정화 */
        ufs_mtk_setup_ref_clk(hba, true);
    }

    return 0;
}

MediaTek 전용 Crypto IP

MediaTek SoC는 UFS 인라인 암호화를 위해 자체 설계한 crypto IP를 사용합니다. Qualcomm ICE와 달리 암호화 엔진이 UFS 컨트롤러 내부에 더 긴밀하게 통합되어 있으며, UFSHCI 표준의 crypto_cap 레지스터를 통해 능력(capability)을 보고합니다.

/* MediaTek Crypto 초기화 — ufs-mediatek.c */
static int ufs_mtk_init_crypto(struct ufs_hba *hba)
{
    struct ufs_mtk_host *host = ufshcd_get_variant(hba);

    /* Crypto 엔진 클럭 활성화 */
    if (host->clk_encrypt) {
        clk_prepare_enable(host->clk_encrypt);
    }

    /*
     * UFSHCI 표준 crypto 인터페이스 사용:
     *   hba->crypto_capabilities   — 지원 알고리즘/키 크기
     *   hba->crypto_cap_array      — 각 capability의 세부 사항
     *
     * MediaTek은 표준 UFSHCI crypto 레지스터를 통해
     * 키 프로그래밍을 수행하므로 별도의 vops->program_key가
     * 불필요한 경우도 있습니다.
     *
     * 단, SoC에 따라 crypto 엔진 리셋 순서에 quirk가 있어
     * suspend/resume 시 특별한 처리가 필요합니다:
     *   - Resume 후 crypto 엔진 리셋 + 키 재프로그래밍
     *   - hba->caps |= UFSHCD_CAP_CRYPTO를 설정하여 코어 드라이버에 알림
     */

    return 0;
}

HiSilicon Kirin — 다단계 PHY 초기화

HiSilicon(화웨이 반도체 부문)의 UFS 드라이버(ufs-hisi.c)는 Kirin 960/970/980/990 SoC를 지원합니다. 다른 벤더 드라이버에 비해 상대적으로 단순하지만, PHY 초기화 시퀀스가 매우 길고 SoC 리비전별로 세밀한 차이가 있는 것이 특징입니다.

/* HiSilicon UFS PHY 초기화 — ufs-hisi.c */

/*
 * Kirin SoC의 PHY 초기화는 3단계로 수행됩니다:
 *   1단계: SYSCTRL 레지스터를 통한 전원/리셋 제어
 *   2단계: PHY 레지스터 직접 프로그래밍 (수십 개의 레지스터)
 *   3단계: PLL 락(Lock) 확인 및 캘리브레이션 완료 대기
 */
static int ufs_hisi_link_startup_pre_change(struct ufs_hba *hba)
{
    struct ufs_hisi_host *host = ufshcd_get_variant(hba);
    int ret;

    /* SYSCTRL: UFS 리셋 해제 */
    writel(BIT_UFS_PSW_MTCMOS_EN,
           host->ufs_sys_ctrl + UFS_SYS_PSW_POWER_CTRL);
    udelay(10);

    /* PHY 레지스터 프로그래밍 (SoC별) */
    if (host->caps & UFS_HISI_CAP_PHY10nm) {
        /* Hi3670/Kirin 980: 10nm PHY */
        ufs_hisi_phy_init_10nm(host);
    } else {
        /* Hi3660/Kirin 960: 이전 세대 PHY */
        ufs_hisi_phy_init(host);
    }

    /* UniPro 파라미터 */
    ufshcd_dme_set(hba, UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), 0);

    /*
     * Hi3660 특유 Quirk:
     * - Hibernate Exit 후 첫 번째 커맨드가 간헐적으로 타임아웃
     * - 워크어라운드: H8 Exit 후 100μs 추가 대기
     *
     * Hi3670 특유 Quirk:
     * - HS-Gear 3 Rate-B에서 RX 에러 발생 가능
     * - 워크어라운드: RX_HS_G3_SYNC_LENGTH를 표준보다 길게 설정
     */

    return 0;
}

/*
 * HiSilicon은 PHY를 별도 드라이버로 분리하지 않고
 * UFS 호스트 드라이버 내부에서 직접 PHY 레지스터를 제어합니다.
 * (다른 벤더는 대부분 drivers/phy/ 아래 별도 PHY 드라이버를 사용)
 *
 * 이 때문에 코드가 하드웨어에 강하게 결합되어 있으며,
 * SoC 리비전 추가 시 if/else 분기가 늘어나는 구조적 한계가 있습니다.
 */

Renesas R-Car — 자동차 등급 UFS

Renesas의 UFS 드라이버(ufs-renesas.c)는 R-Car Gen4(V4H) SoC를 대상으로 하며, 자동차 인포테인먼트와 ADAS(Advanced Driver-Assistance Systems) 시스템에서 사용됩니다. 자동차 환경의 넓은 온도 범위(-40°C~+125°C)와 높은 신뢰성 요구사항에 대응하는 것이 핵심입니다.

/* Renesas R-Car UFS — ufs-renesas.c */

/*
 * R-Car Gen4 UFS의 특징:
 *   1. 자동차 등급 온도 범위 대응: -40°C ~ +125°C
 *   2. 자체 설계 MPHY (Synopsys DesignWare 기반이 아님)
 *   3. 최소한의 vops 구현 — 핵심 기능만 포함
 */

static int ufs_renesas_init(struct ufs_hba *hba)
{
    struct ufs_renesas_priv *priv;

    priv = devm_kzalloc(hba->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    ufshcd_set_variant(hba, priv);

    /*
     * R-Car Gen4 계열에서는 Auto-Hibernate를 기본으로 사용하는 구성이 흔합니다.
     * 자동차 환경에서는 빈번한 Sleep/Wake 전이가 발생하므로
     * H8 타이머를 짧게(1ms) 설정하여 전력 소비를 최소화합니다.
     */
    hba->ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 1) |
                FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3); /* 1ms */

    return 0;
}

static int ufs_renesas_pwr_change_notify(
    struct ufs_hba *hba,
    enum ufs_notify_change_status status,
    struct ufs_pa_layer_attr *dev_max_params,
    struct ufs_pa_layer_attr *dev_req_params)
{
    if (status == PRE_CHANGE) {
        /*
         * PHY 캘리브레이션 파라미터를 Gear에 맞게 조정
         *
         * 자동차 온도 범위에서는 고온에서 PHY 마진이 줄어들므로
         * HS-Gear 3 이상에서 TX/RX 이퀄라이저를 보수적으로 설정
         *
         * 레지스터 값은 Renesas 데이터시트에 의한 권장값:
         * - TX_AMPLITUDE: 고온 보상 +10%
         * - RX_THRESHOLD: 노이즈 마진 확보
         */
        ufs_renesas_set_phy_params(hba, dev_req_params);
    }

    return 0;
}

/*
 * 자동차 등급 UFS 디바이스의 특성:
 *   - eUFS(Embedded UFS) 2.1/3.1 사양 준수
 *   - AEC-Q100 인증 (자동차 전자부품 신뢰성)
 *   - 넓은 동작 온도 범위에서 성능 일관성 보장
 *   - RPMB를 통한 보안 저장 (차량 보안 키, OTA 검증)
 *   - Write Protect로 펌웨어 영역 보호
 */

Intel / Synopsys — PCI 기반 UFS

서버, 데스크탑, 개발 보드 환경에서는 UFS 컨트롤러가 PCI 버스(Bus)에 연결됩니다. ufshcd-pci.c는 PCI 기반 UFS 호스트를 지원하며, Synopsys DesignWare UFS IP를 사용하는 SoC도 ufshcd-dwc.c 헬퍼를 통해 지원됩니다.

/* PCI 기반 UFS — ufshcd-pci.c */

/*
 * PCI UFS는 디바이스 트리 대신 PCI VID/DID로 디바이스를 식별합니다.
 * 플랫폼(SoC) 드라이버와 달리 PHY 초기화, 레귤레이터, 클럭 관리가
 * 불필요하거나 PCI 서브시스템이 담당합니다.
 */
static const struct pci_device_id ufshcd_pci_tbl[] = {
    { PCI_VENDOR_ID_INTEL, 0x9DFA,  /* Intel Ice Lake */
      PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&ufs_intel_cnl_hba_vops },
    { PCI_VENDOR_ID_INTEL, 0x4B41,  /* Intel Elkhart Lake */
      PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&ufs_intel_ehl_hba_vops },
    { PCI_VENDOR_ID_INTEL, 0x0,     /* Intel LKF */
      PCI_ANY_ID, PCI_ANY_ID, 0, 0, (kernel_ulong_t)&ufs_intel_lkf_hba_vops },
    { }  /* terminate list */
};

/*
 * Intel UFS vops는 최소한의 구현:
 *   - init: LTR(Latency Tolerance Reporting) 설정
 *   - resume: PCI D0 복귀 후 레지스터 복원
 *   - link_startup: Intel PHY 고유 파라미터 설정
 *
 * Synopsys DesignWare UFS TC(Test Chip):
 *   - FPGA/ASIC 검증용 UFS 호스트 컨트롤러
 *   - ufshcd-dwc.c + tc-dwc-g210 시리즈 드라이버
 *   - 40비트 어드레싱 지원 (DMA_BIT_MASK(40))
 */

디바이스 Quirks — ufs_fixups[] 테이블

UFS 디바이스(NAND 측) 제조사마다 표준 사양과 미묘하게 다른 동작을 보이는 경우가 있습니다. Linux 커널은 ufs_fixups[] 테이블을 통해 이러한 디바이스별 워크어라운드(quirk)를 관리합니다. 디바이스의 Manufacturer ID와 Model 문자열을 매칭하여 적절한 quirk 플래그를 적용합니다.

ufs_fixups[] Quirk 매칭 흐름 디바이스 식별 정보 → 테이블 순회 매칭 → quirk 플래그 OR 누적 Device Descriptor iManufacturerName iProductName wManufacturerID (제조사명·모델·MID) 식별 정보 ufs_fixups[] — 디바이스 매칭 테이블 MID Model quirk 0x001CE SAMSUNG KLUDG4U… RECOVERY_DL_NAC ✓ 0x0098 TOSHIBA THGAF8… PA_TACTIVATE 0x012B Micron MTFC… DELAY_BEFORE_LPM 커널에 약 20여 개 항목 — 현재 디바이스와 일치한 행만 적용 매칭 적용 hba->dev_quirks 매칭된 quirk를 OR 누적 동작 분기마다 비트 검사 워크어라운드 경로 활성화 vops->apply_dev_quirks() 추가 주요 Quirk 플래그 카탈로그 (ufshci.h) 링크 안정성 UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS DL NAC 오류 수신 시 재시도 복구 절차 추가 UFS_DEVICE_QUIRK_PA_TACTIVATE TOSHIBA 일부 모델 — PA_TACTIVATE 값 조정 저전력(LPM) 전환 타이밍 UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM Hibernate 진입 전 지연 삽입 (Micron) UFS_DEVICE_QUIRK_DELAY_AFTER_LPM 저전력 복귀 직후 안정화 지연 UFS_DEVICE_QUIRK_NO_LINK_OFF SSD 등 일부 디바이스 — 링크 오프 금지 전원/클럭 파라미터 예외 UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME PA_SaveConfigTime 값 보정 UFS_DEVICE_NO_VCCQ VCCQ 전원 레일을 사용하지 않는 디바이스 UFS_DEVICE_NO_FASTAUTO 빠른 자동 Hibernate(Sleep) 금지 기능 확장 UFS_DEVICE_QUIRK_SUPPORT_EXTENDED_FEATURES 확장 기능 지원 디바이스 표시 ※ 각 quirk는 특정 디바이스의 비표준 동작을 보정합니다. 예: DL NAC 에러 후 추가 복구 절차, Hibernate 진입 전 지연 삽입 등 ※ 커널 v6.x 기준 약 15개 이상의 디바이스 quirk가 정의되어 있으며, 벤더별 vops->apply_dev_quirks()로 추가 quirk도 적용 가능
/* ufs_fixups[] — 디바이스별 워크어라운드 테이블 (ufshcd.c) */
static const struct ufs_dev_quirk ufs_fixups[] = {
    /*
     * Samsung UFS 디바이스
     * - 초기 모델에서 DL(Data Link) NAC 에러 복구 문제
     * - PA_TACTIVATE 타이밍 조정 필요
     */
    { .wmanufacturerid = UFS_VENDOR_SAMSUNG,
      .model = UFS_ANY_MODEL,
      .quirk = UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS |
               UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME |
               UFS_DEVICE_QUIRK_PA_TACTIVATE },

    /*
     * SK hynix UFS 디바이스
     * - Hibernate 진입 전 지연 필요 (NAND 내부 GC 완료 대기)
     * - PA_TACTIVATE 표준보다 긴 값 필요
     */
    { .wmanufacturerid = UFS_VENDOR_SKHYNIX,
      .model = UFS_ANY_MODEL,
      .quirk = UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME |
               UFS_DEVICE_QUIRK_PA_TACTIVATE },

    /*
     * Kioxia (구 Toshiba) UFS 디바이스
     * - PA_TACTIVATE 타이밍 조정
     * - 일부 모델에서 Hibernate 후 링크 재수립 지연
     */
    { .wmanufacturerid = UFS_VENDOR_TOSHIBA,
      .model = UFS_ANY_MODEL,
      .quirk = UFS_DEVICE_QUIRK_HOST_PA_SAVECONFIGTIME |
               UFS_DEVICE_QUIRK_PA_TACTIVATE |
               UFS_DEVICE_QUIRK_DELAY_AFTER_LPM },

    /*
     * Micron UFS 디바이스
     * - 특정 모델에서 확장 기능(Extended Features) 보고 방식이 다름
     */
    { .wmanufacturerid = UFS_VENDOR_MICRON,
      .model = UFS_ANY_MODEL,
      .quirk = UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM },

    { } /* sentinel */
};

/*
 * Quirk 적용 시점: ufshcd_probe_hba() → ufshcd_fixup_dev_quirks()
 *
 * 매칭 알고리즘:
 *   1. ufs_fixups[] 순회
 *   2. wmanufacturerid 비교 (UFS_ANY_VENDOR는 전체 매칭)
 *   3. model 문자열 비교 (UFS_ANY_MODEL은 전체 매칭)
 *   4. 매칭되면 hba->dev_quirks |= fixup->quirk
 *
 * 벤더 추가 quirk: vops->apply_dev_quirks()로 플랫폼별 추가 적용 가능
 */
static void ufshcd_fixup_dev_quirks(struct ufs_hba *hba,
                                     const struct ufs_dev_quirk *fixups)
{
    const struct ufs_dev_quirk *f;
    struct ufs_dev_info *dev_info = &hba->dev_info;

    if (!fixups)
        return;

    for (f = fixups; f->quirk; f++) {
        if ((f->wmanufacturerid == dev_info->wmanufacturerid ||
             f->wmanufacturerid == UFS_ANY_VENDOR) &&
            ((f->model == UFS_ANY_MODEL) ||
             !strcmp(f->model, dev_info->model)))
            hba->dev_quirks |= f->quirk;
    }
}

주요 Quirk 상세 설명

UFS Device Quirk 플래그 상세
Quirk 플래그영향받는 벤더문제 현상워크어라운드
RECOVERY_FROM_DL_NAC_ERRORS Samsung Data Link 계층 NAC(Negative Acknowledge) 에러 후 링크가 불안정해짐 NAC 에러 감지 시 추가 복구 시퀀스 수행 (링크 재수립)
PA_TACTIVATE Samsung, SK hynix, Kioxia PA_TACTIVATE(활성화 시간) 기본값이 디바이스 요구사항보다 짧음 디바이스가 보고하는 peer PA_TACTIVATE 값으로 재설정
HOST_PA_SAVECONFIGTIME Samsung, SK hynix, Kioxia 전원 모드 변경 시 Config 저장 시간이 부족하여 Gear 전환 실패 PA_SAVECONFIGTIME을 디바이스 요구에 맞게 증가
DELAY_BEFORE_LPM Micron LPM(Low Power Mode) 진입 전 NAND 내부 작업 미완료 Hibernate 진입 전 추가 지연 삽입 (수 ms)
DELAY_AFTER_LPM Kioxia LPM 탈출 후 디바이스 응답 지연 Hibernate Exit 후 추가 대기 시간(Latency) 삽입
NO_LINK_OFF 일부 디바이스 Link Off(전원 차단) 후 재수립 실패 Suspend 시 Link Off 대신 Hibernate 유지
NO_VCCQ 특정 보드 설계 VCCQ 레귤레이터가 물리적으로 없거나 항상 On VCCQ 전원 제어 스킵
Quirk 디버깅 팁: 새 UFS 디바이스를 브링업할 때 링크 에러, Gear 전환 실패, Hibernate 실패가 발생하면 dmesg | grep -i "quirk\|fixup"로 적용된 quirk를 확인하세요. quirk가 적용되지 않은 새 디바이스라면 ufs_fixups[]에 엔트리를 추가하거나, vops->apply_dev_quirks()에서 플랫폼별 quirk를 적용해야 할 수 있습니다. cat /sys/bus/platform/devices/*/string_descriptors/manufacturer_name과 cat /sys/bus/platform/devices/*/string_descriptors/product_name으로 디바이스 식별 정보를 확인할 수 있습니다.

UFS 디바이스 제조사별 NAND 특성

UFS 디바이스의 성능과 수명은 내부 NAND 플래시의 특성에 크게 좌우됩니다. 각 디바이스 제조사는 자체 NAND 기술을 기반으로 Write Booster, HPB, 가비지 컬렉션(GC) 등의 FTL(Flash Translation Layer) 전략을 다르게 구현하며, 이러한 차이가 실제 워크로드에서의 성능과 수명에 영향을 미칩니다.

UFS 디바이스 제조사별 NAND 특성 비교 (UFS 4.0 기준)
특성SamsungSK hynixKioxia / WDMicron
NAND 기술 V-NAND 8세대 (236단) 238단 4D NAND BiCS8 (218단) 232단 TLC NAND
최대 순차 읽기 ~4,300 MB/s ~4,200 MB/s ~3,500 MB/s ~4,000 MB/s
최대 순차 쓰기 ~2,800 MB/s ~2,800 MB/s ~2,500 MB/s ~2,600 MB/s
Write Booster 최대 양호 (큰 SLC 버퍼) 양호 양호 양호
HPB 지원 HPB 2.0 (Active Region 동적 관리) HPB 2.0 HPB 1.0/2.0 HPB 2.0
내구성 (TBW) 높음 (V-NAND 최적화) 높음 보통~높음 보통~높음
Manufacturer ID 0x1CE (UFS_VENDOR_SAMSUNG) 0x1AD (UFS_VENDOR_SKHYNIX) 0x198 (UFS_VENDOR_TOSHIBA) 0x12C (UFS_VENDOR_MICRON)
Linux Quirks DL NAC 복구, PA_TACTIVATE PA_TACTIVATE, SAVECONFIGTIME PA_TACTIVATE, LPM 후 지연 LPM 전 지연

Write Booster 동작 차이

Write Booster는 TLC/QLC NAND의 일부 영역을 SLC 모드로 사용하여 쓰기 속도를 가속하는 기능입니다. 제조사마다 SLC 버퍼 크기, 플러싱 정책, 잔여 용량에 따른 동작이 다릅니다.

/* Write Booster 제조사별 동작 차이 */

/*
 * 공통 동작 원리:
 *   1. 호스트가 Write Booster 활성화 → bWriteBoosterBufferFlushEn = 1
 *   2. 쓰기 데이터가 SLC 버퍼에 먼저 기록 (고속)
 *   3. 유휴 시간에 SLC → TLC 플러싱 (저속, 백그라운드)
 *   4. SLC 버퍼가 가득 차면 직접 TLC에 기록 (저속)
 *
 * Samsung 특성:
 *   - 상대적으로 큰 SLC 버퍼 (전체 용량의 ~3~5%)
 *   - 어그레시브한 플러싱 정책 (유휴 1초 이내 시작)
 *   - bAvailableWriteBoosterBufferSize로 잔여 SLC 버퍼 조회 가능
 *   - 온도가 높으면 (>45°C) 플러싱 우선도 증가
 *
 * SK hynix 특성:
 *   - 적응형 버퍼 관리 (사용 패턴에 따라 SLC 영역 크기 조절)
 *   - 디바이스 수명에 따라 SLC 영역 축소 (수명 보호)
 *
 * Kioxia/WD 특성:
 *   - 보수적 플러싱 (유휴 시간이 길어야 플러싱 시작)
 *   - 높은 온도에서 WB 비활성화 가능
 *
 * Micron 특성:
 *   - WB 모드 전환 시 약간의 지연 (DELAY_BEFORE_LPM quirk 관련)
 *   - 예측 기반 플러싱 (연속 쓰기 패턴 감지 시 사전 플러싱)
 */

/*
 * Write Booster 상태 확인 (공통):
 *   cat /sys/bus/platform/devices/*/attributes/wb_on
 *   cat /sys/bus/platform/devices/*/attributes/wb_buf_flush_status
 *   cat /sys/bus/platform/devices/*/attributes/wb_avail_buf
 *   cat /sys/bus/platform/devices/*/attributes/wb_buf_lifetime_est
 */

HPB 구현 차이

HPB(Host Performance Booster)는 UFS 디바이스의 L2P(Logical-to-Physical) 매핑 테이블 중 자주 접근하는 영역을 호스트 메모리(DRAM)에 캐싱하여 랜덤 읽기 성능을 개선합니다. HPB 1.0은 디바이스가 능동적으로 매핑을 추천하고, HPB 2.0은 호스트가 주도적으로 관리합니다.

/* HPB 제조사별 차이 — drivers/ufs/core/ufshpb.c */

/*
 * HPB 2.0 공통 동작:
 *   1. 호스트가 READ BUFFER(HPB_READ) 커맨드로 L2P 엔트리 요청
 *   2. 디바이스가 L2P 데이터를 응답
 *   3. 호스트가 DRAM에 캐싱
 *   4. 이후 읽기 시 HPB_READ 커맨드에 물리 주소를 직접 포함
 *   5. 디바이스가 L2P 테이블 조회를 건너뛰어 지연 시간 감소
 *
 * Samsung HPB 특성:
 *   - Active Region 수: 최대 64개 (디바이스 모델에 따라 다름)
 *   - Active Region당 Subregion: 최대 8개
 *   - L2P 캐시 적중 시 랜덤 읽기 지연 개선 (정확한 폭은 디바이스·환경 의존)
 *   - 디바이스 내부에서도 L2P 캐시 유지 (호스트 캐시와 이중)
 *
 * SK hynix HPB 특성:
 *   - 좀 더 공격적인 Inactive/Active 전환
 *   - 써멀 스로틀링 시 HPB 동작 축소
 *
 * HPB 활성화 확인:
 *   cat /sys/bus/platform/devices/*/device_descriptor/hpb_version
 *   cat /sys/bus/platform/devices/*/geometry_descriptor/hpb_region_size
 *   cat /sys/bus/platform/devices/*/geometry_descriptor/hpb_number_lu
 */

/*
 * HPB sysfs 제어:
 *   echo 1 > /sys/class/scsi_device/0:0:0:0/device/hpb_param/requeue_timeout_ms
 *   cat /sys/class/scsi_device/0:0:0:0/device/hpb_stats/hit_cnt
 *   cat /sys/class/scsi_device/0:0:0:0/device/hpb_stats/miss_cnt
 *   cat /sys/class/scsi_device/0:0:0:0/device/hpb_stats/rb_noti_cnt
 */

Logical Unit 배치 전략

UFS 디바이스는 최대 32개의 LU(Logical Unit)를 지원하며, 각 LU는 독립적인 속성(용량, 메모리 타입, Write Protect 등)을 가질 수 있습니다. 제조사마다 기본 LU 배치가 다르며, 출하 시 UFS Provisioning으로 구성합니다.

일반적인 LU 배치 구성
LU 번호용도메모리 타입크기 (예시)비고
LU 0부트 파티션 AEnhanced (SLC)4~32 MB부트로더, 보안 부팅
LU 1부트 파티션 BEnhanced (SLC)4~32 MBA/B 부팅 지원
LU 2메인 사용자 데이터Normal (TLC/QLC)전체 용량의 대부분/data, /system
LU 3RPMBEnhanced (SLC)16~32 MB보안 스토리지
Well-Known LU장치 관리——BOOT, RPMB, Device
# LU 구성 확인
lsscsi -g
# [0:0:0:0]  disk  SAMSUNG  KLUEG4UHDB-B0D1  0400  /dev/sda  /dev/sg0
# [0:0:0:1]  disk  SAMSUNG  KLUEG4UHDB-B0D1  0400  /dev/sdb  /dev/sg1
# [0:0:0:2]  disk  SAMSUNG  KLUEG4UHDB-B0D1  0400  /dev/sdc  /dev/sg2
# [0:0:0:49152] ...  SAMSUNG  KLUEG4UHDB-B0D1       /dev/sg3  ← RPMB W-LU

# 각 LU 속성 확인 (ufs-utils)
ufs-utils desc -a -p /dev/bsg/ufs-bsg0 -t unit -i 0
# bLUEnable: 0x01
# bBootLunID: 0x01
# bLUWriteProtect: 0x00
# bMemoryType: 0x03 (Enhanced 1 = SLC)
# qPhyMemResourceCount: 0x00001000

# Provisioning (LU 재구성) — 주의: 데이터 완전 삭제
ufs-utils desc -w -p /dev/bsg/ufs-bsg0 -t conf -i 0
# bConfigDescrLock이 0x00인 경우에만 가능 (공장 출하 상태)
벤더 식별 방법: UFS 디바이스의 제조사를 코드에서 확인하려면 Device Descriptor의 wManufacturerID 필드를 읽으면 됩니다. 커널은 include/ufs/ufs.h에 주요 벤더 ID를 정의합니다: UFS_VENDOR_SAMSUNG = 0x1CE, UFS_VENDOR_SKHYNIX = 0x1AD, UFS_VENDOR_TOSHIBA = 0x198 (Kioxia), UFS_VENDOR_MICRON = 0x12C. cat /sys/bus/platform/devices/*/device_descriptor/manufacturer_id로 확인하거나, ufs-utils desc -a -p /dev/bsg/ufs-bsg0 -t device로 전체 Device Descriptor를 조회할 수 있습니다.

SCSI 통합 및 블록 레이어 연동

UFS는 SCSI 프로토콜 위에 구축되어 있으므로, Linux 커널에서 UFS 디바이스는 SCSI 서브시스템을 통해 관리됩니다. 애플리케이션의 I/O 요청이 VFS → Block Layer(blk-mq) → SCSI mid-layer → UFS HCD → UFSHCI → M-PHY를 거쳐 UFS 디바이스에 도달하는 전체 경로를 이해하는 것이 UFS 성능 최적화와 디버깅의 핵심입니다.

UFS I/O 경로 — Application → UFS Device 왼쪽 = 계층을 통과하는 데이터 단위 · 가운데 = 처리 계층 · 오른쪽 = 인터럽트 기반 완료 경로 데이터 단위 완료 경로 — Response UPIU Application (read / write) 사용자 프로세스 · libc read()/write() VFS (Virtual File System) file_operations · 페이지 캐시 확인 Block Layer (blk-mq) submit_bio() · bio 병합 → request SCSI Mid-Layer scsi_queue_rq() · CDB 구성 ufshcd (UFS 호스트 드라이버) ufshcd_queuecommand() · UPIU·UTRD·PRDT 구성 ▲ 소프트웨어 (SW) ▼ 하드웨어 (HW) UFSHCI (하드웨어 컨트롤러) UTRLDBR 감시 · UTRD DMA fetch · UPIU 캡슐화 M-PHY / UniPro 링크 T_PDU 직렬화 · HS/PWM-Gear UFS Device (NAND 플래시) UPIU 해석 · FTL 매핑 · NAND 읽기/쓰기 read() / write() 시스템 콜 struct bio — submit_bio() struct request (병합·재정렬) struct scsi_cmnd + CDB Command UPIU + UTRD·PRDT UTRLDBR doorbell → DMA fetch T_PDU (UniPro 프레임) 차동 신호 (HS-Gear 직렬화) scsi_done() — 요청 완료 통지 ufshcd_transfer_req_compl() ufshcd_intr() — 완료 인터럽트 Response UPIU → UCD 기록 ⁃ 실선(↓) = 명령 다운스트림 · 초록 화살표(↑) = 인터럽트 기반 완료(업스트림) · 점선 = 소프트웨어/하드웨어 경계 ⁃ 쓰기 명령은 디바이스가 RTT(Ready To Transfer) UPIU로 데이터 수신을 먼저 요청한 후 데이터 UPIU가 이어집니다

scsi_host_template 정의

UFS 드라이버는 struct scsi_host_template을 통해 SCSI 미드 레이어에 등록됩니다. 이 템플릿은 I/O 처리(queuecommand)와 에러 복구(eh_*_handler) 콜백을 정의합니다.

/* drivers/ufs/core/ufshcd.c — SCSI host template */
static const struct scsi_host_template ufshcd_driver_template = {
    .module                 = THIS_MODULE,
    .name                   = UFSHCD,
    .proc_name              = UFSHCD,
    .map_queues             = ufshcd_map_queues,
    .queuecommand           = ufshcd_queuecommand,
    .mq_poll                = ufshcd_poll,
    .slave_alloc            = ufshcd_slave_alloc,
    .slave_configure        = ufshcd_slave_configure,
    .slave_destroy          = ufshcd_slave_destroy,
    .change_queue_depth     = ufshcd_change_queue_depth,
    .eh_abort_handler       = ufshcd_abort,
    .eh_device_reset_handler = ufshcd_eh_device_reset_handler,
    .eh_host_reset_handler  = ufshcd_eh_host_reset_handler,
    .this_id                = -1,
    .sg_tablesize           = SG_ALL,
    .cmd_per_lun            = UFSHCD_CMD_PER_LUN,
    .can_queue              = UFSHCD_CAN_QUEUE,
    .max_segment_size       = PRDT_DATA_BYTE_COUNT_MAX,
    .max_host_blocked       = 1,
    .track_queue_depth      = 1,
    .skip_settle_delay      = 1,
    .sdev_groups            = ufshcd_driver_groups,
};

queuecommand — I/O 요청 처리

/* ufshcd_queuecommand() — blk-mq → SCSI → UFS 진입점
 * (커널 6.19+: LRB가 hba->lrb 배열에서 scsi_cmd_priv(cmd)로 이동) */
static enum scsi_qc_status ufshcd_queuecommand(struct Scsi_Host *host,
                               struct scsi_cmnd *cmd)
{
    struct ufs_hba *hba = shost_priv(host);
    struct request *rq = scsi_cmd_to_rq(cmd);
    int tag = rq->tag;
    struct ufs_hw_queue *hwq = ufshcd_mcq_req_to_hwq(hba, rq); /* SDB: NULL */
    struct ufshcd_lrb *lrbp = scsi_cmd_priv(cmd);

    /* 1. LRB 설정 + Command UPIU 패킹(CDB 복사 포함) */
    ufshcd_setup_scsi_cmd(hba, cmd,
                          ufshcd_scsi_to_upiu_lun(cmd->device->lun), tag);

    /* 2. UTRD 헤더 구성 (데이터 방향, 암호화 CE/CCI/DUN) */
    ufshcd_prepare_req_desc_hdr(lrbp, &upiu_flags,
                                cmd->sc_data_direction);

    /* 3. Doorbell Ring — HW에 전송 요청 */
    ufshcd_send_command(hba, tag, hwq);

    return 0;
}

/dev/sdX 디바이스 생성 흐름

단계함수/동작설명
1scsi_scan_host()SCSI 호스트에 연결된 LU 스캔 시작
2REPORT LUNS 커맨드UFS 디바이스에 존재하는 LU 목록 조회
3INQUIRY 커맨드각 LU의 디바이스 유형, 벤더 정보 확인
4scsi_add_device()struct scsi_device 생성 및 등록
5sd_probe()SCSI 디스크 드라이버가 LU 인식, gendisk 생성
6device_add_disk()/dev/sdX 블록 디바이스 노드 생성

Well-Known LU (W-LU)

UFS 스펙은 일반 데이터 LU 외에 특수 목적의 Well-Known LU를 정의합니다. 이 W-LU들은 SCSI generic(/dev/sgX) 디바이스로 접근 가능하며, 디바이스 관리, RPMB 접근, 부트 LU 등의 기능을 제공합니다.

W-LUN ID이름용도접근 방식
0x81REPORT LUNS W-LU사용 가능한 LU 목록 보고SCSI REPORT LUNS 명령 자체가 이 W-LU로 발행됨
0xD0UFS Device W-LU디바이스 전체 관리 (전원, Descriptor/Attribute/Flag)/dev/bsg/ufs-bsg0, START STOP UNIT
0xC4RPMB W-LUReplay Protected Memory Block 접근/dev/sgX + SECURITY PROTOCOL IN/OUT
0xB0 / 0xB1Boot W-LU부트 파티션 0/1 (부트로더 저장·백업)READ/WRITE 명령

커널은 UPIU의 W-LUN 값을 그대로 SCSI LUN으로 쓰지 않고, SCSI_W_LUN_BASE(0xC100)에 W-LUN ID를 더한 64비트 확장 주소로 매핑합니다. 예를 들어 Device W-LU(0xD0)는 0xC150 = 십진수 49488이 되어 lsscsi 출력에 큰 LUN 번호로 나타납니다.

blk-mq 큐 매핑: UFS는 blk-mq의 hardware queue를 UFS Transfer Request slot에 매핑합니다. 기존 SDB(Single Doorbell) 모드에서는 하나의 HW 큐만 사용하지만, MCQ(Multi-Circular Queue) 모드에서는 per-CPU 큐를 사용하여 병렬성을 극대화합니다. ufshcd_map_queues()가 이 매핑을 담당하며, MCQ 모드에서는 default, read, poll 3종의 큐 타입을 지원합니다.

MCQ — Multi-Circular Queue (UFS 4.0+)

MCQ(Multi-Circular Queue)는 UFS 4.0 스펙에서 도입된 새로운 큐 모델로, 기존의 Single Doorbell(SDB) 방식을 대체합니다. SDB에서는 모든 I/O 요청이 하나의 UTRL(UTP Transfer Request List)을 공유하고 단일 Doorbell 레지스터로 전송을 트리거했지만, MCQ는 NVMe와 유사한 Submission Queue(SQ)/Completion Queue(CQ) 쌍을 여러 개 생성하여 CPU별 독립적인 I/O 처리를 가능하게 합니다.

MCQ (Multi-Circular Queue) 아키텍처 Tail: CPU가 빈 칸에 SQE 기록 · Head: 컨트롤러가 순서대로 인출 (끝에 도달하면 처음으로 돌아오는 원형 구조) CPU 0 blk-mq 요청 I/O 제출 1 SQE 기록 SQ 0 · Submission Queue 호스트 메모리 링 버퍼 · 깊이 최대 256 0 1 2 3 4 5 6 7 2 Doorbell UFSHCI 4.0+ MCQCAP 지원 · UTP · DMA SQ Tail Doorbell[0] CQ Head Doorbell[0] ESI 벡터[0] → IRQ 3 UPIU UFS 디바이스 UniPro/M-PHY 링크 NAND 매체 4 CQE 기록 5 ESI 인터럽트 6 CQE 인출 후 Head Doorbell 갱신 CQ 0 · Completion Queue 호스트 메모리 링 버퍼 · CQE = 완료 항목 0 1 2 3 4 5 6 7 Tail: 컨트롤러가 CQE 기록 · Head: CPU가 인출 (인출 후 CQ Head Doorbell로 소비 위치 갱신) 이전: SDB — Single DoorBell (UFS 3.1 이하) CPU 0 CPU 1 CPU 2 CPU 3 동시 쓰기 경합 UTRLDBR 단일 Doorbell UFSHCI UTRL 32슬롯 단일 IRQ 집중 • 모든 CPU가 하나의 Doorbell 경쟁 • 단일 인터럽트 → 인터럽트 집중(storm) • 슬롯 최대 32개 고정 — 큐 확장 불가 신규: MCQ — Multi-Circular Queue (UFS 4.0+) CPU 0 CPU 1 CPU 2 CPU 3 SQ/CQ 0 SQ/CQ 1 SQ/CQ 2 SQ/CQ 3 UFSHCI 큐별 Doorbell ESI 벡터별 IRQ IRQ0 IRQ1 IRQ2 IRQ3 • CPU별 독립 큐 → lock 경합 제거 • CQ별 ESI 인터럽트 → 처리 분산 • 큐 깊이 규격 최대 256 · 커널은 nutrs+1
MCQ의 핵심 이점: MCQ는 NVMe의 SQ/CQ 모델을 UFS에 도입한 것입니다. 기존 SDB 모드에서는 모든 CPU가 단일 UTRLDBR(Doorbell Register)에 접근해야 했으므로 spin lock 경쟁이 심했습니다. MCQ는 per-CPU SQ/CQ 쌍을 제공하여 lock contention을 제거하고, CQ별 인터럽트로 인터럽트 처리도 분산합니다. 실측 향상폭은 디바이스·코어 수·워크로드에 따라 달라지지만, 고코어 환경의 랜덤 I/O에서 유의미한 IOPS 개선이 보고됩니다. 큐 깊이는 규격상 최대 256 엔트리까지 가능하며, 커널은 각 HW 큐에 hwq->max_entries = hba->nutrs + 1로 설정합니다.

MCQ 레지스터 셋

레지스터오프셋설명
MCQCAP0x00 (MCQ Base)MCQ Capability — 지원 SQ/CQ 최대 수
SQATTRSQ Base + 0x00Submission Queue 속성 (크기, 타입)
SQLBASQ Base + 0x04SQ Base Address (Lower 32-bit)
SQUBASQ Base + 0x08SQ Base Address (Upper 32-bit)
SQDAOSQ Base + 0x0CSQ Doorbell Address Offset
SQISAOSQ Base + 0x10SQ Interrupt Status Address Offset
CQATTRCQ Base + 0x00Completion Queue 속성 (크기, 인터럽트 벡터)
CQLBACQ Base + 0x04CQ Base Address (Lower 32-bit)
CQUBACQ Base + 0x08CQ Base Address (Upper 32-bit)
CQDAOCQ Base + 0x0CCQ Doorbell Address Offset

MCQ 초기화 및 큐 생성

/* drivers/ufs/core/ufs-mcq.c — MCQ 초기화 핵심 (개념 설명용 의사코드).
 * 실제 구현은 ufshcd_mcq_config_nr_queues(), ufshcd_mcq_alloc_hw_queues(),
 * ufshcd_mcq_make_queues_operational() 등을 참조.
 * 큐당 깊이: hwq->max_entries = hba->nutrs + 1 */
int ufshcd_mcq_init(struct ufs_hba *hba)
{
    int ret, i;
    int num_queues;

    /* 1. MCQ 지원 여부 확인 */
    if (!(hba->mcq_capabilities & MASK_MCQ_SUPPORT))
        return -EOPNOTSUPP;

    /* 2. 큐 수 결정 (online CPU 수 기반) */
    num_queues = min_t(int, num_online_cpus(),
                       hba->mcq_capabilities & MCQ_MAX_QUEUES_MASK);

    /* 3. SQ/CQ 메모리 할당 (DMA coherent) */
    for (i = 0; i < num_queues; i++) {
        ret = ufshcd_mcq_sq_alloc(hba, i);
        if (ret)
            goto err;
        ret = ufshcd_mcq_cq_alloc(hba, i);
        if (ret)
            goto err;
    }

    /* 4. MCQ 레지스터 설정 */
    ufshcd_mcq_config_nr_queues(hba);

    /* 5. 큐 가동 — 링크 스타트업 성공 후 한 번에 수행.
     *    SQATTR/SQLBA/SQUBA/SQDAO/SQISAO 및
     *    CQATTR/CQLBA/CQUBA/CQDAO 레지스터 프로그래밍 포함 */
    ufshcd_mcq_make_queues_operational(hba);

    return 0;
err:
    ufshcd_mcq_release(hba);
    return ret;
}

SQ/CQ 시작/정지 제어

/* SQ 시작 — Tail Doorbell 초기화 */
void ufshcd_mcq_sq_start(struct ufs_hba *hba, struct ufs_hw_queue *hwq)
{
    u32 val;

    /* SQATTR: 큐 크기, 활성화 비트 설정 */
    val = FIELD_PREP(SQ_SIZE_MASK, hwq->max_entries - 1) | SQ_EN;
    writel(val, hba->mmio_base + hwq->sq_attr_offset);

    /* SQ Base Address 설정 */
    writel(lower_32_bits(hwq->sq_dma), hba->mmio_base + hwq->sq_lba_offset);
    writel(upper_32_bits(hwq->sq_dma), hba->mmio_base + hwq->sq_uba_offset);

    /* Tail Pointer 초기화 */
    hwq->sq_tail = 0;
    writel(0, hwq->sq_tail_db);
}

/* SQ 정지(개념도) — 실제 에러 복구는 ufshcd_mcq_sq_cleanup(hba, task_tag)과
 * ufshcd_abort() 흐름에서 처리된다 */
void ufshcd_mcq_sq_stop(struct ufs_hba *hba, struct ufs_hw_queue *hwq)
{
    u32 val;

    val = readl(hba->mmio_base + hwq->sq_attr_offset);
    val &= ~SQ_EN;  /* 비활성화 */
    writel(val, hba->mmio_base + hwq->sq_attr_offset);

    /* SQ가 완전히 정지할 때까지 대기 */
    ufshcd_mcq_poll_sq_stopped(hba, hwq);
}
큐 매핑 전략: MCQ 모드에서 blk-mq의 3종 큐 타입이 SQ에 매핑됩니다. default 큐는 일반 write 요청에, read 큐는 read 요청에, poll 큐는 IRQ 없이 폴링(Polling) 방식으로 완료를 확인하는 저지연 I/O에 사용됩니다. ufshcd_map_queues()에서 blk_mq_map_queues()를 호출하여 CPU → SQ 매핑을 설정합니다.

Write Booster — SLC 캐시 기반 쓰기 가속

Write Booster(WB)는 UFS 3.1 스펙에서 도입된 쓰기 성능 향상 기능입니다. UFS 디바이스 내부의 SLC(Single Level Cell) 모드 영역을 캐시로 활용하여 TLC/QLC NAND의 느린 쓰기 속도를 보상합니다. 호스트 드라이버는 sysfs를 통해 WB를 활성화/비활성화하고, 버퍼 플러시를 제어할 수 있습니다.

Write Booster 원리: TLC NAND는 셀당 3비트를 저장하여 용량은 크지만 쓰기가 느립니다. WB는 일부 영역을 SLC 모드(셀당 1비트)로 운영하여 높은 쓰기 속도를 제공하고, 유휴 시간에 SLC 캐시 데이터를 TLC 영역으로 플러시(flush)합니다. 이는 스마트폰에서 앱 설치, 사진 촬영 등의 버스(Bus)트 쓰기 시나리오에서 큰 성능 이점을 제공합니다.

Dedicated Buffer vs Shared Buffer

모드설명장점단점
Dedicated BufferWB 전용으로 예약된 SLC 영역안정적인 WB 용량 보장, 사용자 데이터 영역과 독립전체 용량 감소 (WB 영역만큼)
Shared Buffer사용자 데이터 영역의 일부를 동적으로 SLC 모드로 전환전용 영역 불필요, 유연한 용량 활용남은 용량에 따라 WB 가용 크기 변동

Write Booster sysfs 인터페이스

sysfs 속성경로R/W설명
wb_on/sys/bus/platform/drivers/ufshcd/*/wb_onR/WWrite Booster 활성화/비활성화 (1/0)
enable_wb_buf_flush동일 경로R/WWB 버퍼 플러시 활성화 (1: 즉시 플러시)
wb_avail_buf동일 경로R현재 사용 가능한 WB 버퍼 크기 (%)
wb_cur_buf동일 경로R현재 사용 중인 WB 버퍼 크기
wb_buf_life_time_est동일 경로RWB 버퍼 수명 추정 (0x00~0x0B)

WB 활성화/비활성화 코드

/* drivers/ufs/core/ufshcd.c — Write Booster 토글 */
int ufshcd_wb_toggle(struct ufs_hba *hba, bool enable)
{
    enum attr_idn attr;
    int ret;

    if (!ufshcd_is_wb_allowed(hba))
        return 0;

    /* on/off 모두 동일 속성 fWriteBoosterEn(QUERY_ATTR_IDN_WB_EN) 사용 */

    ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
                                  attr, 0, 0, &enable);
    if (ret) {
        dev_err(hba->dev, "%s: Write Booster %s failed: %d\n",
                __func__, enable ? "enable" : "disable", ret);
        return ret;
    }

    hba->dev_info.wb_enabled = enable;
    dev_info(hba->dev, "Write Booster %s\n",
             enable ? "enabled" : "disabled");

    return 0;
}

/* WB 버퍼 플러시 트리거 */
static int ufshcd_wb_buf_flush_enable(struct ufs_hba *hba)
{
    int ret;
    u32 val = 1;

    /* fWriteBoosterBufferFlushEn 플래그 설정 */
    ret = ufshcd_query_attr_retry(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
                                  QUERY_ATTR_IDN_WB_BUFF_FLUSH_EN,
                                  0, 0, &val);
    return ret;
}

sysfs 읽기/쓰기 구현

/* drivers/ufs/core/ufs-sysfs.c — wb_on sysfs */
static ssize_t wb_on_show(struct device *dev,
                          struct device_attribute *attr, char *buf)
{
    struct ufs_hba *hba = dev_get_drvdata(dev);
    return sysfs_emit(buf, "%d\n", hba->dev_info.wb_enabled);
}

static ssize_t wb_on_store(struct device *dev,
                           struct device_attribute *attr,
                           const char *buf, size_t count)
{
    struct ufs_hba *hba = dev_get_drvdata(dev);
    bool enable;
    int ret;

    if (kstrtobool(buf, &enable))
        return -EINVAL;

    /* Runtime PM 활성 상태에서만 설정 가능 */
    ufshcd_rpm_get_sync(hba);
    ret = ufshcd_wb_toggle(hba, enable);
    ufshcd_rpm_put_sync(hba);

    return ret ? ret : count;
}
static DEVICE_ATTR_RW(wb_on);
WB 버퍼 수명 주의: SLC 모드의 NAND 셀도 P/E(Program/Erase) 사이클 제한이 있습니다. WB 버퍼는 사용자 데이터 영역보다 빈번하게 쓰기/소거가 반복되므로 수명이 더 빨리 소진됩니다. wb_buf_life_time_est 값이 0x0B(수명 소진)에 도달하면 WB 기능이 자동 비활성화됩니다. 불필요하게 WB를 항상 켜두기보다는 버스트 쓰기 시에만 활성화하는 전략이 권장됩니다.

성능 영향 분석

# Write Booster ON/OFF 성능 비교 (fio 예시)
# WB OFF 상태에서 순차 쓰기 측정
echo 0 > /sys/bus/platform/drivers/ufshcd/*/wb_on
fio --name=seq_write --ioengine=libaio --direct=1 --bs=128k \
    --rw=write --numjobs=1 --size=1G --filename=/dev/sda

# WB ON 상태에서 동일 측정
echo 1 > /sys/bus/platform/drivers/ufshcd/*/wb_on
fio --name=seq_write --ioengine=libaio --direct=1 --bs=128k \
    --rw=write --numjobs=1 --size=1G --filename=/dev/sda

# WB 버퍼 상태 모니터링
cat /sys/bus/platform/drivers/ufshcd/*/wb_avail_buf
cat /sys/bus/platform/drivers/ufshcd/*/wb_cur_buf
cat /sys/bus/platform/drivers/ufshcd/*/wb_buf_life_time_est

HPB — Host Performance Booster

HPB(Host Performance Booster)는 UFS 디바이스의 L2P(Logical to Physical) 매핑 테이블 일부를 호스트 메모리(DRAM)에 캐시하여 읽기 성능을 향상시키는 기능입니다. 일반적으로 UFS 디바이스는 읽기 요청 시 내부 SRAM에 캐시된 L2P 테이블을 참조하는데, 테이블 미스가 발생하면 NAND에서 L2P를 읽어야 하므로 수백 마이크로초의 지연이 추가됩니다. HPB는 호스트가 L2P 정보를 미리 보유하여 이 NAND 접근을 건너뛸 수 있게 합니다.

HPB (Host Performance Booster) 동작 원리 호스트 (Linux 커널) 1 읽기 요청 도착 read() → 블록 계층 → ufshpb_prep() 2 HPB L2P 캐시 조회 (DRAM) ufshpb_prep(): LBA의 Region/Subregion 확인 캐시 히트 → 유효 엔트리에서 PPN 확보 미스 시 → 일반 READ(16)로 폴백 3 HPB READ(16) 명령 생성 CDB에 LBA + PPN + 길이 기록 6 데이터 수신 완료 페이지 캐시 / 사용자 버퍼 복사 UFS 디바이스 UFS 컨트롤러 (FTL) L2P 테이블: SRAM 캐시 + NAND 저장 매핑 미스 시 NAND에서 L2P 로드 (+수백 μs) 내부 L2P 조회 생략 4 명령 수신 → 매핑 조회 생략 수신한 PPN으로 물리 위치 즉시 확정 5 NAND 데이터 페이지 직접 읽기 매핑 조회 없이 PPN 위치로 바로 접근 UFS 버스 (UPIU) HPB READ(16) + PPN 전송 데이터 응답 읽기 지연 비교 (4KB 랜덤 읽기 예시) HPB 미적용 (L2P 매핑 미스) ~200μs HPB 적용 (매핑 조회 생략) ~80μs 0 100μs 200μs 지연 수치는 개념 설명을 위한 예시값 (디바이스·워크로드에 따라 달라짐)

Region과 Subregion 개념

HPB는 UFS 디바이스의 논리적 주소 공간(Address Space)을 Region과 Subregion으로 나누어 관리합니다. Region은 L2P 캐싱의 기본 관리 단위이고, Subregion은 실제 L2P 엔트리를 전송하는 최소 단위입니다. 호스트는 디바이스로부터 HPB 업데이트 알림을 받아 L2P 캐시를 갱신합니다.

개념크기설명
Region일반적으로 512MBL2P 캐시 관리의 단위, 활성화/비활성화 대상
Subregion일반적으로 4MBL2P 엔트리 전송(READ BUFFER) 단위, Region 내 128개
HPB Entry8바이트PPN(Physical Page Number) 4B + Reserved 4B
Active Region디바이스별 상이동시에 호스트 캐시에 유지할 수 있는 Region 수
Pinned Region디바이스별 상이항상 활성 상태를 유지하는 Region (빈번 접근 영역)

HPB sysfs 인터페이스

sysfs 속성R/W설명
hpb_modeR/WHPB 동작 모드 (0: OFF, 1: host-initiated, 2: device-initiated)
hpb_srgn_mem_sizeRSubregion 하나의 L2P 캐시 메모리 크기
hpb_hit_countRHPB 히트 횟수 (성능 모니터링)
hpb_miss_countRHPB 미스 횟수
hpb_active_countR현재 활성화된 Region/Subregion 수

HPB READ(16) 커맨드 구성

/* drivers/ufs/core/ufshpb.c — HPB READ(16) 커맨드 준비 */
void ufshpb_prep(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
{
    struct ufshpb_lu *hpb_lu;
    struct ufshpb_region *rgn;
    struct ufshpb_subregion *srgn;
    u64 ppn;
    unsigned long lpn;
    int transfer_len;

    if (!ufshpb_is_enabled(hba))
        return;

    hpb_lu = ufshpb_get_lu(hba, lrbp->lun);
    if (!hpb_lu)
        return;

    /* 1. LBA → LPN(Logical Page Number) 변환 */
    lpn = ufshpb_get_lpn(lrbp);
    transfer_len = ufshpb_get_len(lrbp);

    /* 2. Region/Subregion 인덱스 계산 */
    rgn = ufshpb_get_region(hpb_lu, lpn);
    srgn = ufshpb_get_subregion(rgn, lpn);

    /* 3. L2P 캐시에서 PPN 조회 */
    if (!ufshpb_get_ppn(srgn, lpn, &ppn)) {
        /* HPB 미스 — 일반 READ(10) 사용 */
        hpb_lu->stats.miss_cnt++;
        return;
    }

    /* 4. HPB READ(16) CDB 구성 */
    hpb_lu->stats.hit_cnt++;
    ufshpb_set_hpb_read_to_upiu(lrbp, lpn, ppn, transfer_len);
}

/* HPB READ(16) CDB 설정 */
static void ufshpb_set_hpb_read_to_upiu(
    struct ufshcd_lrb *lrbp, u64 lpn, u64 ppn, int len)
{
    unsigned char *cdb = lrbp->cmd->cmnd;

    /* Opcode: 0x94 (HPB READ) */
    cdb[0] = HPB_READ_16;
    /* LBA (Big-Endian) */
    put_unaligned_be64(lpn, &cdb[2]);
    /* Transfer Length */
    put_unaligned_be32(len, &cdb[10]);
    /* HPB Entry (PPN) → UPIU에 별도 필드로 전달 */
    lrbp->hpb_entry = ppn;
}

HPB 버전 차이

/* HPB 1.0 (Device-initiated) vs HPB 2.0 (Host-initiated) */

/*
 * HPB 1.0: 디바이스가 주도적으로 Region 활성화/비활성화 결정
 *   - DEV_HPB_RECOMMENDATION UPIU: 디바이스 → 호스트 (Region 추천)
 *   - 호스트는 추천에 따라 READ BUFFER로 L2P 다운로드
 */

/*
 * HPB 2.0: 호스트가 Region 활성화/비활성화를 결정
 *   - 호스트가 접근 패턴 분석 → 자주 접근하는 Region 활성화
 *   - 호스트 주도로 READ BUFFER 발행하여 L2P 캐시
 *   - 더 높은 히트율, 호스트 메모리 관리 최적화 가능
 */
enum ufshpb_mode {
    HPB_MODE_OFF            = 0,
    HPB_MODE_HOST_CONTROL   = 1,  /* HPB 2.0 */
    HPB_MODE_DEVICE_CONTROL = 2,  /* HPB 1.0 */
};
HPB 효과: HPB는 랜덤 읽기 성능에서 가장 큰 효과를 발휘합니다. 특히 데이터베이스(SQLite), 앱 로딩 등 L2P 테이블 미스가 빈번한 워크로드에서 읽기 지연을 크게 단축할 수 있습니다 (정확한 폭은 디바이스·환경 의존). 다만 호스트 DRAM 사용량이 증가하므로 (활성 Region당 수 MB), 메모리 제약이 있는 시스템에서는 활성 Region 수를 제한해야 합니다.
⚠️

업스트림 현황 주의 — HPB 드라이버 제거:

  • 위에서 설명한 ufshpb 드라이버(HPB 1.0)는 커널 5.15에 최초 병합되었으나(2021년), 메인테이너 부재와 제조사별 호환성 이슈로 커널 6.6에서 트리에서 제거되었습니다(2023년 7월, Bart Van Assche). 이후 업스트림 커널은 HPB 경로를 포함하지 않습니다.
  • 후속 표준인 HPB 2.0(Host Control 모드)은 현재까지 업스트림에 병합되지 않았고, 벤더 BSP 커널(Qualcomm / MediaTek / Samsung Exynos)에만 out-of-tree 형태로 유지됩니다.
  • 따라서 본 섹션의 ufshpb_prep(), ufshpb_mode 등의 코드는 병합 당시(~5.15)의 히스토리 참고용이며, 최신 메인라인(6.x 후반~6.15+)에서는 존재하지 않는 인터페이스입니다. HPB 제거 이후의 운영 시스템에서 동일한 성능 이득을 얻으려면 UFS 4.0의 MCQ·디바이스 SRAM 캐시 튠업·읽기 프리페치 설정을 활용해야 합니다.

UFS 4.0 Advanced RPMB (커널 6.11+)

UFS 4.0은 기존 RPMB 왕복을 크게 줄인 Advanced RPMB를 도입했고, 커널 6.11에서 이에 대한 host 측 지원이 본격적으로 정비되었습니다. 전통적인 RPMB는 "write-counter read → authenticated write → result read"의 다단계 UPIU 교환이 필요했지만, Advanced RPMB는 Extra Header Segment(EHS)를 포함한 단일 SECURITY PROTOCOL OUT 커맨드로 인증·nonce·데이터 전송을 함께 수행합니다. ufs_bsg 경로로 노출되어 사용자 공간 도구가 표준화된 BSG SG_IO 인터페이스로 접근할 수 있습니다.

# UFS 4.0 디바이스의 Advanced RPMB 지원 비트 확인
$ ufs-utils desc -t 0 -i 0 -p /dev/bsg/ufs-bsg0
  bAdvancedRPMBVersion: 0x01
  bAdvancedRPMBCapabilities: 0x03   # ARPMB + Secure Write Counter

# BSG ioctl 경로로 ARPMB 단일-round 전송 (예시)
$ ufs-utils arpmb-write -p /dev/bsg/ufs-bsg0 \
    --key /etc/ufs-rpmb.key \
    --data payload.bin --counter-file rpmb.counter

UFS 4.0 MCQ 및 MCQ별 스케줄러 (커널 6.10~6.13)

UFSHCI 4.0 기반 MCQ(Multi-Circular Queue)는 커널 6.10~6.13 구간에서 Qualcomm ufs-qcom, MediaTek ufs-mediatek에 본격 반영되어 실제 상용 제품에서 NVMe 수준의 per-CPU 제출·완료 경로가 열렸습니다. 다음은 관전 포인트입니다.

# MCQ 활성화 플랫폼에서 하드웨어 큐 수 확인
$ cat /sys/class/scsi_host/host0/mcq_nr_hw_queues
8

$ cat /sys/block/sda/queue/nr_hw_queues
8   # blk-mq 하드웨어 컨텍스트 수 (MCQ와 일대일 대응)

$ cat /sys/class/scsi_host/host0/use_mcq_mode
1

# MCQ 지원 컨트롤러에서만 존재하는 sysfs
$ ls /sys/class/scsi_host/host0/
  mcq_nr_hw_queues  mcq_poll_queues  use_mcq_mode  ...

인라인 암호화 — Inline Encryption Framework

UFS 인라인 암호화(Inline Encryption)는 호스트 컨트롤러에 내장된 암호화 엔진(Crypto Engine 또는 ICE)을 활용하여 스토리지 I/O 경로에서 직접 데이터를 암복호화하는 기능입니다. CPU가 소프트웨어적으로 암복호화를 수행하는 대신, 하드웨어가 DMA 전송 과정에서 투명하게 처리하므로 성능 오버헤드가 거의 없습니다. Linux 커널의 blk-crypto 프레임워크와 통합되어 fscrypt, dm-crypt 등에서 활용됩니다.

UFS 인라인 암호화 (Inline Encryption) 데이터 흐름 1 Block I/O 요청 (bio) + bio_crypt_ctx (키슬롯 번호, DUN) 2 ufshcd — UFS 호스트 드라이버 UTRD에 CCI·DUN 기입, DOORBELL 레지스터 작성 UTRD 제출 3 Crypto Engine (ICE) UFS 호스트 컨트롤러 내장 하드웨어 암복호화 엔진 UTRD의 CCI로 키슬롯 선택 · DUN으로 IV(tweak) 생성 AES-256-XTS — DMA 경로 데이터에 투명 적용 (CPU 개입 없음) Keyslot Table — write-only (소프트웨어 읽기 불가) Slot 0 : Key_A (AES-256-XTS) · Slot 1 : Key_B · … 암호문 (쓰기 시) 4 M-PHY / UniPro 링크 암호문만 전송 — 전송 중 평문 노출 없음 5 UFS NAND Flash 암호문 그대로 저장 (at-rest encryption) 읽기 경로는 역방향: NAND → M-PHY → Crypto Engine 복호화 → 호스트 메모리(DMA) 키 프로그래밍 경로 fscrypt / dm-crypt 파일·볼륨 암호화 정책·키 제공 blk-crypto에 등록 blk-crypto 프레임워크 blk_crypto_profile 키슬롯 할당·관리, capability 협상 KEYCFG 레지스터에 키 기입 UTRD = UTP Transfer Request Descriptor · CCI = Crypto Configuration Index · DUN = Data Unit Number ICE = Inline Crypto Engine · FMP = Flash Memory Protector
인라인 암호화 vs 소프트웨어 암호화: 소프트웨어 암호화(dm-crypt fallback)는 CPU에서 데이터를 암복호화하므로 추가 메모리 복사와 CPU 사이클이 필요합니다. 인라인 암호화는 DMA 경로에 암호화 엔진이 삽입되어 데이터가 통과하면서 투명하게 처리됩니다. 결과적으로 순차 읽기/쓰기 성능이 소프트웨어 방식 대비 크게 향상되며, CPU 사용률도 현저히 낮습니다.

지원 알고리즘

알고리즘키 크기블록 크기UFS 스펙 지원비고
AES-256-XTS512비트 (2×256)4096바이트필수Android FBE(File-Based Encryption) 기본
AES-128-XTS256비트 (2×128)4096바이트선택일부 플랫폼에서 지원
AES-256-ECB256비트-선택파일명 암호화용
💡

blk-crypto 프레임워크 전체 구조: blk-crypto 키 관리, ICE keyslot 할당, 소프트웨어 폴백 메커니즘, fscrypt 연동, dm-crypt passthrough 등 인라인 암호화의 전체 스택 아키텍처는 암호화 하드웨어 가속 — 스토리지 인라인 암호화에서 상세히 다룹니다.

blk-crypto 프레임워크 연동

/* drivers/ufs/core/ufshcd-crypto.c — crypto profile 초기화 */
int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba)
{
    struct blk_crypto_profile *profile = &hba->crypto_profile;
    int cap_idx, err;
    unsigned int num_keyslots;

    /* 1. UFSHCI Crypto Capability 레지스터 읽기 */
    hba->crypto_capabilities.reg_val =
        cpu_to_le32(ufshcd_readl(hba, REG_UFS_CCAP));

    num_keyslots = hba->crypto_capabilities.config_count + 1;

    /* 2. blk_crypto_profile 초기화 */
    err = devm_blk_crypto_profile_init(hba->dev, profile, num_keyslots);
    if (err)
        return err;

    profile->ll_ops = ufshcd_crypto_ops;
    profile->max_dun_bytes_supported = 8;  /* 64-bit DUN */

    /* 3. 지원 알고리즘 등록 */
    for (cap_idx = 0; cap_idx < hba->crypto_capabilities.num_crypto_cap; cap_idx++) {
        enum blk_crypto_mode_num blk_mode;
        union ufs_crypto_cap_entry cap;

        cap = hba->crypto_cap_array[cap_idx];

        switch (cap.algorithm_id) {
        case UFS_CRYPTO_ALG_AES_XTS:
            blk_mode = BLK_ENCRYPTION_MODE_AES_256_XTS;
            break;
        default:
            continue;
        }

        profile->modes_supported[blk_mode] |=
            cap.sdus_mask * 512;  /* 지원 데이터 유닛 크기 */
    }

    return 0;
}

UTRD Crypto 필드 설정

/* UTRD에 Crypto 정보 삽입 */
static void ufshcd_prepare_req_desc_hdr_crypto(
    struct ufshcd_lrb *lrbp, struct utp_transfer_req_desc *req_desc)
{
    struct bio_crypt_ctx *bc;
    struct request *rq = scsi_cmd_to_rq(lrbp->cmd);

    if (!rq || !rq->crypt_ctx)
        return;

    bc = rq->crypt_ctx;

    /* CCI (Crypto Configuration Index) — 키슬롯 번호 */
    req_desc->header.crypto_config_index =
        cpu_to_le16(bc->bc_keyslot);

    /* CE (Crypto Enable) 비트 설정 */
    req_desc->header.cci |= UTP_REQ_DESC_CRYPTO_ENABLE;

    /* DUN (Data Unit Number) — IV 생성에 사용 */
    req_desc->header.dunu = cpu_to_le32(
        upper_32_bits(bc->bc_dun[0]));
    req_desc->header.dunl = cpu_to_le32(
        lower_32_bits(bc->bc_dun[0]));
}

플랫폼별 Crypto 구현

/* Qualcomm ICE — ufs-qcom.c */
static int ufs_qcom_ice_program_key(struct ufs_hba *hba,
                                     const union ufs_crypto_cfg_entry *cfg,
                                     int slot)
{
    struct ufs_qcom_host *host = ufshcd_get_variant(hba);
    union {
        u8 bytes[AES_256_XTS_KEY_SIZE];
        u32 words[AES_256_XTS_KEY_SIZE / sizeof(u32)];
    } key;

    /* ICE 키 레지스터에 AES-256-XTS 키 프로그래밍 */
    memcpy(key.bytes, cfg->crypto_key, AES_256_XTS_KEY_SIZE);

    qcom_ice_program_key(host->ice, QCOM_ICE_CRYPTO_ALG_AES_XTS,
                         QCOM_ICE_CRYPTO_KEY_SIZE_256,
                         key.words, cfg->data_unit_size, slot);

    memzero_explicit(&key, sizeof(key));
    return 0;
}

/* Samsung FMP — ufs-exynos.c */
static int exynos_ufs_program_key(struct ufs_hba *hba,
                                   const union ufs_crypto_cfg_entry *cfg,
                                   int slot)
{
    /* FMP(Flash Memory Protector)는 UTRD의 PRDT 엔트리 내에
     * 암호화 키와 IV를 직접 삽입하는 방식을 사용 */
    struct exynos_ufs *ufs = ufshcd_get_variant(hba);
    return exynos_fmp_program_key(ufs->fmp, cfg, slot);
}
fscrypt 연동: Android의 FBE(File-Based Encryption)는 fscrypt를 사용하며, fscrypt는 blk-crypto를 통해 UFS 인라인 암호화를 활용합니다. 파일별 고유 키가 키슬롯에 프로그래밍되고, 파일의 논리적 블록 번호가 DUN으로 사용됩니다. 이를 통해 파일 단위의 세밀한 암호화가 하드웨어 수준에서 이루어집니다. CONFIG_FS_ENCRYPTION_INLINE_CRYPT=y 설정이 필요합니다.

RPMB — Replay Protected Memory Block

RPMB(Replay Protected Memory Block)는 UFS 디바이스 내의 보안 스토리지 영역으로, HMAC-SHA256 인증과 단조 증가 Write Counter를 통해 데이터의 무결성(Integrity)과 재생 공격(Replay Attack) 방지를 보장합니다. RPMB는 Well-Known LU(W-LUN 0xC4)로 접근하며, 보안 부팅(Secure Boot) 인증 정보, 롤백(Rollback) 방지 카운터, 디바이스 키 저장 등에 사용됩니다.

RPMB의 핵심 보안 모델: RPMB는 세 가지 보안 메커니즘을 결합합니다. (1) HMAC-SHA256 인증: 모든 쓰기/읽기 요청에 256비트 인증 키로 서명하여 위변조를 방지합니다. (2) Write Counter: 단조 증가하는 32비트 카운터로 재생 공격을 탐지합니다. (3) Nonce: 읽기 요청에 랜덤 nonce를 포함하여 응답의 신선도(freshness)를 보장합니다.

RPMB 프레임 구조

오프셋크기필드설명
0x0000196바이트Stuff bytes패딩(Padding) (0x00)
0x00C432바이트Key / MACHMAC-SHA256 인증값 또는 키 등록 시 키
0x00E4256바이트Data읽기/쓰기 데이터 (256B 단위)
0x01E416바이트Nonce호스트가 생성한 난수 (읽기 요청 시)
0x01F44바이트Write Counter단조 증가 카운터 (Big-Endian)
0x01F82바이트AddressRPMB 블록 주소 (256B 블록 단위)
0x01FA2바이트Block Count전송할 블록 수
0x01FC2바이트Result응답 결과 코드 (0x0000=성공)
0x01FE2바이트Request / Response Type요청/응답 타입 식별자

RPMB 요청/응답 타입

타입 값이름설명
0x0001Authentication Key WriteRPMB 인증 키 프로그래밍 (최초 1회만)
0x0002Read Write Counter현재 Write Counter 값 조회
0x0003Authenticated Data Write인증된 데이터 쓰기
0x0004Authenticated Data Read인증된 데이터 읽기
0x0005Result Read이전 쓰기 작업의 결과 확인
0x0101Authentication Key Write Response키 쓰기 응답
0x0102Read Write Counter Response카운터 조회 응답
0x0103Authenticated Data Write Response데이터 쓰기·Result Read 결과 응답
0x0104Authenticated Data Read Response데이터 읽기 응답

응답(Request/Response Type) 값은 대응하는 요청 타입에 | 0x0100을 OR한 값입니다. 예를 들어 Authenticated Data Write 요청(0x0003)의 응답은 0x0103이며, 커널 drivers/ufs/core/ufs-rpmb.c(OP-TEE 기반 RPMB 프레임워크 연동)에서도 이 규칙으로 응답 프레임을 검증합니다. Result Read(0x0005)는 별도 응답 타입 없이, 직전 쓰기에 해당하는 0x0103 프레임으로 결과를 회수합니다.

RPMB SCSI 커맨드

/* RPMB 접근은 SECURITY PROTOCOL IN/OUT SCSI 커맨드를 사용 */

/* SECURITY PROTOCOL OUT — RPMB 요청 전송 */
/*
 * CDB (12 bytes):
 * [0] = 0xB5 (SECURITY PROTOCOL OUT)
 * [1] = 0xEC (Security Protocol: UFS)
 * [2-3] = 0x0001 (Security Protocol Specific: RPMB)
 * [4] = 0x00 (INC_512 = 0)
 * [5] = 0x00
 * [6-9] = Transfer Length
 * [10-11] = 0x00
 */

/* SECURITY PROTOCOL IN — RPMB 응답 수신 */
/*
 * CDB (12 bytes):
 * [0] = 0xA2 (SECURITY PROTOCOL IN)
 * [1] = 0xEC (Security Protocol: UFS)
 * [2-3] = 0x0001 (Security Protocol Specific: RPMB)
 * [4] = 0x00 (INC_512 = 0)
 * [5] = 0x00
 * [6-9] = Allocation Length
 * [10-11] = 0x00
 */

/* RPMB 인증 쓰기 시퀀스 */
static int ufs_rpmb_authenticated_write(struct ufs_hba *hba,
                                         u8 *data, u16 addr,
                                         u16 block_count, u8 *key)
{
    struct rpmb_frame frame;
    u8 mac[32];
    int ret;

    memset(&frame, 0, sizeof(frame));

    /* 1. Write Counter 읽기 */
    frame.req_resp_type = cpu_to_be16(0x0002);  /* Read Counter */
    ret = ufs_rpmb_security_out(hba, &frame, sizeof(frame));
    ret = ufs_rpmb_security_in(hba, &frame, sizeof(frame));

    /* 2. 쓰기 프레임 구성 */
    frame.req_resp_type = cpu_to_be16(0x0003);  /* Auth Write */
    frame.write_counter = frame.write_counter;   /* 응답에서 받은 값 */
    frame.address = cpu_to_be16(addr);
    frame.block_count = cpu_to_be16(block_count);
    memcpy(frame.data, data, block_count * 256);

    /* 3. HMAC-SHA256 계산 */
    hmac_sha256(key, 32, (u8 *)&frame.data,
                284,  /* data + nonce + counter + addr + count + type */
                mac);
    memcpy(frame.key_mac, mac, 32);

    /* 4. SECURITY PROTOCOL OUT 전송 */
    ret = ufs_rpmb_security_out(hba, &frame, sizeof(frame));

    /* 5. Result 확인 (SECURITY PROTOCOL IN) */
    frame.req_resp_type = cpu_to_be16(0x0005);  /* Result Read */
    ret = ufs_rpmb_security_out(hba, &frame, sizeof(frame));
    ret = ufs_rpmb_security_in(hba, &frame, sizeof(frame));

    if (be16_to_cpu(frame.result) != 0x0000)
        return -EIO;

    return 0;
}

ufs_bsg 인터페이스

/* drivers/ufs/core/ufs-bsg.c — BSG를 통한 RPMB 접근 */

/*
 * 사용자 공간에서 /dev/bsg/ufs-bsg0 또는 /dev/0:0:0:49476을 통해
 * RPMB 커맨드를 전송할 수 있습니다.
 *
 * W-LUN 0xC4는 SCSI LUN 49476 (0xC140)으로 매핑됩니다.
 * (UFS W-LUN → SCSI LUN 변환 규칙 적용)
 */

/* BSG RPMB 요청 처리 */
static int ufs_bsg_request(struct bsg_job *job)
{
    struct ufs_hba *hba = shost_priv(dev_to_shost(job->dev->parent));
    struct ufs_bsg_request *bsg_request = job->request;
    struct ufs_bsg_reply *bsg_reply = job->reply;
    int ret;

    switch (bsg_request->msgcode) {
    case UPIU_TRANSACTION_QUERY_REQ:
        ret = ufshcd_exec_raw_upiu_cmd(hba, ...);
        break;
    case UPIU_TRANSACTION_NOP_OUT:
        ret = ufshcd_exec_raw_upiu_cmd(hba, ...);
        break;
    default:
        ret = -ENOTSUPP;
    }

    bsg_reply->result = ret;
    job->reply_len = sizeof(*bsg_reply);
    bsg_job_done(job, ret, 0);
    return 0;
}

UFS RPMB v2 vs v3

특성RPMB v2 (UFS 2.x)RPMB v3 (UFS 3.x+)
최대 크기16MB64MB
블록 크기256바이트4KB (선택적)
Multi-block Write미지원 (1블록씩)지원 (최대 64블록)
인증 알고리즘HMAC-SHA256HMAC-SHA256
Write Counter32비트32비트
Atomic Write미보장Power-fail safe 보장

사용자 공간에서 RPMB 접근

# ufs-utils를 사용한 RPMB 접근 예시

# 1. RPMB 키 프로그래밍 (주의: 최초 1회만 가능, 변경 불가!)
ufs-utils rpmb write-key /dev/bsg/ufs-bsg0 --key-file rpmb_key.bin

# 2. Write Counter 읽기
ufs-utils rpmb read-counter /dev/bsg/ufs-bsg0 --key-file rpmb_key.bin

# 3. 데이터 쓰기
ufs-utils rpmb write /dev/bsg/ufs-bsg0 --key-file rpmb_key.bin \
    --address 0 --data-file data.bin

# 4. 데이터 읽기
ufs-utils rpmb read /dev/bsg/ufs-bsg0 --key-file rpmb_key.bin \
    --address 0 --block-count 1 --out-file output.bin

# 5. RPMB 상태 확인
cat /sys/class/scsi_device/0:0:0:49476/device/model
RPMB 키 프로그래밍은 비가역적입니다: RPMB 인증 키는 한 번 프로그래밍되면 변경할 수 없습니다. 키를 분실하면 해당 RPMB 영역에 더 이상 쓰기가 불가능합니다. Android 기기에서는 TEE(Trusted Execution Environment)가 키를 관리하며, 일반적으로 공장 출하 시 프로그래밍됩니다. RPMB는 보안 부팅의 롤백 방지(anti-rollback), Keymaster/Gatekeeper의 키 저장, Verified Boot 상태 보존 등에 핵심적으로 사용됩니다.

커널 표준 RPMB 프레임워크와 ufs-rpmb.c (6.15 → 6.19)

커널 6.15에 범용 RPMB 프레임워크(CONFIG_RPMB)가 들어오면서 eMMC·UFS·NVMe 어느 쪽 RPMB든 동일한 커널 내부 API로 다룰 수 있게 되었고, 커널 6.19의 drivers/ufs/core/ufs-rpmb.c("UFS OP-TEE based RPMB Driver")가 UFS를 이 프레임워크에 연결했습니다. OP-TEE처럼 커널 안에서 RPMB가 필요한 서브시스템은 더 이상 스토리지 종류를 알 필요가 없습니다.

/* include/linux/rpmb.h — 하위 드라이버가 채우는 디스크립터 (발췌) */
struct rpmb_descr {
    enum rpmb_type type;        /* RPMB_TYPE_EMMC / UFS / NVME */
    int (*route_frames)(struct device *dev, u8 *req, unsigned int req_len,
                        u8 *resp, unsigned int resp_len);
    u8  *dev_id;                /* 인증 키 유도에 쓰는 하드웨어 고유 ID */
    size_t dev_id_len;
    u16 reliable_wr_count;      /* 한 번에 reliable write 가능한 섹터 수 */
    u16 capacity;               /* 용량 (128KB 단위) */
};

/* 표준 512바이트 RPMB 프레임 (모든 필드 빅엔디안) */
struct rpmb_frame {
    u8     stuff[196];          /* 패딩 — 프레임 길이를 512바이트로 맞춤 */
    u8     key_mac[32];         /* 인증 키 또는 HMAC-SHA256 MAC */
    u8     data[256];
    u8     nonce[16];
    __be32 write_counter;
    __be16 addr;                /* 256바이트(half sector) 블록 단위 주소 */
    __be16 block_count;
    __be16 result;
    __be16 req_resp;            /* 요청/응답 타입 — 프레임 마지막 필드 */
};

ufs-rpmb.c의 등록 흐름은 다음과 같습니다.

프레임 전송은 SECURITY PROTOCOL IN/OUT 커맨드로 이루어집니다. 주목할 점은 Security Protocol Specific(SPS) 필드에 리전 번호가 포함된다는 것입니다.

/* drivers/ufs/core/ufs-rpmb.c — 프레임 라우팅 (발췌·단순화) */
#define UFS_RPMB_SEC_PROTOCOL     0xEC  /* JEDEC UFS 애플리케이션 프로토콜 */
#define UFS_RPMB_SEC_PROTOCOL_ID  0x01  /* JEDEC UFS RPMB 프로토콜 ID */

static int ufs_sec_submit(struct ufs_hba *hba, u16 spsp, void *buffer,
                          size_t len, bool send)
{
    struct scsi_device *sdev = hba->ufs_rpmb_wlun;
    u8 cdb[12] = { };

    cdb[0] = send ? SECURITY_PROTOCOL_OUT : SECURITY_PROTOCOL_IN;
    cdb[1] = UFS_RPMB_SEC_PROTOCOL;
    put_unaligned_be16(spsp, &cdb[2]);  /* 상위 바이트=리전, 하위 바이트=0x01 */
    put_unaligned_be32(len, &cdb[6]);

    return scsi_execute_cmd(sdev, cdb,
                            send ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN,
                            buffer, len, 30 * HZ, 0, NULL);
}

static int ufs_rpmb_route_frames(struct device *dev, u8 *req,
                                 unsigned int req_len, u8 *resp,
                                 unsigned int resp_len)
{
    req_type = get_unaligned_be16(&frm_out->req_resp);

    switch (req_type) {
    case RPMB_PROGRAM_KEY:       /* 1프레임 요청 -> 1프레임 응답 */
        break;
    case RPMB_GET_WRITE_COUNTER: /* 결과 읽기 불필요 */
        need_result_read = false;
        break;
    case RPMB_WRITE_DATA:        /* N프레임 요청(512B 배수) -> 1프레임 응답 */
        break;
    case RPMB_READ_DATA:         /* 1프레임 요청 -> N프레임 응답 */
        need_result_read = false;
        break;
    }

    protocol_id = ufs_rpmb->region_id << 8 | UFS_RPMB_SEC_PROTOCOL_ID;

    ret = ufs_sec_submit(hba, protocol_id, req, req_len, true);   /* 1. 요청 전송 */
    if (need_result_read) {                                       /* 2. 결과 읽기 */
        frm_resp->req_resp = cpu_to_be16(RPMB_RESULT_READ);
        ret = ufs_sec_submit(hba, protocol_id, resp, resp_len, true);
    }
    ret = ufs_sec_submit(hba, protocol_id, resp, resp_len, false); /* 3. 응답 수신 */
    return ret;
}
요청 타입값요청 프레임응답 프레임결과 읽기(RPMB_RESULT_READ)
PROGRAM_KEY0x000111필요
GET_WRITE_COUNTER0x000211불필요
WRITE_DATA0x0003N (512B 배수)1필요
READ_DATA0x00041N (512B 배수)불필요
SPS 필드와 멀티 리전: SECURITY PROTOCOL 커맨드의 SPSpecific 필드(16비트) 상위 바이트에 RPMB 리전 번호가, 하위 바이트에 프로토콜 ID 0x01이 들어갑니다. 즉 리전 0은 0x0001, 리전 1은 0x0101입니다. 앞선 "RPMB SCSI 커맨드" 절의 CDB 예시(0x0001)는 리전 0을 가리킵니다.

전원 관리 (Power Management)

UFS 전원 관리는 모바일 디바이스의 배터리 수명에 직접적인 영향을 미치는 핵심 요소입니다. UFS 호스트 컨트롤러(ufshcd)는 Linux 커널의 Runtime PM 프레임워크와 통합되어, 유휴 시 자동으로 링크 상태를 전환하고 클럭을 게이팅하여 전력 소비를 최소화합니다.

UFS 전원 상태 전이 (Power State Transitions) 주황(↓) = 절전 진입 · 녹색(↑) = Active 복귀 · 아래로 갈수록 전력 소비·복귀 지연 증가 전력 소비 감소 · 복귀 지연 증가 Active Link Up (HS-Gear) · 클럭 ON VCC / VCCQ ON Idle (Clock Gated) Link Up · 클럭 OFF VCCQ ON · 복귀 ~10µs Hibernate (Hibern8) Link Hibern8 · 클럭 OFF VCCQ ON · 복귀 ~100µs Sleep (UFS-Sleep) Link Off/H8 · 클럭 OFF VCCQ OFF · 복귀 ~1ms PowerDown Link Off · VCC OFF VCCQ OFF · 복귀 ~100ms gate_delay 만료 (~150ms) auto_hibern8 또는 rpm_suspend system_suspend (spm_lvl=3) SSU 3(UFS-Sleep) system_suspend (spm_lvl=5 · SSU 4) I/O 요청 (clock ungate) hibern8_exit I/O 발생 또는 ungate system_resume SSU 1(Active) · link exit system_resume full re-init (~100ms) Active · 최대 Clock Gated · 중간 Hibern8 · 낮음 Sleep · 매우 낮음 PowerDown · ~0 디바이스 전원 모드 전환은 SSU(START STOP UNIT, 0x1B) 커맨드로 수행 — Active=1 · Idle=2 · Sleep=3 · PowerDown=4 shutdown · 에러 복구 같은 긴급 경로에서는 중간 단계를 건너뛰고 SSU 4(PowerDown)가 직접 발행될 수 있습니다.

전원 모드 비교

UFS 전원 모드 특성
전원 모드링크 상태클럭VCCVCCQ/VCCQ2복귀 지연전력 소비
ActiveUp (LS-HS)ONONON—최대
Clock GatedUpOFF (gated)ONON~10 µs중간
Hibernate (H8)Hibern8OFFONON~100 µs낮음
Sleep (DeepSleep)Off / H8OFFONOFF~1 ms매우 낮음
PowerDownOffOFFOFFOFF~100 ms (재초기화)~0

클럭 게이팅 (Clock Gating)

UFS 호스트 드라이버는 일정 시간 유휴 상태(Idle State)가 지속되면 자동으로 클럭을 차단합니다. ufshcd_gate_clk_work()는 gate_delay(기본 150ms)가 경과한 후 실행되어, 호스트 컨트롤러와 PHY 클럭을 순차적으로 게이팅합니다.

/* drivers/ufs/core/ufshcd.c — 클럭 게이팅 워크 */
static void ufshcd_gate_work(struct work_struct *work)
{
    struct ufs_hba *hba = container_of(work, struct ufs_hba,
                                       clk_gating.gate_work);
    unsigned long flags;

    spin_lock_irqsave(hba->host->host_lock, flags);

    /* 아직 활성 요청이 있으면 게이팅 취소 */
    if (hba->clk_gating.active_reqs ||
        hba->clk_gating.state == CLKS_ON) {
        spin_unlock_irqrestore(hba->host->host_lock, flags);
        return;
    }

    /* 클럭 상태 전이: REQ_CLKS_OFF → CLKS_OFF */
    hba->clk_gating.state = CLKS_OFF;
    spin_unlock_irqrestore(hba->host->host_lock, flags);

    /* 실제 클럭 비활성화 */
    ufshcd_setup_clocks(hba, false);
}

/* 클럭 언게이팅: I/O 요청 시 자동 호출 */
static void ufshcd_ungate_work(struct work_struct *work)
{
    struct ufs_hba *hba = container_of(work, struct ufs_hba,
                                       clk_gating.ungate_work);

    cancel_delayed_work_sync(&hba->clk_gating.gate_work);

    /* 클럭 재활성화 */
    ufshcd_setup_clocks(hba, true);

    /* Hibern8 상태였으면 exit 수행 */
    if (ufshcd_is_link_hibern8(hba))
        ufshcd_uic_hibern8_exit(hba);
}

Hibernate (H8) 진입/탈출

Hibernate(H8)는 UniPro 링크를 저전력 상태로 전환하는 UFS 고유 기능입니다. Auto-Hibernate8 기능을 사용하면 하드웨어가 유휴 타이머(Timer) 만료 시 자동으로 H8에 진입하고, 새로운 UTRD/UTMRD 발행 시 자동으로 탈출합니다.

/* drivers/ufs/core/ufshcd.c — Hibernate8 진입 */
int ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
{
    struct uic_command uic_cmd = {0};
    int ret;

    uic_cmd.command = UIC_CMD_DME_HIBER_ENTER;
    ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
    if (ret) {
        dev_err(hba->dev, "hibern8 enter failed: %d\n", ret);
        /* 에러 복구 시도 */
        ufshcd_update_evt_hist(hba, UFS_EVT_HIB_ERR, ret);
        return ret;
    }
    hba->ufs_stats.hibern8_enter_cnt++;
    return 0;
}

/* Auto-Hibernate8 설정 (UFSHCI 3.0+) */
void ufshcd_auto_hibern8_enable(struct ufs_hba *hba)
{
    u32 ahit;

    if (!ufshcd_is_auto_hibern8_supported(hba))
        return;

    /* 타이머 값 설정: scale(10us/100us/1ms/10ms) + timer_val */
    ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, hba->ahit) |
           FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, UFSHCI_AHIBERN8_1MS);

    ufshcd_writel(hba, ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
}

Runtime PM 통합

UFS 드라이버는 Linux Runtime PM과 완전히 통합됩니다. rpm_lvl과 spm_lvl sysfs 속성으로 Runtime Suspend와 System Suspend 시의 전원 레벨을 독립적으로 제어할 수 있습니다.

/* drivers/ufs/core/ufshcd.c — System Suspend/Resume */
int ufshcd_system_suspend(struct device *dev)
{
    struct ufs_hba *hba = dev_get_drvdata(dev);
    int ret;

    /* 이미 Runtime Suspended 상태면 추가 작업 불필요 */
    if (pm_runtime_suspended(hba->dev))
        return 0;

    ret = ufshcd_suspend(hba, UFS_SYSTEM_PM);
    if (ret)
        dev_err(hba->dev, "system suspend failed: %d\n", ret);
    return ret;
}

int ufshcd_system_resume(struct device *dev)
{
    struct ufs_hba *hba = dev_get_drvdata(dev);

    if (pm_runtime_suspended(hba->dev))
        return 0;

    return ufshcd_resume(hba, UFS_SYSTEM_PM);
}

/* 내부 suspend 함수 — rpm_lvl/spm_lvl에 따라 동작 결정 */
static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
    enum ufs_pm_level pm_lvl;
    enum ufs_dev_pwr_mode req_dev_pwr_mode;
    enum uic_link_state req_link_state;

    pm_lvl = (pm_op == UFS_RUNTIME_PM) ?
             hba->rpm_lvl : hba->spm_lvl;

    req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
    req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);

    /* BKOPS flush → SSU(PowerMode) → Hibern8/Off → Clock gate */
    if (req_dev_pwr_mode != hba->curr_dev_pwr_mode)
        ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);

    if (req_link_state == UIC_LINK_HIBERN8_STATE)
        ufshcd_uic_hibern8_enter(hba);

    ufshcd_setup_clocks(hba, false);
    return 0;
}

Gear Scaling (동적 기어 변경)

Gear scaling은 부하에 따라 UFS 링크 속도(Gear)를 동적으로 조절하는 기능입니다. 높은 I/O 부하 시 HS-G4로 올리고, 유휴 시 HS-G1으로 내려 전력을 절감합니다. devfreq 프레임워크와 통합되어 자동으로 동작합니다.

# Gear scaling 상태 확인 — 메인라인 표준 경로는 devfreq
ls /sys/class/devfreq/ | grep -i ufs
cat /sys/class/devfreq/1d84000.ufshc/cur_freq    # 현재 동작 주파수
cat /sys/class/devfreq/1d84000.ufshc/governor    # 스케일링 거버너

# Gear scaling 일시 고정 (디버깅/벤치마크 시)
echo performance > /sys/class/devfreq/1d84000.ufshc/governor

# 현재 링크 속도 확인 — 부팅 로그의 기어 협상 결과 활용
dmesg | grep -iE "ufshc.*(gear|lane)" | tail -3
# 출력 예: HS-G4B (2-lane) 연결
전원 관리와 배터리 수명: 적절한 UFS 전원 관리는 모바일 디바이스 배터리 수명에 결정적입니다. Auto-Hibernate8 타이머를 너무 길게 설정하면 전력 낭비가, 너무 짧게 설정하면 H8 진입/탈출 오버헤드로 인한 지연이 발생합니다. 일반적으로 5~15ms가 적절한 타이머 값으로 권장됩니다. rpm_lvl=5 (dev:PowerDown, link:Off)는 전력 절감이 크지만 복귀 시간이 길어 모바일에서는 rpm_lvl=3 (dev:Sleep, link:Hibern8)을 주로 사용합니다.

디바이스 건강 모니터링 (Device Health Monitoring)

UFS 디바이스는 내장 건강 지표를 제공하여 수명 예측과 사전 장애 대응을 가능하게 합니다. bDeviceLifeTimeEstA/B와 bPreEOLInfo 속성은 디바이스 디스크립터를 통해 조회하며, 임계치를 초과하면 Exception Event를 통해 호스트에 알립니다.

수명 추정치 (Device Life Time Estimation)

UFS 디바이스는 두 가지 수명 추정 속성을 제공합니다. bDeviceLifeTimeEstA는 SLC 영역(Boot/RPMB 포함)의 수명을, bDeviceLifeTimeEstB는 MLC/TLC 사용자 데이터 영역의 수명을 나타냅니다.

bDeviceLifeTimeEstA/B 값 해석
값수명 사용률의미권장 대응
0x00정보 없음디바이스가 수명 정보를 지원하지 않음—
0x010~10%정상 — 수명 충분모니터링 유지
0x0210~20%정상모니터링 유지
0x0320~30%정상모니터링 유지
0x0430~40%정상모니터링 유지
0x0540~50%중간 수명교체 계획 수립
0x0650~60%중간 수명교체 계획 수립
0x0760~70%주의교체 일정 확정
0x0870~80%주의교체 준비
0x0980~90%경고즉시 교체 준비
0x0A90~100%위험즉시 교체 필요
0x0B100% 초과수명 초과즉시 데이터 백업 및 교체

Pre-EOL 정보 (bPreEOLInfo)

bPreEOLInfo는 디바이스의 예비 블록 상태를 세 단계로 간략화한 지표입니다. 이 값이 0x02(Warning) 이상이면 BKOPS 빈도 증가, 쓰기 앰플리피케이션 악화, 갑작스러운 성능 저하가 발생할 수 있습니다.

bPreEOLInfo 값 및 대응
값상태설명대응
0x00미정의정보 제공하지 않음—
0x01Normal예비 블록 충분정상 운용
0x02Warning예비 블록 소진 경고 (80% 이상 사용)교체 일정 수립, BKOPS 모니터링 강화
0x03Urgent예비 블록 거의 소진 (90% 이상 사용)즉시 백업 및 교체 실행

Exception Events

UFS 디바이스는 긴급 상황 시 Exception Event를 호스트에 통보합니다. 호스트 드라이버(ufshcd)는 이 이벤트를 인터럽트로 수신하고 적절한 대응 동작을 수행합니다.

/* drivers/ufs/core/ufshcd.c — Exception Event 처리 */
static void ufshcd_exception_event_handler(struct work_struct *work)
{
    struct ufs_hba *hba = container_of(work, struct ufs_hba,
                                       eeh_work);
    u32 status = 0;

    /* Exception Event Status 읽기 */
    ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
                      QUERY_ATTR_IDN_EE_STATUS, 0, 0, &status);

    /* Urgent BKOPS 필요 */
    if (status & MASK_EE_URGENT_BKOPS)
        ufshcd_bkops_exception_event_handler(hba);

    /* 동적 용량 부족 */
    if (status & MASK_EE_URGENT_TEMP)
        dev_warn(hba->dev, "UFS: device temperature warning!\n");

    /* Write Booster 버퍼 부족 */
    if (status & MASK_EE_WRITEBOOSTER_EVENT)
        ufshcd_wb_exception_event_handler(hba);
}

/* BKOPS (Background Operations) 관리 */
static int ufshcd_bkops_ctrl(struct ufs_hba *hba,
                             enum bkops_status status)
{
    int err;
    u32 curr_status = 0;

    err = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_READ_ATTR,
                            QUERY_ATTR_IDN_BKOPS_STATUS,
                            0, 0, &curr_status);
    if (err)
        return err;

    /*
     * BKOPS Status:
     *   0x00 = No operations needed
     *   0x01 = Non-critical (수행 권장)
     *   0x02 = Performance impacted (즉시 수행 필요)
     *   0x03 = Critical (즉시 수행, 커맨드 처리 불가 수준)
     */
    if (curr_status >= BKOPS_STATUS_PERF_IMPACT) {
        err = ufshcd_enable_auto_bkops(hba);
        dev_info(hba->dev, "BKOPS critical: status=%d, enabled auto\n",
                 curr_status);
    }
    return err;
}

건강 정보 읽기

# sysfs를 통한 건강 정보 읽기
cat /sys/bus/platform/devices/*/health_descriptor/life_time_estimation_a
cat /sys/bus/platform/devices/*/health_descriptor/life_time_estimation_b
cat /sys/bus/platform/devices/*/health_descriptor/eol_info

# ufs-utils를 사용한 건강 정보 조회
ufs-utils desc -a -p /dev/bsg/ufs-bsg0 -t health
# 출력 예:
#   bLength: 0x25
#   bDescriptorIDN: 0x09
#   bPreEOLInfo: 0x01
#   bDeviceLifeTimeEstA: 0x02
#   bDeviceLifeTimeEstB: 0x01

# Query UPIU로 직접 읽기 (bPreEOLInfo)
ufs-utils attr -a -p /dev/bsg/ufs-bsg0 -t 2 -i 0x02
# bPreEOLInfo at Health Descriptor offset 0x02

모니터링 스크립트

#!/bin/bash
# UFS 건강 모니터링 스크립트
# 주기적으로 실행하여 디바이스 상태를 추적

UFS_SYSFS="/sys/bus/platform/devices/1d84000.ufshc"
LOG="/var/log/ufs_health.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# 건강 디스크립터 읽기
LIFE_A=$(cat "${UFS_SYSFS}/health_descriptor/life_time_estimation_a" 2>/dev/null)
LIFE_B=$(cat "${UFS_SYSFS}/health_descriptor/life_time_estimation_b" 2>/dev/null)
EOL=$(cat "${UFS_SYSFS}/health_descriptor/eol_info" 2>/dev/null)

echo "[${TIMESTAMP}] LifeA=${LIFE_A} LifeB=${LIFE_B} EOL=${EOL}" >> "${LOG}"

# 경고 조건 확인
if [ "${EOL}" = "0x02" ] || [ "${EOL}" = "0x03" ]; then
    echo "[${TIMESTAMP}] WARNING: PreEOL=${EOL} — 디바이스 교체 필요!" >> "${LOG}"
    # 알림 전송 (선택)
    logger -p user.crit "UFS PreEOL Warning: ${EOL}"
fi

if [ "${LIFE_A}" = "0x0A" ] || [ "${LIFE_A}" = "0x0B" ] ||
   [ "${LIFE_B}" = "0x0A" ] || [ "${LIFE_B}" = "0x0B" ]; then
    echo "[${TIMESTAMP}] CRITICAL: Lifetime exceeded 90%!" >> "${LOG}"
    logger -p user.crit "UFS Lifetime Critical: A=${LIFE_A} B=${LIFE_B}"
fi
PreEOLInfo 경고를 무시하면 안 됩니다: bPreEOLInfo가 0x02(Warning) 또는 0x03(Urgent)인 상태에서 계속 사용하면, 예비 블록 완전 소진 시 갑작스러운 읽기 전용(Read-Only) 전환 또는 디바이스 응답 불가가 발생할 수 있습니다. 특히 임베디드/자동차 시스템에서는 정기적 건강 모니터링과 사전 교체 정책이 필수입니다. 프로덕션 환경에서는 bDeviceLifeTimeEstA/B ≥ 0x08 시점에 교체 알람을 설정하는 것을 권장합니다.

디바이스 트리 바인딩 (Device Tree Bindings)

UFS 호스트 컨트롤러의 디바이스 트리 바인딩은 하드웨어 자원(레지스터, 인터럽트, 클럭, 레귤레이터, PHY)과 플랫폼별 설정을 선언합니다. 정확한 DT 설정은 UFS 초기화 성공과 최적 성능의 전제 조건입니다.

UFS 디바이스 트리 노드 구조 ufshc@1d84000 UFS 호스트 컨트롤러 (UFS Host Controller) compatible = "qcom,sm8550-ufshc" reg · interrupts · freq-table-hz 클럭 (Clocks) clocks = <&gcc …> clock-names = "core_clk", "bus_aggr_clk", "ref_clk", "phy_aux_clk", … 레귤레이터 (Regulators) vcc-supply = <&vreg_l7b> vccq-supply = <&vreg_l9b> vccq2-supply = <&vreg_s4a> *-max-microamp 전류 상한 PHY (phys) phys = <&ufs_phy> phy-names = "ufshc" M-PHY 레인 연결 (PWM/G1~G7) 우측 하단 phy@1d87000 참조 리셋 (Resets) resets = <&gcc …> reset-names = "rst" 프로브 시 컨트롤러/PHY 리셋 &ufs_phy phandle 참조 주요 DT 속성 예시 — ufshc@1d84000 ufshc@1d84000 {     compatible = "qcom,sm8550-ufshc";     reg = <0x01d84000 0x3000>;     interrupts = <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>;     freq-table-hz = <75000000 300000000>,                 <75000000 300000000>;     vcc-supply   = <&vreg_l7b_2p96>;     vccq-supply  = <&vreg_l9b_1p2>;     vccq2-supply = <&vreg_s4a_1p8>;     resets = <&gcc GCC_UFS_PHY_BCR>;     clocks = <&gcc GCC_UFS_PHY_AXI_CLK>, …;     phys   = <&ufs_phy>; }; ufs_phy: phy@1d87000 compatible = "qcom,sm8550-qmp-ufs-phy" reg = <…>, #phy-cells = <0> vdda-phy-supply, vdda-pll-supply clocks = <&gcc …>, resets = <&gcc …> QMP UFS PHY — SerDes 초기화 담당 phandle 참조 관계 ufshc 노드는 phys = <&ufs_phy>로 위 ufs_phy 노드의 phandle을 참조하고, 드라이버는 devm_phy_get()으로 획득합니다.

표준 DT 속성

UFS 호스트 컨트롤러 DT 속성
속성타입필수설명예시 값
compatiblestringO플랫폼별 호환 문자열"qcom,sm8550-ufshc"
regu32 arrayOMMIO 레지스터 베이스 및 크기<0x1d84000 0x3000>
interruptsu32 arrayO인터럽트 라인<GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>
clocksphandle arrayO클럭 소스 참조<&gcc GCC_UFS_PHY_AXI_CLK>, ...
clock-namesstring arrayO클럭 이름"core_clk", "bus_aggr_clk", ...
freq-table-hzu32 arrayO클럭 주파수 범위 (min max 쌍)<75000000 300000000>
vcc-supplyphandleOVCC 레귤레이터 (2.7~3.6V)<&vreg_l7b_2p96>
vccq-supplyphandle선택VCCQ 레귤레이터 (1.14~1.26V)<&vreg_l9b_1p2>
vccq2-supplyphandle선택VCCQ2 레귤레이터 (1.7~1.95V)<&vreg_s4a_1p8>
vcc-max-microampu32선택VCC 최대 전류<800000>
physphandleOM-PHY 참조<&ufs_mem_phy>
phy-namesstringOPHY 이름"ufsphy"
resetsphandle선택리셋 컨트롤러 참조<&gcc GCC_UFS_PHY_BCR>
reset-namesstring선택리셋 이름"rst"

Qualcomm 플랫폼 예시 (SM8550)

/* Qualcomm SM8550 UFS 호스트 컨트롤러 DT 노드 */
ufshc@1d84000 {
    compatible = "qcom,sm8550-ufshc", "qcom,ufshc", "jedec,ufs-2.0";
    reg = <0x1d84000 0x3000>;
    interrupts = <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>;

    /* 클럭 설정 */
    clocks = <&gcc GCC_UFS_PHY_AXI_CLK>,
             <&gcc GCC_AGGRE_UFS_PHY_AXI_CLK>,
             <&gcc GCC_UFS_PHY_AHB_CLK>,
             <&gcc GCC_UFS_PHY_UNIPRO_CORE_CLK>,
             <&gcc GCC_UFS_PHY_TX_SYMBOL_0_CLK>,
             <&gcc GCC_UFS_PHY_RX_SYMBOL_0_CLK>,
             <&gcc GCC_UFS_PHY_RX_SYMBOL_1_CLK>;
    clock-names = "core_clk",
                  "bus_aggr_clk",
                  "iface_clk",
                  "core_clk_unipro",
                  "tx_lane0_sync_clk",
                  "rx_lane0_sync_clk",
                  "rx_lane1_sync_clk";
    freq-table-hz = <75000000 300000000>,   /* core_clk */
                    <0 0>,                   /* bus_aggr_clk */
                    <0 0>,                   /* iface_clk */
                    <75000000 300000000>,    /* core_clk_unipro */
                    <0 0>,                   /* tx_lane0_sync */
                    <0 0>,                   /* rx_lane0_sync */
                    <0 0>;                   /* rx_lane1_sync */

    /* 레귤레이터 */
    vcc-supply = <&vreg_l7b_2p96>;
    vccq-supply = <&vreg_l9b_1p2>;
    vccq2-supply = <&vreg_s4a_1p8>;
    vcc-max-microamp = <800000>;
    vccq-max-microamp = <900000>;
    vccq2-max-microamp = <800000>;

    /* PHY 참조 */
    phys = <&ufs_mem_phy>;
    phy-names = "ufsphy";

    /* 리셋 */
    resets = <&gcc GCC_UFS_PHY_BCR>;
    reset-names = "rst";

    /* 플랫폼별 속성 */
    qcom,ice = <&ice>;

    status = "okay";

    /* Auto-Hibernate8 타이머 (ms) */
    /* 런타임에 sysfs로도 변경 가능 */
};

Samsung Exynos 플랫폼 예시

/* Samsung Exynos 2200 UFS 호스트 컨트롤러 */
ufs: ufshc@13100000 {
    compatible = "samsung,exynos2200-ufs";
    reg = <0x13100000 0x100>,   /* HCI 레지스터 */
          <0x13180000 0x8000>,  /* UNIPRO 레지스터 */
          <0x131A0000 0x2000>;  /* UFS protector 레지스터 */
    reg-names = "hci", "unipro", "ufsp";
    interrupts = <GIC_SPI 131 IRQ_TYPE_LEVEL_HIGH>;

    clocks = <&cmu_hsi2 CLK_GOUT_HSI2_UFS_EMBD_ACLK>,
             <&cmu_hsi2 CLK_GOUT_HSI2_UFS_EMBD_UNIPRO>;
    clock-names = "core_clk", "core_clk_unipro";
    freq-table-hz = <0 267000000>, <0 267000000>;

    vcc-supply = <&ldo15_reg>;
    vccq-supply = <&ldo16_reg>;
    vccq2-supply = <&ldo17_reg>;

    phys = <&ufs_phy>;
    phy-names = "ufsphy";
    samsung,sysreg-phandle = <&sysreg_hsi2>;

    status = "okay";
};

MediaTek 플랫폼 예시

/* MediaTek Dimensity 9200 (MT6985) UFS 호스트 컨트롤러 */
ufshci: ufshci@11270000 {
    compatible = "mediatek,mt6985-ufshci", "mediatek,ufshci";
    reg = <0x11270000 0x2C00>,   /* HCI */
          <0x11280000 0x2000>;   /* MPHY */
    reg-names = "hci", "mphy";
    interrupts = <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>;

    clocks = <&topckgen CLK_TOP_UFS_SEL>,
             <&infracfg CLK_INFRA_UFS_AXI>,
             <&infracfg CLK_INFRA_UFS_AHB>,
             <&infracfg CLK_INFRA_UNIPRO_SYS>,
             <&infracfg CLK_INFRA_UNIPRO_TICK>;
    clock-names = "ufs_sel", "ufs_axi", "ufs_ahb",
                  "unipro_sys", "unipro_tick";
    freq-table-hz = <0 416000000>, <0 0>, <0 0>,
                    <0 416000000>, <0 0>;

    vcc-supply = <&mt6373_vemc>;
    vccq-supply = <&mt6373_vufs>;
    vccq2-supply = <&mt6373_vufs18>;

    phys = <&ufsphy>;
    phy-names = "ufsphy";

    mediatek,top-gpio = <&pio>;

    status = "okay";
};
DT 바인딩 검증: 커널 소스의 Documentation/devicetree/bindings/ufs/ 디렉터리에서 각 벤더별 YAML 스키마를 확인할 수 있습니다. dt-schema 도구로 DT 노드의 유효성을 빌드 시 자동 검증합니다: make dt_binding_check DT_SCHEMA_FILES=ufs/. freq-table-hz 속성의 쌍 수가 clock-names의 항목 수와 반드시 일치해야 합니다.

sysfs 인터페이스

UFS 드라이버는 /sys/ 아래에 다양한 속성을 노출하여 런타임 모니터링과 튜닝을 지원합니다. 재컴파일 없이 전원 정책, Write Booster, Auto-Hibernate8 등의 동작을 변경할 수 있어 개발 및 디버깅에 필수적인 인터페이스입니다.

주요 sysfs 속성

UFS sysfs 속성 정리
경로 (상대)속성R/W설명기본값 예시
rpm_lvlRuntime PM 전원 레벨
—RWRuntime Suspend 시 전원 레벨 (0~6)플랫폼 설정 의존
spm_lvlSystem PM 전원 레벨
—RWSystem Suspend 시 전원 레벨 (0~6)플랫폼 설정 의존
auto_hibern8—RWAuto-Hibernate8 타이머 (µs), 0=비활성0
wb_on—RWWrite Booster 활성화 (0/1)1
enable_wb_buf_flush—RWWB 버퍼 플러시 활성화0
wb_flush_threshold—RWWB 플러시 시작 임계값 (남은 버퍼 %, 유효 범위 1~10)5
bkops_status—ROBackground Operations 상태 (0x00=None ~ 0x03=Critical)0x00
device_descriptor/—RO디바이스 디스크립터 필드별 노출 (manufacturer_name 등)—
health_descriptor/—RO디바이스 수명·온도 관련 헬스 정보—

전원 레벨 상세

rpm_lvl / spm_lvl 값 매핑
레벨디바이스 전원링크 상태적합 시나리오
0ActiveActive테스트/벤치마크 (절전 비활성)
1ActiveHibern8낮은 지연, 링크만 절전
2SleepActive디바이스만 절전 (드문 설정)
3SleepHibern8모바일 기본값 (균형)
4PowerDownHibern8높은 절전, 중간 복귀 시간
5PowerDownOff최대 절전, 긴 복귀 시간

sysfs 읽기/쓰기 예시

# UFS sysfs 베이스 경로 확인
UFS_PATH=$(find /sys/bus/platform/devices -maxdepth 1 -name "*ufshc*" | head -1)
echo "UFS sysfs path: ${UFS_PATH}"

# 현재 전원 레벨 확인
cat ${UFS_PATH}/rpm_lvl
cat ${UFS_PATH}/spm_lvl

# Runtime PM 레벨을 Sleep+Hibern8으로 변경
echo 3 > ${UFS_PATH}/rpm_lvl

# Auto-Hibernate8 타이머 설정 (1000µs = 1ms)
echo 1000 > ${UFS_PATH}/auto_hibern8

# Write Booster 활성화/비활성화
echo 1 > ${UFS_PATH}/wb_on
echo 0 > ${UFS_PATH}/wb_on

# Clock gating 지연(clkgate_delay_ms)과 Gear scaling(clkscale_enable)은
# 메인라인 표준 sysfs가 아니라 devfreq 및 일부 BSP 확장으로 제공된다.
# 표준 방식의 벤치마크용 성능 고정 예:
echo performance > /sys/class/devfreq/$(basename ${UFS_PATH})/governor

# 건강 정보 조회
ls ${UFS_PATH}/health_descriptor/
cat ${UFS_PATH}/health_descriptor/life_time_estimation_a
cat ${UFS_PATH}/health_descriptor/eol_info

# Unit descriptor 정보 (LUN별)
ls ${UFS_PATH}/unit_descriptor/
cat ${UFS_PATH}/unit_descriptor/unit0/boot_lun_id

ufs-utils 도구

ufs-utils는 UFS 디바이스 관리를 위한 커맨드라인 유틸리티로, /dev/bsg/ufs-bsg0 디바이스 노드를 통해 Query UPIU를 직접 전송합니다.

# ufs-utils 설치 (소스 빌드)
git clone https://github.com/westerndigitalcorporation/ufs-utils.git
cd ufs-utils && make && sudo make install

# 디바이스 디스크립터 읽기
sudo ufs-utils desc -a -p /dev/bsg/ufs-bsg0 -t device
# 출력 예:
#   bLength: 0x59
#   bDescriptorIDN: 0x00
#   bDevice: 0x00
#   bDeviceClass: 0x00
#   bDeviceSubClass: 0x01
#   bNumberLU: 0x01
#   wSpecVersion: 0x0310 (UFS 3.1)

# 설정 디스크립터 읽기
sudo ufs-utils desc -a -p /dev/bsg/ufs-bsg0 -t config

# Attribute 읽기 (bCurrentPowerMode)
sudo ufs-utils attr -a -p /dev/bsg/ufs-bsg0 -t 2

# Flag 읽기 (fDeviceInit, fPermanentWPEn 등)
sudo ufs-utils flag -a -p /dev/bsg/ufs-bsg0

# UFS 디바이스 정보 요약
sudo ufs-utils desc -a -p /dev/bsg/ufs-bsg0 -t device | grep -E "wSpec|bNumber|wManufacturer"
재컴파일 없는 런타임 튜닝: sysfs 인터페이스를 활용하면 커널 재컴파일이나 재부팅 없이 UFS 동작을 실시간으로 조정할 수 있습니다. 벤치마크 전에 devfreq 거버너를 performance로 고정하고 auto_hibern8=0(µs 단위)으로 설정하면 전원 상태 전이로 인한 측정 오차를 제거할 수 있습니다. 프로덕션에서는 rpm_lvl을 3(Sleep+H8)으로, auto_hibern8을 5000~15000µs로 설정하는 것이 일반적입니다.

디버깅 및 문제 해결 (Debugging & Troubleshooting)

UFS 디버깅은 에러 핸들러의 동작 이해, 트레이스포인트 활용, debugfs 분석의 세 축으로 이루어집니다. ufshcd_err_handler는 OCS 에러, UIC 에러, SCSI 에러를 분류하고 단계적 복구를 수행합니다.

UFS 에러 핸들링 흐름 (ufshcd_err_handler) 에러 감지 — 인터럽트 핸들러(ISR) 인터럽트 상태(IS) · UIC 에러 · OCS 확인 eh_work 워크 큐로 지연 처리 ufshcd_err_handler() 전용 워크 큐(프로세스 컨텍스트)에서 비동기 실행 OCS 에러 커맨드 완료 상태(OCS) 이상 UIC 에러 UniPro/M-PHY 링크 에러 SCSI 에러 디바이스 응답 이상 호스트 치명 에러 INT_FATAL_ERRORS 약한 조치부터 강한 조치로 단계적으로 확대 1단계 · 태스크 정리 후 커맨드 재시도 ufshcd_abort() → ufshcd_clear_pending_tasks() 대부분의 에러에서 가장 먼저 시도하는 기본 복구 실패 2단계 · 링크 복구 (Link Recovery) ufshcd_uic_hibern8_exit() UIC 에러로 링크가 불안정한 경우 시도 실패 3단계 · 디바이스 리셋 (Device Reset) ufshcd_device_reset() 디바이스 상태 이상(DEVICE_FATAL 등) 시 시도 실패 4단계 · 호스트 리셋 후 전체 초기화 ufshcd_reset_and_restore() 최후 수단 — 하위 단계가 모두 실패한 경우 성공 성공 성공 성공 모든 단계 실패 복구 실패 모든 단계 실패 — 호스트를 오프라인 처리 복구 성공 scsi_done()으로 I/O 즉시 재개

OCS 에러 코드

Overall Command Status (OCS) 에러 코드
OCS 값이름원인대응
0x00SUCCESS정상 완료—
0x01INVALID_CMD_TABLE_ATTRUTRD 설정 오류드라이버 버그 확인
0x02INVALID_PRDT_ATTRPRDT 엔트리 오류DMA 매핑 확인
0x03MISMATCH_DATA_BUF_SIZE데이터 크기 불일치Transfer 크기 확인
0x04MISMATCH_RESP_UPIU_SIZEResponse UPIU 크기 불일치Response 버퍼 확인
0x05PEER_COMM_FAILUREUniPro 통신 실패링크 복구 시도
0x06ABORTED호스트에 의한 중단타임아웃 or 에러 복구 중
0x07FATAL_ERROR치명적 하드웨어 에러호스트 리셋 필요
0x08DEVICE_FATAL_ERROR디바이스 치명적 에러디바이스 리셋 + 호스트 리셋
0x0FOCS_INVALID_COMMAND_STATUSUFSHCI SW 에러(명령 상태 이상)드라이버 상태 확인

UIC 에러 유형

UIC 에러 분류
에러 유형레지스터원인증상
PA (Physical Adapter)UIC_ERROR_CODE_PAM-PHY 시그널 불안정, 전압/클럭 문제링크 드롭, H8 진입/탈출 실패
DL (Data Link)UIC_ERROR_CODE_DLCRC 에러, NAC 과다, TC 크레딧 소진프레임 재전송 증가, 성능 저하
NL (Network Layer)UIC_ERROR_CODE_NLUniPro 라우팅 에러통신 장애 (드문 경우)
TL (Transport Layer)UIC_ERROR_CODE_TLE2E FC 크레딧 에러, UPIU 형식 에러커맨드 타임아웃
DMEUIC_ERROR_CODE_DMEUIC 커맨드 실행 실패Gear 변경/H8 실패

트레이스포인트 활용

# UFS 트레이스포인트 목록 확인
ls /sys/kernel/debug/tracing/events/ufs/
# ufshcd_command, ufshcd_uic_command, ufshcd_clk_gating,
# ufshcd_clk_scaling, ufshcd_auto_bkops_state, ...

# 커맨드 트레이스 활성화
echo 1 > /sys/kernel/debug/tracing/events/ufs/ufshcd_command/enable

# UIC 커맨드 트레이스 활성화
echo 1 > /sys/kernel/debug/tracing/events/ufs/ufshcd_uic_command/enable

# 트레이스 버퍼 읽기
cat /sys/kernel/debug/tracing/trace_pipe | head -50
# 출력 예:
# ufshcd_command: 0.0.0: send tag: 0, DB: 0x1, size: 4096,
#   IS: 0, LBA: 0x1234, opcode: 0x28 (READ_10), group_id: 0x0
# ufshcd_command: 0.0.0: complete tag: 0, DB: 0x0, size: 4096,
#   IS: 0, scsi_status: 0x0 (GOOD), OCS: 0x0 (SUCCESS)

# ftrace로 UFS I/O 지연 분석
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo ufshcd_queuecommand > /sys/kernel/debug/tracing/set_graph_function
echo 1 > /sys/kernel/debug/tracing/tracing_on
# ... I/O 수행 ...
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace

debugfs 엔트리

# debugfs 마운트 확인
mount | grep debugfs
# debugfs on /sys/kernel/debug type debugfs

# UFS debugfs 엔트리 확인 (루트: ufshcd, HBA마다 dev_name() 하위 디렉터리)
ls /sys/kernel/debug/ufshcd/
# ufshcd0/

ls /sys/kernel/debug/ufshcd/ufshcd0/
# stats                         # 이벤트 히스토그램 (아래 출력 예)
# saved_err  saved_uic_err      # 에러 핸들러 진행 값 (쓰면 EH 재유발)
# exception_event_mask          # EE(예외 이벤트) 사용자 마스크
# exception_event_rate_limit_ms # EE 알림 억제 시간 (기본 20ms)

# 에러 통계 확인
cat /sys/kernel/debug/ufshcd/ufshcd0/stats
# PHY Adapter Layer errors (except LINERESET): 0
# Data Link Layer errors: 0
# Network Layer errors: 0
# Transport Layer errors: 0
# Generic DME errors: 0
# Auto-hibernate errors: 0
# IS Fatal errors (CEFES, SBFES, HCFES, DFES): 0
# DME Link Startup errors: 0
# PM Resume errors: 0
# PM Suspend errors : 0
# Logical Unit Resets: 0
# Host Resets: 0
# SCSI command aborts: 0

# TX Equalization 지원 시 기어별 디렉터리 생성 (HS-G4+는 EQTR 기록 포함)
# (주의: tx_eq_* 계열은 커널 6.20에 병합 예정 — 2026년 8월 master 기준)
ls /sys/kernel/debug/ufshcd/ufshcd0/tx_eq_hs_gear4/
# host_tx_eq_params    device_tx_eq_params
# host_tx_eqtr_record  device_tx_eqtr_record
# tx_eq_ctrl           ← echo retrain > tx_eq_ctrl 로 TX EQ 재학습

# 참고: 디스크립터 덤프(device/string/health)와 레지스터 덤프는 debugfs가 아니라
# sysfs(device_descriptor/, string_descriptors/, health_descriptor/)로 제공된다

Kconfig 디버깅 옵션

# UFS 관련 Kconfig 옵션 확인
grep -E "CONFIG_SCSI_UFS|CONFIG_TRACING" /boot/config-$(uname -r) | sort
# CONFIG_SCSI_UFSHCD=y           # UFS 호스트 컨트롤러 드라이버
# CONFIG_SCSI_UFSHCD_PCI=m       # PCI 기반 UFS (인텔 등)
# CONFIG_SCSI_UFSHCD_PLATFORM=y  # 플랫폼 기반 UFS (모바일)
# CONFIG_SCSI_UFS_BSG=y          # BSG 노드 (/dev/bsg/ufs-bsg0)
# CONFIG_SCSI_UFS_CRYPTO=y       # 인라인 암호화 지원
# CONFIG_SCSI_UFS_HPB            # (커널 6.6부터 옵션 자체가 제거됨)
# CONFIG_SCSI_UFS_FAULT_INJECTION=n  # 장애 주입 테스트
# CONFIG_TRACING=y               # ftrace 프레임워크

# dmesg에서 UFS 초기화 로그 분석
dmesg | grep -i "ufshcd\|ufs" | head -30
# [    1.234] ufshcd-qcom 1d84000.ufshc: ufshcd_pltfrm_init() start
# [    1.240] ufshcd-qcom 1d84000.ufshc: UFS Host Controller version: 3.0
# [    1.251] ufshcd-qcom 1d84000.ufshc: Linked as scsi host0
# [    1.312] ufshcd-qcom 1d84000.ufshc: HS-G4B (2-lane) connected
# [    1.315] ufshcd-qcom 1d84000.ufshc: UFS device is SAMSUNG KLUEG8UHDB
# [    1.320] scsi 0:0:0:0: Direct-Access UFS 3.1 PQ: 0 ANSI: 6
에러 복구 에스컬레이션 순서: UFS 에러 핸들러는 다음 순서로 복구를 시도합니다: (1) 커맨드 재시도 → (2) 링크 복구 (H8 exit → re-link) → (3) 디바이스 리셋 (Task Management UPIU) → (4) 호스트 전체 리셋 (ufshcd_host_reset_and_restore). 호스트 리셋까지 실패하면 SCSI 에러가 상위 블록 레이어로 전파됩니다. /sys/kernel/debug/ufshcd0/err_stats에서 누적 에러 카운트를 정기적으로 확인하면 잠재적 하드웨어 문제를 조기에 발견할 수 있습니다.

성능 튜닝 (Performance Tuning)

UFS 성능 최적화는 하드웨어 계층(Gear/Lane), 컨트롤러 계층(MCQ), 디바이스 기능(WB/HPB), 블록 레이어, 파일시스템에 이르기까지 다층적으로 접근해야 합니다. 각 계층의 튜닝 포인트를 이해하면 단일 스토리지에서도 큰 성능 차이를 만들 수 있습니다.

UFS 성능 최적화 계층 (Bottom-Up) ※ 하위 계층 튜닝이 상위 계층보다 근본적인 성능 영향을 줌 호스트 (PHY · 컨트롤러) UFS 디바이스 기능 커널 SW (블록 · 파일시스템) L6 파일시스템 (Filesystem) F2FS (플래시 최적화), ext4 discard, journal_async_commit, IO priority L5 블록 레이어 (Block Layer) scheduler none/mq-deadline, nr_requests, read_ahead_kb, rq_affinity L4 HPB (Host Performance Booster) L2P 맵 호스트 캐시 → random read 지연 감소 (0.3ms → 0.1ms) L3 Write Booster (WB) SLC 캐시로 burst write 가속 (2~4x), wb_on=1, flush 임계값 조정 L2 MCQ (Multi-Circular Queue) SQ/CQ per CPU → UTRD 경합 제거, auto_hibern8=0 (벤치마크) L1 PHY Gear/Lane HS-G4B 2-Lane = 2×2.9 GB/s, Gear scaling 비활성 (벤치마크) I/O 요청 흐름 (응용 → 하드웨어) 성능 영향 (근본 → 응용) 계층별 튜닝을 누적 적용하면 단일 스토리지에서도 큰 성능 차이를 만들 수 있습니다.

튜닝 파라미터 정리

UFS 성능 튜닝 파라미터 및 권장값
계층파라미터기본값권장값 (성능 우선)효과
PHYHS GearAuto (G1~G4)G4 (고정)최대 대역폭
PHYLanes22대역폭 2배
PHYRate (A/B)BBRate-B가 더 높은 대역폭
ControllerMCQ 큐 수nr_cpusnr_cpus (또는 8)병렬 I/O 처리
Controllerauto_hibern81000 µs0 (벤치마크)H8 전이 오버헤드 제거
WBwb_on11burst write 가속
WBwb_flush_threshold40%60% (대용량 쓰기)flush 빈도 감소
HPBHPB 활성화ONONrandom read 가속
Blockschedulermq-deadlinenone (NVMe형)스케줄러(Scheduler) 오버헤드 제거
Blocknr_requests256256~512큐 깊이 증가
Blockread_ahead_kb128256~512순차 읽기 프리페치
Blockrq_affinity12완료 CPU 고정
FSF2FS discardononTRIM/UNMAP 전달

Gear/Lane 최적화

# 현재 Gear/Lane 상태 확인 — 부팅 로그의 전원 모드 협상 결과 활용
dmesg | grep -iE "ufshc.*(gear|lane|pwr)" | tail -6
# 출력 예: ufs_device_wlun 0: HS-G4B, 2-lane 연결

# Gear scaling 고정 (최대 성능) — 메인라인 표준은 devfreq 거버너
echo performance > /sys/class/devfreq/1d84000.ufshc/governor
cat /sys/class/devfreq/1d84000.ufshc/cur_freq

# Power mode 변경 테스트는 DME UIC 쓰기가 필요하므로 드라이버 경유만 가능.
# 주의: 호스트와 디바이스의 Gear 불일치는 링크 드롭을 유발하므로
# 반드시 ufshcd_config_pwr_mode() 흐름(DME_SET(PA_PWRMODE))으로 변경한다.
# Rate-B, 2-Lane이 상용 디바이스의 기본 최대 설정

블록 레이어 튜닝

# UFS 블록 디바이스 확인
DISK="sda"  # UFS는 SCSI 서브시스템 → /dev/sdX

# I/O 스케줄러 변경 (MCQ 사용 시 none 권장)
echo none > /sys/block/${DISK}/queue/scheduler
cat /sys/block/${DISK}/queue/scheduler
# [none] mq-deadline kyber bfq

# 큐 깊이 조정
echo 512 > /sys/block/${DISK}/queue/nr_requests

# Read-ahead 증가 (순차 읽기 워크로드)
echo 512 > /sys/block/${DISK}/queue/read_ahead_kb

# RQ affinity 설정 (완료 인터럽트 → 요청 CPU)
echo 2 > /sys/block/${DISK}/queue/rq_affinity

# Write Booster 활성화 확인 및 플러시 임계값 설정
echo 1 > /sys/bus/platform/devices/1d84000.ufshc/wb_on
echo 60 > /sys/bus/platform/devices/1d84000.ufshc/wb_flush_threshold

fio 벤치마크 설정

# === UFS Sequential Read 벤치마크 ===
cat > /tmp/ufs_seq_read.fio << 'FIOEOF'
[global]
ioengine=io_uring
direct=1
bs=128k
iodepth=32
numjobs=1
runtime=30
time_based
group_reporting

[seq-read]
filename=/dev/sda
rw=read
FIOEOF

sudo fio /tmp/ufs_seq_read.fio

# === UFS Random Read 벤치마크 (HPB 효과 측정) ===
cat > /tmp/ufs_rand_read.fio << 'FIOEOF'
[global]
ioengine=io_uring
direct=1
bs=4k
iodepth=32
numjobs=4
runtime=30
time_based
group_reporting

[rand-read]
filename=/dev/sda
rw=randread
FIOEOF

sudo fio /tmp/ufs_rand_read.fio

# === UFS Random Write 벤치마크 (WB 효과 측정) ===
cat > /tmp/ufs_rand_write.fio << 'FIOEOF'
[global]
ioengine=io_uring
direct=1
bs=4k
iodepth=16
numjobs=4
runtime=30
time_based
group_reporting

[rand-write]
filename=/dev/sda
rw=randwrite
FIOEOF

sudo fio /tmp/ufs_rand_write.fio

튜닝 전후 비교

UFS 4.0 (HS-G4B 2-Lane) 튜닝 전후 성능 비교 (특정 환경의 참고 수치 — 실제 값은 하드웨어·커널 설정·워크로드에 따라 다르므로 반드시 자신의 환경에서 벤치마크)
워크로드튜닝 전 (기본)튜닝 후개선 방향핵심 설정
순차 읽기 (128KB)~2,000 MB/s~2,800 MB/s향상Gear 고정, read_ahead=512
순차 쓰기 (128KB)~1,400 MB/s~1,800 MB/s향상WB + scheduler=none
랜덤 읽기 (4KB)~60K IOPS~120K IOPS향상HPB + MCQ + rq_affinity=2
랜덤 쓰기 (4KB)~40K IOPS~80K IOPS향상WB + MCQ + nr_requests=512
혼합 R/W (7:3, 4KB)~35K IOPS~70K IOPS향상전체 최적화 적용
벤치마크 환경 설정 체크리스트: 정확한 UFS 성능 측정을 위해 다음을 확인하세요: (1) auto_hibern8=0 — H8 전이 오버헤드 제거, (2) devfreq 거버너 performance 고정 — Gear scaling 억제, (3) scheduler=none — I/O 스케줄러 오버헤드 제거, (4) direct=1 — 페이지 캐시(Page Cache) 바이패스, (5) thermal throttling 비활성화 확인. 프로덕션 환경에서는 전력-성능 균형을 고려하여 Gear scaling과 Auto-H8을 활성 상태로 유지하되, read_ahead_kb와 nr_requests 최적화는 상시 적용할 수 있습니다.

최신 UFS 서브시스템 변경 (커널 6.15~6.20)

커널 6.15부터 6.20 머지 윈도우(2026년 8월 기준)까지 UFS 서브시스템은 장치 예외(exception) 처리, 공유 ICE(Inline Crypto Engine), OPP V2 기반 다중 주파수 스케일링, HID(Host Initiated Defragmentation), 범용 RPMB 프레임워크 편입, LRB(Local Reference Block) 배열 제거, 적응형 TX 이퀄라이제이션 등 폭넓은 정비를 거쳤습니다. 각 버전 귀속은 릴리스 태그(v6.14~v6.19)와 master 소스를 직접 대조해 확인했습니다.

인프라 타임라인 — RPMB 프레임워크와 신규 코어 파일(6.15 → 6.20)

최근 릴리스에서 RPMB와 링크 품질 관련 인프라가 단계적으로 편입되었습니다.

신규 파일 — ufs-rpmb.c(OP-TEE RPMB), ufs-txeq.c(TX Equalization)

6.15: 장치 수준 예외(Device Exception) 처리 지원

커널 6.15에서 UFS 장치가 발생시키는 장치 예외(Device Exception)를 호스트 드라이버가 처리하는 기능이 추가되었습니다. UFS 장치는 특정 조건(예: 온도 임계값 초과, 내부 오류)에서 예외 이벤트를 발생시킬 수 있으며, 이를 UIC(UFS Interconnect) 레이어를 통해 호스트에 통보합니다.

기존에는 이러한 장치 예외 알림이 무시되거나 일반 오류로 처리되었으나, 6.15부터 ufshcd 드라이버가 장치 예외 인터럽트(Exception Event Interrupt)를 별도로 처리하는 경로가 구현되어 장치 상태를 더 세밀하게 관리할 수 있게 되었습니다.

커널 6.15부터: UFS 장치가 발생시키는 예외 이벤트를 ufshcd가 명시적으로 처리합니다. 장치 온도 제한이나 내부 오류 상황에서 더 안정적인 동작이 기대됩니다.

6.15: Qualcomm 공유 ICE (Shared Inline Crypto Engine)

커널 6.15에서 Qualcomm UFS 드라이버(drivers/ufs/host/ufs-qcom.c)에 공유 ICE(Shared Inline Crypto Engine) 지원이 추가되었습니다. 일부 Qualcomm SoC에서는 UFS와 eMMC/SD가 동일한 ICE 하드웨어 블록을 공유하는 구조로 설계되어 있습니다. 기존 드라이버는 UFS 전용 ICE만 가정하고 있었으나, 공유 ICE 구조에서는 다른 초기화 시퀀스와 클럭 관리가 필요합니다.

이 변경으로 Qualcomm 플랫폼에서 UFS 인라인 암호화가 공유 ICE를 통해 올바르게 초기화되고 동작합니다.

커널 6.15부터: Qualcomm 공유 ICE를 사용하는 플랫폼(예: 일부 SM8xxx 계열)에서 UFS 인라인 암호화가 올바르게 동작합니다.

6.15: OPP V2 기반 다중 주파수 스케일링

커널 6.15에서 UFS 클럭 스케일링(Clock Scaling)이 기존의 단순 고속/저속 2단계 방식에서 OPP(Operating Performance Points) V2 테이블 기반의 다중 주파수 스케일링(Frequency Scaling)으로 확장되었습니다.

핵심 변경 사항은 다음과 같습니다.

/* ufshcd-core.h — freq_to_gear_speed 선언 (6.15+) */
int freq_to_gear_speed(struct ufs_hba *hba, unsigned long freq,
                       u32 *gear_speed);

/* ufshcd.c — 클럭 스케일 노티파이 콜백 */
static int ufshcd_clk_scale_notify(struct notifier_block *nb,
                                    unsigned long event, void *data);

커널 6.15부터: UFS 클럭 스케일링이 OPP V2 테이블을 지원합니다. 플랫폼 DTS에 operating-points-v2 속성을 추가하면 세밀한 주파수 제어가 가능합니다.

6.16: WriteBooster 버퍼 리사이즈와 디바이스 명령 타임아웃 제어

커널 6.16의 UFS 코어 변경은 운영 편의성에 초점이 맞춰져 있습니다.

활용: WriteBooster 버퍼 리사이즈는 대용량 순차 쓰기 직전에 Shared Buffer를 Dedicated로 늘리고 작업 후 되돌리는 식으로 활용할 수 있습니다. 앞선 "Write Booster" 절의 sysfs 인터페이스와 함께 사용합니다.

6.17: HID(Host Initiated Defragmentation) 지원

UFS 4.1(JESD220G)이 추가한 HID(호스트 주도 디프래그먼테이션, Host Initiated Defragmentation)를 커널 6.17이 지원합니다(vivo 기여). NAND 플래시에서는 반복된 쓰기로 논리 블록이 물리 페이지 곳곳에 흩어지는 단편화(Fragmentation)가 진행되고, 이는 읽기 성능 저하로 이어집니다. HID는 호스트가 디바이스에게 단편화 분석과 재배치(Relocation)를 지시하는 표준 수단입니다.

실행 정책은 사용자 공간의 몫입니다. 커널은 다음 sysfs 그룹(/sys/bus/platform/devices/*.ufs/hid/)만 제공합니다.

속성Query 속성(ID)읽기/쓰기설명
analysis_triggerbDefragOperation (0x35)WO단편화 분석 시작/중지(enable/disable)
defrag_triggerbDefragOperation (0x35)WO분석+디프래그 실행/중지(enable/disable)
fragmented_sizebHIDAvailableSize (0x36)RO디바이스 전체 단편화 크기
defrag_sizebHIDSize (0x37)R/W디프래그 대상 크기 설정
progress_ratiobHIDProgressRatio (0x38)RO진행률(1~100, % 단위)
statebHIDState (0x39)ROidle / analysis_in_progress / defrag_required / defrag_in_progress / defrag_completed / defrag_not_required
# HID 지원 여부는 디바이스 디스크립터의 dExtendedUFSFeatures BIT(13)로 판단합니다
cat /sys/bus/platform/devices/1d84000.ufshc/hid/state
# idle

echo enable > /sys/bus/platform/devices/1d84000.ufshc/hid/analysis_trigger
sleep 5
cat /sys/bus/platform/devices/1d84000.ufshc/hid/fragmented_size
cat /sys/bus/platform/devices/1d84000.ufshc/hid/state             # defrag_required?

echo enable > /sys/bus/platform/devices/1d84000.ufshc/hid/defrag_trigger
while [ "$(cat /sys/bus/platform/devices/1d84000.ufshc/hid/state)" != "defrag_completed" ]; do
    cat /sys/bus/platform/devices/1d84000.ufshc/hid/progress_ratio
    sleep 10
done
디프래그는 장시간 고부하 동작입니다: NAND 재배치는 내부 쓰기 증폭을 유발하므로 배터리 구동 중이나 열 제약이 큰 환경에서 무분별하게 실행하지 않아야 합니다. bDefragOperation 값은 0(비활성), 1(분석만), 2(분석+디프래그) 세 가지이며, state 머신은 위 표의 6단계로 순환합니다.

6.19: LRB 배열 제거와 ufs-rpmb.c 편입

6.20 병합 예정(master): 적응형 TX 이퀄라이제이션(EQTR)과 전원 협상 통합

2026년 8월 기준 master에는 다음 변경이 들어왔으며 커널 6.20에 포함될 예정입니다.

M-PHY TX Equalization(EQTR)이 필요한 이유

HS-G4 이상 고속 링크에서는 TX 프리엠파시스(pre-shoot)/디엠파시스(de-emphasis) 설정이 눈금 단위로 신호 품질을 좌우합니다. UFS는 디바이스와 호스트가 서로의 TX 특성을 교환해 최적 조합을 찾는 TX EQ 학습(TX EQ Training, EQTR) 절차를 제공하며, 학습 결과는 레인별 PreShoot×DeEmphasis 조합의 FOM(Figure of Merit) 행렬로 남습니다.

ufs-txeq.c는 전원 모드 변경 전에 최적 설정을 찾아 적용하는 적응형(adaptive) 엔진입니다. 핵심 인터페이스는 다음과 같습니다.

/* drivers/ufs/core/ufs-txeq.c — 핵심 인터페이스 발췌 */

/* 모듈 파라미터: 전원 모드 변경 전 적응형 TX EQ 수행 여부(기본 false) */
static bool use_adaptive_txeq;
module_param(use_adaptive_txeq, bool, 0644);
MODULE_PARM_DESC(use_adaptive_txeq,
    "Find and apply optimal TX Equalization settings before changing Power Mode");

/* 목표 기어(기본 HS-G6, UFS_HS_G1~UFS_HS_GEAR_MAX 범위로 제한) */
static unsigned int adaptive_txeq_gear = UFS_HS_G6;

/* 학습된 설정을 디바이스 속성에 기록 — 기어별 Selector 단위 */
static void ufshcd_update_tx_eq_settings_attrs(struct ufs_hba *hba, u32 gear)
{
    /* qTxEQGnSettings(64비트 qword 속성) 기록 */
    ret = ufshcd_query_attr_qword(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
                                  QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS,
                                  gear - 1, (u8)txeq_setting_sel, &eq);
    /* wTxEQGnSettingsExt(확장 비트 필드) 기록 */
    ret = ufshcd_query_attr(hba, UPIU_QUERY_OPCODE_WRITE_ATTR,
                            QUERY_ATTR_IDN_TX_EQ_GN_SETTINGS_EXT,
                            gear - 1, (u8)txeq_setting_sel, &eq_ext);
}

/* 전원 모드 변경 경로에서 호출 — 유효한 모든 기어에 설정 기록 */
void ufshcd_store_tx_eq_settings(struct ufs_hba *hba);

debugfs 인터페이스(앞선 debugfs 절 참조)와 연동하면 다음 흐름이 됩니다.

# 1. 재학습(retrain) 지시 — EQTR 기록은 HS-G4 이상 기어 디렉터리에만 존재
echo retrain > /sys/kernel/debug/ufshcd/ufshcd0/tx_eq_hs_gear4/tx_eq_ctrl

# 2. 학습 결과(FOM 행렬) 확인 — PreShoot(행) × DeEmphasis(열),
#    '*'는 Pre-Coding이 활성된 조합, 'x'는 PHY capability 밖 조합
cat /sys/kernel/debug/ufshcd/ufshcd0/tx_eq_hs_gear4/host_tx_eqtr_record

# 3. 최종 선택된 파라미터(레인별 preshoot/deemphasis/precode) 확인
cat /sys/kernel/debug/ufshcd/ufshcd0/tx_eq_hs_gear4/host_tx_eq_params
cat /sys/kernel/debug/ufshcd/ufshcd0/tx_eq_hs_gear4/device_tx_eq_params
EQTR 기록의 의미: *_tx_eqtr_record는 각 (PreShoot, DeEmphasis) 조합을 실제 링크에 걸고 측정한 품질 점수(FOM)를 기록한 것입니다. 호스트·디바이스 PHY capability 비트맵(Bitmap)에서 허용되지 않는 조합은 x로 표시되며, tx_eq_params는 이 행렬에서 최종 선택된 레인별 설정을 보여줍니다.

6.15: Renesas R-Car S4-8 ES1.2 지원

커널 6.15에서 Renesas R-Car S4-8 SoC의 ES1.2(Engineering Sample 1.2) 실리콘에 대한 UFS 호스트 컨트롤러 지원이 추가되었습니다. ES1.2는 ES1.1 대비 PHY 초기화 시퀀스와 타이밍 파라미터가 변경되었으며, drivers/ufs/host/ufs-renesas.c에 ES1.2 전용 초기화 코드가 추가되었습니다.

6.15/6.14: Rockchip RK3576 UFS 컨트롤러 지원

Rockchip RK3576 SoC에 탑재된 UFS 호스트 컨트롤러 지원이 추가되었습니다(SCSI 트리를 통해 6.14에 병합). RK3576은 Rockchip의 고성능 임베디드 SoC로, UFS 2.x 인터페이스를 지원하며 drivers/ufs/host/ufs-rockchip.c에 전용 초기화 및 PHY 제어 코드가 포함됩니다.

커널 6.14/6.15부터: Rockchip RK3576 기반 시스템에서 UFS 스토리지를 사용할 수 있습니다. 커널 구성 시 CONFIG_SCSI_UFS_ROCKCHIP을 활성화해야 합니다.

UFS 서브시스템을 더 깊이 이해하거나 관련 기술을 학습할 때 참고할 문서를 정리합니다.

관련 내부 문서
페이지(Page)링크관련성
eMMC/MMC 서브시스템eMMC/MMCUFS의 전세대 기술, MMC 프로토콜 비교 학습에 필수
SCSI 서브시스템SCSIUFS는 SCSI 명령 집합을 사용하며, SCSI 미들레이어를 통해 동작
NVMeNVMePCIe 기반 플래시 스토리지, MCQ 아키텍처 비교
SATA/AHCISATA/AHCI또 다른 SCSI 기반 스토리지 서브시스템 비교
블록 I/O 레이어블록 I/OUFS 위의 블록 레이어, blk-mq, I/O 스케줄러
디바이스 트리디바이스 트리UFS DT 바인딩, PHY/클럭/레귤레이터 설정
DMADMAPRDT(Physical Region Descriptor Table), scatter-gather, DMA 매핑
Android 커널AndroidAndroid 환경에서의 UFS 최적화, 보안 부팅, ICE
F2FS 파일시스템F2FSUFS에 최적화된 플래시 전용 파일시스템, TRIM/discard 연계
커널 암호화 (Crypto)Crypto커널 Crypto API, blk-crypto, ICE/FMP 인라인 암호화 프레임워크
클럭 프레임워크Common Clock FrameworkUFS 호스트 클럭 관리, clk_prepare_enable/clk_gating, MediaTek 다단계 클럭
레귤레이터 프레임워크Regulator FrameworkVCC/VCCQ/VA09 레귤레이터 제어, 전원 시퀀싱, MediaTek VA09
전원 관리전원 관리Runtime PM, System Suspend, Auto-Hibernate, DevFreq 스케일링
IOMMUIOMMUUFS DMA 매핑의 IOMMU 변환, ARM SMMU 설정
TrustZone / OP-TEETrustZone / OP-TEEICE Wrapped Key, RPMB 보안 저장소, Secure World 연동
ThermalThermalUFS 디바이스 온도 모니터링, 써멀 스로틀링에 의한 성능 제한

표준 사양서

커널 문서 및 소스 코드

기술 문서 및 분석 기사

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

1단계: 헤더 및 프로토콜 정의

  1. include/ufs/ufs.h — UFS 프로토콜 상수, UPIU 구조체, 디스크립터 IDN, 전원 모드, 벤더 ID 정의(UFS_VENDOR_SAMSUNG 등), 디바이스 quirk 플래그
  2. include/ufs/ufshci.h — UFSHCI 레지스터 오프셋, UTRD/UTMRD 구조체, OCS 코드 정의
  3. include/ufs/ufshcd.h — struct ufs_hba, struct ufs_hba_variant_ops(vops) 정의, quirk 플래그 적용 매크로

2단계: 코어 드라이버

  1. drivers/ufs/core/ufshcd.c — UFS 호스트 컨트롤러 드라이버 코어 (초기화, I/O 경로, 에러 핸들링, PM, ufs_fixups[] 테이블)
  2. drivers/ufs/core/ufs-mcq.c — Multi-Circular Queue 구현 (SQ/CQ 관리, 큐 매핑)
  3. drivers/ufs/core/ufs-sysfs.c — sysfs 속성 정의 및 읽기/쓰기 핸들러
  4. drivers/ufs/core/ufshcd-crypto.c — 인라인 암호화(ICE) 키 프로그래밍 및 crypto 프로파일
  5. drivers/ufs/core/ufshpb.c — HPB 코어 구현(L2P 캐시 관리, Region 전환) — 커널 6.6에서 삭제됨, 과거 코드 이해용

3단계: 플랫폼별 드라이버 (벤더 심층 분석 대응)

  1. drivers/ufs/host/ufs-qcom.c — Qualcomm: ICE 키 프로그래밍, QMP PHY 연동, Testbus 덤프, MCQ 리소스 매핑, DevFreq 스케일링
  2. drivers/ufs/host/ufs-exynos.c — Samsung Exynos: FMP(PRDT 기반 암호화), PMA PHY 캘리브레이션, TXHSLV/RXHSLV 조정, EOM
  3. drivers/ufs/host/ufs-mediatek.c — MediaTek: VA09 레귤레이터 시퀀싱, 다단계 클럭 게이팅, 전용 Crypto IP 초기화
  4. drivers/ufs/host/ufs-hisi.c — HiSilicon: Kirin SoC PHY 다단계 초기화, Hi3660/Hi3670 리비전별 분기
  5. drivers/ufs/host/ufs-renesas.c — Renesas: R-Car Gen4 자동차 등급 PHY, Auto-Hibernate 정책
  6. drivers/ufs/host/ufshcd-pci.c — Intel/PCI: PCI 기반 UFS, LTR 설정, VID/DID 매칭

4단계: 관련 서브시스템

  1. drivers/soc/qcom/ice.c — Qualcomm ICE 공통 드라이버 (키슬롯 레지스터 접근, Wrapped Key 처리)
  2. drivers/phy/qualcomm/phy-qcom-qmp-ufs.c — QMP UFS PHY 드라이버 (SoC별 캘리브레이션 테이블)
  3. drivers/phy/samsung/phy-exynos-ufs.c — Exynos UFS PHY 드라이버 (PMA 레지스터, EOM 구현)

핵심 I/O 경로 (ufshcd_queuecommand()에서 시작):

  1. SCSI 미들레이어가 scsi_cmnd를 전달 → ufshcd_queuecommand()
  2. UPIU 구성
    • ufshcd_comp_devman_upiu() (Query/NOP)
    • ufshcd_comp_scsi_upiu() (SCSI Command)
  3. UTRD 설정 + DMA 매핑
    • ufshcd_prepare_utp_scsi_cmd_upiu()
    • ufshcd_map_sg() (scatter-gather → PRDT)
  4. Doorbell 레지스터 쓰기(SDB) 또는 SQ Tail 갱신(MCQ) → ufshcd_send_command()
  5. 인터럽트 → 완료 처리
    • ufshcd_transfer_req_compl()
    • ufshcd_compl_one_cqe() (MCQ)
# 소스 코드 탐색 팁
# 1. I/O 경로 시작점
grep -n "ufshcd_queuecommand" drivers/ufs/core/ufshcd.c | head -5

# 2. 에러 핸들러 진입점
grep -n "ufshcd_err_handler" drivers/ufs/core/ufshcd.c | head -5

# 3. 전원 관리 진입점
grep -n "ufshcd_suspend\|ufshcd_resume" drivers/ufs/core/ufshcd.c | head -10

# 4. MCQ 관련 함수
grep -n "ufshcd_mcq" drivers/ufs/core/ufs-mcq.c | head -10

# 5. 플랫폼 드라이버 ops 정의 (벤더별)
grep -n "ufs_hba_qcom_vops" drivers/ufs/host/ufs-qcom.c
grep -n "ufs_hba_exynos_ops" drivers/ufs/host/ufs-exynos.c
grep -n "ufs_hba_mtk_vops" drivers/ufs/host/ufs-mediatek.c

# 6. 디바이스 Quirks 테이블 — 벤더별 워크어라운드 확인
grep -n "ufs_fixups\[\]" drivers/ufs/core/ufshcd.c

# 7. ICE 키 프로그래밍 흐름
grep -n "program_key" drivers/ufs/host/ufs-qcom.c
grep -n "qcom_ice_program_key" drivers/soc/qcom/ice.c

# 8. FMP PRDT 확장 구조
grep -n "fmp\|FMP" drivers/ufs/host/ufs-exynos.c

# 9. 벤더 ID 및 quirk 플래그 정의
grep -n "UFS_VENDOR_\|UFS_DEVICE_QUIRK_" include/ufs/ufs.h
학습 시작점: UFS를 처음 접한다면 drivers/ufs/core/ufshcd.c의 ufshcd_queuecommand() 함수부터 읽으세요. SCSI 커맨드가 UFS UPIU로 변환되어 하드웨어에 전달되는 전체 I/O 경로를 하나의 함수 호출 체인에서 따라갈 수 있습니다. 이후 ufshcd_err_handler()로 에러 복구 흐름을, ufshcd_suspend()/ufshcd_resume()으로 전원 관리 흐름을 학습하면 UFS 드라이버의 전체 구조를 파악할 수 있습니다. 벤더별 구현을 비교하려면 ufs-qcom.c와 ufs-exynos.c의 vops 구조체를 나란히 놓고, 같은 콜백(예: program_key)이 ICE와 FMP에서 어떻게 다르게 구현되는지 비교하는 것이 효과적입니다.