git: 3a99f31fdb6c - main - ufshci: Support suspend/resume
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Wed, 03 Dec 2025 04:06:41 UTC
The branch main has been updated by jaeyoon:
URL: https://cgit.FreeBSD.org/src/commit/?id=3a99f31fdb6c66434f77d83ca5166aae4ad2d22c
commit 3a99f31fdb6c66434f77d83ca5166aae4ad2d22c
Author: Jaeyoon Choi <jaeyoon@FreeBSD.org>
AuthorDate: 2025-12-03 04:05:36 +0000
Commit: Jaeyoon Choi <jaeyoon@FreeBSD.org>
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",