官方服务微信:dat818 购买与出租对接

如何搭建程序复现笔记本芯片在电源状态切换中的死锁问题

2万

主题

2

回帖

8万

积分

管理员

积分
84864
发表于 2025-2-27 18:33:47 | 显示全部楼层 |阅读模式
    最近碰到一个问题,是关于芯片测试过程的。这颗芯片被用在笔记本的端口上。笔记本的客户那边会进行一个压力测试,测试内容是频繁地进行电脑电源状态的切换,包括 S0(正常使用的开机状态)、S3(睡眠模式)、S4(休眠模式)以及 S5(关机模式)。

    主要是在压力测试过程中,客户发现了芯片会出现不正常的死锁。客户把机台寄了回来,接下来要如何复现呢?客户那边有自己的一套压力测试系统,但是会测试很多东西,不太便于给我们,并且每一次循环耗时比较长。那么,是否可以自己搭建一套能够控制电脑睡眠、休眠、关机以及唤醒的程序呢?

    上面讲述的是一个应用背景,告知大家实际上是存在需求的,只是在平时不太会使用,所以将其记录了下来。

    首先,将电脑从开机状态 S0 切换至 S3 是比较容易实现的。其次,把电脑从开机状态 S0 切换到 S4 也是比较容易实现的。再者,把电脑从开机状态 S0 切换到 S5 同样是比较容易实现的,见下面代码:

<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'>    <pre class="syl-page-code"><code>Application 设置挂起状态为电源状态挂起(PowerState.Suspend),设置为假(false),设置为假(false);这是从 S0 进入 S3 的操作。
Application 设置挂起状态,状态为电源状态休眠,第二个参数为 false,第三个参数为 false;此操作是从 S0 进入 S4。
Process.Start 函数用于启动一个新进程。其中,第一个参数 "shutdown" 表示要执行的操作是关机。第二个参数 "/s /t 0" 中,"/s" 的意思是要关闭计算机,"/t 0" 表示设置关机的延迟时间为 0 秒,即立即关机。
参数 /t 0 的含义是向计算机传达在 0 秒过后执行命令这一信息。
Process.Start("shutdown", "/r /t 0");  此操作中 /r 参数的含义为要重新启动计算机。</code></pre></p>
    如果调用上述语句就能达成从 S0 到其他电源状态的转变,那么反过来要如何唤醒呢?

    唤醒存在难点。处于 S3、S4 以及 S5 的状态时,我的上位机程序不会运行。所以,上位机软件的定时唤醒无法工作。那么笔记本客户那边是如何操作的呢?他们通过底层的 EC 控制来显示上述功能。然而,我们不知道底层 EC 的接口,并且我们需要一个通用的程式,那该怎么实现呢?

    在笔记本的设计里,S3、S4、S5 通常不是所有东西都会关闭。通常会有一个硬件定时器处于开启状态。如果我们能够操作这个定时器,那么是否就可以实现我们所想要的功能呢?

    可以调用以下两个函数,即某个函数和另一个函数,这两个函数能够控制电脑中所开启的硬件定时器,至于这个硬件定时器究竟是位于 CPU 内部还是 EC 内部,我并不知晓,也未曾进行过研究,如果有精通此领域的大神进行过研究,可以留言,我也可以借此学习学习。

<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'>    <pre class="syl-page-code"><code>[DllImport("kernel32.dll")]
公共静态外部方法 SafeWaitHandle 创建可等待定时器(IntPtr lpTimerAttributes,布尔值 bManualReset,字符串 lpTimerName);
在“kernel32.dll”中进行 DllImport 操作,并且设置了 SetLastError 为 true 。
[return: MarshalAs 为 UnmanagedType.Bool 的形式]
公共静态外部方法 SetWaitableTimer 接收一个 SafeWaitHandle 类型的参数 hTimer,一个引用类型的 long 类型参数 pDueTime,一个 int 类型的参数 lPeriod,一个 IntPtr 类型的参数 pfnCompletionRoutine,一个 IntPtr 类型的参数 lpArgToCompletionRoutine,以及一个 bool 类型的参数 fResume,并返回一个 bool 值。该方法用于设置可等待定时器。</code></pre></p>
    另外,有一点需要说明,使用这个定时器是有条件的。首先,你得先设置笔记本,即“Panel>Power>Plan>Power>Sleep>Allow Wake”,以实现定时器唤醒功能。其次,“Panel>Power>Plan>Power>Brad/>a on”,这一步是关闭唤醒需要密码。

    完成上述设置后,电脑能够从 S3、S4、S5 唤醒。然而,在我使用的过程中,遇到了一个问题,即唤醒之后屏幕不亮,会让人误以为没有唤醒。所以,我增加了控制鼠标移动的命令,这样一来,唤醒之后屏幕就会亮起。

<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'>    <pre class="syl-page-code"><code>[DllImport("user32.dll")]
公共静态外部方法 mouse_event,该方法接收一个 32 位整数 dwFlags,一个 32 位整数 dx,一个 32 位整数 dy,一个 32 位整数 dwData,以及一个无符号指针 dwExtraInfo 作为参数。
调用 mouse_event 函数,传入参数 0x0001、0、1、0 和 UIntPtr.Zero,用于模拟鼠标操作。
调用 mouse_event 函数,其参数为 0x0001、0、-1、0 和 UIntPtr.Zero 。 该函数用于模拟鼠标操作,这里的第一个参数 0x0001 可能代表某种特定的鼠标动作。 第二个参数 0 表示鼠标的水平移动量。 第三个参数 -1 表示鼠标的垂直移动量。 第四个参数 0 可能与鼠标的其他相关设置有关。 最后一个参数 UIntPtr.Zero 也可能在鼠标事件的处理中起到特定的作用。</code></pre></p>
    另外需注意,在笔记本从 S0 到 S3/S4/S5 再到 S0 的循环中,S0 以及 S3/S4/S5 这几种状态的停留时间需足够长。因为每台笔记本完全进入各状态的时间不同,例如我自己的笔记本,这些状态的停留时间至少要 20 秒。否则,笔记本尚未完全进入就退出,会致使电脑关机,而此时笔记本还未被唤醒,从而导致程式死锁。而新的刚买的笔记本,只需要设置10s即可完全进入。

    废话不多说,直接上代码:

<p style='margin-bottom:15px;color:#555555;font-size:15px;line-height:200%;text-indent:2em;'>    <pre class="syl-page-code"><code>using System;
系统使用了一系列的集合类型,这些集合类型包含了多个元素,它们共同构成了特定的数据结构,用于存储和管理相关的数据。
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
使用 Microsoft.Win32.SafeHandles 。
System.Runtime.InteropServices 被使用。
namespace AutoSwitchGUI
{
通过这个类可以创建出相应的图形用户界面。
    {
        [DllImport("kernel32.dll")]
公共静态外部方法 SafeWaitHandle 创建可等待定时器(IntPtr lpTimerAttributes,布尔值 bManualReset,字符串 lpTimerName);
在“kernel32.dll”中进行 DllImport 操作,并且设置了 SetLastError 为 true 。
使用 MarshalAs 特性并将其参数设置为 UnmanagedType.Bool 进行返回
公共静态外部方法 SetWaitableTimer 接受一个 SafeWaitHandle 类型的参数 hTimer,以及一个引用类型的 long 类型参数 pDueTime,一个 int 类型的参数 lPeriod,一个 IntPtr 类型的参数 pfnCompletionRoutine,一个 IntPtr 类型的参数 lpArgToCompletionRoutine 和一个 bool 类型的参数 fResume。该方法用于设置等待定时器。
        [DllImport("kernel32.dll")]
公共静态外部函数 SetThreadExecutionState 接收一个 uint 类型的参数 esFlags 并返回一个 uint 值。
        [DllImport("user32.dll")]
公共静态外部方法 mouse_event,它接受一个 32 位整数 dwFlags,一个 32 位整数 dx,一个 32 位整数 dy,一个 32 位整数 dwData,以及一个无符号指针 dwExtraInfo 作为参数。
公共事件 EventHandler 被引发唤醒;
创建了一个私有类型的 BackgroundWorker 对象,名为 bgWorker 。


公共结构体 auto_switch_gui_status_t
        {
            public bool test_status;
            public UInt64 test_times_cnt;
            public UInt64 test_times;
            public byte cur_state;
            public int s0_duration;
            public int s3_duration;
        }
公共的自动切换图形用户界面状态类型 auto_switch_gui_status_t 为 auto_switch_status ;
        public AutoSwitchGUI()
        {
            InitializeComponent();
bgWorker 的 DoWork 事件添加了一个事件处理程序,该处理程序为 bgWorker_Dowork 方法。
bgWorker 的 RunWorkerCompleted 事件被添加了一个新的事件处理程序,该处理程序为 bgWorker_RunWorkerCompleted 。
        }
在 bgWorker 的 DoWork 事件中,当接收到 DoWorkEventArgs e 时,会执行以下操作:在私有方法内部进行相关处理。
        {
long waketime 等于 (long)e.Argument ;
使用 (SafeWaitHandle  handle = CreateWaitableTimer(IntPtr.Zero, true, 这个GetType().Assembly.GetName().Name.ToString() + "Timer"))
            {
如果设置可等待定时器(handle),将唤醒时间(ref waketime)设置为 0,并且设置为非零值(IntPtr.Zero),设置为非零值(IntPtr.Zero),设置为真(true),那么就会成功设置可等待定时器。
                {
使用一个 EventWaitHandle 对象 wh,将其初始化为 false 状态,并且设置为自动重置模式,即 EventResetMode.AutoReset
                    {
                        wh.SafeWaitHandle = handle;
                        wh.WaitOne();
                    }
                }
                else
                {
使用 Marshal.GetLastWin32Error() 获取的最后一个 Windows 32 错误码,并抛出一个新的 Win32Exception 异常。
                }
            }
        }
当 private 方法被触发时,它会接收到两个参数,分别是 sender 和 RunWorkerCompletedEventArgs e,这个方法用于处理后台工作线程完成后的相关操作。
        {
调用 mouse_event 函数,其参数为 0x0001,0,1,0,UIntPtr.Zero 。
调用 mouse_event 函数,参数为 0x0001、0、-1、0 和 UIntPtr.Zero 。
auto_switch_status 的 test_times_cnt 加 1 ;
TestTimes.Text 等于 auto_switch_status.test_times_cnt 转换为字符串后的结果。
SystemTimer 的 Interval 等于 auto_switch_status 的 s0_duration 乘以 1000 。
            SystemTimer.Start();
        }
公共方法用于设置唤醒时间,该时间以 UInt64 类型的数值表示。
        {
bgWorker 运行工作异步操作,操作的参数是当前时间(System.DateTime.Now)加上指定的秒数(time)后转换为文件时间(ToFileTime())。
        }
在点击开始按钮时,会触发这个私有方法。这个方法接收两个参数,一个是发送者对象 sender,另一个是事件参数 EventArgs e。
        {
            try
            {
auto_switch_status 的 test_times 等于将 SetTestTimes.Text 转换为 UInt64 类型
auto_switch_status 的 s0_duration 等于将 S0Duration.Text 转换为整数类型
auto_switch_status 的 s3_duration 等于将 S3Duration.Text 转换为整数类型。
如果 auto_switch_status 的 test_times 大于 0
                {
   


SetThreadExecutionState 会设置一些特定的状态,包括 0x00000001 这个状态,也包括 0x00000002 这个状态,还包括 0x80000000 这个状态,同时也包括 0x00000040 这个状态。
TestStatus 的背景颜色被设置为绿色。
auto_switch_status 的 test_status 等于 true。
                    TestTimes.Text = "0";
auto_switch_status 的 test_times_cnt 等于 0 。
SystemTimer 的 Interval 等于 auto_switch_status 的 s0_duration 乘以 1000 。
auto_switch_status 的当前状态为 0。
                    SystemTimer.Start();
                    return;
                }
            }
            catch
            {
            }
显示一个消息框,内容为“Configuration Failed!”。
        }
在 StopButton 被点击时(对象为 sender,事件为 e),会执行这个私有方法。
        {
            SystemTimer.Stop();
auto_switch_status 的 test_status 等于 true。
TestStatus 的背景颜色被设置为红色。
        }
在 SystemTimer 的 Tick 事件中,当触发该事件时,会调用这个私有方法,参数分别是 sender 和 e 。
        {
如果 auto_switch_status 的当前状态为 0
            {
auto_switch_status 的当前状态为 0。
                SystemTimer.Stop();
如果 auto_switch_status 的 test_times_cnt 大于或等于 auto_switch_status 的 test_times
                {
                }
                else
                {
设置唤醒时间为 (UInt64)auto_switch_status.s3_duration ;
Application 使系统进入挂起状态,挂起状态为 PowerState.Suspend,并且不强制关闭程序,也不保存程序状态。
Application 使系统进入休眠状态(PowerState.Hibernate),并且设置为不强制关闭应用程序,也不关闭系统电源。
                }
               
            }
如果 auto_switch_status 的当前状态等于 1
            {
auto_switch_status 的 test_times_cnt 加 1 ;
TestTimes.Text 等于 auto_switch_status.test_times_cnt 转换后的字符串形式。
                auto_switch_status.cur_state = 0;
               
                SendKeys.Send(" ");
将 "TEST1\r\n" 添加到了 MessageInfo 的 Text 中
            }
        }
    }
}
</code></pre></p>
    另外声明,关于和我是参考如下链接的:

    希望能帮到大家,此代码在我自己的笔记本上可以适用,在客户的笔记本上也可以适用。

更多帖子推荐

您需要登录后才可以回帖 登录 | 立即注册

Archiver|手机版|小黑屋|关于我们

Copyright © 2001-2025, Tencent Cloud.    Powered by Discuz! X3.5    京ICP备20013102号-30

违法和不良信息举报电话:86-13718795856 举报邮箱:hwtx2020@163.com

GMT+8, 2025-4-21 16:34 , Processed in 0.098243 second(s), 18 queries .