The branch main has been updated by jaeyoon: URL: https://cgit.FreeBSD.org/src/commit/?id=3a99f31fdb6c66434f77d83ca5166aae4ad2d22c
commit 3a99f31fdb6c66434f77d83ca5166aae4ad2d22c Author: Jaeyoon Choi <[email protected]> AuthorDate: 2025-12-03 04:05:36 +0000 Commit: Jaeyoon Choi <[email protected]> CommitDate: 2025-12-03 04:05:36 +0000 ufshci: Support suspend/resume Handle system power events and issue START STOP UNIT (SSU) to the UFS Device WLUN (0x50). Reviewed by: imp (mentor) Sponsored by: Samsung Electronics Differential Revision: https://reviews.freebsd.org/D54002 --- sys/dev/ufshci/ufshci_ctrlr.c | 50 +++++++++++++++++++++++++++++++++++++++ sys/dev/ufshci/ufshci_dev.c | 19 +++++++++++++++ sys/dev/ufshci/ufshci_pci.c | 23 ++++++++++++++++-- sys/dev/ufshci/ufshci_private.h | 35 ++++++++++++++++++++++++++- sys/dev/ufshci/ufshci_req_queue.c | 3 ++- sys/dev/ufshci/ufshci_sim.c | 7 +++++- sys/dev/ufshci/ufshci_sysctl.c | 3 +++ 7 files changed, 135 insertions(+), 5 deletions(-) diff --git a/sys/dev/ufshci/ufshci_ctrlr.c b/sys/dev/ufshci/ufshci_ctrlr.c index 1ffa99255088..f0cb08b34823 100644 --- a/sys/dev/ufshci/ufshci_ctrlr.c +++ b/sys/dev/ufshci/ufshci_ctrlr.c @@ -610,3 +610,53 @@ ufshci_reg_dump(struct ufshci_controller *ctrlr) ufshci_printf(ctrlr, "========================================\n"); } + +int +ufshci_ctrlr_suspend(struct ufshci_controller *ctrlr, enum power_stype stype) +{ + int error; + + if (!ctrlr->ufs_dev.power_mode_supported) + return (0); + + /* TODO: Need to flush the request queue */ + + if (ctrlr->ufs_device_wlun_periph) { + ctrlr->ufs_dev.power_mode = power_map[stype].dev_pwr; + error = ufshci_sim_send_ssu(ctrlr, /*start*/ false, + power_map[stype].ssu_pc, /*immed*/ false); + if (error) { + ufshci_printf(ctrlr, + "Failed to send SSU in suspend handler\n"); + return (error); + } + } + + /* TODO: Change the link state to Hibernate if necessary. */ + + return (0); +} + +int +ufshci_ctrlr_resume(struct ufshci_controller *ctrlr, enum power_stype stype) +{ + int error; + + if (!ctrlr->ufs_dev.power_mode_supported) + return (0); + + /* TODO: Change the link state to Active if necessary. */ + + if (ctrlr->ufs_device_wlun_periph) { + ctrlr->ufs_dev.power_mode = power_map[stype].dev_pwr; + error = ufshci_sim_send_ssu(ctrlr, /*start*/ false, + power_map[stype].ssu_pc, /*immed*/ false); + if (error) { + ufshci_printf(ctrlr, + "Failed to send SSU in resume handler\n"); + return (error); + } + } + + return (0); +} diff --git a/sys/dev/ufshci/ufshci_dev.c b/sys/dev/ufshci/ufshci_dev.c index 9696e8ea8091..136823523fd3 100644 --- a/sys/dev/ufshci/ufshci_dev.c +++ b/sys/dev/ufshci/ufshci_dev.c @@ -469,6 +469,8 @@ ufshci_dev_init_ufs_power_mode(struct ufshci_controller *ctrlr) } ctrlr->ufs_dev.power_mode_supported = true; + ctrlr->ufs_dev.power_mode = UFSHCI_DEV_PWR_ACTIVE; + return (0); } @@ -786,3 +788,20 @@ out: ufshci_dev_disable_write_booster(ctrlr); return (error); } + +int +ufshci_dev_get_current_power_mode(struct ufshci_controller *ctrlr, + uint8_t *power_mode) +{ + uint64_t value; + int err; + + err = ufshci_dev_read_attribute(ctrlr, UFSHCI_ATTR_B_CURRENT_POWER_MODE, + /*index*/ 0, /*selector*/ 0, &value); + if (err) + return (err); + + *power_mode = (uint8_t)value; + + return (0); +} diff --git a/sys/dev/ufshci/ufshci_pci.c b/sys/dev/ufshci/ufshci_pci.c index 7f78e462db72..5fce14997784 100644 --- a/sys/dev/ufshci/ufshci_pci.c +++ b/sys/dev/ufshci/ufshci_pci.c @@ -23,6 +23,8 @@ static int ufshci_pci_probe(device_t); static int ufshci_pci_attach(device_t); static int ufshci_pci_detach(device_t); +static int ufshci_pci_suspend(device_t); +static int ufshci_pci_resume(device_t); static int ufshci_pci_setup_interrupts(struct ufshci_controller *ctrlr); @@ -31,8 +33,8 @@ static device_method_t ufshci_pci_methods[] = { DEVMETHOD(device_probe, ufshci_pci_probe), DEVMETHOD(device_attach, ufshci_pci_attach), DEVMETHOD(device_detach, ufshci_pci_detach), - /* TODO: Implement Suspend, Resume */ - { 0, 0 } + DEVMETHOD(device_suspend, ufshci_pci_suspend), + DEVMETHOD(device_resume, ufshci_pci_resume), { 0, 0 } }; static driver_t ufshci_pci_driver = { @@ -261,3 +263,20 @@ msi: intx: return (ufshci_pci_setup_shared(ctrlr, ctrlr->msi_count > 0 ? 1 : 0)); } + +static int +ufshci_pci_suspend(device_t dev) +{ + struct ufshci_controller *ctrlr = device_get_softc(dev); + + /* Currently, PCI-based ufshci only supports POWER_STYPE_STANDBY */ + return (ufshci_ctrlr_suspend(ctrlr, POWER_STYPE_STANDBY)); +} + +static int +ufshci_pci_resume(device_t dev) +{ + struct ufshci_controller *ctrlr = device_get_softc(dev); + + return (ufshci_ctrlr_resume(ctrlr, POWER_STYPE_AWAKE)); +} diff --git a/sys/dev/ufshci/ufshci_private.h b/sys/dev/ufshci/ufshci_private.h index 22e77c9b9326..fa5caf87196c 100644 --- a/sys/dev/ufshci/ufshci_private.h +++ b/sys/dev/ufshci/ufshci_private.h @@ -26,12 +26,14 @@ #include <sys/memdesc.h> #include <sys/module.h> #include <sys/mutex.h> +#include <sys/power.h> #include <sys/rman.h> #include <sys/taskqueue.h> #include <machine/bus.h> #include <cam/cam.h> +#include <cam/scsi/scsi_all.h> #include "ufshci.h" @@ -233,6 +235,30 @@ struct ufshci_req_queue { bus_dmamap_t ucdmem_map; }; +enum ufshci_dev_pwr { + UFSHCI_DEV_PWR_ACTIVE = 0, + UFSHCI_DEV_PWR_SLEEP, + UFSHCI_DEV_PWR_POWERDOWN, + UFSHCI_DEV_PWR_DEEPSLEEP, + UFSHCI_DEV_PWR_COUNT, +}; + +struct ufshci_power_entry { + enum ufshci_dev_pwr dev_pwr; + uint8_t ssu_pc; /* SSU Power Condition */ +}; + +/* SSU Power Condition 0x40 is defined in the UFS specification */ +static const struct ufshci_power_entry power_map[POWER_STYPE_COUNT] = { + [POWER_STYPE_AWAKE] = { UFSHCI_DEV_PWR_ACTIVE, SSS_PC_ACTIVE }, + [POWER_STYPE_STANDBY] = { UFSHCI_DEV_PWR_SLEEP, SSS_PC_IDLE }, + [POWER_STYPE_SUSPEND_TO_MEM] = { UFSHCI_DEV_PWR_POWERDOWN, + SSS_PC_STANDBY }, + [POWER_STYPE_SUSPEND_TO_IDLE] = { UFSHCI_DEV_PWR_SLEEP, SSS_PC_IDLE }, + [POWER_STYPE_HIBERNATE] = { UFSHCI_DEV_PWR_DEEPSLEEP, 0x40 }, + [POWER_STYPE_POWEROFF] = { UFSHCI_DEV_PWR_POWERDOWN, SSS_PC_STANDBY }, +}; + struct ufshci_device { uint32_t max_lun_count; @@ -252,6 +278,7 @@ struct ufshci_device { /* Power mode */ bool power_mode_supported; + enum ufshci_dev_pwr power_mode; }; /* @@ -386,12 +413,16 @@ void ufshci_sim_detach(struct ufshci_controller *ctrlr); struct cam_periph *ufshci_sim_find_periph(struct ufshci_controller *ctrlr, uint8_t wlun); int ufshci_sim_send_ssu(struct ufshci_controller *ctrlr, bool start, - int pwr_cond, bool immed); + uint8_t pwr_cond, bool immed); /* Controller */ int ufshci_ctrlr_construct(struct ufshci_controller *ctrlr, device_t dev); void ufshci_ctrlr_destruct(struct ufshci_controller *ctrlr, device_t dev); void ufshci_ctrlr_reset(struct ufshci_controller *ctrlr); +int ufshci_ctrlr_suspend(struct ufshci_controller *ctrlr, + enum power_stype stype); +int ufshci_ctrlr_resume(struct ufshci_controller *ctrlr, + enum power_stype stype); /* ctrlr defined as void * to allow use with config_intrhook. */ void ufshci_ctrlr_start_config_hook(void *arg); void ufshci_ctrlr_poll(struct ufshci_controller *ctrlr); @@ -415,6 +446,8 @@ int ufshci_dev_init_uic_power_mode(struct ufshci_controller *ctrlr); int ufshci_dev_init_ufs_power_mode(struct ufshci_controller *ctrlr); int ufshci_dev_get_descriptor(struct ufshci_controller *ctrlr); int ufshci_dev_config_write_booster(struct ufshci_controller *ctrlr); +int ufshci_dev_get_current_power_mode(struct ufshci_controller *ctrlr, + uint8_t *power_mode); /* Controller Command */ void ufshci_ctrlr_cmd_send_task_mgmt_request(struct ufshci_controller *ctrlr, diff --git a/sys/dev/ufshci/ufshci_req_queue.c b/sys/dev/ufshci/ufshci_req_queue.c index 7aa164d00bec..df7e4b159278 100644 --- a/sys/dev/ufshci/ufshci_req_queue.c +++ b/sys/dev/ufshci/ufshci_req_queue.c @@ -147,7 +147,8 @@ ufshci_req_queue_response_is_error(struct ufshci_req_queue *req_queue, /* Check response UPIU header */ if (response->header.response != UFSHCI_RESPONSE_CODE_TARGET_SUCCESS) { ufshci_printf(req_queue->ctrlr, - "Invalid response code = 0x%x\n", + "Function(0x%x) Invalid response code = 0x%x\n", + response->header.ext_iid_or_function, response->header.response); is_error = true; } diff --git a/sys/dev/ufshci/ufshci_sim.c b/sys/dev/ufshci/ufshci_sim.c index 0cd691de47e2..1589e7475ad4 100644 --- a/sys/dev/ufshci/ufshci_sim.c +++ b/sys/dev/ufshci/ufshci_sim.c @@ -46,6 +46,11 @@ ufshci_sim_scsiio_done(void *ccb_arg, const struct ufshci_completion *cpl, ccb->ccb_h.status &= ~CAM_SIM_QUEUED; if (error) { + printf("ufshci: SCSI command completion error, Status(0x%x)" + " Key(0x%x), ASC(0x%x), ASCQ(0x%x)\n", + cpl->response_upiu.cmd_response_upiu.header + .ext_iid_or_status, + sense_data[2], sense_data[12], sense_data[13]); ccb->ccb_h.status = CAM_REQ_CMP_ERR; xpt_done(ccb); } else { @@ -455,7 +460,7 @@ ufshci_sim_find_periph(struct ufshci_controller *ctrlr, uint8_t wlun) /* This function is called during suspend/resume. */ int ufshci_sim_send_ssu(struct ufshci_controller *ctrlr, bool start, - int power_condition, bool immed) + uint8_t power_condition, bool immed) { struct cam_periph *periph = ctrlr->ufs_device_wlun_periph; union ccb *ccb; diff --git a/sys/dev/ufshci/ufshci_sysctl.c b/sys/dev/ufshci/ufshci_sysctl.c index 3ec4ea935464..30b0ccaeed13 100644 --- a/sys/dev/ufshci/ufshci_sysctl.c +++ b/sys/dev/ufshci/ufshci_sysctl.c @@ -201,6 +201,9 @@ ufshci_sysctl_initialize_ctrlr(struct ufshci_controller *ctrlr) CTLFLAG_RD, &dev->power_mode_supported, 0, "Device power mode support"); + SYSCTL_ADD_UINT(ctrlr_ctx, ctrlr_list, OID_AUTO, "power_mode", + CTLFLAG_RD, &dev->power_mode, 0, "Current device power mode"); + SYSCTL_ADD_PROC(ctrlr_ctx, ctrlr_list, OID_AUTO, "timeout_period", CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_MPSAFE, &ctrlr->timeout_period, 0, ufshci_sysctl_timeout_period, "IU",
