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 足够多,能让我们灵活的处理各种情况。

Step to UEFI (152)为什么SetVariable在Windows下不好用?

之前的文章【参考1】提出了一个问题:为什么在Windows 下SetFirmwareEnvironmentVariable API 只能对有限的几个Variable有用?比如:可以修改“BootOrder”,但是无法修改 “Setup”的值。
文章比较长,大篇幅的罗列代码让文章更像技术手册,没有耐心的朋友可以直接拖动到文章末尾看结论。
我们已经知道 Windows 中的SetFirmwareEnvironmentVariable 函数最终是通过 gRT->SetVariable 调用到 RuntimeServiceSetVariable中的【参考2】 。所以我们可以使用WinDBG追踪代码查看是在哪里返回了错误值。
具体分析:

1.下断点

bp hal!HalEfiSetEnvironmentVariable

 

2. HalEfiSetEnvironmentVariable 函数中的call qword ptr [rax+28h] 这个位置就是跳到BIOS 中的SetVariable的入口(再介绍一下 COD文件的生成:
在VariableSmmRuntimeDxe.inf 加入下面的编译指令

[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS = /Oi- /FAcs /Od

 

在编译过程中即可生成
\Build\KabylakePlatSamplePkg\RELEASE_VS2013x86\X64\MdeModulePkg\Universal\Variable\RuntimeDxe\VariableSmmRuntimeDxe\VariableSmmRuntimeDxe.cod 使用 WinDBG 追踪可以看到执行的代码是 RuntimeServiceSetVariable函数,前面的文章介绍过这样的方法追踪 GetVariable)

3.接下来查看BIOS的代码,因为有 COD文件的存在,我们也能知道每一条函数源代码对应的汇编指令。为了便于实验,在 Variable.c 中的 VariableServiceSetVariable中加入对 80 Port 输出91h的代码,这样可以在 WinDBG 追踪中确定具体“干活”代码的位置。

4.查看 RuntimeServiceSetVariable 函数的返回值部分,可以看到是通过 rax 来返回函数结果的

  001b0	48 8b 54 24 58	 mov	 rdx, QWORD PTR VendorGuid$[rsp]
  001b5	48 8b 4c 24 50	 mov	 rcx, QWORD PTR VariableName$[rsp]
  001ba	e8 00 00 00 00	 call	 SecureBootHook
$LN1@RuntimeSer:
$LN2@RuntimeSer:
; 642  :     }
; 643  :   }
; 644  :   return Status;
  001bf	48 8b 44 24 30	 mov	 rax, QWORD PTR Status$[rsp]
$LN9@RuntimeSer:
; 645  : }

 

5.编写 Application,调用2次SetFirmwareEnvironmentVariable,第一次是针对BootOrder 变量的,第二次是针对VendorKeys的。

#define VariableGuidStr      "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}"
#define BootOrderStr         "BootOrder"
	//Get BootOrder
	dwRet = GetFirmwareEnvironmentVariable(
		_T(BootOrderStr),
		_T(VariableGuidStr),
		pBuffer,
		iBufferSize);
	printf("GetFirmwareEnvironmentVariable return value:%x\n", dwRet);

 

实验结果是前者可以正常设置,但是后者会报错。对于SetFirmwareEnvironmentVariable 函数来说,返回值为0表示出错,不为0是正常【参考3】。

这个错误的含义是【参考4】,同样的这个错误的定义可以在【参考5】看到

ERROR_WRITE_PROTECT
19 (0x13)
The media is write protected.

6.使用 WinDBG 在调用处下断点

kd> u
hal!HalEfiSetEnvironmentVariable+0x41:
fffff800`ce10b8a5 4c8b4c2460      mov     r9,qword ptr [rsp+60h]
fffff800`ce10b8aa 458bc3          mov     r8d,r11d
fffff800`ce10b8ad 488b054c570300  mov     rax,qword ptr [hal!HalEfiRuntimeServicesTable (fffff800`ce141000)]
fffff800`ce10b8b4 488bd3          mov     rdx,rbx
fffff800`ce10b8b7 4c894c2420      mov     qword ptr [rsp+20h],r9
fffff800`ce10b8bc 488bcf          mov     rcx,rdi
fffff800`ce10b8bf 4d8bca          mov     r9,r10
fffff800`ce10b8c2 ff5028          call    qword ptr [rax+28h]

 

7.不知道什么原因,我在Application 中只调用了2次SetFirmwareEnvironmentVariable,但是 WinDBG 会停下来4次。经过观察前面2次返回值为0x0000000000000000,后面两次返回值0x8000000000000008。

至此,可以确定SetFirmwareEnvironmentVariable 无法修改是因为 UEFI 代码导致的问题,和 Windows 无关。确定这点之后,就可以在 BIOS Source code 中查找是哪里返回了这个错误。最终确定在 \MdeModulePkg\Library\VarCheckLib\VarCheckLib.c 下面的位置返回的 Write Protected

  //
  // Also check the property revision before using the property data.
  // There is no property set to this variable(wildcard name)
  // if the revision is not VAR_CHECK_VARIABLE_PROPERTY_REVISION.
  //
  if ((Property != NULL) && (Property->Revision == VAR_CHECK_VARIABLE_PROPERTY_REVISION)) {
    if ((RequestSource != VarCheckFromTrusted) && ((Property->Property & VAR_CHECK_VARIABLE_PROPERTY_READ_ONLY) != 0)) {
      DEBUG ((EFI_D_INFO, "Variable Check ReadOnly variable fail %r - %g:%s\n", EFI_WRITE_PROTECTED, VendorGuid, VariableName));
     return EFI_WRITE_PROTECTED;
    }
    if (!((((Attributes & EFI_VARIABLE_APPEND_WRITE) == 0) && (DataSize == 0)) || (Attributes == 0))) {
      //
      // Not to delete variable.
      //
      if ((Property->Attributes != 0) && ((Attributes & (~EFI_VARIABLE_APPEND_WRITE)) != Property->Attributes)) {
        DEBUG ((EFI_D_INFO, "Variable Check Attributes(0x%08x to 0x%08x) fail %r - %g:%s\n", Property->Attributes, Attributes, EFI_INVALID_PARAMETER, VendorGuid, VariableName));
        return EFI_INVALID_PARAMETER;
      }
      if (DataSize != 0) {
        if ((DataSize < Property->MinSize) || (DataSize > Property->MaxSize)) {
          DEBUG ((EFI_D_INFO, "Variable Check DataSize fail(0x%x not in 0x%x - 0x%x) %r - %g:%s\n", DataSize, Property->MinSize, Property->MaxSize, EFI_INVALID_PARAMETER, VendorGuid, VariableName));
          return EFI_INVALID_PARAMETER;
        }
      }
    }
  }

 

从代码中看,这个设定应该是一个安全的 Feature。

总结:BIOS中因为安全设定的缘故阻止了对于一些 Variable 的Write。Windows调用 gRT-> SetVariable 发现返回值是Error,于是 API 也会返回错误值。

参考;
1. http://www.lab-z.com/stu143/ Windows 下BootOrder研究
2. http://www.lab-z.com/haldbg1/ HAL.DLL 中的 HalEfiGetEnvironmentVariable
3. https://msdn.microsoft.com/en-us/library/ms724934(v=vs.85).aspx
4. https://msdn.microsoft.com/en-us/library/ms681382(v=vs.85).aspx
5. 这个错误在 UEFI 中的定义如下

/**
  Produces a RETURN_STATUS code with the highest bit set.

  @param  StatusCode    The status code value to convert into a warning code.
                        StatusCode must be in the range 0x00000000..0x7FFFFFFF.

  @return The value specified by StatusCode with the highest bit set.

**/
#define ENCODE_ERROR(StatusCode)     ((RETURN_STATUS)(MAX_BIT | (StatusCode)))

///
/// The device can not be written to.
///
#define RETURN_WRITE_PROTECTED       ENCODE_ERROR (8)

 

同样的,编写一个 Shell Application 可以确认就是 Write Protected 错误

        Print(L" 0x13=[%r] \n",0x8000000000000008UL);