实做:读取Audio Codec 的 VID/PID

最近在研究 Audio ,目前使用最广泛的是 Intel 的 HD Audio,和之前的 AC97 相比复杂多了,网上也少有关于如何驱动这个 Audio 的文章,看起来很痛苦。因为音频的数据量通常很大,所以通讯方面都是以 DMA 为主。好在除此之外设计上还提供了一套简单发送命令的机制。下面就是我在 Kabylake-R 的平台上实验直接发送 command 读取 codec 的 VID 和 PID的实验。

1. 找到 audio pci 设备,在 KBL-R 上是 D32:F2

我们需要的 Memory Mapped I/O 在 Offset 10 和 14 (一共是8 Bytes)

但是这时,对应的内存位置全都为0xFF:

2. 对 PCI_COMMAND_OFFSET(0x04)发送 EFI_PCI_COMMAND_MEMORY_SPACE (0x02) command:

3. 再打开 Memory Mapped I/O Register,在 0x2F FB43 0000,可以正常工作;

4. 根据 HD Audio Spec, ICW(0x60) IR(0x64) ICS(68h)。在 ICW 中写入NID=0,Verb ID=0xF00,Parameter ID=0x0的Verb Command

然后在ICS(68h) 写入 0x3

5. 很快,我们就能在IR(0x64) 看到返回值。这里我们用的是 Realtek 的 codec ,所以给出的VID 是 Realtek的,也证明方法的正确性。

如果细心观察BIOS中使用的 Verb Table 会发现在头部有一个 VID 和 PID,每次在Load 这个 Table 之前是有一个上述获取然后比较的动作的,因此,不用担心型号上的错误。比如:Realtek 298 和 798 的 PID 不同,如果使用错误会通过 Debug 口输出信息。

USB条码枪的显示

之前有写过 USB条码枪改直显和蓝牙,https://www.arduino.cn/forum.php?mod=viewthread&tid=23635&fromuid=36850

最近看到有些朋友在问,觉得有些奇怪,于是又买了一个条码枪重新实验。很快发现之前的代码无法在新的条码枪上使用。原因如下:

1.USB 条码枪 USB 数据结构有变化。换句话说,之前的那个条码枪更像是单纯的键盘,可以响应 Boot Protocol,但是新的不行。
2. USB HOST Shield 库有变化,会导致编译不过。

于是,重新实验编写了如下的代码:

barcs.ino

#include <SPI.h>
#include "bcsParser.h"
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x3F,16,2);  

USB Usb;
MSPARSER msparser(&Usb);

bool printTilt;

void setup() {
  Serial.begin(115200);
  lcd.init();
  
  if (Usb.Init() == -1) {
    Serial.print(F("\r\nOSC did not start"));
    while (1); // Halt
  }
  Serial.println(F("Barcode scaner\n\r"));
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print("Barcode scaner");
}

void loop() {
  Usb.Task();

  if (msparser.connected()) {
    
    }

}

 

bcsParser.cpp

#include "bcsParser.h"
#include <Mouse.h>
#include <LiquidCrystal_I2C.h>

extern LiquidCrystal_I2C lcd;
#define VALUE_WITHIN(v,l,h) (((v)>=(l)) && ((v)<=(h)))

uint8_t OemToAscii(uint8_t key) {
        
        // [1-9]
        if (VALUE_WITHIN(key, 0x1e, 0x26)) {
                        return (key -0x1e +1 +'0');
        }// Numbers
        //[0]
        if (key == 0x27) {return '0';}

        if (key == 0x28) {return 13;}
        // [1-9]
        if (VALUE_WITHIN(key, 0x04, 0x1D)) {
                        return (key - 0x04+'A');
        }// Alpha        

        return (0x00);
}

//解析USB鼠标的数据
void MSPARSER::ParseHIDData(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
        if (HIDUniversal::VID != STEELSERIES_VID || HIDUniversal::PID != STEELSERIES_SRWS1_PID) 
                return;

        if (len && buf)  {
          /*
                //输出收到的数据
                for (uint8_t i = 0; i < len; i++) {
                        if (buf[i]<0x10) {Serial.print("0");}
                        Serial.print(buf[i],HEX);
                        Serial.print(" ");
                }
                Serial.println();
          */
          
          char c=(OemToAscii(buf[2])&0xFF);
          if (c==13) {
            Serial.println(" "); 
              lcd.setCursor(0,1);
              lcd.print(s);
              Serial.print(s);
              for (int i=0;i<16-s.length();i++) {
                    Serial.print(" ");
                }
              s="";

            }
          else
          if (c!=0x00) {
                //输出收到的数据
                //Serial.print(c); 
                s=s+c;
                }
                 
              
          
        }
}

 

bcsParser.h

#ifndef __srws1_h__
#define __srws1_h__

#include <hiduniversal.h>

#define STEELSERIES_VID       0xFFFF
#define STEELSERIES_SRWS1_PID 0x0035

class MSPARSER : public HIDUniversal {
public:
        MSPARSER(USB *p) : HIDUniversal(p) {};
        bool connected() {
                return HIDUniversal::isReady() && HIDUniversal::VID == STEELSERIES_VID && HIDUniversal::PID == STEELSERIES_SRWS1_PID;
        };

private:
        void ParseHIDData(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf); // Called by the HIDUniversal library
        uint8_t OnInitSuccessful() { // Called by the HIDUniversal library on success
                if (HIDUniversal::VID != STEELSERIES_VID || HIDUniversal::PID != STEELSERIES_SRWS1_PID) // Make sure the right device is actually connected
                        return 1;
                return 0;
        };
        String s;
};

#endif

 

硬件上是Uno + USB Host Shield 插在一起就可以了。

照片上还有一个充放电管理板和功能没关系,普通用户可以直接使用充电宝之类给Uno供电。

完整代码下载

barcs

UEFI TIps:格式化GetLastError 结果的 FormatMessage

通常我们使用 GetLastError 来获得API 的错误代码,在取得之后还需要查表。其实可以直接使用FormatMessage 这个 API ,将错误代码转为错误的信息输出。

本文代码来自 https://www.cnblogs.com/passedbylove/p/6088096.html

static void
win32perror(const TCHAR *s)
{
	LPVOID buf;
	if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS
		| FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&buf,
		0,
		NULL)) {
		_ftprintf(stderr, _T("%s: %s"), s, buf);
		fflush(stderr);
		LocalFree(buf);
	}
	else
		_ftprintf(stderr, _T("%s: unknown Windows error\n"), s);
}

 

在使用的地方申明 TCHAR *wSZError = _T(“error message \n”);

调用上面的函数 win32perror(wSZError); 即可.

微软也提供了一个类似的例子 https://docs.microsoft.com/en-us/windows/desktop/Debug/retrieving-the-last-error-code

void ErrorExit(LPTSTR lpszFunction)
{
	// Retrieve the system error message for the last-error code

	LPVOID lpMsgBuf;
	LPVOID lpDisplayBuf;
	DWORD dw = GetLastError();

	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		dw,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR) &lpMsgBuf,
		0, NULL);

	// Display the error message and exit the process

	lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
		(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
	StringCchPrintf((LPTSTR)lpDisplayBuf,
		LocalSize(lpDisplayBuf) / sizeof(TCHAR),
		TEXT("%s failed with error %d: %s"),
		lpszFunction, dw, lpMsgBuf);
	//MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);

	LocalFree(lpMsgBuf);
	LocalFree(lpDisplayBuf);
	ExitProcess(dw);
}

 

Step to UEFI (156)UEFI 的表达式计算库

在软件工程领域,有一句著名的话,叫做“Don’t Reinvent the Wheel”—–不要重复发明轮子。究其原因一方面是重复发明效率低下,另一方面是重新发明的轮子未必好用,也许发明之后发现轴承有问题,或者是有着各种瑕疵…….对于编程来说,“复用代码”有着更明确的好处。因此,如果有可能,我们希望更多秉承鲁迅先生提出的“拿来主义”在代码设计上。最近研究了一些C语言的库,得益于UEFI 的设计和 CLIB 的支持,大部分库都可以直接使用。今天介绍的是一个可以用于表达式计算的库:TinyExpr【参考1】。

首先是要将 TinyExpr Porting 到UEFI上,直接编译会出现一些 Error 和 Warning。经过研究,需要在 INF中加入下面的内容:
1.引入LibMath,否则一些 cos 之类的函数无法识别

[LibraryClasses]
  ShellCEntryLib
  UefiLib
  LibC
  LibStdio
  LibMath

 

2.关闭一些 Warning

[BuildOptions]
   MSFT:*_*_IA32_CC_FLAGS         = /Ze /wd4201 /wd4152 /wd4090 /wd4204 /wd4055 /wd4244
   MSFT:*_*_X64_CC_FLAGS          = /Ze /wd4201 /wd4152 /wd4090 /wd4204 /wd4055 /wd4244

 

其中 C4201 Warning【参考2】,是 VS 编译器的扩展特性,比如下面这样的定义,在正经的 C 中是不允许的,但是 VC 中做了扩展是可以的:

struct S  
{  
   float y;  
   struct  
   {  
      int a, b, c;  // C4201  
   };  
} z;

 

这样扩展之后,可以直接使用 z.a 和 z.b。

此外,tinyexpr.c 中有关于 NAN 的定义和StdLib中的 Math.h中的存在冲突。我的解决方法是先用 #undef NAN 取消之前的定义,再根据 VS 编译器中 Math.h 的定义重写一次,结果如下:

//#ifndef NAN
//#define NAN (0.0/0.0)
#undef NAN
#define NAN        ((float)(INFINITY * 0.0F))
//#endif    

最终测试代码如下:
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <stdio.h>
#include <math.h>
#include "tinyexpr.h"

/**
  Set the socket options

  @param [in] Argc  The number of arguments
  @param [in] Argv  The argument value array

  @retval  0        The application exited normally.
  @retval  Other    An error occurred.
**/
int
main (
  IN int Argc,
  IN char **Argv
  )
{
    const char *c = "sqrt(5^2+7^2+11^2+(8-2)^2)";
    double r = te_interp(c, 0);
    printf("The expression:\n\t%s\nevaluates to:\n\t%f\n", c, r);

    return 0;
}

 

上述代码计算表达式

结果如下:

完整的代码下载:
expr

参考:
1.https://github.com/codeplea/tinyexpr
2.https://msdn.microsoft.com/en-us/library/c89bw853.aspx

Step to UEFI (155)系统保留内存探究

在进入Windows之后,Runtime Service 和 ACPI Table之类的仍然存在于内存中,但是Windows没有提供标准的方法进行访问。用户能获得的只有经过包装之后 ACPI Table (实际上是来自注册表) ,或者根本就没有提供(比如:Runtime Service Table)。如果想再次获得这样的信息,只能通过扫描系统保留内存来完成。但是 Windows 没有提供类似 E820 的机制告知用户(应该是因为这种信息对于用户来说是根本无需了解的)。经过一段时间的研究,找到了在 Windows下获得系统保留内存的方法:在注册表 HKLM\HARDWARE\RESOURCEMAP\System Resources\Loader Reserved的位置。以我目前使用的笔记本电脑为例,打开这个位置之后可以看到

在双击进入即可看到保留内存的信息:

此外,Device Manager中也会给出硬件占用的内存地址,但是这个和系统的保留内存是没有任何关系的。

前面给出了注册表的位置,接下来就是如何解析的问题。

CM_RESOURCE_LIST structure 【参考1】

typedef struct _CM_RESOURCE_LIST {
  ULONG                       Count;   
  CM_FULL_RESOURCE_DESCRIPTOR List[1];
} CM_RESOURCE_LIST, *PCM_RESOURCE_LIST;

 

接下来是下面这个结构体【参考2】

typedef struct _CM_FULL_RESOURCE_DESCRIPTOR {
	INTERFACE_TYPE InterfaceType; // unused for WDM   == 0
	ULONG BusNumber; // unused for WDM        
	CM_PARTIAL_RESOURCE_LIST PartialResourceList;
} CM_FULL_RESOURCE_DESCRIPTOR, *PCM_FULL_RESOURCE_DESCRIPTOR;

 

继续解析【参考3】。其中 ULONG 占用 4BYTES;USHORT占用2BYTES;UCHAR占用1BYTES.

typedef struct _CM_PARTIAL_RESOURCE_LIST {
	USHORT Version;   == 00
	USHORT Revision;
	ULONG Count;
	CM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptors[1];
} CM_PARTIAL_RESOURCE_LIST, *PCM_PARTIAL_RESOURCE_LIST;

 

USHORT Version ==00

USHORT Revision ==00

Count=0x19

接下来就是每一个保留的内存情况

CM_PARTIAL_RESOURCE_DESCRIPTOR structure【参考4】
typedef struct _CM_PARTIAL_RESOURCE_DESCRIPTOR {
  UCHAR  Type;
  UCHAR  ShareDisposition;
  USHORT Flags;
  union {
………………
………………
………………
}

 

根据上面的方法即可获得当前系统中 Loader 通知系统的 Reserved Memory
对于 type的定义在【参考5】
Identifies the resource type. The constant value specified for Type indicates which structure within the u union is valid, as indicated in the following table. (These flags are used within both CM_PARTIAL_RESOURCE_DESCRIPTORand IO_RESOURCE_DESCRIPTOR structures, except where noted.)

有了上面的基础就可以编写代码:

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

#include "stdafx.h"
#include <Windows.h>
#include <strsafe.h>
#include <tchar.h>

HKEY m_hKey;

#define OffSet(type, field) ((size_t)&(((type*)0)->field))

#pragma pack(1)
//
// Physical address.
//

typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS;

typedef struct _CM_PARTIAL_RESOURCE_DESCRIPTOR {
	UCHAR Type;
	UCHAR ShareDisposition;
	USHORT Flags;
	union {

		//
		// Range of resources, inclusive.  These are physical, bus relative.
		// It is known that Port and Memory below have the exact same layout
		// as Generic.
		//

		struct {
			PHYSICAL_ADDRESS Start;
			ULONG Length;
		} Generic;

		//
		//

		struct {
			PHYSICAL_ADDRESS Start;
			ULONG Length;
		} Port;

		//
		//

		struct {
#if defined(NT_PROCESSOR_GROUPS)
			USHORT Level;
			USHORT Group;
#else
			ULONG Level;
#endif
			ULONG Vector;
			KAFFINITY Affinity;
		} Interrupt;

		//
		// Values for message signaled interrupts are distinct in the
		// raw and translated cases.
		//

		struct {
			union {
				struct {
#if defined(NT_PROCESSOR_GROUPS)
					USHORT Group;
#else
					USHORT Reserved;
#endif
					USHORT MessageCount;
					ULONG Vector;
					KAFFINITY Affinity;
				} Raw;

				struct {
#if defined(NT_PROCESSOR_GROUPS)
					USHORT Level;
					USHORT Group;
#else
					ULONG Level;
#endif
					ULONG Vector;
					KAFFINITY Affinity;
				} Translated;
			} DUMMYUNIONNAME;
		} MessageInterrupt;

		//
		// Range of memory addresses, inclusive. These are physical, bus
		// relative. The value should be the same as the one passed to
		// HalTranslateBusAddress().
		//

		struct {
			PHYSICAL_ADDRESS Start;    // 64 bit physical addresses.
			ULONG Length;
		} Memory;

		//
		// Physical DMA channel.
		//

		struct {
			ULONG Channel;
			ULONG Port;
			ULONG Reserved1;
		} Dma;

		struct {
			ULONG Channel;
			ULONG RequestLine;
			UCHAR TransferWidth;
			UCHAR Reserved1;
			UCHAR Reserved2;
			UCHAR Reserved3;
		} DmaV3;

		//
		// Device driver private data, usually used to help it figure
		// what the resource assignments decisions that were made.
		//

		struct {
			ULONG Data[3];
		} DevicePrivate;

		//
		// Bus Number information.
		//

		struct {
			ULONG Start;
			ULONG Length;
			ULONG Reserved;
		} BusNumber;

		//
		// Device Specific information defined by the driver.
		// The DataSize field indicates the size of the data in bytes. The
		// data is located immediately after the DeviceSpecificData field in
		// the structure.
		//

		struct {
			ULONG DataSize;
			ULONG Reserved1;
			ULONG Reserved2;
		} DeviceSpecificData;

		// The following structures provide support for memory-mapped
		// IO resources greater than MAXULONG
		struct {
			PHYSICAL_ADDRESS Start;
			ULONG Length40;
		} Memory40;

		struct {
			PHYSICAL_ADDRESS Start;
			ULONG Length48;
		} Memory48;

		struct {
			PHYSICAL_ADDRESS Start;
			ULONG Length64;
		} Memory64;

		struct {
			UCHAR Class;
			UCHAR Type;
			UCHAR Reserved1;
			UCHAR Reserved2;
			ULONG IdLowPart;
			ULONG IdHighPart;
		} Connection;

	} u;
} CM_PARTIAL_RESOURCE_DESCRIPTOR, *PCM_PARTIAL_RESOURCE_DESCRIPTOR;

//
// A Partial Resource List is what can be found in the ARC firmware
// or will be generated by ntdetect.com.
// The configuration manager will transform this structure into a Full
// resource descriptor when it is about to store it in the regsitry.
//
// Note: There must a be a convention to the order of fields of same type,
// (defined on a device by device basis) so that the fields can make sense
// to a driver (i.e. when multiple memory ranges are necessary).
//

typedef struct _CM_PARTIAL_RESOURCE_LIST {
	USHORT Version;
	USHORT Revision;
	ULONG Count;
	CM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptors[1];
} CM_PARTIAL_RESOURCE_LIST, *PCM_PARTIAL_RESOURCE_LIST;

//
// Define the I/O bus interface types.
//

typedef enum _INTERFACE_TYPE {
	InterfaceTypeUndefined = -1,
	Internal,
	Isa,
	Eisa,
	MicroChannel,
	TurboChannel,
	PCIBus,
	VMEBus,
	NuBus,
	PCMCIABus,
	CBus,
	MPIBus,
	MPSABus,
	ProcessorInternal,
	InternalPowerBus,
	PNPISABus,
	PNPBus,
	Vmcs,
	ACPIBus,
	MaximumInterfaceType
}INTERFACE_TYPE, *PINTERFACE_TYPE;

//
// A Full Resource Descriptor is what can be found in the registry.
// This is what will be returned to a driver when it queries the registry
// to get device information; it will be stored under a key in the hardware
// description tree.
//
// Note: There must a be a convention to the order of fields of same type,
// (defined on a device by device basis) so that the fields can make sense
// to a driver (i.e. when multiple memory ranges are necessary).
//

typedef struct _CM_FULL_RESOURCE_DESCRIPTOR {
	INTERFACE_TYPE InterfaceType; // unused for WDM
	ULONG BusNumber; // unused for WDM
	CM_PARTIAL_RESOURCE_LIST PartialResourceList;
} CM_FULL_RESOURCE_DESCRIPTOR, *PCM_FULL_RESOURCE_DESCRIPTOR;


//
// The Resource list is what will be stored by the drivers into the
// resource map via the IO API.
//

typedef struct _CM_RESOURCE_LIST {
	ULONG Count;
	CM_FULL_RESOURCE_DESCRIPTOR List[1];
} CM_RESOURCE_LIST, *PCM_RESOURCE_LIST;

int main()
{
	DWORD Index;
	BYTE  *v;

	if (RegOpenKeyEx(
			HKEY_LOCAL_MACHINE, 
			TEXT("HARDWARE\\RESOURCEMAP\\System Resources\\Loader Reserved"),
			0, 
			KEY_READ, 
			&m_hKey) != ERROR_SUCCESS)
	{
		printf("RegOpenKeyEx fail \n"); 
		getchar();
		exit(0);
	}

	BOOL bRet = FALSE;
	LPSTR lpstrValue;
	DWORD dwType = REG_SZ;
	DWORD lpcbData;
	DWORD r;

	r = RegQueryValueEx(m_hKey,
		TEXT(".Raw"),
		NULL,
		&dwType,
		NULL,
		&lpcbData);
	if (r != ERROR_SUCCESS)
	{
		printf("Can't get data from registry\n");
		getchar();
		exit(0);
	}
	bRet = FALSE;
	lpstrValue = (LPSTR)malloc(lpcbData);

	r = RegQueryValueEx(m_hKey,
		TEXT(".Raw"),
		NULL,
		&dwType,
		(BYTE*)(LPCTSTR)lpstrValue,
		&lpcbData);
	if (r != ERROR_SUCCESS)
	{
		printf("fail\n");
		getchar();
		exit(0);
	}

	for (Index = 0; Index < lpcbData; Index++) {
		if (Index % 16 == 0) { printf("\n"); }
		v = (BYTE*)lpstrValue;
		printf("%02X ", *(v + Index));

	}

	PCM_RESOURCE_LIST res= (PCM_RESOURCE_LIST)lpstrValue;
	PCM_PARTIAL_RESOURCE_LIST Partial;
	DWORD  Index1;

	for (Index = 0; Index < res->Count; Index++) 
	{
		printf("\nInteface type: %d\n Bus Number: %d\n", 
				res->List[Index].InterfaceType,
			    res->List[Index].BusNumber);
		Partial = (PCM_PARTIAL_RESOURCE_LIST) &res->List[Index].PartialResourceList;
		printf(" Version: %d\n Revision: %d\n Counter: %x\n",
			Partial->Version,
			Partial->Revision,
			Partial->Count);
		for (Index1 = 0; Index1 < Partial->Count; Index1++) {
			//printf("%d\n", Partial->PartialDescriptors[Index1].Type);
			if (Partial->PartialDescriptors[Index1].Type == 3) {
				printf("Start:%016I64x Length:%x \n",
						Partial->PartialDescriptors[Index1].u.Memory.Start,
						Partial->PartialDescriptors[Index1].u.Memory.Length
					);
			}
		}
	}
	free(lpstrValue);
	getchar();
    return 0;
}

 

运行结果如下(运行的机器和之前做分析的不同,所以 Counter数量不同)

参考:
1. https://msdn.microsoft.com/en-us/library/windows/hardware/ff541994(v=vs.85).aspx
2. https://msdn.microsoft.com/en-us/library/windows/hardware/ff541954(v=vs.85).aspx
3. https://msdn.microsoft.com/en-us/library/windows/hardware/ff541981(v=vs.85).aspx
4. https://msdn.microsoft.com/en-us/library/windows/hardware/ff541977(v=vs.85).aspx
5. https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_cm_partial_resource_descriptor

Step to UEFI (154)反编译一个 EFI

之前介绍过,可以通过生成的 .cod 文件来查看生成的汇编代码,今天偶然在 https://blog.csdn.net/robinsongsog/article/details/77164704 看到 uefi_artisan 提供的更简单方法。
dumpbin.exe 是 VS 提供的一个工具,使用下面的方法可以将一个 EFI 直接反汇编:
dumpbin /disasm 文件名.efi
例子:反编译之前的 shorts.efi

得到的结果如下:

Microsoft (R) COFF/PE Dumper Version 12.00.21005.1
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file showrts.efi
File Type: DLL
00000000000002C0: 4C 8B DC mov r11,rsp
00000000000002C3: 49 89 5B 18 mov qword ptr [r11+18h],rbx
00000000000002C7: 49 89 73 20 mov qword ptr [r11+20h],rsi
00000000000002CB: 57 push rdi
00000000000002CC: 48 83 EC 30 sub rsp,30h
00000000000002D0: 4C 8B 52 60 mov r10,qword ptr [rdx+60h]
00000000000002D4: 48 8B 42 58 mov rax,qword ptr [rdx+58h]
00000000000002D8: 49 83 63 08 00 and qword ptr [r11+8],0
00000000000002DD: 49 83 63 10 00 and qword ptr [r11+10h],0
00000000000002E2: 48 8B F2 mov rsi,rdx
00000000000002E5: 48 89 15 5C 1E 00 mov qword ptr [00002148h],rdx

可以看到这样的方法可以让我们很容易得到结果。
关于 dumpbin 的更详细解释可以参考下面2篇文章:

https://blog.csdn.net/jamestaosh/article/details/4237756
https://blog.csdn.net/fengbingchun/article/details/43956673

网站终于恢复了

之前的服务器受到攻击,所有内容丢失,只能恢复到 2018年7月30日的备份。

然后更换了空间供应商为 Godaddy, 希望能够稳定一些吧。

如果你有任何问题,欢迎给我留言,另外,推荐一下本站的公众号,在页面右下方可以看到。

另外,本站 https 已经开通,有需要的朋友(比如,你所在地方供应商有插入广告之类的行为),可以用 Https://www.lab-z.com 或者 https://lab-z.com

来完成访问。

Intel Brand Verification Tool

最近一直在和Intel BVT 作战。

Intel BVT 工具的全称是 Intel Brand Verification Tool。 是一款用来测试vPro然后生成报告的工具。测试通过之后,客户通过提交报告即可获得贴vPro Logo的资格。这个工具并不是进行功能性检查,而只是检查配置。例如:报告上说TLS已经Ready,但是实际上软件只是测试标志位是否已经设置起来,但是并不表示这个功能没有问题。如果想确认功能好用,那么需要使用 PETS 之类的进行测试。

BVT 还有一个很大的坑就是关于 Wireless Lan 的设定,如果你没有设定那么测试会提示 “CLink interface to Wi-Fi adapter tests FAILED”。而解决方法是令人匪夷所思的从 Gbe 的Lan登录AMT 的WebUI (Http://IP:16992), 然后找到 Wireless 的设定部分随便写一个AP 设定。

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

2019年1月22日 更新

关于最后一点,如果使用特定的Intel WireLess Driver ,可以在不通过 Gbe Lan的情况下通过 http://127.0.0.1:16992 的方式登录到 WebUI 进行设定。但是从测试结果上来看这样的方法并不稳定(经常会出现无法找到页面,需要再次刷新才能进去的情况)。

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

2020年5月22日 更新

AMT 默认密码是 admin ,第一次进入之后会要求你重设一个密码,你可以使用 “Admin98#” 这个符合密码规则。

参考:

https://wenku.baidu.com/view/a09d33bd960590c69ec376b4.html

Intel主动管理技术(Intel AMT)设置指南

https://blog.51cto.com/lzlutao/1218077

如何使用Intel AMT 8.0技术远程管理PC

 

【图解】替换 Windows ACPI Table的方法

本文介绍一种在没有BIOS代码的情况下,修改 ACPI Table 的方法。实际上这种方法已经使用很多很多年了,作为一种测试的方法是非常有效的。
使用到的工具一个是asl 这是微软 wdk 里面带的,用来 dump 和 load 系统acpi table 的;另外一个是 iasl, 这是 acpica 提供的工具,用来编译和反编译 asl 。特别需要注意的问题是,我在实践中发现 iasl 不同版本之间差别很大,如果你在使用中发现编译或者反编译错误非常多,那么最好多尝试几个版本,如果有条件最好直接使用Build 你BIOS的版本。
原理:Windows每次使用的 ACPI table 不是直接从内存读取的,而是缓冲在系统注册表上,因此替换这个注册表里面的就可以取得替换 ACPI Table的效果。
操作:

1. 在 console 下面(需要 Administrator权限),使用命令 asl.exe /tab=DSDT /c

2. 反编译抓下来的 DSDT0000.bin ,使用命令 iasl2016 –ve DSDT0000.bin

虽然报错了,但是还是生成了 dsdt0000.dsl

3. 将生成的 DSDT0000.DSL 改名为 dsdt.dsl,再重新编译之 ,命令是 Iasl2016.exe –ve DSDT.dsl

编译直接出错,修改这个错误(单纯来说,我这次碰到的问题是因为 extern 了 BNUM 和 RTIP,但是后面又重新定义了这两个名字所以会有冲突,修改方法是去掉 extern 的 BNUM和RTIP)

修改之后就可以正常编译了
4. 加入一个我们自定义的设备做为标记,加入的位置在 EC 下面,这样便于观看。加入的代码如下:

            Device (LABZ)
            {
                Name (_HID, EisaId ("LAB33D6") /* Intel Virtual Buttons Device */)  // _HID: Hardware ID
                Method (_STA, 0, Serialized)  // _STA: Status
                {
                    Return (0x0F)
                }

            }

再次编译,

5. 将编译后的结果加入系统中,命令是 asl /loadtable dsdt.aml

6. 重启之后,设备管理器中还是没有变化。需要再设置打开TestMode。 方法是在 console 下面使用
Bcdedit /set testsigning on

7.重启之后就可以在设备管理器中看到我们加入的设备了
加入之前

修改之后

上面的 Unknown device就是我们新加入的设备,设备属性

8. 删除加载的 dsdt 的方法是 asl /loadtable dsdt.aml –d 重启之后即可恢复

changeacpi

从原理上说,Windows会将 DSDT Table “缓存” 在注册表中,在开机的过程中不会去内存解析DSDT而是直接使用缓存的。因此,我们可以通过上面的方法来加入我们需要的代码。

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

2024年5月20日 在 Windows 11 的虚拟机上测试过,上述方法仍然有效。

Step to UEFI (153)用GetFirmwareEnvironmentVariable修改Setup选项

高考结束很多年了,班主任的音容笑貌仍然会出现在噩梦中,我仍然记得他在讲述虚数i的时候特别强调过时时刻刻牢记 i^2=-1,因为这是虚数和实数相互转换的门。

对于BIOS工程师来说SMI 就是当下OS和BIOS代码之间门。前面研究过了 gRT->GetVariable的Windows实现,因此,这里也可以作为非常简单的门。这次我们的目标是:在Shell 和 Windows下修改 Setup 的取值。比如说,我们当前的设计上需要更改四个Setup item才能去掉刷写BIOS的保护,这着实很麻烦,如果能自动修改就方便多了。
前文已经找到了BIOS对于Setup 限制访问的位置,经过研究,VarCheckLib.c 中的VarCheckLibSetVariableCheck() 函数会在 PlatformVarCheckLibNullClass.c 中的 mSetupVariableList[] 中查找 GUID 和 名称,对得上就会 Write Protect。为此,将 gSetupVariableGuid,L”Setup” 的定义去掉即可突破这个限制。

接下来,有2种实现的方法:

方法一:Windows Application直接对 Setup 变量的写入。这里选择 Setup 中的第二个UINT8 实验,对应的是 FastBoot 选项。

#define VariableGuidStr1     "{EC87D643-EBA4-4BB5-A1E5-3F3E36B20DA9}"
#define TestStr2             "Setup"

	printf("Check Setup");
	dwRet = GetFirmwareEnvironmentVariable(
		_T(TestStr2),
		_T(VariableGuidStr1),
		pBuffer,
		iBufferSize);
	printf("GetFirmwareEnvironmentVariable again, return value:%x\n", dwRet);
	Buffer[1] = 1;
	dwRet = SetFirmwareEnvironmentVariable(
		_T(TestStr2),
		_T(VariableGuidStr1),
		pBuffer,
		dwRet);
	printf("SetFirmwareEnvironmentVariable return value:%x\n", dwRet);
	printf("GetLastError:%x\n", GetLastError());

	printf("Check Setup again\n");

	dwRet = GetFirmwareEnvironmentVariable(
		_T(TestStr2),
		_T(VariableGuidStr1),
		pBuffer,
		iBufferSize);
	printf("GetFirmwareEnvironmentVariable again, return value:%x\n", dwRet);

 

运行结果:

再进入 Setup检查,可以看到 Fast Boot 已经设置为 Disabled。

方法二:在代码中根据输入的 GUID 来判断,然后做出设定的动作。在 VariableSmmRuntimeDxe.c 中的 RuntimeGerviceGetVariable() 函数中加入对GUID 的判断,如果是我们期望的,那么就先取出 Setup Variable,修改之后再用 RuntimeServiceSetVariable()放回去。

/**
  This code finds variable in storage blocks (Volatile or Non-Volatile).

  Caution: This function may receive untrusted input.
  The data size is external input, so this function will validate it carefully to avoid buffer overflow.

  @param[in]      VariableName       Name of Variable to be found.
  @param[in]      VendorGuid         Variable vendor GUID.
  @param[out]     Attributes         Attribute value of the variable found.
  @param[in, out] DataSize           Size of Data found. If size is less than the
                                     data, this value contains the required size.
  @param[out]     Data               Data pointer.

  @retval EFI_INVALID_PARAMETER      Invalid parameter.
  @retval EFI_SUCCESS                Find the specified variable.
  @retval EFI_NOT_FOUND              Not found.
  @retval EFI_BUFFER_TO_SMALL        DataSize is too small for the result.

**/
EFI_STATUS
EFIAPI
RuntimeServiceGetVariable (
  IN      CHAR16                            *VariableName,
  IN      EFI_GUID                          *VendorGuid,
  OUT     UINT32                            *Attributes OPTIONAL,
  IN OUT  UINTN                             *DataSize,
  OUT     VOID                              *Data
  )
{
  EFI_STATUS                                Status;
  UINTN                                     PayloadSize;
  SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE  *SmmVariableHeader;
  UINTN                                     TempDataSize;
  UINTN                                     VariableNameSize;

//LABZ_Start
  SETUP_DATA                   SetupData;
  UINTN                        VariableSize;
  UINT32                       VariableAttributes;
  
  if (CompareGuid (VendorGuid, &gLabZTestGuid)) { // 比较查看参数是否要触发
          
  VariableSize = sizeof (SETUP_DATA);
  Status = RuntimeServiceGetVariable (  //取Setup变量
                  L"Setup",
                  &gSetupVariableGuid,
                  &VariableAttributes,
                  &VariableSize,
                  &SetupData
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  SetupData.WakeOnRTCS5=1;  //修改选项值
  Status = RuntimeServiceSetVariable ( //将修改之后的结果回写
                  L"Setup",
                  &gSetupVariableGuid,
                  VariableAttributes,
                  VariableSize,
                  &SetupData
                  );
  ASSERT_EFI_ERROR(Status);
              
  }
//LABZ_End

  if (VariableName == NULL || VendorGuid == NULL || DataSize == NULL) {
    return EFI_INVALID_PARAMETER;
  }

 

在 KBL-R HDK 平台上实验,首先在Shell 中,为了便于实验编写一个测试程序,最主要的功能就是调用 gRT->GetVariable 发送gLabZTestGuid 来触发函数。
在 Windows下运行编译后的 Shell Application ,再进入 Setup查看是能够看到选项已经成功修改。

#define TestGuidStr          "{11111111-1111-1111-1111-111111111111}" //用这个定义只是方便

	dwRet = GetFirmwareEnvironmentVariable(
		_T(TestStr2),
		_T(TestGuidStr),
		pBuffer,
		iBufferSize);
	printf("GetFirmwareEnvironmentVariable again, return value:%x\n", dwRet);

 

仍然是成功的。

从上面可以看到,这个方法无论在 Shell 下还是 Windows下都能正常工作(虽然没有实验,但是我也相信支持 UEFI 的Linux也是可以使用的)。此外,作为参数的 GUID 足够多,能让我们灵活的处理各种情况。