suspend/resume流程

Linux的suspend机制是一种节能技术,用于将计算机的当前状态保存起来,然后进入休眠状态,以节省能源。在休眠状态下,计算机的硬件设备会停止工作,而保存的计算机状态被保存在内存或磁盘中。在用户空间向“/sys/power/state”文件分别写入“freeze”、“standby”和“mem”,即可触发它们。

例如:echo mem > /sys/power/state即可触发

流程图:

suspend/resume流程_第1张图片

/sys/power/state节点在Main.c (kernel4.14\kernel\power)中实现,当向/sys/power/state节点中写入时即会调用如下函数state_store:

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
               const char *buf, size_t n)
{
    suspend_state_t state;
    int error;

    error = pm_autosleep_lock();
    if (error)
        return error;

    if (pm_autosleep_state() > PM_SUSPEND_ON) {
        error = -EBUSY;
        goto out;
    }

    state = decode_state(buf, n);
    if (state < PM_SUSPEND_MAX) {
        if (state == PM_SUSPEND_MEM)
            state = mem_sleep_current;

        error = pm_suspend(state);
    } else if (state == PM_SUSPEND_MAX) {
        error = hibernate();
    } else {
        error = -EINVAL;
    }

 out:
    pm_autosleep_unlock();
    return error ? error : n;
}

power_attr(state);

从上面state_store函数分析,调用pm_suspend进入suspend

Suspend.c (kernel4.14\kernel\power) 

int pm_suspend(suspend_state_t state)
{
    int error;

    if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
        return -EINVAL;

    pr_info("suspend entry (%s)\n", mem_sleep_labels[state]);
    error = enter_state(state);
    if (error) {
        suspend_stats.fail++;
        dpm_save_failed_errno(error);
    } else {
        suspend_stats.success++;
    }
    pr_info("suspend exit\n");
    return error;
}

pm_suspend主要调用enter_state函数
 

static int enter_state(suspend_state_t state)
{
    int error;

    trace_suspend_resume(TPS("suspend_enter"), state, true);
    if (state == PM_SUSPEND_TO_IDLE) {
#ifdef CONFIG_PM_DEBUG
        if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
            pr_warn("Unsupported test mode for suspend to idle, please choose none/freezer/devices/platform.\n");
            return -EAGAIN;
        }
#endif
    } else if (!valid_state(state)) {
        return -EINVAL;
    }
    if (!mutex_trylock(&pm_mutex))
        return -EBUSY;

    if (state == PM_SUSPEND_TO_IDLE)
        s2idle_begin();

#ifndef CONFIG_SUSPEND_SKIP_SYNC
    trace_suspend_resume(TPS("sync_filesystems"), 0, true);
    pr_info("Syncing filesystems ... ");
    sys_sync();
    pr_cont("done.\n");
    trace_suspend_resume(TPS("sync_filesystems"), 0, false);
#endif

    pm_pr_dbg("Preparing system for sleep (%s)\n", mem_sleep_labels[state]);
    pm_suspend_clear_flags();
    error = suspend_prepare(state);
    if (error)
        goto Unlock;

    if (suspend_test(TEST_FREEZER))
        goto Finish;

    trace_suspend_resume(TPS("suspend_enter"), state, false);
    pm_pr_dbg("Suspending system (%s)\n", mem_sleep_labels[state]);
    pm_restrict_gfp_mask();
    error = suspend_devices_and_enter(state);
    pm_restore_gfp_mask();

 Finish:
    events_check_enabled = false;
    pm_pr_dbg("Finishing wakeup.\n");
    suspend_finish();
 Unlock:
    mutex_unlock(&pm_mutex);
    return error;
}

从上:

suspend_prepare主要做休眠前的准备工作

suspend_devices_and_enter设备进入休眠

suspend_prepare函数如下:

static int suspend_prepare(suspend_state_t state)
{
	int error, nr_calls = 0;

	if (!sleep_state_supported(state))
		return -EPERM;

	pm_prepare_console();

	error = __pm_notifier_call_chain(PM_SUSPEND_PREPARE, -1, &nr_calls);
	if (error) {
		nr_calls--;
		goto Finish;
	}

	trace_suspend_resume(TPS("freeze_processes"), 0, true);
	error = suspend_freeze_processes();
	trace_suspend_resume(TPS("freeze_processes"), 0, false);
	if (!error)
		return 0;

	suspend_stats.failed_freeze++;
	dpm_save_failed_step(SUSPEND_FREEZE);
 Finish:
	__pm_notifier_call_chain(PM_POST_SUSPEND, nr_calls, NULL);
	pm_restore_console();
	return error;
}

pm_prepare_console函数的主要作用是在系统进入睡眠状态(suspend)之前,准备控制台以使其能够在唤醒时正常工作。

__pm_notifier_call_chain通知一些设备做睡眠前的准备工作

suspend_freeze_processes 函数来挂起所有用户空间进程和kernel线程。挂起进程的目的是确保在睡眠过程中,没有任何进程在访问或修改系统资源,从而避免数据不一致或冲突的情况发生。

suspend_devices_and_enter函数

int suspend_devices_and_enter(suspend_state_t state)
{
	int error;
	bool wakeup = false;

	if (!sleep_state_supported(state))
		return -ENOSYS;

	pm_suspend_target_state = state;

	error = platform_suspend_begin(state);
	if (error)
		goto Close;

	suspend_console();
	suspend_test_start();
	error = dpm_suspend_start(PMSG_SUSPEND);
	if (error) {
		pr_err("Some devices failed to suspend, or early wake event detected\n");
		log_suspend_abort_reason("Some devices failed to suspend, or early wake event detected");
		goto Recover_platform;
	}
	suspend_test_finish("suspend devices");
	if (suspend_test(TEST_DEVICES))
		goto Recover_platform;

	do {
		error = suspend_enter(state, &wakeup);
	} while (!error && !wakeup && platform_suspend_again(state));

 Resume_devices:
	suspend_test_start();
	dpm_resume_end(PMSG_RESUME);
	suspend_test_finish("resume devices");
	trace_suspend_resume(TPS("resume_console"), state, true);
	resume_console();
	trace_suspend_resume(TPS("resume_console"), state, false);

 Close:
	platform_resume_end(state);
	pm_suspend_target_state = PM_SUSPEND_ON;
	return error;

 Recover_platform:
	platform_recover(state);
	goto Resume_devices;
}

platform_suspend_begin平台suspend 开始,一般是空

suspend_console暂停控制台子系统

suspend_test_start 是空

dpm_suspend_start 所有设备的pm suspend回调,如果某个外设的suspend回调失败,可以通过看出这个函数往下追踪log进行去定位

suspend_enter使系统进入给定的睡眠状态

int dpm_suspend_start(pm_message_t state)
{
	int error;

	error = dpm_prepare(state);
	if (error) {
		suspend_stats.failed_prepare++;
		dpm_save_failed_step(SUSPEND_PREPARE);
	} else
		error = dpm_suspend(state);
	return error;
}

dpm_prepare用于设备系统电源转换为prepare,该函数将从dpm_list中获取设备,然后通过device_prepare函数调用pm prepare回调,并帮prepare的设备放到dpm_prepared_list链表中

int dpm_prepare(pm_message_t state)
{
    int error = 0;

    trace_suspend_resume(TPS("dpm_prepare"), state.event, true);
    might_sleep();

    /*
     * Give a chance for the known devices to complete their probes, before
     * disable probing of devices. This sync point is important at least
     * at boot time + hibernation restore.
     */
    wait_for_device_probe();
    /*
     * It is unsafe if probing of devices will happen during suspend or
     * hibernation and system behavior will be unpredictable in this case.
     * So, let's prohibit device's probing here and defer their probes
     * instead. The normal behavior will be restored in dpm_complete().
     */
    device_block_probing();

    mutex_lock(&dpm_list_mtx);
    while (!list_empty(&dpm_list)) {
        struct device *dev = to_device(dpm_list.next);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        trace_device_pm_callback_start(dev, "", state.event);
        error = device_prepare(dev, state);
        trace_device_pm_callback_end(dev, error);

        mutex_lock(&dpm_list_mtx);
        if (error) {
            if (error == -EAGAIN) {
                put_device(dev);
                error = 0;
                continue;
            }
            printk(KERN_INFO "PM: Device %s not prepared "
                "for power transition: code %d\n",
                dev_name(dev), error);
            put_device(dev);
            break;
        }
        dev->power.is_prepared = true;
        if (!list_empty(&dev->power.entry))
            list_move_tail(&dev->power.entry, &dpm_prepared_list);
        put_device(dev);
    }
    mutex_unlock(&dpm_list_mtx);
    trace_suspend_resume(TPS("dpm_prepare"), state.event, false);
    return error;
}

dpm_suspend用于设备系统电源转换为suspend,该函数将从dpm_prepared_list中获取设备,然后通过device_suspend函数调用pm suspend回调,并帮suspend的设备放到dpm_suspended_list链表中

int dpm_suspend(pm_message_t state)
{
    ktime_t starttime = ktime_get();
    int error = 0;

    trace_suspend_resume(TPS("dpm_suspend"), state.event, true);
    might_sleep();

    cpufreq_suspend();

    mutex_lock(&dpm_list_mtx);
    pm_transition = state;
    async_error = 0;
    while (!list_empty(&dpm_prepared_list)) {
        struct device *dev = to_device(dpm_prepared_list.prev);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        error = device_suspend(dev);

        mutex_lock(&dpm_list_mtx);
        if (error) {
            pm_dev_err(dev, state, "", error);
            dpm_save_failed_dev(dev_name(dev));
            put_device(dev);
            break;
        }
        if (!list_empty(&dev->power.entry))
            list_move(&dev->power.entry, &dpm_suspended_list);
        put_device(dev);
        if (async_error)
            break;
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    if (!error)
        error = async_error;
    if (error) {
        suspend_stats.failed_suspend++;
        dpm_save_failed_step(SUSPEND_SUSPEND);
    } else
        dpm_show_time(starttime, state, NULL);
    trace_suspend_resume(TPS("dpm_suspend"), state.event, false);
    return error;
}
 

suspend_enter函数如下:

static int suspend_enter(suspend_state_t state, bool *wakeup)
{
	char suspend_abort[MAX_SUSPEND_ABORT_LEN];
	int error, last_dev;

	error = platform_suspend_prepare(state);
	if (error)
		goto Platform_finish;

	error = dpm_suspend_late(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		pr_err("late suspend of devices failed\n");
		log_suspend_abort_reason("%s device failed to power down",
			suspend_stats.failed_devs[last_dev]);
		goto Platform_finish;
	}
	error = platform_suspend_prepare_late(state);
	if (error)
		goto Devices_early_resume;

	if (state == PM_SUSPEND_TO_IDLE && pm_test_level != TEST_PLATFORM) {
		s2idle_loop();
		goto Platform_early_resume;
	}

	error = dpm_suspend_noirq(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		pr_err("noirq suspend of devices failed\n");
		log_suspend_abort_reason("noirq suspend of %s device failed",
			suspend_stats.failed_devs[last_dev]);
		goto Platform_early_resume;
	}
	error = platform_suspend_prepare_noirq(state);
	if (error)
		goto Platform_wake;

	if (suspend_test(TEST_PLATFORM))
		goto Platform_wake;

	error = disable_nonboot_cpus();
	if (error || suspend_test(TEST_CPUS)) {
		log_suspend_abort_reason("Disabling non-boot cpus failed");
		goto Enable_cpus;
	}

	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());

	error = syscore_suspend();
	if (!error) {
		*wakeup = pm_wakeup_pending();
		if (!(suspend_test(TEST_CORE) || *wakeup)) {
			trace_suspend_resume(TPS("machine_suspend"),
				state, true);
			error = suspend_ops->enter(state);
			trace_suspend_resume(TPS("machine_suspend"),
				state, false);
		} else if (*wakeup) {
			pm_get_active_wakeup_sources(suspend_abort,
				MAX_SUSPEND_ABORT_LEN);
			log_suspend_abort_reason(suspend_abort);
			error = -EBUSY;
		}
		syscore_resume();
	}

	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());

 Enable_cpus:
	enable_nonboot_cpus();

 Platform_wake:
	platform_resume_noirq(state);
	dpm_resume_noirq(PMSG_RESUME);

 Platform_early_resume:
	platform_resume_early(state);

 Devices_early_resume:
	dpm_resume_early(PMSG_RESUME);

 Platform_finish:
	platform_resume_finish(state);
	return error;
}

下面对suspend_enter函数中各个函数进行简要分析:

platform_suspend_prepare函数如下:主要是当state状态不等于PM_SUSPEND_FREEZE 且看suspend_ops->prepare是否存在,若存在,则调用suspend_ops->prepare(),这个一般与平台有关,不同的平台此回调实现不一样。

static int platform_suspend_prepare(suspend_state_t state)
{
    return state != PM_SUSPEND_FREEZE && suspend_ops->prepare ?
        suspend_ops->prepare() : 0;
}

dpm_suspend_late 调用设备的ops->suspend_late回调 ,该函数将从dpm_suspended_list中获取设备,然后通过device_suspend_late函数调用pm ops->suspend_late回调,并帮late suspend的设备放到dpm_late_early_list链表中

int dpm_suspend_late(pm_message_t state)
{
    ktime_t starttime = ktime_get();
    int error = 0;

    trace_suspend_resume(TPS("dpm_suspend_late"), state.event, true);
    mutex_lock(&dpm_list_mtx);
    pm_transition = state;
    async_error = 0;

    while (!list_empty(&dpm_suspended_list)) {
        struct device *dev = to_device(dpm_suspended_list.prev);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        error = device_suspend_late(dev);

        mutex_lock(&dpm_list_mtx);
        if (!list_empty(&dev->power.entry))
            list_move(&dev->power.entry, &dpm_late_early_list);

        if (error) {
            pm_dev_err(dev, state, " late", error);
            dpm_save_failed_dev(dev_name(dev));
            put_device(dev);
            break;
        }
        put_device(dev);

        if (async_error)
            break;
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    if (!error)
        error = async_error;
    if (error) {
        suspend_stats.failed_suspend_late++;
        dpm_save_failed_step(SUSPEND_SUSPEND_LATE);
        dpm_resume_early(resume_event(state));
    } else {
        dpm_show_time(starttime, state, "late");
    }
    trace_suspend_resume(TPS("dpm_suspend_late"), state.event, false);
    return error;
}

platform_suspend_prepare_late函数如下:主要是当state状态等于PM_SUSPEND_FREEZE 且看freeze_ops->prepare是否存在,若存在,则调用freeze_ops->prepare,这个一般与平台有关,不同的平台此回调实现不一样。

static int platform_suspend_prepare_late(suspend_state_t state)
{
    return state == PM_SUSPEND_FREEZE && freeze_ops && freeze_ops->prepare ?
        freeze_ops->prepare() : 0;
}

dpm_suspend_noirq调用设备的ops->suspend_noirq回调 ,该函数将从dpm_late_early_list中获取设备,然后通过device_suspend_noirq函数调用pm ops->suspend_noirq回调,并帮设备放到dpm_noirq_list链表中

int dpm_suspend_noirq(pm_message_t state)
{
    ktime_t starttime = ktime_get();
    int error = 0;

    trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
    cpuidle_pause();
    device_wakeup_arm_wake_irqs();
    suspend_device_irqs();
    mutex_lock(&dpm_list_mtx);
    pm_transition = state;
    async_error = 0;

    while (!list_empty(&dpm_late_early_list)) {
        struct device *dev = to_device(dpm_late_early_list.prev);

        get_device(dev);
        mutex_unlock(&dpm_list_mtx);

        error = device_suspend_noirq(dev);

        mutex_lock(&dpm_list_mtx);
        if (error) {
            pm_dev_err(dev, state, " noirq", error);
            dpm_save_failed_dev(dev_name(dev));
            put_device(dev);
            break;
        }
        if (!list_empty(&dev->power.entry))
            list_move(&dev->power.entry, &dpm_noirq_list);
        put_device(dev);

        if (async_error)
            break;
    }
    mutex_unlock(&dpm_list_mtx);
    async_synchronize_full();
    if (!error)
        error = async_error;

    if (error) {
        suspend_stats.failed_suspend_noirq++;
        dpm_save_failed_step(SUSPEND_SUSPEND_NOIRQ);
        dpm_resume_noirq(resume_event(state));
    } else {
        dpm_show_time(starttime, state, "noirq");
    }
    trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, false);
    return error;
}
 

以上主要是休眠的过程,唤醒的过程主要是休眠的过程的相反动作,这里不做过多介绍了。

下面说一下休眠失败常遇到的情况

1、设备suspend失败

主要dpm_suspend_start返回失败,如下log可以找到对应的设备进行分析:

 PM: Device a600000.ssusb failed to suspend: error -16
 PM: Some devices failed to suspend, or early wake event detected

2、中断唤醒

可以通过如下函数打印log查看是哪个中断唤醒的

void pm_system_irq_wakeup(unsigned int irq_number)
{
    struct irq_desc *desc;
    const char *name = "null";

    if (pm_wakeup_irq == 0) {
        if (msm_show_resume_irq_mask) {
            desc = irq_to_desc(irq_number);
            if (desc == NULL)
                name = "stray irq";
            else if (desc->action && desc->action->name)
                name = desc->action->name;

            pr_warn("%s: %d triggered %s\n", __func__,
                    irq_number, name);

        }
        pm_wakeup_irq = irq_number;
        pm_system_wakeup();
    }
}

3、唤醒事件唤醒

可以通过如下函数打印log查看唤醒源,

bool pm_wakeup_pending(void)
{
    unsigned long flags;
    bool ret = false;

    spin_lock_irqsave(&events_lock, flags);
    if (events_check_enabled) {
        unsigned int cnt, inpr;

        split_counters(&cnt, &inpr);
        ret = (cnt != saved_count || inpr > 0);
        events_check_enabled = !ret;
    }
    spin_unlock_irqrestore(&events_lock, flags);

    if (ret) {
        pr_info("PM: Wakeup pending, aborting suspend\n");
        pm_print_active_wakeup_sources();
    }

    return ret || pm_abort_suspend;
}

你可能感兴趣的:(休眠唤醒,linux)