/* $OpenBSD: aplns.c,v 1.15 2022/11/11 11:45:10 kettenis Exp $ */ /* * Copyright (c) 2014, 2021 David Gwynne * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ANS_CPU_CTRL 0x0044 #define ANS_CPU_CTRL_RUN (1 << 4) #define ANS_MAX_PEND_CMDS_CTRL 0x01210 #define ANS_MAX_QUEUE_DEPTH 64 #define ANS_BOOT_STATUS 0x01300 #define ANS_BOOT_STATUS_OK 0xde71ce55 #define ANS_MODESEL_REG 0x01304 #define ANS_UNKNOWN_CTRL 0x24008 #define ANS_PRP_NULL_CHECK (1 << 11) #define ANS_LINEAR_SQ_CTRL 0x24908 #define ANS_LINEAR_SQ_CTRL_EN (1 << 0) #define ANS_LINEAR_ASQ_DB 0x2490c #define ANS_LINEAR_IOSQ_DB 0x24910 #define ANS_NVMMU_NUM 0x28100 #define ANS_NVMMU_BASE_ASQ 0x28108 #define ANS_NVMMU_BASE_IOSQ 0x28110 #define ANS_NVMMU_TCB_INVAL 0x28118 #define ANS_NVMMU_TCB_STAT 0x28120 #define ANS_NVMMU_TCB_SIZE 0x4000 #define ANS_NVMMU_TCB_PITCH 0x80 struct ans_nvmmu_tcb { uint8_t tcb_opcode; uint8_t tcb_flags; #define ANS_NVMMU_TCB_WRITE (1 << 0) #define ANS_NVMMU_TCB_READ (1 << 1) uint8_t tcb_cid; uint8_t tcb_pad0[1]; uint32_t tcb_prpl_len; uint8_t tcb_pad1[16]; uint64_t tcb_prp[2]; }; int aplns_match(struct device *, void *, void *); void aplns_attach(struct device *, struct device *, void *); const struct cfattach aplns_ca = { sizeof(struct device), aplns_match, aplns_attach }; struct cfdriver aplns_cd = { NULL, "aplns", DV_DULL }; int nvme_ans_sart_map(void *, bus_addr_t, bus_size_t); int nvme_ans_sart_unmap(void *, bus_addr_t, bus_size_t); int aplns_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return (OF_is_compatible(faa->fa_node, "apple,nvme-m1") || OF_is_compatible(faa->fa_node, "apple,nvme-ans2")); } void aplns_attach(struct device *parent, struct device *self, void *aux) { struct fdt_attach_args *faa = aux; printf("\n"); config_found(self, faa, NULL); } struct nvme_ans_softc { struct nvme_softc asc_nvme; bus_space_tag_t asc_iot; bus_space_handle_t asc_ioh; int asc_node; uint32_t asc_sart; struct rtkit asc_rtkit; struct rtkit_state *asc_rtkit_state; struct nvme_dmamem *asc_nvmmu; }; int nvme_ans_match(struct device *, void *, void *); void nvme_ans_attach(struct device *, struct device *, void *); int nvme_ans_activate(struct device *, int act); const struct cfattach nvme_ans_ca = { sizeof(struct nvme_ans_softc), nvme_ans_match, nvme_ans_attach, NULL, nvme_ans_activate }; int nvme_ans_init(struct nvme_ans_softc *sc); void nvme_ans_shutdown(struct nvme_ans_softc *sc); void nvme_ans_enable(struct nvme_softc *); int nvme_ans_q_alloc(struct nvme_softc *, struct nvme_queue *); void nvme_ans_q_free(struct nvme_softc *, struct nvme_queue *); uint32_t nvme_ans_sq_enter(struct nvme_softc *, struct nvme_queue *, struct nvme_ccb *); void nvme_ans_sq_leave(struct nvme_softc *, struct nvme_queue *, struct nvme_ccb *); void nvme_ans_cq_done(struct nvme_softc *, struct nvme_queue *, struct nvme_ccb *); static const struct nvme_ops nvme_ans_ops = { .op_enable = nvme_ans_enable, .op_q_alloc = nvme_ans_q_alloc, .op_q_free = nvme_ans_q_free, .op_sq_enter = nvme_ans_sq_enter, .op_sq_leave = nvme_ans_sq_leave, .op_sq_enter_locked = nvme_ans_sq_enter, .op_sq_leave_locked = nvme_ans_sq_leave, .op_cq_done = nvme_ans_cq_done, }; int nvme_ans_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return (OF_is_compatible(faa->fa_node, "apple,nvme-m1") || OF_is_compatible(faa->fa_node, "apple,nvme-ans2")); } void nvme_ans_attach(struct device *parent, struct device *self, void *aux) { struct nvme_ans_softc *asc = (struct nvme_ans_softc *)self; struct nvme_softc *sc = &asc->asc_nvme; struct fdt_attach_args *faa = aux; if (faa->fa_nreg < 2) { printf(": no registers\n"); return; } sc->sc_iot = faa->fa_iot; if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_ioh) != 0) { printf(": can't map registers\n"); return; } asc->asc_iot = faa->fa_iot; if (bus_space_map(asc->asc_iot, faa->fa_reg[1].addr, faa->fa_reg[1].size, 0, &asc->asc_ioh)) { bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); printf(": can't map registers\n"); return; } sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO, nvme_intr, sc, sc->sc_dev.dv_xname); if (sc->sc_ih == NULL) { printf(": can't establish interrupt\n"); goto unmap; } asc->asc_node = faa->fa_node; asc->asc_sart = OF_getpropint(faa->fa_node, "apple,sart", 0); asc->asc_rtkit.rk_cookie = asc; asc->asc_rtkit.rk_dmat = faa->fa_dmat; asc->asc_rtkit.rk_map = nvme_ans_sart_map; asc->asc_rtkit.rk_unmap = nvme_ans_sart_unmap; asc->asc_rtkit_state = rtkit_init(faa->fa_node, NULL, 0, &asc->asc_rtkit); if (asc->asc_rtkit_state == NULL) { printf(": can't map mailbox channel\n"); goto disestablish; } if (nvme_ans_init(asc)) { printf(": firmware not ready\n"); goto disestablish; } printf(": "); sc->sc_dmat = faa->fa_dmat; sc->sc_ios = faa->fa_reg[0].size; sc->sc_ops = &nvme_ans_ops; sc->sc_openings = 1; if (nvme_attach(sc) != 0) { /* error printed by nvme_attach() */ goto disestablish; } return; disestablish: fdt_intr_disestablish(sc->sc_ih); sc->sc_ih = NULL; unmap: bus_space_unmap(asc->asc_iot, asc->asc_ioh, faa->fa_reg[1].size); bus_space_unmap(sc->sc_iot, sc->sc_ioh, faa->fa_reg[0].size); sc->sc_ios = 0; } int nvme_ans_activate(struct device *self, int act) { struct nvme_ans_softc *asc = (struct nvme_ans_softc *)self; struct nvme_softc *sc = &asc->asc_nvme; int rv; switch (act) { case DVACT_POWERDOWN: rv = nvme_activate(&asc->asc_nvme, act); nvme_ans_shutdown(asc); break; case DVACT_RESUME: rv = nvme_ans_init(asc); if (rv) { printf("%s: firmware not ready\n", DEVNAME(sc)); goto fail; } rv = nvme_activate(&asc->asc_nvme, act); break; default: rv = nvme_activate(&asc->asc_nvme, act); break; } fail: return rv; } int nvme_ans_init(struct nvme_ans_softc *asc) { struct nvme_softc *sc = &asc->asc_nvme; uint32_t ctrl, status; power_domain_enable_all(asc->asc_node); ctrl = bus_space_read_4(asc->asc_iot, asc->asc_ioh, ANS_CPU_CTRL); bus_space_write_4(asc->asc_iot, asc->asc_ioh, ANS_CPU_CTRL, ctrl | ANS_CPU_CTRL_RUN); status = bus_space_read_4(sc->sc_iot, sc->sc_ioh, ANS_BOOT_STATUS); if (status != ANS_BOOT_STATUS_OK) rtkit_boot(asc->asc_rtkit_state); status = bus_space_read_4(sc->sc_iot, sc->sc_ioh, ANS_BOOT_STATUS); if (status != ANS_BOOT_STATUS_OK) return ENXIO; bus_space_write_4(sc->sc_iot, sc->sc_ioh, ANS_LINEAR_SQ_CTRL, ANS_LINEAR_SQ_CTRL_EN); bus_space_write_4(sc->sc_iot, sc->sc_ioh, ANS_MAX_PEND_CMDS_CTRL, (ANS_MAX_QUEUE_DEPTH << 16) | ANS_MAX_QUEUE_DEPTH); ctrl = bus_space_read_4(sc->sc_iot, sc->sc_ioh, ANS_UNKNOWN_CTRL); bus_space_write_4(sc->sc_iot, sc->sc_ioh, ANS_UNKNOWN_CTRL, ctrl & ~ANS_PRP_NULL_CHECK); return 0; } void nvme_ans_shutdown(struct nvme_ans_softc *asc) { uint32_t ctrl; rtkit_shutdown(asc->asc_rtkit_state); ctrl = bus_space_read_4(asc->asc_iot, asc->asc_ioh, ANS_CPU_CTRL); bus_space_write_4(asc->asc_iot, asc->asc_ioh, ANS_CPU_CTRL, ctrl & ~ANS_CPU_CTRL_RUN); reset_assert_all(asc->asc_node); reset_deassert_all(asc->asc_node); power_domain_disable_all(asc->asc_node); } int nvme_ans_sart_map(void *cookie, bus_addr_t addr, bus_size_t size) { struct nvme_ans_softc *asc = cookie; return aplsart_map(asc->asc_sart, addr, size); } int nvme_ans_sart_unmap(void *cookie, bus_addr_t addr, bus_size_t size) { struct nvme_ans_softc *asc = cookie; return aplsart_unmap(asc->asc_sart, addr, size); } int nvme_ans_q_alloc(struct nvme_softc *sc, struct nvme_queue *q) { bus_size_t db, base; KASSERT(q->q_entries <= (ANS_NVMMU_TCB_SIZE / ANS_NVMMU_TCB_PITCH)); q->q_nvmmu_dmamem = nvme_dmamem_alloc(sc, ANS_NVMMU_TCB_SIZE); if (q->q_nvmmu_dmamem == NULL) return (-1); memset(NVME_DMA_KVA(q->q_nvmmu_dmamem), 0, NVME_DMA_LEN(q->q_nvmmu_dmamem)); switch (q->q_id) { case NVME_IO_Q: db = ANS_LINEAR_IOSQ_DB; base = ANS_NVMMU_BASE_IOSQ; break; case NVME_ADMIN_Q: db = ANS_LINEAR_ASQ_DB; base = ANS_NVMMU_BASE_ASQ; break; default: panic("unsupported queue id %u", q->q_id); /* NOTREACHED */ } q->q_sqtdbl = db; nvme_dmamem_sync(sc, q->q_nvmmu_dmamem, BUS_DMASYNC_PREWRITE); nvme_write8(sc, base, NVME_DMA_DVA(q->q_nvmmu_dmamem)); return (0); } void nvme_ans_enable(struct nvme_softc *sc) { nvme_write4(sc, ANS_NVMMU_NUM, (ANS_NVMMU_TCB_SIZE / ANS_NVMMU_TCB_PITCH) - 1); nvme_write4(sc, ANS_MODESEL_REG, 0); } void nvme_ans_q_free(struct nvme_softc *sc, struct nvme_queue *q) { nvme_dmamem_sync(sc, q->q_nvmmu_dmamem, BUS_DMASYNC_POSTWRITE); nvme_dmamem_free(sc, q->q_nvmmu_dmamem); } uint32_t nvme_ans_sq_enter(struct nvme_softc *sc, struct nvme_queue *q, struct nvme_ccb *ccb) { return (ccb->ccb_id); } static inline struct ans_nvmmu_tcb * nvme_ans_tcb(struct nvme_queue *q, unsigned int qid) { caddr_t ptr = NVME_DMA_KVA(q->q_nvmmu_dmamem); ptr += qid * ANS_NVMMU_TCB_PITCH; return ((struct ans_nvmmu_tcb *)ptr); } void nvme_ans_sq_leave(struct nvme_softc *sc, struct nvme_queue *q, struct nvme_ccb *ccb) { unsigned int id = ccb->ccb_id; struct nvme_sqe_io *sqe; struct ans_nvmmu_tcb *tcb = nvme_ans_tcb(q, id); sqe = NVME_DMA_KVA(q->q_sq_dmamem); sqe += id; bus_dmamap_sync(sc->sc_dmat, NVME_DMA_MAP(q->q_nvmmu_dmamem), ANS_NVMMU_TCB_PITCH * id, sizeof(*tcb), BUS_DMASYNC_POSTWRITE); memset(tcb, 0, sizeof(*tcb)); tcb->tcb_opcode = sqe->opcode; tcb->tcb_flags = ANS_NVMMU_TCB_WRITE | ANS_NVMMU_TCB_READ; tcb->tcb_cid = id; tcb->tcb_prpl_len = sqe->nlb; tcb->tcb_prp[0] = sqe->entry.prp[0]; tcb->tcb_prp[1] = sqe->entry.prp[1]; bus_dmamap_sync(sc->sc_dmat, NVME_DMA_MAP(q->q_nvmmu_dmamem), ANS_NVMMU_TCB_PITCH * id, sizeof(*tcb), BUS_DMASYNC_PREWRITE); nvme_write4(sc, q->q_sqtdbl, id); } void nvme_ans_cq_done(struct nvme_softc *sc, struct nvme_queue *q, struct nvme_ccb *ccb) { unsigned int id = ccb->ccb_id; struct ans_nvmmu_tcb *tcb = nvme_ans_tcb(q, id); uint32_t stat; bus_dmamap_sync(sc->sc_dmat, NVME_DMA_MAP(q->q_nvmmu_dmamem), ANS_NVMMU_TCB_PITCH * id, sizeof(*tcb), BUS_DMASYNC_POSTWRITE); memset(tcb, 0, sizeof(*tcb)); bus_dmamap_sync(sc->sc_dmat, NVME_DMA_MAP(q->q_nvmmu_dmamem), ANS_NVMMU_TCB_PITCH * id, sizeof(*tcb), BUS_DMASYNC_PREWRITE); nvme_write4(sc, ANS_NVMMU_TCB_INVAL, id); stat = nvme_read4(sc, ANS_NVMMU_TCB_STAT); if (stat != 0) { printf("%s: nvmmu tcp stat is non-zero: 0x%08x\n", DEVNAME(sc), stat); } }