Step to UEFI (143)Windows 下BootOrder研究

之前介绍了 Shell 下 BootOrder 的一些事情,这次介绍一下 Windows中如何取得和更改这个设定。Windows中相关API是SetFirmwareEnvironmentVariable 和GetFirmwareEnvironmentVariable (还有~Ex版)【参考1】【参考2】。需要注意,只有在 UEFI Based 系统上才可以使用上述API。
上面两个 API 的原型:

DWORD WINAPI GetFirmwareEnvironmentVariable( //取得Variable
_In_ LPCTSTR lpName, //变量名称
_In_ LPCTSTR lpGuid, //变量 GUID
_Out_ PVOID pBuffer, //读取结果放在pBuffer指向的内存中
_In_ DWORD nSize //给出pBuffer内存大小
);

返回值如果为0 ,表示失败,可以用GetLastError获取原因;如果返回值不为0,则是成功,该值应该是成功写入的字节数。

BOOL WINAPI SetFirmwareEnvironmentVariable( //设置 Variable
_In_ LPCTSTR lpName, //变量名称
_In_ LPCTSTR lpGuid, //变量 GUID
_In_ PVOID pBuffer, //要设置的内容放在pBuffer指向的内存中
_In_ DWORD nSize //给出pBuffer 指向内存的大小
);

返回值如果为0 ,表示失败,可以用GetLastError获取原因;如果返回值不为0,则是成功,该值应该是成功写入的字节数。

从上面的原型可以看出来,并没有给出Variable 大小的方法。实验如果给一个很小的Buffer会收到 7A 错误。因此在调用之前最好开一个足够大的空间以便使用(相比Windows可以分配的内存,Variable 小的可怜)。还有特别需要注意的地方是:使用这个 API 除了使用 管理员权限运行,还需要提升权限才可以。具体的代码来自【参考3】。
下面的代码首先用GetFirmwareEnvironmentVariable取得BootOrder的Variable,然后修改顺序,再使用SetFirmwareEnvironmentVariable把修改后的Variable写入,最后再读取显示一次:

// getfwenv.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"

#define VariableGuidStr      "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"
#define BootOrderStr         "BootOrder"

DWORD    dwRet = 0;

BOOL adjusttoken()
{
	HANDLE htoken;


	if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &htoken))
	{
		size_t           s = sizeof(TOKEN_PRIVILEGES) + 2 * sizeof(LUID_AND_ATTRIBUTES);
		TOKEN_PRIVILEGES *p = (PTOKEN_PRIVILEGES)malloc(s);


		if (!LookupPrivilegeValue(NULL, SE_SYSTEM_ENVIRONMENT_NAME, &(p->Privileges[0].Luid)) ||
			!LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &(p->Privileges[1].Luid)) ||
			!LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &(p->Privileges[2].Luid)))
		{
			printf("failed to LookupPrivilegeValue error code : %d \r\n", GetLastError());
			free(p);
			return FALSE;
		}
		p->PrivilegeCount = 3;


		for (int i = 0; i < 3; ++i)
		{
			p->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED;
		}

		 
		if (!AdjustTokenPrivileges(htoken, FALSE, p, (DWORD) s, NULL, NULL) || GetLastError() != ERROR_SUCCESS)
		{
			printf("AdjustTokenPrivileges failed! error code : %d \r\n", GetLastError());
			free(p);
			return FALSE;
		}
		//do something here...
		free(p);
	}
	else
	{
		printf("Open process token failed! error code : %d \r\n", GetLastError());
		return FALSE;
	}


	return TRUE;
}

int main()
{
	byte Buffer[2000];
	byte *pBuffer;
	DWORD  iBufferSize=sizeof(Buffer);
	DWORD dwRet;

	adjusttoken();

	pBuffer = Buffer;

	//Get BootOrder
	dwRet = GetFirmwareEnvironmentVariable(
		_T(BootOrderStr),
		_T(VariableGuidStr),
		pBuffer,
		iBufferSize);
	printf("GetFirmwareEnvironmentVariable return value:%x\n", dwRet);
	for (DWORD i = 0; i < dwRet;i++)
		printf("%X ",pBuffer[i]);
	printf("\n");

	//Set boot from shell
	pBuffer[0] = 0;	pBuffer[2] = 4;

	//Set new Bootorder
	dwRet = SetFirmwareEnvironmentVariable(
		_T(BootOrderStr),
		_T(VariableGuidStr),
		pBuffer,
		dwRet);
	printf("SetFirmwareEnvironmentVariable return value:%x\n", dwRet);

	//Check Bootorder again
	dwRet = GetFirmwareEnvironmentVariable(
		_T(BootOrderStr),
		_T(VariableGuidStr),
		pBuffer,
		iBufferSize);
	printf("GetFirmwareEnvironmentVariable again, return value:%x\n", dwRet);

	for (DWORD i = 0; i < dwRet; i++)
		printf("%X ", pBuffer[i]);

	getchar();

    return 0;
}

 

最终运行结果:

winorder

在我的KBL-R HDK上重启之后就会直接进入 Shell (原因可以在前一篇看到)

编译好的X64 Application和Sourcecode
getfwenv

从实验上来看,所有的变量都是可以读取到,但是不知道为什么很多变量在 Windows下无法写(写保护错误)。如果能够做到写入,那么就可以自动的改变Setup的设定。另外,我无法判断这个写保护错误是Windows本身造成的还是BIOS造成的。如果未来有机会会继续研究下去,有兴趣的朋友也可以在下面给我留言说出你的想法。

参考:
1. https://msdn.microsoft.com/en-us/library/windows/desktop/ms724934(v=vs.85).aspx
2. https://msdn.microsoft.com/en-us/library/windows/desktop/ms724325(v=vs.85).aspx

Step to UEFI (142)UEFI 和 Arduino 的 HID 通讯

如果想让 Arduino 和 UEFI 进行交互,可以使用USB串口驱动,比如:【参考1】提供了一个FTDI的UEFI驱动,如果想用在 Arduino 上需要一些修改。此外,可行的方案就是通过USB HID 直接和 Arduino Leonardo进行通讯(Arduino Uno 也是可以的,但是需要修改 16U2的Firmware,比较麻烦和折腾)。本文就介绍一下具体的实现。

首先说一下硬件部分,在一个 Proto Shied上连接了一个串口转USB的小卡,将 TX/RX/GND 三根对应的接在一起就能正常工作了。

ehid1

软件方面,Arduino代码如下使用了NicoHood 的 HID库。代码就是不断检查 HID是否收到消息,如果有,那么就从Serial1 发送出去。

/*

  Copyright (c) 2014-2015 NicoHood

  See the readme for credit to other people.



  Advanced RawHID example



  Shows how to send bytes via RawHID.

  Press a button to send some example values.



  Every received data is mirrored to the host via Serial.



  See HID Project documentation for more information.

  https://github.com/NicoHood/HID/wiki/RawHID-API

*/



#include "HID-Project.h"





// Buffer to hold RawHID data.

// If host tries to send more data than this,

// it will respond with an error.

// If the data is not read until the host sends the next data

// it will also respond with an error and the data will be lost.

uint8_t rawhidData[64];



void setup() {



  Serial1.begin(115200);



  // Set the RawHID OUT report array.

  // Feature reports are also (parallel) possible, see the other example for this.

  RawHID.begin(rawhidData, sizeof(rawhidData));



}



void loop() {



  // Check if there is new data from the RawHID device

  auto bytesAvailable = RawHID.available();

  int c;

  if (bytesAvailable)

  {

    // Mirror data via Serial

    while (bytesAvailable--) {

      c=(RawHID.read()&0xFF);

      if (c / 16 ==0) {Serial1.print("0");}

      Serial1.print(c,HEX);

      Serial1.print("  ");

    }

  }

}

 

 

UEFI  Shell Application的原理就是使用 USBIO枚举系统中的全部USB 设备,找到VID/PID是Arduino Leonardo 的设备,再检测InterfaceClass是否为03 (HID),如果是那么就打开,用UsbSetReportRequest 发送一段随机数过去。完整代码如下:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UsbIo.h>

#include <stdlib.h>

extern EFI_BOOT_SERVICES         *gBS;

EFI_GUID gEfiUsbIoProtocolGuid = 
 { 0x2B2F68D6, 0x0CD2, 0x44CF, { 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};

//C:\UDK2015\MdePkg\Library\UefiUsbLib\Hid.c
/**
  Set the report descriptor of the specified USB HID interface.

  Submit a USB set HID report request for the USB device specified by UsbIo,
  Interface, ReportId, and ReportType, and set the report descriptor using the
  buffer specified by ReportLength and Report.
  If UsbIo is NULL, then ASSERT().
  If Report is NULL, then ASSERT().

  @param  UsbIo         A pointer to the USB I/O Protocol instance for the specific USB target.
  @param  Interface     The index of the report interface on the USB target.
  @param  ReportId      The identifier of the report to retrieve.
  @param  ReportType    The type of report to retrieve.
  @param  ReportLength  The size, in bytes, of Report.
  @param  Report        A pointer to the report descriptor buffer to set.

  @retval  EFI_SUCCESS       The request executed successfully.
  @retval  EFI_TIMEOUT       A timeout occurred executing the request.
  @retval  EFI_DEVICE_ERROR  The request failed due to a device error.

**/
EFI_STATUS
EFIAPI
UsbSetReportRequest (
  IN EFI_USB_IO_PROTOCOL     *UsbIo,
  IN UINT8                   Interface,
  IN UINT8                   ReportId,
  IN UINT8                   ReportType,
  IN UINT16                  ReportLen,
  IN UINT8                   *Report
  )
{
  UINT32                  Status;
  EFI_STATUS              Result;
  EFI_USB_DEVICE_REQUEST  Request;

  //
  // Fill Device request packet
  //
  Request.RequestType = USB_HID_CLASS_SET_REQ_TYPE;
  Request.Request = EFI_USB_SET_REPORT_REQUEST;
  Request.Value   = (UINT16) ((ReportType << 8) | ReportId);
  Request.Index   = Interface;
  Request.Length  = ReportLen;

  Result = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &Request,
                    EfiUsbDataOut,
                    3000, //PcdGet32 (PcdUsbTransferTimeoutValue),
                    Report,
                    ReportLen,
                    &Status
                    );

  return Result;
}

UINTN GetUSB()
{
  EFI_STATUS    Status;
  UINTN         HandleIndex, HandleCount;
  EFI_HANDLE    *DevicePathHandleBuffer = NULL;
  EFI_USB_IO_PROTOCOL          *USBIO;
  EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;
  EFI_USB_INTERFACE_DESCRIPTOR  IfDesc;
  UINT8                         arr[64];
  UINT8                         i;

  
  //Get all the Handles that have UsbIO Protocol
  Status = gBS->LocateHandleBuffer(
                  ByProtocol,
                  &gEfiUsbIoProtocolGuid,
                  NULL,
                  &HandleCount,
                  &DevicePathHandleBuffer);
  if (EFI_ERROR(Status)) 
  {
    Print(L"ERROR : Get USBIO count fail.\n");
    return 0;
  }   

  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) 
  { 
    Status = gBS->HandleProtocol(
                      DevicePathHandleBuffer[HandleIndex],
                      &gEfiUsbIoProtocolGuid,
                      (VOID**)&USBIO);
    if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Open USBIO fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

    //Get USB Device Descriptor
    Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);     
    if (EFI_ERROR(Status))
     {
        Print(L"ERROR : Usb Get Device Descriptor fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return EFI_SUCCESS;
     }
    
    //Find the device which VID and PID is Arduino Leonrado     
    if ((0x2341==DeviceDescriptor.IdVendor) && (0x8036==DeviceDescriptor.IdProduct))
        {
                //Show the PID and VID
                Print(L"Found a Leonrado Device VendorID = %04X, ProductID = %04X\n", 
                        DeviceDescriptor.IdVendor, 
                        DeviceDescriptor.IdProduct);       
                //
                // Get Interface Descriptor
                //
                Status = USBIO->UsbGetInterfaceDescriptor (USBIO, &IfDesc);
                if (EFI_ERROR (Status)) 
                {
                        Print(L"ERROR : Usb Get Interface Descriptor fail.\n");
                        return EFI_SUCCESS;
                }
                
                //Check the Interface Class for HID
                if (0x03 == IfDesc.InterfaceClass) 
                {
                        Print(L"Found HID device, send the data!\n");
  
                        for (i=0;i<64;i++) {
                           arr[i]=(UINT8) rand();
                           Print(L"%2X  ",arr[i]);
                        }
                        Print(L"\n");
                        Status=UsbSetReportRequest (
                                USBIO,
                                IfDesc.InterfaceNumber,
                                0,  //Report ID
                                HID_OUTPUT_REPORT,
                                sizeof(arr),
                                arr);    
                        if (EFI_ERROR (Status)) 
                        {
                                Print(L"Error=[%r]\n",Status);
                                return EFI_SUCCESS;
                        }        
                }
        }        
  }
  gBS->FreePool(DevicePathHandleBuffer);       
  return HandleCount;
}


int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  GetUSB();
  return EFI_SUCCESS;
}

 

运行结果,Shell 下显示:

hidr

PC端串口接收到的数据如下:

ehid3

完整的代码和 X64 EFI 文件下载:

HIDTest

参考:

  1. http://www.lab-z.com/stufdti/ FTDI 串口驱动

AP3216C 模块

AP3216C 模块的核心就是这个芯片本身。这颗芯片集成了光强传感器(ALS:Ambient Light Sensor),接近传感器(PS: Proximity Sensor),还有一个红外LED(IR LED)。这个芯片设计的用途是给手机之类的使用,比如:返回当前环境光强以便调整屏幕亮度;用户接听电话时,将手机放置在耳边后,自动关闭屏幕避免用户误触碰。

ap32

可能是因为模块接线非常简单,我在网上找不到模块的电路图,只能用芯片的DataSheet对照进行查看。
从上到下分别是 :
VLED IR LED的供电,IR LED 电流最高为 20ma。使用 3.3v给模块和IR LED 同时供电时,在IR LED 上串联了一个200欧姆的电阻,这样保证电流不会超过20ma。
GND 地
VCC 模块供电,特别注意:最高 3.6V,对于 Arduino 来说,只能选择 3.3V供电输出
SCL I2C 的 CLOCK,对应 A5
SDA I2C 的 DATA,对应 A4
INT 中断输出,可以通知 Arduino有数据。对于轮询,无需使用。

ap321

参考【参考1】, 可以使用的代码如下:

// Interfacing the AP3216 light / proximity sensor with Arduino UNO

// By RoboRemo
// www.roboremo.com

// Big thanks to ICStation for providing the AP3216 sensor
// http://www.icstation.com/ap3216-ambient-light-sensorals-proximity-sensorps-p-7958.html

// Command examples:
// "write 0x00 0x01\n" - will write value 0x01 to the register 0x00
// "read 0x0C\n" - will read the value from register 0x0C
// "als start\n" - will start streaming the value from the ALS (ambient light sensor)
// "ps start\n" - will start streaming the value from the PS (proximity sensor)
// "stop\n" - will stop streaming the ALS / PS data.

// Commands can be sent using Serial Monitor / Terminal,
// Or using the RoboRemo app from Google Play.

// RoboRemo app can also display a nice plot of the ALS / PS data,
// and also log to a file on the sdcard of the phone.


// Hardware wiring:
// Arduino     AP3216
//             VLED --,
// GND ------- GND   |R| 240 Ohm
// 3.3V ------ VCC ---'
// A5 -------- SCL
// A4 -------- SDA
            


long baud = 115200;

#include <Wire.h>


char cmd[100];
int cmdIndex;

bool als_on = false;
bool ps_on = false;



boolean cmdStartsWith(const char *st) { // checks if cmd starts with st
  for(int i=0; ; i++) {
    if(st[i]==0) return true;
    if(cmd[i]==0) return false;
    if(cmd[i]!=st[i]) return false;;
  }
  return false;
}



int hexCharToInt(char c) {
  if(c>='a') return (c-'a')+10;
  if(c>='A') return (c-'A')+10;
  return c-'0';
}

String hexByteToString(int val) {
  char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'A', 'B', 'C', 'D', 'E', 'F'};

  char a = digits[(val/16) %16];
  char b = digits[val%16];
  
  return (String) "" + a + b;
}



void alsStart() {
  AP3216_write(0x00, 0x01);
  als_on = true;
}


void alsPsStop() {
  als_on = false;
  ps_on = false;
  AP3216_write(0x00, 0x00);
}


void psStart() {
  AP3216_write(0x00, 0x02);
  ps_on = true;
}




void AP3216_write(int regAddress, int value) {
  Wire.beginTransmission(0x1E); // I2C Address of AP3216 sensor is 0x1E
  Wire.write(regAddress);
  Wire.write(value);
  Wire.endTransmission(); 
}

int AP3216_read(int regAddress) {
  Wire.beginTransmission(0x1E); // I2C Address of AP3216 sensor is 0x1E
  Wire.write(regAddress);
  Wire.endTransmission();
  Wire.requestFrom(0x1E, 1, true);
  return Wire.read() & 0xFF;
}


void exeCmd() {

  if( cmdStartsWith("read 0x") ) {  // example: read 0x1A
    int a = hexCharToInt(cmd[7]); // '1' -> 1
    int b = hexCharToInt(cmd[8]); // 'A' -> 10
    
    int regAddress = (a*16)+b; // 0x1A = 26

    int regValue = AP3216_read(regAddress);
    
    Serial.print( (String)"reg_0x");
    Serial.print( hexByteToString(regAddress) );
    Serial.print(" = ");
    Serial.print( hexByteToString(regValue) );
    Serial.print("\n");
  }


  if( cmdStartsWith("write 0x") ) {  // example: write 0x1A 0x55
    int a = hexCharToInt(cmd[8]); // '1' -> 1
    int b = hexCharToInt(cmd[9]); // 'A' -> 10
    int regAddress = (a*16)+b; // 0x1A = 26

    a = hexCharToInt(cmd[13]);
    b = hexCharToInt(cmd[14]);
    int regValue = (a*16)+b;
    
    AP3216_write(regAddress, regValue); 

    Serial.print( (String)"reg_0x");
    Serial.print( hexByteToString(regAddress) );
    Serial.print(" <- ");
    Serial.print( hexByteToString(regValue) );
    Serial.print("\n");
  }

  if( cmdStartsWith("als start") ) {
    alsStart();
  }

  if( cmdStartsWith("stop") ) {
    alsPsStop();
  }


  if( cmdStartsWith("ps start") ) {
    psStart();
  }

  
}


void setup() {
  Wire.begin();
  Serial.begin(baud);
  cmdIndex = 0;
}

void loop() {

  while( Serial.available() ) {
    char c = Serial.read();

    if(c=='\n' || c=='\r') {
      cmd[cmdIndex] = 0; // end cmd string with 0
      exeCmd();  // execute the command
      cmdIndex = 0; // reset the cmdIndex
    } else {      
      cmd[cmdIndex] = c; // append c to the cmd string
      if(cmdIndex<99) cmdIndex++;
    } 
  }

  if(als_on) {
    int a = AP3216_read(0x0D); // ALS Data HIGH Byte
    int b = AP3216_read(0x0C); // ALS Data LOW Byte

    long alsValue = a;
    alsValue = alsValue << 8;
    alsValue = alsValue + b;
    
    Serial.print("als ");
    Serial.print(alsValue);
    Serial.print("\n");
    delay(100);
  }


  if(ps_on) {
    int a = AP3216_read(0x0F) & 0b00111111; // PS Data HIGH 6 bits
    int b = AP3216_read(0x0E) & 0b00001111; // PS Data LOW 4 bits
    long psValue = (a << 4) + b;
    Serial.print("ps ");
    Serial.print(psValue);
    Serial.print("\n");
    delay(13);
  }
   
}

 

使用方法:
串口输入 als start 获取当前的光强
串口输入 ps start获得当前接近传感器的数值
串口输入 stop 停止输出

ap322

目前这个模块没有成熟的库供使用,上述代码只是实现一个大概的功能,如果应用在产品上,还需要根据需求对照DataSheet进行详细的调试。
参考:
1. http://www.roboremo.com/reading-ap3216-with-arduino.html

Step to UEFI (141)Windows 重启变关机的实现

最近看到一篇文章【参考1】,提到了修改 ACPI可以做到Windows重启变成关机【参考1】,从原理上说,Windows实现重启的方法是从 ACPI Table的FADT Table中Reset_Reg 和RESET_VALUE中读取到做重启的Port和Value。当需要重启的时候,将 Value写入Port即可。这个事情很多年前我碰到过问题,那还是XP的时代,当时遇到的问题是无法正常 Restart系统,后来研究发现当时的 ACPI Table 写的是向EC 的Port写Command的方式进行重启(同一个Chipset上的BIOS我们尽量会重用),但是恰巧EC没有实现这个功能。最后修改为正常的向 0xCF9 写入 06 即可解决)。类似的,Windows关机也是从 ACPI 中取得Port然后写入特定值。
具体的Table 定义在 ACPI Spec 中:

1. 我们需要确定 RSD PTR
r2s1

2. 找到 Extended System Description Table (Signature 是 “XSDT”)
r2s2

3. 在 XSDT 中查找到 Fixed ACPI Description Table (Signature 是 “FADT”),其中的 RESET_REG金额 RESET_VALUE就是我们需要的
r2s3

4. 将 FADT 中的 RESET_REG 修改为Pm1aCnBlk+1,将RESET_VALUE修改为0x3C(例如,我们读取到的Pm1aCnBlk == 1804h,对这个Port写入 3C00h 能够实现关机操作,那么对 1805h写入 3Ch同样能够实现关机)。特别注意的是,RESET_REG 是一个结构体,其中的 RegisterBitWidth 需要设置为 18 (默认为8)
r2s4

5. 重新计算 FADT 的 Checksum 填写回去。

完整的代码:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include "acpi.h"
#include "acpi61.h"

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_RUNTIME_SERVICES      *gRT;
extern EFI_SYSTEM_TABLE          *gST;


EFI_GUID        gEfiAcpi20TableGuid =   { 0x8868E871, 0xE4F1, 0x11D3, 
                        { 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};
//              
//Calcaulate a checksum
//              
void SetFADTChecksum(EFI_ACPI_6_1_FIXED_ACPI_DESCRIPTION_TABLE       *pFADT) {
        UINT8   c=0;
        UINT8   *p;
        UINT32  i;
        
        p=(UINT8*)pFADT;
        pFADT->Header.Checksum=0;
        for (i=0;i<pFADT->Header.Length;i++)    {
                c=c + *p;
                p++;
        }
        pFADT->Header.Checksum=0-c;
}

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        EFI_STATUS      Status;
        EFI_ACPI_DESCRIPTION_HEADER                     *XSDT;
        EFI_ACPI_6_1_ROOT_SYSTEM_DESCRIPTION_POINTER    *RSDP;
        UINT8           *p;
        UINTN           Index;
        UINT64          *Entry;
        EFI_ACPI_6_1_FIXED_ACPI_DESCRIPTION_TABLE       *pFADT;
        
        //1. Find RSDP
        Status=EfiGetSystemConfigurationTable(&gEfiAcpi20TableGuid,(VOID**)&RSDP);
        if(EFI_ERROR(Status)) {
                Print(L"Can't find Acpi Table\n");
                return 0;
        }
        
        //2. Find XSDT
        Print(L"RSDP address [%X]\n",RSDP);
        Print(L"XSDT address [%X]\n",RSDP->XsdtAddress);
        XSDT=(EFI_ACPI_DESCRIPTION_HEADER*)RSDP->XsdtAddress;
        Print(L"XSDT information\n");
        p=(UINT8*)XSDT;
        
        //Show some DSDT information
        Print(L" Signature [%c%c%c%c]\n",*p,*(p+1),*(p+2),*(p+3));
        
        //3.Find entries
        Entry=(UINT64*)&XSDT[1];
        Print(L" Entry 0 @[0x%x]\n",Entry);
        for (Index=0;Index<(XSDT->Length-sizeof(EFI_ACPI_DESCRIPTION_HEADER))/8;Index++) {
           //Print(L" Entry [0x%x]",Index);
           p=(UINT8*)(*Entry);
           //You can show every signature here
           //Print(L" [%x][%c%c%c%c]\n",*Entry,*p,*(p+1),*(p+2),*(p+3));
           if ((*p=='F')&&(*(p+1)=='A')&&(*(p+2)=='C')&&(*(p+3)=='P')) {
                   pFADT=(EFI_ACPI_6_1_FIXED_ACPI_DESCRIPTION_TABLE*) p;
                   Print(L"  Found FADT @[0x%X]\n",*Entry);
                   Print(L"      ResetReg[0x%X]\n",pFADT->ResetReg.Address);
                   Print(L"    ResetValue[0x%X]\n",pFADT->ResetValue);
                   Print(L"   XPm1aCntBlk[0x%X]\n",pFADT->XPm1aCntBlk.Address);
                   Print(L" Changing table value\n");
                   pFADT->ResetReg.RegisterBitWidth=16;
                   pFADT->ResetReg.Address=pFADT->XPm1aCntBlk.Address+1;
                   pFADT->ResetValue=0x3C;
                   Print(L"      ResetReg[0x%X]\n",pFADT->ResetReg.Address);
                   Print(L"    ResetValue[0x%X]\n",pFADT->ResetValue);

                   SetFADTChecksum(pFADT);
           }
           Entry++;
        }
        return 0;
}

 

运行结果:
r2s5
执行这个 Application 之后,再Exit进入 Windows(不可以重启进入 Windows),用 RW 检查 Acpi Table 结果如下:
r2s6
最后,我们从菜单上选择 restart,会发现系统没有 reset 而是关机了。这样就实现了重启变关机。

完整的代码下载:
Reset2Shutdown

X64 Shell Application 下载:
r2s

参考:
1. http://blog.csdn.net/miss_lazygoat/article/details/48161645 ACPI table遍历并实现重启变关机

介绍一个 USB 分析软件 Usblyzer

之前我的很多USB分析都是直接使用USB逻辑分析仪完成的,优点是:可靠性高,全部信息都可以抓取到;缺点是价格昂贵(20万人民币,个人基本上不可能购买。国产的我看到过8千的,但是貌似只有USB Full Speed),架设配置复杂。在Windows上还有一些纯软件的抓包分析工具,比如:BusHound【参考1】,可以抓取USB、SCSI & ATAPI、IDE & SATA、FireWire、1394等等总线的底层数据。从原理上说,是通过安装一个过滤驱动来实现的。

usb1

最近发现了一款专门用来抓取 USB 数据的工具软件:USBlyzer。因为他是专门设计用来抓取USB数据的,所以和 Bus Hound 相比,对USB数据能够进行详细的解析。
这个软件可以免费试用30天,对于一般的用户来说已经足够。

main

软件界面如下:

usb2

使用非常简单,首先在 Device Tree 上选中需要监视的USB设备

usb3

然后使用 Start Capture 即可开始抓包,使用Stop Capture 停止抓包后,可以在Capture List 中看到抓到的包。

usb4

下面是一个实际操作的例子,比如,我要分析微软 InteilMouse optical1.1A 这款鼠标的数据

usb5

首先选中设备:

usb6

在 USB Properties可以看到设备属性,对于标准支持的属性都有详细分析。因为鼠标是标准的HID协议,所以在协议分析中除了常见的描述符还能看到HID的描述符。

usb7

usb8

点击 Start Capture开始抓包,移动鼠标会有很多数据,使用 Stop Capture 后停止

usb9

点击抓到的数据,可以在 Data Analysis 中直接看到解析后的数据,这个解析是根据 HID描述符进行的。

usba

从可以看到通过这个软件可以方便进行鼠标数据的分析,并且能够看到驱动级别的消息,对于编写驱动会有莫大的帮助。

参考:
1. http://perisoft.net/bushound/index.htm
2. http://www.usblyzer.com

Step to UEFI (140)ProtocolsPerHandle 的例子

Boot Services 提供了 ProtocolsPerHandle 函数,用它可以取得一个 Handle上面的 Protocols。这里编写程序用来验证一下。
函数原型在 UEFI Specification 中可以看到:

pph

编写程序,取得当前 Application 上面的 Protocol:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        EFI_GUID     **ProtocolBuffer;
        UINTN        ProtocolCount;
        EFI_STATUS   Status;
        UINTN        i;
        
        Status = gBS->ProtocolsPerHandle(ImageHandle, &ProtocolBuffer, &ProtocolCount);
        if (EFI_ERROR (Status)) {
                //
                // TheHandle is not valid, so do not add to handle list
                //
                Print(L"Can't get Protcols on ImageHandle\n");
                return 0;
        }

        Print(L"Protocol list\n");        
        for (i=0;i<ProtocolCount;i++)   {
                Print(L"%g \n",ProtocolBuffer[i]);
        }
        FreePool (ProtocolBuffer);
        

        return 0;
}

 

运行结果如下(NT32虚拟机中运行结果):

pph2

在 DEC 文件中查找这些 GUID对应的 Protocol,如下:

752F3136-4E16-4FDC-A22A-E5F46812F4CA gEfiShellParametersProtocolGuid
BC62157E-3E33-4FEC-9920-2D3B36D750DF gEfiLoadedImageDevicePathProtocolGuid
5B1B31A1-9562-11D2-8E3F-00A0C969723B gEfiLoadedImageProtocolGuid

源代码和EFI下载:
sd

=============================================================
2018年2月24日 krishnaLee(sssky307)做了一番深入的研究,他发现一个可以将 UUID 转化为对应名称的简单方法。例程如下:

#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle

#include <Protocol/LoadedImage.h>      //EFI_LOADED_IMAGE_PROTOCOL
#include <Protocol/DevicePath.h>       //EFI_DEVICE_PATH_PROTOCOL

#include <Library/DevicePathLib.h>     //link
#include <Library/ShellLib.h>
#include <Library/ShellCommandLib.h> 
#include <Library/HandleParsingLib.h> 

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable)
{

  EFI_GUID **ProtocolGuidArray;
  UINTN ArrayCount;
  CHAR16 *str;

  Print(L"1\n");
  EFI_STATUS status = gBS->ProtocolsPerHandle(gImageHandle,&ProtocolGuidArray,&ArrayCount);

  if (status == EFI_SUCCESS)
  {
    for (UINTN i = 0; i < ArrayCount; i++)
    {
      str = GetStringNameFromGuid(ProtocolGuidArray[i], 0);
      Print(L"%s,%g\n", str,ProtocolGuidArray[i]);
      if (str)
      {
        gBS->FreePool(str);
        str=0;
      }
    }

    if (ProtocolGuidArray)
    {
      gBS->FreePool(ProtocolGuidArray);
      ProtocolGuidArray=0;
    }
  }

  EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
    //open the Loaded image protocol,which is binded on the imageHandle,to get the device handle.
    status = gBS->OpenProtocol(
      gImageHandle,
      &gEfiLoadedImageProtocolGuid,
      (VOID **)&LoadedImage,
      gImageHandle,
      NULL,
      EFI_OPEN_PROTOCOL_GET_PROTOCOL);

  if(status == EFI_SUCCESS)
  {
    Print(L"2\n");
   status = gBS->ProtocolsPerHandle(LoadedImage->DeviceHandle,&ProtocolGuidArray,&ArrayCount);
    
      if (status == EFI_SUCCESS)
      {
        for (UINTN i = 0; i < ArrayCount; i++)
        {
          str = GetStringNameFromGuid(ProtocolGuidArray[i], 0);
          Print(L"%s,%g\n", str,ProtocolGuidArray[i]);
          if (str)
          {
            gBS->FreePool(str);
            str=0;
          }
        }
    
        if (ProtocolGuidArray)
        {
          gBS->FreePool(ProtocolGuidArray);
          ProtocolGuidArray=0;
        }
      }

      gBS->CloseProtocol(
        gImageHandle,
        &gEfiLoadedImageProtocolGuid,
        gImageHandle,
        NULL);
  }


  return EFI_SUCCESS;
}

 

在 NT32 下运行的结果如下:

dpe

完整的代码下载:

mytesthandle

特别提醒,如果在 AppPkg 中编译,需要在AppPkg.dsc的 [LibraryClasses] 中加入下面的语句:

HandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf
ShellCommandLib|ShellPkg/Library/UefiShellCommandLib/UefiShellCommandLib.inf

再次感谢 krishnaLee(sssky307)

Shell MSR 工具

最近需要在 Shell 下读取 MSR 设置,然后发现没有顺手的工具,于是写了一个。
最主要的工作是AsmReadMsr64 和AsmWriteMsr64 函数来完成的,所以整体上并不复杂,代码如下:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>

extern EFI_BOOT_SERVICES         *gBS;

BOOLEAN	WriteOpt=FALSE;
BOOLEAN	ReadOpt=FALSE;

//Copied from UDK2017\UefiCpuPkg\Include\Register\Msr\SandyBridgeMsr.h
/**
  Package. PP0 Balance Policy (R/W) See Section 14.9.4, "PP0/PP1 RAPL
  Domains.".

  @param  ECX  MSR_SANDY_BRIDGE_PP0_POLICY (0x0000063A)
  @param  EAX  Lower 32-bits of MSR value.
  @param  EDX  Upper 32-bits of MSR value.

  <b>Example usage</b>
  @code
  UINT64  Msr;

  Msr = AsmReadMsr64 (MSR_SANDY_BRIDGE_PP0_POLICY);
  AsmWriteMsr64 (MSR_SANDY_BRIDGE_PP0_POLICY, Msr);
  @endcode
  @note MSR_SANDY_BRIDGE_PP0_POLICY is defined as MSR_PP0_POLICY in SDM.
**/
#define MSR_SANDY_BRIDGE_PP0_POLICY              0x0000063A

void PrintUsage() {
  Print(L"******************************************\n");
  Print(L"*  MSR Read & Write utility for KBL      *\n");
  Print(L"*               Powered by www.lab-z.com *\n");
  Print(L"*               11/14/2017 Version 0.5   *\n");
  Print(L"*  Usage:                                *\n");
  Print(L"*  Ex1:read MSR                          *\n");
  Print(L"*       msrtest -r 63A                   *\n");
  Print(L"*  Ex2:write MSR                         *\n");
  Print(L"*       msrtest -w 63A 11                *\n");
  Print(L"******************************************\n");
}

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        UINT64          MsrValue;
        UINT32          MsrAddr;
        
        if (Argc<2) {
                PrintUsage();
                return 0;
        }
        
        if ((StrStr(Argv[1],L"-w")!=NULL) ||
           (StrStr(Argv[1],L"-W")!=NULL)) {
                WriteOpt=TRUE;
                if (Argc!=4) {
                        PrintUsage();
                        return 0;
                }
                MsrAddr=StrHexToUintn(Argv[2]) & 0xFFFFFFFF;
                MsrValue=StrHexToUint64(Argv[3]);
                AsmWriteMsr64(MsrAddr,MsrValue);   
                Print(L"Write MSR[%X] as %lX\n",MsrAddr,MsrValue);
        }
        
        if ((StrStr(Argv[1],L"-r")!=NULL) ||
            (StrStr(Argv[1],L"-R")!=NULL)) {
                ReadOpt=TRUE;
                if (Argc!=3) {
                        PrintUsage();
                        return 0;
                }
                MsrAddr=StrHexToUintn(Argv[2]) & 0xFFFFFFFF;                
                MsrValue=AsmReadMsr64(MsrAddr);   
                Print(L"MSR[%X]=%lX\n",MsrAddr,MsrValue);
        }
        
  return 0;
}

 

在KBL-R HDK 上运行结果如下:

msr

完整代码下载:

MSRTestSRC

编译好的 X64 EFI 代码:

msrtest

Step to UEFI (139)关于Image DevicePath 的小实验

最近做了一个关于 Image 的 Device Path 的实验,比较简单,分享一下。
首先是根据当前 Application 的 ImageHandle 取得对应的 Handle。使用 HandleProtcol 直接打开加载在Application 上面的 EFI_Loaded_Image_Protocol。在这个 Protocol上有 FilePath,就是当前 Image 的 Device Path Protocol ,具体分析可以在【参考1】看到。
接下来,要把取得的 FilePath转化为人类易读的字符串。使用 Device_Path_To_Text_Protocol,具体用法可以在【参考2】找到。
最后,我们看一下这个 Device Path 的Raw Data,先是输出 Type、SubType和Length。从结果中看到属于 File Path Media Path,对照 Specification进一步分析,就是说在 Length 后面跟着一个CHAT16 字符串。
dp2
完整代码如下:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/DevicePath.h> //EFI_DEVICE_PATH_PROTOCOL
#include <Protocol/DevicePathToText.h> //EFI_DEVICE_PATH_TO_TEXT_PROTOCOL

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        EFI_STATUS               Status;
        EFI_LOADED_IMAGE_PROTOCOL *ImageInfo = NULL;
        EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *Device2TextProtocol;
        CHAR16  *TextDevicePath;
        UINTN           Index;
        CHAR8  *p;
        
        Status = gBS -> HandleProtocol(
                        ImageHandle,
                        &gEfiLoadedImageProtocolGuid,
                        &ImageInfo);
        if (EFI_ERROR(Status)) {
                Print(L"Error!\n");
        }

        //get a converter.
        Status = gBS->LocateProtocol(&gEfiDevicePathToTextProtocolGuid,
                                     NULL,
                                     (VOID**)&Device2TextProtocol
                                    );

        //convert device path to text.
        if (!EFI_ERROR (Status))
        {
                TextDevicePath= Device2TextProtocol->ConvertDevicePathToText(ImageInfo->FilePath, TRUE, FALSE);
                Print(L"%s\n",TextDevicePath);
                gBS->FreePool(TextDevicePath);
        }
        
        Print(L"Type:[%d] SubType:[%d] Length:[%d] \n",
                    ImageInfo->FilePath->Type,            
                    ImageInfo->FilePath->SubType,
                    ImageInfo->FilePath->Length[0]+
                    (ImageInfo->FilePath->Length[1]<<8));
        
        Print(L"%x\n",ImageInfo->FilePath);  
        p=(CHAR8 *)ImageInfo->FilePath;
        p=p+4;
        Print(L"%s\n",p);          
        for (Index=0;Index<ImageInfo->FilePath->Length[0]+(ImageInfo->FilePath->Length[1]<<8)-4;Index++) {
              Print(L"%2X ",*p);  
              p++;
        }
        Print(L"\n");  

        return 0;
}

 

运行结果:

dp1

结果中第一行是Device_Path_To_Text_Protocol转化的结果;第二行是这个DevciePath 的基本信息;第三行是DevicePath的内存地址,实验表明当运行完 Application后,这里面的内容会被释放掉,无法使用MEM之类的指令查看到;第四行是直接输出字符串的结果,可以看到和前面的使用 Protocol 的结果一致;最后一行直接输出DevicePath 的内容,就是 “\idp.EFI”的 Unicode值。
完整的 源代码和 X64 EFI 下载:
ImageDevicePath

参考:
1. http://www.lab-z.com/efiloadedimageprotocol/ EFILOADEDIMAGEPROTOCOL的使用
2. http://www.lab-z.com/dpdemo/解析 DevicePath 的例子

背诵,唐诗 和 MicroBit

对于小朋友是否要背诵唐诗是有争论的,一方认为有助于提升品味,不至于长大了只会夸赞“牛13,很牛13”;另外一方也有人认为因为孩子太小无法理解内容,只是机械的记忆并没有多少帮助。譬如说,长大了之后《咏鹅》只记得“鹅鹅鹅,曲项用刀割,拔毛加瓢水,点火盖上锅”。不过以我的经验来看,无论用什么内容来训练,对于小朋友的语言和记忆力很有帮助。并且,诗歌也算得上汉族人抒发感情独特的方法。譬如,有一次,送我岳父去火车站回老家。作为一个酱菜行业的手工业资本家,他对毛爷爷有着在我看起来莫名其妙但是无比深厚的感情。到火车站,还有一段时间才检票,为了抒发一下我的心情,我吟诗一段毛爷爷的诗歌“春风杨柳万千条,六亿神州尽舜尧。 红雨随心翻作浪,青山着意化为桥”。看着他点头称赞的样子更加坚定我让孩子背唐诗的信心。
最近拿到了YWROBOT提供的 MicroBit,这是一款BBC针对青少年编程推出的设备,配合微软提供的工具【参考1】可以方便的进行图形画的编程。为了实现训练孩子的目的,我用这个板子做一套手机遥控的盒子,背出之后可以自动弹开,孩子能够吃到里面放置的食物。
image001

首先介绍一下硬件设备:

1. 电控锁,工作电压12V,通电就打开,力道挺大,能把锁扣一下弹走,这种也是广泛使用在快递柜上的。
image002

2.除了 Micro Bit本身,我还买了一个扩展板,因为 MicroBit本身没有排针,不容易焊接。有了扩展版,插上后可以用杜邦线连接。
image003

3. 继电器,5V,这是很常见的低电压控制高电压设备的元件
image004

4. 三极管。我第一次直接使用这种元件。起因是偶然间我发现 MicroBit的供电能力太弱了,无法驱动继电器。具体现象是,接上之后继电器的PWR可以亮,但是控制无反应。我随后还实验了Mosfet的模块,现象相同,始悟应该是MicroBit驱动能力不足导致的。去查了一下扩流的方法,最简单的莫过于选择三极管。
最终电路如下:
image005

工作流程:手机通过蓝牙和MicroBit连接,运行BittyBlue 软件。关于这个软件和蓝牙的使用,可以在【参考2】找到。代码非常简单,使用了 Bluetooth LED 的服务,当我们设置 (0,0)的LED从0 到1的时候,就触发将 Pin16拉高1秒,驱动一个继电器,继电器将接通锁的电源,锁就会自动打开
image006

外壳使用亚克力制作,使用六面螺母搭配 M3螺丝固定
image007

组装后的外观如下:
image008

参考:
1. https://makecode.microbit.org/#
2. http://www.arduino.cn/thread-74162-1-1.html Micro:Bit 的蓝牙实验

Step to UEFI (138) Shell 下 BootOrder 的研究

对于普通用户来说,启动顺序显而易见,因此是重要的功能,比如:希望使用U盘安装系统,就必须能从U盘上引导起来。

在 Legacy 的时代,关于启动部分只有一份BBS specification可供参考,这个规范看起来非常生涩,缺点是理解不同,好处也是理解不同。很多年前,我遇到过一个客户报告的问题。他们使用IDE光驱,如果启动 失败并不会直接跳转到下一个启动设备而是显示 Boot Failure。板子上的南桥是 Intel最新推出的 ICH6, 刚好这一代南桥取消了IDE接口,为了兼容板子上使用了一颗 SATA转IDE 的JMicron 的芯片,启动的时候BIOS首先会 Load他们的Option ROM来完成初始化之类的工作。
问题确切,一时也找不到原因,我拿着BBS Spec翻来翻去,最终看懂了上面提到:报告不同种类设备(BEV 或者BCV)的启动 fail 有 Int 18 和 Int 19 的差别。于是,做了一个大胆的猜测:光驱启动失败之后,他们使用了某一种错误的报告错误方式,以至于我们在期待的点上没有办法“捕捉”到。接下来用十六进制工具,将 JMicron Option ROM 中的 INT 18 (CD 18)全都修改为 INT 19 (CD 19)。在重新编译BIOS的时间里顺便祈祷一番,然后烧录,实验成功故障消失。有了这样的结果,按照BBS 解读,再就去和 JMicron 理论了,最终他们重新给我们出了一个ROM问题解决。

时至今日,BIOS 已进化为 UEFI,启动引导有了统一的标准,使用上方便很多。理论部分可以在 UEFI Specification中的第三章 “3 Boot Manager”中看到,有兴趣的朋友可以仔细研读。

本文介绍如何在 Shell 下面查看启动顺序之类的设定,并且给出了一个实现查看的Application,资料和代码来自【参考1】 ,在此特别鸣谢。

先介绍一下原理,其实我是先看【参考2】的代码才理解的,如果下面讲述有误,也请读者朋友直接查看后面的代码,能否正常工作才是检验一切猜测的真理标准。

1. 启动设备的顺序设定
UEFI 下面的启动顺序是存储在 variable 中,放在 NVRAM 中的。使用 gRT->GetVariable ,参数为 “BootOrder”, GUID 如下即可取得设定的顺序
#define EFI_GLOBAL_VARIABLE
{0x8BE4DF61,0x93CA,0x11d2,0xAA,0x0D,0x00,0xE0,0x98,0x03,0x2B,0x8C}
取得的结果是一串数字(起初我以为有结构体,但是查找之后发现就是2个BYTE一组组成的)。
例如:实体机上,在 Shell 下 dmpstore 的结果如下:
Variable NV+RT+BS ‘8BE4DF61-93CA-11D2-AA0D-00E098032B8C:BootOrder’ DataSize = 0x0C
00000000: 04 00 00 00 01 00 02 00-03 00 05 00 *…………*
意思是 UEFI 会首先尝试0004设备, 然后是 0000设备,然后是 0001设备一直到最后,其中的 0004设备指的就是 BOOT0004

2.BOOTXXXX设备
在variable 中,还有 Boot0004这样的定义,例如:

Variable NV+RT+BS ‘8BE4DF61-93CA-11D2-AA0D-00E098032B8C:Boot0002’ DataSize = 0x4A
00000000: 09 01 00 00 2C 00 45 00-6E 00 74 00 65 00 72 00 *….,.E.n.t.e.r.*
00000010: 20 00 53 00 65 00 74 00-75 00 70 00 00 00 04 07 * .S.e.t.u.p…..*
00000020: 14 00 67 D5 81 A8 B0 6C-EE 4E 84 35 2E 72 D3 3E *..g….l.N.5.r.>*
00000030: 45 B5 04 06 14 00 21 AA-2C 46 14 76 03 45 83 6E *E…..!.,F.v.E.n*
00000040: 8A B6 F4 66 23 31 7F FF-04 00 *…f#1….*

对应内容是一个结构体:
so
so2

Attributes中有一个项目是 LOAD_OPTION_ACTIVE,用来标记这个设备是否可以启动,如果设置起来,系统会尝试从它上面进行引导。如果你不想让这个设备启动,可以直接清楚该标志。这样无需增减项目就可以实现控制。
比较有趣的是其中的 Description,给出了一个人类易读的说明,比如上面的Boot0004 设备说明即为”Enter Setup”

3.当前启动设备

“BootCurrent”变量,可以用来指出当前的引导设备。比如:当前我直接启动到了 Shell 下,BootCurrent 记录的启动设备就是 Boot0000,即Internal EDK Shell
Variable NV+RT+BS ‘8BE4DF61-93CA-11D2-AA0D-00E098032B8C:Boot0000’ DataSize = 0x58
00000000: 01 00 00 00 2C 00 49 00-6E 00 74 00 65 00 72 00 *….,.I.n.t.e.r.*
00000010: 6E 00 61 00 6C 00 20 00-45 00 44 00 4B 00 20 00 *n.a.l. .E.D.K. .*
00000020: 53 00 68 00 65 00 6C 00-6C 00 00 00 04 07 14 00 *S.h.e.l.l…….*
00000030: 22 F3 2C B9 FA 8A A4 4A-B9 46 00 5D F1 D6 97 78 *”.,….J.F.]…x*
00000040: 04 06 14 00 B7 D6 7A C5-15 05 A8 40 9D 21 55 16 *……z….@.!U.*
00000050: 52 85 4E 37 7F FF 04 00- *R.N7….*
下面是来自【参考2】的Shell Application,可以显示和设定当前的启动顺序:

运行结果:
so3

X64 UEFI Application 下载  bttest

源代码下载:bootmgr

最后顺便说一下,JMicron 后来推出了新的Sata转 IDE 芯片,无需Option ROM 了,也就没有BIOS什么事情了。

参考:
1. http://kurtqiao.github.io/uefi/2015/01/13/uefi-boot-manager.html
2. https://github.com/kurtqiao/MyPkg/tree/master/Application/bootmgr