ATF BL1 UFS初始化简单分析
ATF bl1 ufshc_dme_get/set处理流程分析
https://github.com/ARM-software/arm-trusted-firmware
可以通过下面的命令来下载ATF的代码,或者通过打包下载的方式也可以。
git clone git@github.com:ARM-software/arm-trusted-firmware.git
ATF BL1/BL2 ufs_read_blocks/ufs_write_blocks的处理流程是类似的,只ufs_send_cmd下传的命令是有区别的,在ufs_prepare_cmd的时候需要依据ufs_send_cmd传递的命令参数去组织cmd UPIU。
It shows how a UFS host is connected to a UFS device, the position of UFS host controller and its related UFS HCI interface.
它显示了 UFS 主机与 UFS 设备的连接方式、UFS 主机控制器的位置及其相关的 UFS HCI 接口。
The UFS host consists of the application which wishes to communicate with the UFS device. It communicates with the device using the UFS driver. The UFS driver is meant for managing the UFS host controller through the UFS HCI (UFS Host Controller Interface). The UFS HCI is basically a set of registers exposed by the host controller.
UFS 主机由希望与 UFS 设备通信的应用程序组成。它使用 UFS 驱动程序与设备通信。UFS 驱动程序用于通过 UFS 主控制器接口(UFS HCI)管理 UFS 主控制器。UFS HCI 基本上是主控制器公开的一组寄存器。
ufs_send_cmd(&utrd, CDBCMD_READ_10, lun, lba, buf, size);
size_t ufs_read_blocks(int lun, int lba, uintptr_t buf, size_t size)
{
utp_utrd_t utrd;
resp_upiu_t *resp;
assert((ufs_params.reg_base != 0) &&
(ufs_params.desc_base != 0) &&
(ufs_params.desc_size >= UFS_DESC_SIZE));
ufs_send_cmd(&utrd, CDBCMD_READ_10, lun, lba, buf, size);
#ifdef UFS_RESP_DEBUG
dump_upiu(&utrd);
#endif
/*
* Invalidate prefetched cache contents before cpu
* accesses the buf.
*/
inv_dcache_range(buf, size);
resp = (resp_upiu_t *)utrd.resp_upiu;
return size - resp->res_trans_cnt;
}
ufs_send_cmd(&utrd, CDBCMD_WRITE_10, lun, lba, buf, size);
size_t ufs_write_blocks(int lun, int lba, const uintptr_t buf, size_t size)
{
utp_utrd_t utrd;
resp_upiu_t *resp;
assert((ufs_params.reg_base != 0) &&
(ufs_params.desc_base != 0) &&
(ufs_params.desc_size >= UFS_DESC_SIZE));
ufs_send_cmd(&utrd, CDBCMD_WRITE_10, lun, lba, buf, size);
#ifdef UFS_RESP_DEBUG
dump_upiu(&utrd);
#endif
resp = (resp_upiu_t *)utrd.resp_upiu;
return size - resp->res_trans_cnt;
}
get_utrd(utrd);
ufs_prepare_cmd(utrd, cmd_op, lun, lba, buf, length);
ufs_send_request(utrd->task_tag);
ufs_check_resp(utrd, RESPONSE_UPIU, CMD_TIMEOUT_MS);
static void ufs_send_cmd(utp_utrd_t *utrd, uint8_t cmd_op, uint8_t lun, int lba, uintptr_t buf,
size_t length)
{
int result, i;
for (i = 0; i < UFS_CMD_RETRIES; ++i) {
get_utrd(utrd);
result = ufs_prepare_cmd(utrd, cmd_op, lun, lba, buf, length);
assert(result == 0);
ufs_send_request(utrd->task_tag);
result = ufs_check_resp(utrd, RESPONSE_UPIU, CMD_TIMEOUT_MS);
if (result == 0 || result == -EIO) {
break;
}
}
assert(result == 0);
(void)result;
}
get_utrd
函数用于获取一个公共的utrd结构hd->ucdba = utrd->upiu & UINT32_MAX; hd->ucdbau = (utrd->upiu >> 32) & UINT32_MAX;
用于UTP传输请求列表基地址的配置。/* UTP Transfer Request Descriptor */
typedef struct utrd_header {
uint32_t reserved0 : 24;
uint32_t i : 1; /* interrupt */
uint32_t dd : 2; /* data direction */
uint32_t reserved1 : 1;
uint32_t ct : 4; /* command type */
uint32_t reserved2;
uint32_t ocs : 8; /* Overall Command Status */
uint32_t reserved3 : 24;
uint32_t reserved4;
uint32_t ucdba; /* aligned to 128-byte */
uint32_t ucdbau; /* Upper 32-bits */
uint32_t rul : 16; /* Response UPIU Length */
uint32_t ruo : 16; /* Response UPIU Offset */
uint32_t prdtl : 16; /* PRDT Length */
uint32_t prdto : 16; /* PRDT Offset */
} utrd_header_t; /* 8 words with little endian */
typedef struct utp_utrd {
uintptr_t header; /* utrd_header_t */
uintptr_t upiu;
uintptr_t resp_upiu;
uintptr_t prdt;
size_t size_upiu;
size_t size_resp_upiu;
size_t prdt_length;
int task_tag;
} utp_utrd_t;
static void get_utrd(utp_utrd_t *utrd)
{
uintptr_t base;
int result;
utrd_header_t *hd;
assert(utrd != NULL);
result = is_slot_available();
assert(result == 0);
/* clear utrd */
memset((void *)utrd, 0, sizeof(utp_utrd_t));
base = ufs_params.desc_base;
/* clear the descriptor */
memset((void *)base, 0, UFS_DESC_SIZE);
utrd->header = base;
utrd->task_tag = 1; /* We always use the first slot */
/* CDB address should be aligned with 128 bytes */
utrd->upiu = ALIGN_CDB(utrd->header + sizeof(utrd_header_t));
utrd->resp_upiu = ALIGN_8(utrd->upiu + sizeof(cmd_upiu_t));
utrd->size_upiu = utrd->resp_upiu - utrd->upiu;
utrd->size_resp_upiu = ALIGN_8(sizeof(resp_upiu_t));
utrd->prdt = utrd->resp_upiu + utrd->size_resp_upiu;
hd = (utrd_header_t *)utrd->header;
hd->ucdba = utrd->upiu & UINT32_MAX;
hd->ucdbau = (utrd->upiu >> 32) & UINT32_MAX;
/* Both RUL and RUO is based on DWORD */
hd->rul = utrd->size_resp_upiu >> 2;
hd->ruo = utrd->size_upiu >> 2;
(void)result;
}
The COMMAND UPIU contains the basic UPIU header plus additional information needed to specify a command. The Initiator device will generate this UPIU and send it to a Target device to request a SCSI command service to be performed by the Target.
COMMAND UPIU 包含基本 UPIU 标头以及指定命令所需的附加信息。启动程序设备将生成此 UPIU 并将其发送至目标设备,以请求目标设备执行 SCSI 命令服务。
The READ (10) command requests that the Device Server read from the medium the specified number of logical block(s) and transfer them to the Application Client.
READ (10) 命令要求设备服务器从介质读取指定数量的逻辑块,并将其传输到应用程序客户端。
The Command CDB shall be sent in a single COMMAND UPIU.
命令 CDB 应在单个 COMMAND UPIU 中发送。
The RDPROTECT field is set to zero for UFS.
对于 UFS,RDPROTECT 字段设置为零。
The RESPONSE UPIU contains the basic UPIU header plus additional information indicating the command and device level status resulting from the successful or failed execution of a command. The Target will generate this UPIU and send it to the Initiator device after it has completed the requested task.
RESPONSE UPIU 包含基本的 UPIU 标头和附加信息,表明命令和设备级状态,这些状态来自命令的成功或失败执行。目标设备将生成此 UPIU,并在完成请求任务后将其发送给启动设备。
Before terminating a command which requires Data-Out data transfer and before sending the RESPONSE UPIU, the Target device shall wait until it receives all DATA OUT UPIUs related to any outstanding READY TO TRANSFER UPIUs. Also, the Target device should stop sending READY TO TRANSFER UPIUs for the command which requires Data-Out data transfer and to be terminated.
在终止需要数据输出数据传输的命令和发送 RESPONSE UPIU 之前,目标设备应等待收到与任何未完成的 "准备传输 "UPIU 相关的所有 "数据输出 "UPIU。此外,目标设备应停止为需要数据输出数据传输和终止的命令发送 READY TO TRANSFER UPIU。
The WRITE (10) UFS command requests that the Device Server transfer the specified number of logical blocks(s) from the Application Client and write them to the medium.
WRITE (10) UFS 命令要求设备服务器从应用程序客户端传输指定数量的逻辑块并将其写入介质。
The Command CDB shall be sent in a single COMMAND UPIU.
命令 CDB 应在单个命令 UPIU 中发送。
The RDPROTECT field is set to zero for UFS.
对于 UFS,RDPROTECT 字段设置为零。
ufs_send_cmd
的cmd_op去组command upiu。CDBCMD_READ_10
和CDBCMD_WRITE_10
upiu->cdb[0] = op;
CDBCMD_READ_10
对于读来说,其upiu以及command的组织形式 case CDBCMD_READ_10:
hd->dd = DD_OUT;
upiu->flags = UPIU_FLAGS_R | UPIU_FLAGS_ATTR_S;
upiu->lun = lun;
upiu->cdb[1] = RW_WITHOUT_CACHE;
/* set logical block address */
upiu->cdb[2] = (ulba >> 24) & 0xff;
upiu->cdb[3] = (ulba >> 16) & 0xff;
upiu->cdb[4] = (ulba >> 8) & 0xff;
upiu->cdb[5] = ulba & 0xff;
/* set transfer length */
upiu->cdb[7] = (lba_cnt >> 8) & 0xff;
upiu->cdb[8] = lba_cnt & 0xff;
break;
CDBCMD_WRITE_10
对于写来说,其upiu以及command的组织形式 case CDBCMD_WRITE_10:
hd->dd = DD_IN;
upiu->flags = UPIU_FLAGS_W | UPIU_FLAGS_ATTR_S;
upiu->lun = lun;
upiu->cdb[1] = RW_WITHOUT_CACHE;
/* set logical block address */
upiu->cdb[2] = (ulba >> 24) & 0xff;
upiu->cdb[3] = (ulba >> 16) & 0xff;
upiu->cdb[4] = (ulba >> 8) & 0xff;
upiu->cdb[5] = ulba & 0xff;
/* set transfer length */
upiu->cdb[7] = (lba_cnt >> 8) & 0xff;
upiu->cdb[8] = lba_cnt & 0xff;
break;
函数实现
/*
- Prepare UTRD, Command UPIU, Response UPIU.
*/
static int ufs_prepare_cmd(utp_utrd_t *utrd, uint8_t op, uint8_t lun,
int lba, uintptr_t buf, size_t length)
{
utrd_header_t *hd;
cmd_upiu_t *upiu;
prdt_t *prdt;
unsigned int ulba;
unsigned int lba_cnt;
uintptr_t desc_limit;
uintptr_t prdt_end;
hd = (utrd_header_t *)utrd->header;
upiu = (cmd_upiu_t *)utrd->upiu;
hd->i = 1;
hd->ct = CT_UFS_STORAGE;
hd->ocs = OCS_MASK;
upiu->trans_type = CMD_UPIU;
upiu->task_tag = utrd->task_tag;
upiu->cdb[0] = op;
ulba = (unsigned int)lba;
lba_cnt = (unsigned int)(length >> UFS_BLOCK_SHIFT);
switch (op) {
case CDBCMD_TEST_UNIT_READY:
break;
case CDBCMD_READ_CAPACITY_10:
hd->dd = DD_OUT;
upiu->flags = UPIU_FLAGS_R | UPIU_FLAGS_ATTR_S;
upiu->lun = lun;
break;
case CDBCMD_READ_10:
hd->dd = DD_OUT;
upiu->flags = UPIU_FLAGS_R | UPIU_FLAGS_ATTR_S;
upiu->lun = lun;
upiu->cdb[1] = RW_WITHOUT_CACHE;
/* set logical block address */
upiu->cdb[2] = (ulba >> 24) & 0xff;
upiu->cdb[3] = (ulba >> 16) & 0xff;
upiu->cdb[4] = (ulba >> 8) & 0xff;
upiu->cdb[5] = ulba & 0xff;
/* set transfer length */
upiu->cdb[7] = (lba_cnt >> 8) & 0xff;
upiu->cdb[8] = lba_cnt & 0xff;
break;
case CDBCMD_WRITE_10:
hd->dd = DD_IN;
upiu->flags = UPIU_FLAGS_W | UPIU_FLAGS_ATTR_S;
upiu->lun = lun;
upiu->cdb[1] = RW_WITHOUT_CACHE;
/* set logical block address */
upiu->cdb[2] = (ulba >> 24) & 0xff;
upiu->cdb[3] = (ulba >> 16) & 0xff;
upiu->cdb[4] = (ulba >> 8) & 0xff;
upiu->cdb[5] = ulba & 0xff;
/* set transfer length */
upiu->cdb[7] = (lba_cnt >> 8) & 0xff;
upiu->cdb[8] = lba_cnt & 0xff;
break;
default:
assert(0);
break;
}
if (hd->dd == DD_IN) {
flush_dcache_range(buf, length);
} else if (hd->dd == DD_OUT) {
inv_dcache_range(buf, length);
}
utrd->prdt_length = 0;
if (length) {
upiu->exp_data_trans_len = htobe32(length);
assert(lba_cnt <= UINT16_MAX);
prdt = (prdt_t *)utrd->prdt;
desc_limit = ufs_params.desc_base + ufs_params.desc_size;
while (length > 0) {
if ((uintptr_t)prdt + sizeof(prdt_t) > desc_limit) {
ERROR("UFS: Exceeded descriptor limit. Image is too large\n");
panic();
}
prdt->dba = (unsigned int)(buf & UINT32_MAX);
prdt->dbau = (unsigned int)((buf >> 32) & UINT32_MAX);
/* prdt->dbc counts from 0 */
if (length > MAX_PRDT_SIZE) {
prdt->dbc = MAX_PRDT_SIZE - 1;
length = length - MAX_PRDT_SIZE;
} else {
prdt->dbc = length - 1;
length = 0;
}
buf += MAX_PRDT_SIZE;
prdt++;
utrd->prdt_length++;
}
hd->prdtl = utrd->prdt_length;
hd->prdto = (utrd->size_upiu + utrd->size_resp_upiu) >> 2;
}
prdt_end = utrd->prdt + utrd->prdt_length * sizeof(prdt_t);
flush_dcache_range(utrd->header, prdt_end - utrd->header);
return 0;
}
mmio_setbits_32(ufs_params.reg_base + UTRLDBR, 1 << slot);
UFS HCI 向设备端发送一个doorbell,告诉设备端对应的slot去处理由host 发送给设备端的命令。static void ufs_send_request(int task_tag)
{
unsigned int data;
int slot;
slot = task_tag - 1;
/* clear all interrupts */
mmio_write_32(ufs_params.reg_base + IS, ~0);
mmio_write_32(ufs_params.reg_base + UTRLRSR, 1);
assert(mmio_read_32(ufs_params.reg_base + UTRLRSR) == 1);
data = UTRIACR_IAEN | UTRIACR_CTR | UTRIACR_IACTH(0x1F) |
UTRIACR_IATOVAL(0xFF);
mmio_write_32(ufs_params.reg_base + UTRIACR, data);
/* send request */
mmio_setbits_32(ufs_params.reg_base + UTRLDBR, 1 << slot);
}
data = mmio_read_32(ufs_params.reg_base + UTRLDBR);
在ufs_check_resp函数中检查UTRLDBR状态,检查对应slot位的状态值。static int ufs_check_resp(utp_utrd_t *utrd, int trans_type, unsigned int timeout_ms)
{
utrd_header_t *hd;
resp_upiu_t *resp;
sense_data_t *sense;
unsigned int data;
int slot, result;
hd = (utrd_header_t *)utrd->header;
resp = (resp_upiu_t *)utrd->resp_upiu;
result = ufs_wait_for_int_status(UFS_INT_UTRCS, timeout_ms, false);
if (result != 0) {
return result;
}
slot = utrd->task_tag - 1;
data = mmio_read_32(ufs_params.reg_base + UTRLDBR);
assert((data & (1 << slot)) == 0);
/*
* Invalidate the header after DMA read operation has
* completed to avoid cpu referring to the prefetched
* data brought in before DMA completion.
*/
inv_dcache_range((uintptr_t)hd, UFS_DESC_SIZE);
assert(hd->ocs == OCS_SUCCESS);
assert((resp->trans_type & TRANS_TYPE_CODE_MASK) == trans_type);
sense = &resp->sd.sense;
if (sense->resp_code == SENSE_DATA_VALID &&
sense->sense_key == SENSE_KEY_UNIT_ATTENTION && sense->asc == 0x29 &&
sense->ascq == 0) {
WARN("Unit Attention Condition\n");
return -EAGAIN;
}
(void)resp;
(void)slot;
(void)data;
return 0;
}