Win11 下 RW_Everything 无法运行的解决方法

最近在 Win11 下发现 RW 无法运行,会弹出下面这个错误提示:

RW_Everything 无法运行的提示信息

经过实验可以用下面的方法解决,将注册表中下面这个值从 1修改为0【参考1】:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity

Enabled DWORD

0 = Off
1 = On

个人感觉,Win11 中有很多安全方面的设置会影响 RW 导致其无法运行。上面只是其中的一个。后面如果再遇到其他原因我会在本文持续更新。

参考:

1.https://www.cnblogs.com/chucklu/p/13130267.html Memory integrity 导致 A driver can’t load on this device

=========================================================

2022年6月8日 RW 无法在 Windows 11 22H2 下运行的解决方法:

Intel Shenzhen 招聘 BIOS(Nov 2021)

Job Description: 

BIOS engineer is responsible for working with customers and architects to drive firmware requirements into clientbased platform designs Responsibilities include triage and replication resolving and tracking incoming customer issues related to the firmware on customer designs involving BIOS ME TBT and other platform components defining compliance requirements for our customers and developingsupporting documents and application notesResponsibilities also include issue management and collaborating with other organizations such as firmware development System validation and other hardware teams to resolve issues together In addition the individual will coordinate with other global organizations A strong technical background is required with the ability to lead taskforces and initiatives supporting customer designs to match launch plans

Qualifications:

You must possess a Bachelor of Science degree in Electrical Engineering Computer Engineering or equivalent with at least four years of industry experience in BIOS with Intel platform development debug and engineering An advanced degree is preferred Additional qualifications include
Excellent problemsolving skills comfortable with the use of software tools to diagnose and debug BIOS firmware and software issues
Excellent knowledge of UEFI kernel architecture PEIDXEBDS dispatch flow EFI driver framework
Strong coding skill knowledge of MASM and C languages
Familiar with PC standards such as PCI SMBUS ACPI USB SATA PCI Express especially ACPI
Deep knowledge of PC architecture hardware software firmware and Windows OS
Excellent communication including spoken and written English and customer support skills
Strong interpersonal skills with proven track record of cross group collaboration
Presentation skills and the ability understand specify and communicate requirements to marketing and engineering teams
Ability to work in a highly ambiguous environment and to achieve high quality results
Ability to international and domestic travel and travel to customer facilities

The following qualifications would be added advantages
Experience in Mobile UEFI BIOS with Intel platform
Experience in EC firmware development
Work experience in BIOS vendor
Experience in UEFI source level debug
Knowledge of Python languages

Primary Location:

PRC, Shenzhen

有兴趣的朋友可以给我发邮件 2925059@qq.com

从 Core 的设计来看CPU 执行指令的流程

最近在看一些资料,这里从 Core 架构入手,研究CPU 是如何读取执行一条指令的。从下面可以看到 Core 架构是很老的设计了:

Intel P6以来核心架构及对应型号、芯片组一览表 【参考1】
Core 架构的指令流程【参考2】

之所以选择 Core 来进行查看最主要的原因是整体流程非常清晰。

首先是“Instruction Fetch and PreDecode” 取得指令,“这个阶段指令预解码单元会接收到从instruction cache或者instruction prefetch buffer发出的16字节,并执行以下任务:”

  • 解码出指令的长度
  • 解码出指令的所有前缀
  • 为指令打上标记(如“是分支 / is branch”)

之后放入 Instruction Queue 中。“Instruction Queue(IQ)最大的作用就是侦测并存储循环指令。IQ内提供了能存储小于18条指令的loop cache,如果loop stream detector(LSD)侦测到IQ内的指令为循环内的指令,则会把这些指令锁定下来,那么在循环期间就可以直接从IQ中获取指令到解码器,从而省去前面fetch以及predecode等工作。如此一来能很好地提高效率以及降低功耗。”

接下来是 Decode,这个阶段会将指令解释为微指令(μop),之前在 CISC 和 RISC 的介绍有提到过,Intel CPU 虽然是 CISC 架构,但是内部会将 CISC指令解释成为类似 RISC 的指令再进行执行。

Allocator/Renamer 是之前介绍过的寄存器重命名,Allocator 应该是为一些微指令(比如: load 和 store)创建 buffer。

Retirement (Reorder buffer, ROB) ,”主要用于记录μops的状态以及存储EU执行完成后返回的结果,然后按照in-order的顺序把执行结果写回寄存器,使得在用户层看来指令在以in-order的顺序执行,in-order写回的这一步被称为retirement。Core微处理器一共可以容纳96项μops。”

“Scheduler(RS)的目的是把μops分配到相应的execution port。为了实现这个目的,RS必须具备识别μop是否ready的能力。当μop的所有的源(source)都就位时则表明该μop为ready,可以让RS调度到execution unit执行。RS会根据Issue Port的可用情况以及已就绪μops的优先级来对μop进行调度。”【参考3】

接下来的指令会进入不同的 ALU 中进行执行,从图中可以看到这些ALU 相对独立,再在运行时可以并行进行,也正是因为这样的设计,保证了CPU的运行效率。

最终的运算结果放在L1D Cache 中。

上面的介绍大部分来自【参考2】,强烈推荐有兴趣的朋友阅读原文。最后再看一下最新的2个架构框图。分别是 Sandy Bridge 和 Icelake 的架构:

Sandy Bridge 的架构【参考4】
IceLake 的架构 【参考4】

有了前面的知识理解这两个并不困难。

参考:

1.https://www.sohu.com/a/245702002_467792 Intel P6以来核心架构及对应型号、芯片组一览表

2.http://www.qdpma.com/systemarchitecture/IntelMicroArchitectureDiagrams.html

3.https://www.cnblogs.com/TaigaCon/p/7678394.html Intel Core Microarchitecture Pipeline

4.Intel® 64 and IA-32 Architectures Optimization Reference Manual

DS3231 2165/165/165 165:165:85 问题分析

最近制作的一个ESP32项目需要使用RTC,最先选中的是一个DS1307,经过实验发现有奇怪的问题:设定好时间之后经过一段时间会出现时间错乱的情况;无奈之下更换为 DS3231。

DS3231 模块

卖家关于这个模块介绍如下:

DS3231是低成本、高精度I2C实时时钟(RTC),具有集成的温补晶振(TCXO)和晶体。该器件包含电池输入端,断开主电源时仍可保持精确的计时。集成晶振提高了器件的长期精确度,并减少了生产线的元件数量。DS3231提供商用级和工业级温度范围,采用16引脚300mil的SO封装。

 RTC保存秒、分、时、星期、日期、月和年信息。少于31天的月份,将自动调整月末的日期,包括闰年的修正。时钟的工作格式可以是24小时或带/AM/PM指示的12小时格式。提供两个可设置的日历闹钟和一个可设置的方波输出。地址与数据通过I2C双向总线串行传输。

精密的、经过温度补偿的电压基准和比较器电路用来监视VCC状态,检测电源故障,提供复位输出,并在必要时自动切换到备份电源。另外,/RST监视引脚可以作为产生μP复位的手动输入。

除计时精度高之外,DS3231还具有一些其它功能,这些功能扩展了系统主机的附加功能和选择范围。该器件内部集成了一个非常精确的数字温度传感器,可通过I2C*接口对其进行访问(如同时间一样)。这个温度传感器的精度为±3°C。片上控制电路可实现自动电源检测,并管理主电源和备用电源(即低压电池)之间的电源切换。如果主电源掉电,该器件仍可继续提供精确的计时和温度,性能不受影响。当主电源重新加电或电压值返回到容许范围内时,片上复位功能可用来重新启动系统微处理器。

模块参数:

1.尺寸:38mm(长)*22mm(宽)*14mm(高)

2.重量:8g

3.工作电压:3.3–5.5V

4.时钟芯片:高精度时钟芯片DS3231

5.时钟精度:0-40℃范围内,精度2ppm,年误差约1分钟

6.带2个日历闹钟

7.可编程方波输出

8.实时时钟产生秒、分、时、星期、日期、月和年计时,并提供有效期到2100年的闰年补偿

9.芯片内部自带温度传感器,精度为±3℃

10.存储芯片:AT24C32(存储容量32K)

11.IIC总线接口,最高传输速度400KHz(工作电压为5V时)

12.可级联其它IIC设备,24C32地址可通过短路A0/A1/A2修改,默认地址为0x57

13.带CR2032电池,保证系统断电后,时钟任然正常走动

14.包装方式:单个防静电包装

接线说明(以Arduino uno r3为例):

SCL→A5

SDA→A4

VCC→5V

GND→GND

从网上看这个比前面那个口碑好很多,但是惊奇的发现经过一段时间运行同样会出现问题。典型问题如下,可以看到前一个时间尚且正常,随后就变成了 2165/165/165 165:165:85 这样的错误时间。

58:2d:34:3a:81:cf,2021/8/26 20:0:48,74.9,31.8

58:2d:34:3a:81:cf,2021/8/26 20:0:50,74.9,31.8

58:2d:34:3a:e6:d1,2021/8/26 20:0:51,77.1,31.8

58:2d:34:3a:e6:d1,2165/165/165 165:165:85,76.3,31.7

58:2d:34:3a:81:cf,2021/8/45 78:79:41,74.8,31.8

58:2d:34:3a:e6:d1,2021/8/45 78:79:41,75.7,31.7

58:2d:34:3a:81:cf,2021/8/45 78:79:41,74.8,31.7

58:2d:34:3a:81:cf,2021/8/45 78:79:43,74.8,31.8

58:2d:34:3a:81:cf,2021/8/45 78:79:45,74.8,31.8

连续试验了2个库都有同样的问题。想来应该不是软件的问题。另外,可以观察到出现错误之后 RTC 仍然会按照错误的时间继续输出,这个说明芯片仍然在工作。最终,我将目光投向了模块本身。前面提到过DS1307电池的问题,这次仍然从这里下手。在工作状态下,首先测量了一下电池电压,在3.3V 说明是电力充足的;之后,拆下来电池再次测量电池位(具体可以从正面下面的位置测量到)。惊奇的发现电压是 4.2V 左右。再接上电池之后发现这个位置虽然是 3.3V 左右,但是以每秒0.01v的速度在升高。经过一段时间的观察发现它能升高到 3.48V,仍然在不停的升高。这个看起来是模块在试图对电池充电。

DS3231 电池电压测量点

在英文网站上搜索发现,有人指出这个模块必须使用可充电纽扣电池:LIR2032,而这种电池的电压是 3.6v。我购买的模块也确实带有了一个这个型号的电池,但是目前已经鼓包了。查看模块的电路如下:

DS3231 电池充电电路

可以看到上面的 VCC 经过 R5 和一个二极管直接给电池供电,这样的话,如果使用3.3V对模块供电,那么出现在电池上的电压是3.3-0.7=2.6V 左右(电池低于2.6V时可以进行充电);但是如果使用5V 对模块供电的话,出现在电池上的压降将会时 5-0.7=4.3V 左右,这个电压远超过电池的最高电压。这就是为什么我看到电池两端电压缓慢上升的原因,也许是原配电池鼓包的原因。

确定这一点后,去掉下面 R5 处的电阻即可断开充电电路:

Rework R5

但是,测试结果仍然会出现错误。但是,如果硬件上有一个问题,那么很可能还有其他的问题。从上面的信息来看,问题发生很可能和 IIC 有关系。从 DS3231 上看过去 IIC 被拉到了 VCC 上。但是,我们的 VCC 是 5V。换句话说,这里出现在 IIC 上的是5V,而ESP32端是 3.3V 的引脚。因此,这里会出现电平不匹配的问题(5V供电的设备可以将 3.3V判定为1, 但是3.3V供电的设备无法正确判断5V信号)。

I2C 上拉

最终的解决方法是:使用 3.3V 作为RTC模块的供电。当然,如果使用3.3V进行供电的话,前面的 rework 也不是必须的动作了。

总结:这个模块的卖家说明是错误的,如果你使用GPIO 是 3.3V的单片机/SOC 作为主控,那么必须使用3.3V对DS3231模块供电才能保证工作。

Step to UEFI (233)屏幕分辨率研究

最近测试 (USH)UEFI Shell Helper 的时候发现一个奇怪的现象:直接启动内置的 Shell 时,会全屏打字显示;而启动 USH 上面的 Shell 后,显示字体非常小。具体照片如下,使用 HDMI 显示器,下图是正常情况:

显示器中分辨率正常,字体足够大

下面是从 USH 启动后的照片,可以看到居中,字体非常小:

分辨率很高,字很小

于是针对这个现象进行一番研究。首先,重新编译 BIOS ,替换它内置的 Shell.efi 为和 USH 相同的版本,测试显示仍然有这样的现象。之后,编译 DEBUG 版本的BIOS, 在串口 Log 中看到如下字样:

GraphicsConsole video resolution 1920 x 1200
Graphics - Mode 0, Column = 80, Row = 25
Graphics - Mode 1, Column = 80, Row = 50
Graphics - Mode 2, Column = 100, Row = 31
Graphics - Mode 3, Column = 240, Row = 63
……………..
GraphicsConsole video resolution 800 x 600
Graphics - Mode 0, Column = 80, Row = 25
Graphics - Mode 1, Column = 0, Row = 0
Graphics - Mode 2, Column = 100, Row = 31

虽然没有找到直接证据,但是感觉上问题和当前屏幕分辨率有关。从代码上上,上面的Log 来自于 edk2\MdeModulePkg\Universal\Console\GraphicsConsoleDxe 代码中。于是,首先编写一个查看当前系统 GRAPHICS_CONSOLE_DEV 的代码,这个结构体定义如下:

typedef struct
{
        UINTN                            Signature;
        EFI_GRAPHICS_OUTPUT_PROTOCOL     *GraphicsOutput;
        EFI_UGA_DRAW_PROTOCOL            *UgaDraw;
        EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  SimpleTextOutput;
        EFI_SIMPLE_TEXT_OUTPUT_MODE      SimpleTextOutputMode;
        GRAPHICS_CONSOLE_MODE_DATA       *ModeData;
        EFI_GRAPHICS_OUTPUT_BLT_PIXEL    *LineBuffer;
} GRAPHICS_CONSOLE_DEV;

对于我们来说,首先枚举系统中的 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL ,之后使用下面的 CR 定义即可找到GRAPHICS_CONSOLE_DEV 结构体,这些都是定义在 edk2\MdeModulePkg\Universal\Console\GraphicsConsoleDxe\GraphicsConsole.h 中的代码:

#define GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS(a) \
  CR (a, GRAPHICS_CONSOLE_DEV, SimpleTextOutput, GRAPHICS_CONSOLE_DEV_SIGNATURE)

具体代码如下:

Private = GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS (SimpleTextOutput);
                
if (Private->Signature != GRAPHICS_CONSOLE_DEV_SIGNATURE) {
                        continue;
                }

Print(L"Show ModeData:\n");
Print(L" Col      : %d\n",Private->ModeData->Columns);
Print(L" Row      : %d\n",Private->ModeData->Rows);
Print(L" DeltaX   : %d\n",Private->ModeData->DeltaX);
Print(L" DeltaY   : %d\n",Private->ModeData->DeltaY);
Print(L" GopWidth : %d\n",Private->ModeData->GopWidth);
Print(L" GopHeight: %d\n",Private->ModeData->GopHeight);
Print(L" GopMode  : %d\n",Private->ModeData->GopModeNumber);

这里可以看到内置 Shell 和启动USH 上的 Shell 会有一些差别,前者运行结果:

Show ModeData:
 Col      : 80
 Row      : 25
 DeltaX   : 80
 DeltaY   : 62
 GopWidth : 800
 GopHeight: 600
 GopMode  : 2

后者运行结果:

Show ModeData:
 Col      : 80
 Row      : 25
 DeltaX   : 640
 DeltaY   : 362
 GopWidth : 1920
 GopHeight: 1200
 GopMode  : 0

两者运行在不同的分辨率下。接下来的问题就是:当运行在 1920×1200 时,是否有机会再切成 800×600 的分辨率呢?

这里还要从 GraphicsConsole 代码入手。在 CheckModeSupported() 函数中,我们可以看到代码使用 GraphicsOutput->SetMode 进行分辨率的切换,于是我们照搬这个代码到我们的 Application 中,切换为 800×600:

        //
        // if not supporting current mode, try 800x600 which is required by UEFI/EFI spec
        //
        HorizontalResolution = 800;
        VerticalResolution   = 600;
        Status = CheckModeSupported (
                     Private->GraphicsOutput,
                     HorizontalResolution,
                     VerticalResolution,
                     &ModeNumber
                     );

运行结果分辨率看起来是正确的,但是内容偏于一隅:

都在一边

这时候我注意到前面还有两个参数DeltaX  和 DeltaY   ,屏幕内容显示的位置应该是这里决定的。查看代码,在InitializeGraphicsConsoleTextMode() 函数中,有计算这两个参数的代码如下:

  NewModeBuffer[ValidCount].DeltaX        = (HorizontalResolution - (NewModeBuffer[ValidCount].Columns * EFI_GLYPH_WIDTH)) >> 1;
  NewModeBuffer[ValidCount].DeltaY        = (VerticalResolution - (NewModeBuffer[ValidCount].Rows * EFI_GLYPH_HEIGHT)) >> 1;   

其中EFI_GLYPH_WIDTH 定义为 8,EFI_GLYPH_HEIGHT定义为 19。例如,当前如果是 800×600分辨率,那么

DeltaX = (800-(80*8))/2=80 就是前面 Private->ModeData->DeltaX 中给出的值。这里我猜测这样的设计是为了保证屏幕内容居中所以进行了这样的设定。但是DeltaX和DeltaY并不会因为切换分辨率而有所不同(屏幕分辨率是GraphicsOutput负责,字符显示由GRAPHICS_CONSOLE_DEV 负责)。所以,我们应该需要手工设定 DeltaX 和 DeltaY,然后再对  Protocol 进行 Reset。完整代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/SimpleFileSystem.h>
#include  <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle
#include  <Library/ShellLib.h>
#include  <Library/MemoryAllocationLib.h>
#include  <Library/BaseMemoryLib.h>
#include  <Protocol/UgaDraw.h>
#include  <Library/DebugLib.h>
#include  <stdio.h>
#include  <stdlib.h>

//
// Device Structure
//
#define GRAPHICS_CONSOLE_DEV_SIGNATURE  SIGNATURE_32 ('g', 's', 't', 'o')

typedef struct
{
        UINTN   Columns;
        UINTN   Rows;
        INTN    DeltaX;
        INTN    DeltaY;
        UINT32  GopWidth;
        UINT32  GopHeight;
        UINT32  GopModeNumber;
} GRAPHICS_CONSOLE_MODE_DATA;

typedef struct
{
        UINTN                            Signature;
        EFI_GRAPHICS_OUTPUT_PROTOCOL     *GraphicsOutput;
        EFI_UGA_DRAW_PROTOCOL            *UgaDraw;
        EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  SimpleTextOutput;
        EFI_SIMPLE_TEXT_OUTPUT_MODE      SimpleTextOutputMode;
        GRAPHICS_CONSOLE_MODE_DATA       *ModeData;
        EFI_GRAPHICS_OUTPUT_BLT_PIXEL    *LineBuffer;
} GRAPHICS_CONSOLE_DEV;

#define GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS(a) \
  CR (a, GRAPHICS_CONSOLE_DEV, SimpleTextOutput, GRAPHICS_CONSOLE_DEV_SIGNATURE)

// Include/Protocol/SimpleTextOut.h
EFI_GUID        gEfiSimpleTextOutProtocolGuid  = { 0x387477C2, 0x69C7, 0x11D2,
        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }
};

/**
  Check if the current specific mode supported the user defined resolution
  for the Graphics Console device based on Graphics Output Protocol.

  If yes, set the graphic devcice's current mode to this specific mode.

  @param  GraphicsOutput        Graphics Output Protocol instance pointer.
  @param  HorizontalResolution  User defined horizontal resolution
  @param  VerticalResolution    User defined vertical resolution.
  @param  CurrentModeNumber     Current specific mode to be check.

  @retval EFI_SUCCESS       The mode is supported.
  @retval EFI_UNSUPPORTED   The specific mode is out of range of graphics
                            device supported.
  @retval other             The specific mode does not support user defined
                            resolution or failed to set the current mode to the
                            specific mode on graphics device.

**/
EFI_STATUS
CheckModeSupported (
  EFI_GRAPHICS_OUTPUT_PROTOCOL  *GraphicsOutput,
  IN  UINT32                    HorizontalResolution,
  IN  UINT32                    VerticalResolution,
  OUT UINT32                    *CurrentModeNumber
  )
{
  UINT32     ModeNumber;
  EFI_STATUS Status;
  UINTN      SizeOfInfo;
  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
  UINT32     MaxMode;

  Status  = EFI_SUCCESS;
  MaxMode = GraphicsOutput->Mode->MaxMode;

  for (ModeNumber = 0; ModeNumber < MaxMode; ModeNumber++) {
    Status = GraphicsOutput->QueryMode (
                       GraphicsOutput,
                       ModeNumber,
                       &SizeOfInfo,
                       &Info
                       );
    if (!EFI_ERROR (Status)) {
      if ((Info->HorizontalResolution == HorizontalResolution) &&
          (Info->VerticalResolution == VerticalResolution)) {
        if ((GraphicsOutput->Mode->Info->HorizontalResolution == HorizontalResolution) &&
            (GraphicsOutput->Mode->Info->VerticalResolution == VerticalResolution)) {
          //
          // If video device has been set to this mode, we do not need to SetMode again
          //
          FreePool (Info);
          break;
        } else {
          Status = GraphicsOutput->SetMode (GraphicsOutput, ModeNumber);
          if (!EFI_ERROR (Status)) {
            FreePool (Info);
            break;
          }
        }
      }
      FreePool (Info);
    }
  }

  if (ModeNumber == GraphicsOutput->Mode->MaxMode) {
    Status = EFI_UNSUPPORTED;
  }

  *CurrentModeNumber = ModeNumber;
  return Status;
}

INTN
EFIAPI
main (
        IN UINTN Argc,
        IN CHAR16 **Argv
)
{
        UINTN                            NumHandles;
        EFI_STATUS                       Status;
        EFI_HANDLE                      *HandleBuffer;
        GRAPHICS_CONSOLE_DEV             *Private;
        UINTN                            Index;
        EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *SimpleTextOutput;
        UINT32                          HorizontalResolution;
        UINT32                          VerticalResolution;
        UINT32                               ModeNumber;
        EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE    *Mode;        
        //
        // Locate all handles that are using the SFS protocol.
        //
        Status = gBS->LocateHandleBuffer(
                        ByProtocol,
                        &gEfiSimpleTextOutProtocolGuid,
                        NULL,
                        &NumHandles,
                        &HandleBuffer);
        if (EFI_ERROR(Status) != FALSE)
        {
                Print(L"failed to locate any handles using the EfiSimpleTextOutProtocol\n");
                goto CleanUp;
        }

        for (Index = 0; (Index < NumHandles); Index += 1)
        {
                Status = gBS->HandleProtocol(
                                HandleBuffer[Index],
                                &gEfiSimpleTextOutProtocolGuid,
                                (VOID**)&SimpleTextOutput);

                if (EFI_ERROR(Status))
                {
                        Print(L"Failed to locate SimpleTextOutProtocol.\n");
                        continue;
                }
                
                Private = GRAPHICS_CONSOLE_CON_OUT_DEV_FROM_THIS (SimpleTextOutput);
                
                if (Private->Signature != GRAPHICS_CONSOLE_DEV_SIGNATURE) {
                        continue;
                }
                
                Print(L"Show ModeData:\n");
                Print(L" Col      : %d\n",Private->ModeData->Columns);
                Print(L" Row      : %d\n",Private->ModeData->Rows);
                Print(L" DeltaX   : %d\n",Private->ModeData->DeltaX);
                Print(L" DeltaY   : %d\n",Private->ModeData->DeltaY);
                Print(L" GopWidth : %d\n",Private->ModeData->GopWidth);
                Print(L" GopHeight: %d\n",Private->ModeData->GopHeight);
                Print(L" GopMode  : %d\n",Private->ModeData->GopModeNumber);

                // Set new screen offset
                Private->ModeData->DeltaX=80;
                Private->ModeData->DeltaY=62;
                //
                // if not supporting current mode, try 800x600 which is required by UEFI/EFI spec
                //
                HorizontalResolution = 800;
                VerticalResolution   = 600;
                Status = CheckModeSupported (
                             Private->GraphicsOutput,
                             HorizontalResolution,
                             VerticalResolution,
                             &ModeNumber
                             );
                
                Mode = Private->GraphicsOutput->Mode;
                
                if (EFI_ERROR (Status) && Mode->MaxMode != 0) {
                  //
                  // Set default mode failed or device don't support default mode, then get the current mode information
                  //
                  HorizontalResolution = Mode->Info->HorizontalResolution;
                  VerticalResolution = Mode->Info->VerticalResolution;
                  ModeNumber = Mode->Mode;
                  Print(L" CheckModeSupported failed\n");
                } else {
                  Print(L" CheckModeSupported passed\n");
                }
                Private->SimpleTextOutput.Reset(&Private->SimpleTextOutput,TRUE);
        }

CleanUp:
        if (HandleBuffer != NULL)
        {
                FreePool(HandleBuffer);
        }

        return(0);
}

经过上述操作之后,可以满足要求,不过有时候并不太稳定需要多次执行。遇到同样问题的朋友可以试试。

完整代码和编译后的EFI 程序:

虽然没有找到从内置Shell 和U盘上 Shell 启动之后分辨率不同的原因,但是我们有了一个可以在 Shell 下切换分辨率的工具,这个问题也算有一个解决方法。

CH340 的替代者:CH343

南京沁恒的 CH340 是非常好用的USB 转串口芯片,在日常使用中完全可以替代FT232R。美中不足的是CH340 虽然在 Datasheet中标明可以支持2 000 000的高速波特率,但是在实际测试中这个波特率会有丢失数据的问题(该问题可以使用 LoopBack 的方式看到)。CH340C 和CH340B 同样都是内置晶振的,但是C 表现会比B 的好一点。我用逻辑分析仪确认过,问题发生在接收的时候,概率性丢失数据。理论上这种问题可以通过通讯协议来克服,但这样做会导致代码复杂性增高可靠性降低。

经过和沁恒的工程师交流,更新的 CH343(貌似是21年6月新出品的)能够支持2000000的波特率,于是尝试之。新型号基本特性和CH340 一样:


●全速 USB 设备接口,兼容 USB V2.0。
●内置固件,仿真标准串口,用于升级原串口外围设备,或者通过 USB 增加额外串口。
● 计算机端 Windows 操作系统下的串口应用程序完全兼容,无需修改。
● 支持免安装的操作系统内置 CDC 类驱动程序或者多功能高速率的 VCP 厂商驱动程序。
● 硬件全双工串口,内置独立的收发缓冲区,支持通讯波特率 50bps~6Mbps。
● 可选自动识别和动态自适应在 115200bps 及以下的常用通讯波特率。
● 串口支持 5、6、7 或者 8 个数据位,支持奇校验、偶校验、空白、标志以及无校验。
● 支持常用的 MODEM 联络信号 RTS、DTR、DCD、RI、DSR、CTS。
● 支持 CTS 和 RTS 硬件自动流控。
● 支持半双工,提供正在发送状态 TNOW 支持 RS485 切换。
● 通过外加电平转换器件,支持 RS232 接口。
● USB 端支持 5V 电源电压和 3.3V 电源电压。
● 串口 I/O 独立供电,支持 5V、3.3V、2.5V、1.8V 电源电压。
● 内置上电复位,内置时钟,无需外部晶振。
● CH343P 内置 EEPROM,可配置芯片 VID、PID、最大电流值、厂商和产品信息字符串等参数。
● 芯片内置 Unique ID(USB Serial Number)。
● 提供 SOP16 和 ESSOP10 及 QFN16 无铅封装,兼容 RoHS。

CH343 这个型号有三种封装,基本功能相同。其中的 CH343P 还提供了修改定制VID,PID 以及其他信息的功能(对标 CH340B)。

CH343 三种型号

这次尝试自己制作了一个  CH343P 的开发板,PCB 如下:

设计的 PCB 验证板

焊接之后发现无法工作,经过了3天的调试最终成功。总结如下:

  1. 务必准备热风枪,电烙铁焊接可靠性不强;
  2. 要想使这款芯片工作,只需要下面4个电设置正确即可
芯片的4个电

首先 VBUS, 需要接到USB接口上面的 VCC(5V);其次,VDD 是芯片供电输入位置,需要输入5V;接下来V3 是芯片内部将5V转为3.3V输出的Pin;最后 VIO 是用来决定UART 信号电平的输入Pin,如果这里是 3.3V 那么 TXD RXD 将会是3.3V,如果是5V 那么TXD RXD 将会是5V 电平。在DetaSheet中有如下描述:

特别注意:如果VIO给的是 3.3V ,而其他 VUART 送入了 5V,那么你的芯片就会损坏(我因为这个原因损坏了2个芯片)。

TXD RXD 工作电平为3.3V 最稳妥的电路如下:

  1. VBUS 和 VDD5 都使用 USB接口上的5V 供电
  2. V3 和 VIO 在一起,这样TXD RXD都是3.3V
电路

当然更稳妥的是跟着参考电路设计(官网可以下载)

官方电路

焊接时建议先焊接 USB 接口,然后焊接上这个芯片,焊接完成后即可插入PC 进行实验,在没有外围电容的情况下,这个芯片是能够正常工作的,确认之后再进行其他外围元件的焊接。

最终调试成功的开发板(上面有一粒大米用于比较尺寸)

CATERR 介绍

CATERR# 是 CPU 上的一个引脚,当CPU 有严重错误发生时,这个引脚会拉低(#表示低有效)。它在所有的Intel CPU 上都有。特别注意:这里是 OD 输出,是没法输出高电平,想要输出高电平,必须外部再接一个上拉电阻(pull-up resistor)。换句话说,如果测量这里为高或者低,务必记得在外面连接一个上拉电阻才能得到正确值【参考3】.

CATERR# 来自【参考1】

Intel 错误分类

首先是两大类:可以检测到的(Detected) 和 不可以检测到的(Undetected)。其中的 Undetected 是非常重要的,因为这种错误无法检测到的错误是没有办法捕捉到和处理的。进一步分为影响不大的 (Benign)和Critical(严重的,这种又被称作 Silent Data Corruption缩写 SDC)。作为系统设计者,必须努力降低这种情况的发生率。

更多的,我们需要关注Detected 这一类。其中又分作可纠正(Corrected)错误(例如,ECC 内存发现了错误,然后可以纠正为正确值)和不可纠正(Uncorrected)错误。例如,我们经常看到的蓝屏就是可以检测不可纠正错误。再进一步,不可纠正(Uncorrected)错误又分作可检测但是不可修正错误(DUE)和可检测不可纠正但可恢复错误(UCR,比如在从U盘Copy 数据到硬盘时,发生了错误,这个错误就是可以检测不可纠正,但是再次尝试读取还可以继续Copy,就是 UCR错误)。

显而易见,我们最大的敌人是DUE。

Intel 内置了 MCA(Machine Check Architecture)来帮助诊断DUE。这也是为什么在碰到稀奇古怪的问题时需要使用 CCA/DCI 的原因:CPU 死翘翘的,只能从不依赖CPU 的路径取得当前的错误。MCA提供了检测和记录:系统总线错误,内存错误,奇偶校验错误,Cache错误和TLB 错误等等。它是通过CPU内部的一组专用的MSR寄存器来实现的。例如:下面就是一组MCA 的 MSR 寄存器:

这里有一个MCA 应用的典型例子【参考4】。当问题发生的时候CATERR#会拉低,进一步检查出现的错误是ROB Timeout(这个有时候也被称作  “three-strike timeout”。3 strike 翻译为“三振出局” ,通常出现问题的时候CPU会进行多次尝试,尝试都失败后就放弃之)。这里提到的 ROB 在前面有介绍过,作用是:“Retirement (Reorder buffer, ROB) ,”主要用于记录μops的状态以及存储EU执行完成后返回的结果,然后按照in-order的顺序把执行结果写回寄存器”。ROB Timeout 的意思是有正在执行的指令超时。所有的指令发送给下一层的 Scheduler 来分配执行的时候会进行记录,如果15秒之后无法得到结果,会报告这个错误。

很明显这样的错误更容易出现在内存读写,IO读写等等和外围设备打交道的情景中。【参考4】提到的错误是发生在内存读写中,发生问题的内存地址是一个 PCIE设备映射的位置,最终配合PCIE 逻辑分析仪找到了原因。

作为BIOS工程师,大部分工作是在诊断定位问题,真正BIOS本身的问题少之又少,类似上面这种问题,如果能确定是某个PICE 的问题,下面直接交给对应工程师或者联系厂商就可以了。

参考:

  1. https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/10th-gen-core-families-datasheet-vol-1-datasheet.pdf
  2. https://www.intel.com/content/dam/www/public/us/en/documents/research/2012-vol16-iss-2-intel-technology-journal.pdf
  3. https://blog.csdn.net/zwl1584671413/article/details/83095044?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163100098916780265447231%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=163100098916780265447231&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_v29-1-83095044.pc_v2_rank_blog_default&utm_term=OD&spm=1018.2226.3001.4450 单片机I/O口推挽输出与开漏输出的区别(open-drain与push-pull)
  4. https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/rob-timeout-debug-guide-paper.pdf

EDK2 202108 来了

edk2-stable202108 在  https://github.com/tianocore/edk2/releases/tag/edk2-stable202108

下载解压 stable202108.tar.gz 之后,尝试编译自带的模拟环境:

  1. edksetup.bat
  2. build -a X64 -p EmulatorPkg\EmulatorPkg.dsc

会遇到下面的错误

stable202108 报错1

错误的原因是无法找到 Brotli相关的内容。解决方法是下载 submodule-MdeModulePkg-Library-BrotliCustomDecompressLib-brotli.zip 将解压后的内容放到\MdeModulePkg\Library 目录下。

再次编译遇到下面的错误:

        "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Vc\bin\x86_amd64\cl.exe" /showIncludes /nologo /E /TC /DVFRCOMPILE /FIFileExplorerLibStrDefs.h /Ic:\buildbs\stable202108\MdeModulePkg\Library\FileExplorerLib  /Ic:\buildbs\stable202108\Build\EmulatorX64\DEBUG_VS2015x86\X64\MdeModulePkg\Library\FileExplorerLib\FileExplorerLib\DEBUG  /Ic:\buildbs\stable202108\MdePkg  /Ic:\buildbs\stable202108\MdePkg\Include  /Ic:\buildbs\stable202108\MdePkg\Test\UnitTest\Include  /Ic:\buildbs\stable202108\MdePkg\Include\X64  /Ic:\buildbs\stable202108\MdeModulePkg  /Ic:\buildbs\stable202108\MdeModulePkg\Include  /Ic:\buildbs\stable202108\MdeModulePkg\Library\BrotliCustomDecompressLib\brotli\c\include c:\buildbs\stable202108\MdeModulePkg\Library\FileExplorerLib\FileExplorerVfr.vfr > c:\buildbs\stable202108\Build\EmulatorX64\DEBUG_VS2015x86\X64\MdeModulePkg\Library\FileExplorerLib\FileExplorerLib\OUTPUT\FileExplorerVfr.i
BootManagerVfr.Vfr
DeviceManagerVfr.Vfr
'VfrCompile' is not recognized as an internal or external command,
operable program or batch file.

查了一下,这个错误应该是 VfrCompile 这个工具没有编译为 EXE 导致的。所以我们需要先编译准备好 Windows 下的Build工具。命令:

edksetup.bat Rebuild

错误如下:

Microsoft (R) Program Maintenance Utility Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

        cl.exe -c  /nologo /Zi /c /O2 /MT /W4 /WX /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /W2 -I .\brotli\c\include  -I . -I C:\BuildBs\stable202108\BaseTools\Source\C\Include -I C:\BuildBs\stable202108\BaseTools\Source\C\Include\Ia32 -I C:\BuildBs\stable202108\BaseTools\Source\C\Common BrotliCompress.c -FoBrotliCompress.obj
cl : Command line warning D9025 : overriding '/W4' with '/W2'
BrotliCompress.c
BrotliCompress.c(20): fatal error C1083: Cannot open include file: './brotli/c/common/constants.h': No such file or directory
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\cl.exe"' : return code '0x2'
Stop.

NMAKE : fatal error U1077: 'if' : return code '0x1'
Stop.
NMAKE : fatal error U1077: 'if' : return code '0x1'
Stop.

应该是缺少BrotliCompress的代码导致的,下载 submodule-BaseTools-Source-C-BrotliCompress-brotli.zip,将内容解压到 \BaseTools\Source\C\BrotliCompress\brotli 中。

再次编译能够正常生成build工具:

stable202108 编译 BaseTools

此外,再次强调必须使用 x86 编译窗口,我是用的是VS2015。

接下来再次编译模拟工具,成功:

stable202108 编译成功

运行  \Build\EmulatorX64\DEBUG_VS2015x86\X64\WinHost.exe

stable202108 模拟器

之后测试编译支持SecureBoot 的OVMF BIOS:

build -a X64 -p OvmfPkg\OvmfPkgx64.dsc -D SECURE_BOOT_ENABLE=TRUE

收到错误提示如下:

Processing meta-data .
Architecture(s)  = X64
Build target     = DEBUG
Toolchain        = VS2015x86

Active Platform          = c:\buildbs\stable202108\OvmfPkg\OvmfPkgX64.dsc
...

build.py...
c:\buildbs\stable202108\CryptoPkg\Library\OpensslLib\OpensslLibCrypto.inf(26): error 000E: File/directory not found in workspace
        c:\buildbs\stable202108\CryptoPkg\Library\OpensslLib\openssl\e_os.h

和前面的方法一样,补充 submodule-CryptoPkg-Library-OpensslLib-openssl.zip 文件到

\CryptoPkg\Library\OpensslLib\openssl 目录下。,

之后即可正常编译。

本问题到的完整编译环境已经打包,可以在 https://pan.baidu.com/s/1pqD3XYAzrZbExRQALxs4OQ 提取码: tuqy  下载。

寄存器重命名(register renaming)

寄存器重命名是计算机CPU的微体系结构(Microarchitecture)中的一种技术,避免了机器指令或者微指令(μop)不必要的顺序化执行,从而提高了处理器的指令级并行的能力。【参考1】

例如:

mov eax, [mem1]
imul eax, 6
mov [mem2], eax
mov eax, [mem3]
add eax, 2
mov [mem4], eax

这段代码看起来是依赖于 EAX 的计算结果,但是如果将最后三条指令中的 EAX 替换为其他寄存器,就可以清楚的看到代码是在执行两个独立的操作:[MEM1]乘以6结果保存在[MEM2], 以及[MEM3]加上2之后存在[MEM4]中。就是说,这两个操作完全可以并行执行。处理器会自动给最后三条指令分配一个另外的寄存器,这样两个运算可以同时进行。

参考:

1.https://baike.baidu.com/item/%E5%AF%84%E5%AD%98%E5%99%A8%E9%87%8D%E5%91%BD%E5%90%8D/10927257?fr=aladdin

好用的 ESP32 DS1307 库

最近需要实验使用 DS1307 作为 RTC,惊奇的发现很多库都是为标准 Arduino 开发的,因此可能遇到使用 ESP32 不支持的寄存器,或者 TimeLib.h 没有定义的情况。

经过一段时间的探索,找到了来自下面这个网址的库

https://www.elecrow.com/wiki/index.php?title=Tiny_RTC

经过实验(在 DFRobot FireBeetle上),可以正常工作。

代码如下:
#include <Wire.h>
#include "RTClib.h"
RTC_DS1307 RTC;

void setup () {
    Serial.begin(9600);
    Wire.begin();
    RTC.begin();
  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
}
void loop () {
    DateTime now = RTC.now(); 
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println(); 
    delay(1000);
}
DS1307 取得时间

有需要的朋友可以在这里: