SATA / AHCI — Serial ATA & Advanced Host Controller Interface

SATA(Serial ATA)는 병렬 ATA(PATA)를 대체한 직렬 스토리지 인터페이스이며, AHCI(Advanced Host Controller Interface)는 SATA HBA(Host Bus Adapter)의 표준화된 레지스터 인터페이스입니다. 이 문서에서는 SATA 프로토콜의 변천(SATA I/II/III), AHCI 레지스터 아키텍처와 커맨드 처리 메커니즘, FIS(Frame Information Structure) 구조, 리눅스 커널 libata 서브시스템의 핵심 구조체와 드라이버 콜백, AHCI 드라이버(ahci.c) 구현 상세, NCQ(Native Command Queuing) 심화, M.2 폼 팩터와 SATA vs NVMe 비교, SATA 전원 관리(ALPM/DIPM/DevSlp), 핫 플러그, Port Multiplier, 에러 처리와 복구, ATA 보안 기능, sysfs 인터페이스, S.M.A.R.T. 모니터링, 커널 빌드 설정, 디버깅, 성능 튜닝, OOB 링크 초기화까지 SATA/AHCI의 모든 것을 심층 분석합니다.

전제 조건: 블록 I/OPCI 서브시스템 문서를 먼저 읽으세요. AHCI 드라이버는 PCI 디바이스로 등록되며, 블록 I/O 요청이 SCSI 미드 레이어를 거쳐 libata로 내려갑니다. SCSI 서브시스템의 이해가 필요하면 SCSI 서브시스템도 함께 보시면 좋습니다.
일상 비유: SATA는 전용 고속 택배 레인과 비슷합니다. 예전 PATA는 여러 택배(마스터/슬레이브)가 하나의 도로(40/80핀 리본 케이블)를 공유했지만, SATA는 각 디바이스가 전용 도로(점대점 직렬 링크)를 가집니다. AHCI는 택배 터미널의 표준화된 접수 창구로, 어떤 제조사의 터미널이든 같은 방식으로 사용할 수 있습니다.

핵심 요약

SATA(Serial ATA)는 하드 디스크, SSD, 광학 드라이브 등을 연결하는 직렬 스토리지 인터페이스입니다. 2003년 SATA I(1.5 Gbps)부터 시작해, 현재 SATA III(6 Gbps)까지 발전했습니다. 점대점(Point-to-Point) 토폴로지로 각 디바이스에 전용 대역폭을 제공하며, 7핀 데이터 케이블과 15핀 전원 케이블을 사용합니다.
AHCI(Advanced Host Controller Interface)는 Intel이 설계한 SATA HBA의 표준 레지스터 인터페이스입니다. PCI BAR5(ABAR)를 통해 메모리 매핑된 레지스터에 접근하며, NCQ(Native Command Queuing), 핫 플러그, 전원 관리 등 SATA의 고급 기능을 지원합니다. AHCI 이전에는 각 벤더마다 독자적인 레지스터 레이아웃을 사용해 드라이버가 달랐습니다.
핵심 포인트: 리눅스 커널에서 SATA/AHCI는 libata 서브시스템이 담당합니다. libata는 ATA 명령어를 SCSI 명령어로 변환(SAT: SCSI-ATA Translation)하여 SCSI 미드 레이어와 블록 레이어에 통합됩니다. 따라서 SATA 디스크도 /dev/sda, /dev/sdb 등 SCSI 디바이스 이름을 사용합니다.
SATA 세대별 비교
사양SATA I (Gen1)SATA II (Gen2)SATA III (Gen3)
출시 연도200320042009
인터페이스 속도1.5 Gbps3.0 Gbps6.0 Gbps
유효 대역폭150 MB/s300 MB/s600 MB/s
인코딩8b/10b8b/10b8b/10b
NCQ 지원선택적필수필수
핫 플러그선택적필수필수
전원 관리Partial/Slumber+ALPM+DIPM, DevSlp
하위 호환-SATA ISATA I/II
전체 데이터 흐름 한눈 요약: 애플리케이션의 read()/write() 호출 → VFS → 블록 레이어(blk-mq) → SCSI 미드 레이어 → libata-scsi(SAT 변환) → libata-core → AHCI LLD(ahci.c) → PCI BAR5 레지스터(PxCI 쓰기) → AHCI HBA 하드웨어가 DMA로 Command Table/PRDT 읽기 → FIS 생성 → SATA PHY(8b/10b 직렬 전송) → SATA 디바이스(HDD/SSD)

SATA vs NVMe vs SAS 한눈 비교

SATA / NVMe / SAS 주요 사양 비교
항목SATA IIINVMe (PCIe 4.0 x4)SAS-4 (24G)
최대 대역폭600 MB/s~7,000 MB/s2,400 MB/s (풀 듀플렉스)
큐 깊이32 (NCQ)65,535 큐 × 65,536 엔트리256 (태그 큐)
평균 레이턴시~100 μs (SSD 기준)~10-20 μs~70-100 μs
주요 용도클라이언트 SSD/HDD, NAS고성능 워크스테이션, 서버, 게이밍엔터프라이즈 스토리지, 데이터센터
상대 가격대저가 (GB당 최저)중-고가고가 (엔터프라이즈 전용)
참고: SATA는 AHCI 레거시 위에 구축되어 단일 큐 32-depth로 제한되지만, NVMe는 처음부터 플래시 스토리지에 최적화된 프로토콜로 멀티 큐를 지원합니다. SAS는 엔터프라이즈 환경에서 듀얼 포트 고가용성과 긴 케이블(최대 10m)을 제공합니다. SATA SSD와 NVMe SSD의 순차 읽기 성능 차이는 약 10배이며, 랜덤 IOPS 차이는 3~5배 정도입니다.
SATA 프로토콜 스택 개요 전송 계층 (Transport) FIS 조립/분해, ATA 명령/상태 전달 링크 계층 (Link) SOF/EOF 프레이밍, CRC, 흐름 제어, 스크램블링 물리 계층 (Physical/PHY) 8b/10b 인코딩, OOB 시그널링, 직렬화 FIS (1~2048 Dwords) 예: H2D FIS 0x27 = 5 Dwords (20B) Frame = SOF + FIS + CRC + EOF 프리미티브: ALIGN, SYNC, R_OK, R_ERR Dword = 40비트 (32비트 + 8b/10b) 차동 신호 쌍 A+/A- (Tx), B+/B- (Rx) 호스트(AHCI HBA)와 디바이스 양쪽 모두 동일한 3계층 스택을 구현

8b/10b 인코딩으로 인해 실제 유효 대역폭은 인터페이스 속도의 80%입니다. 예를 들어, SATA III 6 Gbps의 유효 대역폭은 6 × 0.8 = 4.8 Gbps = 600 MB/s입니다.

단계별 이해

SATA/AHCI를 체계적으로 학습하기 위한 4단계 로드맵입니다.

1단계: SATA 프로토콜 기초
SATA의 물리 계층(PHY), 링크 계층, 전송 계층의 3계층 구조를 이해합니다. PATA에서 SATA로의 전환 배경, 점대점 직렬 링크의 장점, FIS(Frame Information Structure)의 개념을 학습합니다. 이 단계에서는 SATA 개요와 프로토콜 변천FIS 구조 섹션을 참고하세요.
2단계: AHCI 하드웨어 인터페이스
AHCI 레지스터 맵(GHC, 포트 레지스터), Command List/FIS Receive 영역, PRDT(Physical Region Descriptor Table)의 메모리 레이아웃을 이해합니다. HBA가 커맨드를 가져와 FIS로 변환하고 디바이스에 전송하는 흐름을 파악합니다. AHCI 스펙과 레지스터 아키텍처AHCI 드라이버 구현 섹션을 보세요.
3단계: 리눅스 libata 서브시스템
ata_host, ata_port, ata_device, ata_queued_cmd 핵심 구조체와 ata_port_operations 드라이버 콜백의 역할을 학습합니다. SCSI-ATA Translation(SAT)이 어떻게 SCSI CDB를 ATA 명령어로 변환하는지 이해합니다. libata 서브시스템커맨드 실행 경로를 참고하세요.
4단계: 고급 주제
NCQ(Native Command Queuing)의 32-태그 병렬 처리, 전원 관리(ALPM/DIPM/DevSlp), 핫 플러그 메커니즘, Port Multiplier, 에러 처리와 복구 전략 등 고급 주제를 다룹니다. 성능 튜닝, S.M.A.R.T. 모니터링, 디버깅 기법까지 실무 지식을 쌓습니다. NCQ 심화부터 마지막 섹션까지 순서대로 학습하세요.

단계별 실습 명령어

각 학습 단계에서 실행해 볼 수 있는 실습 명령어입니다. 시스템 환경에 따라 root 권한이 필요할 수 있습니다.

# ── 1단계: SATA 프로토콜 기초 확인 ──
# SATA 링크 초기화/속도 확인 (dmesg에서 OOB 시퀀스 결과)
dmesg | grep ata
# 출력 예: ata1: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

# 연결된 ATA 디바이스 정보
dmesg | grep "ata[0-9]"

# ── 2단계: AHCI 하드웨어 인터페이스 확인 ──
# AHCI 컨트롤러 PCI 정보
lspci | grep AHCI
# 출력 예: 00:1f.2 SATA controller: Intel Corporation 8 Series SATA Controller [AHCI mode]

# AHCI BAR5(ABAR) 주소 확인
lspci -vv -s $(lspci | grep AHCI | awk '{print $1}') | grep "Region 5"

# AHCI 레지스터 덤프 (setpci 활용)
setpci -s $(lspci | grep AHCI | awk '{print $1}') CAP_LIST_ID+0.l

# ── 3단계: libata 서브시스템 확인 ──
# ATA 디바이스 속성 (sysfs)
ls /sys/class/ata_device/
cat /sys/class/ata_device/dev1.0/id
# IDENTIFY 데이터를 raw hex로 확인
cat /sys/class/ata_device/dev1.0/id | xxd | head -20

# libata 모듈 로드 상태
lsmod | grep "libata\|ahci"

# ── 4단계: 고급 주제 (S.M.A.R.T., 성능) ──
# S.M.A.R.T. 정보 확인
smartctl -a /dev/sda

# NCQ 큐 깊이 확인
cat /sys/block/sda/device/queue_depth

# SATA 링크 전원 관리 정책
cat /sys/class/scsi_host/host0/link_power_management_policy
추천 학습 자료 (커널 소스):
  • drivers/ata/ahci.h — AHCI 레지스터 정의, 구조체 선언
  • drivers/ata/ahci.c — PCI AHCI 드라이버 진입점, 포트 초기화
  • drivers/ata/libata-core.c — ATA 명령 발행, IDENTIFY 파싱, 전송 모드 설정
  • drivers/ata/libata-scsi.c — SCSI-ATA Translation(SAT) 변환 테이블
  • drivers/ata/libata-eh.c — 에러 핸들러, 리셋 체인, 복구 로직
  • include/linux/ata.h — ATA 명령어 코드, FIS 타입, IDENTIFY 필드 인덱스
  • include/linux/libata.h — libata 핵심 구조체 (ata_host, ata_port, ata_device)

SATA 개요와 프로토콜 변천

PATA(Parallel ATA, IDE)는 40핀/80핀 리본 케이블로 최대 2개 디바이스(마스터/슬레이브)를 연결하는 방식이었습니다. 최고 속도인 Ultra DMA/133도 133 MB/s에 불과했고, 긴 리본 케이블은 공기 순환을 방해하며 신호 무결성 문제(crosstalk, skew)가 심각했습니다.

SATA는 이 문제를 해결하기 위해 2003년 Serial ATA International Organization(SATA-IO)이 제정했습니다. 핵심 설계 원칙은 다음과 같습니다:

PATA vs SATA 비교

PATA(Parallel ATA) vs SATA(Serial ATA) 비교
항목PATA (IDE)SATA
신호 방식병렬 (16비트 데이터 버스)직렬 (차동 1쌍 x 2)
케이블40/80핀 리본 (최대 46cm)7핀 (최대 1m)
토폴로지마스터/슬레이브 (공유 버스)점대점 (전용 링크)
최대 속도133 MB/s (UDMA/133)600 MB/s (SATA III)
핫 플러그미지원지원
NCQTCQ (미흡)NCQ (32-tag)
전원 커넥터4핀 Molex15핀 SATA Power
점퍼 설정필요 (Master/Slave/CS)불필요
공기 순환나쁨 (넓은 리본)좋음 (얇은 케이블)

프로토콜 계층 구조

SATA 프로토콜은 세 계층으로 나뉩니다:

계층역할주요 단위
전송 계층 (Transport)FIS 조립/분해, 명령/데이터 전달FIS (Frame Information Structure)
링크 계층 (Link)프레임 구성, CRC, 흐름 제어, 스크램블링Frame (SOF + FIS + CRC + EOF)
물리 계층 (PHY)8b/10b 인코딩, 직렬화, OOB 시그널링Dword (32비트 = 40비트 인코딩)

프레임 구조

SATA 링크 계층의 프레임은 다음과 같이 구성됩니다:

필드크기설명
SOF (Start of Frame)1 Dword프레임 시작 프리미티브
FIS 페이로드1~2048 DwordFIS 타입에 따른 페이로드
CRC1 DwordFIS 페이로드의 32비트 CRC
EOF (End of Frame)1 Dword프레임 종료 프리미티브

SATA Express와 mSATA

SATA Express(SATAe)는 SATA 커넥터에 PCIe 레인 2개를 추가해 최대 2 GB/s를 제공하려 했으나, NVMe M.2의 등장으로 시장에서 사라졌습니다. mSATA는 미니 PCIe 슬롯의 물리적 형태를 빌려 SATA 신호를 전달하는 소형 폼 팩터로, M.2에 의해 대체되었습니다.

SATA 신호 전기적 특성

SATA는 LVDS(Low Voltage Differential Signaling) 기반의 차동 신호 쌍을 사용합니다. 송신(Tx)과 수신(Rx) 각각 독립된 차동 쌍을 가져 전이중(full-duplex) 통신이 가능합니다.

OOB(Out of Band) 시그널링: SATA 링크 초기화 시에는 일반 데이터 전송과 다른 OOB 시그널링을 사용합니다. COMRESET(호스트→디바이스), COMINIT(디바이스→호스트), COMWAKE(양방향)의 세 가지 신호가 있으며, 버스트/갭 타이밍 패턴으로 구분됩니다. COMRESET/COMINIT은 175.0 ns 버스트 + 175.0 ns 갭을 6회 반복, COMWAKE는 175.0 ns 버스트 + 52.5 ns 갭을 6회 반복합니다.

8b/10b 인코딩 상세

SATA 물리 계층은 8b/10b 인코딩을 사용하여 8비트 데이터를 10비트 심볼로 변환합니다. 이 인코딩의 핵심 목적은 다음과 같습니다:

SATA에서 사용되는 주요 K-코드 (특수 문자)
K-코드심볼10b 인코딩 (RD-)용도
K28.5ALIGN001111 1010워드 정렬, 클럭 보정용 프리미티브 접두사
K28.3SOF/EOF001111 0011프레임 시작/종료 프리미티브의 구성 요소
K28.0-001111 0100제어 프리미티브 구성용
K28.1-001111 1001제어 프리미티브 구성용
K28.6-001111 0110제어 프리미티브 구성용

Running Disparity (RD)는 현재까지 전송된 1과 0의 누적 불균형을 추적합니다. 각 10비트 심볼은 RD+ 또는 RD- 두 가지 인코딩을 가지며, 현재 RD 상태에 따라 적절한 인코딩을 선택합니다. RD가 양수이면 0이 많은 인코딩을, RD가 음수이면 1이 많은 인코딩을 선택하여 DC 밸런스를 유지합니다. 코드 위반(code violation)은 유효하지 않은 10비트 패턴이 수신될 때 검출되며, 이는 전송 오류의 증거로 링크 계층에서 에러 처리됩니다.

대역폭 오버헤드: 8b/10b 인코딩으로 인해 유효 대역폭은 물리 속도의 80%로 제한됩니다. 8비트당 2비트(20%)의 오버헤드가 발생하며, 이는 PCIe 3.0 이후 도입된 128b/130b 인코딩(약 1.5% 오버헤드)과 비교하면 상당한 손실입니다. 이것이 SATA가 6 Gbps에서 유효 대역폭 600 MB/s로 제한되는 근본 원인입니다.

SATA 커넥터 핀 배치 (데이터)

SATA 데이터 커넥터는 7핀으로 구성되며, 3개의 접지 핀이 차동 쌍 사이를 분리하여 신호 무결성을 보장합니다.

SATA 데이터 커넥터 7핀 배치
신호명설명
1GND접지 (차폐)
2A+ (Tx+)호스트 송신 차동 양극
3A- (Tx-)호스트 송신 차동 음극
4GND접지 (Tx/Rx 분리)
5B- (Rx-)호스트 수신 차동 음극
6B+ (Rx+)호스트 수신 차동 양극
7GND접지 (차폐)

L자형 키잉 설계로 역삽입이 방지되며, 핫 플러그를 위해 접지 핀(1, 7)이 신호 핀보다 먼저 접촉합니다. 케이블 최대 길이는 1m(외장 eSATA는 2m)이며, 이 제한은 신호 감쇠와 지터 허용 범위에 의해 결정됩니다.

SATA 프레임 구조와 프리미티브 SATA 링크 계층 프레임 SOF 1 Dword FIS 페이로드 (스크램블링 적용) 1~2048 Dwords (타입에 따라 가변) CRC 1 Dword EOF 1 Dword 주요 프리미티브 신호 X_RDY 전송 준비 알림 R_RDY 수신 준비 완료 R_OK 프레임 정상 수신 R_ERR CRC 오류 검출 SYNC 유휴 상태 유지 ALIGN 클럭 보정용 전송 흐름: SYNC(유휴) → X_RDY → R_RDY → SOF+FIS+CRC+EOF → R_OK/R_ERR → SYNC 스크램블링: FIS 페이로드와 CRC에 LFSR(Linear Feedback Shift Register) 다항식 적용 목적: 연속 비트 패턴 제거로 EMI 감소 및 CDR(클럭 데이터 복원) 개선
# 현재 시스템의 SATA 디바이스 확인
lsblk -d -o NAME,ROTA,TRAN,SIZE,MODEL
# SATA 링크 속도 확인
dmesg | grep "SATA link up"
# 출력 예: ata1: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

SATA 전원 커넥터

SATA 전원 커넥터(15핀)는 3.3V, 5V, 12V 세 가지 전압을 공급합니다. 3.3V는 초기 SATA SSD에서 사용되었으나, 대부분의 현대 디바이스는 5V와 12V만 사용합니다. HDD는 12V(스핀들 모터)와 5V(전자회로)를 모두 사용하며, SSD는 주로 5V만 사용합니다.

전압용도
1-33.3V옵션 (대부분 미사용)
4-6GND접지
7-95V전자회로, 컨트롤러
10-12GND접지
13-1512V모터 구동 (HDD)

AHCI 스펙과 레지스터 아키텍처

AHCI(Advanced Host Controller Interface)는 Intel이 2004년에 설계한 SATA HBA(Host Bus Adapter)의 표준 레지스터 인터페이스입니다. AHCI 이전에는 SiI3112, VIA VT6421 등 각 벤더가 독자적인 레지스터 레이아웃을 사용해 벤더별 드라이버가 필요했습니다. AHCI는 PCI BAR5(ABAR, AHCI Base Address Register)를 통해 메모리 매핑된 표준 레지스터 셋을 정의함으로써, 하나의 드라이버(ahci.c)로 모든 AHCI 호환 HBA를 지원할 수 있게 했습니다.

AHCI HBA 레지스터 맵 (BAR5 / ABAR) Generic Host Control (0x00-0x2B) CAP (0x00) GHC (0x04) IS (0x08) PI (0x0C) VS (0x10) CCC_CTL (0x14) CCC_PORTS (0x18) EM_LOC (0x1C) EM_CTL (0x20) CAP2 (0x24) BOHC (0x28) Port Registers (0x100 + 0x80*p) Port 0 (0x100-0x17F) PxCLB (0x00) PxCLBU (0x04) PxFB (0x08) PxFBU (0x0C) PxIS (0x10) PxIE (0x14) PxCMD (0x18) PxTFD (0x20) PxSSTS (0x28) PxSCTL (0x2C) PxSERR (0x30) PxSACT (0x34) PxCI (0x38) Port 1 (0x180-0x1FF) ... (최대 32개 포트) Port N (0x100 + 0x80*N) IS GHC.IS 비트 = 포트별 인터럽트 PI 비트 = 구현된 포트 표시 포트당 0x80 바이트 레지스터 공간 PxCI 쓰기 = 커맨드 발행 트리거

CAP 레지스터 주요 필드

비트필드설명
31S64A64비트 주소 지원
30SNCQNCQ(Native Command Queuing) 지원
29SSNTFSNotification 레지스터 지원
28SMPSMechanical Presence Switch 지원
27SSSStaggered Spin-up 지원
26SALPAggressive Link PM 지원
25SALActivity LED 지원
24SCLOCommand List Override 지원
23:20ISS인터페이스 속도 (1=Gen1, 2=Gen2, 3=Gen3)
17SAMAHCI Only 모드
16SPMPort Multiplier 지원
15FBSSFIS-Based Switching 지원
14PMDPIO Multiple DRQ Block 지원
13SSCSlumber State Capable
12PSCPartial State Capable
12:8NCSCommand Slot 수 (0=1, 31=32)
7CCCSCommand Completion Coalescing 지원
6EMSEnclosure Management 지원
5SXSExternal SATA 지원
4:0NP포트 수 (0=1, 31=32)

GHC 레지스터

비트필드설명
31AEAHCI Enable — 1이면 AHCI 모드, 0이면 레거시 IDE 호환 모드
1IEInterrupt Enable — 전역 인터럽트 활성화
0HRHBA Reset — 1 쓰면 HBA 리셋, 완료 후 자동으로 0
/* drivers/ata/ahci.h — AHCI 레지스터 오프셋 정의 */
enum {
    HOST_CAP        = 0x00,  /* host capabilities */
    HOST_CTL        = 0x04,  /* global host control */
    HOST_IRQ_STAT   = 0x08,  /* interrupt status */
    HOST_PORTS_IMPL = 0x0c,  /* bitmap of implemented ports */
    HOST_VERSION    = 0x10,  /* AHCI spec version */
    HOST_CAP2       = 0x24,  /* host capabilities, extended */
    HOST_BOHC       = 0x28,  /* BIOS/OS handoff control */

    /* port registers (0x100 + port * 0x80) */
    PORT_LST_ADDR       = 0x00,  /* command list DMA addr (PxCLB) */
    PORT_LST_ADDR_HI    = 0x04,  /* command list DMA addr hi (PxCLBU) */
    PORT_FIS_ADDR       = 0x08,  /* FIS rx buf addr (PxFB) */
    PORT_FIS_ADDR_HI    = 0x0c,  /* FIS rx buf addr hi (PxFBU) */
    PORT_IRQ_STAT       = 0x10,  /* interrupt status (PxIS) */
    PORT_IRQ_MASK       = 0x14,  /* interrupt enable/mask (PxIE) */
    PORT_CMD            = 0x18,  /* port command (PxCMD) */
    PORT_TFDATA         = 0x20,  /* taskfile data (PxTFD) */
    PORT_SCR_STAT       = 0x28,  /* SATA phy status (PxSSTS) */
    PORT_SCR_CTL        = 0x2c,  /* SATA phy control (PxSCTL) */
    PORT_SCR_ERR        = 0x30,  /* SATA phy error (PxSERR) */
    PORT_SCR_ACT        = 0x34,  /* SATA phy active (PxSACT for NCQ) */
    PORT_CMD_ISSUE      = 0x38,  /* command issue (PxCI) */
};

포트 인터럽트 상태 (PxIS) 비트

비트필드설명
31CPDSCold Port Detect Status
30TFESTask File Error Status
29HBFSHost Bus Fatal Error Status
28HBDSHost Bus Data Error Status
27IFSInterface Fatal Error Status
26INFSInterface Non-fatal Error Status
24OFSOverflow Status
23IPMSIncorrect Port Multiplier Status
22PRCSPhyRdy Change Status
7DMPSDevice Mechanical Presence Status
6PCSPort Connect Change Status
5DPSDescriptor Processed
4UFSUnknown FIS Interrupt
3SDBSSet Device Bits Interrupt (NCQ 완료)
2DSSDMA Setup FIS Interrupt
1PSSPIO Setup FIS Interrupt
0DHRSDevice to Host Register FIS Interrupt

PxCMD 레지스터 주요 필드

비트필드설명
31:28ICCInterface Communication Control (전원 상태 전환 명령)
27ASPAggressive Slumber/Partial — ALPM 자동 전환 전략
26ALPEAggressive Link PM Enable — ALPM 활성화
21ATAPIATAPI 디바이스 연결 표시
20APSTEAuto Partial to Slumber Transitions Enabled
19FBSCPFIS-Based Switching Capable Port
18ESPExternal SATA Port
17CPDCold Presence Detection
16MPSPMechanical Presence Switch attached to Port
15CRCommand List Running (읽기 전용)
14FRFIS Receive Running (읽기 전용)
4FREFIS Receive Enable
3:1CLOCommand List Override
0STStart — Command List 처리 시작
/* AHCI 포트 시작/정지 절차 */
static int ahci_port_start_engine(void __iomem *port_mmio)
{
    u32 cmd;

    /* 1. FIS Receive 활성화 */
    cmd = readl(port_mmio + PORT_CMD);
    cmd |= PORT_CMD_FIS_RX;    /* FRE = 1 */
    writel(cmd, port_mmio + PORT_CMD);

    /* 2. Command List 처리 시작 */
    cmd |= PORT_CMD_START;     /* ST = 1 */
    writel(cmd, port_mmio + PORT_CMD);

    /* CR(Command List Running)이 1이 될 때까지 대기 */
    return ahci_wait_cmd_start(port_mmio);
}

static int ahci_port_stop_engine(void __iomem *port_mmio)
{
    u32 cmd;

    /* 1. ST = 0으로 클리어 */
    cmd = readl(port_mmio + PORT_CMD);
    cmd &= ~PORT_CMD_START;
    writel(cmd, port_mmio + PORT_CMD);

    /* 2. CR = 0 될 때까지 대기 (최대 500ms) */
    return ahci_wait_cmd_stop(port_mmio);
}

PxSSTS (SStatus) 레지스터 상세

PxSSTS는 SATA PHY 링크의 현재 상태를 보고하는 읽기 전용 레지스터입니다. dmesg에서 볼 수 있는 "SStatus 133"은 이 레지스터의 값입니다.

PxSSTS (Port x Serial ATA Status) 필드
비트필드의미
3:0DET (Device Detection)0디바이스 미검출, PHY 미확립
1디바이스 검출, PHY 미확립
3디바이스 검출, PHY 확립 (정상 통신 가능)
4PHY 오프라인 (COMRESET 후 미응답)
7:4SPD (Current Speed)0속도 미결정
1Gen1 (1.5 Gbps)
2Gen2 (3.0 Gbps)
3Gen3 (6.0 Gbps)
11:8IPM (Interface PM State)0디바이스 미존재
1Active 상태
2Partial 절전 상태
6Slumber 절전 상태
8DevSlp 초절전 상태
SStatus 해석 예: "SStatus 133"은 16진수가 아닌 10진수입니다. 0x133 = DET=3(PHY 확립), SPD=3(Gen3 6Gbps), IPM=1(Active). 즉 SATA III 속도로 정상 연결된 상태입니다. "SControl 300" = 0x300 = DET=0(제한없음), SPD=3(Gen3까지 허용), IPM=0(PM 제한없음)입니다.

PxSCTL (SControl) 레지스터

PxSCTL은 PHY 링크 동작을 제어하는 레지스터입니다. DET 필드에 값을 쓰면 COMRESET을 발행할 수 있습니다.

PxSCTL (Port x Serial ATA Control) 필드
비트필드의미
3:0DET (Device Detection Init)0제어 안 함 (정상 동작)
1COMRESET 발행 (하드 리셋)
4PHY 비활성화
7:4SPD (Speed Allowed)0제한 없음 (최대 속도 협상)
1Gen1만 허용 (1.5 Gbps)
2Gen2까지 허용 (3.0 Gbps)
3Gen3까지 허용 (6.0 Gbps)
11:8IPM (Interface PM Transitions)0제한 없음
1Partial 전이 비허용
2Slumber 전이 비허용
3Partial + Slumber 모두 비허용
/* COMRESET 발행 예시 (drivers/ata/libata-core.c 참조) */
static int sata_link_hardreset(struct ata_link *link,
                               const unsigned long *timing,
                               unsigned long deadline,
                               bool *online, int (*check_ready)(...))
{
    u32 scontrol;

    /* 1. SControl.DET = 1 → COMRESET 시작 */
    sata_scr_read(link, SCR_CONTROL, &scontrol);
    scontrol = (scontrol & 0x0f0) | 0x301; /* DET=1, SPD 유지 */
    sata_scr_write(link, SCR_CONTROL, scontrol);

    /* 2. 최소 1ms 대기 (SATA spec 요구) */
    ata_msleep(link->ap, 1);

    /* 3. SControl.DET = 0 → COMRESET 해제, 링크 협상 시작 */
    scontrol = (scontrol & 0x0f0) | 0x300;
    sata_scr_write(link, SCR_CONTROL, scontrol);

    /* 4. SStatus.DET = 3 (PHY 확립) 될 때까지 대기 */
    return ata_wait_after_reset(link, deadline, check_ready);
}

AHCI BIOS/OS 핸드오프 (BOHC)

BIOS가 AHCI를 사용 중이면 OS가 직접 레지스터를 건드리면 충돌이 발생합니다. AHCI 스펙은 BOHC(BIOS/OS Handoff Control) 레지스터(오프셋 0x28)를 통해 안전한 소유권 이전 절차를 정의합니다.

/* drivers/ata/ahci.c — BIOS/OS 핸드오프 절차 (간략화) */
static void ahci_bios_os_handoff(void __iomem *mmio)
{
    u32 bohc;

    bohc = readl(mmio + HOST_BOHC);
    if (!(bohc & (1 << 0)))  /* BOS (BIOS Owned Semaphore) */
        return;  /* BIOS가 소유 중이 아니면 핸드오프 불필요 */

    /* 1. OOS(OS Owned Semaphore) 비트 설정 */
    writel(bohc | (1 << 1), mmio + HOST_BOHC);  /* OOS = 1 */

    /* 2. BIOS가 BOS를 클리어할 때까지 최대 25ms 대기 */
    int timeout = 25;
    while (readl(mmio + HOST_BOHC) & (1 << 0)) {
        msleep(1);
        if (--timeout == 0) {
            /* BIOS 응답 없음 → BB(BIOS Busy) 체크 후 강제 전환 */
            if (readl(mmio + HOST_BOHC) & (1 << 4))  /* BB */
                msleep(2000);  /* BIOS 작업 완료 대기 */
            break;
        }
    }

    /* 3. OOS 유지, BOS 클리어 확인 → OS가 AHCI 소유 */
}

CAP2 확장 기능

CAP2 레지스터(오프셋 0x24)는 AHCI 1.2 이후 추가된 확장 기능을 보고합니다.

CAP2 레지스터 주요 비트
비트필드설명
5DESODevSlp Entrance from Slumber Only
4SADMSupports Aggressive Device Sleep Management
3SDSSupports Device Sleep (DevSlp)
2APSTAuto Partial to Slumber Transitions
1NVMPNVMHCI Present (NVMe와 공유 HBA)
0BOHBIOS/OS Handoff 지원
# 사용자 공간에서 AHCI 레지스터 읽기 (setpci 활용)
# 먼저 AHCI 컨트롤러의 BDF 주소 확인
BDF=$(lspci | grep AHCI | awk '{print $1}')

# BAR5 (ABAR) 주소 확인
ABAR=$(setpci -s $BDF BASE_ADDRESS_5)
echo "ABAR = 0x$ABAR"

# devmem2로 AHCI CAP 레지스터 읽기 (root 필요)
# CAP = ABAR + 0x00
devmem2 0x${ABAR%0}0 w  # CAP 레지스터
devmem2 0x${ABAR%0}4 w  # GHC 레지스터
devmem2 0x${ABAR%0}c w  # PI (Ports Implemented)
devmem2 0x${ABAR%0}10 w # VS (Version)

# 또는 /sys에서 간접 확인
cat /sys/class/scsi_host/host0/ahci_host_caps
cat /sys/class/scsi_host/host0/ahci_host_version

FIS (Frame Information Structure)

FIS는 SATA 전송 계층에서 호스트와 디바이스 간에 주고받는 데이터 패킷입니다. FIS 타입 바이트(1번째 바이트)로 FIS 종류를 식별하며, 각 FIS 타입마다 고정된 레이아웃을 가집니다.

Register Host-to-Device FIS (Type 0x27) Dword Byte 3 [31:24] Byte 2 [23:16] Byte 1 [15:8] Byte 0 [7:0] Features [7:0] Command C|PMPORT[3:0] FIS Type (0x27) 0 Device LBA High [23:16] LBA Mid [15:8] LBA Low [7:0] 1 Features Exp LBA High Exp LBA Mid Exp LBA Low Exp 2 Control Reserved Count [15:8] Count [7:0] 3 Reserved (Dword 4) 4 C 비트: 1 = Command 레지스터 갱신, 0 = Control 레지스터 갱신 PMPORT: Port Multiplier 포트 번호 (0~15) 전체 크기: 5 Dwords = 20 바이트 48비트 LBA: LBA Low/Mid/High + Exp 필드 사용 (총 48비트 주소)

FIS 타입 일람

FIS 타입방향크기 (Dword)설명
Register H2D0x27Host → Device5ATA 명령/제어 레지스터 전달
Register D2H0x34Device → Host5ATA 상태/에러 레지스터 반환
Data0x46양방향가변PIO/DMA 데이터 전송
DMA Activate0x39Device → Host1디바이스가 DMA 전송 준비 완료 알림
DMA Setup0x41양방향7First Party DMA(NCQ) 셋업
PIO Setup0x5FDevice → Host5PIO 전송 파라미터 전달
Set Device Bits0xA1Device → Host2NCQ 완료 통지 (SActive 비트 갱신)
/* include/linux/ata.h — FIS 타입 상수 */
enum {
    ATA_FIS_TYPE_REG_H2D    = 0x27,
    ATA_FIS_TYPE_REG_D2H    = 0x34,
    ATA_FIS_TYPE_DATA       = 0x46,
    ATA_FIS_TYPE_DMA_ACT    = 0x39,
    ATA_FIS_TYPE_DMA_SETUP  = 0x41,
    ATA_FIS_TYPE_PIO_SETUP  = 0x5F,
    ATA_FIS_TYPE_SDB        = 0xA1,
};

/* Register H2D FIS 구조체 (drivers/ata/ahci.c에서 사용) */
struct ahci_cmd_hdr {
    __le32  opts;        /* PRDTL, flags, CFL */
    __le32  status;      /* PRDBC (bytes transferred) */
    __le32  tbl_addr;    /* Command Table base addr low */
    __le32  tbl_addr_hi; /* Command Table base addr high */
    __le32  reserved[4]; /* 패딩, 총 32바이트 */
};

Register Device-to-Host FIS (0x34)

디바이스가 명령 실행 결과를 호스트에 보고할 때 사용하는 FIS입니다. ATA Status/Error 레지스터 값이 포함되어 있으며, AHCI는 이를 FIS Receive 영역에 자동으로 저장합니다.

Dword바이트 3바이트 2바이트 1바이트 0
0ErrorStatusReserved | I | PMPORTFIS Type (0x34)
1DeviceLBA HighLBA MidLBA Low
2ReservedLBA High ExpLBA Mid ExpLBA Low Exp
3ReservedReservedCount [15:8]Count [7:0]
4Reserved

Set Device Bits FIS (0xA1)

NCQ 명령의 완료를 알리는 FIS입니다. 여러 태그를 한 번에 완료 통보할 수 있어, 인터럽트 오버헤드를 줄입니다.

Dword바이트 3바이트 2바이트 1바이트 0
0ErrorStatus Hi | N | I | Status LoReserved | I | PMPORTFIS Type (0xA1)
1SActive (완료된 태그 비트맵)

AHCI HBA는 SDB FIS를 수신하면 자동으로 PxSACT 레지스터에서 해당 태그 비트를 클리어하고, PxIS.SDBS 인터럽트를 발생시킵니다. 드라이버는 이전 PxSACT 값과 비교하여 어떤 태그가 완료되었는지 식별합니다.

DMA Setup FIS (0x41)

NCQ의 First Party DMA에서 사용됩니다. 디바이스가 호스트에게 DMA 전송 파라미터를 알려주며, 호스트 메모리 주소(DMA Buffer Identifier)와 전송 방향을 포함합니다.

Dword내용
0FIS Type (0x41) | PMPORT | D(방향) | I(인터럽트) | A(Auto-activate)
1-2DMA Buffer Identifier Low/High (64비트 주소)
3Reserved
4DMA Buffer Offset
5DMA Transfer Count
6Reserved

PIO Setup FIS (0x5F) 상세

PIO Setup FIS는 디바이스가 PIO 데이터 전송 파라미터를 호스트에 알려주는 FIS입니다. IDENTIFY DEVICE, SMART READ DATA 등 PIO 기반 명령에서 사용됩니다.

PIO Setup FIS (0x5F) 필드 레이아웃
Dword바이트 3바이트 2바이트 1바이트 0
0ErrorStatusRsvd | I | D | PMPORTFIS Type (0x5F)
1DeviceLBA HighLBA MidLBA Low
2ReservedLBA High ExpLBA Mid ExpLBA Low Exp
3E_StatusReservedCount [15:8]Count [7:0]
4ReservedTransfer Count (바이트 수)
E_Status (Ending Status): PIO 전송 완료 후의 최종 Status 레지스터 값입니다. Dword 0의 Status는 전송 시작 시점의 상태이고, E_Status는 전송 완료 후 상태입니다. Transfer Count는 이번 DRQ 블록에서 전송할 바이트 수(보통 512B)를 나타냅니다. D 비트: 데이터 전송 방향 (1 = Device→Host, 0 = Host→Device). I 비트: 인터럽트 비트로, 전송 완료 시 인터럽트 발생 여부를 결정합니다.

FIS 수신 영역 (FIS Receive Area)

AHCI HBA는 디바이스가 전송한 FIS를 자동으로 PxFB가 가리키는 메모리 영역에 저장합니다. 이 FIS Receive 영역은 포트당 256바이트 크기이며, 각 FIS 타입별 고정 오프셋에 저장됩니다. HBA는 수신한 FIS를 해당 오프셋에 DMA로 복사한 후 PxIS 인터럽트를 발생시킵니다.

AHCI FIS Receive 영역 오프셋 맵 (256 바이트)
오프셋크기FIS 타입설명
0x0028 바이트DMA Setup FIS (DSFIS)NCQ DMA 셋업 파라미터 저장
0x1C4 바이트(패딩)32바이트 정렬용
0x2020 바이트PIO Setup FIS (PSFIS)PIO 전송 파라미터 저장
0x3412 바이트(패딩)정렬용
0x4020 바이트Register D2H FIS (RFIS)명령 완료 상태/에러 저장
0x544 바이트(패딩)정렬용
0x588 바이트Set Device Bits FIS (SDBFIS)NCQ 완료 태그 비트맵 저장
0x6064 바이트Unknown FIS (UFIS)미분류 FIS 저장 (디버깅용)
0xA096 바이트Reserved예약 영역 (총 256바이트 채움)
/* drivers/ata/ahci.h — FIS Receive 영역 구조체 */
struct ahci_received_fis {
    /* 0x00 — DMA Setup FIS */
    u8    dsfis[28];
    u8    dsfis_pad[4];

    /* 0x20 — PIO Setup FIS */
    u8    psfis[20];
    u8    psfis_pad[12];

    /* 0x40 — Register D2H FIS */
    u8    rfis[20];
    u8    rfis_pad[4];

    /* 0x58 — Set Device Bits FIS */
    u8    sdbfis[8];

    /* 0x60 — Unknown FIS */
    u8    ufis[64];

    /* 0xA0 — Reserved */
    u8    reserved[96];
} __attribute__((packed)); /* 총 256 바이트 */

/* 드라이버에서 D2H FIS 읽기 예시 */
static bool ahci_qc_fill_rtf(struct ata_queued_cmd *qc)
{
    struct ahci_port_priv *pp = qc->ap->private_data;
    u8 *rx_fis = pp->rx_fis;  /* FIS Receive 영역 포인터 */

    if (qc->tf.protocol == ATA_PROT_PIO && qc->dma_dir == DMA_FROM_DEVICE) {
        /* PIO 명령: PIO Setup FIS(0x20)에서 결과 읽기 */
        ata_tf_from_fis(rx_fis + RX_FIS_PIO_SETUP, &qc->result_tf);
    } else {
        /* DMA/NCQ 명령: Register D2H FIS(0x40)에서 결과 읽기 */
        ata_tf_from_fis(rx_fis + RX_FIS_D2H_REG, &qc->result_tf);
    }
    return true;
}
AHCI FIS Receive 영역 메모리 레이아웃 (256 Bytes) PxFB가 가리키는 DMA 일관 메모리 (256 바이트) DSFIS 28B PSFIS 20B RFIS 20B SDBFIS 8B UFIS 64B Reserved 96B 0x00 0x20 0x40 0x58 0x60 0xA0 0x100 HBA 자동 저장 동작 Register D2H FIS 수신 시 (오프셋 0x40) 1. RFIS 영역에 FIS 데이터 DMA 복사 2. PxTFD 레지스터에 Status/Error 갱신 → PxIS.DHRS 인터럽트 Set Device Bits FIS 수신 시 (오프셋 0x58) 1. SDBFIS 영역에 FIS 데이터 DMA 복사 2. PxSACT에서 완료 태그 클리어 → PxIS.SDBS 인터럽트 PIO Setup FIS 수신 시 (오프셋 0x20) 1. PSFIS 영역에 FIS 데이터 DMA 복사 2. PxTFD 갱신 → Data FIS 수신 대기 → PxIS.PSS 인터럽트 DMA Setup FIS 수신 시 (오프셋 0x00) 1. DSFIS 영역에 FIS 데이터 DMA 복사 2. DMA 엔진에 버퍼 주소/크기 설정 → PxIS.DSS 인터럽트

SATA/AHCI 소프트웨어 스택

SATA/AHCI 리눅스 커널 소프트웨어 스택 User Space (read/write/ioctl) VFS (Virtual File System) Block Layer (blk-mq) SCSI Mid Layer (scsi_cmnd) libata-scsi (SAT) libata-eh (에러 처리) libata-core (ata_qc_issue) AHCI LLD (ahci.c / ahci_platform.c) HBA Hardware (AHCI Controller) SATA PHY (직렬 링크, OOB 시그널링) SATA Device (HDD / SSD) libata-core.c libata-scsi.c ahci.c libata-eh.c libata-sff.c SCSI CDB → ATA Command 변환 (SAT: SCSI-ATA Translation)

각 계층의 역할을 정리하면 다음과 같습니다:

계층핵심 소스 파일역할
SCSI Mid Layerdrivers/scsi/SCSI 명령 디스패치, 디바이스 스캔
libata-scsidrivers/ata/libata-scsi.cSCSI CDB → ATA 명령어 변환 (SAT)
libata-coredrivers/ata/libata-core.cATA 명령 발행, 디바이스 초기화, IDENTIFY
libata-sffdrivers/ata/libata-sff.cSFF(Small Form Factor, 레거시 IDE) 지원
libata-ehdrivers/ata/libata-eh.c에러 처리, 리셋, 복구
AHCI LLDdrivers/ata/ahci.cPCI AHCI 컨트롤러 드라이버
AHCI Platformdrivers/ata/ahci_platform.c임베디드/SoC AHCI 컨트롤러

SCSI-ATA Translation (SAT) 계층 상세

libata-scsi.c는 SCSI 미드 레이어와 ATA 명령 사이의 브릿지 역할을 합니다. 이 변환 계층이 존재하기 때문에 SATA 디스크도 /dev/sd* 이름을 사용하며, sg_inq, sg_vpd 등 SCSI 유틸리티가 동작합니다.

/* drivers/ata/libata-scsi.c — SAT 디바이스 탐색 */
static struct ata_device *ata_scsi_find_dev(
    struct ata_port *ap, const struct scsi_device *scsidev)
{
    struct ata_link *link;
    unsigned int devno;

    /* SCSI target ID로 ATA 디바이스 매핑:
     * target 0 = link.device[0] (master)
     * target 1 = link.device[1] (slave, PATA only) */
    link = &ap->link;
    if (unlikely(scsidev->channel || scsidev->lun))
        return NULL;

    devno = scsidev->id;
    return &link->device[devno];
}

/* SAT 변환 테이블 등록 (SCSI opcode → 변환 함수 매핑) */
static const struct ata_scsi_xlat_handler ata_scsi_xlat_table[] = {
    { INQUIRY,          ata_scsi_inquiry_xlat },
    { TEST_UNIT_READY,  ata_scsi_tur_xlat },
    { READ_6,           ata_scsi_rw_xlat },
    { READ_10,          ata_scsi_rw_xlat },
    { READ_16,          ata_scsi_rw_xlat },
    { WRITE_6,          ata_scsi_rw_xlat },
    { WRITE_10,         ata_scsi_rw_xlat },
    { WRITE_16,         ata_scsi_rw_xlat },
    { SYNCHRONIZE_CACHE, ata_scsi_flush_xlat },
    { MODE_SENSE,       ata_scsi_mode_sense_xlat },
    { START_STOP,       ata_scsi_start_stop_xlat },
    { WRITE_SAME_16,    ata_scsi_write_same_xlat }, /* → TRIM */
    /* ... */
};

libata 모듈 구조와 심볼 의존성

libata는 여러 모듈로 분리되어 있으며, 각 모듈은 EXPORT_SYMBOL_GPL로 심볼을 공유합니다.

libata 모듈 간 주요 익스포트 심볼
모듈주요 익스포트 심볼사용처
libata-coreata_qc_issue, ata_dev_classify, ata_dev_read_id모든 ATA LLD
libata-coreata_host_alloc, ata_host_register, ata_host_activateAHCI, SFF LLD
libata-scsiata_scsi_queuecmd, ata_scsi_slave_configSCSI 미드 레이어
libata-sffata_sff_port_ops, ata_bmdma_port_ops레거시 IDE LLD
libata-ehata_std_error_handler, sata_link_hardreset모든 ATA LLD

요청 흐름 함수 경로 상세

/* 전체 I/O 요청 처리 흐름 (함수 이름 포함) */

/* ── 블록 레이어 ── */
submit_bio()
  → blk_mq_submit_bio()
    → blk_mq_dispatch_rq_list()
      → scsi_queue_rq()              /* SCSI 미드 레이어 진입 */

/* ── SCSI 미드 레이어 ── */
scsi_queue_rq()
  → scsi_dispatch_cmd()
    → ata_scsi_queuecmd()            /* libata-scsi 진입 */

/* ── libata-scsi (SAT 변환) ── */
ata_scsi_queuecmd()
  → __ata_scsi_queuecmd()
    → ata_scsi_translate()
      → ata_scsi_rw_xlat()           /* CDB → taskfile 변환 */
      → ata_qc_issue()               /* libata-core 진입 */

/* ── libata-core ── */
ata_qc_issue()
  → ap->ops->qc_prep(qc)            /* = ahci_qc_prep() */
  → ap->ops->qc_issue(qc)           /* = ahci_qc_issue() */

/* ── AHCI LLD ── */
ahci_qc_prep()                       /* CFIS + PRDT 채우기 */
ahci_qc_issue()                      /* PxCI/PxSACT 레지스터 쓰기 */

/* ── 하드웨어 (HBA) ── */
/* Command Header DMA 읽기 → Command Table DMA 읽기 */
/* → H2D FIS 생성 → PHY 전송 → 디바이스 응답 대기 */

ATA 전송 모드 (Transfer Mode)

ATA 디바이스는 다양한 전송 모드를 지원하며, libata는 IDENTIFY 데이터를 기반으로 최적 모드를 협상합니다.

ATA 전송 모드 일람
모드최대 속도방식비고
PIO 03.3 MB/sCPU 주도 전송모든 ATA 디바이스 기본 지원
PIO 15.2 MB/sCPU 주도 전송
PIO 28.3 MB/sCPU 주도 전송
PIO 311.1 MB/sCPU 주도 전송IORDY 신호 도입
PIO 416.7 MB/sCPU 주도 전송IORDY 필수
MDMA 04.2 MB/sMultiword DMA레거시 DMA
MDMA 113.3 MB/sMultiword DMA
MDMA 216.7 MB/sMultiword DMA
UDMA 016.7 MB/sUltra DMA더블 에지 클럭킹 도입
UDMA 125.0 MB/sUltra DMA
UDMA 233.3 MB/sUltra DMAATA/33
UDMA 344.4 MB/sUltra DMA
UDMA 466.7 MB/sUltra DMAATA/66, 80핀 케이블 필요
UDMA 5100 MB/sUltra DMAATA/100
UDMA 6133 MB/sUltra DMAATA/133 (PATA 최종)
SATA와 전송 모드: SATA 디바이스는 내부적으로 UDMA 모드 협상을 생략하고, SATA PHY 링크 속도(Gen1/2/3)가 대역폭을 결정합니다. 그러나 IDENTIFY 데이터의 UDMA 지원 비트는 여전히 설정되며, libata는 이를 참조하여 DMA 전송 활성화를 결정합니다. ata_dev_set_xfermode()는 SET FEATURES(0xEF) 명령으로 디바이스에 전송 모드를 통보합니다.

libata 서브시스템

libata는 리눅스 커널에서 ATA/SATA 디바이스를 지원하는 핵심 서브시스템입니다. SCSI 미드 레이어 위에 구축되어 있으며, ATA 명령어를 SCSI 명령어로 변환(SAT)합니다. 다음은 libata의 핵심 데이터 구조체 계층입니다.

/* include/linux/libata.h — 핵심 구조체 계층 */

/* ata_host: 하나의 AHCI 컨트롤러 (PCI 디바이스) */
struct ata_host {
    struct device       *dev;        /* PCI/platform 디바이스 */
    void __iomem        *iomap[6];   /* PCI BAR 매핑 */
    unsigned int        n_ports;     /* 포트 수 */
    struct ata_port     *ports[0];   /* 가변 배열 */
    /* ... */
};

/* ata_port: SATA 포트 하나 (물리적 커넥터) */
struct ata_port {
    struct ata_host     *host;       /* 소속 호스트 */
    const struct ata_port_operations *ops; /* 드라이버 콜백 */
    struct ata_link     link;        /* 기본 링크 */
    struct ata_link     *pmp_link;   /* PMP 멀티 링크 */
    unsigned int        port_no;     /* 포트 번호 */
    struct ata_queued_cmd qcmd[ATA_MAX_QUEUE]; /* 커맨드 슬롯 */
    unsigned int        qc_active;   /* 활성 QC 비트맵 */
    /* ... */
};

/* ata_link: 포트와 디바이스 사이의 링크 */
struct ata_link {
    struct ata_port     *ap;         /* 소속 포트 */
    struct ata_device   device[2];   /* dev0, dev1 (PATA 호환) */
    unsigned int        pmp;         /* PMP 포트 번호 */
    struct ata_eh_info  eh_info;     /* EH 정보 */
    /* ... */
};

/* ata_device: 연결된 ATA/ATAPI 디바이스 */
struct ata_device {
    struct ata_link     *link;       /* 소속 링크 */
    unsigned int        devno;       /* 디바이스 번호 (0 or 1) */
    u16                 id[ATA_ID_WORDS]; /* IDENTIFY 데이터 */
    unsigned int        class;       /* ATA_DEV_ATA, ATA_DEV_ATAPI 등 */
    u64                 n_sectors;   /* 총 섹터 수 */
    /* ... */
};

/* ata_queued_cmd: 발행된 하나의 ATA 명령 */
struct ata_queued_cmd {
    struct ata_port     *ap;
    struct ata_device   *dev;
    struct scsi_cmnd   *scsicmd;    /* 원본 SCSI 명령 */
    struct ata_taskfile tf;          /* ATA taskfile 레지스터 */
    unsigned int        tag;         /* 커맨드 슬롯 태그 (0~31) */
    unsigned int        n_elem;      /* scatter-gather 엔트리 수 */
    /* ... */
};

ata_port_operations 주요 콜백

콜백호출 시점설명
qc_prep커맨드 발행 직전Command Table, CFIS, PRDT 채우기
qc_issue커맨드 발행PxCI 비트 쓰기 → HBA에 커맨드 전달
qc_fill_rtf커맨드 완료 후결과 taskfile(D2H FIS) 읽기
freezeEH 시작포트 인터럽트 비활성화
thawEH 종료포트 인터럽트 재활성화
prereset리셋 전리셋 전 조건 검사 (데드라인 설정)
softreset소프트 리셋SRST FIS 전송
hardreset하드 리셋COMRESET 발행 (PHY 레벨 리셋)
postreset리셋 후SError 클리어, 디바이스 재식별
error_handler에러 발생에러 분류, 복구 전략 결정
port_start포트 초기화DMA 영역 할당, 포트 활성화
port_stop포트 종료DMA 영역 해제, 포트 비활성화
/* drivers/ata/ahci.c — AHCI 드라이버의 port_operations */
static struct ata_port_operations ahci_ops = {
    .inherits       = &sata_pmp_port_ops,
    .qc_defer       = ahci_pmp_qc_defer,
    .qc_prep        = ahci_qc_prep,
    .qc_issue       = ahci_qc_issue,
    .qc_fill_rtf    = ahci_qc_fill_rtf,
    .freeze         = ahci_freeze,
    .thaw           = ahci_thaw,
    .softreset      = ahci_softreset,
    .hardreset      = ahci_hardreset,
    .postreset      = ahci_postreset,
    .pmp_softreset  = ahci_softreset,
    .pmp_hardreset  = ahci_hardreset,
    .error_handler  = ahci_error_handler,
    .post_internal_cmd = ahci_post_internal_cmd,
    .port_start     = ahci_port_start,
    .port_stop      = ahci_port_stop,
};

libata 초기화 흐름

/* AHCI 드라이버 로드 시 libata 초기화 경로 */
ahci_pci_probe()
  → ata_host_alloc_pinfo()        /* ata_host + ata_port 할당 */
    → ata_port_alloc()            /* 포트당 메모리 할당 */
  → ahci_host_activate()
    → ata_host_start()            /* port_start 콜백 호출 */
      → ahci_port_start()        /* DMA 영역 할당 */
    → devm_request_threaded_irq() /* 인터럽트 핸들러 등록 */
    → ata_host_register()         /* 호스트 등록 */
      → async_port_probe()       /* 비동기 포트 프로브 */
        → ata_port_probe()
          → ata_eh_recover()     /* 초기 리셋 + 디바이스 발견 */
            → ata_dev_read_id()  /* IDENTIFY DEVICE */
            → ata_dev_configure() /* 전송 모드, NCQ 설정 */
          → scsi_add_host()      /* SCSI 호스트 등록 */
          → scsi_scan_host()     /* SCSI 디바이스 스캔 */

IDENTIFY DEVICE 데이터

ATA 디바이스가 IDENTIFY DEVICE (0xEC) 명령에 응답하는 512바이트 데이터에는 디바이스의 모든 기능과 속성이 포함되어 있습니다. libata는 이 데이터를 파싱하여 전송 모드, NCQ 지원 여부, 48비트 LBA 지원 등을 결정합니다.

Word필드설명
0General Config디바이스 타입 (ATA/ATAPI)
10-19Serial Number시리얼 번호 (20자 ASCII)
23-26Firmware Rev펌웨어 버전 (8자 ASCII)
27-46Model Number모델 번호 (40자 ASCII)
47Max Sectors/DRQREAD/WRITE MULTIPLE 최대 섹터
49CapabilitiesDMA/LBA 지원 여부
60-61Total Sectors (28)28비트 LBA 총 섹터 수
75Queue DepthNCQ 큐 깊이 (0=1, 31=32)
76SATA CapabilitiesNCQ, 속도, PHY 이벤트 카운터
77SATA FeaturesNCQ Priority, Unload, DevSlp 등
80Major VersionATA 버전 (ATA-8, ACS-2, ACS-3 등)
83Command Set 248비트 LBA, SMART, Security 등
100-103Total Sectors (48)48비트 LBA 총 섹터 수
106Logical Sector논리 섹터 크기 (512B or 4KB)
217Nominal RPM회전 속도 (1=SSD, 5400/7200/10000/15000)
# IDENTIFY 데이터 전체 확인 (hdparm)
hdparm -I /dev/sda

# 주요 항목 해석 예:
# Model Number:    Samsung SSD 870 EVO 500GB
# Serial Number:   S5Y1NX0T123456K
# Firmware Revision: SVT02B6Q
# Transport:       Serial, ATA8-AST, SATA Rev 3.0
# Queue depth:     32
# SATA Gen3 signaling speed (6.0Gb/s)
# Native Command Queueing (NCQ)

ata_taskfile 구조체

ata_taskfile은 ATA 레지스터의 소프트웨어 표현으로, 모든 ATA 명령 발행과 결과 읽기에 사용됩니다. 48비트 LBA 모드에서는 hob(High Order Byte) 필드가 확장 레지스터를 담당합니다.

/* include/linux/ata.h — ata_taskfile 구조체 */
struct ata_taskfile {
    unsigned long   flags;       /* ATA_TFLAG_* */
    u8              protocol;    /* ATA_PROT_* */

    u8              ctl;         /* Control 레지스터 (nIEN, SRST) */

    /* 28비트 Taskfile 레지스터 */
    u8              command;     /* Command 레지스터 (0x25=READ_DMA_EXT 등) */
    u8              feature;     /* Feature 레지스터 (SET FEATURES 하위 명령) */
    u8              lbal;        /* LBA Low [7:0] */
    u8              lbam;        /* LBA Mid [15:8] */
    u8              lbah;        /* LBA High [23:16] */
    u8              device;      /* Device/Head 레지스터 */
    u8              nsect;       /* Sector Count [7:0] */

    /* 48비트 확장 (HOB: High Order Byte) */
    u8              hob_feature; /* Feature Exp */
    u8              hob_lbal;    /* LBA Low Exp [31:24] */
    u8              hob_lbam;    /* LBA Mid Exp [39:32] */
    u8              hob_lbah;    /* LBA High Exp [47:40] */
    u8              hob_nsect;   /* Sector Count Exp [15:8] */

    /* 결과용 (D2H FIS에서 읽은 값) */
    u8              error;       /* Error 레지스터 */
    u8              status;      /* Status 레지스터 (BSY, DRQ, ERR 등) */

    u8              auxiliary;    /* Auxiliary 레지스터 (NCQ 확장) */
};

/* Taskfile → H2D FIS 변환 (libata-core.c) */
void ata_tf_to_fis(const struct ata_taskfile *tf, u8 pmp, int is_cmd, u8 *fis)
{
    fis[0] = ATA_FIS_TYPE_REG_H2D;     /* 0x27 */
    fis[1] = (pmp & 0x0f) | (is_cmd ? 0x80 : 0);  /* C 비트 */
    fis[2] = tf->command;
    fis[3] = tf->feature;
    fis[4] = tf->lbal;
    fis[5] = tf->lbam;
    fis[6] = tf->lbah;
    fis[7] = tf->device;
    fis[8] = tf->hob_lbal;
    fis[9] = tf->hob_lbam;
    fis[10] = tf->hob_lbah;
    fis[11] = tf->hob_feature;
    fis[12] = tf->nsect;
    fis[13] = tf->hob_nsect;
    /* fis[14..19] = reserved/0 */
}

libata 명령 프로토콜

ATA 명령은 데이터 전송 방식에 따라 여러 프로토콜로 분류됩니다. ata_taskfile.protocol 필드가 이를 결정하며, 프로토콜에 따라 AHCI의 처리 방식이 달라집니다.

ATA 프로토콜 (ata_taskfile.protocol 값)
프로토콜상수데이터 전송예시 명령
ATA_PROT_NODATA1없음SET FEATURES, FLUSH CACHE, STANDBY
ATA_PROT_PIO2PIO (CPU 주도)IDENTIFY DEVICE, SMART READ DATA
ATA_PROT_DMA3DMA (HBA 주도)READ/WRITE DMA EXT
ATA_PROT_NCQ4First Party DMAREAD/WRITE FPDMA QUEUED
ATA_PROT_NCQ_NODATA5NCQ 비데이터NCQ Non-Data 서브커맨드
ATAPI_PROT_NODATA6없음 (ATAPI)TEST UNIT READY (ATAPI)
ATAPI_PROT_PIO7PIO (ATAPI)INQUIRY (ATAPI)
ATAPI_PROT_DMA8DMA (ATAPI)READ(10) (ATAPI CD/DVD)
NCQ vs DMA: NCQ(ATA_PROT_NCQ)는 일반 DMA와 달리 디바이스가 DMA 전송 주도권을 가집니다 (First Party DMA). 최대 32개의 명령을 동시에 큐잉하여 디바이스 내부에서 최적 순서로 재정렬할 수 있습니다. DMA Setup FIS를 통해 디바이스가 호스트에게 전송할 데이터 위치를 알려줍니다.

ata_eh_info와 EH 컨텍스트

libata의 에러 핸들러(EH)는 ata_eh_info 구조체에 에러 정보를 축적합니다. 인터럽트 핸들러에서 에러를 감지하면 ata_ehi_push_desc()로 설명을 추가하고, EH 스레드가 이를 분석하여 복구 전략을 결정합니다.

/* include/linux/libata.h — EH 정보 축적 구조 */
struct ata_eh_info {
    struct ata_device  *dev;          /* 에러 발생 디바이스 */
    u32                serror;        /* SError 레지스터 스냅샷 */
    unsigned int       err_mask;      /* AC_ERR_* 비트맵 */
    unsigned int       action;        /* ATA_EH_* 복구 액션 */
    unsigned int       dev_action[ATA_MAX_DEVICES];
    char               desc[ATA_EH_DESC_LEN]; /* 에러 설명 텍스트 */
    int                desc_len;
};

/* 에러 마스크 예시 (include/linux/libata.h) */
enum {
    AC_ERR_DEV        = (1 << 0),  /* 디바이스 에러 (Status.ERR) */
    AC_ERR_HSM        = (1 << 1),  /* HSM(Host State Machine) 위반 */
    AC_ERR_TIMEOUT    = (1 << 2),  /* 명령 타임아웃 */
    AC_ERR_MEDIA      = (1 << 3),  /* 미디어 에러 (불량 섹터) */
    AC_ERR_ATA_BUS    = (1 << 4),  /* ATA 버스 에러 (CRC 등) */
    AC_ERR_HOST_BUS   = (1 << 5),  /* 호스트 버스 에러 */
    AC_ERR_NCQ        = (1 << 7),  /* NCQ 관련 에러 */
};

/* 복구 액션 (에러 심각도에 따라 에스컬레이션) */
enum {
    ATA_EH_REVALIDATE = (1 << 0),  /* 디바이스 재검증 */
    ATA_EH_SOFTRESET  = (1 << 1),  /* SRST FIS 전송 */
    ATA_EH_HARDRESET  = (1 << 2),  /* COMRESET 발행 */
    ATA_EH_RESET      = ATA_EH_SOFTRESET | ATA_EH_HARDRESET,
    ATA_EH_SET_ACTIVE = (1 << 4),  /* 전원 상태 Active 강제 */
};

libata 링크 속도 관리

libata는 IDENTIFY 데이터와 SControl 레지스터를 사용하여 전송 모드를 협상합니다. ata_dev_set_xfermode()는 SET FEATURES(0xEF) 명령으로 디바이스에 선택된 모드를 통보합니다.

/* drivers/ata/libata-core.c — 전송 모드 설정 (간략화) */
static int ata_dev_set_xfermode(struct ata_device *dev)
{
    struct ata_taskfile tf;

    ata_tf_init(dev, &tf);
    tf.command = ATA_CMD_SET_FEATURES;   /* 0xEF */
    tf.feature = SETFEATURES_XFER;       /* 0x03: Set Transfer Mode */
    tf.nsect = dev->xfer_mode;           /* 예: XFER_UDMA_6 = 0x46 */
    tf.protocol = ATA_PROT_NODATA;

    /* 내부 명령으로 발행 (동기 대기) */
    return ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0, 0);
}

/* UDMA 모드 선택 로직 (ata_dev_configure에서 호출) */
/* 1. IDENTIFY Word 88: UDMA 모드 지원 비트 확인 */
/* 2. 컨트롤러가 지원하는 최대 UDMA 모드와 교집합 */
/* 3. 80핀 케이블 감지 (PATA만 해당) */
/* 4. 가장 높은 공통 모드 선택 */

ATA 명령어 주요 목록

주요 ATA 명령어 코드와 용도
명령코드프로토콜설명
IDENTIFY DEVICE0xECPIO디바이스 정보 512B 읽기 (모델명, 용량, 기능)
IDENTIFY PACKET DEVICE0xA1PIOATAPI 디바이스 정보 읽기
READ DMA EXT0x25DMA48비트 LBA DMA 읽기
WRITE DMA EXT0x35DMA48비트 LBA DMA 쓰기
READ FPDMA QUEUED0x60NCQNCQ 읽기 (최대 32 동시 태그)
WRITE FPDMA QUEUED0x61NCQNCQ 쓰기 (최대 32 동시 태그)
SET FEATURES0xEFNODATA전송 모드, 캐시, 전원 등 설정 변경
FLUSH CACHE EXT0xEANODATA디바이스 캐시 플러시 (fsync 매핑)
SMART READ DATA0xB0/0xD0PIOS.M.A.R.T. 건강 정보 읽기
SMART RETURN STATUS0xB0/0xDANODATA디바이스 건강 상태 확인 (PASS/FAIL)
SECURITY SET PASSWORD0xF1PIOATA 보안 비밀번호 설정
SECURITY ERASE UNIT0xF4PIO보안 삭제 (전체 디스크 소거)
DATA SET MANAGEMENT0x06DMATRIM/Unmap (SSD 가비지 컬렉션 힌트)
SANITIZE DEVICE0xB4NODATA디바이스 데이터 완전 삭제
CHECK POWER MODE0xE5NODATA현재 전원 상태 확인 (Active/Idle/Standby)
STANDBY IMMEDIATE0xE0NODATA즉시 대기 모드 전환
NCQ 명령 특수성: READ/WRITE FPDMA QUEUED는 일반 ATA 명령과 레지스터 매핑이 다릅니다. Feature 레지스터가 Sector Count 역할을, Sector Count 레지스터가 Tag 번호 역할을 합니다. 이 때문에 ahci_qc_prep()에서 H2D FIS 구성 시 특수 처리가 필요합니다. NCQ 명령은 PxSACT에 태그 비트를 먼저 설정한 후 PxCI를 써야 합니다.

AHCI 드라이버 구현

AHCI 커맨드 구조 메모리 레이아웃 Command List (PxCLB) 32개 Command Header Header 0 (32 bytes) Header 1 (32 bytes) Header 2 (32 bytes) ... Header 31 (32 bytes) 총 1024 바이트 Command Header (32B) DW0: PRDTL|PMP|C|B|R|P|W|A|CFL DW1: PRDBC (bytes transferred) DW2: CTBA (Cmd Table Addr Low) DW3: CTBAU (Cmd Table Addr Hi) DW4-7: Reserved Command Table CFIS (64 bytes) Register H2D FIS ACMD (16 bytes) Reserved (48 bytes) PRDT Entry 0 (16B) PRDT Entry 1 (16B) ... PRDT Entry N (16B) PRDT: DBA(4B)+DBAU(4B) +Rsvd(4B)+DBC|I(4B) 최대 65535 PRDT 엔트리 FIS Receive (PxFB) DMA Setup FIS (28B) PIO Setup FIS (20B) Register D2H FIS (20B) Set Device Bits FIS (8B)

AHCI 드라이버는 PCI 디바이스로 프로브(probe)되며, 초기화 과정은 다음과 같습니다:

/* drivers/ata/ahci.c — PCI 프로브 경로 (간략화) */
static int ahci_pci_probe(struct pci_dev *pdev,
                          const struct pci_device_id *ent)
{
    struct ata_host *host;
    struct ahci_host_priv *hpriv;
    int n_ports, rc;

    /* 1. PCI 리소스 활성화 */
    rc = pcim_enable_device(pdev);

    /* 2. BAR5 (ABAR) 메모리 매핑 */
    rc = pcim_iomap_regions(pdev, 1 << AHCI_PCI_BAR, DRV_NAME);
    hpriv->mmio = pcim_iomap_table(pdev)[AHCI_PCI_BAR];

    /* 3. HBA 리셋 및 AHCI 모드 활성화 */
    ahci_reset_controller(host);

    /* 4. 포트 수 확인 (CAP.NP, PI) */
    n_ports = ahci_nr_ports(hpriv->cap);

    /* 5. ata_host 할당 */
    host = ata_host_alloc_pinfo(&pdev->dev, ppi, n_ports);

    /* 6. 포트별 DMA 영역 할당 (Command List + FIS Receive) */
    /* ahci_port_start()에서 수행 */

    /* 7. 인터럽트 등록 및 호스트 활성화 */
    rc = ahci_host_activate(host);
    return rc;
}

Command Header 필드 상세

필드비트설명
PRDTL31:16PRDT 엔트리 수
PMP15:12Port Multiplier 포트 번호
C10Clear Busy upon R_OK (Set Device Bits FIS)
B9BIST FIS
R8Reset (SRST 비트)
P7Prefetchable (PRDT 프리페치 가능)
W6Write (1=H2D 데이터, 0=D2H 데이터)
A5ATAPI (ACMD 필드 사용)
CFL4:0Command FIS Length (Dword 단위, 보통 5)

PRD (Physical Region Descriptor) 엔트리

/* AHCI PRDT 엔트리 (16바이트) */
struct ahci_sg {
    __le32  addr;       /* DBA: Data Base Address (물리 주소 하위) */
    __le32  addr_hi;    /* DBAU: Data Base Address Upper */
    __le32  reserved;
    __le32  flags_size; /* DBC[21:0]: 바이트 카운트 (0-based), 비트31: I (인터럽트) */
};
/* DBC 최대값: 0x3FFFFF = 4MB - 1 (한 PRD 엔트리당 최대 4MB 전송) */
/* 전체 PRDT 크기: 최대 65535 엔트리 × 4MB = ~256TB (실제로는 제한적) */

ahci_port_start() DMA 영역 할당 상세

AHCI는 각 포트에 대해 Command List(1024바이트), FIS Receive(256바이트), 그리고 커맨드 슬롯당 Command Table을 DMA 일관(coherent) 메모리로 할당해야 합니다. ahci_port_start()는 포트 초기화 시 이 영역들을 할당하고 레지스터에 물리 주소를 기록합니다.

/* drivers/ata/ahci.c — ahci_port_start (간략화) */
static int ahci_port_start(struct ata_port *ap)
{
    struct ahci_host_priv *hpriv = ap->host->private_data;
    struct ahci_port_priv *pp;
    void *mem;
    dma_addr_t mem_dma;
    size_t dma_sz, rx_fis_sz;

    pp = devm_kzalloc(dev, sizeof(*pp), GFP_KERNEL);

    /* ── 1. Command List + Command Table 할당 ── */
    /* Command Table 크기: 128B(CFIS+ACMD+Reserved) + PRDT */
    dma_sz = AHCI_CMD_SLOT_SZ                /* 32 * 32 = 1024B (CLB) */
           + AHCI_CMD_TBL_HDR * AHCI_MAX_CMDS  /* 128B * 32 = 4096B */
           + AHCI_MAX_SG * sizeof(struct ahci_sg) * AHCI_MAX_CMDS;

    mem = dmam_alloc_coherent(dev, dma_sz, &mem_dma, GFP_KERNEL);
    memset(mem, 0, dma_sz);

    /* Command List Base Address 설정 */
    pp->cmd_slot = mem;
    pp->cmd_slot_dma = mem_dma;

    mem += AHCI_CMD_SLOT_SZ;
    mem_dma += AHCI_CMD_SLOT_SZ;

    /* 각 슬롯의 Command Table 주소 기록 */
    for (int i = 0; i < AHCI_MAX_CMDS; i++) {
        pp->cmd_tbl[i] = mem;
        pp->cmd_tbl_dma[i] = mem_dma;

        /* Command Header에 CTBA/CTBAU 기록 */
        pp->cmd_slot[i].tbl_addr = cpu_to_le32(mem_dma & 0xFFFFFFFF);
        pp->cmd_slot[i].tbl_addr_hi = cpu_to_le32((mem_dma >> 16) >> 16);

        mem += AHCI_CMD_TBL_SZ;  /* 128B + PRDT 크기 */
        mem_dma += AHCI_CMD_TBL_SZ;
    }

    /* ── 2. FIS Receive 영역 할당 ── */
    rx_fis_sz = AHCI_RX_FIS_SZ;  /* 256 바이트 */
    pp->rx_fis = dmam_alloc_coherent(dev, rx_fis_sz,
                                      &pp->rx_fis_dma, GFP_KERNEL);
    memset(pp->rx_fis, 0, rx_fis_sz);

    /* ── 3. 포트 레지스터에 물리 주소 기록 ── */
    void __iomem *port_mmio = ahci_port_base(ap);
    writel(pp->cmd_slot_dma & 0xFFFFFFFF,
           port_mmio + PORT_LST_ADDR);
    writel(pp->cmd_slot_dma >> 32,
           port_mmio + PORT_LST_ADDR_HI);
    writel(pp->rx_fis_dma & 0xFFFFFFFF,
           port_mmio + PORT_FIS_ADDR);
    writel(pp->rx_fis_dma >> 32,
           port_mmio + PORT_FIS_ADDR_HI);

    /* 4. FIS Receive 활성화 → Command Engine 시작 */
    ahci_start_engine(ap);

    return 0;
}

AHCI 인터럽트 처리 상세

AHCI 인터럽트 핸들러는 GHC.IS(전역 인터럽트 상태) 레지스터를 읽어 어느 포트에서 인터럽트가 발생했는지 확인한 후, 각 포트의 PxIS를 읽어 이벤트 종류를 식별합니다.

/* drivers/ata/libahci.c — 인터럽트 핸들러 (간략화) */
static irqreturn_t ahci_single_level_irq_intr(
    int irq, void *dev_instance)
{
    struct ata_host *host = dev_instance;
    struct ahci_host_priv *hpriv = host->private_data;
    void __iomem *mmio = hpriv->mmio;
    u32 irq_stat, irq_masked;

    /* 1. 전역 인터럽트 상태 읽기 */
    irq_stat = readl(mmio + HOST_IRQ_STAT);
    if (!irq_stat)
        return IRQ_NONE;

    /* 구현된 포트만 마스킹 */
    irq_masked = irq_stat & hpriv->port_map;

    /* 2. 각 포트별 인터럽트 처리 */
    for (int i = 0; i < host->n_ports; i++) {
        if (!(irq_masked & (1 << i)))
            continue;

        struct ata_port *ap = host->ports[i];
        ahci_port_intr(ap);
    }

    /* 3. 전역 IS 클리어 (W1C: Write 1 to Clear) */
    writel(irq_stat, mmio + HOST_IRQ_STAT);

    return IRQ_HANDLED;
}

/* 포트 인터럽트 처리 */
static void ahci_port_intr(struct ata_port *ap)
{
    void __iomem *port_mmio = ahci_port_base(ap);
    u32 status = readl(port_mmio + PORT_IRQ_STAT);

    /* PxIS 클리어 */
    writel(status, port_mmio + PORT_IRQ_STAT);

    if (status & PORT_IRQ_ERROR) {
        /* 에러 인터럽트: EH(Error Handler) 트리거 */
        ahci_error_intr(ap, status);
    } else {
        /* 정상 완료: QC(Queued Command) 완료 처리 */
        ahci_handle_port_interrupt(ap, status);
        /* → PxCI/PxSACT 비교하여 완료된 태그 식별 */
        /* → ata_qc_complete() 호출 */
    }
}
인터럽트 코알레싱 (CCC): AHCI는 Command Completion Coalescing(CCC) 기능을 통해 여러 명령 완료를 하나의 인터럽트로 합칠 수 있습니다. CCC_CTL(0x14) 레지스터의 TV(Timeout Value)와 CC(Command Completions) 필드로 타이머 기반 또는 명령 수 기반 코알레싱을 설정합니다. 높은 IOPS 워크로드에서 인터럽트 오버헤드를 크게 줄일 수 있습니다.

MSI/MSI-X 설정

AHCI 컨트롤러는 전통적인 INTx 핀 인터럽트 외에 MSI(Message Signaled Interrupt) 또는 MSI-X를 지원할 수 있습니다. MSI-X가 가능한 경우 포트별 독립 인터럽트 벡터를 할당하여 멀티코어 시스템에서 인터럽트 처리를 병렬화할 수 있습니다.

AHCI 인터럽트 모드 비교
모드벡터 수특징성능
INTx (레거시)1 (공유)PCI 핀 기반, 다른 디바이스와 공유 가능낮음 (공유 인터럽트)
MSI1메시지 기반, 공유 없음, 에지 트리거중간
MSI-X포트당 1개포트별 독립 벡터, CPU 어피니티 설정 가능높음 (멀티코어 최적)
/* drivers/ata/ahci.c — MSI-X 설정 경로 (간략화) */
static int ahci_init_interrupts(struct pci_dev *pdev,
                                unsigned int n_ports,
                                struct ahci_host_priv *hpriv)
{
    int nvec;

    /* 1. MSI-X 시도 (포트별 벡터) */
    nvec = pci_alloc_irq_vectors(pdev, n_ports, n_ports, PCI_IRQ_MSIX);
    if (nvec == n_ports) {
        hpriv->flags |= AHCI_HFLAG_MULTI_MSI;
        return nvec;
    }

    /* 2. MSI 시도 (단일 벡터) */
    nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI);
    if (nvec == 1)
        return nvec;

    /* 3. INTx 폴백 */
    pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);
    return 1;
}
AHCI 포트당 DMA 메모리 할당 구조 dmam_alloc_coherent()로 할당된 DMA 일관(coherent) 메모리 영역 Port N (ahci_port_priv) Command List (CLB) 1024 바이트 32 x Command Header (각 32B) PxCLB/PxCLBU 레지스터 FIS Receive (FB) 256 바이트 DSFIS+PSFIS+RFIS+SDBFIS+UFIS PxFB/PxFBU 레지스터 Command Tables (태그당 1개) Tag 0 CFIS (64B) ACMD (16B) Rsvd (48B) PRDT[0..N] Tag 1 CFIS (64B) ACMD (16B) Rsvd (48B) PRDT[0..N] ... Tag 31 CFIS (64B) ACMD (16B) Rsvd (48B) PRDT[0..N] 각 Command Table = 128B(고정) + PRDT 크기(가변, sg 엔트리 수에 비례) AHCI_MAX_SG = 168 (리눅스 기본값) → 각 Table 최대 128 + 168*16 = 2816B CTBA 포트당 총 DMA 메모리 ≈ 1024(CLB) + 256(FB) + 32 × 2816(CT) = ~91 KB

커맨드 실행 경로

블록 레이어의 읽기 요청이 SATA 디스크에 도달하기까지의 전체 경로를 추적합니다.

1단계: 블록 요청 → SCSI 명령

/* blk-mq가 struct request를 생성하면 SCSI 레이어로 전달 */
scsi_queue_rq()
  → scsi_dispatch_cmd()
    → scsi_cmnd 구성 (CDB: READ_10 / READ_16)
    → host->hostt->queuecommand()  /* = ata_scsi_queuecmd */

2단계: SCSI → ATA 변환 (SAT)

/* drivers/ata/libata-scsi.c */
ata_scsi_queuecmd()
  → __ata_scsi_queuecmd()
    → ata_scsi_translate()
      → ata_scsi_rw_xlat()  /* READ_10 → taskfile 구성 */

/* SCSI CDB → ATA 명령 변환 예:
 * READ_10  → READ_DMA_EXT (non-NCQ) 또는 READ_FPDMA_QUEUED (NCQ)
 * WRITE_10 → WRITE_DMA_EXT 또는 WRITE_FPDMA_QUEUED
 * INQUIRY  → ATA IDENTIFY DEVICE
 * TEST_UNIT_READY → ATA CHECK POWER MODE
 */

3단계: ATA 명령 발행

/* drivers/ata/libata-core.c */
ata_qc_issue()
  → ap->ops->qc_prep(qc)   /* = ahci_qc_prep *//* Command Table에 CFIS(H2D FIS) 채우기 *//* scatter-gather 리스트 → PRDT 엔트리 채우기 *//* Command Header 구성 (CFL, W, PRDTL 설정) */
  → ap->ops->qc_issue(qc)  /* = ahci_qc_issue */
    → writel(1 << tag, port_mmio + PORT_CMD_ISSUE)  /* PxCI 쓰기 */

4단계: HBA 하드웨어 동작

PxCI에 비트가 설정되면 HBA는 자동으로:

  1. Command List에서 해당 Command Header를 DMA로 읽기
  2. CTBA가 가리키는 Command Table을 DMA로 읽기
  3. CFIS를 Register H2D FIS로 변환하여 디바이스에 전송
  4. 디바이스의 응답에 따라 PRDT 기반 데이터 DMA 전송 수행

5단계: 완료 인터럽트

/* drivers/ata/libata-core.c + ahci.c — 완료 경로 */
ahci_interrupt()                  /* MSI/MSI-X 또는 INTx */
  → ahci_port_intr()              /* PxIS 읽기 → 이벤트 식별 */
    → ahci_handle_port_interrupt()
      → ata_qc_complete()         /* QC 완료 처리 */
        → scsi_done()             /* SCSI 완료 콜백 */
          → blk_mq_end_request()  /* 블록 요청 완료 */
NCQ 커맨드의 경우: READ_FPDMA_QUEUED (0x60) 또는 WRITE_FPDMA_QUEUED (0x61)는 PxSACT(SActive) 레지스터에 태그 비트를 먼저 설정한 후 PxCI를 쓰며, 완료 시 Set Device Bits FIS(0xA1)로 여러 태그를 한 번에 완료 알림합니다.

SCSI-ATA Translation (SAT) 주요 변환 테이블

SCSI 명령 (CDB)ATA 명령비고
READ(6) / READ(10) / READ(16)READ DMA EXT / READ FPDMA QUEUEDNCQ 활성 시 FPDMA
WRITE(6) / WRITE(10) / WRITE(16)WRITE DMA EXT / WRITE FPDMA QUEUEDNCQ 활성 시 FPDMA
INQUIRYIDENTIFY DEVICEVPD 페이지 에뮬레이션
TEST UNIT READYCHECK POWER MODE디바이스 상태 확인
READ CAPACITY(10/16)IDENTIFY DEVICE섹터 수/크기 추출
MODE SENSE / MODE SELECTSET FEATURES / IDENTIFY캐싱, 전원 관리 등
SYNCHRONIZE CACHEFLUSH CACHE EXTWrite Cache 플러시
UNMAP (TRIM)DATA SET MANAGEMENTSSD TRIM 명령
WRITE SAMESCT Write Same패턴 쓰기
ATA PASS-THROUGH(12/16)(직접 전달)hdparm, smartctl 사용
REQUEST SENSE(에뮬레이션)libata가 내부적으로 생성
REPORT LUNS(에뮬레이션)단일 LUN 응답
/* drivers/ata/libata-scsi.c — SCSI CDB → ATA 변환 디스패치 */
static unsigned int ata_scsi_rw_xlat(
    struct ata_queued_cmd *qc)
{
    struct scsi_cmnd *scmd = qc->scsicmd;
    struct ata_taskfile *tf = &qc->tf;
    u64 block;
    u32 n_block;

    /* SCSI CDB에서 LBA와 섹터 수 추출 */
    scsi_10_lba_len(scmd->cmnd, &block, &n_block);

    /* NCQ 가능하면 FPDMA, 아니면 DMA EXT */
    if (ata_ncq_enabled(qc->dev)) {
        tf->protocol = ATA_PROT_NCQ;
        tf->command = (rw == READ) ?
            ATA_CMD_FPDMA_READ : ATA_CMD_FPDMA_WRITE;
        /* NCQ에서는 Sector Count 필드에 태그 번호 저장 */
        tf->nsect = qc->tag << 3;
    } else {
        tf->protocol = ATA_PROT_DMA;
        tf->command = (rw == READ) ?
            ATA_CMD_READ_EXT : ATA_CMD_WRITE_EXT;
    }
    /* LBA를 taskfile의 LBA Low/Mid/High + Exp에 채우기 */
    ata_tf_from_lba48(tf, block, n_block);
    return 0;
}

taskfile 레지스터 상세 매핑

ata_taskfile 구조체의 각 필드가 Register H2D FIS(27h)의 바이트에 매핑됩니다.

ata_taskfile 필드FIS 바이트H2D FIS 필드설명
tf->commandByte 2CommandATA 명령 코드 (0x60=READ FPDMA 등)
tf->featureByte 3Features (7:0)NCQ: 전송 섹터 수 하위
tf->lbalByte 4LBA Low (7:0)LBA [7:0]
tf->lbamByte 5LBA Mid (7:0)LBA [15:8]
tf->lbahByte 6LBA High (7:0)LBA [23:16]
tf->deviceByte 7Device비트6: LBA, 비트4: DEV
tf->hob_lbalByte 8LBA Low (15:8)LBA [31:24] (48-bit)
tf->hob_lbamByte 9LBA Mid (15:8)LBA [39:32] (48-bit)
tf->hob_lbahByte 10LBA High (15:8)LBA [47:40] (48-bit)
tf->hob_featureByte 11Features (15:8)NCQ: 전송 섹터 수 상위
tf->nsectByte 12Count (7:0)NCQ: 태그(비트 7:3)
tf->hob_nsectByte 13Count (15:8)확장 섹터 카운트
tf->ctlByte 15ControlnIEN(1), SRST(2)
NCQ 필드 재활용: NCQ(FPDMA)에서는 Sector Count(byte 12) 상위 5비트에 태그 번호, Features(byte 3,11)에 실제 섹터 수가 들어갑니다. ata_scsi_rw_xlat()에서 자동 처리됩니다.

DMA vs PIO 전송 모드 선택

libata는 디바이스 능력과 명령 유형에 따라 전송 모드를 자동 선택합니다.

/* drivers/ata/libata-core.c — PIO IORDY 판단 */
unsigned int ata_pio_need_iordy(const struct ata_device *adev)
{
    if (adev->pio_mode > XFER_PIO_2)
        return 1;  /* PIO 3+: IORDY 필요 */
    if (ata_id_is_cfa(adev->id))
        return 1;  /* CF 카드: 항상 */
    return 0;
}

/* 전송 모드 선택: UDMA > MWDMA > PIO */
static int ata_dev_set_mode(struct ata_device *dev)
{
    unsigned int udma_mask, dma_mask, pio_mask;
    ata_id_to_dma_mode(dev->id, &dma_mask, &udma_mask);
    pio_mask = ata_id_pio_modes(dev->id);

    if (udma_mask)
        dev->xfer_mode = fls(udma_mask) - 1 + XFER_UDMA_0;
    else if (dma_mask)
        dev->xfer_mode = fls(dma_mask) - 1 + XFER_MW_DMA_0;
    else
        dev->xfer_mode = fls(pio_mask) - 1 + XFER_PIO_0;

    ata_dev_set_feature(dev, SETFEATURES_XFER, dev->xfer_mode);
    return 0;
}
PIO 사용 상황: (1) IDENTIFY DEVICE 등 초기 식별, (2) EH 복구 중 READ LOG EXT, (3) DMA 설정 실패 시 폴백, (4) 구형 DMA 미지원 디바이스.

scatter-gather → PRDT 변환

/* drivers/ata/ahci.c — SG → PRDT 변환 */
static unsigned int ahci_fill_sg(struct ata_queued_cmd *qc)
{
    struct ahci_sg *ahci_sg = pp->cmd_tbl + AHCI_CMD_TBL_HDR_SZ;
    struct scatterlist *sg;
    unsigned int si;

    for_each_sg(qc->sg, sg, qc->n_elem, si) {
        dma_addr_t addr = sg_dma_address(sg);
        u32 sg_len = sg_dma_len(sg);
        ahci_sg[si].addr = cpu_to_le32(addr & 0xFFFFFFFF);
        ahci_sg[si].addr_hi = cpu_to_le32((addr >> 32) & 0xFFFFFFFF);
        /* DBC: 바이트 카운트 - 1, 최대 4MB per entry */
        ahci_sg[si].flags_size = cpu_to_le32(sg_len - 1);
    }
    return si;  /* → Command Header PRDTL */
}
PRDT 정렬 제약: DBA는 워드(2바이트) 정렬, DBC는 짝수 필수. 위반 시 Task File Error 발생.
SATA 커맨드 실행 전체 타임라인 Host CPU AHCI HBA SATA Device 1. CFIS/PRDT 작성 ahci_qc_prep() 2. PxCI 쓰기 writel(1<<tag, PxCI) 3. Cmd Header DMA 4. Cmd Table DMA 5. Register H2D FIS 6. DMA Setup FIS 7. DMA 전송 (PRDT) 8. SDB/D2H FIS 9. MSI 인터럽트 ata_qc_complete() scsi_done() SW ~2us + HBA ~2us + PHY ~0.5us + Device NCQ: SDB FIS로 다중 태그 동시 완료

NCQ 심화

NCQ(Native Command Queuing)는 SATA 디바이스가 여러 명령을 동시에 받아 내부적으로 최적 순서로 재배열하여 처리하는 기술입니다. 특히 HDD에서 헤드 탐색 거리를 최소화하고, SSD에서 내부 병렬성을 활용하는 데 효과적입니다.

NCQ 동작 흐름 (32-Tag 병렬 처리) Host (AHCI HBA) PxSACT (SActive Register) 비트 0~31: 각 태그 활성 상태 커맨드 발행 순서: Tag 0 Tag 3 Tag 7 Tag 1 Tag 5 Tag 12 PxCI = 0x...8B (태그 비트맵) 완료: SDB FIS 수신 SActive 비트 자동 클리어 DMA Setup FIS (각 태그별) Set Device Bits FIS (다중 태그 완료) SATA Device (HDD/SSD) 내부 큐 (최대 32개) 명령 재배열 (Reordering) 발행순: Tag0 → Tag3 → Tag7 실행순: Tag3 → Tag0 → Tag7 (LBA 근접도 기반 최적화) First Party DMA: 디바이스가 DMA Setup FIS로 호스트 메모리에 직접 DMA 전송 SDB FIS: SActive=0x89 Tag 0,3,7 동시 완료 통보 NCQ = FPDMA 명령 (READ/WRITE_FPDMA_QUEUED)

NCQ 관련 ATA 명령어

명령어코드설명
READ FPDMA QUEUED0x60NCQ 읽기 (최대 65536 섹터)
WRITE FPDMA QUEUED0x61NCQ 쓰기 (최대 65536 섹터)
NCQ NON DATA0x63NCQ 비데이터 명령 (SATA 3.1+)
SEND FPDMA QUEUED0x64NCQ 호스트→디바이스 데이터
RECEIVE FPDMA QUEUED0x65NCQ 디바이스→호스트 데이터

NCQ vs Non-NCQ 성능 비교

NCQ 활성화/비활성화에 따른 성능 차이 (SATA SSD 기준)
메트릭NCQ 비활성 (depth=1)NCQ 활성 (depth=32)향상률
랜덤 4K 읽기 IOPS~10,000~95,000~9.5배
랜덤 4K 쓰기 IOPS~8,000~85,000~10.6배
순차 읽기 MB/s~540~550미미
순차 쓰기 MB/s~510~520미미
혼합 70/30 IOPS~7,000~70,000~10배
HDD에서의 NCQ 효과: HDD는 NCQ의 명령 재배열이 특히 효과적입니다. 디스크 헤드의 탐색 경로를 최적화(엘리베이터 알고리즘)하여 랜덤 읽기 IOPS를 30-50% 향상시킬 수 있습니다. 다만 HDD의 절대적인 IOPS가 낮아(~100-200) 체감 차이는 SSD만큼 크지 않습니다.

NCQ TRIM (Data Set Management)

SATA 3.1부터 TRIM 명령도 NCQ 프로토콜로 발행할 수 있습니다 (Queued TRIM). 기존 non-queued TRIM은 다른 I/O와 병렬 실행이 불가능해 I/O 지연이 발생했지만, NCQ TRIM은 읽기/쓰기 명령과 함께 큐에 넣어 동시에 처리할 수 있습니다.

# Queued TRIM 지원 확인
hdparm -I /dev/sda | grep -i "queued trim"
#    * Queued TRIM supported

# 커널의 NCQ TRIM 사용 여부 확인
dmesg | grep "NCQ Send/Recv"

NCQ 구현 핵심

/* libata-core.c — NCQ 태그 할당 */
static unsigned int ata_qc_new(struct ata_port *ap)
{
    unsigned int tag;

    /* blk-mq 태그를 직접 사용 (qc->tag = scsi_cmnd->request->tag) */
    /* NCQ: 태그 0~31, Non-NCQ: 내부 태그 ATA_TAG_INTERNAL */
    tag = find_first_zero_bit(&ap->qc_active, ATA_MAX_QUEUE);
    return tag;
}

/* ahci.c — NCQ 커맨드 발행 */
static unsigned int ahci_qc_issue(struct ata_queued_cmd *qc)
{
    struct ata_port *ap = qc->ap;
    void __iomem *port_mmio = ahci_port_base(ap);

    if (qc->tf.protocol == ATA_PROT_NCQ) {
        /* NCQ: PxSACT에 태그 비트 설정 */
        writel(1 << qc->tag, port_mmio + PORT_SCR_ACT);
    }
    /* PxCI에 태그 비트 설정 → HBA가 커맨드 페치 */
    writel(1 << qc->tag, port_mmio + PORT_CMD_ISSUE);
    return 0;
}
NCQ Priority: SATA 3.1부터 NCQ 명령에 우선순위(높음/낮음)를 부여할 수 있습니다. 리눅스 커널에서는 ata_deviceflagsATA_DFLAG_NCQ_PRIO가 설정되면 sysfs를 통해 우선순위를 제어할 수 있습니다.
NCQ Autosense: NCQ 명령 실패 시 디바이스가 자동으로 sense data를 반환하는 기능입니다. 기존에는 NCQ 에러 발생 시 Request Sense 명령을 별도로 보내야 했지만, Autosense 지원 디바이스는 Set Device Bits FIS에 sense data를 포함합니다.

NCQ 에러 로그 분석

NCQ 명령 실패 시 디바이스는 NCQ Error Log(Log Page 10h)에 실패 정보를 기록합니다. libata는 이 로그를 READ LOG EXT(PIO) 명령으로 읽어 실패한 태그와 에러 원인을 파악합니다.

/* drivers/ata/libata-eh.c — NCQ 에러 로그 분석 */
static void ata_eh_analyze_ncq_error(struct ata_link *link)
{
    struct ata_port *ap = link->ap;
    u8 *buf = ap->ncq_err_page;  /* 512바이트 버퍼 */
    u8 tag;

    /* READ LOG EXT, Log Page 10h (NCQ Command Error) */
    ata_read_log_page(ap->dev, ATA_LOG_NCQ_ERR, 0, buf, 1);

    /* Byte 0: NQ 비트(7) + 실패 태그(4:0) */
    tag = buf[0] & 0x1F;

    /* Byte 2: Status, Byte 3: Error 레지스터 */
    link->eh_info.err_mask |= AC_ERR_DEV;
    /* 해당 태그의 QC에 에러 설정 → 재시도 대상 */
    qc = __ata_qc_from_tag(ap, tag);
    qc->err_mask |= AC_ERR_DEV;
}
# smartctl로 NCQ 에러 로그 확인
smartctl -l sataphy /dev/sda     # SATA PHY 이벤트 카운터
smartctl -l ncq /dev/sda         # NCQ Command Error 로그
smartctl -l devstat /dev/sda     # Device Statistics (GP Log 04h)

NCQ vs TCQ (Tagged Command Queuing)

항목TCQ (SCSI/PATA)NCQ (SATA)
표준ATA/ATAPI-4 (PATA)SATA II (3Gbps+)
최대 큐 깊이3232
FIS 유형DMA QUEUED (C8h/CCh)FPDMA (60h/61h)
완료 통지SERVICE 명령 폴링SDB FIS (자동)
DMA 주도호스트 주도 (Third Party DMA)디바이스 주도 (First Party DMA)
채택거의 미사용 (호환성 문제)사실상 표준

NCQ Non-Data 명령

SATA 3.1부터 NCQ NON DATA(0x63) 명령으로 데이터 전송 없이 NCQ 큐에 제어 명령을 넣을 수 있습니다. 일반 NCQ I/O와 함께 큐잉되어 순서 보장이 가능합니다.

서브커맨드Feature 값설명
Abort NCQ Queue0x00지정 태그 또는 전체 NCQ 큐 취소
Deadline Handling0x01특정 태그에 완료 기한 설정
Hybrid Demote By Size0x02하이브리드 캐시에서 데이터 강등
Hybrid Evict0x03하이브리드 캐시에서 데이터 퇴출
Set Features0x05NCQ 큐 내에서 SET FEATURES 실행
Zero EXT0x06지정 LBA 범위를 0으로 초기화

NCQ Streaming

NCQ Streaming은 연속 미디어 워크로드(비디오 녹화/재생 등)를 위한 기능으로, 디바이스에게 특정 NCQ 명령이 스트리밍 데이터임을 알려줍니다. 디바이스는 스트리밍 명령의 완료 시간을 보장하기 위해 내부 스케줄링을 조정합니다.

/* NCQ Streaming은 READ/WRITE FPDMA에서
 * Auxiliary 필드(ICC: Isochronous Command Completion)를 사용
 * Feature(15:8)의 Streaming 비트 설정 */
if (qc->flags & ATA_QCFLAG_STREAMING) {
    tf->auxiliary |= ATA_AUX_STREAM;
    /* Streaming ID와 완료 기한(deadline) 설정 */
    tf->auxiliary |= stream_tag << 16;
}

M.2 폼 팩터

M.2(NGFF, Next Generation Form Factor)는 mSATA를 대체하는 소형 폼 팩터로, SATA와 PCIe(NVMe) 두 가지 인터페이스를 모두 지원합니다. 키(Key) 노치 위치로 지원하는 인터페이스를 구별합니다.

M.2 키 (Key) 구분과 지원 인터페이스 B-key 노치 핀 12-19 위치 지원 인터페이스: SATA PCIe x2 USB 3.0 Socket 2 (B-key) 드물게 단독 사용 M-key 노치 핀 59-66 위치 지원 인터페이스: PCIe x4 (NVMe) SATA (일부) Socket 3 (M-key) NVMe SSD 전용 B+M key B M 양쪽 노치 지원 인터페이스: SATA (대부분) PCIe x2 B+M 소켓 호환 SATA M.2 SSD 대부분

M.2 크기 규격

코드명폭 (mm)길이 (mm)주요 용도
22302230WiFi/BT, 소형 SSD
22422242SATA SSD, 일부 NVMe
22602260SATA/NVMe SSD
22802280가장 일반적인 NVMe/SATA SSD
2211022110엔터프라이즈 NVMe SSD
M.2 SATA vs M.2 NVMe 혼동 주의: M.2 소켓이 물리적으로 맞더라도, SATA M.2 SSD를 NVMe 전용 소켓에 꽂으면 인식되지 않습니다. 반대도 마찬가지입니다. 반드시 메인보드 매뉴얼에서 해당 M.2 슬롯이 SATA, NVMe, 또는 둘 다 지원하는지 확인하세요.
# M.2 SATA SSD 확인: TRAN이 'sata'이면 SATA M.2
lsblk -d -o NAME,TRAN,SIZE,MODEL
# NVMe는 TRAN이 비어있고 /dev/nvme* 경로 사용
ls /dev/nvme*
# M.2 SATA는 일반 SATA와 동일하게 /dev/sd* 사용
ls /dev/sd*

M.2 소켓 핀 배치 상세

M.2 커넥터는 총 75핀으로, 소켓 유형(Key)에 따라 각 핀에 할당되는 신호가 다릅니다.

핀 범위Socket 2 (B-key)Socket 3 (M-key)비고
1-3GNDGND공통
4-53.3V3.3V전원
12-19Key B 노치USB 3.0 / HSICB-key 삽입 방지
20-23SATA B- / B+ReservedSATA 수신 쌍
29-32PCIe x2 RxPCIe x4 Rx[0]PCIe 수신
35-38PCIe x2 TxPCIe x4 Tx[0]PCIe 송신
39-40I2C SDA/SCLI2C SDA/SCL디바이스 관리
41-42SATA A- / A+ReservedSATA 송신 쌍
47-50PCIe x2 Ref ClockPCIe x4 Ref Clock기준 클럭 100MHz
49-54ReservedPCIe x4 Rx/Tx[2,3]M-key 추가 레인
56-58ReservedPCIe x4 Rx/Tx[1]M-key 레인 1
59-66PCIe Ref ClockKey M 노치M-key 삽입 방지
67-70UIM (SIM)PCIe x4 Tx[3]WWAN/PCIe
71-75GND / 3.3VGND / 3.3V전원/접지

M.2 열 관리

M.2 SSD, 특히 NVMe 모델은 소형 폼팩터에서 높은 발열이 문제됩니다. SATA M.2 SSD는 상대적으로 발열이 적지만(~45°C), NVMe는 80°C 이상까지 올라갈 수 있습니다.

# SMART로 SSD 온도 모니터링
smartctl -A /dev/sda | grep -i temperature
# 194 Temperature_Celsius     0x0022   067   052   000    Old_age   Always       -       33

# NVMe 온도 확인
smartctl -A /dev/nvme0 | grep -i temperature
# Temperature:                        38 Celsius
# Warning  Comp. Temp. Threshold:     85 Celsius
# Critical Comp. Temp. Threshold:     90 Celsius

# 실시간 온도 모니터링
watch -n1 "smartctl -A /dev/sda | grep Temperature"
M.2 방열 팁: (1) 메인보드 제공 M.2 방열판을 반드시 사용하세요. (2) 써멀 패드는 SSD 컨트롤러 칩에 밀착되도록 합니다. (3) SATA M.2 SSD는 써멀 스로틀링 위험이 낮아 별도 방열판 없이도 안정적입니다. (4) 양면 NAND 칩 SSD는 하판 방열도 고려하세요.

mSATA에서 M.2로의 전환

mSATA(Mini-SATA)는 Mini PCIe와 동일한 물리 커넥터를 사용하지만 SATA 신호를 전달하는 규격이었습니다. M.2로 대체되면서 다음과 같은 차이가 있습니다.

항목mSATAM.2 SATA
커넥터Mini PCIe 52핀M.2 75핀
크기30 x 50.95 mm (고정)22mm 폭, 길이 가변
인터페이스SATA 전용SATA + PCIe 겸용
최대 속도600 MB/s (SATA III)600 MB/s (SATA) / 32 Gbps (PCIe 4.0 x4)
현재 상태단종 추세현행 표준
호환성 주의: mSATA와 M.2는 물리적으로 완전히 다른 커넥터입니다. 어댑터 없이 호환되지 않습니다. mSATA에서 M.2로 마이그레이션 시 반드시 M.2 SATA(B+M key) SSD를 선택해야 합니다.

메인보드 M.2 슬롯 식별 방법

메인보드의 M.2 슬롯이 SATA, NVMe, 또는 둘 다 지원하는지 확인하는 단계별 방법입니다.

# 1단계: M.2 슬롯의 Key 노치 확인 (물리적)
# - M-key만: 대부분 NVMe 전용
# - B+M key: SATA 가능, 일부 NVMe도 가능

# 2단계: lspci로 연결된 컨트롤러 확인
lspci -v | grep -A5 "SATA controller"
# AHCI 컨트롤러에 연결된 M.2 = SATA 지원

lspci -v | grep -A5 "Non-Volatile memory"
# NVMe 컨트롤러 = NVMe 지원

# 3단계: dmidecode로 슬롯 정보 확인 (root 필요)
dmidecode -t slot | grep -A10 "M.2"

# 4단계: SATA M.2 SSD가 인식되는지 확인
dmesg | grep -i "ata.*SATA link"
lsblk -d -o NAME,TRAN,MODEL | grep sata

# 5단계: NVMe M.2 SSD가 인식되는지 확인
nvme list

SATA vs NVMe 비교

SATA vs NVMe 데이터 경로 비교 SATA (AHCI) Application VFS Block Layer (blk-mq) SCSI Mid Layer libata (SAT 변환) AHCI Driver SATA Device 큐: 1 x 32 태그 프로토콜 변환: 2회 NVMe Application VFS Block Layer (blk-mq) (SCSI 레이어 없음) NVMe Driver (직접) NVMe Device 큐: 65535 x 65536 태그 프로토콜 변환: 0회
SATA vs NVMe 상세 비교
항목SATA (AHCI)NVMe
버스SATA 전용 링크PCIe 레인 (x1~x4)
최대 대역폭600 MB/s (SATA III)~7 GB/s (PCIe 4.0 x4)
큐 모델1 큐 x 32 태그65535 큐 x 65536 태그
커맨드 오버헤드높음 (SCSI/SAT 변환)낮음 (직접 명령)
인터럽트MSI/INTx (1~2개)MSI-X (CPU 코어당)
랜덤 4K IOPS~100K~1M+
레이턴시~100 us~10 us
CPU 효율낮음높음
핫 플러그AHCI 지원PCIe Surprise Removal
전원 관리ALPM/DIPM/DevSlpAPST (Autonomous PS)
드라이버 소스drivers/ata/drivers/nvme/
디바이스 이름/dev/sdX/dev/nvmeNnP
SATA SSD의 장점: NVMe가 성능에서 압도적이지만, SATA SSD는 여전히 유용합니다. 레거시 시스템 호환, 저렴한 가격, 충분한 순차 읽기 성능(~550 MB/s), 낮은 발열, 그리고 기존 2.5인치 폼 팩터 호환성이 장점입니다.

프로토콜 오버헤드 정량 비교

SATA와 NVMe의 커맨드 처리 오버헤드를 단계별로 분해하면 성능 차이의 원인을 정확히 이해할 수 있습니다.

단계SATA (AHCI) 지연NVMe 지연비고
명령 준비~1.5 us (SCSI→SAT 변환)~0.5 us (SQE 직접 작성)SATA는 2회 프로토콜 변환
명령 발행~1.0 us (MMIO: PxCI 쓰기)~0.3 us (Doorbell 쓰기)단일 MMIO 쓰기
HBA/컨트롤러 처리~2.0 us (Cmd Header/Table DMA)~0.5 us (SQE 직접 읽기)SATA: 2회 DMA 필요
PHY/버스 전송~0.5 us (SATA FIS)~0.2 us (PCIe TLP)SATA: FIS 오버헤드
완료 인터럽트~1.0 us (MSI, 단일 벡터)~0.5 us (MSI-X, 코어별)NVMe: 코어 로컬 인터럽트
총 오버헤드~6 us~2 usSATA는 3배 오버헤드

멀티코어 확장성

NVMe가 고성능 워크로드에서 압도적인 이유는 멀티코어 확장성에 있습니다. AHCI는 단일 커맨드 큐를 공유하므로 CPU 코어 간 lock contention이 발생합니다.

CPU 코어 수에 따른 IOPS 확장 (랜덤 4K 읽기, 이론적 최대)
CPU 코어 수SATA AHCI IOPSNVMe IOPSSATA 병목
1~95K~200K대역폭
2~95K~400K큐 Lock
4~95K~750K큐 Lock
8~90K (감소)~1.2M큐 Lock + IRQ 집중
16~85K (감소)~1.8M큐 Lock + IRQ 집중
SATA의 멀티코어 한계: AHCI는 포트당 1개 큐(32 슬롯)만 지원하므로, 모든 CPU 코어가 하나의 큐에 대해 lock을 경쟁합니다. NVMe는 CPU 코어당 독립 큐(최대 65535개)를 가져 lock-free에 가까운 동작이 가능합니다.

마이그레이션 가이드: SATA → NVMe

기존 SATA 기반 시스템을 NVMe로 마이그레이션할 때 확인해야 할 실무 체크리스트입니다.

항목SATA (이전)NVMe (이후)필요 작업
디바이스 이름/dev/sda, /dev/sdb/dev/nvme0n1fstab, 스크립트 수정
파티션 이름/dev/sda1/dev/nvme0n1p1grub.cfg, fstab UUID 권장
커널 드라이버ahci (libata)nvmeinitramfs에 nvme 포함 확인
부트로더AHCI/IDE 모드NVMe 부팅 지원 필수BIOS/UEFI에서 NVMe boot 활성
TRIMhdparm / fstrimfstrim (동일)fstab에 discard 마운트 옵션
전원 관리ALPM/DIPMAPSTnvme_core.default_ps_max_latency_us 조정
모니터링smartctl /dev/sdasmartctl /dev/nvme0smartmontools 업데이트
# dd 기반 디스크 복제 (SATA → NVMe)
# 주의: 대상 디스크의 모든 데이터가 삭제됩니다!
dd if=/dev/sda of=/dev/nvme0n1 bs=64M status=progress conv=sync,noerror

# 파티션 크기 조정 (NVMe가 더 큰 경우)
parted /dev/nvme0n1 resizepart 2 100%
resize2fs /dev/nvme0n1p2

# fstab을 UUID 기반으로 변경 (디바이스명 변경에 대비)
blkid /dev/nvme0n1p2
# UUID=xxxx-xxxx  /  ext4  defaults,discard  0  1

# initramfs 재생성 (nvme 드라이버 포함)
update-initramfs -u   # Debian/Ubuntu
dracut --force        # RHEL/Fedora

# GRUB 업데이트
update-grub

SATA 전원 관리

SATA PHY 전원 상태 머신 Active PHY 동작 중 전력: 최대 Partial 복귀: ~10 us 절전: 약간 Slumber 복귀: ~10 ms 절전: 중간 DevSlp 복귀: ~20 ms 절전: 최대 (~5mW) COMWAKE deeper ALPM: Host-Initiated | DIPM: Device-Initiated | DevSlp: DEVSLP 신호 기반

전원 관리 정책 (sysfs)

# 현재 정책 확인
cat /sys/class/scsi_host/host0/link_power_management_policy
# 정책 변경
echo "med_power_with_dipm" > /sys/class/scsi_host/host0/link_power_management_policy
정책ALPMDIPM설명
max_performance비활성비활성항상 Active 상태 유지
medium_powerPartial 허용비활성호스트만 Partial 전환 가능
med_power_with_dipmPartial 허용활성디바이스도 절전 전환 가능 (권장)
min_powerSlumber 허용활성최대 절전, 레이턴시 증가 가능
/* drivers/ata/libata-core.c — ALPM 정책 설정 */
static ssize_t ata_scsi_lpm_store(struct device *dev,
    struct device_attribute *attr,
    const char *buf, size_t count)
{
    struct Scsi_Host *shost = class_to_shost(dev);
    struct ata_port *ap = ata_shost_to_port(shost);

    /* PxSCTL의 IPM 필드를 설정하여 허용 상태 제어 */
    /* PxCMD의 ALPE, ASP 비트로 자동 전환 활성화 */
    ata_dev_set_feature(dev, SETFEATURES_SATA_ENABLE,
                        SATA_DIPM);
    return count;
}

Runtime PM 통합

# Runtime PM 상태 확인
cat /sys/class/scsi_host/host0/device/power/runtime_status
# active / suspended / suspending / resuming

# Runtime PM 제어
cat /sys/class/scsi_host/host0/device/power/control
# auto / on
echo "auto" > /sys/class/scsi_host/host0/device/power/control

# Runtime PM 자동 대기 시간 (ms)
cat /sys/class/scsi_host/host0/device/power/autosuspend_delay_ms
# 기본: 15000 (15초)

# AHCI 포트의 Runtime PM
# ALPM 정책과 Runtime PM이 연동됨:
# - max_performance: Runtime PM 비활성
# - med_power_with_dipm: 15초 후 Partial 전환
# - min_power: 즉시 Slumber 전환
DevSlp (Device Sleep): DevSlp는 SATA 링크 전원을 완전히 차단하고 별도의 DEVSLP 핀으로 웨이크업하는 가장 깊은 절전 상태입니다. 전력 소모를 ~5mW까지 줄일 수 있지만, 복귀에 ~20ms가 걸립니다. 하드웨어(메인보드 + 디바이스)와 BIOS/UEFI 모두 지원해야 합니다.

PHY 전원 상태 전환 타이밍 상세

전환전환 시간트리거비고
Active → Partial~2 usALPM 또는 DIPMPHY 부분 비활성, PLL 유지
Partial → Active~10 us새 명령 또는 COMWAKE빠른 복귀, 사용자 체감 없음
Active → Slumber~2 usALPM min_power 정책PHY + PLL 비활성
Slumber → Active~10 ms새 명령 또는 COMWAKEPLL 재잠금 필요, 체감 지연
Slumber → DevSlp가변 (~5 ms)DEVSLP 핀 신호HW 지원 필수
DevSlp → Active~20 msDEVSLP 핀 해제 + COMRESET가장 긴 복귀 시간

ALPM 구현 상세

ALPM(Aggressive Link Power Management)은 AHCI 포트 레지스터를 통해 제어됩니다.

/* drivers/ata/libata-core.c — ALPM 활성화 */
static int ata_dev_enable_pm(struct ata_device *dev,
    enum link_pm policy)
{
    struct ata_port *ap = dev->link->ap;
    u32 scontrol, cmd;

    /* PxSCTL.IPM: 허용할 전원 상태 설정
     * 비트 8-11:
     *   0x0 = 제한 없음 (모든 상태 허용)
     *   0x1 = Partial 진입 금지
     *   0x2 = Slumber 진입 금지
     *   0x3 = Partial + Slumber 진입 금지 */
    scontrol = readl(port_mmio + PORT_SCR_CTL);
    scontrol &= ~(0xF << 8);
    if (policy == MAX_PERFORMANCE)
        scontrol |= (0x3 << 8);  /* 절전 금지 */
    writel(scontrol, port_mmio + PORT_SCR_CTL);

    /* PxCMD.ALPE: Aggressive LPM Enable
     * PxCMD.ASP: Aggressive Slumber/Partial
     *   ASP=0 → Partial 선호, ASP=1 → Slumber 선호 */
    cmd = readl(port_mmio + PORT_CMD);
    if (policy != MAX_PERFORMANCE) {
        cmd |= PORT_CMD_ALPE;   /* ALPM 활성화 */
        if (policy == MIN_POWER)
            cmd |= PORT_CMD_ASP;  /* Slumber 선호 */
    } else {
        cmd &= ~PORT_CMD_ALPE;
    }
    writel(cmd, port_mmio + PORT_CMD);

    /* DIPM: 디바이스 측에서 절전 전환 허용 */
    if (policy == MED_POWER_WITH_DIPM || policy == MIN_POWER)
        ata_dev_set_feature(dev, SETFEATURES_SATA_ENABLE, SATA_DIPM);

    return 0;
}

SATA 전원 관리와 배터리 수명

노트북 환경에서 SATA 전원 관리 정책은 배터리 수명에 상당한 영향을 미칩니다.

전원 상태HDD 소비 전력SSD 소비 전력적합한 상황
Active (R/W)~6W (7200RPM)~3WI/O 진행 중
Active (Idle)~4W~1.5WI/O 대기
Partial~3W~1W짧은 유휴 (수 ms)
Slumber~1.5W~0.5W긴 유휴 (수 초 이상)
DevSlp지원 드묾~5mW장기 유휴 (분 단위)
SATA 전원 상태 전환과 전력 소모 3W 1.5W 0.5W 5mW 전력 시간 Active ~2us Partial ~2us Slumber ~5ms DevSlp ~20ms Active

SATA 핫 플러그

AHCI는 SATA 디바이스의 런타임 삽입/제거(핫 플러그)를 지원합니다. 핫 플러그 이벤트는 PHY 상태 변화로 감지되며, 포트 인터럽트를 통해 드라이버에 알려집니다.

핫 플러그 이벤트 감지

레지스터/비트설명
PxSERR.DIAG.XDevice Exchanged — PHY 상태가 변경됨 (디바이스 삽입/제거)
PxSERR.DIAG.NPhyRdy Change — PHY가 Ready 상태로 전환됨
PxIS.PCSPort Connect Status Change — 핫 플러그 이벤트 인터럽트
PxIS.PRCSPhyRdy Change Status — PHY 상태 변화 인터럽트
PxSSTS.DETDevice Detection — 0=없음, 1=감지중, 3=정상 연결, 4=오프라인

핫 플러그 처리 경로

/* 디바이스 삽입 시 */
ahci_port_intr()
  → PxIS.PCS 또는 PxIS.PRCS 감지
  → ata_port_schedule_eh(ap)    /* EH 스케줄 */
    → ata_eh_link_autopsy()     /* 상태 분석 */
    → ata_eh_recover()          /* 포트 리셋 + 디바이스 식별 */
      → ata_eh_reset()          /* COMRESET 수행 */
      → ata_eh_revalidate_and_attach()
        → ata_dev_read_id()     /* IDENTIFY DEVICE */
        → ata_dev_configure()   /* 디바이스 설정 */
        → scsi_add_device()     /* SCSI 디바이스 등록 */

/* 디바이스 제거 시 */
ahci_port_intr()
  → PxSSTS.DET == 0 감지
  → ata_port_schedule_eh(ap)
    → ata_eh_link_autopsy()
    → ata_eh_recover()
      → ata_dev_disable()       /* 디바이스 비활성화 */
      → scsi_remove_device()    /* SCSI 디바이스 제거 */

핫 플러그 dmesg 출력 예

# 디바이스 삽입
[  123.456789] ata3: SATA link up 6.0 Gbps (SStatus 133 SControl 300)
[  123.567890] ata3.00: ATA-10: Samsung SSD 870 EVO 500GB, SVT02B6Q, max UDMA/133
[  123.567891] ata3.00: 976773168 sectors, multi 1: LBA48 NCQ (depth 32)
[  123.678901] ata3.00: configured for UDMA/133
[  123.678902] scsi 2:0:0:0: Direct-Access     ATA      Samsung SSD 870  02B6 PQ: 0 ANSI: 5
[  123.789012] sd 2:0:0:0: [sdc] 976773168 512-byte logical blocks: (500 GB/466 GiB)

# 디바이스 제거
[  456.789012] ata3: SATA link down (SStatus 0 SControl 300)
[  456.789013] ata3.00: disabled

udev 규칙과 핫 플러그 자동화

# /etc/udev/rules.d/99-sata-hotplug.rules
# SATA 디바이스 삽입 시 자동 마운트
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z]1", \
  ENV{ID_BUS}=="ata", \
  RUN+="/usr/bin/systemd-mount --no-block /dev/%k /mnt/hotplug-%k"

# SATA 디바이스 제거 시 자동 언마운트
ACTION=="remove", SUBSYSTEM=="block", KERNEL=="sd[a-z]1", \
  ENV{ID_BUS}=="ata", \
  RUN+="/usr/bin/systemd-umount /mnt/hotplug-%k"

# udev 규칙 리로드
udevadm control --reload-rules
udevadm trigger

소프트웨어적 핫 플러그 (안전 제거)

# 방법 1: SCSI 디바이스 제거
echo 1 > /sys/block/sdc/device/delete

# 방법 2: SCSI 호스트 스캔으로 재감지
echo "- - -" > /sys/class/scsi_host/host2/scan

# 방법 3: ATA 포트 리셋으로 재감지
echo 1 > /sys/class/ata_port/ata3/device/rescan

# 안전 제거 순서
# 1. 마운트 해제
umount /mnt/data
# 2. 디바이스 삭제
echo 1 > /sys/block/sdc/device/delete
# 3. 물리적 분리
Surprise Removal vs Orderly Removal: Orderly removalecho 1 > /sys/block/sdX/device/delete로 먼저 소프트웨어적으로 제거한 후 물리적으로 분리하는 안전한 방법입니다. Surprise removal은 예고 없이 물리적으로 분리하는 것으로, 미완료 I/O가 있으면 데이터 손실이 발생할 수 있습니다.

AHCI 핫 플러그 레지스터 흐름

디바이스 삽입 시 AHCI 하드웨어 레벨에서 일어나는 레지스터 변화 순서입니다.

/* 1. PHY 상태 변화 감지 */
PxSERR.DIAG.X = 1    /* Device Exchanged 비트 설정 */
PxSERR.DIAG.N = 1    /* PhyRdy Change 비트 설정 */

/* 2. 포트 인터럽트 상태에 반영 */
PxIS.PCS = 1          /* Port Connect Status Change */
PxIS.PRCS = 1         /* PhyRdy Change Status */

/* 3. 글로벌 인터럽트 상태 업데이트 */
GHC.IS |= (1 << port) /* 해당 포트 비트 설정 */

/* 4. MSI/INTx 인터럽트 발생 → 드라이버 핸들러 진입 */
ahci_single_level_irq_intr()
  → ahci_port_intr()
    → irq_stat = readl(port_mmio + PORT_IRQ_STAT)
    → /* PCS/PRCS 비트 확인 */
    → writel(PxSERR, port_mmio + PORT_SCR_ERR)  /* SError 클리어 */
    → writel(irq_stat, port_mmio + PORT_IRQ_STAT)
    → ata_port_schedule_eh(ap)  /* EH 스케줄링 */

핫 플러그 안정성 개선

핫 스왑 베이 구성 시 권장사항: (1) Staggered Spin-up 활성화: CAP.SSS 비트가 설정된 HBA에서 디바이스를 순차적으로 스핀업하여 전원 서지를 방지합니다. (2) SATA 백플레인: SAS/SATA 백플레인에는 SGPIO(Serial GPIO)를 통한 활동 LED, 장애 LED 제어가 내장되어 있어 디스크 상태를 시각적으로 확인할 수 있습니다. (3) eSATA 사용 시: eSATA 커넥터는 일반 SATA보다 삽입/분리에 강한 커넥터를 사용하지만, 전원 공급은 별도(eSATAp 제외)이므로 전원 시퀀싱에 유의하세요. (4) PHY 안정화 대기: 디바이스 삽입 후 COMRESET 완료까지 최소 100ms를 대기합니다.

핫 플러그 이벤트와 systemd

systemd 환경에서 SATA 핫 플러그 디바이스의 자동 마운트를 설정하는 방법입니다.

# /etc/systemd/system/mnt-hotplug.mount
[Unit]
Description=SATA Hot-plug Auto Mount
After=blockdev@dev-disk-by\x2dlabel-HOTDATA.target

[Mount]
What=/dev/disk/by-label/HOTDATA
Where=/mnt/hotplug
Type=ext4
Options=defaults,noatime,nofail

[Install]
WantedBy=multi-user.target
# systemd-udevd가 처리하는 핫 플러그 이벤트 모니터링
udevadm monitor --subsystem-match=block --property

# 출력 예시 (디바이스 삽입):
# UDEV  [1234.567] add /devices/.../block/sdc (block)
# ID_BUS=ata
# ID_ATA_SATA=1
# ID_MODEL=Samsung_SSD_870_EVO

# 커널 notification chain (ATA 디바이스 이벤트)
# drivers/ata/libata-scsi.c:
# scsi_add_device() → kobject_uevent(KOBJ_ADD)
# scsi_remove_device() → kobject_uevent(KOBJ_REMOVE)

Port Multiplier (FBS)

SATA Port Multiplier(PMP)는 하나의 SATA 호스트 포트에 여러 디바이스를 팬아웃(fan-out) 방식으로 연결하는 장치입니다. 최대 15개의 디바이스 포트를 제공하며, 1개의 제어 포트(포트 15)를 가집니다.

PMP 토폴로지

Host Port 0 ─── PMP ─┬── PMP Port 0 → SATA Device 0
                      ├── PMP Port 1 → SATA Device 1
                      ├── PMP Port 2 → SATA Device 2
                      │   ...
                      ├── PMP Port 14 → SATA Device 14
                      └── PMP Port 15 (Control Port)

FIS-Based Switching (FBS)

FBS가 없으면 PMP는 Command-Based Switching을 사용하며, 한 번에 하나의 디바이스에만 명령을 보낼 수 있습니다. FBS를 사용하면 여러 디바이스에 동시에 명령을 발행할 수 있어 성능이 크게 향상됩니다.

항목Command-Based SwitchingFIS-Based Switching
동시 명령1개 디바이스여러 디바이스
NCQ 지원1개 디바이스만모든 디바이스
HBA 요구기본CAP.FBSS 비트 필요
PMP 요구기본FBS 지원 PMP 필요
성능낮음높음

GSCR (General Status and Control Registers)

/* PMP GSCR 레지스터 */
enum {
    SATA_PMP_GSCR_DEVO       = 0,   /* Device/Vendor OUI */
    SATA_PMP_GSCR_REV        = 1,   /* Revision */
    SATA_PMP_GSCR_PORT_INFO  = 2,   /* Port Info (포트 수) */
    SATA_PMP_GSCR_ERROR      = 32,  /* Error Information */
    SATA_PMP_GSCR_ERROR_EN   = 33,  /* Error Enable */
    SATA_PMP_GSCR_FEAT       = 64,  /* Features */
    SATA_PMP_GSCR_FEAT_EN    = 96,  /* Feature Enable */
};

/* libata에서 PMP 구조: 포트당 ata_link를 추가 생성 */
/* ap->pmp_link[0..14] 배열로 각 PMP 포트 표현 */

PMP 리눅스 커널 구현

/* drivers/ata/libata-pmp.c — PMP 프로브 경로 */
static int sata_pmp_attach(struct ata_device *dev)
{
    struct ata_port *ap = dev->link->ap;
    int nr_ports;

    /* 1. PMP GSCR 읽기 → 포트 수 확인 */
    sata_pmp_read(dev->link, SATA_PMP_GSCR_PORT_INFO, &val);
    nr_ports = val & 0xf;

    /* 2. PMP 링크 배열 할당 */
    ap->pmp_link = kcalloc(SATA_PMP_MAX_PORTS,
                           sizeof(struct ata_link), GFP_KERNEL);

    /* 3. 각 PMP 포트에 대해 ata_link 초기화 */
    for (i = 0; i < nr_ports; i++) {
        struct ata_link *link = &ap->pmp_link[i];
        ata_link_init(ap, link, i);
    }

    /* 4. FBS(FIS-Based Switching) 활성화 시도 */
    if (ap->flags & ATA_FLAG_FPDMA_AUX)
        sata_pmp_enable_fbs(ap);

    return 0;
}

/* PMP GSCR 레지스터 읽기/쓰기는 Register H2D FIS의
 * PMPORT 필드를 15(제어 포트)로 설정하여 수행 */
PMP 제한사항: (1) 모든 AHCI 컨트롤러가 PMP를 지원하는 것은 아닙니다 (CAP.SPM 비트 확인). (2) NCQ와 PMP의 조합은 FBS 없이는 성능이 크게 저하됩니다. (3) 핫 플러그와 PMP의 조합은 불안정할 수 있습니다. (4) 리눅스 커널에서 PMP 지원은 완전하지만, 일부 저가형 PMP 칩에서 호환성 문제가 보고됩니다.

PMP 커맨드 라우팅

PMP 환경에서 호스트가 특정 디바이스에 명령을 보내려면 FIS의 PMPORT 필드(4비트)에 대상 포트 번호를 설정합니다.

/* Register H2D FIS에서 PMPORT 필드 설정 */
/* Byte 1의 비트 [3:0] = PM Port */
struct ata_taskfile tf;
tf.flags |= ATA_TFLAG_FUA;
/* libata에서 PMPORT는 ata_link->pmp에 저장 */
struct ata_link *link = qc->dev->link;
u8 pmport = link->pmp;  /* 0~14: 디바이스, 15: 제어 포트 */

/* ahci_fill_cmd_slot()에서 Command Header에 PMPORT 기록 */
opts = cmd_fis_len | (qc->n_elem << 16);
opts |= (pmport << 12);  /* PMP 필드 (비트 15:12) */
PMP 제어 포트(Port 15): GSCR(General Status and Control Register) 읽기/쓰기와 PMP 자체 설정(FBS 활성화, 에러 보고 설정 등)은 모두 Port 15를 통해 수행됩니다.

PMP 에러 처리

PMP 환경에서의 에러 처리는 개별 링크 단위로 수행되며, 일반 직접 연결보다 복잡합니다.

/* drivers/ata/libata-pmp.c — PMP 에러 복구 */
static int sata_pmp_eh_recover(struct ata_port *ap)
{
    struct ata_link *link;
    int rc;

    /* 1. 모든 PMP 링크의 에러 상태 수집 */
    ata_for_each_link(link, ap, EDGE) {
        sata_pmp_read(link, SATA_PMP_GSCR_ERROR, &err);
        /* 각 PMP 포트별 SError 읽기 */
        sata_scr_read(link, SCR_ERROR, &serror);
    }

    /* 2. 개별 링크 리셋 시도 (호스트 포트 리셋 불필요) */
    ata_for_each_link(link, ap, EDGE) {
        if (link->eh_info.action) {
            rc = ata_eh_reset(link, 0, softreset, hardreset);
            /* PMP 포트별 COMRESET: GSCR에 쓰기 */
        }
    }

    /* 3. 전체 실패 시 호스트 포트 리셋 (PMP 포함 전체 재초기화) */
    if (rc) {
        ata_eh_reset(ap->link, 0, softreset, hardreset);
        sata_pmp_attach(ap->link->device);
    }
    return rc;
}

PMP 실제 사용 시나리오

시나리오구성PMP 역할주의사항
5-bay NASAHCI 1포트 + PMP 5포트단일 SATA 포트에서 5개 HDD 연결FBS 없으면 성능 저하, RAID 시 병목
PCIe SATA 카드PCIe x1 + ASMedia ASM1166내장 PMP로 6포트 제공실제로는 PMP 기반, 대역폭 공유
eSATA 도킹노트북 eSATA + 외장 PMP다중 외장 디스크 연결드라이버 호환성 확인 필수
서버 백플레인SAS HBA + SATA PMPSAS Expander와 유사한 역할SAS Expander 사용이 더 안정적
Port Multiplier 토폴로지와 FIS 라우팅 AHCI Host Port 0 SATA Link Port Multiplier PMPORT 필드로 라우팅 Port 0 (PMPORT=0) Port 1 (PMPORT=1) Port 2 (PMPORT=2) ... Port 15 (Control) SATA Device 0 (HDD) FIS.PMPORT = 0 SATA Device 1 (SSD) FIS.PMPORT = 1 SATA Device 2 (HDD) FIS.PMPORT = 2 FBS 모드: 여러 포트에 동시 NCQ 명령 가능 CBS 모드: 한 번에 하나의 포트만 명령 가능 대역폭: 모든 포트가 단일 SATA 링크(6Gbps)를 공유

SATA 에러 처리와 복구

libata EH (Error Handler) 복구 흐름 에러 감지 (타임아웃/HSM/버스) 포트 동결 (freeze) PxIE = 0 ata_eh_autopsy (에러 분류) ata_eh_report (로그 출력) ata_eh_recover (리셋 시퀀스) softreset → hardreset (COMRESET) → 디바이스 재식별 → 재설정 ata_eh_finish → 포트 해동 (thaw) 실패시 재시도 (최대 5회) libata-eh.c: 에러 분류 → 복구 전략 결정 → 리셋 → 재초기화

SError 레지스터 비트 상세

카테고리비트필드설명
ERR0IRecovered Data Integrity Error
1MRecovered Communications Error
8TTransient Data Integrity Error
9CPersistent Comm/Data Integrity Error
10PProtocol Error
11EInternal Error
DIAG16NPhyRdy Change
17IPHY Internal Error
18WComm Wake
19B10B-to-8B Decode Error
20DDisparity Error
21HHandshake Error
23TTransport state transition error
26XDevice Exchanged

에러 복구 시나리오 예시

# 실제 에러 복구 dmesg 출력 예:
# 1. 에러 감지 → 포트 동결
ata1.00: exception Emask 0x10 SAct 0x7 SErr 0x400000 action 0x6 frozen
ata1.00: irq_stat 0x08000000, interface fatal error

# 2. SError 분석
ata1: SError: { Handshk }
# → DIAG.H: 핸드셰이크 에러 (PHY 레벨 통신 문제)

# 3. 소프트 리셋 시도
ata1: hard resetting link
# → softreset 생략, 바로 hardreset (COMRESET)

# 4. 링크 재성립
ata1: SATA link up 6.0 Gbps (SStatus 133 SControl 300)

# 5. 디바이스 재식별
ata1.00: configured for UDMA/133

# 6. 실패한 NCQ 명령 재시도
ata1.00: EH complete

에러 분류 (libata)

/* include/linux/libata.h — 에러 마스크 */
enum {
    AC_ERR_DEV       = (1 << 0),  /* 디바이스 에러 (Status.ERR) */
    AC_ERR_HSM       = (1 << 1),  /* Host State Machine 위반 */
    AC_ERR_TIMEOUT   = (1 << 2),  /* 커맨드 타임아웃 */
    AC_ERR_MEDIA     = (1 << 3),  /* 미디어 에러 (배드 섹터) */
    AC_ERR_ATA_BUS   = (1 << 4),  /* ATA 버스 에러 (CRC, 인코딩) */
    AC_ERR_HOST_BUS  = (1 << 5),  /* 호스트 버스 에러 (PCI) */
    AC_ERR_SYSTEM    = (1 << 6),  /* 시스템 에러 */
    AC_ERR_INVALID   = (1 << 7),  /* 잘못된 인자 */
    AC_ERR_OTHER     = (1 << 8),  /* 기타 */
    AC_ERR_NODEV_HINT = (1 << 9), /* 디바이스 없음 힌트 */
    AC_ERR_NCQ       = (1 << 10), /* NCQ 에러 */
};

/* 복구 동작 */
enum {
    ATA_EH_RESET     = (1 << 0),  /* 리셋 (soft → hard) */
    ATA_EH_REVALIDATE = (1 << 1), /* 디바이스 재식별 */
    ATA_EH_SET_MODE  = (1 << 4),  /* 전송 모드 재설정 */
    ATA_EH_LPM       = (1 << 5),  /* 전원 관리 재설정 */
};

NCQ 에러 복구 상세

NCQ 명령이 실패하면 다음 단계로 복구가 진행됩니다. 실패한 태그만 재시도하고 나머지는 계속 진행할 수 있습니다.

/* NCQ 에러 복구 흐름 */
/* 1. SDB FIS에서 에러 감지 (I 비트 = 1) */
ahci_port_intr()
  → status & PORT_IRQ_SDB_FIS
  → D2H FIS의 Status.ERR 또는 SDB FIS의 Error 비트 확인

/* 2. 포트 동결 — 추가 명령 발행 차단 */
ata_port_freeze(ap);
/* PxIE = 0, PxCMD.ST 클리어, 진행중 QC 모두 freeze */

/* 3. READ LOG EXT (Log Page 10h) — NCQ 에러 로그 읽기 */
ata_eh_read_log_10h(dev, &tag, &tf);
/* PIO 명령: 실패한 태그, Status, Error 레지스터 획득 */

/* 4. 실패한 태그 식별 및 에러 분류 */
qc = __ata_qc_from_tag(ap, tag);
qc->err_mask |= AC_ERR_DEV;
/* 나머지 활성 QC는 AC_ERR_TIMEOUT으로 마킹 */

/* 5. 포트 리셋 (COMRESET) + 디바이스 재초기화 */
ata_eh_reset(link);

/* 6. 실패한 명령 재시도, 나머지 QC 재발행 */
ata_eh_finish(ap);
/* 재시도 카운터 감소, 한계 도달 시 SCSI에 에러 반환 */

libata EH 상태 머신

libata의 에러 핸들러는 명확한 상태 머신으로 동작합니다.

상태함수동작다음 상태
IDLE-정상 동작, EH 비활성SCHEDULE (에러 발생 시)
SCHEDULEata_port_schedule_eh()EH 워크큐에 작업 등록FREEZE
FREEZEata_port_freeze()포트 인터럽트 비활성, 명령 중단AUTOPSY
AUTOPSYata_eh_autopsy()에러 원인 분류, 복구 전략 결정REPORT
REPORTata_eh_report()dmesg에 에러 로그 출력RECOVER
RECOVERata_eh_recover()softreset/hardreset, 디바이스 재설정FINISH 또는 AUTOPSY (실패 시)
FINISHata_eh_finish()실패 QC 완료/재시도, 포트 해동IDLE

SATA 에러 통계 수집

# sysfs를 통한 ATA 포트 에러 통계
cat /sys/class/ata_port/ata1/port_no
cat /sys/class/ata_port/ata1/idle_irq

# SATA PHY 이벤트 카운터 (SMART Log 11h)
smartctl -l sataphy /dev/sda
# ID  Description                   Value
# 0x0001  Command failed                0
# 0x0003  R_ERR response for device-to-host data FIS  2
# 0x0009  Transition from drive PhyRdy to drive PhyNRdy  1

# libata 에러 tracepoint 활성화
echo 1 > /sys/kernel/debug/tracing/events/libata/ata_eh_link_autopsy/enable
echo 1 > /sys/kernel/debug/tracing/events/libata/ata_eh_about_to_do/enable
cat /sys/kernel/debug/tracing/trace_pipe

# dmesg에서 ATA 에러 패턴 추출
dmesg | grep -E "ata[0-9]+\.(00|[0-9]+): (exception|error|hard resetting|COMRESET)"

케이블/커넥터 문제 진단 패턴

SError 비트 패턴dmesg 표시가능한 원인조치
DIAG.B (10B-to-8B){ 10B8B }케이블 불량, 신호 감쇠SATA 케이블 교체
DIAG.D (Disparity){ Dispar }EMI 간섭, 케이블 너무 긴 경우케이블 경로 변경/단축
DIAG.H (Handshake){ Handshk }커넥터 접촉 불량커넥터 재삽입, 단자 청소
ERR.C (Persistent){ CommWake }전원 공급 불안정PSU 점검, 전원 케이블 교체
ERR.T (Transient){ TrDataI }간헐적 신호 문제케이블 교체 + 재시도 관찰
ERR.P (Protocol){ Proto }FIS 프로토콜 위반펌웨어 업데이트, 속도 다운그레이드
DIAG.N (PhyRdy){ PHYRdyChg }핫 플러그 또는 링크 불안정정상(핫플러그) 또는 케이블 점검
복합 (B+D+H){ 10B8B Dispar Handshk }심각한 케이블/커넥터 손상케이블+커넥터 모두 교체

ATA 보안 기능

ATA Security Feature Set은 디스크에 비밀번호를 설정하여 무단 접근을 방지하는 기능입니다. BIOS/UEFI 레벨에서 디스크를 잠그면, 올바른 비밀번호 없이는 디스크를 읽을 수 없습니다.

ATA Security 명령어

명령ATA 코드설명
SECURITY SET PASSWORD0xF1User/Master 비밀번호 설정
SECURITY UNLOCK0xF2비밀번호로 디스크 잠금 해제
SECURITY ERASE PREPARE0xF3보안 삭제 준비
SECURITY ERASE UNIT0xF4전체 디스크 보안 삭제
SECURITY FREEZE LOCK0xF5보안 설정 동결 (재부팅까지)
SECURITY DISABLE PASSWORD0xF6비밀번호 비활성화
# ATA 보안 상태 확인
hdparm -I /dev/sda | grep -A10 "Security:"

# 비밀번호 설정 (주의: 비밀번호 분실 시 디스크 접근 불가)
hdparm --security-set-passwd "mypassword" /dev/sda

# 보안 삭제 (Normal Erase)
hdparm --security-erase "mypassword" /dev/sda

# Enhanced Secure Erase (SSD 권장: TRIM 기반 전체 삭제)
hdparm --security-erase-enhanced "mypassword" /dev/sda

# 보안 동결 (BIOS가 보통 부팅 시 수행)
hdparm --security-freeze /dev/sda

TCG Opal (자체 암호화 드라이브, SED)

TCG Opal은 하드웨어 기반 전체 디스크 암호화(FDE)를 제공합니다. 디스크 컨트롤러가 모든 데이터를 실시간으로 암호화/복호화하며, AES-256 키는 디스크 내부에 저장됩니다.

# sedutil-cli로 Opal SED 관리
sedutil-cli --scan                 # Opal 지원 디스크 검색
sedutil-cli --query /dev/sda       # Opal 상태 확인
sedutil-cli --initialSetup "password" /dev/sda  # 초기 설정

# 리눅스 커널 OPAL 지원 (block/sed-opal.c)
# ioctl(fd, IOC_OPAL_SAVE, &opal_lock_unlock) 등

ATA Sanitize

Sanitize 모드설명속도
BLOCK ERASE블록 단위 삭제 (SSD에 최적)빠름 (수 초)
CRYPTO SCRAMBLE암호화 키 변경으로 데이터 무효화매우 빠름
OVERWRITE지정 패턴으로 전체 덮어쓰기느림 (HDD 크기 비례)

ATA Sanitize 명령 사용

# Sanitize 지원 확인
hdparm -I /dev/sda | grep -i sanitize
#    * BLOCK ERASE
#    * CRYPTO SCRAMBLE EXT
#    * OVERWRITE EXT

# hdparm으로 Sanitize 실행 (주의: 데이터 완전 삭제!)
# Block Erase (SSD 권장)
hdparm --sanitize-block-erase /dev/sda

# Crypto Scramble (자체 암호화 드라이브)
hdparm --sanitize-crypto-scramble /dev/sda

# Sanitize 상태 확인
hdparm --sanitize-status /dev/sda
Secure Erase vs Sanitize: ATA Secure Erase(0xF4)는 오래된 표준이며, 일부 SSD에서 실제로 전체 셀을 삭제하지 않는 구현이 존재합니다. ATA Sanitize(ACS-3 이후)는 더 엄격한 삭제를 보장하며, BLOCK ERASE는 모든 NAND 블록을 삭제하고, CRYPTO SCRAMBLE은 암호화 키를 파기합니다. 보안 폐기 목적으로는 Sanitize를 권장합니다.

TCG Opal 잠금/해제 흐름

TCG Opal SED(Self-Encrypting Drive)의 잠금/해제 흐름은 다음과 같습니다.

/* TCG Opal 초기 설정 및 잠금 흐름 */

/* 1. Initial Setup — Locking SP 활성화 */
sedutil-cli --initialSetup "password" /dev/sda
/* 내부적으로:
 * - Admin SP에 SID(Security ID) 비밀번호 설정
 * - Locking SP 활성화
 * - MBR Shadow 테이블 활성화 (PBA용) */

/* 2. Locking Range 설정 */
sedutil-cli --setLockingRange 0 RW "password" /dev/sda
/* Range 0 = Global Range (전체 디스크)
 * RW = ReadWrite 잠금 활성화 */

/* 3. 잠금 */
sedutil-cli --setLockingRange 0 LK "password" /dev/sda
/* LK = Locked, 모든 읽기/쓰기 차단 */

/* 4. 잠금 해제 (부팅 시 PBA에서) */
sedutil-cli --setLockingRange 0 RW "password" /dev/sda
/* 올바른 비밀번호로 Range 잠금 해제 */

/* 5. MBR Shadow — Pre-Boot Authentication */
/* 잠금 상태에서 MBR 읽기 시 PBA 이미지 반환
 * → PBA가 비밀번호 입력 받아 잠금 해제
 * → 실제 MBR로 전환되어 OS 부팅 */

커널의 OPAL 지원 (sed-opal.c)

리눅스 커널은 block/sed-opal.c에서 TCG Opal 명령을 ioctl 인터페이스로 노출합니다.

/* block/sed-opal.c — 사용자 공간 OPAL ioctl */
switch (cmd) {
    case IOC_OPAL_SAVE:
        /* Locking Range 잠금 상태 저장 */
        return opal_save(bdev, p);
    case IOC_OPAL_LOCK_UNLOCK:
        /* Locking Range 잠금/해제 */
        return opal_lock_unlock(bdev, p);
    case IOC_OPAL_ACTIVATE_LSP:
        /* Locking SP 활성화 */
        return opal_activate_lsp(bdev, p);
    case IOC_OPAL_SET_PW:
        /* 비밀번호 설정/변경 */
        return opal_set_new_pw(bdev, p);
    case IOC_OPAL_PSID_REVERT_TPR:
        /* PSID를 사용한 공장 초기화 (데이터 삭제) */
        return opal_reverttper(bdev, p, 1);
    case IOC_OPAL_MBR_DONE:
        /* MBR Shadow → 실제 MBR 전환 (PBA 완료) */
        return opal_mbr_done(bdev, p);
}

/* 사용 예시 (C 코드):
 * int fd = open("/dev/sda", O_RDWR);
 * struct opal_lock_unlock lk = {
 *     .session.opal_key.lr = 0,
 *     .session.opal_key.key = "password",
 *     .session.opal_key.key_len = 8,
 *     .l_state = OPAL_RW,
 * };
 * ioctl(fd, IOC_OPAL_LOCK_UNLOCK, &lk);
 */

ATA Security Frozen 상태 우회

대부분의 BIOS/UEFI는 부팅 과정에서 ATA SECURITY FREEZE LOCK(0xF5) 명령을 보내 보안 설정 변경을 방지합니다. Frozen 상태에서는 비밀번호 설정, 삭제 등이 차단됩니다.

# Frozen 상태 확인
hdparm -I /dev/sda | grep frozen
#    frozen   ← BIOS가 동결한 상태
#    not frozen  ← 보안 명령 사용 가능

# Frozen 해제 방법: Sleep/Resume 트릭
# 시스템을 S3(Suspend to RAM)에 넣었다 깨우면
# BIOS의 freeze 명령이 재실행되지 않아 not frozen 상태가 됨

# 1. 시스템 Suspend
systemctl suspend
# 2. 깨운 직후 즉시 확인
hdparm -I /dev/sda | grep frozen
#    not frozen  ← 이제 보안 명령 사용 가능

# 3. 이제 Secure Erase 등 실행 가능
hdparm --security-set-passwd "p" /dev/sda
hdparm --security-erase "p" /dev/sda
보안 주의: Frozen 상태 우회는 정당한 소유자가 디스크 삭제/재설정을 위해 사용하는 방법입니다. Sleep/Resume 트릭은 모든 시스템에서 동작하지 않을 수 있으며, 일부 BIOS는 Resume 시에도 재동결합니다.

데이터 보안 삭제 모범 사례

방법대상속도보안 수준복구 가능성
ATA Secure EraseHDD/SSDHDD: 수 시간, SSD: 수 분중간SSD에서 일부 잔존 가능
ATA Enhanced Secure EraseHDD/SSDSSD: 수 초~분높음매핑 테이블 포함 삭제
Sanitize BLOCK ERASESSD수 초매우 높음모든 NAND 블록 삭제
Sanitize CRYPTO SCRAMBLESED SSD즉시매우 높음암호화 키 파기 = 데이터 무효화
Sanitize OVERWRITEHDD수 시간매우 높음물리적 패턴 덮어쓰기
물리적 파괴모든 매체-최고물리적 복구 불가
SSD 삭제 권장순서: (1) Sanitize CRYPTO SCRAMBLE (SED 지원 시) → 가장 빠르고 안전 (2) Sanitize BLOCK ERASE → 모든 NAND 블록 삭제 (3) ATA Enhanced Secure Erase → 위 두 가지 미지원 시 (4) ATA Secure Erase → 최후 수단 (일부 SSD에서 불완전할 수 있음)

sysfs 인터페이스

리눅스 커널은 SATA/ATA 디바이스 정보를 sysfs를 통해 사용자 공간에 노출합니다.

sysfs 디렉터리 구조

# ATA 호스트/포트/링크/디바이스 계층
/sys/class/ata_port/ata1/              # ATA 포트
/sys/class/ata_port/ata1/nr_pmp_links  # PMP 링크 수
/sys/class/ata_port/ata1/idle_irq      # 유휴 인터럽트 카운트

/sys/class/ata_link/link1/             # ATA 링크
/sys/class/ata_link/link1/sata_spd     # 현재 SATA 속도
/sys/class/ata_link/link1/hw_sata_spd_limit  # 하드웨어 최대 속도

/sys/class/ata_device/dev1.0/          # ATA 디바이스
/sys/class/ata_device/dev1.0/class     # 디바이스 클래스
/sys/class/ata_device/dev1.0/id        # IDENTIFY 데이터 (512B)
/sys/class/ata_device/dev1.0/pio_mode  # PIO 모드
/sys/class/ata_device/dev1.0/xfer_mode # 현재 전송 모드

# SCSI 호스트 레벨
/sys/class/scsi_host/host0/link_power_management_policy  # ALPM 정책
/sys/class/scsi_host/host0/ahci_host_caps  # AHCI CAP 레지스터

# 블록 디바이스 심볼릭 링크
/sys/block/sda/device/                 # → SCSI 디바이스
/sys/block/sda/device/vendor           # "ATA     "
/sys/block/sda/device/model            # 모델명
/sys/block/sda/device/rev              # 펌웨어 버전
/sys/block/sda/device/queue_depth      # 큐 깊이 (NCQ=32)

주요 sysfs 속성 활용

# SATA 링크 속도 확인
cat /sys/class/ata_link/link1/sata_spd
# 출력: 6.0 Gbps

# 큐 깊이 확인 (NCQ 활성화 여부)
cat /sys/block/sda/device/queue_depth
# 출력: 32 (NCQ 활성), 1 (NCQ 비활성)

# IDENTIFY 데이터 원본 읽기 (바이너리)
xxd /sys/class/ata_device/dev1.0/id | head -5

# 전송 모드 확인
cat /sys/class/ata_device/dev1.0/xfer_mode
# 출력: UDMA/133

# ALPM 정책 설정
for host in /sys/class/scsi_host/host*/; do
  echo "med_power_with_dipm" > "${host}link_power_management_policy"
done

sysfs ATA 속성 전체 목록

libata는 세 가지 sysfs 클래스를 통해 ATA 서브시스템의 상태를 노출합니다.

ata_port 속성 (/sys/class/ata_port/ataN/)
속성명권한형식설명
nr_pmp_linksR정수PMP에 연결된 링크 수
idle_irqR정수유휴 인터럽트 카운트
port_noR정수물리적 포트 번호
ata_link 속성 (/sys/class/ata_link/linkN/)
속성명권한형식설명
sata_spdR문자열현재 협상된 SATA 속도
hw_sata_spd_limitR문자열하드웨어 최대 SATA 속도
sata_spd_limitR문자열소프트웨어 제한 SATA 속도
ata_device 속성 (/sys/class/ata_device/devN.M/)
속성명권한형식설명
classR문자열디바이스 클래스 (ata, atapi, pmp)
idR바이너리IDENTIFY DEVICE 데이터 (512B)
gscrR바이너리PMP GSCR 레지스터
pio_modeR문자열PIO 전송 모드
dma_modeR문자열DMA 전송 모드
xfer_modeR문자열현재 활성 전송 모드
eringR텍스트에러 링 버퍼
spdn_cntR정수속도 다운그레이드 횟수
trimR문자열TRIM 지원 상태

sysfs를 통한 디바이스 정보 스크립팅

#!/bin/bash — sata_report.sh
echo "===== SATA 디바이스 보고서 ($(date)) ====="
for port in /sys/class/ata_port/ata*; do
    pn=$(basename "$port"); pnum=${pn#ata}
    lp="/sys/class/ata_link/link${pnum}"
    [ -d "$lp" ] || continue
    spd=$(cat "${lp}/sata_spd" 2>/dev/null)
    [ "$spd" = "<unknown>" ] && continue
    echo "--- ${pn} (${spd}) ---"
    dp="/sys/class/ata_device/dev${pnum}.0"
    [ -d "$dp" ] && echo "  전송 모드: $(cat ${dp}/xfer_mode 2>/dev/null)"
    for sd in /sys/block/sd*; do
        sdn=$(basename "$sd")
        hl=$(readlink -f "${sd}/device")
        if echo "$hl" | grep -q "ata${pnum}"; then
            echo "  /dev/${sdn}: $(cat ${sd}/device/model 2>/dev/null | xargs)"
            echo "  펌웨어: $(cat ${sd}/device/rev 2>/dev/null | xargs)"
            echo "  큐 깊이: $(cat ${sd}/device/queue_depth 2>/dev/null)"
            hn=$(echo "$hl" | grep -oP 'host\K[0-9]+')
            echo "  ALPM: $(cat /sys/class/scsi_host/host${hn}/link_power_management_policy 2>/dev/null)"
            if command -v smartctl &>/dev/null; then
                echo "  SMART: $(smartctl -H /dev/${sdn} 2>/dev/null | grep result)"
            fi
            dm=$(cat "${sd}/queue/discard_max_bytes" 2>/dev/null)
            [ "$dm" != "0" ] && echo "  TRIM: 지원" || echo "  TRIM: 미지원"
        fi
    done
done

커널의 sysfs 속성 등록

/* drivers/ata/libata-transport.c */

/* 전송 모드 속성 show 콜백 */
static ssize_t ata_tdev_xfer_mode_show(
    struct device *dev,
    struct device_attribute *attr, char *buf)
{
    struct ata_device *ata_dev = transport_class_to_dev(dev);
    return sysfs_emit(buf, "%s\n",
        ata_mode_string(ata_dev->xfer_mode));
}

/* ALPM 정책 store 콜백 */
static ssize_t link_pm_policy_store(
    struct device *dev,
    struct device_attribute *attr,
    const char *buf, size_t count)
{
    struct Scsi_Host *shost = class_to_shost(dev);
    struct ata_port *ap = ata_shost_to_port(shost);
    enum ata_lpm_policy policy = ata_parse_lpm_policy(buf);
    if (policy < 0) return -EINVAL;
    return ata_set_lpm(ap, policy, ATA_LPM_UNKNOWN);
}
DEVICE_ATTR_RW(link_power_management_policy);

procfs와의 차이점

항목/proc/scsi/scsisysfs (/sys/class/ata_*)
구조단일 텍스트 파일디렉터리/파일 계층
SATA 속도미제공sata_spd, hw_sata_spd_limit
전송 모드미제공xfer_mode, pio_mode
IDENTIFY미제공id (512B 바이너리)
에러 이력미제공ering
쓰기 속성제한적ALPM, 큐 깊이 등
지원 상태레거시표준 (장기 지원)
sysfs ATA 디바이스 트리 계층 구조 /sys/class/ata_port/ata1 nr_pmp_links, idle_irq, port_no /sys/class/ata_link/link1 sata_spd, hw_sata_spd_limit /sys/class/ata_device/dev1.0 class, id, xfer_mode, ering, trim /sys/class/scsi_host/host0 link_power_management_policy /sys/block/sda device/model, queue_depth symlink /dev/disk/by-id/ata-MODEL_SERIAL

S.M.A.R.T. 모니터링

S.M.A.R.T.(Self-Monitoring, Analysis and Reporting Technology)는 저장 장치가 자체적으로 건강 상태를 모니터링하고 보고하는 기술입니다. 리눅스에서는 smartmontools 패키지의 smartctlsmartd를 사용합니다.

# SMART 전체 정보 조회
smartctl -a /dev/sda

# SMART 상태만 빠르게 확인
smartctl -H /dev/sda
# SMART overall-health self-assessment test result: PASSED

# 자체 테스트 실행
smartctl -t short /dev/sda    # 짧은 테스트 (~2분)
smartctl -t long /dev/sda     # 긴 테스트 (~수 시간)
smartctl -t conveyance /dev/sda  # 운송 테스트

# 테스트 결과 확인
smartctl -l selftest /dev/sda

# 에러 로그 확인
smartctl -l error /dev/sda

핵심 SMART 속성

ID속성명설명위험 기준
5Reallocated Sectors Count재할당된 불량 섹터 수1 이상이면 주의
9Power-On Hours누적 전원 켜진 시간HDD: ~30000h, SSD: ~50000h
12Power Cycle Count전원 ON/OFF 횟수참고용
187Reported Uncorrectable Errors교정 불가 에러 수증가하면 위험
188Command Timeout타임아웃된 명령 수증가하면 케이블/전원 확인
197Current Pending Sector Count읽기 실패, 재할당 대기 섹터1 이상이면 주의
198Offline Uncorrectable오프라인 검사 중 발견된 불량 섹터1 이상이면 주의
199UDMA CRC Error CountCRC 에러 (케이블/커넥터 문제)증가하면 케이블 교체
241Total LBAs Written총 쓰기량 (SSD 수명 지표)TBW 한계 확인

smartd 데몬 설정

# /etc/smartd.conf 예시
# 모든 SATA 디스크 모니터링, 이메일 알림
/dev/sda -a -o on -S on -s (S/../.././02|L/../../6/03) \
  -m admin@example.com -M exec /usr/share/smartmontools/smartd_warning.sh

# -a: 모든 SMART 속성 모니터링
# -o on: 오프라인 테스트 자동 활성화
# -S on: 속성 자동 저장 활성화
# -s: 자체 테스트 스케줄 (Short: 매일 02시, Long: 토요일 03시)

libata SMART 패스스루

/* SMART 명령은 SCSI-ATA Translation(SAT)을 통해 전달됨 */
/* SCSI 명령: ATA_16 (0x85) 또는 ATA_12 (0xA1) CDB */
/* smartctl은 SG_IO ioctl로 ATA passthrough CDB를 전송 */

/* libata-scsi.c에서 ATA passthrough 처리: */
ata_scsi_simulate()
  → ata_scsi_pass_thru()  /* CDB에서 ATA taskfile 추출 */
    → ata_qc_issue()      /* ATA 명령 직접 발행 */

SMART 속성 임계값과 경고 해석

# SMART 속성 상세 출력 (원시값 포함)
smartctl -A /dev/sda
# 출력 예:
# ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE     UPDATED WHEN_FAILED RAW_VALUE
#   5 Reallocated_Sector_Ct   0x0033   100   100   010    Pre-fail Always       -       0
#   9 Power_On_Hours          0x0032   094   094   000    Old_age  Always       -       28456
# 197 Current_Pending_Sector  0x0012   100   100   000    Old_age  Always       -       0
# 199 UDMA_CRC_Error_Count    0x003e   200   200   000    Old_age  Always       -       3

# VALUE: 정규화된 현재 값 (높을수록 좋음)
# WORST: 기록된 최저 VALUE
# THRESH: 임계값 (VALUE가 이 아래면 FAIL)
# TYPE: Pre-fail (FAIL=즉시 교체) / Old_age (점진적 열화)
# RAW_VALUE: 실제 원시 카운터 값
SMART 모니터링 자동화 (systemd timer): smartd 대신 systemd timer로 주기적 SMART 체크를 구성할 수도 있습니다. smartctl --json=o -a /dev/sda로 JSON 출력을 받아 모니터링 시스템(Prometheus, Zabbix 등)에 연동하면 대규모 서버 환경에서 유용합니다.

SSD 전용 SMART 속성

SSD는 HDD와 다른 고유한 SMART 속성을 제공합니다. 제조사마다 ID 해석이 다를 수 있으므로, 정확한 해석을 위해 smartmontools의 디바이스 데이터베이스를 참조하세요.

ID속성명설명위험 기준
170Available Spare Blocks남은 예비 NAND 블록 수임계값 이하면 교체 필요
171Program Fail CountNAND 프로그램(쓰기) 실패 횟수증가 추세면 주의
172Erase Fail CountNAND 이레이즈 실패 횟수증가 추세면 주의
173Average Block Erase Count평균 블록 이레이즈 횟수TBW 한계 참조
174Unexpected Power Loss비정상 전원 차단 횟수잦으면 전원 점검
175Power Loss Protection Failure전원 보호 회로 실패1 이상이면 위험
177Wear Leveling Count웨어 레벨링 카운터 (마모 균등화)0에 가까울수록 위험
180Unused Reserve Block미사용 예비 블록 수0이면 교체 필요
232Available Reserve Space남은 예비 공간 비율10% 이하면 주의
233Media Wearout Indicator미디어 마모 지표 (100→0)10 이하면 교체 고려
241Total LBAs Written총 쓰기량 (LBA 단위)TBW 한계 대비 확인
242Total LBAs Read총 읽기량 (LBA 단위)참고용

SMART 자체 테스트 (Self-Test) 상세

SMART Self-Test는 디스크가 자체적으로 하드웨어 무결성을 검사하는 기능입니다. 테스트 유형별로 검사 범위와 소요 시간이 다릅니다.

테스트 유형검사 범위소요 시간용도
Short전자 회로, 서보, 일부 표면 읽기~2분빠른 건강 진단
Extended (Long)전체 디스크 표면 스캔HDD: 수 시간, SSD: 수 분~수십 분완전한 표면 검사
Conveyance운송 중 손상 감지~5분새 디스크 초기 검사
Selective지정 LBA 범위만 검사범위에 비례특정 영역 문제 확인
# 각 테스트 유형 실행
smartctl -t short /dev/sda       # Short: ~2분
smartctl -t long /dev/sda        # Extended: 전체 표면
smartctl -t conveyance /dev/sda  # 운송 테스트
smartctl -t select,0-999999 /dev/sda  # LBA 0~999999 선택 테스트

# 예상 소요 시간 확인
smartctl -c /dev/sda | grep "Total time"

# 진행 상황 모니터링
smartctl -c /dev/sda | grep "remaining"

# 테스트 결과 확인
smartctl -l selftest /dev/sda
# Num  Test_Description  Status              Remaining  LifeTime  LBA_of_first_error
# # 1  Extended offline  Completed without error  00%     28456     -

smartctl JSON 출력과 자동화

# JSON 형식으로 SMART 전체 정보 조회
smartctl --json=o -a /dev/sda | jq '.ata_smart_attributes.table[] |
  select(.id == 5 or .id == 197 or .id == 198) |
  {id: .id, name: .name, raw: .raw.value}'

# 온도 추출
smartctl --json=o -a /dev/sda | jq '.temperature.current'

# 전원 켜진 시간 추출
smartctl --json=o -a /dev/sda | jq '.power_on_time.hours'
#!/usr/bin/env python3
# smart_alert.py — SMART 임계값 기반 알림
import json, subprocess, sys

THRESHOLDS = {
    5: ("Reallocated_Sector_Ct", 0),   # 재할당 섹터: 0 초과 시 경고
    197: ("Current_Pending_Sector", 0), # 대기 섹터: 0 초과 시 경고
    198: ("Offline_Uncorrectable", 0),  # 오프라인 불량: 0 초과 시 경고
    199: ("UDMA_CRC_Error_Count", 10),  # CRC 에러: 10 초과 시 경고
}

result = subprocess.run(
    ["smartctl", "--json=o", "-A", sys.argv[1]],
    capture_output=True, text=True)
data = json.loads(result.stdout)

for attr in data.get("ata_smart_attributes", {}).get("table", []):
    if attr["id"] in THRESHOLDS:
        name, threshold = THRESHOLDS[attr["id"]]
        raw_val = attr["raw"]["value"]
        if raw_val > threshold:
            print(f"WARNING: {name} = {raw_val} (threshold: {threshold})")

SMART 패스스루 상세 (ATA_16/ATA_12)

smartctl은 리눅스의 SG_IO ioctl을 통해 SCSI CDB를 전송하며, ATA_16(0x85) 또는 ATA_12(0xA1) CDB를 사용하여 ATA 명령을 패스스루합니다.

/* ATA_16 CDB (16바이트) 구조 — SAT-5 표준 */
CDB[0]  = 0x85;         /* ATA PASS-THROUGH (16) */
CDB[1]  = PROTOCOL;     /* [7:4] = 프로토콜:                */
                         /*   3=Non-data, 4=PIO Data-In,    */
                         /*   5=PIO Data-Out, 12=DMA         */
CDB[2]  = FLAGS;        /* BYTE_BLOCK | T_DIR | T_LENGTH    */
                         /*   | CK_COND (체크 컨디션)        */
CDB[3]  = FEATURES_H;   /* Features 상위 바이트 (48-bit)    */
CDB[4]  = FEATURES_L;   /* Features 하위 바이트             */
CDB[5]  = SECTOR_CNT_H; /* Sector Count 상위 (48-bit)       */
CDB[6]  = SECTOR_CNT_L; /* Sector Count 하위               */
CDB[7]  = LBA_LOW_H;    /* LBA Low 상위 (48-bit)            */
CDB[8]  = LBA_LOW_L;    /* LBA Low 하위                    */
CDB[9]  = LBA_MID_H;    /* LBA Mid 상위                    */
CDB[10] = LBA_MID_L;    /* LBA Mid 하위                    */
CDB[11] = LBA_HIGH_H;   /* LBA High 상위                   */
CDB[12] = LBA_HIGH_L;   /* LBA High 하위                   */
CDB[13] = DEVICE;       /* Device 레지스터                  */
CDB[14] = COMMAND;      /* ATA 명령 코드                   */
                         /*   0xB0=SMART, 0xEC=IDENTIFY     */
CDB[15] = CONTROL;      /* Control 바이트                   */

/* SMART READ DATA 예시: */
/* COMMAND=0xB0, FEATURES=0xD0, LBA_MID=0x4F, LBA_HIGH=0xC2 */

PHY 이벤트 카운터

SATA PHY Event Counters(Log Page 11h)는 PHY 레벨의 통신 오류를 추적합니다. 케이블 품질 문제나 EMI 간섭을 진단하는 데 핵심적인 도구입니다.

카운터 ID이름설명
0x001Command failed명령 실패 횟수
0x002R_ERR response for data FIS데이터 FIS의 CRC/인코딩 에러
0x003R_ERR response for device-to-host data FIS디바이스→호스트 데이터 에러
0x004R_ERR response for host-to-device data FIS호스트→디바이스 데이터 에러
0x005R_ERR response for non-data FIS비데이터 FIS 에러
0x006R_ERR response for device-to-host non-data FIS디바이스→호스트 비데이터 에러
0x008Device-to-host non-data FIS retries비데이터 FIS 재시도 횟수
0x009Transition from drive PhyRdy to drive PhyNRdyPHY 링크 다운 횟수
0x00BCRC errors within host-to-device FIS호스트→디바이스 CRC 에러
0x00DNon-CRC errors within host-to-device FIS비CRC 에러 (인코딩 등)
# PHY 이벤트 카운터 읽기
smartctl -l sataphy /dev/sda
# 출력 예:
# ID      Size     Value  Description
# 0x0001  2            0  Command failed due to ICRC error
# 0x0002  2            0  R_ERR response for data FIS
# 0x0009  2            3  Transition from drive PhyRdy to PhyNRdy
# → 0x0009가 높으면 케이블/커넥터 접촉 불량 의심

# PHY 이벤트 카운터 리셋
smartctl -l sataphy,reset /dev/sda

커널 빌드 설정

SATA/AHCI 지원을 위한 주요 커널 설정 옵션입니다. make menuconfig에서 Device Drivers → Serial ATA and Parallel ATA drivers (libata) 아래에 있습니다.

Kconfig 옵션기본값설명
CONFIG_ATAYlibata 서브시스템 (최상위 스위치)
CONFIG_SATA_AHCIYPCI AHCI 컨트롤러 드라이버
CONFIG_SATA_AHCI_PLATFORMM임베디드/SoC AHCI (DT 기반)
CONFIG_ATA_SFFYSFF (Small Form Factor) ATA 지원
CONFIG_ATA_BMDMAYBMDMA (Bus Master DMA) 지원
CONFIG_ATA_PIIXMIntel PIIX/ICH SATA 컨트롤러
CONFIG_SATA_MVMMarvell SATA 컨트롤러
CONFIG_SATA_SILMSilicon Image SATA 컨트롤러
CONFIG_SATA_SISMSiS SATA 컨트롤러
CONFIG_SATA_VIAMVIA SATA 컨트롤러
CONFIG_ATA_GENERICMGeneric ATA (알 수 없는 컨트롤러)
CONFIG_PATA_*M각종 PATA(병렬 ATA) 컨트롤러
# 현재 커널의 SATA 관련 설정 확인
zgrep "CONFIG_SATA\|CONFIG_ATA" /proc/config.gz
# 또는
grep "CONFIG_SATA\|CONFIG_ATA" /boot/config-$(uname -r)

# menuconfig 경로
# Device Drivers
#   → Serial ATA and Parallel ATA drivers (libata)
#     → AHCI SATA support [CONFIG_SATA_AHCI]
#     → Platform AHCI SATA support [CONFIG_SATA_AHCI_PLATFORM]
#     → ATA SFF support (for legacy IDE) [CONFIG_ATA_SFF]

# 로드된 AHCI 모듈 확인
lsmod | grep ahci
# 출력 예: ahci    40960  3
#          libahci  36864  1 ahci
임베디드 시스템: ARM/ARM64 SoC에서 SATA를 사용할 때는 CONFIG_SATA_AHCI_PLATFORM과 해당 SoC의 PHY 드라이버를 함께 활성화해야 합니다. Device Tree에서 compatible = "generic-ahci" 또는 벤더별 compatible 문자열을 사용합니다.

Device Tree AHCI 바인딩 예시

/* AHCI Platform 디바이스 트리 노드 예시 */
sata@f1090000 {
    compatible = "marvell,armada-380-ahci";
    reg = <0xf1090000 0x2000>;
    interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&gate_clk 15>;
    phys = <&comphy0 1>;
    phy-names = "sata-phy";
    #address-cells = <1>;
    #size-cells = <0>;
    status = "okay";

    sata-port@0 {
        reg = <0>;
        /* 포트별 PHY 설정 */
    };
    sata-port@1 {
        reg = <1>;
    };
};

커널 모듈 의존성

# AHCI 모듈 의존성 트리 확인
modinfo ahci | grep depends
# depends: libahci,libata

# 모듈 로드 순서
# 1. libata.ko (core)
# 2. libahci.ko (AHCI 공통 함수)
# 3. ahci.ko (PCI AHCI 드라이버)
# 또는 ahci_platform.ko (플랫폼 AHCI)

# 수동 모듈 로드/언로드
modprobe ahci        # 의존성 자동 해결
modprobe -r ahci     # 언로드 (디바이스 사용 중이면 실패)

커널 설정 최적화 가이드

용도AHCIATA_SFFATA_PIIXPATA비고
서버=y (내장)=n=n=n불필요 코드 제거, 부팅 속도 향상
데스크톱=m (모듈)=m=m=m유연성 확보, 다양한 하드웨어 지원
임베디드PLATFORM=y=n=n=nSoC AHCI만 필요
가상 머신=y=y=y=nQEMU/KVM 레거시: ATA_PIIX 필요

initramfs와 AHCI

루트 파일시스템이 SATA 디스크에 있는 경우, AHCI 드라이버가 initramfs에 포함되거나 커널에 내장(=y)되어야 합니다. 모듈(=m)로 빌드했는데 initramfs에 포함하지 않으면 커널이 루트 파일시스템을 마운트할 수 없어 부팅에 실패합니다.

# 방법 1: 커널에 내장 (권장, 가장 간단)
# .config에서:
CONFIG_SATA_AHCI=y  # 모듈(=m)이 아닌 내장(=y)
CONFIG_ATA=y

# 방법 2: initramfs에 모듈 포함 (dracut)
dracut --add-drivers "ahci libahci" -f /boot/initramfs-$(uname -r).img $(uname -r)

# 방법 3: initramfs에 모듈 포함 (mkinitramfs/update-initramfs)
echo "ahci" >> /etc/initramfs-tools/modules
update-initramfs -u

# initramfs 내용 확인
lsinitrd /boot/initramfs-$(uname -r).img | grep ahci
# 또는
lsinitramfs /boot/initrd.img-$(uname -r) | grep ahci

컴파일 타임 디버그 옵션

옵션설명영향
CONFIG_ATA_VERBOSE_ERRORATA 에러 메시지 상세 출력dmesg에 에러 원인 텍스트 추가
CONFIG_ATA_FORCElibata.force 커널 파라미터 지원런타임 NCQ/속도 강제 설정 가능
CONFIG_SATA_PMPPort Multiplier 지원PMP 미사용 시 비활성화 가능
CONFIG_ATA_ACPIATA ACPI 통합ACPI 기반 전원 관리, 핫 플러그
# SATA 전용 최소 .config 프래그먼트 (PATA/SFF 제외)
CONFIG_ATA=y
CONFIG_ATA_VERBOSE_ERROR=y
CONFIG_ATA_FORCE=y
CONFIG_ATA_ACPI=y
CONFIG_SATA_AHCI=y
CONFIG_SATA_PMP=y
# CONFIG_ATA_SFF is not set      ← SFF/PATA 전부 비활성화
# CONFIG_ATA_BMDMA is not set
# CONFIG_ATA_GENERIC is not set
# CONFIG_PATA_LEGACY is not set
가상 머신 주의: QEMU/KVM의 기본 SATA 에뮬레이션은 ICH9 AHCI이므로 CONFIG_SATA_AHCI=y만으로 충분합니다. 단, 레거시 IDE 모드(-M ide)를 사용하면 CONFIG_ATA_PIIX가 필요합니다. VirtIO 블록(virtio-blk)을 사용하면 SATA 드라이버가 불필요합니다.

디버깅과 트러블슈팅

SATA/AHCI 문제를 진단하기 위한 도구와 기법을 정리합니다.

dmesg 핵심 메시지

# 정상 초기화
ata1: SATA max UDMA/133 abar m2048@0xf7c10000 port 0xf7c10100 irq 30
ata1: SATA link up 6.0 Gbps (SStatus 133 SControl 300)
ata1.00: ATA-10: Samsung SSD 870 EVO 500GB, SVT02B6Q, max UDMA/133
ata1.00: 976773168 sectors, multi 1: LBA48 NCQ (depth 31/32)
ata1.00: configured for UDMA/133

# 속도 다운그레이드 (문제 징후)
ata1: SATA link up 3.0 Gbps (SStatus 123 SControl 300)
# → 6Gbps가 아닌 3Gbps로 연결: 케이블 또는 PHY 문제

# 링크 다운 (디바이스 미감지)
ata1: SATA link down (SStatus 0 SControl 300)
# → 케이블 미연결, 전원 미공급, 또는 디바이스 고장

# 에러 발생
ata1.00: exception Emask 0x10 SAct 0x1 SErr 0x400100 action 0x6 frozen
ata1.00: irq_stat 0x08000000, interface fatal error
# → SErr 분석: 0x400100 = DIAG.X + ERR.I

# COMRESET 반복 (링크 불안정)
ata1: hard resetting link
ata1: COMRESET failed (errno=-16)
ata1: reset failed, giving up

libata.force 커널 파라미터

# 부트 파라미터로 libata 동작 강제 설정
# 형식: libata.force=[port][.device]:feature

# NCQ 비활성화 (문제 디버깅용)
libata.force=noncq

# 특정 포트만 NCQ 비활성화
libata.force=1:noncq

# SATA 속도 제한 (3Gbps)
libata.force=1.5Gbps
libata.force=3.0Gbps

# DMA 모드 강제
libata.force=udma5

# 디버그 메시지 활성화
libata.force=dump_id  # IDENTIFY 데이터 덤프

Tracepoints

# ATA tracepoint 목록
ls /sys/kernel/tracing/events/libata/
# ata_qc_issue, ata_qc_complete, ata_eh_link_autopsy, ata_eh_about_to_do ...

# QC 발행/완료 추적
echo 1 > /sys/kernel/tracing/events/libata/ata_qc_issue/enable
echo 1 > /sys/kernel/tracing/events/libata/ata_qc_complete/enable
cat /sys/kernel/tracing/trace_pipe

# AHCI tracepoint (있는 경우)
ls /sys/kernel/tracing/events/ahci/ 2>/dev/null

일반적인 문제와 해결

증상원인해결
SATA link down케이블/전원/디바이스 고장케이블 교체, 다른 포트 시도
3Gbps로 다운그레이드케이블 품질, PHY 협상 실패SATA III 케이블로 교체
NCQ 에러 반복펌웨어 버그, 디바이스 호환성libata.force=noncq
COMRESET 루프PHY 불안정, 전원 부족전원 공급 확인, 속도 제한
Command Timeout디바이스 응답 없음SMART 확인, 디바이스 교체 고려
CRC 에러 증가케이블/커넥터 접촉 불량케이블 교체, 커넥터 청소

SError 디코딩 도구

# dmesg의 SErr 값을 수동 디코딩하는 방법
# 예: SErr 0x00400100
# 비트 8 (ERR.T) = Transient Data Integrity Error
# 비트 22 (DIAG.X) = Device Exchanged (핫 플러그)

# SError 레지스터 직접 확인 (AHCI 포트)
# BAR5 + 0x100 (Port 0) + 0x30 (PxSERR)
setpci -s 00:1f.2 ABAR.l  # BAR5 주소 확인

# SError 클리어 (1을 쓰면 해당 비트 클리어)
# 커널이 자동으로 처리하므로 수동 개입은 드묾

# 전체 AHCI 레지스터 덤프
lspci -s 00:1f.2 -vvv | grep -A20 "Memory at"

동적 디버그 활성화

# libata 동적 디버그 메시지 활성화
echo "module libata +p" > /sys/kernel/debug/dynamic_debug/control
echo "module ahci +p" > /sys/kernel/debug/dynamic_debug/control

# 특정 파일의 디버그만 활성화
echo "file libata-eh.c +p" > /sys/kernel/debug/dynamic_debug/control
echo "file libata-scsi.c +p" > /sys/kernel/debug/dynamic_debug/control

# 비활성화
echo "module libata -p" > /sys/kernel/debug/dynamic_debug/control

ftrace를 이용한 SATA I/O 추적

# ftrace 디렉터리로 이동
cd /sys/kernel/tracing

# function_graph 트레이서 설정
echo function_graph > current_tracer

# AHCI 관련 함수만 필터링
echo 'ahci_qc_*' > set_ftrace_filter
echo 'ata_scsi_*' >> set_ftrace_filter
echo 'ahci_interrupt' >> set_ftrace_filter

# 추적 시작
echo 1 > tracing_on

# I/O 워크로드 실행 (예: dd)
dd if=/dev/sda of=/dev/null bs=4k count=100 iflag=direct

# 추적 중지 및 결과 확인
echo 0 > tracing_on
cat trace | head -50
# 출력 예:
#  2)   0.341 us  |  ahci_qc_prep();
#  2)   0.120 us  |  ahci_qc_issue();
#  2)   +15.234 us |  ahci_single_level_irq_intr();
#  → 15.234us = 명령 발행부터 인터럽트까지의 레이턴시

# 정리
echo nop > current_tracer
echo > set_ftrace_filter

blktrace/blkparse를 이용한 블록 레벨 분석

# blktrace 수집 (10초간)
blktrace -d /dev/sda -o sda_trace -w 10

# blkparse로 분석
blkparse -i sda_trace -d sda_trace.bin

# 주요 이벤트 단계:
# Q (Queue)    — 요청이 블록 레이어에 진입
# G (Get)      — 요청 구조체 할당
# I (Insert)   — I/O 스케줄러 큐에 삽입
# D (Dispatch) — 드라이버에 전달 (AHCI로 발행)
# C (Complete) — 완료 인터럽트 수신

# btt로 통계 분석
btt -i sda_trace.bin
# Q2Q: 요청 간 간격
# Q2D: 큐 진입부터 디스패치까지 (소프트웨어 오버헤드)
# D2C: 디스패치부터 완료까지 (하드웨어 서비스 시간)
# Q2C: 전체 요청 레이턴시

# D2C가 크면 디바이스 자체 성능 문제
# Q2D가 크면 스케줄러/큐 설정 문제

perf를 이용한 AHCI 인터럽트 분석

# AHCI 인터럽트 빈도 추적
perf record -g -e irq:irq_handler_entry -a -- sleep 10
perf report --stdio | grep ahci

# 블록 I/O 이벤트 통계
perf stat -e block:block_rq_issue,block:block_rq_complete,\
block:block_rq_insert -a -- sleep 10

# AHCI 함수별 CPU 시간 프로파일링
perf top -g -p $(pgrep -d, kworker) 2>/dev/null
# → ahci_handle_port_interrupt, ata_scsi_queuecmd 등 확인

AHCI 레지스터 덤프 분석

# lspci로 AHCI BAR5 주소 확인
lspci -s 00:1f.2 -v | grep "Memory at"
# Memory at f7c10000 (32-bit, non-prefetchable) [size=2K]

# 주요 AHCI Generic Host Control 레지스터
# BAR5 + 0x00: CAP (Host Capabilities)
# BAR5 + 0x04: GHC (Global Host Control)
# BAR5 + 0x08: IS  (Interrupt Status)
# BAR5 + 0x0C: PI  (Ports Implemented)

# 포트 레지스터 (Port 0 = BAR5 + 0x100)
# +0x00: PxCLB  (Command List Base Address)
# +0x08: PxFB   (FIS Base Address)
# +0x10: PxIS   (Port Interrupt Status)
# +0x18: PxCMD  (Port Command and Status)
# +0x28: PxSSTS (SATA Status — DET, SPD, IPM)
# +0x2C: PxSCTL (SATA Control)
# +0x30: PxSERR (SATA Error)
# +0x34: PxSACT (SATA Active — NCQ)
# +0x38: PxCI   (Command Issue)

# lspci -xxxx로 PCI 설정 공간 전체 덤프
lspci -s 00:1f.2 -xxxx | head -20
SATA 문제 진단 플로우차트 SATA 문제 발생 Link Down? Yes 케이블/전원 점검 No SError 비트? Yes SError 디코딩 분석 No NCQ 에러? Yes noncq 시도 No Timeout → SMART 확인 성능 저하 → 스케줄러/NCQ/ALPM

SATA 성능 튜닝

SATA 디바이스의 성능을 최적화하기 위한 커널 및 시스템 설정입니다.

NCQ 활성화 확인

# NCQ 지원 및 활성화 확인
dmesg | grep NCQ
# ata1.00: 976773168 sectors, multi 1: LBA48 NCQ (depth 31/32)
# depth 31/32 = NCQ 활성화, 최대 32 슬롯 중 31 사용

# 큐 깊이 확인
cat /sys/block/sda/device/queue_depth
# 32 = NCQ 활성, 1 = NCQ 비활성

# NCQ 비활성화 (디버깅용)
echo 1 > /sys/block/sda/device/queue_depth

I/O 스케줄러

# 현재 스케줄러 확인
cat /sys/block/sda/queue/scheduler
# [mq-deadline] none kyber bfq

# SATA HDD: mq-deadline 또는 bfq 권장
echo "mq-deadline" > /sys/block/sda/queue/scheduler

# SATA SSD: none (noop) 또는 mq-deadline
echo "none" > /sys/block/sda/queue/scheduler

Read-Ahead 설정

# 현재 read-ahead 값 (섹터 단위, 512B)
cat /sys/block/sda/queue/read_ahead_kb
# 기본: 128 KB

# HDD 순차 읽기 최적화: 증가
echo 2048 > /sys/block/sda/queue/read_ahead_kb

# SSD 랜덤 I/O: 줄이기
echo 64 > /sys/block/sda/queue/read_ahead_kb

Write Cache

# Write Cache 상태 확인
hdparm -W /dev/sda
# write-caching = 1 (on)

# Write Cache 비활성화 (데이터 안전 우선)
hdparm -W0 /dev/sda

# Write Cache 활성화 (성능 우선)
hdparm -W1 /dev/sda
Write Cache 주의사항: Write Cache가 활성화된 상태에서 전원이 갑자기 차단되면 캐시에 남아있던 데이터가 유실될 수 있습니다. UPS(무정전 전원 장치) 없이 Write Cache를 사용하면 파일시스템 무결성이 위험해질 수 있습니다. 서버 환경에서는 배터리 백업이 있는 경우에만 Write Cache를 활성화하세요.

fio 벤치마크 예시

# 순차 읽기 (SATA 대역폭 한계 테스트)
fio --name=seq-read --filename=/dev/sda --rw=read \
    --bs=128k --iodepth=32 --numjobs=1 --direct=1 \
    --ioengine=libaio --runtime=30 --time_based

# 랜덤 4K 읽기 (NCQ + IOPS 테스트)
fio --name=rand-read --filename=/dev/sda --rw=randread \
    --bs=4k --iodepth=32 --numjobs=1 --direct=1 \
    --ioengine=libaio --runtime=30 --time_based

# 순차 쓰기
fio --name=seq-write --filename=/dev/sda --rw=write \
    --bs=128k --iodepth=32 --numjobs=1 --direct=1 \
    --ioengine=libaio --runtime=30 --time_based

# 혼합 랜덤 R/W (70:30)
fio --name=mixed --filename=/dev/sda --rw=randrw --rwmixread=70 \
    --bs=4k --iodepth=32 --numjobs=4 --direct=1 \
    --ioengine=libaio --runtime=30 --time_based
SATA 디바이스 유형별 기대 성능
메트릭SATA HDD (7200RPM)SATA SSD
순차 읽기~150-200 MB/s~550 MB/s
순차 쓰기~130-180 MB/s~520 MB/s
랜덤 4K 읽기 IOPS~100-200~90,000-100,000
랜덤 4K 쓰기 IOPS~100-200~80,000-90,000
레이턴시~5-10 ms~0.05-0.1 ms

MSI vs Legacy Interrupt

# AHCI 인터럽트 모드 확인
grep ahci /proc/interrupts
# 출력 예 (MSI):
#  30:   12345   PCI-MSI 512000-edge  ahci[0000:00:1f.2]
# 출력 예 (INTx):
#  19:   12345   IO-APIC  19-fasteoi  ahci

# MSI가 활성화되지 않은 경우 (일부 구형 칩셋)
# BIOS에서 MSI 활성화 또는 커널 파라미터 확인
lspci -s 00:1f.2 -vv | grep "MSI:"
# MSI: Enable+ Count=1/1 Maskable- 64bit-

TRIM/Discard 설정 (SATA SSD)

# TRIM 지원 확인
hdparm -I /dev/sda | grep -i trim
#    * Data Set Management TRIM supported (limit 8 blocks)

# discard 마운트 옵션 (연속 TRIM)
mount -o discard /dev/sda1 /mnt

# 주기적 TRIM (fstrim, 권장)
fstrim -v /mnt
# 또는 systemd timer 사용 (보통 기본 활성화)
systemctl enable --now fstrim.timer

# TRIM 지원 확인 (블록 레벨)
cat /sys/block/sda/queue/discard_max_bytes
# 0이면 TRIM 미지원
cat /sys/block/sda/queue/discard_granularity
연속 TRIM vs 주기적 TRIM: mount -o discard (연속 TRIM)은 파일 삭제 시마다 TRIM 명령을 보내므로 삭제 성능이 약간 저하될 수 있습니다. fstrim (주기적 TRIM)은 한꺼번에 모아서 TRIM을 보내므로 일상적인 성능에 영향이 없습니다. 서버 환경에서는 fstrim.timer (주 1회)를 권장합니다.

I/O 스케줄러 상세 비교 (SATA 관점)

스케줄러SATA HDD 순차SATA HDD 랜덤SATA SSD레이턴시공정성권장 용도
mq-deadline우수양호양호보장 (deadline)제한적범용, 서버 HDD
bfq양호우수양호양호우수 (cgroup)데스크톱, 멀티 사용자
kyber양호양호우수자동 조절양호SSD, 고성능
none보통보통최적최소 오버헤드없음NVMe, SSD 단일 용도
SATA HDD 권장: mq-deadline은 읽기/쓰기 각각에 deadline을 설정하여 기아(starvation)를 방지합니다. SATA HDD에서는 bfq도 좋은 선택이며, 데스크톱 환경에서 반응성이 더 좋을 수 있습니다. SATA SSD에서는 none 또는 kyber가 적합합니다.

블록 레이어 큐 설정

# nr_requests: 큐당 최대 요청 수 (기본 256)
cat /sys/block/sda/queue/nr_requests
echo 512 > /sys/block/sda/queue/nr_requests  # 서버에서 증가

# max_sectors_kb: 단일 I/O 최대 크기 (기본 512KB)
cat /sys/block/sda/queue/max_sectors_kb
echo 1024 > /sys/block/sda/queue/max_sectors_kb  # 순차 I/O 향상

# nomerges: 요청 병합 비활성화 (SSD 랜덤 워크로드)
echo 2 > /sys/block/sda/queue/nomerges  # 0=병합, 1=단순, 2=비활성

# rotational: 회전 미디어 여부 (스케줄러 동작 영향)
cat /sys/block/sda/queue/rotational
# 1=HDD, 0=SSD (SATA SSD는 자동 감지되지만 확인 필요)

# io_poll: 폴링 모드 (SATA에서는 보통 효과 없음)
cat /sys/block/sda/queue/io_poll
# SATA/AHCI는 인터럽트 기반이므로 폴링 비권장

NUMA와 SATA 성능

다중 소켓 서버에서 AHCI 컨트롤러가 연결된 NUMA 노드와 I/O를 수행하는 프로세스의 NUMA 노드가 다르면, 메모리 접근 레이턴시 증가로 인해 성능이 저하될 수 있습니다.

# AHCI 컨트롤러의 NUMA 노드 확인
cat /sys/block/sda/device/../../numa_node
# 0 = NUMA 노드 0, -1 = NUMA 정보 없음

# lstopo로 PCI 디바이스의 NUMA 토폴로지 시각화
lstopo --of ascii | grep -A5 PCI

# AHCI 컨트롤러와 같은 NUMA 노드에서 fio 실행
numactl --cpunodebind=0 --membind=0 \
  fio --name=test --filename=/dev/sda --rw=randread \
  --bs=4k --iodepth=32 --direct=1 --ioengine=libaio --runtime=30

성능 모니터링 도구 모음

도구용도사용 예시
iostat디바이스별 I/O 통계iostat -xz 1
iotop프로세스별 I/O 사용량iotop -oPa
dstat통합 시스템 통계dstat -tdD sda
pidstat -d프로세스별 디스크 I/Opidstat -d 1
bpftrace biolatency블록 I/O 레이턴시 히스토그램biolatency.bt
bpftrace biosnoop블록 I/O 요청별 추적biosnoop.bt
blktrace블록 레이어 상세 추적blktrace -d /dev/sda -w 10

SATA SSD 성능 저하 원인과 대응

SATA SSD는 사용 시간이 경과하면 성능이 저하될 수 있습니다. 주요 원인과 대응 방법을 정리합니다.

원인메커니즘대응
Write Amplification (WA)NAND 특성상 블록 단위 이레이즈 필요, 실제 쓰기량이 호스트 쓰기량보다 큼정렬된 순차 쓰기, 4K 정렬 파티션
Garbage Collection (GC)유효 페이지 이동 후 블록 이레이즈, I/O 레이턴시 스파이크 유발Over-provisioning 확보, TRIM 활성화
TRIM 미적용삭제된 블록 정보가 SSD에 전달되지 않아 GC 효율 저하fstrim.timer 활성화
Over-provisioning 부족예비 공간 부족으로 GC 빈도 증가전체 용량의 10-20% 미사용 유지
Steady-state 진입초기 성능(FOB) 대비 장기 사용 후 성능 하락벤치마크 시 steady-state 기준 사용
온도 쓰로틀링NAND 온도 상승 시 성능 자동 제한방열 확보, SMART 온도 모니터링
Over-provisioning: 예를 들어 500GB SSD에서 실제 사용 파티션을 450GB로 제한하면 나머지 50GB가 SSD 컨트롤러의 GC와 웨어 레벨링에 활용되어 성능과 수명이 향상됩니다. 서버 환경에서는 이 여유 공간이 특히 중요합니다.
SATA OOB 시그널링 & 링크 초기화 시퀀스 Host Device COMRESET 버스트: 175.6ns ON / 175.6ns OFF x 6쌍 간격: 480ns OFF 구간 COMINIT (= COMRESET 응답) 동일한 버스트 패턴 (디바이스→호스트) COMWAKE (Host) 버스트: 106.7ns ON / 106.7ns OFF x 6쌍 COMWAKE (Device 응답) 속도 협상 (D10.2 Pattern) 최고 속도(Gen3)부터 시도, 실패 시 하위 속도로 링크 성립 (PxSSTS.DET = 3) ALIGN primitive 교환 → 정상 통신 개시

OOB 시그널링 상세

OOB(Out-of-Band) 시그널링은 SATA PHY 레벨에서 링크 초기화를 위해 사용하는 특수 신호입니다. 정상적인 데이터 전송과 구별되는 전기적 패턴으로, PHY가 꺼진 상태에서도 동작합니다.

시그널버스트 패턴간격용도
COMRESET175.6ns burst x 6480ns gap호스트→디바이스 리셋 요청
COMINIT175.6ns burst x 6480ns gap디바이스→호스트 리셋 응답
COMWAKE106.7ns burst x 6106.7ns gap슬립 상태에서 웨이크업
/* drivers/ata/libata-core.c — COMRESET 수행 */
int sata_link_hardreset(struct ata_link *link,
    const unsigned long *timing, unsigned long deadline,
    bool *online, int (*check_ready)(struct ata_link *))
{
    u32 scontrol;

    /* 1. SControl에 DET=1 쓰기 → COMRESET 시작 */
    sata_scr_read(link, SCR_CONTROL, &scontrol);
    scontrol = (scontrol & 0x0f0) | 0x01;  /* DET = 1 */
    sata_scr_write(link, SCR_CONTROL, scontrol);
    msleep(timing[0]);  /* 최소 COMRESET 기간 대기 */

    /* 2. DET=0으로 복원 → COMRESET 종료, 속도 협상 시작 */
    scontrol = (scontrol & 0x0f0) | 0x00;
    sata_scr_write(link, SCR_CONTROL, scontrol);

    /* 3. PxSSTS.DET == 3 (링크 성립) 대기 */
    return ata_wait_after_reset(link, deadline, check_ready);
}

PxSSTS.DET 값 해석

DET 값의미
0디바이스 미감지, PHY 통신 없음
1디바이스 감지됨, PHY 통신 미성립
3디바이스 감지, PHY 통신 성립 (정상)
4PHY 오프라인 모드

속도 협상 실패 시 동작

속도 협상은 최고 속도(Gen3, 6Gbps)부터 시도하며, PHY 레벨에서 D10.2 패턴을 성공적으로 교환하지 못하면 한 단계 낮은 속도로 재시도합니다. 리눅스 커널에서는 PxSCTL.SPD 필드로 최대 속도를 제한할 수 있습니다.

PxSCTL.SPD의미
0제한 없음 (최고 속도 시도)
1Gen1 (1.5 Gbps)로 제한
2Gen2 (3.0 Gbps)로 제한
3Gen3 (6.0 Gbps)로 제한
# 속도 제한 적용 (커널 파라미터)
# 특정 포트를 3Gbps로 제한
libata.force=1:3.0Gbps

# SStatus로 현재 협상된 속도 확인
# SStatus[7:4] = SPD: 1=Gen1, 2=Gen2, 3=Gen3
# SStatus[3:0] = DET: 3=링크 성립
dmesg | grep "SATA link up"
# ata1: SATA link up 6.0 Gbps (SStatus 133 SControl 300)
# SStatus 133: DET=3(링크OK), SPD=3(Gen3), IPM=1(Active)

Staggered Spin-up

다수의 HDD가 동시에 전원이 인가되면 순간 전류가 급등하여 전원 공급이 불안정해질 수 있습니다. AHCI의 Staggered Spin-up 기능은 포트별로 순차적으로 디스크 모터를 시작하여 이 문제를 해결합니다. CAP.SSS 비트로 지원 여부를 확인하며, PxCMD.SUD(Spin-Up Device) 비트를 설정하여 개별 포트의 스핀업을 제어합니다.

/* Staggered Spin-up 절차 */
if (hpriv->cap & HOST_CAP_SSS) {
    /* 각 포트를 순차적으로 스핀업 */
    for (i = 0; i < host->n_ports; i++) {
        u32 cmd = readl(port_mmio + PORT_CMD);
        cmd |= PORT_CMD_SPIN_UP;  /* SUD = 1 */
        writel(cmd, port_mmio + PORT_CMD);
        msleep(10);  /* 포트 간 지연 */
    }
}

PHY 캘리브레이션과 이퀄라이저

SATA PHY는 링크 초기화 과정에서 신호 무결성을 보장하기 위해 자동 캘리브레이션을 수행합니다. 특히 6Gbps(Gen3) 동작 시에는 고주파 신호 감쇠와 ISI(Inter-Symbol Interference)가 심해지므로 PHY 이퀄라이저가 중요합니다.

파라미터역할Gen3 영향
TX Amplitude송신 신호 진폭 조절케이블 길이에 따라 증폭 필요
TX Pre-emphasis고주파 성분 미리 강조긴 케이블에서 신호 보상
TX De-emphasis저주파 성분 감쇠반사파 감소
RX Equalization수신측 신호 보정 (CTLE/DFE)고주파 감쇠 보상
/* 일부 SoC AHCI 드라이버는 PHY 파라미터를 Device Tree로 설정 */
sata-phy@e0003000 {
    compatible = "vendor,sata-phy";
    reg = <0xe0003000 0x100>;
    tx-amplitude-millivolt = <600>;  /* TX 진폭: 600mV */
    tx-de-emphasis-db = <6>;         /* DE-emphasis: -6dB */
};

스프레드 스펙트럼 클럭킹 (SSC)

SSC(Spread Spectrum Clocking)는 클럭 주파수를 의도적으로 미세하게 변동시켜 전자파 방출(EMI)의 피크 에너지를 분산시키는 기술입니다. SATA 스펙은 ±0.5% (5000ppm) 범위의 다운-스프레드를 허용합니다.

항목
주파수 변동 범위-0.5% ~ 0% (다운스프레드)
변조 주파수30~33 kHz
목적EMI 방출 피크 저감 (FCC/CE 인증)
링크 마진 영향수신측 CDR(Clock Data Recovery) 추적 범위 내
호환성호스트/디바이스 모두 SSC 지원 필요 (Gen3 필수)
SSC와 링크 안정성: SSC가 활성화되면 수신측 CDR(Clock Data Recovery) 회로가 주파수 변동을 추적해야 합니다. 일부 구형 디바이스에서는 SSC로 인한 링크 불안정이 보고되었습니다. BIOS에서 SSC를 비활성화하면 해결될 수 있지만, EMI 규정 미준수 위험이 있습니다.

SATA 링크 초기화 타임아웃

libata는 다양한 상황에 맞는 타임아웃 값을 정의합니다. 이 값들은 include/linux/libata.h에서 확인할 수 있습니다.

상수명기본값용도
ATA_TMOUT_BOOT30초부팅 시 초기 디바이스 감지 대기
ATA_TMOUT_BOOT_QUICK7초빠른 부팅 모드 디바이스 감지
ATA_TMOUT_INTERNAL30초내부 명령 타임아웃
ATA_TMOUT_INTERNAL_QUICK5초빠른 내부 명령 (IDENTIFY 등)
ATA_TMOUT_FLUSH40초FLUSH CACHE 명령
ATA_TMOUT_PMP_SRST10초PMP 소프트 리셋
SATA_COMRESET_DELAY1ms + 대기COMRESET 최소 지속 시간
# 커널 로그에서 링크 초기화 타이밍 분석
dmesg -T | grep -E "ata[0-9]+:" | head -20
# [Sun Mar 09 10:00:01] ahci 0000:00:1f.2: AHCI 0001.0301
# [Sun Mar 09 10:00:01] ahci 0000:00:1f.2: 6 ports detected
# [Sun Mar 09 10:00:01] ata1: SATA max UDMA/133 ... irq 30
# [Sun Mar 09 10:00:02] ata1: SATA link up 6.0 Gbps  ← +1초: OOB 완료
# [Sun Mar 09 10:00:02] ata1.00: ATA-10: Samsung SSD ...   ← IDENTIFY
# [Sun Mar 09 10:00:02] ata1.00: configured for UDMA/133   ← SET FEATURES
# [Sun Mar 09 10:00:32] ata6: SATA link down   ← +30초: 빈 포트 타임아웃
# → 빈 포트는 ATA_TMOUT_BOOT(30초) 후 포기

일반적인 실수와 올바른 패턴

실수 1: M.2 SATA vs M.2 NVMe 혼동
M.2 SATA SSD를 NVMe 전용 M.2 슬롯에 꽂거나, NVMe SSD를 SATA 전용 슬롯에 꽂으면 물리적으로는 맞아도 전혀 인식되지 않습니다. 메인보드 매뉴얼에서 M.2 슬롯의 지원 인터페이스를 반드시 확인하세요.
올바른 패턴: B+M key SSD는 대부분 SATA입니다. M key only SSD는 대부분 NVMe입니다. lsblk -d -o NAME,TRAN으로 TRAN이 sata이면 SATA M.2, nvme 디바이스는 /dev/nvmeN으로 나타납니다.
실수 2: BIOS에서 IDE/Legacy 모드 사용
BIOS/UEFI에서 SATA 모드를 IDE/Legacy로 설정하면 AHCI 기능(NCQ, 핫 플러그, ALPM)을 전혀 사용할 수 없습니다. SSD의 NCQ 없이는 성능이 크게 저하됩니다.
올바른 패턴: BIOS/UEFI에서 SATA 모드를 반드시 AHCI로 설정하세요. Windows가 이미 IDE 모드로 설치된 경우 레지스트리 수정 후 AHCI로 전환해야 합니다. 리눅스는 대부분 자동으로 AHCI 드라이버를 로드합니다.
실수 3: ALPM으로 인한 레이턴시 스파이크
min_power 정책을 설정하면 Slumber 상태에서 깨어나는 데 ~10ms가 걸려 간헐적인 I/O 레이턴시 스파이크가 발생합니다. 데이터베이스 서버에서 특히 문제됩니다.
올바른 패턴: 서버/데스크톱에서는 max_performance, 노트북에서는 med_power_with_dipm을 권장합니다. min_power는 극단적인 배터리 절약이 필요한 경우에만 사용하세요.
실수 4: NCQ를 불필요하게 비활성화
과거 특정 디바이스의 NCQ 버그 때문에 libata.force=noncq를 설정한 채 잊어버리는 경우가 있습니다. NCQ 없이 SSD를 사용하면 성능이 크게 저하됩니다.
올바른 패턴: NCQ 문제가 의심되면 dmesg에서 NCQ 관련 에러를 확인하고, 특정 포트/디바이스에만 NCQ를 비활성화하세요 (libata.force=1:noncq). 문제가 해결되면 반드시 원복하세요.
실수 5: 케이블 품질 무시
저가형 SATA 데이터 케이블은 6Gbps에서 신호 무결성 문제를 일으킬 수 있습니다. CRC 에러(SMART ID 199)가 증가하거나, 링크가 3Gbps로 다운그레이드되는 증상이 나타납니다.
올바른 패턴: SATA III 인증 케이블을 사용하고, 케이블 길이는 가능한 짧게(50cm 이하) 유지하세요. 구부림 반경이 작은 L자 커넥터를 사용하면 접촉 불량 위험이 줄어듭니다. SMART의 UDMA CRC Error Count(ID 199)를 정기적으로 모니터링하세요.
실수 6: Write Cache 활성화 + 배터리 백업 없음
Write Cache는 성능을 크게 향상시키지만, 정전 시 캐시의 데이터가 유실됩니다. 파일시스템 저널링만으로는 Write Cache 유실을 완전히 보호하지 못합니다.
올바른 패턴: 서버 환경에서 Write Cache를 사용하려면 반드시 UPS를 설치하고, UPS 소프트웨어(apcupsd, nut)가 정전 감지 시 안전한 셧다운을 수행하도록 설정하세요. 또는 hdparm -W0 /dev/sda로 Write Cache를 비활성화하세요.
실수 7: TRIM이 동작하지 않는 경우
SATA SSD를 사용하면서 TRIM을 설정하지 않으면 시간이 지남에 따라 쓰기 성능이 저하됩니다. 또한 USB-SATA 브리지(외장 케이스)를 통해 연결된 SSD는 대부분의 USB 브리지 칩이 TRIM(UNMAP) 명령을 전달하지 않아 TRIM이 동작하지 않습니다.
올바른 패턴: 내장 SATA 연결 시 fstrim.timer를 활성화하세요. USB-SATA 브리지 환경에서는 lsblk -DDISC-MAX가 0이 아닌지 확인하세요. UAS(USB Attached SCSI) 모드를 지원하는 최신 브리지 칩(ASM2362 등)은 TRIM을 전달할 수 있습니다.
실수 8: SATA 포트 번호와 디바이스 이름 불일치
BIOS에서 보이는 SATA 포트 번호(Port 0, 1, 2...)와 리눅스의 ata1, ata2 번호, 그리고 /dev/sda, /dev/sdb 이름이 서로 일치하지 않는 경우가 흔합니다. 디바이스 추가/제거 시 sdX 이름이 바뀔 수도 있어, 잘못된 디스크를 포맷하는 사고가 발생할 수 있습니다.
올바른 패턴: /dev/sdX 대신 안정적인 디바이스 경로를 사용하세요: /dev/disk/by-id/ata-MODEL_SERIAL (모델+시리얼 기반), /dev/disk/by-uuid/UUID (파일시스템 UUID), /dev/disk/by-path/pci-0000:00:1f.2-ata-1 (PCI 경로 기반). fstab에서도 UUID를 사용하세요.
실수 9: RAID 컨트롤러의 AHCI 모드 혼동
일부 RAID 컨트롤러(특히 Intel RST)는 BIOS에서 AHCI 모드로 설정해도 내부적으로 RAID 기능이 활성화되어 있을 수 있습니다. 이 경우 리눅스에서 순수 AHCI 드라이버 대신 ahci의 RAID 변형이 로드되거나, RAID 메타데이터가 디스크에 남아 있어 예기치 않은 동작이 발생할 수 있습니다.
올바른 패턴: Intel RST를 사용하지 않을 경우 BIOS에서 SATA 모드를 순수 "AHCI"로 설정하세요. 기존 RAID 메타데이터가 남아 있으면 mdadm --zero-superblock /dev/sdX로 제거하세요. dmraid -r로 가짜 RAID(FakeRAID) 메타데이터도 확인하세요.

자주 묻는 질문 (FAQ)

질문답변
SATA SSD에서 NCQ가 필요한가요?필요합니다. NCQ는 SSD 내부의 병렬 NAND 채널을 활용하여 랜덤 I/O 성능을 크게 향상시킵니다. queue_depth=32가 정상입니다.
SATA 케이블 길이 제한은?SATA 스펙상 최대 1미터입니다. 가능한 50cm 이하를 권장하며, 6Gbps에서는 케이블 품질이 중요합니다.
SATA 핫 플러그는 안전한가요?AHCI 모드에서 핫 플러그를 지원합니다. 단, 마운트된 파일시스템은 반드시 언마운트 후 제거하세요. echo 1 > /sys/block/sdX/device/delete로 안전 제거합니다.
SATA와 NVMe 중 어떤 것을 선택해야 하나요?성능이 중요하면 NVMe, 비용 효율과 호환성이 중요하면 SATA입니다. SATA SSD의 순차 성능은 ~550MB/s로 인터페이스 한계에 도달합니다.
dmesg에서 "configured for UDMA/133"은 무엇인가요?SATA 디바이스가 libata의 SAT 레이어를 통해 UDMA mode 6(133MB/s)으로 설정된 것입니다. 실제 SATA 대역폭(600MB/s)과는 다른 레거시 표현입니다.
SATA 포트 수가 부족하면?PCI Express SATA 확장 카드(ASM1166 등)를 사용하거나, SATA Port Multiplier를 사용합니다. 단, PMP는 대역폭을 공유합니다.

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.

외부 참고 자료

자료설명
AHCI 1.3.1 SpecificationIntel의 공식 AHCI 스펙 문서 (레지스터 상세, 동작 모델)
Serial ATA Revision 3.5aSATA-IO의 공식 SATA 프로토콜 스펙
ATA/ATAPI Command Set (ACS-4)T13 위원회의 ATA 명령어 셋 표준
SCSI/ATA Translation (SAT-5)T10 위원회의 SAT 표준
drivers/ata/ (커널 소스)libata, ahci 드라이버 소스 코드
Documentation/ABI/testing/sysfs-ata커널 sysfs ATA 인터페이스 문서
smartmontools.orgsmartctl/smartd 공식 문서와 디바이스 데이터베이스

추천 커널 소스 읽기 순서

libata/AHCI 서브시스템을 이해하기 위해 다음 순서로 소스 코드를 읽는 것을 권장합니다.

순서파일핵심 내용
1include/linux/libata.h핵심 구조체 정의: ata_host, ata_port, ata_device, ata_queued_cmd. 전체 아키텍처의 뼈대
2include/linux/ata.hATA 레지스터, 명령 코드, 상태/에러 비트 상수. ATA 프로토콜 레퍼런스
3drivers/ata/libata-core.clibata 코어: 디바이스 감지, IDENTIFY 처리, 전송 모드 설정, 모듈 초기화
4drivers/ata/libata-scsi.cSCSI-ATA Translation(SAT): SCSI CDB를 ATA 명령으로 변환, scsi_host_template
5drivers/ata/ahci.hAHCI 레지스터 정의, 포트 구조체, CAP/GHC 비트 상수
6drivers/ata/libahci.cAHCI 공통 함수: 인터럽트 핸들러, 포트 시작/정지, QC 발행/완료
7drivers/ata/ahci.cPCI AHCI 드라이버: PCI probe, 칩셋별 quirk, 모듈 등록
8drivers/ata/libata-eh.c에러 핸들링: EH 상태 머신, autopsy(원인 분석), 리셋 절차, 속도 다운그레이드

관련 RFC/표준 문서

표준관리 기관최신 버전핵심 추가 사항
ATA/ATAPI Command SetT13 (INCITS)ACS-6ZAC(Zoned), Streams, Concurrent Positioning
AT AttachmentT13ATA-8SATA 통합, NCQ, 48-bit LBA 확장
SCSI/ATA TranslationT10 (INCITS)SAT-5ATA passthrough 개선, ZBC/ZAC 매핑
Serial ATASATA-IORevision 3.5aDevSleep, MDAT(Multilink), 하이브리드 정보
AHCIIntel1.3.1FBS(FIS-Based Switching), Enclosure Management
SFF-8xxxSFF CommitteeSFF-8482 등커넥터/케이블 물리 규격
표준 문서 입수: SATA-IO 스펙은 sata-io.org에서 회원 가입 후 무료 열람 가능합니다. T13/T10 표준은 INCITS(incits.org)에서 구매하거나, 공개된 드래프트 버전을 t13.org/t10.org에서 다운로드할 수 있습니다. Intel AHCI 스펙은 인터넷에서 PDF로 무료 배포됩니다.