# eMMC와 UFS: 블록 요청을 장치 명령으로 바꾸는 경계

v6.18.37 / drivers/ufs/core/ufshcd.c

eMMC와 UFS는 둘 다 블록 저장장치로 보이지만 명령을 보내는 하부 경로는 다릅니다. eMMC는 MMC 계층을, UFS는 SCSI 명령을 UPIU와 UFS 전송 구조로 바꾸는 경로를 사용합니다. 아래 코드는 UFS 쪽 ufshcd_queuecommand 전체입니다. 두 장치에 같은 레지스터나 같은 명령 형식이 적용된다고 설명하지 않습니다.

## ufshcd_queuecommand

```c

static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
{
	struct ufs_hba *hba = shost_priv(host);
	int tag = scsi_cmd_to_rq(cmd)->tag;
	struct ufshcd_lrb *lrbp;
	int err = 0;
	struct ufs_hw_queue *hwq = NULL;

	switch (hba->ufshcd_state) {
	case UFSHCD_STATE_OPERATIONAL:
		break;
	case UFSHCD_STATE_EH_SCHEDULED_NON_FATAL:
		/*
		 * SCSI error handler can call ->queuecommand() while UFS error
		 * handler is in progress. Error interrupts could change the
		 * state from UFSHCD_STATE_RESET to
		 * UFSHCD_STATE_EH_SCHEDULED_NON_FATAL. Prevent requests
		 * being issued in that case.
		 */
		if (ufshcd_eh_in_progress(hba)) {
			err = SCSI_MLQUEUE_HOST_BUSY;
			goto out;
		}
		break;
	case UFSHCD_STATE_EH_SCHEDULED_FATAL:
		/*
		 * pm_runtime_get_sync() is used at error handling preparation
		 * stage. If a scsi cmd, e.g. the SSU cmd, is sent from hba's
		 * PM ops, it can never be finished if we let SCSI layer keep
		 * retrying it, which gets err handler stuck forever. Neither
		 * can we let the scsi cmd pass through, because UFS is in bad
		 * state, the scsi cmd may eventually time out, which will get
		 * err handler blocked for too long. So, just fail the scsi cmd
		 * sent from PM ops, err handler can recover PM error anyways.
		 */
		if (hba->pm_op_in_progress) {
			hba->force_reset = true;
			set_host_byte(cmd, DID_BAD_TARGET);
			scsi_done(cmd);
			goto out;
		}
		fallthrough;
	case UFSHCD_STATE_RESET:
		err = SCSI_MLQUEUE_HOST_BUSY;
		goto out;
	case UFSHCD_STATE_ERROR:
		set_host_byte(cmd, DID_ERROR);
		scsi_done(cmd);
		goto out;
	}

	hba->req_abort_count = 0;

	ufshcd_hold(hba);

	lrbp = &hba->lrb[tag];

	ufshcd_setup_scsi_cmd(hba, lrbp, cmd, ufshcd_scsi_to_upiu_lun(cmd->device->lun), tag);

	err = ufshcd_map_sg(hba, lrbp);
	if (err) {
		ufshcd_release(hba);
		goto out;
	}

	if (hba->mcq_enabled)
		hwq = ufshcd_mcq_req_to_hwq(hba, scsi_cmd_to_rq(cmd));

	ufshcd_send_command(hba, tag, hwq);

out:
	if (ufs_trigger_eh(hba)) {
		unsigned long flags;

		spin_lock_irqsave(hba->host->host_lock, flags);
		ufshcd_schedule_eh_work(hba);
		spin_unlock_irqrestore(hba->host->host_lock, flags);
	}

	return err;
}

```

### 3001행

```c

static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)

```

SCSI 호스트와 명령을 받아 UFS 장치에 보낼 준비를 합니다. 반환값은 명령 접수 또는 재시도 조건을 알립니다.

### 3003행

```c

	struct ufs_hba *hba = shost_priv(host);

```

SCSI 호스트에 연결된 UFS 전용 상태를 꺼냅니다.

### 3004행

```c

	int tag = scsi_cmd_to_rq(cmd)->tag;

```

SCSI 명령의 블록 request에서 태그를 얻어 UFS 요청 슬롯을 식별합니다.

### 3005행

```c

	struct ufshcd_lrb *lrbp;

```

이번 명령이 사용할 UFS 로컬 요청 블록의 포인터입니다.

### 3006행

```c

	int err = 0;

```

기본 반환값은 명령을 접수했다는 0입니다. 장치 완료 성공과 동일한 뜻은 아닙니다.

### 3007행

```c

	struct ufs_hw_queue *hwq = NULL;

```

MCQ 큐 선택 전에는 하드웨어 큐 포인터를 NULL로 둡니다.

### 3009행

```c

	switch (hba->ufshcd_state) {

```

호스트의 현재 동작·복구 상태에 따라 명령 처리 방식을 고릅니다.

### 3010행

```c

	case UFSHCD_STATE_OPERATIONAL:

```

정상적으로 명령을 처리할 수 있는 상태입니다.

### 3011행

```c

		break;

```

상태 검사만 끝내고 아래 명령 준비를 계속합니다.

### 3012행

```c

	case UFSHCD_STATE_EH_SCHEDULED_NON_FATAL:

```

치명적이지 않은 오류 복구가 예약된 상태를 처리합니다.

### 3020행

```c

		if (ufshcd_eh_in_progress(hba)) {

```

복구가 실제로 진행 중인지 확인해 리셋 도중 명령이 끼어들지 않게 합니다.

### 3021행

```c

			err = SCSI_MLQUEUE_HOST_BUSY;

```

상위 SCSI 계층에 호스트가 바쁘므로 재시도해야 한다고 알릴 값을 설정합니다.

### 3022행

```c

			goto out;

```

새 명령을 만들지 않고 공통 종료 절차로 갑니다.

### 3024행

```c

		break;

```

복구가 진행 중이 아니면 명령 준비를 계속할 수 있습니다.

### 3025행

```c

	case UFSHCD_STATE_EH_SCHEDULED_FATAL:

```

치명적 오류 복구가 예약된 상태를 구분합니다.

### 3036행

```c

		if (hba->pm_op_in_progress) {

```

전원관리 동작 중 발행된 명령인지 검사합니다. 복구와 전원관리가 서로 기다리는 상황을 피해야 합니다.

### 3037행

```c

			hba->force_reset = true;

```

이후 복구에서 강제 리셋이 필요하다는 상태를 표시합니다.

### 3038행

```c

			set_host_byte(cmd, DID_BAD_TARGET);

```

현재 명령에 DID_BAD_TARGET 호스트 오류를 기록합니다.

### 3039행

```c

			scsi_done(cmd);

```

이 명령을 즉시 오류 완료해 상위 계층이 계속 기다리지 않게 합니다.

### 3040행

```c

			goto out;

```

이미 완료했으므로 실제 장치 발행은 건너뜁니다.

### 3042행

```c

		fallthrough;

```

전원관리 특수 처리가 아니면 다음 RESET 처리와 같은 재시도 경로를 사용합니다. 의도한 switch 낙하를 명시한 표기입니다.

### 3043행

```c

	case UFSHCD_STATE_RESET:

```

호스트 리셋 중인 상태입니다.

### 3044행

```c

		err = SCSI_MLQUEUE_HOST_BUSY;

```

아직 발행할 수 없으므로 HOST_BUSY를 선택합니다.

### 3045행

```c

		goto out;

```

발행 준비를 건너뜁니다.

### 3046행

```c

	case UFSHCD_STATE_ERROR:

```

호스트가 오류 상태인 경우입니다.

### 3047행

```c

		set_host_byte(cmd, DID_ERROR);

```

명령에 일반 호스트 오류 DID_ERROR를 저장합니다.

### 3048행

```c

		scsi_done(cmd);

```

오류 결과로 명령을 완료합니다.

### 3049행

```c

		goto out;

```

이미 완료한 명령을 아래에서 다시 발행하지 않습니다.

### 3052행

```c

	hba->req_abort_count = 0;

```

새 정상 요청을 처리하며 요청 중단 시도 계수를 초기화합니다.

### 3054행

```c

	ufshcd_hold(hba);

```

명령 처리 동안 필요한 호스트 동작 상태를 유지하도록 hold를 얻습니다.

### 3056행

```c

	lrbp = &hba->lrb[tag];

```

태그를 배열 인덱스로 사용해 해당 요청 슬롯을 고릅니다. 태그는 장치 섹터 주소가 아닙니다.

### 3058행

```c

	ufshcd_setup_scsi_cmd(hba, lrbp, cmd, ufshcd_scsi_to_upiu_lun(cmd->device->lun), tag);

```

SCSI 명령과 LUN을 UFS가 사용하는 표현으로 바꾸고 태그와 함께 요청 블록을 준비합니다.

### 3060행

```c

	err = ufshcd_map_sg(hba, lrbp);

```

데이터 버퍼의 scatter-gather 목록을 DMA 전송에 맞게 매핑합니다.

### 3061행

```c

	if (err) {

```

매핑에 실패했는지 확인합니다.

### 3062행

```c

		ufshcd_release(hba);

```

발행하지 못하므로 앞서 얻은 호스트 hold를 내려놓습니다.

### 3063행

```c

		goto out;

```

오류를 보존한 채 종료합니다.

### 3066행

```c

	if (hba->mcq_enabled)

```

여러 명령 큐를 쓰는 MCQ 기능이 활성화됐는지 확인합니다.

### 3067행

```c

		hwq = ufshcd_mcq_req_to_hwq(hba, scsi_cmd_to_rq(cmd));

```

블록 request가 사용할 UFS 하드웨어 큐를 결정합니다.

### 3069행

```c

	ufshcd_send_command(hba, tag, hwq);

```

준비한 태그와 큐로 실제 명령 발행을 요청합니다. 완료를 기다리는 호출로 읽으면 안 됩니다.

### 3071행

```c

out:

```

접수, 재시도, 오류 완료 경로가 모입니다.

### 3072행

```c

	if (ufs_trigger_eh(hba)) {

```

오류 복구를 시작해야 할 조건이 생겼는지 검사합니다.

### 3073행

```c

		unsigned long flags;

```

잠금을 잡기 전의 인터럽트 상태를 보관할 변수입니다.

### 3075행

```c

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

```

인터럽트 상태를 보존하고 호스트 잠금을 잡아 복구 예약 상태를 보호합니다.

### 3076행

```c

		ufshcd_schedule_eh_work(hba);

```

오류 복구 작업을 예약합니다.

### 3077행

```c

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

```

호스트 잠금을 풀고 원래 인터럽트 상태를 복구합니다.

### 3080행

```c

	return err;

```

명령을 받아들였는지 또는 재시도가 필요한지 상위 SCSI 계층에 알립니다.

