如何制作一个 UEFI Shell 启动盘

UEFI Shell 是一个类似 DOS Command 启动环境的东西,对于普通用户来说它最常见的功能是启动进入之后能够烧写更新BIOS或者其他Firmware。

这次就介绍一个如何制作标准的 UEFI Shell 启动盘。

简单的说,主要分为两步:

1.格式化一个U盘,特别注意需要格式化为 FAT

对于一些容量较大的U盘,还可以选择 exFAT,但是兼容性会差一些,不能保证一定能够启动

2.下载一个 UEFI Shell 的 EFI 文件,这个是 EDK2 开源的项目,在 https://github.com/tianocore/edk2 下载到源代码后,可以编译获得 EFI 文件。如果觉得麻烦,可以在这里下载一个我编译好的(来自edk2-stable202408.01)

3.在上面格式化好的U盘上创建 EFI\BOOT 目录,然后放入 UEFI Shell 文件并且命名为 BOOTX64.EFI

4.在目标机上选择启动到U盘,即可进入 UEFI Shell。

如果你希望其他版本的UEFI Shell(比如 IA32版本, ARM 版本),可以在 https://github.com/pbatard/UEFI-Shell/releases/tag/24H2 下载到

EasyX 测试颜色渐变代码

让AI 给我写了一个颜色渐变的算法,代码如下:

#include <graphics.h>  // EasyX图形库头文件
#include <conio.h>     // 用于_getch()
#include <math.h>    
#include <stdio.h>  
#include <cstdio>
#include <conio.h>
#include <tchar.h>

// HSL转RGB辅助函数
void HSLtoRGB(float h, float s, float l, BYTE& r, BYTE& g, BYTE& b) {
    float q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    float p = 2 * l - q;
    float t[3] = { h + 1 / 3.0f, h, h - 1 / 3.0f };

    for (int i = 0; i < 3; ++i) {
        if (t[i] < 0) t[i] += 1;
        if (t[i] > 1) t[i] -= 1;

        if (t[i] < 1 / 6.0f)
            t[i] = p + (q - p) * 6 * t[i];
        else if (t[i] < 0.5f)
            t[i] = q;
        else if (t[i] < 2 / 3.0f)
            t[i] = p + (q - p) * 6 * (2 / 3.0f - t[i]);
        else
            t[i] = p;
    }

    r = static_cast<BYTE>(t[0] * 255);
    g = static_cast<BYTE>(t[1] * 255);
    b = static_cast<BYTE>(t[2] * 255);
}

// 在视图类中定义成员变量
float m_currentHue = 0.0f;        // 当前色相值 [0,1]
float m_targetHue = 0.0f;         // 目标色相值 [0,1]
float m_transitionSpeed = 0.1f; // 颜色过渡速度


int main()
{
    char Buffer[256];
    // 初始化640x480像素的图形窗口
    initgraph(640, 480);

    for (int i = 0; i < 60*30; i++) {
        BeginBatchDraw();
        cleardevice();
        // 线性插值过渡
        m_currentHue += (m_targetHue - m_currentHue) * m_transitionSpeed;

        // 到达目标时生成新随机目标
        if (fabs(m_targetHue - m_currentHue) < 0.001f) {
            m_targetHue = static_cast<float>(rand() % 1000) / 1000.0f;
        }

        BYTE r, g, b;
        HSLtoRGB(m_currentHue, 0.7f, 0.5f, r, g, b); // 固定饱和度和亮度
        printf("[%f]->%d  %d  %d\n", m_currentHue,r, g, b);
        setbkcolor(RGB(r, g, b));
        EndBatchDraw();
       
        sprintf_s(Buffer, sizeof(Buffer), "output\\%05d.png", i);
        LPCTSTR lpctstr = (LPCTSTR)Buffer;  // 直接强制类型转换
        printf("%s\n", Buffer);
        saveimage(lpctstr);

    }

    // 保持窗口显示
    //_getch();

    // 关闭图形窗口
    closegraph();
    return 0;
}

上述代码能够无限生成渐变的颜色。

测试的视频如下:

Step to UEFI (298)将 RU 装入 Shell 中

这次的实验是将 RU 内置在Shell 中,当你输入 zru 之后就可以执行 RU(特别命令为 zru 是为了和 ru 避免冲突)。代码是基于之前的 MyCmd 实现的。特别需要注意的地方是 RuCommand.c 文件中的 MyShellCommand() 函数中,我们调用 LoadImage 需要传递 ParentImageHandle, 但是MyShellCommand(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) 传递进来的ImageHandle一直是0。这样会导致LoadImage调用失败。因此,这里使用了gImageHandle,给出的是 UEFI Shell 的 Handle。

        //
        // Load the image with:
        //
        Status = gBS->LoadImage(
                        FALSE,
                        gImageHandle,
                        DP,
                        (VOID*)&amp;ru_efi[0],
                        ru_efi_size,
                        &amp;NewHandle);     
        if (EFI_ERROR(Status)) {
                Print(L"Load image Error! %r\n",Status);
                return 0;
        }

最终生成的完整 UEFI Shell:

完整的代码(基于 EDK2 202411 ):

这样做的好处是方便用户使用,只用一个文件就可以实现很多功能,缺点是:更新内容不太容易。

参考:

1. https://www.lab-z.com/stu165/

检查 HSTI 的简便方法

‌HSTI(Hardware Security Test Interface)‌是由Microsoft定义的硬件安全测试接口,其全称为Hardware Security Testability Interface。HSTI的主要作用是确保Windows设备上的安全配置正确,通过定义一系列测试来验证设备的安全性。这些测试包括SPI闪存或eMMC分区锁定、SMM配置、Intel Boot Guard等安全功能的正确设置‌。

基本的原理是:BIOS在启动的时候进行一些检查,然后将检查结果汇报给 Windows。在进行微软 WHQL 测试时,这是一个必测的项目。通常情况下,BIOS工程师需要搭建WHQL测试环境,在目标机上安装 WHQL Client软件,每次修改后都需要在 WHQL Server 上运行测试并且查看结果。

现在,可以通过本地运行 FaintSnow 编写的 HE (Hardware Read & Write Utility)来进行检测,避免繁琐的WHQL环境设定:

打开工具后在 Tools ->Dump Hardware Security Test Interface Report中即可看到结果:

工具读取到的信息:

可以直接拉到最下面查看结果:

作者的网站是 http://hwrwdrv.phpnet.us 。可以在上面看到最新版。此外,还可以在 https://www.lab-z.com/allsoft/ 常用测试工具以及软件下载页面看到(不定期更新,下载速度较快),有测试需要的朋友可以试试这个工具。

参考:

1. https://www.lab-z.com/hstiwhql/

USB 手柄转无线

这是一个能够将USB 手柄转为无线手柄的设备。这样,你可以将有线的 USB 手柄变为无线的手柄进行游戏。之所以做这个项目是因为我发现Linux 系统在支持多HID设备上和 Windows上有所不同。之前设计的,能够在 Windows上工作正常的USB转蓝牙设备无法在 Linux 系统上正常工作。

这次带来的方案是有两部分:发射端和接收端。

发射端是 ESP32 C3 配合 CH9350实现的,它能够实现USB手柄数据的读取和无线信号的发送。其中 CH9350负责USB手柄数据的解析;解析后的数据由ESP32 C3通过ESP-Now发送出去。CH9350是 WCH (就是出品 Ch340 的那个公司)推出的USB HID 转串口通讯控制芯片。就是说USB 手柄连接到这个芯片之后,数据会转化为串口输出。关于这个芯片的功能介绍如下:

  • 支持12Mbps全速USB传输和1.5Mbps低速USB传输,兼容USB V2.0。
  • 上位机端USB端口符合标准HID类协议,不需要额外安装驱动程序,支持内置HID类设备驱动的Windows、Linux、macOS等操作系统。
  • 同一芯片可配置为上位机模式和下位机模式,分别连接USB-Host主机和USB键盘、鼠标。
  • 支持USB键盘鼠标在BIOS界面使用,支持多媒体功能键,支持不同分辨率USB鼠标。
  • 支持各种品牌的USB键盘鼠标、USB无线键盘鼠标、USB转PS2线等。
  • 上位机端和下位机端支持热插拔。
  • 提供发送状态引脚,支持485通讯。
  • 串口支持115200/57600/38400串口通信波特率。
  • 内置晶振和上电复位电路,外围电路简单。
  • 支持5V、3.3V电源电压。
  • 提供LQFP-48无铅封装,兼容RoHS。

发射端方案的优点是:成本比较低,体积比较小,容易DIY焊接(ESP32 C3 是我用过的最容易焊接的ESP32芯片)。能够同时支持2个USB设备,就是说你可以同时使用2个USB手柄,同时转化为无线给主机使用。

接收器使用的是ESP32-S2 ,它带有USB Device ,能够方便的将自身模拟为一个USB手柄。因此,无论 Windows 还是 Linux 只要支持USB 手柄,在操作系统端看起来插入的就是一个USB手柄,完全不会碰到兼容性问题。这里使用淘宝直接购买的Mini ESP32-S2 开发板,体积小,价格便宜。

接下来首先进行电路的设计。

  1. 主控 ESP32 C3 部分。这款主控内置了 USB 下载电路,我们设计一个 USB接口即可工作。此外,复位与下载按钮是必须的,当出现问题无法下载时,这两个按钮随时可以帮助恢复。

2.接下来时CH9350部分,它外部电路非常简单,只需要一个 3.3uf和一个 0.1uf电容即可工作。外部的 LED1和 LED2用来指示USB工作状态,没有有效数据时会亮,有数据传输时会熄灭。其中的USB3 是一个双层USB座子,这样我们可以同时使用两个USB手柄。

3.我们使用 TLV1117将5V 转为3.3V,同时还预留一个假负载,用来避免在使用充电宝供电,输出小于50ma 一段时间后自动关机的问题。

电路比较简单PCB设计也相对简单:

预览如下:

上面就是硬件设计,接下来着手软件的设计。

同样,分成发送端和接收端分别介绍。

发送端的主要工作是:USB 手柄数据的获取,获得数据的发送。

对于USB 手柄数据的获取和之前的并没有多少差别,只需要解析来自串口的数据即可;特别注意代码加入了判断,只有收到和之前数据不同的时候才会发送;

我们使用 ESP-Now 进行数据发送。对于 Arduino 开发来说,这个非常简单。创建 ESPNow 对象,然后指定发送的地址即可:

// 创建esp_now_peer_info_t类型变量存储有关peer方的信息。
esp_now_peer_info_t peerInfo0;
esp_now_peer_info_t peerInfo1;

//  ESP32 接收器 MAC 地址
uint8_t broadcastAddress0[] = {'L', 'A', 'B', 'Z', '-', '0'};
uint8_t broadcastAddress1[] = {'L', 'A', 'B', 'Z', '-', '1'};

之后每次收到改变后的USB手柄数据,可以使用下面的函数进行发送

// Send message via ESP-NOW
                esp_err_t result = esp_now_send(broadcastAddress1, (uint8_t *) &amp;Data[i + 6], 7);

对应的接收端的设计如下:

  1. 程序开始处,设置当前ESP32 的MAC地址,作为接收数据的地址。发送端同时支持2个USB手柄,因此我们通过GPIO 判断设置2个Mac。这样,我们的程序刷到2个ESP32 S2主板上之后,根据外部跳线可以选择不同的 Mac 非常方便。
  if (digitalRead(ADDRESSPIN2) == HIGH) {
    esp_wifi_set_mac(WIFI_IF_STA, &newMACAddress0[0]);
  } else {
    esp_wifi_set_mac(WIFI_IF_STA, &newMACAddress1[0]);
  }
  if (DEBUGMODE) {
    Serial.print("[NEW] ESP32 Board MAC Address:  ");
    Serial.println(WiFi.macAddress());
  }

2.为了实现USB 手柄,我们使用USB 手柄相同的 Report 描述符,这样在操作系统端看起来插入的就是一个 HID 手柄。

    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        HID.addDevice(this, sizeof(report_descriptor));
      }
}

3.接收到的来自ESP NOW 的数据会出现在 OnDataRecv() 这个回调函数中,我们接收之后无需额外处理直接作为USB数据发送给主机即可。

// 创建一个回调函数,当 ESP32 通过 ESP-NOW 接收到数据时将被调用
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  if (DEBUGMODE) {
    // 收到的数字,例如:
    // 128 128 128    128 31 0
    // 发过来的数据直接就是 RAW 格式
    for (uint8_t i = 0; i < len; i++) {
      printf("%02x  ", incomingData[i]);
    }
    printf("\n");
  }

  if (HID.ready()) {
    Device.send((uint8_t *)&incomingData[0]);
  }
}

工作的视频在B站可以看到:

https://www.bilibili.com/video/BV168411r7r3/?share_source=copy_web&vd_source=5ca375392c3dd819bfc37d4672cb6d54

对应的 Arduino 代码在这里下载:

香港保险投诉局案例分析1

最近听说香港有一个“香港保险投诉局(Insurance Complaints Bureau)”,它一家成立于2018年1月16日,前身是香港保险投诉索偿局,是一个独立的、非营利性的保险纠纷调解机构。该机构旨在为保险消费者提供便捷、高效的纠纷解决服务,维护消费者权益,促进保险市场的公平和诚信。

也就是说当投保人申请理赔时遇到拒赔时,且认为理赔结果不合理时,无需先走司法程序,可直接向保险投诉局投诉,他们接到投诉后将以裁决的方式处理。【参考1】

好奇之下,去看看他们的案例【参考2】,第二页:

何谓「表面证据不成立」?

陈先生计划于2021年7月前往上海,并向保险公司投购旅游保险,而保险公司网页上闪烁的宣传字句「隔离现金津贴」吸引了他的注意。
根据中国对2019冠状病毒病疫情实施的检疫政策,陈先生于上海的一家酒店接受了 14 天的集中强制检疫,酒店费用接近12,000人民币。回港后,他向保险公司申索于隔离期间的酒店费用,但他的索偿遭保险公司拒绝。
有关保单的「强制隔离现金津贴」条款订明:「如受保人因疑似感染或确诊患上传染病而于旅程期间或于返回常住地方后七日内被强制隔离,保险公司将就每个完整天数支付现金津贴500港元……」
陈先生的个案被定为表面证据不成立,原因是:
1) 他被强制隔离的原因并非因为疑似感染或确诊患上传染病;及
2) 保险的原则仅就不可预见或预料的事件向个人或实体提供保障。由于内地当时的政策要求
所有到上海的入境旅客必须隔离至少 14 天,因此相关隔离而导致的酒店费用属预期和可
以预见,不符合保险的承保原则。

这就是为什么语文要做阅读理解吧?话说保险条款还真是阅读理解的好材料。

三十多年前,我老家(大庆市)流传着一个故事。本地人习惯将罐车称作“大罐”。

然后有“狡猾”的外地人过来和本地人签合同,内容是以几十万元的价格出售“大罐”给本地。交付的时候,真的是大铁罐,但是合同如此,最终官司打不赢白白让对方占了便宜。

参考:

1.https://baijiahao.baidu.com/s?id=1820155891286124609&wfr=spider&for=pc

2.https://www.icb.org.hk/gb/doc/ICB-Connect-Issue-1.pdf

Step to UEFI (297)编写一个 UEFI Shell Command 的框架

本文根据 https://github.com/apop2/GopRotate 的代码编写,这个在之前的【参考1】和【参考2】中有介绍过。

本文介绍了如何编写一个 UEFI Shell Command 的方法,然后给出了一个基本的框架,有需要的朋友可以方便的扩展定制自己所需的 UEFI Shell。代码在 edk2 202411 下实验成功。

以WinHost 模拟器为例,介绍例子的测试方法:

1.解压到 \ShellPkg\Library 下面

2.修改 \EmulatorPkg\EmulatorPkg.dsc文件

BcfgCommandLib|ShellPkg/Library/UefiShellBcfgCommandLib/UefiShellBcfgCommandLib.inf
      IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
##LABZDEBUG_Start
	  NULL|ShellPkg/Library/MyShellCommandLib/MyShellCommandLib.inf
##LABZDEBUG_End	

    &lt;PcdsFixedAtBuild>
      gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
      gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
      gEfiMdePkgTokenSpaceGuid.PcdUefiLibMaxPrintBufferSize|8000

3.重新编译,使用 build -a X64 即可

运行 WinHost测试如下:

这里定了一个 mycmd 命令,然后可以接受一个参数。MyCommand.c 中的MyShellCommand() 函数负责实际执行 ShellPkg\Library\MyShellCommandLib\MyShellCommandLib.c 中的MyShellCommandLibConstructor() 函数负责向 Shell 注册 mycmd 命令,同时注册一个帮助文本,通过 STR_GET_MYCMD_HELP 定义。

EFI_STATUS EFIAPI MyShellCommandLibConstructor(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
    gMyShellCommandHiiHandle = HiiAddPackages (&gMyShellCommandGuid, ImageHandle, MyShellCommandLibStrings, NULL);
    if(gMyShellCommandHiiHandle != NULL)
    {
        ShellCommandRegisterCommandName(L"mycmd", MyShellCommand, MyShellCommandGetFileName, 0, L"", FALSE, gMyShellCommandHiiHandle, STRING_TOKEN(STR_GET_MYCMD_HELP));
    }
    return EFI_SUCCESS;
}

有兴趣的朋友可以使用这个框架自行扩展 UEFI Shell。

参考:

  1. https://www.lab-z.com/stu90shc/
  2. http://www.lab-z.com/stu88/