ATF BL1/BL2 ufs_read_blocks/ufs_write_blocks使用分析
- 1 ATF的下载链接
- 2 ATF BL1/BL2 ufs_read_blocks/ufs_write_blocks处理流程
- 2.1 ATF BL1/BL2 ufs_read_blocks
- 2.2 ATF BL1/BL2 ufs_write_blocks
- 3 UFS System Model
- 4 ufs_read_blocks/ufs_write_blocks详细分析
- 4.1 ufs_read_blocks
- 4.2 ufs_write_blocks
- 4.3 ufs_send_cmd
- 4.4 get_utrd
- 4.5 ufs_prepare_cmd
- 4.5.1 COMMAND UPIU
- 4.5.2 READ (10) Command
- 4.5.3RESPONSE UPIU
- 4.5.4 WRITE (10) Command
- 4.5.5 ufs_prepare_cmd
- 4.6 ufs_send_request
- 4.7 ufs_check_resp
ATF BL1 UFS初始化简单分析
ATF bl1 ufshc_dme_get/set处理流程分析
1 ATF的下载链接
https://github.com/ARM-software/arm-trusted-firmware
可以通过下面的命令来下载ATF的代码,或者通过打包下载的方式也可以。
git clone git@github.com:ARM-software/arm-trusted-firmware.git
2 ATF BL1/BL2 ufs_read_blocks/ufs_write_blocks处理流程
ATF BL1/BL2 ufs_read_blocks/ufs_write_blocks的处理流程是类似的,只ufs_send_cmd下传的命令是有区别的,在ufs_prepare_cmd的时候需要依据ufs_send_cmd传递的命令参数去组织cmd UPIU。
2.1 ATF BL1/BL2 ufs_read_blocks
2.2 ATF BL1/BL2 ufs_write_blocks
3 UFS System Model
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 基本上是主控制器公开的一组寄存器。
4 ufs_read_blocks/ufs_write_blocks详细分析
4.1 ufs_read_blocks
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;
}
4.2 ufs_write_blocks
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;
}
4.3 ufs_send_cmd
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;
}
4.4 get_utrd
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;
}
4.5 ufs_prepare_cmd
4.5.1 COMMAND UPIU
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 命令服务。
4.5.2 READ (10) Command
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 字段设置为零。
4.5.3RESPONSE UPIU
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。
4.5.4 WRITE (10) Command
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 字段设置为零。
4.5.5 ufs_prepare_cmd
- 按照
ufs_send_cmd
的cmd_op去组command upiu。 - 对于ufs的读写操作,其cmd_op分别为
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;
}
4.6 ufs_send_request
mmio_setbits_32(ufs_params.reg_base + UTRLDBR, 1 << slot);
UFS HCI 向设备端发送一个doorbell,告诉设备端对应的slot去处理由host 发送给设备端的命令。
UTP Task Management Request List DoorBell Register(UTMRLDBR): This field is bit significant. Each bit corresponds to a slot in the task management request List, where bit 0 corresponds to slot 0. A bit in this field is set by host software to indicate to the host controller that a task management request has been built in system memory for the associated task management request slot, and may be ready for execution. The host software indicates no change to request slots by setting the associated bits in this field to ‘0’. Bits in this field shall only be set to ‘1’ by host software when UTMRLRSR is set to ‘1’.
UTP任务管理请求列表门铃寄存器(UTMRLDBR):该字段是位有效的。每个位对应任务管理请求列表中的一个槽,其中位 0 对应槽 0。该字段中的位由主机软件设置,以向主机控制器指示系统内存中已为该任务建立了一个任务管理请求。关联的任务管理请求槽,并且可以准备好执行。主机软件通过将该字段中的相关位设置为“0”来指示请求时隙没有变化。当 UTMRLRSR 设置为“1”时,该字段中的位只能由主机软件设置为“1”。
When a task management request is completed (with success or error), the corresponding bit is cleared to ‘0’ by the host controller.
当任务管理请求完成(成功或错误)时,相应位被主机控制器清除为“0”。
The host controller always process task management request in-order according to the order submitted to the list. In case of multiple requests with single doorbell register ringing (batch mode), The dispatch order for these requests by host controller will base on their index in the List. A task management with lower index value will be executed before a task management request with higher index value.
主机控制器总是按照提交到列表的顺序按顺序处理任务管理请求。如果单个门铃寄存器响铃有多个请求(批处理模式),主机控制器对这些请求的调度顺序将基于它们在列表中的索引。具有较低索引值的任务管理将在具有较高索引值的任务管理请求之前执行。
This field is also cleared when UTMRLRSR is written from a ‘1’ to a ‘0’ by host software.
当主机软件将 UTMRLRSR 从“1”写入“0”时,该字段也会被清除。
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);
}
4.7 ufs_check_resp
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;
}