目录
一 . UFS Hibernate简介
二 . UFS Hibernate系统上的行为
1.Auto-Hibernation
2. Clk-gate with hibernate
3.suspend with hibernate
一 . UFS Hibernate简介
1. 系统 Soc上的UFS Host Controller 模块处理完成命令和数据后,就会进入UFS Idle状态,也就是空闲状态, 也会调用UFS相关的PM Runtime的回调接口,执行对应的操作,进入UFS Idle之后,又会通过发送DME_HIBERNATE_ENTER命令让Link进入Hibernate状态,受影响的是本地和对等 UniPort(UniPro L1.5-L4 和 M-PHY)。
2. 当系统Soc上的UFS Host Controller收到命令和数据请求后,又会会进入UFS Active状态,进入UFS Active状态之后,通过发送 DME_HIBERNATE_EXIT命令让Link出Hibernate状态。
Note: Link(链路)指的是也是本地端和远程端的数据通道,也可以理解为M-PHY层的M-TX和M-RX。
5.6.1 UniPro Link Management 869 An available Lane is a Lane that is implemented in the PHY. A Lane can be either connected or unconnected, depending of the interconnection between the local PHY and the remote PHY. The connection state is automatically discovered during the Link Startup Sequence. An active Lane is used for data transfers while an inactive or unused Lane is not. The number of active Lanes is set by PA_ActiveTxDataLanes and PA_ActiveRxDataLanes
二 . UFS Hibernate系统上的行为
UFS Hibernate在系统上有3种行为,分别为 Auto-Hibernate, Clk-gate with hibernate, suspend with hibernate(其中suspend分为runtime suspend和runtime suspend).
1. Auto-Hibernate
Auto-Hibernation是UFS Host Controller的一个功能,可以通过UFS Host Controller Register Offset 00h: CAP - Controller Capabilities Bit[23]的值来判断释放支持该功能,Bit[23] Vaue为1,说明支持 Auto-Hibernate功能,为0说明不支持 Auto-Hibernate.
Auto-Hibernate功能完全是通过硬件去实现的,
主要过程:当UFS Host Controller处理完成命令和数据后,会间隔AHIT时间通过硬件层面给UFS Device发送DME_HIBERNATE_ENTER命令,全程系统软件无感知,所以通过Event Trace也抓不到对应的Event事件,想要验证这个过程,可以通过PA协议分析仪去做实验,分析Unipro协议包会有Hibernate的整个流程。
上文的AHIT指的是UFS Host Controller处理完成命令和数据后,会间隔一段时间,才发送DME_HIBERNATE_ENTER命令,这个间隔的时间指的就是AHIT
(1)这个AHIT可以通过系统软件去配置,
(2)这个AHIT值可以通过Sys节点去查询(不同的系统软件节点位置可能有差异)
# cat /sys/device/platform/soc/1d84000.ufshc/auto_hibern8 1000
UFS Hibernate实现相关:
ufshcd_init:初始化UFS Hibernate相关信息,设置Auto Hibernate的时间位1ms,即当UFS Host控制器空闲后,间隔1ms就会通过硬件下发DME_HIBERNATE_ENTER命令给UFS Device让Link处于Hibernate状态。
static void ufshcd_init_hibern8(struct ufs_hba *hba)
{
struct ufs_hibern8_on_idle *h8 = &hba->hibern8_on_idle;
h8->state = HIBERN8_EXITED;
if (!ufshcd_is_hibern8_on_idle_allowed(hba) &&
!ufshcd_is_auto_hibern8_supported(hba))
return;
if (ufshcd_is_auto_hibern8_supported(hba)) {
hba->ahit = FIELD_PREP(UFSHCI_AHIBERN8_TIMER_MASK, 1) |
FIELD_PREP(UFSHCI_AHIBERN8_SCALE_MASK, 3);
h8->state = AUTO_HIBERN8;
hba->caps &= ~UFSHCD_CAP_HIBERN8_ENTER_ON_IDLE;
} else {
h8->delay_ms = 10;
INIT_DELAYED_WORK(&hba->hibern8_on_idle.enter_work,
ufshcd_hibern8_enter_work);
INIT_WORK(&hba->hibern8_on_idle.exit_work,
ufshcd_hibern8_exit_work);
h8->is_enabled = true;
h8->delay_attr.show = ufshcd_hibern8_on_idle_delay_show;
h8->delay_attr.store = ufshcd_hibern8_on_idle_delay_store;
sysfs_attr_init(&h8->delay_attr.attr);
h8->delay_attr.attr.name = "hibern8_on_idle_delay_ms";
h8->delay_attr.attr.mode = 0644;
if (device_create_file(hba->dev, &h8->delay_attr))
dev_err(hba->dev, "Failed to create sysfs for hibern8_on_idle_delayn");
h8->enable_attr.show = ufshcd_hibern8_on_idle_enable_show;
h8->enable_attr.store = ufshcd_hibern8_on_idle_enable_store;
sysfs_attr_init(&h8->enable_attr.attr);
h8->enable_attr.attr.name = "hibern8_on_idle_enable";
h8->enable_attr.attr.mode = 0644;
if (device_create_file(hba->dev, &h8->enable_attr))
dev_err(hba->dev, "Failed to create sysfs for hibern8_on_idle_enablen");
}
}
ufshcd_probe_hba:将设置Auto Hibernate的时间写入AHIT寄存器
static void ufshcd_set_auto_hibern8_timer(struct ufs_hba *hba)
{
unsigned long flags;
if (!ufshcd_is_auto_hibern8_supported(hba))
return;
spin_lock_irqsave(hba->host->host_lock, flags);
ufshcd_writel(hba, hba->ahit, REG_AUTO_HIBERNATE_IDLE_TIMER);
mb();
spin_unlock_irqrestore(hba->host->host_lock, flags);
}
2. Clk-gate with hibernate
接触过内核驱动的同学们应该都知道,一个进程在访问对应的驱动设备的之前需要先Hold住驱动设备,获取到对应的资源,资源包括驱动设备的访问请求,CLK资源等, CPU资源等, 保证此设备只允许本进程访问,避免出现多进程同时访问此驱动设备出现不可预知的问题。访问完成之后需要释放对应的资源。
其中CLK资源是内核驱动比较重要的资源,在UFS控制器驱动中,在访问UFS控制器之前,需要先让LInk 推出Hibernate状态,然后获取并使能CLK资源,访问完成UFS控制器后,需要先让Link进入Hibernate状态,然后将CLK资源关闭,可以节省功耗。
总的来说,UFS控制器处理完成命令和数据之后,就会进入UFS Idle,进入UFS Idle之后,就会发起Clk gate的动作,从 __ufshcd_release开始计时,间隔delay_ms给UFS Devices发送DME_HIBERNATE_ENTER命令。
其中间隔时间delay_ms是在UFS初始化里面设置的:分为性能模式和省电模式
delay_ms分为三个阶段:
UFS初始化阶段:默认设置为性能模式,delay_ms设置为50ms, 即UFS Idle之后50ms进行CLK Gate流程。
UFS Gear Scale UP阶段:设置为性能模式,delay_ms设置为50ms, 即UFS Idle之后50ms进行CLK Gate流程。
UFS Gear Scale Down阶段:设置为省电模式,delay_ms设置为10ms, 即UFS Idle之后10ms进行CLK Gate流程。
#define UFSHCD_CLK_GATING_DELAY_MS_PWR_SAVE 10 #define UFSHCD_CLK_GATING_DELAY_MS_PERF 50
CLK Gate系统相关的接口:
# cat /sys/device/platform/soc/1d84000.ufshc/clkgate_delay_ms_perf 50 # cat /sys/device/platform/soc/1d84000.ufshc/clkgate_delay_ms_pwr_save 10
UFS CLK Gate整体流程:
| __ufshcd_transfer_req_compl | UFS命令和数据处理完成的处理函数 |
| __ufshcd_release | UFS控制器驱动处理完成请求之后,释放相关的资源,比如CLK资源 |
| hrtimer_start(&hba->clk_gating.gate_hrtimer,ms_to_ktime(hba->clk_gating.delay_ms),HRTIMER_MODE_REL); | 启动CLK_Gate回调函数 |
| ufshcd_gate_work | CLK Gate具体实现,主要是将Link放到Hibernate状态,然后关闭CLK |
| ufshcd_uic_hibern8_enter | 将Link置为Hibernate Mode |
| ufshcd_disable_clocks | 关闭CLK资源 |
UFS实现相关:
__ufshcd_release:
static void __ufshcd_release(struct ufs_hba *hba, bool no_sched)
{
if (!ufshcd_is_clkgating_allowed(hba))
return;
hba->clk_gating.active_reqs--;
if (hba->clk_gating.active_reqs || hba->clk_gating.is_suspended
|| hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL
|| hba->lrb_in_use || hba->outstanding_tasks
|| hba->active_uic_cmd || hba->uic_async_done
|| ufshcd_eh_in_progress(hba) || no_sched)
return;
hba->clk_gating.state = REQ_CLKS_OFF;
trace_ufshcd_clk_gating(dev_name(hba->dev), hba->clk_gating.state);
hba->ufs_stats.clk_rel.ts = ktime_get();
hrtimer_start(&hba->clk_gating.gate_hrtimer,
ms_to_ktime(hba->clk_gating.delay_ms),
HRTIMER_MODE_REL);
}
ufshcd_gate_work:
static void ufshcd_gate_work(struct work_struct *work)
{
struct ufs_hba *hba = container_of(work, struct ufs_hba,
clk_gating.gate_work);
unsigned long flags;
spin_lock_irqsave(hba->host->host_lock, flags);
#ifdef VENDOR_EDIT
//yh@BSP.Storage.UFS, 2020-1-15 add for fix race condition during hrtimer active(ufshcd_release/ufshcd_gate_work)
if (hba->clk_gating.state == CLKS_OFF)
{
goto rel_lock;
}
#endif
if (hba->clk_gating.is_suspended ||
(hba->clk_gating.state != REQ_CLKS_OFF)) {
hba->clk_gating.state = CLKS_ON;
trace_ufshcd_clk_gating(dev_name(hba->dev),
hba->clk_gating.state);
goto rel_lock;
}
if (hba->clk_gating.active_reqs
|| hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL
|| hba->lrb_in_use || hba->outstanding_tasks
|| hba->active_uic_cmd || hba->uic_async_done)
goto rel_lock;
spin_unlock_irqrestore(hba->host->host_lock, flags);
if (ufshcd_is_hibern8_on_idle_allowed(hba) &&
hba->hibern8_on_idle.is_enabled)
flush_delayed_work(&hba->hibern8_on_idle.enter_work);
if (ufshcd_can_hibern8_during_gating(hba)) {
if (ufshcd_uic_hibern8_enter(hba)) {
hba->clk_gating.state = CLKS_ON;
trace_ufshcd_clk_gating(dev_name(hba->dev),
hba->clk_gating.state);
goto out;
}
ufshcd_set_link_hibern8(hba);
}
if ((ufshcd_is_auto_hibern8_enabled(hba) ||
!ufshcd_is_link_active(hba)) && !hba->no_ref_clk_gating)
ufshcd_disable_clocks(hba, true);
else
ufshcd_disable_clocks_keep_link_active(hba, true);
ufshcd_hba_vreg_set_lpm(hba);
spin_lock_irqsave(hba->host->host_lock, flags);
if (hba->clk_gating.state == REQ_CLKS_OFF) {
hba->clk_gating.state = CLKS_OFF;
trace_ufshcd_clk_gating(dev_name(hba->dev),
hba->clk_gating.state);
}
rel_lock:
spin_unlock_irqrestore(hba->host->host_lock, flags);
out:
return;
}
__ufshcd_uic_hibern8_enter
static int __ufshcd_uic_hibern8_enter(struct ufs_hba *hba)
{
int ret;
struct uic_command uic_cmd = {0};
ktime_t start = ktime_get();
ufshcd_vops_hibern8_notify(hba, UIC_CMD_DME_HIBER_ENTER, PRE_CHANGE);
uic_cmd.command = UIC_CMD_DME_HIBER_ENTER;
ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
trace_ufshcd_profile_hibern8(dev_name(hba->dev), "enter",
ktime_to_us(ktime_sub(ktime_get(), start)), ret);
ufsdbg_error_inject_dispatcher(hba, ERR_INJECT_HIBERN8_ENTER, 0, &ret);
if (ret || hba->full_init_linereset) {
int err;
hba->full_init_linereset = false;
ufshcd_update_error_stats(hba, UFS_ERR_HIBERN8_ENTER);
dev_err(hba->dev, "%s: hibern8 enter failed. ret = %dn",
__func__, ret);
err = ufshcd_link_recovery(hba);
if (err) {
dev_err(hba->dev, "%s: link recovery failedn",
__func__);
ret = err;
} else {
ret = -EAGAIN;
}
} else {
ufshcd_vops_hibern8_notify(hba, UIC_CMD_DME_HIBER_ENTER,
POST_CHANGE);
dev_dbg(hba->dev, "%s: Hibern8 Enter at %lld usn", __func__,
ktime_to_us(ktime_get()));
return ret;
}
3.suspend with hibernate
系统为了在空闲的时候可以释放关闭一些不需要的资源,在需要用到的时候开启需要的资源,提供了PM框架,主要是包括深睡deep sleep(也称为system suspend)和浅睡light sleep(也称为runtime suspend), 合理运用PM机制在系统空闲的时候,进入睡眠模式。可以降低功耗,提高续航能力。
我们此篇以浅睡light sleep(runtime suspend)举例说明:
基本流程是UFS控制器处理完成命令和数据后,进入UFS Idle, UFS Idle保持3s后,开始进入ufs runtime suspend流程,给UFS Devices发送DME_HIBERNATE_ENTER命令, 让Link进入Hibernate状态。
相关接口:
# cat /sys/device/platform/soc/1d84000.ufshc/power/autosuspend_delay_ms 3
UFS suspend with hibernate Flow:
相关实现:
ufshcd_suspend:
static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
{
int ret = 0;
enum ufs_pm_level pm_lvl;
enum ufs_dev_pwr_mode req_dev_pwr_mode;
enum uic_link_state req_link_state;
hba->pm_op_in_progress = 1;
if (!ufshcd_is_shutdown_pm(pm_op)) {
pm_lvl = ufshcd_is_runtime_pm(pm_op) ?
hba->rpm_lvl : hba->spm_lvl;
req_dev_pwr_mode = ufs_get_pm_lvl_to_dev_pwr_mode(pm_lvl);
req_link_state = ufs_get_pm_lvl_to_link_pwr_state(pm_lvl);
} else {
req_dev_pwr_mode = UFS_POWERDOWN_PWR_MODE;
req_link_state = UIC_LINK_OFF_STATE;
}
#if defined(VENDOR_EDIT) && defined(CONFIG_UFSFEATURE)
ufsf_hpb_suspend(&hba->ufsf);
ufsf_tw_suspend(&hba->ufsf);
#endif
WARN_ON(hba->hibern8_on_idle.is_enabled &&
hba->hibern8_on_idle.active_reqs);
ufshcd_hold_all(hba);
hba->clk_gating.is_suspended = true;
hba->hibern8_on_idle.is_suspended = true;
if (hba->clk_scaling.is_allowed) {
cancel_work_sync(&hba->clk_scaling.suspend_work);
cancel_work_sync(&hba->clk_scaling.resume_work);
ufshcd_suspend_clkscaling(hba);
}
if (req_dev_pwr_mode == UFS_ACTIVE_PWR_MODE &&
req_link_state == UIC_LINK_ACTIVE_STATE) {
goto disable_clks;
}
if ((req_dev_pwr_mode == hba->curr_dev_pwr_mode) &&
(req_link_state == hba->uic_link_state))
goto enable_gating;
if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba))
goto set_vreg_lpm;
if (ufshcd_is_runtime_pm(pm_op)) {
if (ufshcd_can_autobkops_during_suspend(hba)) {
ret = ufshcd_urgent_bkops(hba);
if (ret)
goto enable_gating;
} else {
ufshcd_disable_auto_bkops(hba);
}
ufshcd_wb_toggle_flush(hba);
} else if (!ufshcd_is_runtime_pm(pm_op)) {
ufshcd_wb_buf_flush_disable(hba);
hba->dev_info.keep_vcc_on = false;
}
if ((req_dev_pwr_mode != hba->curr_dev_pwr_mode) &&
((ufshcd_is_runtime_pm(pm_op) && (!hba->auto_bkops_enabled)
&& !hba->wb_buf_flush_enabled) ||
!ufshcd_is_runtime_pm(pm_op))) {
ufshcd_disable_auto_bkops(hba);
ret = ufshcd_set_dev_pwr_mode(hba, req_dev_pwr_mode);
if (ret)
goto enable_gating;
}
ret = ufshcd_link_state_transition(hba, req_link_state, 1);
if (ret)
goto set_dev_active;
if (ufshcd_is_link_hibern8(hba) &&
ufshcd_is_hibern8_on_idle_allowed(hba))
hba->hibern8_on_idle.state = HIBERN8_ENTERED;
set_vreg_lpm:
if (!hba->auto_bkops_enabled)
ufshcd_vreg_set_lpm(hba);
disable_clks:
ret = ufshcd_vops_suspend(hba, pm_op);
if (ret)
goto set_link_active;
ufshcd_disable_irq(hba);
if (!ufshcd_is_link_active(hba))
ret = ufshcd_disable_clocks(hba, false);
else
ret = ufshcd_disable_clocks_keep_link_active(hba, false);
if (ret)
goto set_link_active;
if (ufshcd_is_clkgating_allowed(hba)) {
hba->clk_gating.state = CLKS_OFF;
trace_ufshcd_clk_gating(dev_name(hba->dev),
hba->clk_gating.state);
}
ufshcd_hba_vreg_set_lpm(hba);
goto out;
set_link_active:
ufshcd_enable_irq(hba);
if (hba->clk_scaling.is_allowed)
ufshcd_resume_clkscaling(hba);
ufshcd_vreg_set_hpm(hba);
if (ufshcd_is_link_hibern8(hba) && !ufshcd_uic_hibern8_exit(hba)) {
ufshcd_set_link_active(hba);
} else if (ufshcd_is_link_off(hba)) {
ufshcd_update_error_stats(hba, UFS_ERR_VOPS_SUSPEND);
ufshcd_host_reset_and_restore(hba);
}
set_dev_active:
if (!ufshcd_set_dev_pwr_mode(hba, UFS_ACTIVE_PWR_MODE))
ufshcd_disable_auto_bkops(hba);
enable_gating:
if (hba->clk_scaling.is_allowed)
ufshcd_resume_clkscaling(hba);
hba->hibern8_on_idle.is_suspended = false;
hba->clk_gating.is_suspended = false;
ufshcd_release_all(hba);
#if defined(VENDOR_EDIT) && defined(CONFIG_UFSFEATURE)
ufsf_hpb_resume(&hba->ufsf);
ufsf_tw_resume(&hba->ufsf);
#endif
out:
hba->pm_op_in_progress = 0;
if (ret)
ufshcd_update_error_stats(hba, UFS_ERR_SUSPEND);
return ret;
}
ufshcd_link_state_transition:
static int ufshcd_link_state_transition(struct ufs_hba *hba,
enum uic_link_state req_link_state,
int check_for_bkops)
{
int ret = 0;
if (req_link_state == hba->uic_link_state)
return 0;
if (req_link_state == UIC_LINK_HIBERN8_STATE) {
ret = ufshcd_uic_hibern8_enter(hba);
if (!ret)
ufshcd_set_link_hibern8(hba);
else
goto out;
}
else if ((req_link_state == UIC_LINK_OFF_STATE) &&
(!check_for_bkops || (check_for_bkops &&
!hba->auto_bkops_enabled))) {
ret = ufshcd_uic_hibern8_enter(hba);
if (ret)
goto out;
ufshcd_hba_stop(hba, true);
ufshcd_set_link_off(hba);
}
out:
return ret;
}



