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/) 전체를 심층 분석합니다.
핵심 요약
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 | eMMC | NVMe | SATA |
|---|---|---|---|---|
| 인터페이스 | 직렬 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) | 최대 65535 | 32 (NCQ) |
| 폼 팩터 | BGA (11.5x13mm) | BGA (11.5x13mm) | M.2 / U.2 / AIC | 2.5" / M.2 |
| 디바이스 노드 | /dev/sda | /dev/mmcblk0 | /dev/nvme0n1 | /dev/sda |
| 전형적 용도 | 플래그십 스마트폰, 자동차 | IoT, 미드레인지 폰 | PC, 서버 | 데스크톱, NAS |
| 전이중 통신 | 지원 | 미지원 | 지원 | 미지원 |
| 스펙 버전 | 발표 연도 | 최대 Gear | 최대 속도 (2L) | 주요 특징 |
|---|---|---|---|---|
| UFS 1.0 (JESD220) | 2011 | PWM-G1 | ~3 MB/s | 초기 규격, MIPI M-PHY + UniPro + SCSI 기반 아키텍처 정의 |
| UFS 1.1 (JESD220A) | 2012 | HS-G1 | 300 MB/s | HS(High Speed) Gear 도입, Boot LU 지원 |
| UFS 2.0 (JESD220B) | 2013 | HS-G2 | 1.2 GB/s | HS-G2, 2레인 지원, RPMB(Replay Protected Memory Block) |
| UFS 2.1 (JESD220C) | 2016 | HS-G2 | 1.2 GB/s | 보안 강화, DeepSleep, 인라인 암호화 옵션 |
| UFS 3.0 (JESD220D) | 2018 | HS-G4 | 2.9 GB/s | HS-G4, Write Booster, HPB(Host Performance Booster) |
| UFS 3.1 (JESD220E) | 2020 | HS-G4 | 2.9 GB/s | Write Booster 필수화, HPB 표준화, DeepSleep 개선 |
| UFS 4.0 (JESD220F) | 2022 | HS-G5 | 4.6 GB/s | HS-G5 (11.6Gbps/lane), MCQ(Multi-Circular Queue), 향상된 RPMB |
| UFS 4.1 | 2024 | HS-G5 | 4.6 GB/s | 향상된 전원 관리, Performance Throttling Notification |
/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 처리 흐름
ufshcd 드라이버,
UFSHCI 하드웨어, M-PHY/UniPro 링크를 거쳐 UFS 디바이스에 도달합니다.
아래 5단계로 전체 흐름을 요약합니다.
-
블록 레이어 → 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; } -
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 오프셋 등을 담습니다. -
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 */ } -
M-PHY/UniPro 전송 및 디바이스 처리
UFSHCI 하드웨어는 Command UPIU를 UniPro 프레임으로 캡슐화하고 M-PHY 물리 계층을 통해 UFS 디바이스에 전송합니다. 디바이스는 UPIU를 수신하여 SCSI 명령을 해석하고, NAND 플래시에 대한 읽기/쓰기를 수행합니다. 쓰기 요청의 경우, 디바이스는 먼저 RTT(Ready To Transfer) UPIU를 보내 호스트에 데이터 전송을 요청합니다. -
응답 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; }
UFS 아키텍처 개요
UFS의 프로토콜 스택은 네트워크 OSI 모델과 유사한 계층 구조를 갖습니다. 최상위 응용 계층에서 SCSI 명령을 생성하고, UTP 계층이 이를 UPIU로 캡슐화하며, UniPro 계층이 전송을 관리하고, M-PHY 물리 계층이 실제 직렬 데이터를 전송합니다.
| 계층 | 스펙 | 주요 역할 | 핵심 개념 |
|---|---|---|---|
| Application (SAP) | JESD220 | SCSI 명령 처리, UFS 명령(Query, NOP, Task Mgmt) | LU, Descriptor, Attribute, Flag |
| UTP (Transport) | JESD220 | UPIU 생성/파싱, 커맨드 큐잉 | UPIU Header, CDB, Data Segment, PRDT |
| UniPro L4 (Transport) | MIPI UniPro | CPort 멀티플렉싱, 세그먼트 전송 | CPort, T_PDU, Connection ID |
| UniPro L3 (Network) | MIPI UniPro | Device 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
MIPI M-PHY / UniPro 물리·전송 계층
| HS-Gear | Rate Series | 속도 (per Lane) | 유효 대역폭 (per Lane) | 2-Lane 유효 대역폭 | UFS 버전 |
|---|---|---|---|---|---|
| HS-G1 | A | 1.4584 Gbps | ~150 MB/s | ~300 MB/s | UFS 1.1+ |
| HS-G2 | A | 2.9168 Gbps | ~300 MB/s | ~600 MB/s | UFS 2.0+ |
| HS-G3 | A | 5.8336 Gbps | ~600 MB/s | ~1.2 GB/s | UFS 2.1+ |
| HS-G4 | B | 11.6672 Gbps | ~1.2 GB/s | ~2.4 GB/s | UFS 3.0+ |
| HS-G5 | B | 23.3344 Gbps | ~2.3 GB/s | ~4.6 GB/s | UFS 4.0+ |
| PWM-Gear | 속도 (per Lane) | 용도 |
|---|---|---|
| PWM-G1 | 9 Mbps | 링크 초기화 시 기본 모드 |
| PWM-G2 | 18 Mbps | 저전력 데이터 전송 |
| PWM-G3 | 36 Mbps | 저전력 데이터 전송 |
| PWM-G4 | 72 Mbps | 저전력 데이터 전송 |
| PWM-G5 | 144 Mbps | 저전력 유지 통신 |
| PWM-G6 | 288 Mbps | 저전력 유지 통신 |
| PWM-G7 | 576 Mbps | 저전력 유지 통신 |
UniPro는 M-PHY 위에 4개 서브 계층(L1.5, L2, L3, L4)을 쌓아 신뢰성 있는 데이터 전송을 보장합니다.
- L1.5 (PHY Adapter) — M-PHY 레인을 관리합니다. 다중 레인 병합(lane merging), 기어/모드 전환, 전력 모드(FAST/SLOW/FASTAUTO/SLOWAUTO) 설정을 담당합니다.
- L2 (Data Link) — AFC(Adaptive Flow Control)를 통한 흐름 제어, CRC 기반 오류 검출, NAC(Negative Acknowledgment) 기반 재전송, TC(Traffic Class) 0/1 우선순위 분리를 수행합니다.
- L3 (Network) — DeviceID 기반 라우팅을 제공합니다. UFS는 점대점(point-to-point) 연결이므로 라우팅이 단순하지만, UniPro 스위치를 통한 다중 디바이스 연결도 규격상 가능합니다.
- L4 (Transport) — CPort(Connection Port) 기반 멀티플렉싱을 제공합니다. 하나의 물리적 링크 위에 여러 논리적 연결(connection)을 생성할 수 있으며, UFS는 CPort 0을 데이터 전송에 사용합니다.
/* 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
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) 프로토콜
| 코드 | 이름 | 방향 | 설명 |
|---|---|---|---|
0x01 | NOP OUT | Host → Device | 링크 활성 확인용. 디바이스는 NOP IN으로 응답 |
0x20 | NOP IN | Device → Host | NOP OUT에 대한 응답 |
0x06 | COMMAND | Host → Device | SCSI 명령 전송 (CDB 16바이트 포함) |
0x22 | DATA IN | Device → Host | 읽기 데이터 전송 (디바이스가 호스트에 전달) |
0x02 | DATA OUT | Host → Device | 쓰기 데이터 전송 (호스트가 디바이스에 전달) |
0x21 | RESPONSE | Device → Host | COMMAND에 대한 SCSI 상태 응답 |
0x31 | READY TO TRANSFER | Device → Host | 쓰기 시 디바이스가 데이터 수신 준비 완료 통보 |
0x16 | QUERY REQUEST | Host → Device | Descriptor/Attribute/Flag 읽기/쓰기 요청 |
0x36 | QUERY RESPONSE | Device → Host | Query 요청에 대한 응답 |
0x04 | TASK MANAGEMENT REQ | Host → Device | Abort Task, Query Task, Logical Unit Reset 등 |
0x24 | TASK MANAGEMENT RESP | Device → Host | Task Management 요청에 대한 응답 |
0x3F | REJECT | 양방향 | 잘못된 UPIU 수신 시 거부 응답 |
| 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_UNIT | 0x1B | 전원 상태 전환 | PowerDown, Sleep, Active 전환 |
| UNMAP | 0x42 | TRIM (논리 블록 해제 통보) | discard 지원 |
| SECURITY PROTOCOL IN | 0xA2 | RPMB 읽기 | 보안 프로토콜 데이터 수신 |
| SECURITY PROTOCOL OUT | 0xB5 | RPMB 쓰기 | 보안 프로토콜 데이터 전송 |
| INQUIRY | 0x12 | 디바이스 정보 조회 | Vendor, Product, Revision |
| TEST UNIT READY | 0x00 | 디바이스 준비 상태 확인 | 정상 여부 확인 |
| REPORT LUNS | 0xA0 | 사용 가능한 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));
}
UFS 디바이스 아키텍처
| Well-Known LU | W-LUN 값 | 용도 | 접근 방법 |
|---|---|---|---|
| REPORT LUNS | 0x81 | 사용 가능한 LUN 목록 보고 | SCSI REPORT LUNS 명령 |
| UFS Device | 0xD0 | 디바이스 전체 관리 (Power, Reset) | START_STOP_UNIT 명령 |
| Boot LU 0 | 0xB0 | 부트 파티션 0 (부트로더 저장) | READ/WRITE 명령 |
| Boot LU 1 | 0xB1 | 부트 파티션 1 (부트로더 백업) | READ/WRITE 명령 |
| RPMB | 0xC4 | Replay Protected Memory Block | SECURITY PROTOCOL IN/OUT |
UFS 디바이스의 설정과 상태는 Descriptor, Attribute, Flag 세 가지 체계로 관리됩니다. 모두 Query UPIU를 통해 읽기/쓰기가 가능합니다.
| Descriptor IDN | 이름 | 크기 | 주요 필드 |
|---|---|---|---|
0x00 | Device Descriptor | ~64B | bNumberLU, bUFSFeaturesSupport, wManufacturerID, bBootEnable, bDeviceClass |
0x01 | Configuration Descriptor | ~144B | bBootEnable, bDescrAccessEn, bInitPowerMode, 각 LU 설정 배열 |
0x02 | Unit Descriptor | ~45B | bLUEnable, bBootLunID, bLUWriteProtect, qLogicalBlockCount, dEraseBlockSize |
0x04 | Interconnect Descriptor | ~6B | UniPro/M-PHY 버전 정보 |
0x05 | String Descriptor | 가변 | 제조사명, 제품명, 시리얼 번호 (UTF-16) |
0x07 | Geometry Descriptor | ~72B | 총 NAND 용량, 최대 LU 수, 세그먼트 크기, 얼로케이션 단위 |
0x08 | Power Parameters Descriptor | ~98B | 각 기어/모드별 전력 소비량 |
0x09 | Device Health Descriptor | ~45B | bPreEOLInfo, 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);
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) 레지스터 아키텍처
ufshcd 드라이버가 MMIO를 통해 이 레지스터들을 제어합니다.
핵심은 UTRL(UTP Transfer Request List), UTMRL(UTP Task Management Request List),
그리고 인터럽트/상태 레지스터입니다.
| 오프셋 | 이름 | 접근 | 설명 |
|---|---|---|---|
0x00 | CAP (Capabilities) | RO | 호스트 역량: NUTRS(슬롯 수), NUTMRS, 64-bit 지원, 오토 히버네이트, MCQ 지원 등 |
0x08 | VER (Version) | RO | UFSHCI 스펙 버전 (예: 0x0300 = v3.0) |
0x20 | IS (Interrupt Status) | RW1C | 인터럽트 상태: UTP Transfer/Task 완료, UIC 명령 완료, 오류 등 |
0x24 | IE (Interrupt Enable) | RW | 인터럽트 활성화 마스크 |
0x30 | HCS (Host Controller Status) | RO | 컨트롤러 상태: UCRDY, UTRLRDY, UTMRLRDY, DP(Device Present) |
0x34 | HCE (Host Controller Enable) | RW | 컨트롤러 활성화/비활성화 (비트 0: HCE) |
0x50 | UTRLBA | RW | UTRL 베이스 주소 (하위 32비트) |
0x54 | UTRLBAU | RW | UTRL 베이스 주소 (상위 32비트, 64-bit 주소용) |
0x58 | UTRLDBR | RW | Transfer Request Doorbell: 비트 N 세팅 → UTRD[N] 전송 시작 |
0x5C | UTRLCLR | RW | Transfer Request Clear: 비트 N 세팅 → UTRD[N] 취소 |
0x60 | UTRLRSR | RW | Transfer Request List Run-Stop Register |
0x70 | UTMRLBA | RW | UTMRL 베이스 주소 (하위 32비트) |
0x74 | UTMRLBAU | RW | UTMRL 베이스 주소 (상위 32비트) |
0x78 | UTMRLDBR | RW | Task Management Request Doorbell |
0x90 | UICCMD | RW | UIC Command 레지스터 (DME_GET/SET, DME_PEER_GET/SET 등) |
0x94 | UCMDARG1 | RW | UIC 명령 인자 1 (MIB Attribute ID) |
0x98 | UCMDARG2 | RW | UIC 명령 인자 2 (Attribute Set Type) |
0x9C | UCMDARG3 | RW | UIC 명령 인자 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: 비활성) */
};
struct ufshcd_sg_entry는 각 엔트리가 16바이트이며,
인라인 암호화(ICE) 활성 시 추가 필드가 포함됩니다.
hba->sg_entry_size로 실제 크기를 확인할 수 있습니다.
ufshcd_transfer_req_compl()은 먼저 UTRD의 OCS(Overall Command Status)를 확인합니다.
OCS가 OCS_SUCCESS이면 Response UPIU의 SCSI Status를 파싱하고,
그렇지 않으면 OCS 자체가 에러 원인입니다.
OCS_PEER_COMM_FAILURE는 M-PHY/UniPro 링크 에러를,
OCS_FATAL_ERROR는 UFSHCI 하드웨어 치명적 오류를 나타냅니다.
ufshcd 호스트 컨트롤러 드라이버
ufshcd(UFS Host Controller Driver)는 Linux 커널의 UFS 핵심 드라이버로,
drivers/ufs/core/ufshcd.c에 구현되어 있습니다.
SCSI 호스트 어댑터로 등록되며, 벤더별 플랫폼 드라이버(ufs-qcom, ufs-exynos 등)는
ufs_hba_variant_ops(vops) 콜백을 통해 하드웨어 특화 동작을 제공합니다.
ufshcd의 핵심은 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()을 호출하면 다음 순서로 초기화가 진행됩니다:
- ufshcd_alloc_host() —
Scsi_Host와ufs_hba할당, SCSI 호스트 템플릿 설정 - ufshcd_init() — UTRL/UTMRL/UCD DMA 메모리 할당, LRB 배열 초기화, 인터럽트 등록, 워크큐 생성
- 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_alloc_host() | ufshcd.c | Scsi_Host + ufs_hba 할당 |
ufshcd_init() | ufshcd.c | DMA 메모리, IRQ, 워크큐 초기화 |
ufshcd_async_scan() | ufshcd.c | 비동기 HCE→링크→NOP→스캔 시퀀스 |
ufshcd_hba_enable() | ufshcd.c | HCE 레지스터 활성화, 컨트롤러 리셋 |
ufshcd_link_startup() | ufshcd.c | UniPro 링크 초기화 (DME_LINKSTARTUP) |
ufshcd_config_pwr_mode() | ufshcd.c | HS-Gear/Rate/Lane 전환 |
ufshcd_queuecommand() | ufshcd.c | SCSI 명령 → UPIU → doorbell |
ufshcd_send_command() | ufshcd.c | UTRLDBR 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.c | UIC 명령 전송 (DME_SET/GET 등) |
ufshcd_query_attr() | ufshcd.c | Query UPIU로 Attribute 읽기/쓰기 |
ufshcd_query_flag() | ufshcd.c | Query UPIU로 Flag 설정/해제/읽기 |
ufshcd_read_desc_param() | ufshcd.c | Query UPIU로 Descriptor 읽기 |
ufshcd는 scsi_host_template을 통해 SCSI mid-layer에 자신을 등록합니다.
.queuecommand = ufshcd_queuecommand,
.eh_abort_handler = ufshcd_abort,
.eh_device_reset_handler = ufshcd_eh_device_reset_handler,
.eh_host_reset_handler = ufshcd_eh_host_reset_handler 등이 핵심 콜백입니다.
.cmd_per_lun은 기본 32이고, .can_queue는 hba->nutrs 값을 사용합니다.
ufshcd_err_handler()는 3단계 에러 복구를 수행합니다:
(1) Abort — 개별 태그의 UTRLCLR로 명령 취소,
(2) LU Reset — Task Management UPIU로 특정 LU 리셋,
(3) Host Reset — HCE 비활성화/재활성화로 전체 UFSHCI 리셋 후 링크 재초기화.
각 단계 실패 시 다음 단계로 에스컬레이션됩니다.
UIC 에러(M-PHY/UniPro 링크 오류)의 경우 즉시 Host Reset으로 진행할 수 있습니다.
/* 에러 핸들러 개요 */
static void ufshcd_err_handler(struct work_struct *work)
{
struct ufs_hba *hba = container_of(work, struct ufs_hba, eh_work);
/* 1단계: 개별 명령 Abort */
for_each_set_bit(tag, &hba->outstanding_reqs, hba->nutrs) {
err = ufshcd_try_to_abort_task(hba, tag);
if (err) goto reset_lu;
}
return;
reset_lu:
/* 2단계: LU 리셋 (Task Management UPIU) */
err = ufshcd_eh_device_reset_handler(/* ... */);
if (err) goto reset_host;
return;
reset_host:
/* 3단계: 호스트 전체 리셋 */
ufshcd_reset_and_restore(hba);
/* → HCE disable → HCE enable → link startup → restore */
}
# ufshcd 디바이스 트리 바인딩 예시 (Qualcomm SM8550)
ufs_mem_hc: ufshc@1d84000 {
compatible = "qcom,sm8550-ufshc", "qcom,ufshc", "jedec,ufs-2.0";
reg = <0x01d84000 0x3000>, /* UFSHCI MMIO */
<0x01d90000 0x8000>; /* UFS PHY */
reg-names = "std", "ice";
interrupts = <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&gcc GCC_UFS_PHY_AXI_CLK>,
<&gcc GCC_UFS_PHY_ICE_CORE_CLK>,
<&gcc GCC_UFS_PHY_UNIPRO_CORE_CLK>,
<&rpmhcc RPMH_CXO_CLK>;
clock-names = "core_clk", "ice_core_clk",
"unipro_core_clk", "ref_clk";
freq-table-hz = <75000000 300000000>, /* core_clk */
<75000000 300000000>, /* ice_core_clk */
<75000000 300000000>, /* unipro_core_clk */
<75000000 75000000>; /* ref_clk */
vcc-supply = <&vreg_l17b_2p5>;
vccq-supply = <&vreg_l6b_1p2>;
vcc-max-microamp = <800000>;
vccq-max-microamp = <900000>;
power-domains = <&gcc GCC_UFS_PHY_GDSC>;
resets = <&gcc GCC_UFS_PHY_BCR>;
reset-names = "rst";
phys = <&ufs_mem_phy>;
phy-names = "ufsphy";
lanes-per-direction = <2>; /* 2-Lane 구성 */
status = "okay";
};
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 스택의 동작 원리를 소스 레벨에서 이해할 수 있습니다.
drivers/ufs/(이전에는 drivers/scsi/ufs/)에,
헤더 정의는 include/ufs/에 위치합니다. 커널 6.0부터 SCSI 디렉터리에서 독립적인 서브시스템으로
분리되었으며, drivers/ufs/core/와 drivers/ufs/host/로 세분화됩니다.
소스 파일 맵
| 경로 | 파일명 | 역할 | 대략적 규모 |
|---|---|---|---|
drivers/ufs/core/ | ufshcd.c | UFS Host Controller Driver 코어 — 초기화, I/O 처리, 에러 핸들링, 전원 관리 | ~10,000줄 |
drivers/ufs/core/ | ufshcd-priv.h | 코어 내부 private 헤더 | ~200줄 |
drivers/ufs/core/ | ufs-sysfs.c | sysfs 인터페이스 — 디바이스 속성 노출, 런타임 설정 | ~1,000줄 |
drivers/ufs/core/ | ufs-mcq.c | Multi-Circular Queue(MCQ) 지원 — UFS 4.0+ | ~800줄 |
drivers/ufs/core/ | ufshcd-crypto.c | 인라인 암호화 — blk-crypto 프레임워크 연동 | ~300줄 |
drivers/ufs/core/ | ufs-bsg.c | BSG(Block SCSI Generic) — 사용자 공간 UFS 커맨드 통로 | ~400줄 |
drivers/ufs/core/ | ufs-fault-inject.c | Fault injection 프레임워크 연동 | ~100줄 |
drivers/ufs/core/ | ufs-debugfs.c | debugfs 인터페이스 — 런타임 디버깅 | ~200줄 |
include/ufs/ | ufshci.h | UFSHCI 레지스터 정의, UTRD/UTMRD 구조체 | ~600줄 |
include/ufs/ | ufshcd.h | ufs_hba, ufshcd_lrb 등 핵심 구조체 | ~1,200줄 |
include/ufs/ | ufs.h | UFS 프로토콜 상수, Descriptor/Flag/Attribute 정의 | ~700줄 |
include/ufs/ | ufs_quirks.h | 벤더별 quirk 플래그 정의 | ~100줄 |
include/ufs/ | unipro.h | UniPro 레이어 상수 (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);
}
tracepoint를 활용하세요.
trace_ufshcd_command, trace_ufshcd_uic_command,
trace_ufshcd_upiu 등의 tracepoint가 정의되어 있으며,
echo 1 > /sys/kernel/debug/tracing/events/ufs/enable로 활성화할 수 있습니다.
주요 구조체 관계
| 구조체 | 정의 위치 | 역할 | 주요 필드 |
|---|---|---|---|
struct ufs_hba | include/ufs/ufshcd.h | HBA 인스턴스 — 드라이버 전체 상태 관리 | host, mmio_base, utrdl_base_addr, lrb, vops |
struct ufshcd_lrb | include/ufs/ufshcd.h | Local Reference Block — 개별 I/O 요청 추적 | utr_descriptor_ptr, ucd_req_ptr, cmd, task_tag |
struct utp_transfer_req_desc | include/ufs/ufshci.h | UTP Transfer Request Descriptor (UTRD) | header, command_desc_base_addr, prd_table_* |
struct ufs_dev_info | include/ufs/ufshcd.h | UFS 디바이스 정보 캐시 | wmanufacturerid, model, wspecversion |
struct ufs_hba_variant_ops | include/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 초기화, 클럭 설정, 암호화 엔진 등을 처리합니다.
이 분리 구조 덕분에 새 플랫폼 지원을 추가할 때 코어 드라이버 수정 없이 글루 드라이버만 작성하면 됩니다.
struct ufs_hba_variant_ops입니다.
이 구조체의 각 함수 포인터가 초기화, 링크 수립, 전원 관리, 암호화 등의
시점별 콜백을 정의합니다. 코어 드라이버는 적절한 시점에 ufshcd_vops_xxx()
래퍼 매크로를 통해 이 콜백들을 호출합니다.
플랫폼 드라이버 비교
| 드라이버 | 파일 | compatible | 주요 특징 | PHY 유형 |
|---|---|---|---|---|
| Qualcomm | ufs-qcom.c | qcom,ufshc | ICE(Inline Crypto Engine), QMP PHY, testbus 디버깅 | QMP UFS PHY |
| Samsung Exynos | ufs-exynos.c | samsung,exynos7-ufs | FMP(Flash Memory Protector), TXHSLV/RXHSLV 캘리브레이션 | Exynos UFS PHY |
| MediaTek | ufs-mediatek.c | mediatek,mt8183-ufshci | VA09 레귤레이터, 전용 crypto IP, MPHY 캘리브레이션 | MediaTek MPHY |
| HiSilicon | ufs-hisi.c | hisilicon,hi3670-ufs | Kirin SoC용, PHY 초기화 시퀀스 | HiSilicon MPHY |
| Renesas | ufs-renesas.c | renesas,r8a779f0-ufs | R-Car Gen4용, 자체 PHY setup | Renesas MPHY |
| Intel PCI | ufshcd-pci.c | PCI 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);
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 성능 최적화와 디버깅의 핵심입니다.
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 디바이스 생성 흐름
| 단계 | 함수/동작 | 설명 |
|---|---|---|
| 1 | scsi_scan_host() | SCSI 호스트에 연결된 LU 스캔 시작 |
| 2 | REPORT LUNS 커맨드 | UFS 디바이스에 존재하는 LU 목록 조회 |
| 3 | INQUIRY 커맨드 | 각 LU의 디바이스 유형, 벤더 정보 확인 |
| 4 | scsi_add_device() | struct scsi_device 생성 및 등록 |
| 5 | sd_probe() | SCSI 디스크 드라이버가 LU 인식, gendisk 생성 |
| 6 | device_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 | 이름 | 용도 | 접근 방식 |
|---|---|---|---|
0xD0 | REPORT LUNS W-LU | LU 목록 보고 | SCSI REPORT LUNS |
0x50 | UFS Device W-LU | 디바이스 관리 (Descriptor/Flag/Attribute R/W) | /dev/bsg/ufs-bsg0 |
0xC4 | RPMB W-LU | Replay Protected Memory Block 접근 | /dev/sgX + SECURITY PROTOCOL |
0xB0 | Boot W-LU | 부트 이미지 저장 (Boot LU A) | /dev/sdX |
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 레지스터 셋
| 레지스터 | 오프셋 | 설명 |
|---|---|---|
MCQCAP | 0x00 (MCQ Base) | MCQ Capability — 지원 SQ/CQ 최대 수 |
SQATTR | SQ Base + 0x00 | Submission Queue 속성 (크기, 타입) |
SQLBA | SQ Base + 0x04 | SQ Base Address (Lower 32-bit) |
SQUBA | SQ Base + 0x08 | SQ Base Address (Upper 32-bit) |
SQDAO | SQ Base + 0x0C | SQ Doorbell Address Offset |
SQISAO | SQ Base + 0x10 | SQ Interrupt Status Address Offset |
CQATTR | CQ Base + 0x00 | Completion Queue 속성 (크기, 인터럽트 벡터) |
CQLBA | CQ Base + 0x04 | CQ Base Address (Lower 32-bit) |
CQUBA | CQ Base + 0x08 | CQ Base Address (Upper 32-bit) |
CQDAO | CQ Base + 0x0C | CQ 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);
}
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를 활성화/비활성화하고, 버퍼 플러시를 제어할 수 있습니다.
Dedicated Buffer vs Shared Buffer
| 모드 | 설명 | 장점 | 단점 |
|---|---|---|---|
| Dedicated Buffer | WB 전용으로 예약된 SLC 영역 | 안정적인 WB 용량 보장, 사용자 데이터 영역과 독립 | 전체 용량 감소 (WB 영역만큼) |
| Shared Buffer | 사용자 데이터 영역의 일부를 동적으로 SLC 모드로 전환 | 전용 영역 불필요, 유연한 용량 활용 | 남은 용량에 따라 WB 가용 크기 변동 |
Write Booster sysfs 인터페이스
| sysfs 속성 | 경로 | R/W | 설명 |
|---|---|---|---|
wb_on | /sys/bus/platform/drivers/ufshcd/*/wb_on | R/W | Write Booster 활성화/비활성화 (1/0) |
wb_buf_flush_en | 동일 경로 | R/W | WB 버퍼 플러시 활성화 (1: 즉시 플러시) |
wb_avail_buf | 동일 경로 | R | 현재 사용 가능한 WB 버퍼 크기 (%) |
wb_cur_buf | 동일 경로 | R | 현재 사용 중인 WB 버퍼 크기 |
wb_buf_life_time_est | 동일 경로 | R | WB 버퍼 수명 추정 (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_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 접근을 건너뛸 수 있게 합니다.
Region과 Subregion 개념
HPB는 UFS 디바이스의 논리적 주소 공간을 Region과 Subregion으로 나누어 관리합니다. Region은 L2P 캐싱의 기본 관리 단위이고, Subregion은 실제 L2P 엔트리를 전송하는 최소 단위입니다. 호스트는 디바이스로부터 HPB 업데이트 알림을 받아 L2P 캐시를 갱신합니다.
| 개념 | 크기 | 설명 |
|---|---|---|
| Region | 일반적으로 512MB | L2P 캐시 관리의 단위, 활성화/비활성화 대상 |
| Subregion | 일반적으로 4MB | L2P 엔트리 전송(READ BUFFER) 단위, Region 내 128개 |
| HPB Entry | 8바이트 | PPN(Physical Page Number) 4B + Reserved 4B |
| Active Region | 디바이스별 상이 | 동시에 호스트 캐시에 유지할 수 있는 Region 수 |
| Pinned Region | 디바이스별 상이 | 항상 활성 상태를 유지하는 Region (빈번 접근 영역) |
HPB sysfs 인터페이스
| sysfs 속성 | R/W | 설명 |
|---|---|---|
hpb_mode | R/W | HPB 동작 모드 (0: OFF, 1: host-initiated, 2: device-initiated) |
hpb_srgn_mem_size | R | Subregion 하나의 L2P 캐시 메모리 크기 |
hpb_hit_count | R | HPB 히트 횟수 (성능 모니터링) |
hpb_miss_count | R | HPB 미스 횟수 |
hpb_active_count | R | 현재 활성화된 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 */
};
인라인 암호화 — Inline Encryption Framework
UFS 인라인 암호화(Inline Encryption)는 호스트 컨트롤러에 내장된 암호화 엔진(Crypto Engine 또는 ICE)을 활용하여
스토리지 I/O 경로에서 직접 데이터를 암복호화하는 기능입니다. CPU가 소프트웨어적으로 암복호화를 수행하는 대신,
하드웨어가 DMA 전송 과정에서 투명하게 처리하므로 성능 오버헤드가 거의 없습니다.
Linux 커널의 blk-crypto 프레임워크와 통합되어 fscrypt, dm-crypt 등에서 활용됩니다.
지원 알고리즘
| 알고리즘 | 키 크기 | 블록 크기 | UFS 스펙 지원 | 비고 |
|---|---|---|---|---|
| AES-256-XTS | 512비트 (2×256) | 4096바이트 | 필수 | Android FBE(File-Based Encryption) 기본 |
| AES-128-XTS | 256비트 (2×128) | 4096바이트 | 선택 | 일부 플랫폼에서 지원 |
| AES-256-ECB | 256비트 | - | 선택 | 파일명 암호화용 |
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);
}
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 프레임 구조
| 오프셋 | 크기 | 필드 | 설명 |
|---|---|---|---|
| 0x0000 | 196바이트 | Stuff bytes | 패딩 (0x00) |
| 0x00C4 | 32바이트 | Key / MAC | HMAC-SHA256 인증값 또는 키 등록 시 키 |
| 0x00E4 | 256바이트 | Data | 읽기/쓰기 데이터 (256B 단위) |
| 0x01E4 | 16바이트 | Nonce | 호스트가 생성한 난수 (읽기 요청 시) |
| 0x01F4 | 4바이트 | Write Counter | 단조 증가 카운터 (Big-Endian) |
| 0x01F8 | 2바이트 | Address | RPMB 블록 주소 (256B 블록 단위) |
| 0x01FA | 2바이트 | Block Count | 전송할 블록 수 |
| 0x01FC | 2바이트 | Result | 응답 결과 코드 (0x0000=성공) |
| 0x01FE | 2바이트 | Request / Response Type | 요청/응답 타입 식별자 |
RPMB 요청/응답 타입
| 타입 값 | 이름 | 설명 |
|---|---|---|
0x0001 | Authentication Key Write | RPMB 인증 키 프로그래밍 (최초 1회만) |
0x0002 | Read Write Counter | 현재 Write Counter 값 조회 |
0x0003 | Authenticated Data Write | 인증된 데이터 쓰기 |
0x0004 | Authenticated Data Read | 인증된 데이터 읽기 |
0x0005 | Result Read | 이전 쓰기 작업의 결과 확인 |
0x0100 | Authentication Key Write Response | 키 쓰기 응답 |
0x0200 | Read Write Counter Response | 카운터 읽기 응답 |
0x0300 | Authenticated Data Write Response | 데이터 쓰기 응답 |
0x0400 | Authenticated 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+) |
|---|---|---|
| 최대 크기 | 16MB | 64MB |
| 블록 크기 | 256바이트 | 4KB (선택적) |
| Multi-block Write | 미지원 (1블록씩) | 지원 (최대 64블록) |
| 인증 알고리즘 | HMAC-SHA256 | HMAC-SHA256 |
| Write Counter | 32비트 | 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
전원 관리 (Power Management)
UFS 전원 관리는 모바일 디바이스의 배터리 수명에 직접적인 영향을 미치는 핵심 요소입니다.
UFS 호스트 컨트롤러(ufshcd)는 Linux 커널의 Runtime PM 프레임워크와 통합되어,
유휴 시 자동으로 링크 상태를 전환하고 클럭을 게이팅하여 전력 소비를 최소화합니다.
전원 모드 비교
| 전원 모드 | 링크 상태 | 클럭 | VCC | VCCQ/VCCQ2 | 복귀 지연 | 전력 소비 |
|---|---|---|---|---|---|---|
| Active | Up (LS-HS) | ON | ON | ON | — | 최대 |
| Clock Gated | Up | OFF (gated) | ON | ON | ~10 µs | 중간 |
| Hibernate (H8) | Hibern8 | OFF | ON | ON | ~100 µs | 낮음 |
| Sleep (DeepSleep) | Off / H8 | OFF | ON | OFF | ~1 ms | 매우 낮음 |
| PowerDown | Off | OFF | OFF | OFF | ~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_lvl과 spm_lvl sysfs 속성으로 Runtime Suspend와 System Suspend 시의
전원 레벨을 독립적으로 제어할 수 있습니다.
/* drivers/ufs/core/ufshcd.c — System Suspend/Resume */
int ufshcd_system_suspend(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
int ret;
/* 이미 Runtime Suspended 상태면 추가 작업 불필요 */
if (pm_runtime_suspended(hba->dev))
return 0;
ret = ufshcd_suspend(hba, UFS_SYSTEM_PM);
if (ret)
dev_err(hba->dev, "system suspend failed: %d\n", ret);
return ret;
}
int ufshcd_system_resume(struct device *dev)
{
struct ufs_hba *hba = dev_get_drvdata(dev);
if (pm_runtime_suspended(hba->dev))
return 0;
return ufshcd_resume(hba, UFS_SYSTEM_PM);
}
/* 내부 suspend 함수 — rpm_lvl/spm_lvl에 따라 동작 결정 */
static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
enum ufs_pm_level pm_lvl;
enum ufs_dev_pwr_mode req_dev_pwr_mode;
enum uic_link_state req_link_state;
pm_lvl = (pm_op == UFS_RUNTIME_PM) ?
hba->rpm_lvl : hba->spm_lvl;
req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
/* BKOPS flush → SSU(PowerMode) → Hibern8/Off → Clock gate */
if (req_dev_pwr_mode != hba->curr_dev_pwr_mode)
ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
if (req_link_state == UIC_LINK_HIBERN8_STATE)
ufshcd_uic_hibern8_enter(hba);
ufshcd_setup_clocks(hba, false);
return 0;
}
Gear Scaling (동적 기어 변경)
Gear scaling은 부하에 따라 UFS 링크 속도(Gear)를 동적으로 조절하는 기능입니다.
높은 I/O 부하 시 HS-G4로 올리고, 유휴 시 HS-G1으로 내려 전력을 절감합니다.
devfreq 프레임워크와 통합되어 자동으로 동작합니다.
# Gear scaling 상태 확인
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)
rpm_lvl=5 (dev:PowerDown, link:Off)는 전력 절감이 크지만 복귀 시간이 길어
모바일에서는 rpm_lvl=3 (dev:Sleep, link:Hibern8)을 주로 사용합니다.
디바이스 건강 모니터링 (Device Health Monitoring)
UFS 디바이스는 내장 건강 지표를 제공하여 수명 예측과 사전 장애 대응을 가능하게 합니다.
bDeviceLifeTimeEstA/B와 bPreEOLInfo 속성은 디바이스 디스크립터를 통해
조회하며, 임계치를 초과하면 Exception Event를 통해 호스트에 알립니다.
수명 추정치 (Device Life Time Estimation)
UFS 디바이스는 두 가지 수명 추정 속성을 제공합니다.
bDeviceLifeTimeEstA는 SLC 영역(Boot/RPMB 포함)의 수명을,
bDeviceLifeTimeEstB는 MLC/TLC 사용자 데이터 영역의 수명을 나타냅니다.
| 값 | 수명 사용률 | 의미 | 권장 대응 |
|---|---|---|---|
0x00 | 정보 없음 | 디바이스가 수명 정보를 지원하지 않음 | — |
0x01 | 0~10% | 정상 — 수명 충분 | 모니터링 유지 |
0x02 | 10~20% | 정상 | 모니터링 유지 |
0x03 | 20~30% | 정상 | 모니터링 유지 |
0x04 | 30~40% | 정상 | 모니터링 유지 |
0x05 | 40~50% | 중간 수명 | 교체 계획 수립 |
0x06 | 50~60% | 중간 수명 | 교체 계획 수립 |
0x07 | 60~70% | 주의 | 교체 일정 확정 |
0x08 | 70~80% | 주의 | 교체 준비 |
0x09 | 80~90% | 경고 | 즉시 교체 준비 |
0x0A | 90~100% | 위험 | 즉시 교체 필요 |
0x0B | 100% 초과 | 수명 초과 | 즉시 데이터 백업 및 교체 |
Pre-EOL 정보 (bPreEOLInfo)
bPreEOLInfo는 디바이스의 예비 블록 상태를 세 단계로 간략화한 지표입니다.
이 값이 0x02(Warning) 이상이면 BKOPS 빈도 증가, 쓰기 앰플리피케이션 악화,
갑작스러운 성능 저하가 발생할 수 있습니다.
| 값 | 상태 | 설명 | 대응 |
|---|---|---|---|
0x00 | 미정의 | 정보 제공하지 않음 | — |
0x01 | Normal | 예비 블록 충분 | 정상 운용 |
0x02 | Warning | 예비 블록 소진 경고 (80% 이상 사용) | 교체 일정 수립, BKOPS 모니터링 강화 |
0x03 | Urgent | 예비 블록 거의 소진 (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
bPreEOLInfo가 0x02(Warning) 또는 0x03(Urgent)인 상태에서
계속 사용하면, 예비 블록 완전 소진 시 갑작스러운 읽기 전용 전환 또는
디바이스 응답 불가가 발생할 수 있습니다.
특히 임베디드/자동차 시스템에서는 정기적 건강 모니터링과 사전 교체 정책이 필수입니다.
프로덕션 환경에서는 bDeviceLifeTimeEstA/B ≥ 0x08 시점에 교체 알람을 설정하는 것을 권장합니다.
디바이스 트리 바인딩 (Device Tree Bindings)
UFS 호스트 컨트롤러의 디바이스 트리 바인딩은 하드웨어 자원(레지스터, 인터럽트, 클럭, 레귤레이터, PHY)과 플랫폼별 설정을 선언합니다. 정확한 DT 설정은 UFS 초기화 성공과 최적 성능의 전제 조건입니다.
표준 DT 속성
| 속성 | 타입 | 필수 | 설명 | 예시 값 |
|---|---|---|---|---|
compatible | string | O | 플랫폼별 호환 문자열 | "qcom,sm8550-ufshc" |
reg | u32 array | O | MMIO 레지스터 베이스 및 크기 | <0x1d84000 0x3000> |
interrupts | u32 array | O | 인터럽트 라인 | <GIC_SPI 265 IRQ_TYPE_LEVEL_HIGH> |
clocks | phandle array | O | 클럭 소스 참조 | <&gcc GCC_UFS_PHY_AXI_CLK>, ... |
clock-names | string array | O | 클럭 이름 | "core_clk", "bus_aggr_clk", ... |
freq-table-hz | u32 array | O | 클럭 주파수 범위 (min max 쌍) | <75000000 300000000> |
vcc-supply | phandle | O | VCC 레귤레이터 (2.7~3.6V) | <&vreg_l7b_2p96> |
vccq-supply | phandle | 선택 | VCCQ 레귤레이터 (1.14~1.26V) | <&vreg_l9b_1p2> |
vccq2-supply | phandle | 선택 | VCCQ2 레귤레이터 (1.7~1.95V) | <&vreg_s4a_1p8> |
vcc-max-microamp | u32 | 선택 | VCC 최대 전류 | <800000> |
phys | phandle | O | M-PHY 참조 | <&ufs_mem_phy> |
phy-names | string | O | PHY 이름 | "ufsphy" |
resets | phandle | 선택 | 리셋 컨트롤러 참조 | <&gcc GCC_UFS_PHY_BCR> |
reset-names | string | 선택 | 리셋 이름 | "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";
};
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 속성
| 경로 (상대) | 속성 | R/W | 설명 | 기본값 예시 |
|---|---|---|---|---|
rpm_lvl | Runtime PM 전원 레벨 | |||
| — | RW | Runtime Suspend 시 전원 레벨 (0~5) | 5 | |
spm_lvl | System PM 전원 레벨 | |||
| — | RW | System Suspend 시 전원 레벨 (0~5) | 5 | |
auto_hibern8 | — | RW | Auto-Hibernate8 타이머 (ms), 0=비활성 | 10 |
wb_on | — | RW | Write Booster 활성화 (0/1) | 1 |
wb_buf_flush_en | — | RW | WB 버퍼 플러시 활성화 | 0 |
wb_flush_threshold | — | RW | WB 플러시 시작 임계값 (%) | 40 |
clkscale_enable | — | RW | Gear scaling 활성화 (0/1) | 1 |
clkgate_enable | — | RW | Clock gating 활성화 (0/1) | 1 |
clkgate_delay_ms | — | RW | Clock gating 지연 (ms) | 150 |
rtc_update_ms | — | RW | RTC 동기화 주기 (ms) | 10000 |
urgent_bkops | — | RO | Urgent BKOPS 발생 횟수 | 0 |
전원 레벨 상세
| 레벨 | 디바이스 전원 | 링크 상태 | 적합 시나리오 |
|---|---|---|---|
0 | Active | Active | 테스트/벤치마크 (절전 비활성) |
1 | Active | Hibern8 | 낮은 지연, 링크만 절전 |
2 | Sleep | Active | 디바이스만 절전 (드문 설정) |
3 | Sleep | Hibern8 | 모바일 기본값 (균형) |
4 | PowerDown | Hibern8 | 높은 절전, 중간 복귀 시간 |
5 | PowerDown | Off | 최대 절전, 긴 복귀 시간 |
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"
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 에러를 분류하고
단계적 복구를 수행합니다.
OCS 에러 코드
| OCS 값 | 이름 | 원인 | 대응 |
|---|---|---|---|
0x00 | SUCCESS | 정상 완료 | — |
0x01 | INVALID_CMD_TABLE_ATTR | UTRD 설정 오류 | 드라이버 버그 확인 |
0x02 | INVALID_PRDT_ATTR | PRDT 엔트리 오류 | DMA 매핑 확인 |
0x03 | MISMATCH_DATA_BUF_SIZE | 데이터 크기 불일치 | Transfer 크기 확인 |
0x04 | MISMATCH_RESP_UPIU_SIZE | Response UPIU 크기 불일치 | Response 버퍼 확인 |
0x05 | PEER_COMM_FAILURE | UniPro 통신 실패 | 링크 복구 시도 |
0x06 | ABORTED | 호스트에 의한 중단 | 타임아웃 or 에러 복구 중 |
0x07 | FATAL_ERROR | 치명적 하드웨어 에러 | 호스트 리셋 필요 |
0x08 | DEVICE_FATAL_ERROR | 디바이스 치명적 에러 | 디바이스 리셋 + 호스트 리셋 |
0x0F | INVALID_OCS_VALUE | UFS HCI SW 에러 | 드라이버 상태 확인 |
UIC 에러 유형
| 에러 유형 | 레지스터 | 원인 | 증상 |
|---|---|---|---|
| PA (Physical Adapter) | UIC_ERROR_CODE_PA | M-PHY 시그널 불안정, 전압/클럭 문제 | 링크 드롭, H8 진입/탈출 실패 |
| DL (Data Link) | UIC_ERROR_CODE_DL | CRC 에러, NAC 과다, TC 크레딧 소진 | 프레임 재전송 증가, 성능 저하 |
| NL (Network Layer) | UIC_ERROR_CODE_NL | UniPro 라우팅 에러 | 통신 장애 (드문 경우) |
| TL (Transport Layer) | UIC_ERROR_CODE_TL | E2E FC 크레딧 에러, UPIU 형식 에러 | 커맨드 타임아웃 |
| DME | UIC_ERROR_CODE_DME | UIC 커맨드 실행 실패 | 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
/sys/kernel/debug/ufshcd0/err_stats에서 누적 에러 카운트를 정기적으로 확인하면
잠재적 하드웨어 문제를 조기에 발견할 수 있습니다.
성능 튜닝 (Performance Tuning)
UFS 성능 최적화는 하드웨어 계층(Gear/Lane), 컨트롤러 계층(MCQ), 디바이스 기능(WB/HPB), 블록 레이어, 파일시스템에 이르기까지 다층적으로 접근해야 합니다. 각 계층의 튜닝 포인트를 이해하면 단일 스토리지에서도 2~5배의 성능 차이를 만들 수 있습니다.
튜닝 파라미터 정리
| 계층 | 파라미터 | 기본값 | 권장값 (성능 우선) | 효과 |
|---|---|---|---|---|
| PHY | HS Gear | Auto (G1~G4) | G4 (고정) | 최대 대역폭 |
| PHY | Lanes | 2 | 2 | 대역폭 2배 |
| PHY | Rate (A/B) | B | B | Rate-B가 더 높은 대역폭 |
| Controller | MCQ 큐 수 | nr_cpus | nr_cpus (또는 8) | 병렬 I/O 처리 |
| Controller | auto_hibern8 | 10 ms | 0 (벤치마크) | H8 전이 오버헤드 제거 |
| WB | wb_on | 1 | 1 | burst write 가속 |
| WB | wb_flush_threshold | 40% | 60% (대용량 쓰기) | flush 빈도 감소 |
| HPB | HPB 활성화 | ON | ON | random read 가속 |
| Block | scheduler | mq-deadline | none (NVMe형) | 스케줄러 오버헤드 제거 |
| Block | nr_requests | 256 | 256~512 | 큐 깊이 증가 |
| Block | read_ahead_kb | 128 | 256~512 | 순차 읽기 프리페치 |
| Block | rq_affinity | 1 | 2 | 완료 CPU 고정 |
| FS | F2FS discard | on | on | TRIM/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
튜닝 전후 비교
| 워크로드 | 튜닝 전 (기본) | 튜닝 후 | 개선율 | 핵심 설정 |
|---|---|---|---|---|
| 순차 읽기 (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% | 전체 최적화 적용 |
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_kb와 nr_requests 최적화는 상시 적용할 수 있습니다.
관련 문서
UFS 서브시스템을 더 깊이 이해하거나 관련 기술을 학습할 때 참고할 문서를 정리합니다.
내부 문서 (사이트 내)
| 페이지 | 링크 | 관련성 |
|---|---|---|
| eMMC/MMC 서브시스템 | eMMC/MMC | UFS의 전세대 기술, MMC 프로토콜 비교 학습에 필수 |
| SCSI 서브시스템 | SCSI | UFS는 SCSI 명령 집합을 사용하며, SCSI 미들레이어를 통해 동작 |
| NVMe | NVMe | PCIe 기반 플래시 스토리지, MCQ 아키텍처 비교 |
| SATA/AHCI | SATA/AHCI | 또 다른 SCSI 기반 스토리지 서브시스템 비교 |
| 블록 I/O 레이어 | 블록 I/O | UFS 위의 블록 레이어, blk-mq, I/O 스케줄러 |
| 디바이스 트리 | 디바이스 트리 | UFS DT 바인딩, PHY/클럭/레귤레이터 설정 |
| DMA | DMA | PRDT(Physical Region Descriptor Table), scatter-gather, DMA 매핑 |
| Android 커널 | Android | Android 환경에서의 UFS 최적화, 보안 부팅, ICE |
| F2FS 파일시스템 | F2FS | UFS에 최적화된 플래시 전용 파일시스템, TRIM/discard 연계 |
외부 참고 자료
- JEDEC JESD220F: UFS 4.0 표준 사양서 — SCSI 커맨드 매핑, 디스크립터, 전원 관리, Write Booster, HPB 전체 정의
- JEDEC JESD223D: UFS Host Controller Interface (UFSHCI) 4.0 사양서 — UTRD, UTMRD, MCQ, 레지스터 맵 정의
- MIPI M-PHY v5.0: UFS 물리 계층 사양 — HS/PWM 모드, Gear/Rate/Lane, 시그널링, 전기적 특성
- MIPI UniPro v2.0: UFS 전송 계층 사양 — Data Link, Network, Transport 레이어, E2E 플로우 컨트롤
- 커널 Documentation/scsi/ufs.rst: UFS 드라이버 개발자 문서, sysfs 속성 설명
- 커널 Documentation/ABI/testing/sysfs-driver-ufs: sysfs ABI 정의 문서
커널 소스 읽기 순서
UFS 서브시스템을 소스 코드 레벨에서 이해하려면 다음 순서로 읽는 것을 권장합니다.
include/ufs/ufs.h— UFS 프로토콜 상수, UPIU 구조체, 디스크립터 IDN, 전원 모드 정의include/ufs/ufshci.h— UFSHCI 레지스터 오프셋, UTRD/UTMRD 구조체, OCS 코드 정의drivers/ufs/core/ufshcd.c— UFS 호스트 컨트롤러 드라이버 코어 (초기화, I/O 경로, 에러 핸들링, PM)drivers/ufs/core/ufs-mcq.c— Multi-Circular Queue 구현 (SQ/CQ 관리, 큐 매핑)drivers/ufs/core/ufs-sysfs.c— sysfs 속성 정의 및 읽기/쓰기 핸들러drivers/ufs/core/ufshcd-crypto.c— 인라인 암호화(ICE) 키 프로그래밍 및 crypto 프로파일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
drivers/ufs/core/ufshcd.c의 ufshcd_queuecommand() 함수부터 읽으세요.
SCSI 커맨드가 UFS UPIU로 변환되어 하드웨어에 전달되는 전체 I/O 경로를
하나의 함수 호출 체인에서 따라갈 수 있습니다.
이후 ufshcd_err_handler()로 에러 복구 흐름을,
ufshcd_suspend()/ufshcd_resume()으로 전원 관리 흐름을 학습하면
UFS 드라이버의 전체 구조를 파악할 수 있습니다.