eMMC와 UFS의 host 경계를 나눠 읽기
eMMC는 MMC core와 선택한 host controller driver를 거친다. 이 글의 host 예시는 SDHCI다. UFS는 SCSI 명령을 UFS HBA가 받아 tag별 LRB를 준비하는 다른 경로다. 두 저장 장치의 요청 객체·완료 callback을 하나의 직선 호출열로 합치지 않는다.
mmc_start_request카드·요청 검사__mmc_start_requestretune·SDIO busy 분기host->ops->requestSDHCI ops라면 sdhci_requestufshcd_queuecommandHBA 상태·tag 식별ufshcd_send_commandMCQ SQ 또는 legacy doorbellscsi_done두 레인 사이에는 호출 화살표가 없다. sdhci_request는 SDHCI host ops가 설치된 경우에만 MMC core의 callback 대상이며 모든 eMMC host가 SDHCI인 것은 아니다.
drivers/mmc/core/core.c:335–364 · drivers/mmc/host/sdhci.c:3051–3054 · drivers/ufs/core/ufshcd.c:2818–2908
대표 코드와 실제 진입점의 위치
v6.6의 mmc_start_request()는 카드 제거와 요청 준비를 검사한 뒤 내부 시작 함수를 호출한다. MMC core의 host->ops->request가 SDHCI로 가는 조건은 SDHCI ops 테이블에 .request = sdhci_request가 설치된 경우다. UFS의 .queuecommand와 연결되지 않는다.
| 층 | v6.6 고정 원문 | 읽을 범위 |
|---|---|---|
| MMC core | drivers/mmc/core/core.c:216–263 | 기존 40줄은 앞 함수 끝과 내부 시작 함수; 실제 mmc_start_request는 뒤쪽 |
| SDHCI host | drivers/mmc/host/sdhci.c:2176–2213 | host ops가 이 callback을 고를 때의 card-present·send 분기 |
| UFS core | drivers/ufs/core/ufshcd.c:2818–2908 | SCSI 상태 분기·LRB/SG 준비 후 제출 |
| UFS submit | drivers/ufs/core/ufshcd.c:2157–2191 | 새 세 번째 원문: MCQ와 legacy 전송 경계 |
drivers/mmc/host/sdhci.c:3051–3054 · drivers/ufs/core/ufshcd.c:2818–2822
대표 코드
mmc_request_done 끝 → __mmc_start_request 시작
mrq->done(mrq);
}
EXPORT_SYMBOL(mmc_request_done);
static void __mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
int err;
/* Assumes host controller has been runtime resumed by mmc_claim_host */
err = mmc_retune(host);
if (err) {
mrq->cmd->error = err;
mmc_request_done(host, mrq);
return;
}
/*
* For sdio rw commands we must wait for card busy otherwise some
* sdio devices won't work properly.
* And bypass I/O abort, reset and bus suspend operations.
*/
if (sdio_is_io_busy(mrq->cmd->opcode, mrq->cmd->arg) &&
host->ops->card_busy) {
int tries = 500; /* Wait aprox 500ms at maximum */
while (host->ops->card_busy(host) && --tries)
mmc_delay(1);
if (tries == 0) {
mrq->cmd->error = -EBUSY;
mmc_request_done(host, mrq);
return;
}
}
if (mrq->cap_cmd_during_tfr) {
host->ongoing_mrq = mrq;
/*
* Retry path could come through here without having waiting on
읽기 기준: 앞의 mmc_request_done() 끝 뒤 __mmc_start_request()가 시작된다. retune 실패·조건부 SDIO busy timeout은 명령 오류를 기록하고 done을 호출한다. 실제 host callback 호출은 발췌 뒤쪽 원문에 있다.
SDHCI 주석 끝 → sdhci_request → sdhci_request_atomic 시작
* *
* MMC callbacks *
* *
\*****************************************************************************/
void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host = mmc_priv(mmc);
struct mmc_command *cmd;
unsigned long flags;
bool present;
/* Firstly check card presence */
present = mmc->ops->get_cd(mmc);
spin_lock_irqsave(&host->lock, flags);
sdhci_led_activate(host);
if (sdhci_present_error(host, mrq->cmd, present))
goto out_finish;
cmd = sdhci_manual_cmd23(host, mrq) ? mrq->sbc : mrq->cmd;
if (!sdhci_send_command_retry(host, cmd, flags))
goto out_finish;
spin_unlock_irqrestore(&host->lock, flags);
return;
out_finish:
sdhci_finish_mrq(host, mrq);
spin_unlock_irqrestore(&host->lock, flags);
}
EXPORT_SYMBOL_GPL(sdhci_request);
int sdhci_request_atomic(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct sdhci_host *host = mmc_priv(mmc);
읽기 기준: 앞 네 줄은 주석 끝, 끝 세 줄은 다음 atomic 함수 시작이다. 가운데 sdhci_request()는 카드·명령 전송 분기를 보여 준다. out_finish의 finish는 완료 work를 예약하며 곧바로 MMC core done을 뜻하지 않는다.
ufshcd_send_command: MCQ / legacy 제출 분기
void ufshcd_send_command(struct ufs_hba *hba, unsigned int task_tag,
struct ufs_hw_queue *hwq)
{
struct ufshcd_lrb *lrbp = &hba->lrb[task_tag];
unsigned long flags;
lrbp->issue_time_stamp = ktime_get();
lrbp->issue_time_stamp_local_clock = local_clock();
lrbp->compl_time_stamp = ktime_set(0, 0);
lrbp->compl_time_stamp_local_clock = 0;
ufshcd_add_command_trace(hba, task_tag, UFS_CMD_SEND);
ufshcd_clk_scaling_start_busy(hba);
if (unlikely(ufshcd_should_inform_monitor(hba, lrbp)))
ufshcd_start_monitor(hba, lrbp);
if (is_mcq_enabled(hba)) {
int utrd_size = sizeof(struct utp_transfer_req_desc);
struct utp_transfer_req_desc *src = lrbp->utr_descriptor_ptr;
struct utp_transfer_req_desc *dest = hwq->sqe_base_addr + hwq->sq_tail_slot;
spin_lock(&hwq->sq_lock);
memcpy(dest, src, utrd_size);
ufshcd_inc_sq_tail(hwq);
spin_unlock(&hwq->sq_lock);
} else {
spin_lock_irqsave(&hba->outstanding_lock, flags);
if (hba->vops && hba->vops->setup_xfer_req)
hba->vops->setup_xfer_req(hba, lrbp->task_tag,
!!lrbp->cmd);
__set_bit(lrbp->task_tag, &hba->outstanding_reqs);
ufshcd_writel(hba, 1 << lrbp->task_tag,
REG_UTP_TRANSFER_REQ_DOOR_BELL);
spin_unlock_irqrestore(&hba->outstanding_lock, flags);
}
}읽기 기준: UFS의 별도 SCSI/HBA 경로다. MCQ이면 SQE를 작성하고 tail을 진행하며, 그렇지 않으면 outstanding bit와 doorbell을 사용한다. 이 호출의 끝은 장치 명령의 완료가 아니다.
요청을 받았다는 것과 제출했다는 것을 구분
기존 첫 코드 상자는 MMC 내부 시작 함수의 일부만 보이고, 두 번째는 SDHCI callback의 card-present·send 분기를 보여준다. 두 상자 사이의 실제 연결은 MMC host ops 테이블을 통해서만 말할 수 있다. UFS는 별도의 SCSI host callback과 완료를 사용한다.
mmc_start_request는 카드 제거·요청 준비 실패면 내부 시작 전에 반환한다. __mmc_start_request의 retune 실패 또는 조건부 SDIO busy timeout은 mrq->cmd->error 설정과 mmc_request_done로 끝난다. SDHCI callback에 도달해도 카드 감지 오류나 send 시도 실패는 out_finish로 간다.NON_FATAL은 오류 복구가 실제 진행 중일 때만 HOST_BUSY이며, 그렇지 않으면 준비를 계속한다. RESET과 PM 동작 외 FATAL도 재시도한다. PM 중 FATAL은 DID_BAD_TARGET, ERROR는 DID_ERROR로 즉시 오류 완료한다. 운영 상태에서도 SG 매핑 실패는 hold를 해제하고 제출하지 않는다. 성공 경로만 ufshcd_send_command에 이른다.MMC 요청 시작 함수의 반환, SDHCI command send, UFS queuecommand의 반환, 실제 장치 완료는 각각 다른 시점이다.
drivers/mmc/core/core.c:335–364 · drivers/mmc/core/core.c:216–262 · drivers/ufs/core/ufshcd.c:2818–2908
| 경로 | 실제 조건 | 결과·다음 지점 |
|---|---|---|
| MMC core 시작 | 카드 제거 / mmc_mrq_prep 오류 | host callback 이전 반환; v6.18에는 별도 확장 주소·UHS2 준비 분기 |
| MMC 내부 시작 | retune 오류 / SDIO busy timeout | 명령 오류 설정 → mmc_request_done; 정상일 때 host ops request |
| 선택한 SDHCI host | sdhci_present_error / send retry 실패 | sdhci_finish_mrq가 완료 work를 예약; 즉시 MMC done으로 단정 금지 |
| UFS queue | HBA 상태 / SG map 오류 / 정상 | HOST_BUSY 또는 즉시 done / release / MCQ·legacy submit |
MMC request와 UFS LRB의 소유권
mmc_host는 host->ops callback을 보유한다. mmc_request의 cmd 오류와 done callback은 MMC core의 상태다. SDHCI 구현은 mmc_priv(mmc)로 얻은 sdhci_host를 사용한다.scsi_cmnd와 request tag로 ufs_hba.lrb[tag]를 찾는다. SG/PRDT와 UTRD를 준비한 뒤 MCQ SQ 또는 legacy doorbell에 제출한다. 완료는 결과와 자원 해제 뒤 scsi_done를 호출한다.동일한 “storage I/O”라는 이름만으로 mmc_request를 UFS LRB에 대응시키거나 같은 DMA·완료 callback으로 설명하지 않는다.
| 객체 | 사용자·역할 | 완료 경계 |
|---|---|---|
mmc_host / mmc_request | MMC core가 host ops와 요청·명령 오류를 관리 | mmc_request_done 또는 host callback의 완료 정책 |
sdhci_host | SDHCI host 구현, lock과 command·data 상태 | finish는 완료 work 예약; 뒤에서 request_done override 또는 MMC core |
ufs_hba / LRB | tag별 UFS 요청 정보·UPIU/UTRD·SG 매핑 | UFS completion이 result·DMA/hold 처리 후 SCSI done |
drivers/mmc/core/core.c:139–174 · drivers/mmc/host/sdhci.c:3185–3189 · drivers/ufs/core/ufshcd.c:5382–5407
host 제출 방식과 완료 경계를 나눠 표시
MMC/SDHCI의 완료 예약과 UFS의 MCQ/legacy 전송은 서로 다른 설계다. 특히 SDHCI의 sdhci_finish_mrq 호출을 즉시 mmc_request_done 반환으로 읽지 않는다.
ufshcd_hold → tag별 LRB/UPIU 준비 → SG mapping. 오류면 release 후 제출 없음.is_mcq_enabled(hba)가 참이면 UTRD를 hardware queue의 SQE에 복사하고 tail을 진행한다.완료는 별도 CQE/IRQ 처리 경로다. SCSI 명령인 경우 완료 코드가 결과를 정하고 자원을 해제한 뒤 scsi_done를 부른다. 이 버전의 UFS 제출 분기는 is_mcq_enabled(hba)를 검사한다.
drivers/ufs/core/ufshcd.c:2157–2191 · drivers/ufs/core/ufshcd.c:5382–5407
| 단계 | 즉시 분기 | 완료와 구분 |
|---|---|---|
| MMC core | retune/SDIO busy 오류면 mmc_request_done 호출 | 일반 host 제출에 도달하지 않을 수 있음 |
| SDHCI request | present/send 오류면 sdhci_finish_mrq | finish는 work를 예약; 나중에 override 또는 core done |
| UFS queuecommand | 조건부 HOST_BUSY / PM 중 FATAL·ERROR 오류 완료 / SG map release | 실제 send에 도달하지 않은 경우를 분리 |
| UFS send/complete | MCQ SQ 또는 legacy doorbell; 이후 completion | send 기록은 장치 완료 증거가 아님 |
오류·완료를 읽을 때 유지할 구분
retune/SDIO busy 오류는 명령 오류 설정과 mmc_request_done 뒤 끝난다. host callback 제출과 섞지 않는다.
sdhci_request의 out_finish는 완료 work 예약 경로다. host의 request_done override 여부에 따라 나중의 core done 호출이 달라진다.
ufshcd_queuecommand의 상태·SG map 분기를 통과한 경우에만 MCQ 또는 legacy 제출로 간다.
완료에서 scsi_done 전 결과와 DMA/hold 자원을 정리한다. SCSI done 뒤 LRB를 다시 만지지 말라는 원문 주석을 따른다.
읽기 전용 진단으로 어느 경계에 있는지 확인
MMC 요청 시작·완료와 UFS send·complete는 각각 다른 source 지점이다. 우선 사용 가능한 host·event 목록과 로그를 읽고, tracer/filter를 공용 시스템에서 변경하지 않는다. tracefs가 없으면 소스에 보이는 tracepoint를 실제 사용 가능하다고 가정하지 않는다.
| 가설 | 읽기 전용 자료 | 한계 |
|---|---|---|
| 어떤 host인가 | ls /sys/class/mmc_host 또는 ls /sys/class/scsi_host | 디렉터리 존재만으로 실제 driver callback 확정 불가 |
| 사용 가능한 event | tracefs가 이미 있으면 grep -E '^(mmc|ufs):' /sys/kernel/tracing/available_events | event 이름과 활성 여부는 빌드별 |
| MMC 요청 시작·완료 | 고정 소스의 trace_mmc_request_start/done 위치와 로그 비교 | 시작 trace가 host 제출·완료를 보장하지 않음 |
| UFS 전송·완료 | 고정 소스의 UFS_CMD_SEND/COMP 기록과 tag·결과 비교 | 전송 기록만으로 장치 완료 단정 불가 |
drivers/mmc/core/core.c:256–263 · drivers/mmc/core/core.c:159–166 · drivers/ufs/core/ufshcd.c:2167–2173 · drivers/ufs/core/ufshcd.c:5392–5402
선택한 장치에서 확인할 질문
- 대상 eMMC host의
mmc_host_ops.request가 실제로 SDHCI 구현을 가리키는가, 다른 host driver인가? - MMC 요청이 카드·준비·retune·SDIO busy 중 어디서 끝났고, SDHCI callback까지 도달했는가?
- SDHCI finish 뒤 어떤 완료 work와
request_doneoverride가 사용됐는가? - UFS 명령은 queuecommand의 HBA 상태·SG map을 통과했으며 MCQ와 legacy 중 어디에 제출됐는가?
- send 로그와 completion 결과·
scsi_done를 같은 tag 기준으로 분리해 확인했는가?