Windows下读取 ACPI Tabel

Windows下读取 ACPI Table 的正经做法是使用EnumSystemFirmwareTables和GetSystemFirmwareTable。下面是一个来自【参考1】的例子,读取系统中的 MSDM Table 并且显示出来。读取结果可以和之前介绍过的 FirmwareTablesView 读取结果结合起来对照,二者是相同的。

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

#include "stdafx.h"
#include "Windows.h"

int main()
{
	DWORD FirmwareTableProviderSignature;
	PVOID pFirmwareTableBuffer;
	DWORD BufferSize;
	UINT  BytesWritten;
	DWORD FirmwareTableID;
	DWORD *pFirmwareTableID;
	BOOL  foundTable = FALSE;
	BOOL  verbose = TRUE;


	FirmwareTableProviderSignature = 'ACPI';
	pFirmwareTableBuffer = NULL;
	BufferSize = NULL;

	// get buffer size, call with null values
	BufferSize = EnumSystemFirmwareTables(FirmwareTableProviderSignature,
		NULL,
		NULL);

	// alloc memory
	pFirmwareTableBuffer = malloc(BufferSize);

	// enum acpi tables
	BytesWritten = EnumSystemFirmwareTables(FirmwareTableProviderSignature,
		pFirmwareTableBuffer,
		BufferSize);

	// enumerate ACPI tables, look for MSDM table
	pFirmwareTableID = (DWORD*)pFirmwareTableBuffer;
	for (DWORD i = 0; i < BytesWritten / 4; i++)
	{
		FirmwareTableID = *pFirmwareTableID;
		if (verbose) printf("%.*s\n", 4, pFirmwareTableID);
		if (FirmwareTableID == _byteswap_ulong('MSDM')) {
			if (verbose) printf("Found MSDM table\n");
			foundTable = TRUE;
			break;
		}
		pFirmwareTableID++;
	}


	if (foundTable) {
		// get buffer size, call with null values
		BufferSize = GetSystemFirmwareTable(FirmwareTableProviderSignature,
			FirmwareTableID,
			NULL,
			NULL);
		// alloc memory
		pFirmwareTableBuffer = malloc(BufferSize);

		BytesWritten = GetSystemFirmwareTable(FirmwareTableProviderSignature,
			FirmwareTableID,
			pFirmwareTableBuffer,
			BufferSize);

		/*
		Table description form Miocrosoft at: http://go.microsoft.com/fwlink/p/?LinkId=234834

		Microsoft Software Licensing Tables (SLIC and MSDM)
		http://msdn.microsoft.com/library/windows/hardware/hh673514
		Byte 			Byte
		Field							Lenght			Offset	Description
		======							=====			======	===========
		Signature						4				0		MSDM
		Length							4				4		Length, in bytes, of the entire table.
		Revision						1				8		0x01
		Checksum						1				9		Checksum of the entire table.
		OEMID							6				10		An OEM-supplied string that identifies the OEM.
		OEM Table ID					8				16		Optional motherboard/BIOS logical identifier.
		OEM Revision					4				24		OEM revision number of the table for the supplied OEM Table ID.
		Creator ID						4				28		Vendor ID of the utility that created the table.
		Creator Revision				4				32		Revision of the utility that created the table.
		Software Licensing Structure	Variable length	36		Proprietary data structure that contains all the licensing
		data necessary to enable Windows activation.
		Details can be found in the appropriate Microsoft OEM
		licensing kit by first visiting the Microsoft OEM website
		(http://www.microsoft.com/oem/pages/index.aspx).
		*/

		BYTE *Signature = (BYTE*)memset(malloc(4 + 1), NULL, 4 + 1);
		UINT Length;
		BYTE Revision;
		BYTE Checksum;
		BYTE *OEMID = (BYTE*)memset(malloc(6 + 1), NULL, 6 + 1);
		BYTE *OEMTbleID = (BYTE*)memset(malloc(8 + 1), NULL, 8 + 1);
		BYTE OEMRev;
		BYTE *CreatorID = (BYTE*)memset(malloc(4 + 1), NULL, 4 + 1);
		UINT CreatorRev;
		UINT SLS_Size = BytesWritten - 36;
		BYTE *SLS = (BYTE*)memset(malloc(SLS_Size), NULL, SLS_Size);
		UINT SLS_Version;
		UINT SLS_Reserved;
		UINT SLS_DataType;
		UINT SLS_DataReserved;
		UINT SLS_DataLenght;
		BYTE *ProductKey = (BYTE*)memset(malloc(30 + 1), NULL, 30 + 1);


		memcpy_s(Signature, 4 + 1, (BYTE*)pFirmwareTableBuffer + 0, 4);
		Length = *(DWORD*)((BYTE*)pFirmwareTableBuffer + 4);
		Revision = *((BYTE*)pFirmwareTableBuffer + 8);
		Checksum = *((BYTE*)pFirmwareTableBuffer + 9);
		memcpy_s(OEMID, 6 + 1, (BYTE*)pFirmwareTableBuffer + 10, 6);
		memcpy_s(OEMTbleID, 8 + 1, (BYTE*)pFirmwareTableBuffer + 16, 8);
		OEMRev = *(DWORD*)((BYTE*)pFirmwareTableBuffer + 24);
		memcpy_s(CreatorID, 4 + 1, (BYTE*)pFirmwareTableBuffer + 28, 4);
		CreatorRev = *(DWORD*)((BYTE*)pFirmwareTableBuffer + 32);
		memcpy_s(SLS, SLS_Size, (BYTE*)pFirmwareTableBuffer + 36, SLS_Size);
		SLS_Version = *(DWORD*)((BYTE*)SLS);
		SLS_Reserved = *(DWORD*)((BYTE*)SLS + 4);
		SLS_DataType = *(DWORD*)((BYTE*)SLS + 8);
		SLS_DataReserved = *(DWORD*)((BYTE*)SLS + 12);
		SLS_DataLenght = *(DWORD*)((BYTE*)SLS + 16);
		memcpy_s(ProductKey, SLS_DataLenght, (BYTE*)SLS + 20, SLS_DataLenght);

		if (verbose) {
			printf("Signature         : %s\n", Signature);
			printf("Length            : %d\n", Length);
			printf("Revision          : %d\n", Revision);
			printf("Checksum          : %d\n", Checksum);
			printf("OEMID             : %s\n", OEMID);
			printf("OEM Table ID      : %s\n", OEMTbleID);
			printf("OEM Revision      : %d\n", OEMRev);
			printf("Creator ID        : %s\n", CreatorID);
			printf("Creator Revision  : %d\n", CreatorRev);
			printf("SLS Version       : %d\n", SLS_Version);
			printf("SLS Reserved      : %d\n", SLS_Reserved);
			printf("SLS Data Type     : %d\n", SLS_DataType);
			printf("SLS Data Reserved : %d\n", SLS_DataReserved);
			printf("SLS Data Lenght   : %d\n", SLS_DataLenght);
			printf("Key               : %s\n", ProductKey);
			/*printf ("\n\nPress [Enter] to continue . . .");
			fflush (stdout);
			getchar();*/
		}
		else {
			printf("%s", ProductKey);
		}
	}


	getchar();
    return 0;
}

 

运行结果:

参考:
1. https://pastebin.com/d7WFJ5ce

UEFI Tips:UDK2017 fopen 失效了

最近在 Porting 一段代码的时候有使用到 fread(),但是非常奇怪的是一直无法正常工作,得到的错误返回值一直为0。后来在网上找到Robins_Lee 的文章提到了这个问题https://blog.csdn.net/mini92/article/details/79274823。原来新的EDK2中,需要在INF [LibraryClasses] 中加入 DevShell 才行。
对于这个要求在 \StdLib\ReadMe.txt 有描述如下:

Figure 5: Package Build Options
===============================

INF Files
=========
The INF files for most modules will not require special directives in order to
support the Standard Libraries. The two sections which require attention: LibraryClasses
and BuildOptions, are described below.

[LibraryClasses]
UefiLib
LibC
LibString
LibStdio
DevShell

Figure 6: Module Library Classes
================================

Modules of type UEFI_APPLICATION that perform file I/O must include library
class DevShell. Including this library class will allow file operations to be
handled by the UEFI Shell. Without this class, only Console I/O is supported.

An application’s INF file might need to include a [BuildOptions] section
specifying additional compiler and linker flags necessary to allow the
application to be built. Usually, this section is not needed. When building
code from external sources, though, it may be necessary to disable some
warnings or enable/disable some compiler features.

[BuildOptions]
INTEL:*_*_*_CC_FLAGS = /Qdiag-disable:181,186
MSFT:*_*_*_CC_FLAGS = /Oi- /wd4018 /wd4131
GCC:*_*_IPF_SYMRENAME_FLAGS = –redefine-syms=Rename.txt

编写测试代码:

#include <uefi.h>
#include <Library/UefiLib.h>

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <errno.h>
#include<wchar.h>

int
main (
  IN int Argc,
  IN char *Argv[]
  )
{
	FILE *fd;
	char text[100];
        
	if (Argc<2) {
		printf("Please input filename\n");
		exit(1);
	}
	if ((fd = fopen(Argv[1], "rb")) == NULL)
	{
		printf("open file failed. %s\n", strerror(errno));
		exit(1);
	}
	memset(text,0,sizeof(text));
	fread(text, sizeof(char), 100, fd);
	printf("%s\n", text);
	fclose(fd);

	return 0;
}

 

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = fot
  FILE_GUID                      = 4ea97c46-0995-4dfd-b442-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#
#  VALID_ARCHITECTURES           = IA32 X64
#

[Sources]
  fopentest.c

[Packages]
  StdLib/StdLib.dec
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  LibC
  LibStdio
  UefiLib
  DevShell

 

运行结果:

完整代码:
fopentest

2018 Maker Carnival Shanghai

2018年10月13日,本站会参加位于上海市淞沪路388号创智天地广场7号楼1楼举办的创客嘉年华活动,展位编号 A34。主要展示 Arduino 设计相关内容。

展览中出售的模块如下:
1. USB Host Mini http://www.lab-z.com/cuhm/
2. USB Host Shield http://www.lab-z.com/arduinousb1/
3. Leonardo2UNO Shield http://www.lab-z.com/l2u/
4. ProtoShield V1 http://www.lab-z.com/prototype-shield-v3/
5. ProtoShield V3 http://www.lab-z.com/prototype-shield-v3/

欢迎新老朋友前来捧场。

ProtoType Shield V3

很多时候,我们需要制作自己的 Shield板,这时候可以选用 Prototype Shield,可以理解为就是形状为Shield 的洞洞板。

这样做的好处是:方便使用,即插即用;成本比较低。

淘宝上也有做好的这样的 Shield 板, 价格为 6元

相比之下,我的设计的好处是:可以使用排针+排母的方式进行扩展。这样就避免购买长脚排母的麻烦。同时Taobao上找不到足够硬的排母,这意味着安装好的排母经不起几次插拔。

下面是焊接好的样子:

附件是 Eagle版的电路图。
zProtoShieldv1.1

此外,存在的问题:

1.在 leonrado上用力下压会顶到9V电源,没有短路之虞但是对于追求完美的人可能无法忍受;
2.在Uno 上,左上角会接触到 USB口,可能会短路,在使用的时候我都是贴上胶带的.

祖国版 USB HOST MINI 使用

第一, 我们淘宝上能买到的,不是原本的设计,有一些修改,这样的修改会导致USB设备的供电问题。下面是原版的设计,在绿色圈标记的位置,有一个焊接的跳线还有一个排针的位置。当焊接跳线接起来的时候,USB设备收到的是3.3V的电压(我不清楚有什么设备可以在3.3V供电的情况下工作,所以这个设计让我觉得莫名其妙)。

下面是我们能买到的祖国版

可以看出标记的位置是有差别的。对于我们来说,需要切开上面的连接,然后在排针上送入5V即可正常驱动 USB 设备工作。

第二, 模块有一个 USB Host Reset Pin,在正常使用的时候需要设置为High。为了简单起见,可以将这个 Pin 直接连接到3.3v。

实做:读取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