From 90de4d2d0274e20b60a3641a65c8f8d643e1ffd4 Mon Sep 17 00:00:00 2001 From: mglocker Date: Wed, 5 Jun 2024 04:58:05 +0000 Subject: [PATCH] Now that our hibernation sub-system supports 4096 byte sectors, add hibernation support for ufshci(4). Input from kettenis@, krw@, deraadt@, mlarkin@. ok mlarkin@, deraadt@ --- sys/arch/amd64/amd64/hibernate_machdep.c | 8 +- sys/dev/ic/ufshci.c | 296 +++++++++++++++++------ 2 files changed, 224 insertions(+), 80 deletions(-) diff --git a/sys/arch/amd64/amd64/hibernate_machdep.c b/sys/arch/amd64/amd64/hibernate_machdep.c index 19fe8e808ac..db59d086636 100644 --- a/sys/arch/amd64/amd64/hibernate_machdep.c +++ b/sys/arch/amd64/amd64/hibernate_machdep.c @@ -1,4 +1,4 @@ -/* $OpenBSD: hibernate_machdep.c,v 1.50 2023/04/24 09:04:03 dv Exp $ */ +/* $OpenBSD: hibernate_machdep.c,v 1.51 2024/06/05 04:58:05 mglocker Exp $ */ /* * Copyright (c) 2012 Mike Larkin @@ -51,6 +51,7 @@ #include "sd.h" #include "nvme.h" #include "sdmmc.h" +#include "ufshci.h" /* Hibernate support */ void hibernate_enter_resume_4k_pte(vaddr_t, paddr_t); @@ -97,6 +98,8 @@ get_hibernate_io_function(dev_t dev) vaddr_t addr, size_t size, int op, void *page); extern int sdmmc_scsi_hibernate_io(dev_t dev, daddr_t blkno, vaddr_t addr, size_t size, int op, void *page); + extern int ufshci_hibernate_io(dev_t dev, daddr_t blkno, + vaddr_t addr, size_t size, int op, void *page); struct device *dv = disk_lookup(&sd_cd, DISKUNIT(dev)); struct { const char *driver; @@ -113,6 +116,9 @@ get_hibernate_io_function(dev_t dev) #endif #if NSDMMC > 0 { "sdmmc", sdmmc_scsi_hibernate_io }, +#endif +#if NUFSHCI > 0 + { "ufshci", ufshci_hibernate_io }, #endif }; diff --git a/sys/dev/ic/ufshci.c b/sys/dev/ic/ufshci.c index 60ccc4bda64..7267c6b7a2a 100644 --- a/sys/dev/ic/ufshci.c +++ b/sys/dev/ic/ufshci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ufshci.c,v 1.33 2024/05/27 10:27:58 mglocker Exp $ */ +/* $OpenBSD: ufshci.c,v 1.34 2024/06/05 04:58:05 mglocker Exp $ */ /* * Copyright (c) 2022 Marcus Glocker @@ -42,6 +42,13 @@ #include #include +#ifdef HIBERNATE +#include +#include +#include +#include +#endif + #ifdef UFSHCI_DEBUG int ufshci_dbglvl = 1; #define DPRINTF(l, x...) do { if ((l) <= ufshci_dbglvl) printf(x); } \ @@ -59,7 +66,9 @@ int ufshci_is_poll(struct ufshci_softc *, uint32_t); struct ufshci_dmamem *ufshci_dmamem_alloc(struct ufshci_softc *, size_t); void ufshci_dmamem_free(struct ufshci_softc *, struct ufshci_dmamem *); +int ufshci_alloc(struct ufshci_softc *); int ufshci_init(struct ufshci_softc *); +int ufshci_disable(struct ufshci_softc *); int ufshci_doorbell_read(struct ufshci_softc *); void ufshci_doorbell_write(struct ufshci_softc *, int); int ufshci_doorbell_poll(struct ufshci_softc *, int, @@ -104,6 +113,11 @@ void ufshci_scsi_io_done(struct ufshci_softc *, void ufshci_scsi_done(struct ufshci_softc *, struct ufshci_ccb *); +#if HIBERNATE +int ufshci_hibernate_io(dev_t, daddr_t, vaddr_t, size_t, + int, void *); +#endif + const struct scsi_adapter ufshci_switch = { ufshci_scsi_cmd, NULL, NULL, NULL, NULL }; @@ -227,6 +241,8 @@ ufshci_attach(struct ufshci_softc *sc) #if 0 sc->sc_flags |= UFSHCI_FLAGS_AGGR_INTR; /* Enable intr. aggregation */ #endif + /* Allocate the DMA buffers and initialize the controller. */ + ufshci_alloc(sc); ufshci_init(sc); if (ufshci_ccb_alloc(sc, sc->sc_nutrs) != 0) { @@ -371,6 +387,39 @@ ufshci_dmamem_free(struct ufshci_softc *sc, struct ufshci_dmamem *udm) free(udm, M_DEVBUF, sizeof(*udm)); } +int +ufshci_alloc(struct ufshci_softc *sc) +{ + /* 7.1.1 Host Controller Initialization: 13) */ + sc->sc_dmamem_utmrd = ufshci_dmamem_alloc(sc, + sizeof(struct ufshci_utmrd) * sc->sc_nutmrs); + if (sc->sc_dmamem_utmrd == NULL) { + printf("%s: Can't allocate DMA memory for UTMRD\n", + sc->sc_dev.dv_xname); + return -1; + } + + /* 7.1.1 Host Controller Initialization: 15) */ + sc->sc_dmamem_utrd = ufshci_dmamem_alloc(sc, + sizeof(struct ufshci_utrd) * sc->sc_nutrs); + if (sc->sc_dmamem_utrd == NULL) { + printf("%s: Can't allocate DMA memory for UTRD\n", + sc->sc_dev.dv_xname); + return -1; + } + + /* Allocate UCDs. */ + sc->sc_dmamem_ucd = ufshci_dmamem_alloc(sc, + sizeof(struct ufshci_ucd) * sc->sc_nutrs); + if (sc->sc_dmamem_ucd == NULL) { + printf("%s: Can't allocate DMA memory for UCD\n", + sc->sc_dev.dv_xname); + return -1; + } + + return 0; +} + int ufshci_init(struct ufshci_softc *sc) { @@ -430,44 +479,18 @@ ufshci_init(struct ufshci_softc *sc) * TODO: More UIC commands to issue? */ - /* 7.1.1 Host Controller Initialization: 13) */ - sc->sc_dmamem_utmrd = ufshci_dmamem_alloc(sc, - sizeof(struct ufshci_utmrd) * sc->sc_nutmrs); - if (sc->sc_dmamem_utmrd == NULL) { - printf("%s: Can't allocate DMA memory for UTMRD\n", - sc->sc_dev.dv_xname); - return -1; - } /* 7.1.1 Host Controller Initialization: 14) */ dva = UFSHCI_DMA_DVA(sc->sc_dmamem_utmrd); DPRINTF(2, "%s: utmrd dva=%llu\n", __func__, dva); UFSHCI_WRITE_4(sc, UFSHCI_REG_UTMRLBA, (uint32_t)dva); UFSHCI_WRITE_4(sc, UFSHCI_REG_UTMRLBAU, (uint32_t)(dva >> 32)); - /* 7.1.1 Host Controller Initialization: 15) */ - sc->sc_dmamem_utrd = ufshci_dmamem_alloc(sc, - sizeof(struct ufshci_utrd) * sc->sc_nutrs); - if (sc->sc_dmamem_utrd == NULL) { - printf("%s: Can't allocate DMA memory for UTRD\n", - sc->sc_dev.dv_xname); - return -1; - } /* 7.1.1 Host Controller Initialization: 16) */ dva = UFSHCI_DMA_DVA(sc->sc_dmamem_utrd); DPRINTF(2, "%s: utrd dva=%llu\n", __func__, dva); UFSHCI_WRITE_4(sc, UFSHCI_REG_UTRLBA, (uint32_t)dva); UFSHCI_WRITE_4(sc, UFSHCI_REG_UTRLBAU, (uint32_t)(dva >> 32)); - - /* Allocate UCDs. */ - sc->sc_dmamem_ucd = ufshci_dmamem_alloc(sc, - sizeof(struct ufshci_ucd) * sc->sc_nutrs); - if (sc->sc_dmamem_ucd == NULL) { - printf("%s: Can't allocate DMA memory for UCD\n", - sc->sc_dev.dv_xname); - return -1; - } - /* 7.1.1 Host Controller Initialization: 17) */ UFSHCI_WRITE_4(sc, UFSHCI_REG_UTMRLRSR, UFSHCI_REG_UTMRLRSR_START); @@ -482,6 +505,19 @@ ufshci_init(struct ufshci_softc *sc) return 0; } +int +ufshci_disable(struct ufshci_softc *sc) +{ + /* Stop run queues. */ + UFSHCI_WRITE_4(sc, UFSHCI_REG_UTMRLRSR, UFSHCI_REG_UTMRLRSR_STOP); + UFSHCI_WRITE_4(sc, UFSHCI_REG_UTRLRSR, UFSHCI_REG_UTRLRSR_STOP); + + /* Disable interrupts. */ + UFSHCI_WRITE_4(sc, UFSHCI_REG_IE, 0); + + return 0; +} + int ufshci_doorbell_read(struct ufshci_softc *sc) { @@ -1353,11 +1389,11 @@ ufshci_activate(struct ufshci_softc *sc, int act) case DVACT_POWERDOWN: DPRINTF(1, "%s: POWERDOWN\n", __func__); rv = config_activate_children(&sc->sc_dev, act); - ufshci_powerdown(sc); + ufshci_disable(sc); break; case DVACT_RESUME: DPRINTF(1, "%s: RESUME\n", __func__); - ufshci_resume(sc); + rv = ufshci_init(sc); if (rv == 0) rv = config_activate_children(&sc->sc_dev, act); break; @@ -1369,56 +1405,6 @@ ufshci_activate(struct ufshci_softc *sc, int act) return rv; } -int -ufshci_powerdown(struct ufshci_softc *sc) -{ - uint32_t reg; - - /* Send "hibernate enter" command. */ - UFSHCI_WRITE_4(sc, UFSHCI_REG_UICCMD, - UFSHCI_REG_UICCMD_CMDOP_DME_HIBERNATE_ENTER); - if (ufshci_is_poll(sc, UFSHCI_REG_IS_UHES) != 0) { - printf("%s: hibernate enter cmd failed\n", __func__); - return 1; - } - - /* Check if "hibernate enter" command was executed successfully. */ - reg = UFSHCI_READ_4(sc, UFSHCI_REG_HCS); - DPRINTF(1, "%s: UPMCRS=0x%x\n", __func__, UFSHCI_REG_HCS_UPMCRS(reg)); - if (UFSHCI_REG_HCS_UPMCRS(reg) > UFSHCI_REG_HCS_UPMCRS_PWR_REMTOTE) { - printf("%s: hibernate enter cmd returned UPMCRS error=0x%x\n", - __func__, UFSHCI_REG_HCS_UPMCRS(reg)); - return 1; - } - - return 0; -} - -int -ufshci_resume(struct ufshci_softc *sc) -{ - uint32_t reg; - - /* Send "hibernate exit" command. */ - UFSHCI_WRITE_4(sc, UFSHCI_REG_UICCMD, - UFSHCI_REG_UICCMD_CMDOP_DME_HIBERNATE_EXIT); - if (ufshci_is_poll(sc, UFSHCI_REG_IS_UHXS) != 0) { - printf("%s: hibernate exit command failed\n", __func__); - return 1; - } - - /* Check if "hibernate exit" command was executed successfully. */ - reg = UFSHCI_READ_4(sc, UFSHCI_REG_HCS); - DPRINTF(1, "%s: UPMCRS=0x%x\n", __func__, UFSHCI_REG_HCS_UPMCRS(reg)); - if (UFSHCI_REG_HCS_UPMCRS(reg) > UFSHCI_REG_HCS_UPMCRS_PWR_REMTOTE) { - printf("%s: hibernate exit cmd returned UPMCRS error=0x%x\n", - __func__, UFSHCI_REG_HCS_UPMCRS(reg)); - return 1; - } - - return 0; -} - /* SCSI */ int @@ -1928,3 +1914,155 @@ ufshci_scsi_done(struct ufshci_softc *sc, struct ufshci_ccb *ccb) xs->resid = 0; scsi_done(xs); } + +#if HIBERNATE +int +ufshci_hibernate_io(dev_t dev, daddr_t blkno, vaddr_t addr, size_t size, + int op, void *page) +{ + struct ufshci_hibernate_page { + struct ufshci_utrd utrd; + struct ufshci_ucd ucd; + + struct ufshci_softc *sc; /* Copy of softc */ + + daddr_t poffset; /* Start of SWAP partition */ + size_t psize; /* Size of SWAP partition */ + uint32_t secsize; /* Our sector size */ + } *my = page; + paddr_t data_phys, page_phys; + uint64_t data_bus_phys, page_bus_phys; + uint64_t timeout_us; + int off, len, slot; + uint32_t blocks, reg; + uint64_t lba; + + if (op == HIB_INIT) { + struct device *disk; + struct device *scsibus; + extern struct cfdriver sd_cd; + + /* Find ufshci softc. */ + disk = disk_lookup(&sd_cd, DISKUNIT(dev)); + if (disk == NULL) + return ENOTTY; + scsibus = disk->dv_parent; + my->sc = (struct ufshci_softc *)disk->dv_parent->dv_parent; + + /* Stop run queues and disable interrupts. */ + ufshci_disable(my->sc); + + /* Tell the controler the new hibernate UTRD address. */ + pmap_extract(pmap_kernel(), (vaddr_t)page, &page_phys); + page_bus_phys = page_phys + ((void *)&my->utrd - page); + UFSHCI_WRITE_4(my->sc, UFSHCI_REG_UTRLBA, + (uint32_t)page_bus_phys); + UFSHCI_WRITE_4(my->sc, UFSHCI_REG_UTRLBAU, + (uint32_t)(page_bus_phys >> 32)); + + /* Start run queues. */ + UFSHCI_WRITE_4(my->sc, UFSHCI_REG_UTMRLRSR, + UFSHCI_REG_UTMRLRSR_START); + UFSHCI_WRITE_4(my->sc, UFSHCI_REG_UTRLRSR, + UFSHCI_REG_UTRLRSR_START); + + my->poffset = blkno; + my->psize = size; + my->secsize = UFSHCI_LBS; + + return 0; + } + + if (op != HIB_W) + return 0; + + if (blkno + (size / DEV_BSIZE) > my->psize) + return E2BIG; + blocks = size / my->secsize; + lba = (blkno + my->poffset) / (my->secsize / DEV_BSIZE); + + /* + * The following code is a ripped down version of ufshci_utr_cmd_io() + * adapted for hibernate. + */ + slot = 0; /* We only use the first slot for hibernate */ + + memset(&my->utrd, 0, sizeof(struct ufshci_utrd)); + + my->utrd.dw0 = UFSHCI_UTRD_DW0_CT_UFS; + my->utrd.dw0 |= UFSHCI_UTRD_DW0_DD_I2T; + my->utrd.dw0 |= UFSHCI_UTRD_DW0_I_REG; + my->utrd.dw2 = UFSHCI_UTRD_DW2_OCS_IOV; + + memset(&my->ucd, 0, sizeof(struct ufshci_ucd)); + + my->ucd.cmd.hdr.tc = UPIU_TC_I2T_COMMAND; + my->ucd.cmd.hdr.flags = (1 << 5); /* Bit-5 = Write */ + + my->ucd.cmd.hdr.lun = 0; + my->ucd.cmd.hdr.task_tag = slot; + my->ucd.cmd.hdr.cmd_set_type = 0; /* SCSI command */ + my->ucd.cmd.hdr.query = 0; + my->ucd.cmd.hdr.response = 0; + my->ucd.cmd.hdr.status = 0; + my->ucd.cmd.hdr.ehs_len = 0; + my->ucd.cmd.hdr.device_info = 0; + my->ucd.cmd.hdr.ds_len = 0; + + my->ucd.cmd.expected_xfer_len = htobe32(UFSHCI_LBS * blocks); + my->ucd.cmd.cdb[0] = WRITE_10; /* 0x2a */ + my->ucd.cmd.cdb[1] = (1 << 3); /* FUA: Force Unit Access */ + my->ucd.cmd.cdb[2] = (lba >> 24) & 0xff; + my->ucd.cmd.cdb[3] = (lba >> 16) & 0xff; + my->ucd.cmd.cdb[4] = (lba >> 8) & 0xff; + my->ucd.cmd.cdb[5] = (lba >> 0) & 0xff; + my->ucd.cmd.cdb[7] = (blocks >> 8) & 0xff; + my->ucd.cmd.cdb[8] = (blocks >> 0) & 0xff; + + pmap_extract(pmap_kernel(), (vaddr_t)page, &page_phys); + page_bus_phys = page_phys + ((void *)&my->ucd - page); + my->utrd.dw4 = (uint32_t)page_bus_phys; + my->utrd.dw5 = (uint32_t)(page_bus_phys >> 32); + + off = sizeof(struct upiu_command) / 4; /* DWORD offset */ + my->utrd.dw6 = UFSHCI_UTRD_DW6_RUO(off); + + len = sizeof(struct upiu_response) / 4; /* DWORD length */ + my->utrd.dw6 |= UFSHCI_UTRD_DW6_RUL(len); + + off = (sizeof(struct upiu_command) + sizeof(struct upiu_response)) / 4; + my->utrd.dw7 = UFSHCI_UTRD_DW7_PRDTO(off); + + my->utrd.dw7 |= UFSHCI_UTRD_DW7_PRDTL(1); /* dm_nsegs */ + + pmap_extract(pmap_kernel(), (vaddr_t)addr, &data_phys); + data_bus_phys = data_phys; + my->ucd.prdt[0].dw0 = (uint32_t)data_bus_phys; + my->ucd.prdt[0].dw1 = (uint32_t)(data_bus_phys >> 32); + my->ucd.prdt[0].dw2 = 0; + my->ucd.prdt[0].dw3 = size - 1; /* ds_len */ + + if (UFSHCI_READ_4(my->sc, UFSHCI_REG_UTRLRSR) != 1) + return EIO; + + ufshci_doorbell_write(my->sc, slot); + + /* ufshci_doorbell_poll() adaption for hibernate. */ + for (timeout_us = 1000000 * 1000; timeout_us != 0; + timeout_us -= 1000) { + reg = UFSHCI_READ_4(my->sc, UFSHCI_REG_UTRLDBR); + if ((reg & (1U << slot)) == 0) + break; + delay(1000); + } + if (timeout_us == 0) + return EIO; + UFSHCI_WRITE_4(my->sc, UFSHCI_REG_UTRLCNR, (1U << slot)); + + /* Check if the command was succesfully executed. */ + if (my->utrd.dw2 != UFSHCI_UTRD_DW2_OCS_SUCCESS) + return EIO; + + return 0; +} +#endif /* HIBERNATE */ -- 2.20.1