diff options
Diffstat (limited to 'tools/src/nvme.c')
| -rw-r--r-- | tools/src/nvme.c | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/tools/src/nvme.c b/tools/src/nvme.c new file mode 100644 index 0000000..e6741eb --- /dev/null +++ b/tools/src/nvme.c @@ -0,0 +1,505 @@ +/* SPDX-License-Identifier: MIT */ + +#include "adt.h" +#include "assert.h" +#include "malloc.h" +#include "nvme.h" +#include "pmgr.h" +#include "rtkit.h" +#include "sart.h" +#include "string.h" +#include "utils.h" + +#define NVME_TIMEOUT 1000000 +#define NVME_ENABLE_TIMEOUT 5000000 +#define NVME_SHUTDOWN_TIMEOUT 5000000 +#define NVME_QUEUE_SIZE 64 + +#define NVME_CC 0x14 +#define NVME_CC_SHN GENMASK(15, 14) +#define NVME_CC_SHN_NONE 0 +#define NVME_CC_SHN_NORMAL 1 +#define NVME_CC_SHN_ABRUPT 2 +#define NVME_CC_EN BIT(0) + +#define NVME_CSTS 0x1c +#define NVME_CSTS_SHST GENMASK(3, 2) +#define NVME_CSTS_SHST_NORMAL 0 +#define NVME_CSTS_SHST_BUSY 1 +#define NVME_CSTS_SHST_DONE 2 +#define NVME_CSTS_RDY BIT(0) + +#define NVME_AQA 0x24 +#define NVME_ASQ 0x28 +#define NVME_ACQ 0x30 + +#define NVME_DB_ACQ 0x1004 +#define NVME_DB_IOCQ 0x100c + +#define NVME_BOOT_STATUS 0x1300 +#define NVME_BOOT_STATUS_OK 0xde71ce55 + +#define NVME_LINEAR_SQ_CTRL 0x24908 +#define NVME_LINEAR_SQ_CTRL_EN BIT(0) + +#define NVME_UNKNONW_CTRL 0x24008 +#define NVME_UNKNONW_CTRL_PRP_NULL_CHECK BIT(11) + +#define NVME_MAX_PEND_CMDS_CTRL 0x1210 +#define NVME_DB_LINEAR_ASQ 0x2490c +#define NVME_DB_LINEAR_IOSQ 0x24910 + +#define NVMMU_NUM 0x28100 +#define NVMMU_ASQ_BASE 0x28108 +#define NVMMU_IOSQ_BASE 0x28110 +#define NVMMU_TCB_INVAL 0x28118 +#define NVMMU_TCB_STAT 0x29120 + +#define NVME_ADMIN_CMD_DELETE_SQ 0x00 +#define NVME_ADMIN_CMD_CREATE_SQ 0x01 +#define NVME_ADMIN_CMD_DELETE_CQ 0x04 +#define NVME_ADMIN_CMD_CREATE_CQ 0x05 +#define NVME_QUEUE_CONTIGUOUS BIT(0) + +#define NVME_CMD_FLUSH 0x00 +#define NVME_CMD_WRITE 0x01 +#define NVME_CMD_READ 0x02 + +struct nvme_command { + u8 opcode; + u8 flags; + u8 tag; + u8 rsvd; // normal NVMe has tag as u16 + u32 nsid; + u32 cdw2; + u32 cdw3; + u64 metadata; + u64 prp1; + u64 prp2; + u32 cdw10; + u32 cdw11; + u32 cdw12; + u32 cdw13; + u32 cdw14; + u32 cdw15; +}; + +struct nvme_completion { + u64 result; + u32 rsvd; // normal NVMe has the sq_head and sq_id here + u16 tag; + u16 status; +}; + +struct apple_nvmmu_tcb { + u8 opcode; + u8 dma_flags; + u8 slot_id; + u8 unk0; + u32 len; + u64 unk1[2]; + u64 prp1; + u64 prp2; + u64 unk2[2]; + u8 aes_iv[8]; + u8 _aes_unk[64]; +}; + +struct nvme_queue { + struct apple_nvmmu_tcb *tcbs; + struct nvme_command *cmds; + struct nvme_completion *cqes; + + u8 cq_head; + u8 cq_phase; + + bool adminq; +}; + +static_assert(sizeof(struct nvme_command) == 64, "invalid nvme_command size"); +static_assert(sizeof(struct nvme_completion) == 16, "invalid nvme_completion size"); +static_assert(sizeof(struct apple_nvmmu_tcb) == 128, "invalid apple_nvmmu_tcb size"); + +static bool nvme_initialized = false; +static u8 nvme_die; + +static asc_dev_t *nvme_asc = NULL; +static rtkit_dev_t *nvme_rtkit = NULL; +static sart_dev_t *nvme_sart = NULL; + +static u64 nvme_base; + +static struct nvme_queue adminq, ioq; + +static bool alloc_queue(struct nvme_queue *q) +{ + memset(q, 0, sizeof(*q)); + + q->tcbs = memalign(SZ_16K, NVME_QUEUE_SIZE * sizeof(*q->tcbs)); + if (!q->tcbs) + return false; + + q->cmds = memalign(SZ_16K, NVME_QUEUE_SIZE * sizeof(*q->cmds)); + if (!q->cmds) + goto free_tcbs; + + q->cqes = memalign(SZ_16K, NVME_QUEUE_SIZE * sizeof(*q->cqes)); + if (!q->cqes) + goto free_cmds; + + memset(q->tcbs, 0, NVME_QUEUE_SIZE * sizeof(*q->tcbs)); + memset(q->cmds, 0, NVME_QUEUE_SIZE * sizeof(*q->cmds)); + memset(q->cqes, 0, NVME_QUEUE_SIZE * sizeof(*q->cqes)); + q->cq_head = 0; + q->cq_phase = 1; + return true; + +free_cmds: + free(q->cmds); +free_tcbs: + free(q->tcbs); + return false; +} + +static void free_queue(struct nvme_queue *q) +{ + free(q->cmds); + free(q->tcbs); + free(q->cqes); +} + +static void nvme_poll_syslog(void) +{ + struct rtkit_message msg; + rtkit_recv(nvme_rtkit, &msg); +} + +static bool nvme_ctrl_disable(void) +{ + u64 timeout = timeout_calculate(NVME_TIMEOUT); + + clear32(nvme_base + NVME_CC, NVME_CC_EN); + while (read32(nvme_base + NVME_CSTS) & NVME_CSTS_RDY && !timeout_expired(timeout)) + nvme_poll_syslog(); + + return !(read32(nvme_base + NVME_CSTS) & NVME_CSTS_RDY); +} + +static bool nvme_ctrl_enable(void) +{ + u64 timeout = timeout_calculate(NVME_ENABLE_TIMEOUT); + + mask32(nvme_base + NVME_CC, NVME_CC_SHN, NVME_CC_EN); + while (!(read32(nvme_base + NVME_CSTS) & NVME_CSTS_RDY) && !timeout_expired(timeout)) + nvme_poll_syslog(); + + return read32(nvme_base + NVME_CSTS) & NVME_CSTS_RDY; +} + +static bool nvme_ctrl_shutdown(void) +{ + u64 timeout = timeout_calculate(NVME_SHUTDOWN_TIMEOUT); + + mask32(nvme_base + NVME_CC, NVME_CC_SHN, FIELD_PREP(NVME_CC_SHN, NVME_CC_SHN_NORMAL)); + while (FIELD_GET(NVME_CSTS_SHST, read32(nvme_base + NVME_CSTS)) != NVME_CSTS_SHST_DONE && + !timeout_expired(timeout)) + nvme_poll_syslog(); + + return FIELD_GET(NVME_CSTS_SHST, read32(nvme_base + NVME_CSTS)) == NVME_CSTS_SHST_DONE; +} + +static bool nvme_exec_command(struct nvme_queue *q, struct nvme_command *cmd, u64 *result) +{ + bool found = false; + u64 timeout; + u8 tag = 0; + struct nvme_command *queue_cmd = &q->cmds[tag]; + struct apple_nvmmu_tcb *tcb = &q->tcbs[tag]; + + memcpy(queue_cmd, cmd, sizeof(*cmd)); + queue_cmd->tag = tag; + + memset(tcb, 0, sizeof(*tcb)); + tcb->opcode = queue_cmd->opcode; + tcb->dma_flags = 3; // always allow read+write to the PRP pages + tcb->slot_id = tag; + tcb->len = queue_cmd->cdw12; + tcb->prp1 = queue_cmd->prp1; + tcb->prp2 = queue_cmd->prp2; + + /* make sure ANS2 can see the command and tcb before triggering it */ + dma_wmb(); + + nvme_poll_syslog(); + if (q->adminq) + write32(nvme_base + NVME_DB_LINEAR_ASQ, tag); + else + write32(nvme_base + NVME_DB_LINEAR_IOSQ, tag); + nvme_poll_syslog(); + + timeout = timeout_calculate(NVME_TIMEOUT); + struct nvme_completion cqe; + while (!timeout_expired(timeout)) { + nvme_poll_syslog(); + + /* we need a DMA read barrier here since the CQ will be updated using DMA */ + dma_rmb(); + memcpy(&cqe, &q->cqes[q->cq_head], sizeof(cqe)); + if ((cqe.status & 1) != q->cq_phase) + continue; + + if (cqe.tag == tag) { + found = true; + if (result) + *result = cqe.result; + } else { + printf("nvme: invalid tag in CQ: expected %d but got %d\n", tag, cqe.tag); + } + + write32(nvme_base + NVMMU_TCB_INVAL, cqe.tag); + if (read32(nvme_base + NVMMU_TCB_STAT)) + printf("nvme: NVMMU invalidation for tag %d failed\n", cqe.tag); + + /* increment head and switch phase once the end of the queue has been reached */ + q->cq_head += 1; + if (q->cq_head == NVME_QUEUE_SIZE) { + q->cq_head = 0; + q->cq_phase ^= 1; + } + + if (q->adminq) + write32(nvme_base + NVME_DB_ACQ, q->cq_head); + else + write32(nvme_base + NVME_DB_IOCQ, q->cq_head); + break; + } + + if (!found) { + printf("nvme: could not find command completion in CQ\n"); + return false; + } + + cqe.status >>= 1; + if (cqe.status) { + printf("nvme: command failed with status %d\n", cqe.status); + return false; + } + + return true; +} + +bool nvme_init(void) +{ + if (nvme_initialized) { + printf("nvme: already initialized\n"); + return true; + } + + int adt_path[8]; + int node = adt_path_offset_trace(adt, "/arm-io/ans", adt_path); + if (node < 0) { + printf("nvme: Error getting NVMe node /arm-io/ans\n"); + return NULL; + } + + u32 cg; + if (ADT_GETPROP(adt, node, "clock-gates", &cg) < 0) { + printf("nvme: Error getting NVMe clock-gates\n"); + return NULL; + } + nvme_die = FIELD_GET(PMGR_DIE_ID, cg); + printf("nvme: ANS is on die %d\n", nvme_die); + + if (adt_get_reg(adt, adt_path, "reg", 3, &nvme_base, NULL) < 0) { + printf("nvme: Error getting NVMe base address.\n"); + return NULL; + } + + if (!alloc_queue(&adminq)) { + printf("nvme: Error allocating admin queue\n"); + return NULL; + } + if (!alloc_queue(&ioq)) { + printf("nvme: Error allocating admin queue\n"); + goto out_adminq; + } + + ioq.adminq = false; + adminq.adminq = true; + + nvme_asc = asc_init("/arm-io/ans"); + if (!nvme_asc) + goto out_ioq; + + nvme_sart = sart_init("/arm-io/sart-ans"); + if (!nvme_sart) + goto out_asc; + + nvme_rtkit = rtkit_init("nvme", nvme_asc, NULL, NULL, nvme_sart); + if (!nvme_rtkit) + goto out_sart; + + if (!rtkit_boot(nvme_rtkit)) + goto out_rtkit; + + if (poll32(nvme_base + NVME_BOOT_STATUS, 0xffffffff, NVME_BOOT_STATUS_OK, USEC_PER_SEC) < 0) { + printf("nvme: ANS did not boot correctly.\n"); + goto out_shutdown; + } + + /* setup controller and NVMMU for linear submission queue */ + set32(nvme_base + NVME_LINEAR_SQ_CTRL, NVME_LINEAR_SQ_CTRL_EN); + clear32(nvme_base + NVME_UNKNONW_CTRL, NVME_UNKNONW_CTRL_PRP_NULL_CHECK); + write32(nvme_base + NVME_MAX_PEND_CMDS_CTRL, + ((NVME_QUEUE_SIZE - 1) << 16) | (NVME_QUEUE_SIZE - 1)); + write32(nvme_base + NVMMU_NUM, NVME_QUEUE_SIZE - 1); + write64_lo_hi(nvme_base + NVMMU_ASQ_BASE, (u64)adminq.tcbs); + write64_lo_hi(nvme_base + NVMMU_IOSQ_BASE, (u64)ioq.tcbs); + + /* setup admin queue */ + if (!nvme_ctrl_disable()) { + printf("nvme: timeout while waiting for CSTS.RDY to clear\n"); + goto out_shutdown; + } + write64_lo_hi(nvme_base + NVME_ASQ, (u64)adminq.cmds); + write64_lo_hi(nvme_base + NVME_ACQ, (u64)adminq.cqes); + write32(nvme_base + NVME_AQA, ((NVME_QUEUE_SIZE - 1) << 16) | (NVME_QUEUE_SIZE - 1)); + if (!nvme_ctrl_enable()) { + printf("nvme: timeout while waiting for CSTS.RDY to be set\n"); + goto out_disable_ctrl; + } + + /* setup IO queue */ + struct nvme_command cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = NVME_ADMIN_CMD_CREATE_CQ; + cmd.prp1 = (u64)ioq.cqes; + cmd.cdw10 = 1; // cq id + cmd.cdw10 |= (NVME_QUEUE_SIZE - 1) << 16; + cmd.cdw11 = NVME_QUEUE_CONTIGUOUS; + if (!nvme_exec_command(&adminq, &cmd, NULL)) { + printf("nvme: create cq command failed\n"); + goto out_disable_ctrl; + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = NVME_ADMIN_CMD_CREATE_SQ; + cmd.prp1 = (u64)ioq.cmds; + cmd.cdw10 = 1; // sq id + cmd.cdw10 |= (NVME_QUEUE_SIZE - 1) << 16; + cmd.cdw11 = NVME_QUEUE_CONTIGUOUS; + cmd.cdw11 |= 1 << 16; // cq id for this sq + if (!nvme_exec_command(&adminq, &cmd, NULL)) { + printf("nvme: create sq command failed\n"); + goto out_delete_cq; + } + + nvme_initialized = true; + printf("nvme: initialized at 0x%lx\n", nvme_base); + return true; + +out_delete_cq: + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = NVME_ADMIN_CMD_DELETE_CQ; + cmd.cdw10 = 1; // cq id + if (!nvme_exec_command(&adminq, &cmd, NULL)) + printf("nvme: delete cq command failed\n"); +out_disable_ctrl: + nvme_ctrl_shutdown(); + nvme_ctrl_disable(); + nvme_poll_syslog(); +out_shutdown: + rtkit_sleep(nvme_rtkit); + // Some machines call this ANS, some ANS2... + pmgr_reset(nvme_die, "ANS"); + pmgr_reset(nvme_die, "ANS2"); +out_rtkit: + rtkit_free(nvme_rtkit); +out_sart: + sart_free(nvme_sart); +out_asc: + asc_free(nvme_asc); +out_ioq: + free_queue(&ioq); +out_adminq: + free_queue(&adminq); + return false; +} + +void nvme_shutdown(void) +{ + if (!nvme_initialized) { + printf("nvme: trying to shut down but not initialized\n"); + return; + } + + struct nvme_command cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = NVME_ADMIN_CMD_DELETE_SQ; + cmd.cdw10 = 1; // sq id + if (!nvme_exec_command(&adminq, &cmd, NULL)) + printf("nvme: delete sq command failed\n"); + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = NVME_ADMIN_CMD_DELETE_CQ; + cmd.cdw10 = 1; // cq id + if (!nvme_exec_command(&adminq, &cmd, NULL)) + printf("nvme: delete cq command failed\n"); + + if (!nvme_ctrl_shutdown()) + printf("nvme: timeout while waiting for controller shutdown\n"); + if (!nvme_ctrl_disable()) + printf("nvme: timeout while waiting for CSTS.RDY to clear\n"); + + rtkit_sleep(nvme_rtkit); + // Some machines call this ANS, some ANS2... + pmgr_reset(nvme_die, "ANS"); + pmgr_reset(nvme_die, "ANS2"); + rtkit_free(nvme_rtkit); + sart_free(nvme_sart); + asc_free(nvme_asc); + free_queue(&ioq); + free_queue(&adminq); + nvme_initialized = false; + + printf("nvme: shutdown done\n"); +} + +bool nvme_flush(u32 nsid) +{ + struct nvme_command cmd; + + if (!nvme_initialized) + return false; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = NVME_CMD_FLUSH; + cmd.nsid = nsid; + + return nvme_exec_command(&ioq, &cmd, NULL); +} + +bool nvme_read(u32 nsid, u64 lba, void *buffer) +{ + struct nvme_command cmd; + u64 buffer_addr = (u64)buffer; + + if (!nvme_initialized) + return false; + + /* no need for 16K alignment here since the NVME page size is 4k */ + if (buffer_addr & (SZ_4K - 1)) + return false; + + memset(&cmd, 0, sizeof(cmd)); + cmd.opcode = NVME_CMD_READ; + cmd.nsid = nsid; + cmd.prp1 = (u64)buffer_addr; + cmd.cdw10 = lba; + cmd.cdw11 = lba >> 32; + cmd.cdw12 = 1; // 4096 bytes + + return nvme_exec_command(&ioq, &cmd, NULL); +} |
