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), 인라인 암호화(ICE), RPMB, 전원 관리, 디버깅, 성능 튜닝까지 Linux 커널의 UFS 서브시스템(drivers/ufs/) 전체를 심층 분석합니다.

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

핵심 요약

UFS(Universal Flash Storage)는 JEDEC JESD220 표준에 의해 정의된 고성능 임베디드 스토리지 인터페이스입니다. MIPI M-PHY 물리 계층(최대 2레인, 각 레인 당 최대 23.2Gbps)과 UniPro 전송 프로토콜을 기반으로 하며, 상위 응용 계층에서 SCSI 명령 체계를 사용합니다. 전이중(full-duplex) 직렬 통신으로 eMMC 대비 수 배 높은 대역폭과 낮은 지연 시간을 제공합니다. Linux 커널에서는 drivers/ufs/ 디렉터리의 ufshcd 드라이버가 UFS 호스트 컨트롤러를 관리합니다.
  • ufs_hba — UFS 호스트 컨트롤러 하나를 대표하는 최상위 구조체. UFSHCI 레지스터, UTRL/UTMRL, 클럭, 전원 상태를 관리합니다.
  • ufshcd_lrb — UFS Local Reference Block. 개별 I/O 요청의 SCSI 명령, UPIU, UTRD를 연결하는 중간 구조체입니다.
  • utp_transfer_req_desc — UTP Transfer Request Descriptor(UTRD). UFSHCI 하드웨어가 DMA로 읽어가는 전송 요청 디스크립터입니다.
  • scsi_device — UFS LU(Logical Unit) 하나에 대응하는 SCSI 디바이스. UFS는 최대 32개의 일반 LU와 4개의 Well-Known LU를 지원합니다.
  • /dev/sda — UFS는 SCSI 계층을 사용하므로 블록 디바이스가 /dev/sda, /dev/sdb 등으로 나타납니다.
UFS vs eMMC vs NVMe vs SATA 비교
항목UFSeMMCNVMeSATA
인터페이스직렬 MIPI M-PHY (1~2 레인)병렬 8-bit MMC직렬 PCIe (x1~x4)직렬 SATA (1 레인)
최대 속도4.6 GB/s (UFS 4.0, 2L HS-G5)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 스펙 버전 변천
스펙 버전발표 연도최대 Gear최대 속도 (2L)주요 특징
UFS 1.0 (JESD220)2011PWM-G1~3 MB/s초기 규격, MIPI M-PHY + UniPro + SCSI 기반 아키텍처 정의
UFS 1.1 (JESD220A)2012HS-G1300 MB/sHS(High Speed) Gear 도입, Boot LU 지원
UFS 2.0 (JESD220B)2013HS-G21.2 GB/sHS-G2, 2레인 지원, RPMB(Replay Protected Memory Block)
UFS 2.1 (JESD220C)2016HS-G21.2 GB/s보안 강화, DeepSleep, 인라인 암호화 옵션
UFS 3.0 (JESD220D)2018HS-G42.9 GB/sHS-G4, Write Booster, HPB(Host Performance Booster)
UFS 3.1 (JESD220E)2020HS-G42.9 GB/sWrite Booster 필수화, HPB 표준화, DeepSleep 개선
UFS 4.0 (JESD220F)2022HS-G54.6 GB/sHS-G5 (11.6Gbps/lane), MCQ(Multi-Circular Queue), 향상된 RPMB
UFS 4.12024HS-G54.6 GB/s향상된 전원 관리, Performance Throttling Notification
/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 배열 */
    struct ufshcd_lrb *lrb;          /* LRB 배열 (태그별) */
    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 디바이스 확인
$ lsscsi
[0:0:0:49488] disk    SAMSUNG  KLUDG4U1EA-B0C1  0200  /dev/sda
[0:0:0:49456] disk    SAMSUNG  KLUDG4U1EA-B0C1  0200  /dev/sdb

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

# UFS 디바이스 속성 확인
$ cat /sys/class/scsi_device/0:0:0:49488/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
    사용자 공간의 read()/write() 호출이 VFS, 파일시스템을 거쳐 블록 레이어의 struct request로 변환됩니다. blk-mq가 이를 SCSI mid-layer에 전달하면, scsi_queue_rq()struct scsi_cmnd를 생성하고 UFS 호스트 드라이버의 ufshcd_queuecommand()을 호출합니다.
    /* SCSI mid-layer → UFS 호스트 드라이버 진입점 */
    static int ufshcd_queuecommand(struct Scsi_Host *host,
                                    struct scsi_cmnd *cmd)
    {
        struct ufshcd_lrb *lrbp;
        int tag = scsi_cmd_to_rq(cmd)->tag;
    
        lrbp = &hba->lrb[tag];
        lrbp->cmd = cmd;
        lrbp->task_tag = tag;
        lrbp->lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun);
    
        ufshcd_compose_devman_upiu(hba, lrbp);  /* UPIU 구성 */
        ufshcd_send_command(hba, tag);            /* doorbell ring */
        return 0;
    }
  2. UPIU(UFS Protocol Information Unit) 및 UTRD 구성
    ufshcd_compose_devman_upiu()는 SCSI CDB(Command Descriptor Block)를 UPIU 형식으로 변환합니다. Command UPIU(32바이트 헤더 + CDB)를 UCD(UTP Command Descriptor) 내에 배치하고, 데이터 전송이 필요한 경우 PRDT(Physical Region Descriptor Table)를 scatter-gather 리스트로부터 구성합니다. UTRD(UTP Transfer Request Descriptor)는 UCD 주소, 데이터 방향, 응답 UPIU 오프셋 등을 담습니다.
  3. UFSHCI DMA 매핑 및 Doorbell 링
    UTRD와 UCD가 DMA 접근 가능한 메모리에 준비되면, ufshcd_send_command()가 UFSHCI의 UTRLDBR(UTP Transfer Request List DoorBell Register)에 해당 태그 비트를 설정합니다. 호스트 컨트롤러 하드웨어는 이를 감지하여 DMA로 UTRD를 읽어가고, UCD 내의 Command UPIU를 추출합니다.
    /* Doorbell 링 — UFSHCI에 전송 시작 알림 */
    void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag)
    {
        ufshcd_writel(hba, 1 << task_tag, REG_UTP_TRANSFER_REQ_DOOR_BELL);
        /* 하드웨어가 UTRD[task_tag]를 DMA로 fetch */
    }
  4. M-PHY/UniPro 전송 및 디바이스 처리
    UFSHCI 하드웨어는 Command UPIU를 UniPro 프레임으로 캡슐화하고 M-PHY 물리 계층을 통해 UFS 디바이스에 전송합니다. 디바이스는 UPIU를 수신하여 SCSI 명령을 해석하고, NAND 플래시에 대한 읽기/쓰기를 수행합니다. 쓰기 요청의 경우, 디바이스는 먼저 RTT(Ready To Transfer) UPIU를 보내 호스트에 데이터 전송을 요청합니다.
  5. 응답 UPIU 수신 및 SCSI 완료
    디바이스가 명령 처리를 완료하면 Response UPIU를 호스트에 전송합니다. UFSHCI가 이를 수신하여 UCD의 Response UPIU 영역에 기록하고, 인터럽트를 발생시킵니다. 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 Host (SoC) UFS Device Application Layer SCSI Command Set (READ/WRITE/UNMAP) UFS Command Set (Query, NOP, Task Mgmt) Application Layer SCSI Target + FTL + NAND Controller LU Management, Descriptor/Attribute/Flag UFS Transport Protocol (UTP) UPIU (UFS Protocol Information Unit) UTRD / UCD / PRDT 관리 UFS Transport Protocol (UTP) UPIU Decode / Response Generation Command Queuing, Task Management UniPro (MIPI) L4: Transport (CPort, T_PDU) L3: Network / L2: Data Link (AFC) L1.5: PHY Adapter (PA_GEAR, PA_PWR) UniPro (MIPI) L4: Transport (CPort, T_PDU) L3: Network / L2: Data Link (AFC) L1.5: PHY Adapter MIPI M-PHY (L1) HS-Gear 1~5 / PWM-Gear 1~7 / TX&RX Lane MIPI M-PHY (L1) HS-Gear 1~5 / PWM-Gear 1~7 / TX&RX Lane TX Lane RX Lane UFSHCI (Host Controller Interface) MMIO Registers, UTRL, UTMRL, DMA Engine NAND Flash Array 3D TLC/QLC NAND, FTL, ECC, Wear Leveling Full-Duplex Serial Link: TX/RX 동시 전송 가능 (최대 2레인)

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 기반 라우팅DeviceID, N_PDU
UniPro L2 (Data Link)MIPI UniPro흐름 제어, 오류 복구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/ 디렉터리에 집중되어 있습니다. 커널 6.x 기준으로 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는 그 위에 계층화된 프로토콜로, 흐름 제어, 오류 복구, 레인 관리 등을 담당합니다.
UFS Host (SoC M-PHY TX/RX) UFS Device (Device M-PHY TX/RX) TX Lane 0 RX Lane 0 DIF_P (Differential Pair +) DIF_N (Differential Pair -) RX Lane 0 TX Lane 0 TX Lane 1 (opt) RX Lane 1 (opt) RX Lane 1 (opt) TX Lane 1 (opt) M-PHY HS-Gear 속도 (per Lane) G1 1.46 Gbps G2 2.92 Gbps G3 5.83 Gbps G4 11.6 Gbps G5 23.2 Gbps Rate A (Series A): G1~G3 기본 Rate B (Series B): G4~G5 고속 2-Lane HS-G5: 2 x 23.2 Gbps = 46.4 Gbps (양방향 합산 92.8 Gbps)
M-PHY HS-Gear 속도 사양
HS-GearRate Series속도 (per Lane)유효 대역폭 (per Lane)2-Lane 유효 대역폭UFS 버전
HS-G1A1.4584 Gbps~150 MB/s~300 MB/sUFS 1.1+
HS-G2A2.9168 Gbps~300 MB/s~600 MB/sUFS 2.0+
HS-G3A5.8336 Gbps~600 MB/s~1.2 GB/sUFS 2.1+
HS-G4B11.6672 Gbps~1.2 GB/s~2.4 GB/sUFS 3.0+
HS-G5B23.3344 Gbps~2.3 GB/s~4.6 GB/sUFS 4.0+
M-PHY PWM-Gear 속도 사양 (저전력 모드)
PWM-Gear속도 (per Lane)용도
PWM-G19 Mbps링크 초기화 시 기본 모드
PWM-G218 Mbps저전력 데이터 전송
PWM-G336 Mbps저전력 데이터 전송
PWM-G472 Mbps저전력 데이터 전송
PWM-G5144 Mbps저전력 유지 통신
PWM-G6288 Mbps저전력 유지 통신
PWM-G7576 Mbps저전력 유지 통신

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() 콜백을 통해 플랫폼 특화 PHY 설정을 적용합니다. 전환 실패 시 SLOW_MODE 폴백을 시도합니다.

UPIU (UFS Protocol Information Unit) 프로토콜

UPIU는 UFS 호스트와 디바이스 간에 주고받는 모든 정보의 기본 단위입니다. 32바이트 고정 헤더와 가변 길이 데이터 세그먼트로 구성되며, SCSI 명령, 데이터 전송, 응답, Query 요청/응답, NOP, Task Management 등 모든 UFS 통신이 UPIU 형식으로 이루어집니다.
UPIU Header Layout (32 Bytes) Byte 0-3 Byte 4-7 Byte 8-11 Byte 12-15 Transaction Type [7:0] Flags LUN [7:0] Task Tag [7:0] Cmd Set Type / IID Query Func / TMF Response Status EHS Length Device Info Data Segment Len [15:0] DWORD 3~7: CDB (16 bytes) / Query Parameters Command UPIU: SCSI CDB 16바이트 / Query: TSF (Transaction Specific Fields) EHS (Extra Header Segment) — optional, UFS 4.0+ Data Segment (variable length, max 65535 bytes) UPIU Transaction Types Host → Device (Requests) 0x01 NOP OUT 0x06 COMMAND 0x02 DATA OUT 0x04 TASK MANAGEMENT REQ 0x16 QUERY REQUEST Device → Host (Responses) 0x20 NOP IN 0x21 RESPONSE 0x22 DATA IN 0x31 READY TO TRANSFER 0x36 QUERY RESPONSE 0x3F REJECT (any direction) 0x24 TASK MANAGEMENT RESP Command UPIU: 32B Header + 16B CDB = 48 Bytes (최소) + Data Segment Query UPIU: 32B Header + TSF(Transaction Specific Fields) + Data Segment Response UPIU Status Codes 0x00: GOOD 0x02: CHECK_CONDITION 0x04: BUSY 0x08: RESERVATION_CONFLICT 0x28: TASK_ABORTED
UPIU Transaction Types
코드이름방향설명
0x01NOP OUTHost → Device링크 활성 확인용. 디바이스는 NOP IN으로 응답
0x20NOP INDevice → HostNOP OUT에 대한 응답
0x06COMMANDHost → 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 읽기에도 사용
WRITE(16)0x8A데이터 쓰기 (64-bit LBA)대용량 LU 지원
SYNCHRONIZE_CACHE(10)0x35디바이스 캐시 플러시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 {
    __be32 dword_0;  /* Transaction Type | Flags | LUN | Task Tag */
    __be32 dword_1;  /* Cmd Set Type/IID | Query Func | Response | Status */
    __be32 dword_2;  /* EHS Length | Device Info | Data Segment Length */
    __be32 dword_3;  /* Reserved (or Query specific) */
};

/* 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_compose_devman_upiu() */
static void ufshcd_prepare_utp_scsi_cmd_upiu(
    struct ufshcd_lrb *lrbp, u8 upiu_flags)
{
    struct utp_upiu_req *ucd_req_ptr = lrbp->ucd_req_ptr;
    struct scsi_cmnd *cmd = lrbp->cmd;

    /* UPIU 헤더 설정 */
    ucd_req_ptr->header.dword_0 = cpu_to_be32(
        UPIU_TRANSACTION_COMMAND << 24 |   /* Transaction Type */
        upiu_flags << 16 |                  /* Flags (R/W/None) */
        lrbp->lun << 8 |                   /* LUN */
        lrbp->task_tag);                    /* Task Tag */

    ucd_req_ptr->header.dword_1 = cpu_to_be32(
        UPIU_COMMAND_SET_TYPE_SCSI << 24);  /* SCSI Command Set */

    /* 예상 데이터 전송 길이 */
    ucd_req_ptr->sc.exp_data_transfer_len =
        cpu_to_be32(scsi_bufflen(cmd));

    /* SCSI CDB 복사 */
    memcpy(ucd_req_ptr->sc.cdb, cmd->cmnd, min_t(u8, cmd->cmd_len, UFS_CDB_SIZE));
}
RTT(Ready To Transfer) 흐름: 쓰기 명령의 경우, 호스트가 Command UPIU를 보낸 후 즉시 데이터를 전송하지 않습니다. 디바이스가 RTT UPIU를 보내 “데이터를 보내도 좋다”고 알려주면, 그때 호스트가 DATA OUT UPIU로 데이터를 전송합니다. 이 메커니즘은 디바이스 내부 버퍼 관리를 위한 것으로, eMMC의 busy 시그널링과 유사한 역할을 합니다. 읽기 명령은 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 M-PHY / UniPro Interface (Device Side) UTP Engine — UPIU Decode / Response / Command Queue (32 slots) LU Manager — Logical Unit Routing & Descriptor/Attribute/Flag Engine General Purpose Logical Units (최대 32개) LU 0 /dev/sda LU 1 /dev/sdb LU 2 /dev/sdc ... LU 31 /dev/sd? 각 LU는 독립 SCSI 디바이스 — LBA 주소 공간, 블록 크기, 프로비저닝 타입 별도 Well-Known LUs W-LU BOOT LUN=0xB0 W-LU RPMB LUN=0xC4 W-LU Device LUN=0xD0 W-LU Boot 1 LUN=0xB1 Flash Translation Layer (FTL) — L2P Mapping, GC, Wear Leveling, ECC Write Buffer(SLC Cache) | HPB L2P Cache | Write Booster Buffer 3D NAND Flash Array TLC/QLC NAND | Multi-Plane | Multi-Die | Multi-Channel
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 (부트로더 저장)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 주요 필드 오프셋 */
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비트 값으로, 실시간 상태나 동적 설정(전원 모드, 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 MMIO Registers CAP (0x00) VER (0x08) HCE (0x34) HCS (0x30) IS (0x20) IE (0x24) UTRLBA / UTRLBAU (0x50/0x54) — Transfer Request List Base UTRLDBR (0x58) — Transfer Request DoorBell UTRLCLR (0x5C) — Transfer Request List Clear UTMRLBA / UTMRLBAU (0x70/0x74) — Task Mgmt Base UTMRLDBR (0x78) — Task Mgmt DoorBell UICCMD (0x90) UCMDARG1-3 (0x94+) MCQ Config (0x300+) — UFS 4.0+ Multi-Circular Queue Vendor Specific Registers (0xC0~) DMA Memory Layout UTRL — UTP Transfer Request List (DMA 주소: UTRLBA/UTRLBAU가 가리킴) UTRD[0] UTRD[1] UTRD[2] ... 최대 32 슬롯 (SDB) — 각 UTRD = 32 bytes UTRD → UCD(Command Descriptor) DMA 포인터 포함 UCD — UTP Command Descriptor (각 UTRD가 가리키는 DMA 메모리) Command UPIU (32B+) Response UPIU (32B+) PRDT (Physical Region Descriptor Table) Scatter-Gather: {base_addr, size, flags} x N entries UTMRL — UTP Task Management Request List 최대 8 슬롯 — Abort Task, LU Reset, Query Task 각 UTMRD = Task Mgmt Request UPIU + Response UPIU UFSHCI v3.0+: UTRL max 32 slots / UTMRL max 8 slots / UFS 4.0 MCQ: variable SQ/CQ
UFSHCI 주요 레지스터
오프셋이름접근설명
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 {
    /* DWord 0-1: Command Descriptor 관련 */
    struct {
        __le32 dword_0;  /* CT(Command Type) | DD(Data Direction) | I(Interrupt) | OCS */
        __le32 dword_1;  /* Reserved */
    } header;

    /* DWord 2-3: UCD Base Address (Command UPIU 주소) */
    __le32 command_desc_base_addr_lo;  /* UCD 하위 32비트 주소 */
    __le32 command_desc_base_addr_hi;  /* UCD 상위 32비트 주소 */

    /* DWord 4-5: Response UPIU offset/length + PRDT offset/length */
    __le16 response_upiu_length;  /* Response UPIU 길이 (DWord 단위) */
    __le16 response_upiu_offset;  /* UCD 내 Response UPIU 오프셋 */
    __le16 prd_table_length;      /* PRDT 엔트리 수 */
    __le16 prd_table_offset;      /* UCD 내 PRDT 오프셋 */

    /* DWord 6-7: Reserved */
    __le32 reserved[2];
};

/* 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) 콜백을 통해 하드웨어 특화 동작을 제공합니다.
Block Layer (blk-mq) → SCSI Mid-Layer (scsi_queue_rq) ufshcd Core (drivers/ufs/core/ufshcd.c) struct ufs_hba Scsi_Host | mmio_base | utrdl | lrb[] | vops dev_info | clk_list | vreg | pm_lvl | caps Scsi_Host scsi_host_template ufshcd_queuecommand() SCSI CDB → UPIU → doorbell ufshcd_send_command() UTRLDBR write → HW DMA ufshcd_transfer_req_compl() OCS check → scsi_done() ufshcd_init() / _async_scan() ufshcd_err_handler() ufshcd_uic_cmd(DME_SET/GET) vops Callbacks init, setup_clocks pwr_change_notify hce_enable_notify Clock/Regulator core_clk, ref_clk, pclk vcc, vccq, vccq2 regulators Runtime PM hibernate / resume rpm_lvl / spm_lvl sysfs / debugfs / bsg ufs-sysfs.c, ufs-debugfs.c ufs_bsg.c (Query UPIU) UFSHCI Hardware (MMIO Registers + DMA) UTRL, UTMRL, Interrupt, UIC Command, MCQ Config ufs-qcom.c Qualcomm Snapdragon ufs-exynos.c Samsung Exynos ufs-mediatek.c MediaTek Dimensity ufshcd-pci.c Intel / PCI-based Platform drivers register vops callbacks → ufshcd_pltfrm_init() → ufshcd_init()

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) 배열 === */
    struct ufshcd_lrb *lrb;          /* 태그별 런타임 정보 */
    unsigned long lrb_in_use;        /* 사용 중인 태그 비트맵 */

    /* === 벤더 콜백 === */
    const struct ufs_hba_variant_ops *vops;
    struct ufs_hba_variant *priv;    /* 벤더 전용 데이터 */

    /* === 역량 및 슬롯 수 === */
    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 런타임 설정 */

    /* 프로그램 키 (인라인 암호화) */
    int  (*program_key)(struct ufs_hba *,
            const union ufs_crypto_cfg_entry *, int);
};

/* 벤더 드라이버 등록 예시 (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            = ufs_qcom_ice_program_key,
    .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_Hostufs_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() 호출 */
static int ufshcd_queuecommand(struct Scsi_Host *host,
                                struct scsi_cmnd *cmd)
{
    struct ufs_hba *hba = shost_priv(host);
    int tag = scsi_cmd_to_rq(cmd)->tag;
    struct ufshcd_lrb *lrbp = &hba->lrb[tag];

    /* 히버네이트 상태면 resume 먼저 */
    ufshcd_hold(hba);

    /* LRB 설정 */
    lrbp->cmd = cmd;
    lrbp->task_tag = tag;
    lrbp->lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun);

    /* UTRD 기본 필드 설정 */
    ufshcd_prepare_lrbp_crypto(cmd, lrbp);  /* 인라인 암호화 */
    lrbp->req_abort_skip = false;

    /* UPIU + PRDT 구성 */
    ufshcd_comp_scsi_upiu(hba, lrbp);
    /* → Command UPIU 헤더, CDB, PRDT scatter-gather 설정 */

    /* 2. Doorbell 링으로 HW에 전송 */
    ufshcd_send_command(hba, tag);
    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. 개별 요청 완료 처리 */
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 ufshcd_lrb *lrbp = &hba->lrb[tag];
        struct scsi_cmnd *cmd = lrbp->cmd;

        /* UTRD의 OCS(Overall Command Status) 확인 */
        int ocs = le32_to_cpu(lrbp->utr_descriptor_ptr->header.dword_0)
                  & OCS_MASK;

        if (ocs == OCS_SUCCESS) {
            /* Response UPIU에서 SCSI Status 추출 */
            int result = ufshcd_transfer_rsp_status(hba, lrbp);
            cmd->result = result;
        } else {
            /* OCS 에러 → 에러 핸들러로 위임 */
            cmd->result = DID_ERROR << 16;
        }

        scsi_done(cmd);   /* SCSI 레이어에 완료 통보 */
        hba->outstanding_reqs &= ~(1UL << tag);
    }
}
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인터럽트 핸들러 (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 호스트 템플릿: ufshcdscsi_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_queuehba->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/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)를 통해 비동기적으로 처리됩니다. 인터럽트 컨텍스트에서 에러가 감지되면 워크를 스케줄하고, 별도 스레드에서 복구를 수행합니다. 이 구조는 에러 복구 중에도 시스템이 응답성을 유지할 수 있게 합니다.

/* 에러 핸들러 워크 — 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() 래퍼 매크로를 통해 이 콜백들을 호출합니다.

플랫폼 드라이버 비교

드라이버파일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,
    .program_key            = ufs_qcom_ice_program_key,  /* ICE 암호화 */
    .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,
    .program_key            = exynos_ufs_program_key,    /* FMP 암호화 */
};

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

/* 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 개입 없이 데이터 경로에서 직접 암복호화를 수행하므로 성능 오버헤드가 거의 없습니다.

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

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 Application (read/write) VFS (Virtual File System) Block Layer (blk-mq) I/O 스케줄링, request 병합, hw queue 분배 SCSI Mid-Layer scsi_cmnd 생성, CDB 구성, 에러 복구 UFS Host Controller Driver (ufshcd) UPIU 패킹, UTRD/PRDT 구성, Doorbell Ring UFSHCI (HW Registers) UTRLDBR Doorbell, Transfer/Task Mgmt Ring M-PHY / UniPro UFS Device (NAND Flash) struct request struct scsi_cmnd UPIU + UTRD Crypto 처리

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 진입점 */
static int ufshcd_queuecommand(struct Scsi_Host *host,
                               struct scsi_cmnd *cmd)
{
    struct ufs_hba *hba = shost_priv(host);
    int tag = scsi_cmd_to_rq(cmd)->tag;
    struct ufshcd_lrb *lrbp = &hba->lrb[tag];

    /* 1. LRB(Local Reference Block) 설정 */
    lrbp->cmd = cmd;
    lrbp->task_tag = tag;
    lrbp->lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun);

    /* 2. UPIU 구성 (Command UPIU 패킹) */
    ufshcd_compose_devman_upiu(hba, lrbp);  /* 또는 ufshcd_compose_scsi_cmd() */

    /* 3. UTRD(Transfer Request Descriptor) 설정 */
    ufshcd_prepare_utp_scsi_cmd_upiu(lrbp, cmd->sc_data_direction);

    /* 4. Crypto 설정 (인라인 암호화 사용 시) */
    ufshcd_prepare_req_desc_hdr(lrbp, &lrbp->tr_type, UTP_CMD_TYPE_SCSI);

    /* 5. Doorbell Ring — HW에 전송 요청 */
    ufshcd_send_command(hba, tag, hba->nutrs);

    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이름용도접근 방식
0xD0REPORT LUNS W-LULU 목록 보고SCSI REPORT LUNS
0x50UFS Device W-LU디바이스 관리 (Descriptor/Flag/Attribute R/W)/dev/bsg/ufs-bsg0
0xC4RPMB W-LUReplay Protected Memory Block 접근/dev/sgX + SECURITY PROTOCOL
0xB0Boot W-LU부트 이미지 저장 (Boot LU A)/dev/sdX
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) 아키텍처 CPU 0 CPU 1 CPU 2 CPU 3 SQ 0 (default) SQE[0]..SQE[N-1] SQ 1 (default) SQ 2 (read) SQ 3 (poll) UFS Host Controller (UFSHCI 4.0+) SQ Tail Doorbell Reg CQ Head Doorbell Reg MCQ Config Registers CQ 0 CQE[0]..CQE[N-1] CQ 1 CQ 2 CQ 3 Legacy SDB (Single Doorbell) UTRL (단일 Transfer Request List) UTRLDBR (단일 Doorbell Register) 모든 CPU가 하나의 doorbell 경쟁 Lock contention + Interrupt storm 최대 32 슬롯 MCQ (Multi-Circular Queue) SQ/CQ 쌍 × N (per-CPU 분배) 개별 SQ Tail / CQ Head Doorbell CPU별 독립 큐 → Lock-free 전송 CQ별 독립 인터럽트 → 분산 처리 큐당 최대 256 엔트리
MCQ의 핵심 이점: MCQ는 NVMe의 SQ/CQ 모델을 UFS에 도입한 것입니다. 기존 SDB 모드에서는 모든 CPU가 단일 UTRLDBR(Doorbell Register)에 접근해야 했으므로 spin lock 경쟁이 심했습니다. MCQ는 per-CPU SQ/CQ 쌍을 제공하여 lock contention을 제거하고, CQ별 인터럽트로 인터럽트 처리도 분산합니다. 결과적으로 랜덤 4K IOPS가 SDB 대비 40~60% 향상됩니다.

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 초기화 핵심 */
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. SQ/CQ 활성화 */
    for (i = 0; i < num_queues; i++) {
        ufshcd_mcq_sq_start(hba, &hba->mcq_sq[i]);
        ufshcd_mcq_cq_start(hba, &hba->mcq_cq[i]);
    }

    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 정지 — 에러 복구 또는 shutdown 시 */
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 없이 폴링 방식으로 완료를 확인하는 저지연 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)합니다. 이는 스마트폰에서 앱 설치, 사진 촬영 등의 버스트 쓰기 시나리오에서 큰 성능 이점을 제공합니다.

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)
wb_buf_flush_en동일 경로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;

    /* fWriteBoosterEn 플래그 설정/해제 */
    if (enable)
        attr = QUERY_ATTR_IDN_WB_EN;
    else
        attr = 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) 동작 원리 Host (Linux Kernel) Host DRAM — L2P Cache (HPB) Region 0 | Region 1 | ... | Region N 각 Region = 여러 Subregion (4KB L2P 엔트리) ufshpb 드라이버 ufshpb_prep() → HPB READ(16) 구성 HPB READ(16) Command CDB + HPB Entry (PPN: Physical Page Number) UFS Device UFS Controller (FTL) L2P Table (SRAM 캐시 + NAND 저장) NAND Flash (L2P Miss → 추가 지연) L2P 테이블이 SRAM에 없으면 NAND 접근 필요 NAND Data Pages HPB Entry 유효 시 → NAND L2P 조회 생략! 1. L2P 조회 2. HPB READ(16) + PPN 3. L2P 조회 생략! 4. 직접 데이터 읽기 5. 데이터 응답 HPB 없음 ~200μs (L2P miss) HPB 적용 ~80μs (L2P skip)

Region과 Subregion 개념

HPB는 UFS 디바이스의 논리적 주소 공간을 RegionSubregion으로 나누어 관리합니다. 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 테이블 미스가 빈번한 워크로드에서 읽기 지연을 40~60% 단축할 수 있습니다. 다만 호스트 DRAM 사용량이 증가하므로 (활성 Region당 수 MB), 메모리 제약이 있는 시스템에서는 활성 Region 수를 제한해야 합니다.

인라인 암호화 — Inline Encryption Framework

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

UFS 인라인 암호화 (Inline Encryption) 데이터 흐름 Block I/O Request + bio_crypt_ctx (key, DUN) ufshcd (UFS HCD) UTRD에 Crypto 필드 삽입 UTRD CCI (Crypto Config Index) DUN (Data Unit Number) Crypto Engine (ICE / FMP) AES-256-XTS 암복호화 Keyslot[CCI]에서 키 로드 → DUN으로 IV 생성 Keyslot Table Slot 0: Key_A (AES-256-XTS) Slot 1: Key_B ... CCI로 키 선택 암호화된 데이터 M-PHY / UniPro Link UFS NAND Flash 암호화된 데이터 저장 (at-rest encryption) fscrypt / dm-crypt blk_crypto_profile을 통해 키 프로그래밍 blk_crypto_profile 키슬롯 관리, 알고리즘 capability CCI = Crypto Configuration Index | DUN = Data Unit Number | ICE = Inline Crypto Engine | FMP = Flash Memory Protector
인라인 암호화 vs 소프트웨어 암호화: 소프트웨어 암호화(dm-crypt fallback)는 CPU에서 데이터를 암복호화하므로 추가 메모리 복사와 CPU 사이클이 필요합니다. 인라인 암호화는 DMA 경로에 암호화 엔진이 삽입되어 데이터가 통과하면서 투명하게 처리됩니다. 결과적으로 순차 읽기/쓰기 성능이 소프트웨어 방식 대비 20~50% 향상되며, 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 등 인라인 암호화의 전체 스택 아키텍처는 Crypto Framework — 스토리지 인라인 암호화에서 상세히 다룹니다.

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를 통해 데이터의 무결성과 재생 공격(Replay Attack) 방지를 보장합니다. RPMB는 Well-Known LU(W-LUN 0xC4)로 접근하며, 보안 부팅(Secure Boot) 인증 정보, 롤백 방지 카운터, 디바이스 키 저장 등에 사용됩니다.

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

RPMB 프레임 구조

오프셋크기필드설명
0x0000196바이트Stuff bytes패딩 (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이전 쓰기 작업의 결과 확인
0x0100Authentication Key Write Response키 쓰기 응답
0x0200Read Write Counter Response카운터 읽기 응답
0x0300Authenticated Data Write Response데이터 쓰기 응답
0x0400Authenticated Data Read Response데이터 읽기 응답

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 상태 보존 등에 핵심적으로 사용됩니다.

전원 관리 (Power Management)

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

UFS 전원 상태 전이 (Power State Transitions) Active Link Up, Clocks On Idle (Clock Gated) Link Up, Clocks Off Hibernate (H8) Link Hibern8, Clocks Off Sleep Device Sleep, VCCQ Off PowerDown VCC/VCCQ Off, Link Down SSU PowerDown (Emergency) gate_delay 만료 I/O 요청 auto_hibern8 또는 rpm_suspend hibern8_exit system_suspend (sleep) system_resume system_suspend (off) full re-init Active (전력 최대) Clock Gated (중간) Hibernate (저전력) Sleep (초저전력) 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 호스트 드라이버는 일정 시간 유휴 상태가 지속되면 자동으로 클럭을 차단합니다. 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 기능을 사용하면 하드웨어가 유휴 타이머 만료 시 자동으로 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_lvlspm_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 상태 확인
cat /sys/bus/platform/devices/*/clkscale_enable

# Gear scaling 비활성화 (디버깅/벤치마크 시)
echo 0 > /sys/bus/platform/devices/1d84000.ufshc/clkscale_enable

# 현재 링크 속도 확인
cat /sys/kernel/debug/ufshcd0/show_hba
# 출력 예: HS Gear: 4, Lanes: 2, Rate: B (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/BbPreEOLInfo 속성은 디바이스 디스크립터를 통해 조회하며, 임계치를 초과하면 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 경고를 무시하면 안 됩니다: bPreEOLInfo0x02(Warning) 또는 0x03(Urgent)인 상태에서 계속 사용하면, 예비 블록 완전 소진 시 갑작스러운 읽기 전용 전환 또는 디바이스 응답 불가가 발생할 수 있습니다. 특히 임베디드/자동차 시스템에서는 정기적 건강 모니터링과 사전 교체 정책이 필수입니다. 프로덕션 환경에서는 bDeviceLifeTimeEstA/B ≥ 0x08 시점에 교체 알람을 설정하는 것을 권장합니다.

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

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

UFS 디바이스 트리 노드 구조 ufshc@1d84000 compatible = "qcom,sm8550-ufshc" reg, interrupts, freq-table-hz Clocks core_clk, bus_clk, ref_clk, tx/rx_symbol Regulators vcc, vccq, vccq2 vdd-hba-supply UFS PHY phys = <ufs_phy> M-PHY (qcom,sm8550) Reset resets = <gcc ...> reset-names ufs_phy: phy@1d87000 compatible = "qcom,sm8550-qmp-ufs-phy" reg, clocks, vdda-phy-supply, vdda-pll-supply #phy-cells = <0> 주요 DT 속성 reg = <0x1d84000 0x3000>; interrupts = <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>; freq-table-hz = <75000000 300000000>, ... vcc-supply = <&vreg_l7b_2p96>; vccq-supply = <&vreg_l9b_1p2>; vccq2-supply = <&vreg_s4a_1p8>; vcc-max-microamp = <800000>;

표준 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 = ;

    /* 클럭 설정 */
    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 = ;

    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 = ;

    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~5)5
spm_lvlSystem PM 전원 레벨
RWSystem Suspend 시 전원 레벨 (0~5)5
auto_hibern8RWAuto-Hibernate8 타이머 (ms), 0=비활성10
wb_onRWWrite Booster 활성화 (0/1)1
wb_buf_flush_enRWWB 버퍼 플러시 활성화0
wb_flush_thresholdRWWB 플러시 시작 임계값 (%)40
clkscale_enableRWGear scaling 활성화 (0/1)1
clkgate_enableRWClock gating 활성화 (0/1)1
clkgate_delay_msRWClock gating 지연 (ms)150
rtc_update_msRWRTC 동기화 주기 (ms)10000
urgent_bkopsROUrgent BKOPS 발생 횟수0

전원 레벨 상세

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 타이머 설정 (10ms)
echo 10 > ${UFS_PATH}/auto_hibern8

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

# Clock gating 지연 조정 (200ms)
echo 200 > ${UFS_PATH}/clkgate_delay_ms

# Gear scaling 비활성화 (벤치마크 시)
echo 0 > ${UFS_PATH}/clkscale_enable

# 건강 정보 조회
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 동작을 실시간으로 조정할 수 있습니다. 벤치마크 전에 clkscale_enable=0, auto_hibern8=0으로 설정하면 전원 상태 전이로 인한 측정 오차를 제거할 수 있습니다. 프로덕션에서는 rpm_lvl을 3(Sleep+H8)으로, auto_hibern8을 5~15ms로 설정하는 것이 일반적입니다.

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

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

UFS 에러 핸들링 흐름 (ufshcd_err_handler) UFS Error 감지 (ISR) ufshcd_err_handler() OCS Error Overall Command Status UIC Error UniPro/M-PHY 링크 에러 SCSI Error 디바이스 응답 에러 Host Error 시스템 버스/레지스터 커맨드 재시도 Link Recovery Device Reset (LU) Host Reset (Full) 에스컬레이션: 재시도 실패 → 링크 복구 → 디바이스 리셋 → 호스트 리셋 복구 성공 복구 실패 → scsi_host_reset / 시스템 패닉

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디바이스 치명적 에러디바이스 리셋 + 호스트 리셋
0x0FINVALID_OCS_VALUEUFS HCI 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 엔트리 확인
ls /sys/kernel/debug/ufshcd0/
# dump_device_desc  dump_health_desc  dump_string_desc
# err_stats         host_regs         req_stats
# show_hba          stats

# HBA 상태 전체 덤프
cat /sys/kernel/debug/ufshcd0/show_hba
# 출력 예:
#   UFS Host state: OPERATIONAL
#   lrb in use: 0x0
#   Outstanding reqs: 0x0
#   HS Gear: 4, Lanes: 2, Rate: B
#   Dev Power Mode: ACTIVE
#   Link State: UP

# 에러 통계 확인
cat /sys/kernel/debug/ufshcd0/err_stats
# UIC Errors (PA): 0
# UIC Errors (DL): 0
# UIC Errors (NL): 0
# UIC Errors (TL): 0
# UIC Errors (DME): 0
# SCSI cmd errors: 0
# Host reset count: 0

# 호스트 레지스터 덤프 (하드웨어 상태 분석)
cat /sys/kernel/debug/ufshcd0/host_regs

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=y          # Host Performance Booster
# 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), 블록 레이어, 파일시스템에 이르기까지 다층적으로 접근해야 합니다. 각 계층의 튜닝 포인트를 이해하면 단일 스토리지에서도 2~5배의 성능 차이를 만들 수 있습니다.

UFS 성능 최적화 계층 (Bottom-Up) PHY 계층: Gear / Lane 최적화 HS-G4B 2-Lane = 2×2.9 GB/s (Max), Gear scaling 비활성 (벤치마크) 컨트롤러: MCQ (Multi-Circular Queue) SQ/CQ per CPU → UTRD lock contention 제거, nr_hw_queues 튜닝 디바이스 기능: Write Booster (WB) SLC 캐시로 burst write 가속 (2~4x), wb_on=1, flush 임계값 조정 디바이스 기능: HPB (Host Performance Booster) L2P 맵 호스트 캐시 → random read 지연 감소 (0.3ms → 0.1ms) 블록 레이어 튜닝 I/O scheduler (none/mq-deadline), nr_requests, read_ahead_kb, rq_affinity 파일시스템 튜닝 F2FS (flash-friendly), ext4 (discard, journal_async_commit), IO priority 성능 영향 ↑ L6 L5 L4 L3 L2 L1 ※ 하위 계층 튜닝이 상위 계층보다 근본적인 성능 영향을 줌

튜닝 파라미터 정리

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

Gear/Lane 최적화

# 현재 Gear/Lane 상태 확인
cat /sys/kernel/debug/ufshcd0/show_hba | grep -E "Gear|Lane|Rate"
# HS Gear: 4, Lanes: 2, Rate: B

# Gear scaling 비활성화 (최대 성능 고정)
echo 0 > /sys/bus/platform/devices/1d84000.ufshc/clkscale_enable

# Power mode 변경 테스트 (개발 환경)
# 주의: 잘못된 Gear 설정은 링크 드롭을 유발
echo "HS-G4" > /sys/kernel/debug/ufshcd0/power_mode
# 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+40%Gear 고정, read_ahead=512
순차 쓰기 (128KB)~1,400 MB/s~1,800 MB/s+28%WB + scheduler=none
랜덤 읽기 (4KB)~60K IOPS~120K IOPS+100%HPB + MCQ + rq_affinity=2
랜덤 쓰기 (4KB)~40K IOPS~80K IOPS+100%WB + MCQ + nr_requests=512
혼합 R/W (7:3, 4KB)~35K IOPS~70K IOPS+100%전체 최적화 적용
벤치마크 환경 설정 체크리스트: 정확한 UFS 성능 측정을 위해 다음을 확인하세요: (1) auto_hibern8=0 — H8 전이 오버헤드 제거, (2) clkscale_enable=0 — Gear 고정, (3) scheduler=none — I/O 스케줄러 오버헤드 제거, (4) direct=1 — 페이지 캐시 바이패스, (5) thermal throttling 비활성화 확인. 프로덕션 환경에서는 전력-성능 균형을 고려하여 Gear scaling과 Auto-H8을 활성 상태로 유지하되, read_ahead_kbnr_requests 최적화는 상시 적용할 수 있습니다.

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

관련 내부 문서
페이지링크관련성
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 연계

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

  1. include/ufs/ufs.h — UFS 프로토콜 상수, UPIU 구조체, 디스크립터 IDN, 전원 모드 정의
  2. include/ufs/ufshci.h — UFSHCI 레지스터 오프셋, UTRD/UTMRD 구조체, OCS 코드 정의
  3. drivers/ufs/core/ufshcd.c — UFS 호스트 컨트롤러 드라이버 코어 (초기화, I/O 경로, 에러 핸들링, PM)
  4. drivers/ufs/core/ufs-mcq.c — Multi-Circular Queue 구현 (SQ/CQ 관리, 큐 매핑)
  5. drivers/ufs/core/ufs-sysfs.c — sysfs 속성 정의 및 읽기/쓰기 핸들러
  6. drivers/ufs/core/ufshcd-crypto.c — 인라인 암호화(ICE) 키 프로그래밍 및 crypto 프로파일
  7. drivers/ufs/host/ufs-qcom.c — Qualcomm 플랫폼 드라이버 (ICE, PHY, testbus 등 벤더 확장 예시)
/* 핵심 I/O 경로 — ufshcd_queuecommand() 에서 시작 */

/*
 * 1. SCSI 미들레이어가 scsi_cmnd를 전달
 *    → ufshcd_queuecommand()
 *
 * 2. UPIU 구성
 *    → ufshcd_compose_devman_upiu()  (Query/NOP)
 *    → ufshcd_compose_scsi_cmd()     (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
학습 시작점: UFS를 처음 접한다면 drivers/ufs/core/ufshcd.cufshcd_queuecommand() 함수부터 읽으세요. SCSI 커맨드가 UFS UPIU로 변환되어 하드웨어에 전달되는 전체 I/O 경로를 하나의 함수 호출 체인에서 따라갈 수 있습니다. 이후 ufshcd_err_handler()로 에러 복구 흐름을, ufshcd_suspend()/ufshcd_resume()으로 전원 관리 흐름을 학습하면 UFS 드라이버의 전체 구조를 파악할 수 있습니다.