好用的UEFI GUID工具

前面提到过在编写UEFI 程序的时候不可避免的需要引用PROTOCOL 等等的 GUID。最近在网上看到了一个Python 编写的工具,作者是Xiang Lian【参考1】,可以将项目中定义的 GUID都提取到一个文件中个,这样便于查找和使用。我实验了一下,挺好用的推荐给大家。

#!/c:\python27\python.exe
#
#      Filename:   guidxref.py
#       Written:   Xiang Lian
#
#
# This python script scans through UDK2010 build tree and saves all GUID definitions into
# an output file. Refer list TargetFileTypes for currently suported source file types.
#
#-------------------------------------------------------------------------------------------------
#
#   Rev history:   rev 1.0    10/07/2012
#                       - Guid_In_h had missed some lower-cased GUIDs;
#
#                  rev 1.1    10/07/2012
#                       - Added captured groups in Guid_In_h, refer RegFormatOutput;
#
#                  rev 1.2    10/08/2012
#                       - Swapped content in output line so that GUID strings always come first
#
#                  rev 1.5    10/08/2012
#                       - Simplified os.walk logic to significantly reduce the redundant scans
#                       - Added summary to report total number of files scanned by type
#
#                  rev 1.6    10/08/2012
#                       - Added logging module, to turn on logging output:
#                             . Uncomment the logging.basicConfig line
#                             . Choose level in basicConfig
#                       - Always save result into a newly create output file
#
#                  rev 2.0    10/09/2012
#                       - Created function NormalizeGuidString to replace the buggy RegFormatOutput pattern.
#                         Now all output GUIDs have a fixed length and are made uppercase
#
#                  rev 2.2    10/11/2012
#                       - Added list ExcludedDirs which folders will not be scanned
#                       - Added lambda function toU to conver registry format GUIDs to uppercase
#                       - Output filenames are now including timestamp strings
#                       - Logging filenames are always saved into a newly created file (suffixed with seq#)
#
#                  rev 3.0    10/11/2012
#                       - Added filter to remove some invalid lines in the output
#                       - Output lines are sorted and all duplicates removed
#
#                  rev 3.1    10/19/2012
#                       - Collected all user customizable values/configs into class UserConfig
#                       - Minor adjustment on summary output
#
#                  rev 3.2    10/25/2012
#                       - Modified Guid_In_h pattern to match wider range of GUID formats (in EDK)
#
#                  rev 3.3    10/30/2012
#                       - Defined VimScriptMode switch to output result in vim script format
#
#                  rev 3.4    10/31/2012
#                       - Added .c into the scan file list (TargetFileTypes)
#                       - Added (?i) in Guid_In_h: This makes the entire pattern case in-sensitive
#                       - Resolved lines like: EFI_GUID  PciIoProtocolInstallEventGuid = {...};
#                       - Resolved lines containing tabs
#                       - No more year info in result file name
#
#                  rev 3.5    10/31/2012
#                       - Added NormalizedGuidLine for final validity check
#
#
#
#
#
#
# Cases currently cannot be handled:
#
#    EFI_GUID  gEfiUsbKeyboardDriverGuid = {
#      0xa05f5f78, 0xfb3, 0x4d10, 0x90, 0x90, 0xac, 0x4, 0x6e, 0xeb, 0x7c, 0x3c
#    };
# 
#----------------------------------------------------------------------------------------------------
#
import os, re, string, sys
import logging
import datetime


#
# Global variables
#
class UserConfig:
  """ This class defines a set of constants and configurations which can be customized by 
      the script user. 
  """
  ScriptRev = " Ver 3.5"

  # To generate logging output, change this to 1
  LoggingEnable = 0

  # Set this to 1 to generate the result file in vim script format
  VimScriptMode = 0

  # The maximum character width of the console output line
  MAXLINEWIDTH = 110

  # This list provides all the file types to be scanned
  TargetFileTypes = {'.h' : 0, '.dec' : 0, '.inf' : 0, '.dsc' : 0, '.c' : 0}

  # Directories to be excluded from the scan
  ExcludedDirs = ('.svn', 'Build', 'uefi64native')

  # Base file name for result file
  BaseOutputName = "guidxref_"

  # Base file name for logging output
  BaseLogName = ".\debug"



# This defines the continuation character at end of line
ContinuingEol = "\\\n"

# Define directive in GUID definition (usually in .h and .c files)
DefineDirective = "#define"

# Header part of the GUID definition line (usually in INF or DSC files)
RegGuidDef = "^.*\=\s*"          #note: "^\(.*\)\=\s*" doesn't work!

# GUID Definitive format - Below pattern matches lines like: 
#      { 0xbf9d5465, 0x4fc3, 0x4021, {0x92, 0x5, 0xfa, 0x5d, 0x3f, 0xe2, 0xa5, 0x95}}
Guid_In_h = "(?i)\{\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*{?\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+)\s*}?\s*\}.*"

#This is buggy, has already been replaced by NormalizeGuidString2
RegFormatOutput = r"\1-\2-\3-\4\5-\6\7\8\9\10\11"   # Note: have to add prefix 'r' to make it raw here

# GUID Registry format - Below pattern matches lines like:  FILE_GUID = A5102DBA-C528-47bd-A992-A2906ED0D22B
Guid_In_Inf = "[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+"

# Normalized GUID lines like: A5102DBA-C528-47bd-A992-A2906ED0D22B  xxxx
NormalizedGuidLine = r"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}  [^#]+"


#################################### Functions Definition #################################################

def NormalizeGuidString (matchobj):
  """ Definitive format GUID string normalization - Prefixing with 0s for every captured group
      
      Parameter matchobj is a MatchObject instance which contains one or more subgroups of the pattern
      match done by the re.sub() method. It's same as the return object of re.search(). 
  """
  hex = [""]
  for i in range (1, 12):
    hex.append(matchobj.group(i))
  hex[1]  = format (int(hex[1],  16), '08x').upper()
  hex[2]  = format (int(hex[2],  16), '04x').upper()
  hex[3]  = format (int(hex[3],  16), '04x').upper()
  hex[4]  = format (int(hex[4],  16), '02x').upper()
  hex[5]  = format (int(hex[5],  16), '02x').upper()
  hex[6]  = format (int(hex[6],  16), '02x').upper()
  hex[7]  = format (int(hex[7],  16), '02x').upper()
  hex[8]  = format (int(hex[8],  16), '02x').upper()
  hex[9]  = format (int(hex[9],  16), '02x').upper()
  hex[10] = format (int(hex[10], 16), '02x').upper()
  hex[11] = format (int(hex[11], 16), '02x').upper()
  return hex[1]+'-'+hex[2]+'-'+hex[3]+'-'+hex[4]+hex[5]+'-'+hex[6]+hex[7]+hex[8]+hex[9]+hex[10]+hex[11]


def SearchGuidsFromList (SrcList, filename):
  """ This function searchs for GUID definitions from a given string list.
  """
  GuidLines = []
  for n,line in enumerate(SrcList):
    while line.endswith(ContinuingEol):
      line = line.rstrip(ContinuingEol)
      #this doesnt work?? line = re.sub("\\\n", "", line)
      MergeLine = [line, SrcList[n+1]]
      line = "".join(MergeLine)      # This converts a list to a string
      del SrcList[n+1]
      #logging.debug ("  Merged line #%d, %s", n, line)
  
    # Now start searching for GUID pattern

    # Process .inf and .dsc files
    match = re.search(Guid_In_Inf, line, re.IGNORECASE | re.MULTILINE)
    if match:
      logging.debug ("Found a registry format GUID")
      line = re.sub(RegGuidDef, filename + "  ", line)       # Trim out useless part
      line = re.sub(Guid_In_Inf, lambda toU: toU.group().upper(), line) # Convert GUID to uppercase
      #str = raw_input ("................................................. Press ENTER key to continue:")
      line = line.lstrip()
      line = re.sub("\A(.*?)\s+(.*)", r"\2  \1", line)       # Swap it. lx-'\A' and '?' are both important
      GuidLines.append(line)

    # Process .h and .dec files
    match = re.search(Guid_In_h, line, re.IGNORECASE | re.MULTILINE)
    if match:
      logging.debug ("Found a definitive GUID")
      line = re.sub(DefineDirective, "", line)             # Trim out useless part
      line = re.sub(Guid_In_h, NormalizeGuidString, line)  # Convert to registry format
      #str = raw_input ("................................................. Press ENTER key to continue:")
      line = re.sub(r"\A(.*?)EFI_GUID\s+", "", line)   # Trim EFI_GUID 
      line = line.lstrip()
      line = re.sub("\A(.*?)[ =\t]+(.*)", r"\2  \1", line)   # Swap it. lx-'\A' and '?' are both important
      GuidLines.append(line)

  return GuidLines


def main():

  # Configure the logging module to send debug messages to file
  #
  # Refer:
  #    logging.debug (msg, *args)
  #    logging.info (msg, *args)
  #    logging.warning (msg, *args)
  #    logging.error (msg, *args)
  #    logging.critical (msg, *args)
  #    logging.setLevel (level)
  #    logging.disable (level)
  #
  # Valid values to set for level (by severity) in basicConfig are: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
  #
  #full format: logging.basicConfig(level=logging.DEBUG, filename='debug.log', format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
  if UserConfig.LoggingEnable:
    for seq in range (0, 99):
      LogFileName = UserConfig.BaseLogName + format (seq, '02d') + ".log"
      if not os.path.exists(LogFileName):
        break
      seq += 1
    logging.basicConfig(level=logging.DEBUG, filename=LogFileName, format='%(levelname)s: %(message)s')

  # Ensure input parameter is valid
  if len(sys.argv) < 2:
    print "Usage - ", sys.argv[0], " <Directory>\n"
    exit(0)

  RootDir = sys.argv[1]
  if (os.path.exists(RootDir) == False):
    print "Invalid directory name, please try again!"
    exit(1)
  
  # Determine output file
  now = datetime.datetime.now()
  ##suffix = now.strftime ("%Y_%m_%d_%H_%M_%S")
  suffix = now.strftime ("%m_%d_%H_%M_%S")

  if UserConfig.VimScriptMode == 0:
    ofile = open(UserConfig.BaseOutputName + suffix + ".txt", "w")
    # Print header message
    ofile.write("Generated by " + sys.argv[0] + UserConfig.ScriptRev + "\n")
    ofile.write("=" * 40 + "\n\n")
  else:
    ofile = open(UserConfig.BaseOutputName + suffix + ".vim", "w")
    # Print header option settings
    ofile.write(":set mm=2000000\n")
    ofile.write(":set undolevels=-1\n")

  # Traverse the root directory path for required source files
  TotalGuids = 0
  TempBuffer = []
  for root, dirs, files in os.walk(RootDir):
    # Check directories to be excluded from the scan
    for folder in UserConfig.ExcludedDirs:
      if folder in dirs:
        dirs.remove(folder)

    logging.debug ('  root = %s', root)
    #logging.debug ('  dirs = %s', dirs)
    #for file in files:
    #logging.debug ('  file = %s', file)
  
    for file in files:
      for type in UserConfig.TargetFileTypes.keys():
        if file.endswith(type):
          logging.info ("Found needed files = %s\\%s", root, file)
          UserConfig.TargetFileTypes[type] += 1

          # Scan the file for GUID strings
          try:
            fullpath = os.path.join(root, "".join(file))
            ifile = open(fullpath, "r")
          except:
            print folder, "\\", file, " could not be opened, abort!"
            sys.exit(2)
          
          logging.debug ('Opening source file ........%s', fullpath)
          AllLines = ifile.readlines()
          GuidList = SearchGuidsFromList (AllLines, "".join(file))
          if (len(GuidList) > 0):
            OutputLineWidth = UserConfig.MAXLINEWIDTH - len(fullpath)
            print fullpath, "." * OutputLineWidth, len(GuidList)
            for line in GuidList:
              # Remove some invalid lines
              if not re.search (r"\/\/$", line, re.MULTILINE):    #lx: "\Z" and "\z" don't work. ??
                TempBuffer.append(line)

          ifile.close()

  # Remove duplicates from the list
  #
  # [Python.docs] A set object is an **unordered** collection of distinct hashable objects. Common uses 
  # include membership testing, removing duplicates from a sequence, and computing mathematical operations 
  # such as intersection, union, difference, and symmetric difference.
  #
  TempBuffer = list(set(TempBuffer))

  # Now sort the list
  #
  # [Python.docs] sorted (iterable [, key][, reverse])
  #
  #   key - specifies a function of one argument that is used to extract a comparison key from
  #         each list element: key=str.lower. The default value is None (compare the elements directly)
  #   reverse - a boolean value. If set to True, the list elements are sorted as if each comparison 
  #             were reversed.
  #
  TempBuffer = sorted(TempBuffer)
  for line in TempBuffer:
    if re.match(NormalizedGuidLine, line):
      #
      # Ideally we don't need to add this extra validity check on each processed line. However there
      # are always some corner cases or various special cases which cannot be well handled by current
      # code logic. These filtered lines here can later be investigated when I have spare time.
      # 
      TotalGuids += 1
      if UserConfig.VimScriptMode:
        line = re.sub(r"\A([^ ]*)  (.*)$", r":%s/\1/\2/e", line)
      ofile.write(line)
    else:
      #lx-Adding following lines will unexpectedly remove the next line as well, why?
      #TempBuffer.remove(line)
      print "Invalid line: ", line

  ofile.close()

  # Print summary
  print "\n", "-" * 50, "Summary", "-" * 55
  for type in UserConfig.TargetFileTypes.keys():
    print "Scanned ", format (UserConfig.TargetFileTypes[type], '04d'), format (type, "4s"), " files"

  print "\nTotal number of GUIDs found: ", TotalGuids 


#
# Why do we need this?
# A .py file can be interpreted by Python as either standalone program to execute directly,
# or a module to be imported into other .py files. 
#   1) Standalone program - __name__ equals to "__main__"; 
#   2) imported as a module - __name__ equals to something else, therefore contents behind
#      the if statement won't get executed.
#
if __name__ == "__main__":
  main()

 

下载:

guidxref

参考:
1. https://github.com/simonlian/guidref/blob/master/guidxref.py

Step to UEFI (112)FSNTx和FSx

之前的文章提到过,我注意到UDK2014自带的Shell 使用的是 Fsnt0 这样的盘符,而我重新编译生成的却是 Fs0 这样的盘符。今天研究和实验相关代码,解开了困扰已久的问题。

首先,这个盘符的概念不是UEFI 的,而是Shell 这个程序自己创建和分配的。于是在代码中搜索,在UefiShellCommandLib.c 找到了下面的代码:

/**
  Function to generate the next default mapping name.

  If the return value is not NULL then it must be callee freed.

  @param Type                   What kind of mapping name to make.

  @retval NULL                  a memory allocation failed.
  @return a new map name string
**/
CHAR16*
EFIAPI
ShellCommandCreateNewMappingName(
  IN CONST SHELL_MAPPING_TYPE Type
  )
{
  CHAR16  *String;
  ASSERT(Type < MappingTypeMax);

  String = NULL;

  String = AllocateZeroPool(PcdGet8(PcdShellMapNameLength) * sizeof(String[0]));
  UnicodeSPrint(
    String,
    PcdGet8(PcdShellMapNameLength) * sizeof(String[0]),
    Type == MappingTypeFileSystem?L"FS%d:":L"BLK%d:",
    Type == MappingTypeFileSystem?mFsMaxCount++:mBlkMaxCount++);

  return (String);
}

 

可以看到,使用的是FS%d 这样的来进行分配,用重新编译Shell的方式确定这个位置。

然后我们修改代码,增加一个字符作为标记:

Type == MappingTypeFileSystem?L"FSz%d:":L"BLK%d:",

 

根据【参考1】进行编译,当然我们已经工作在UDK2015上,但是之前的方法仍然是可行的。编译好Shell.efi 之后,我们还要查看一下 \Nt32Pkg\Nt32Pkg.fdf 对于 Shell 的调用。看起来和之前不同了,我们猜测使用了UefiShell.inf 。

################################################################################
#
# FILE statements are provided so that a platform integrator can include
# complete EFI FFS files, as well as a method for constructing FFS files
# using curly "{}" brace scoping. The following three FILEs are
# for binary shell, binary fat and logo module.
#
################################################################################
!ifndef $(USE_OLD_SHELL)
INF  ShellBinPkg/UefiShell/UefiShell.inf
!else
INF  EdkShellBinPkg/FullShell/FullShell.inf
!endif

INF FatBinPkg/EnhancedFatDxe/Fat.inf

FILE FREEFORM = PCD(gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdLogoFile) {
    SECTION RAW = MdeModulePkg/Logo/Logo.bmp
  }

 

在打开UefiShell.inf 根据指引替换了 Shell.efi 为新编译出来的。再 build nt32和 build run。 得到的结果如下,可以看到,盘符发生了变化:

image001

我们再用十六进制工具打开 shell.efi ,搜索 fsz (特别注意这是unicode不是 ascii)

image003

因此,盘符的前缀就是在这里生成的。

然后我们再返回到 UDK2014中,打开模拟器运行结果如下:

image005

这就是我们一直疑惑的 FsntX: 直接使用十六进制编辑工具打开 Shell_Full.efi ,搜索 Fsnt 。找到之后我们修改为 Fsnz,再保存,重新build Nt32,然后再次运行编译器,结果就发生了变化:

image007
于是,可以解释我们之前的疑惑了:他们 Publish 的 Shell.efi 和我们拿到的并非一套代码,一些细节存在差别。
本文只相当于定性的分析,后面我们还会进行更详细的分析,到底盘符是如何来的,就是这样。

参考:
1. http://www.lab-z.com/how2buildshell/ How to build Shell.efi

Arduino 101 可以用来生成 1-1Mhz的方波

最近在查看资料【参考1】的时候,发现 101 提供了定时器输出 PWM 的功能。

“定时器输出PWM
除了作中断源使用,定时器也可以用作PWM输出,CurieTimerOne提供的pwmStart函数可以输出PWM。
在之前的章节中使用的analogWrite函数输出的PWM,周期固定,占空比可调,可用作LED调光;tone函数输出的PWM,周期不变,占空比可调,可用作无源蜂鸣器发声;而pwmStart输出的PWM周期和占空比都可调,更具灵活性,适用场合更广。
需注意的是pwmStart是重载函数,其有两种重载方式:

pwmStart(unsigned int outputPin, double dutyPercentage, unsigned int periodUsec);

pwmStart(unsigned int outputPin, int dutyRange, unsigned int periodUsec);

参数outputPin为输出PWM的引脚编号,periodUsec为每个周期的时间,单位为微秒。
而第二个参数可以为double 型,也可以为int型。当参数为double 型时,编译器会以dutyPercentage进行重载,参数以百分比形式表示PWM占空比;当参数为int型时,编译器会以dutyRange进行重载,参数以0到1023的形式表示PWM占空比;”

正好,手上有示波器,随手测量一下,发现效果不错

下面是生成的 1K 结果:
1K

2.5K 结果:
25k

25K 结果:
205k

参考:
1.http://www.arduino.cn/thread-42007-1-1.html 【Arduino101教程】定时器的使用

检查当前是否支持 DEVSLP 的软件

DEVSLP 是 Intel 推出的用于 SATA硬盘的省电功能。在PCH上,有一个Pin,当HIGH时,表示要求硬盘进入省电模式,否则处于Active模式。

下面是一段介绍,来自【参考1】

DEVSLP is a signal that is sent to a SATA disk drive to tell it to enter a very low power state. The SATA specification has always allowed for the drive to enter a low power state, but in the past the drive had to keep its high speed transceiver circuitry powered up in order to receive the signal to wake up again. DEVSLP removes this power hungry requirement by using a seperate low speed pin.

The ability to enter an ultra low power state is crucial for ultrabooks and other battery powered devices. Using DEVSLP, manufacturers are able to produce devices with power consumption of micro Watts compared to milli Watts in previous generation drives

Where did they find the extra pin?

There were no spare pins in the SATA conector standard for 2.5″ and 3.5″ drives (SFF-8482). Revision 3.2 assigns pin P3 as DEVSLP. Pins P1,P2 and P3 used to be a 3.3V power supply to the drive, very few drives (if any) and very few systems ever implemented 3.3V power so the pins were re-designated. If you should happen to put a DEVSLP enabled drive in a system that provides 3.3V power, the drive will go into low power mode and stay there.

如果想实现这个功能,首先需要硬盘支持,在ATAPI的ID命令中有定义对应的位置(Device Sleep Supported)。然后需要在BIOS中打开对应的DEVSLP 功能。
推荐使用 TxBench工具【参考2】能够直接检测当前的硬盘是否支持以及系统中这个功能是否打开。

下图是BIOS中打开DEVSLP 的截图。 1标记位置是当前硬盘是否支持,2标记位置是当前系统是否支持DEVSLP。可以看到都是支持的。

image001

下图是BIOS中关闭DEVSLP的截图,可以对照上面查看。

image002

下面是对这块硬盘(Samsung 750 EVO )的简单性能测试:

image003

如果你是专门为 Connected Standby 选购硬盘的话,个人建议最好入手 Intel SSD最保险。

参考:
1. http://www.storageinterface.com/articles/12-sata-devslp
2. http://www.texim.jp/txbenchus.html

Step to UEFI (111)Shell下面的HDD菜单

最近在尝试编写Shell下面的一些关于磁盘工具,设计了一个Shell 下面硬盘的选择菜单。这个程序会枚举当前系统中的全部硬盘,然后以菜单的形式提供给客户操作。
程序流程介绍,首先,枚举系统中全部Block Io 的 Handle,之后检查每个 Block Io 的Media,看看是否为物理硬盘,因为我们的操作目标是完整的硬盘而不是某个分区。接下来,再针对每个Handle枚举上面的 DevicePath Protocol, 我们需要这个 Protocol 转化出来的String作为给用户的提示信息。最后,结合之前我们编写过的一个菜单程序的框架,将HDDx的选择和提示信息展示在屏幕上。用户通过上下键和回车键来选择希望操作的硬盘。
具体代码如下,其中包括了一些简单的注视,还有一些用于Debug的位置。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Library/ShellLib.h>

#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Protocol/DevicePath.h>
#include <Protocol/DevicePathToText.h>

extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_HANDLE					 gImageHandle;

EFI_GUID gEfiDevicePathToTextProtocolGuid =
		{ 0x8B843E20, 0x8132, 0x4852, 
			{ 0x90, 0xCC, 0x55, 0x1A, 0x4E, 0x4A, 0x7F, 0x1C }};
EFI_GUID	gEfiBlockIoProtocolGuid = 
		{ 0x964E5B21, 0x6459, 0x11D2, 
			{ 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
			
#define MAXDEVICEPATH  255	
typedef struct  {
	CHAR16		Name[6];
	CHAR16  	DevicePathString[MAXDEVICEPATH];
	EFI_HANDLE	Controller;
	EFI_BLOCK_IO_PROTOCOL   *BlockIo;
} tItem;

#define NUM  9
tItem	Items[]=	{
	{L"HDD-0",L"",NULL,NULL},
	{L"HDD-1",L"",NULL,NULL},
	{L"HDD-2",L"",NULL,NULL},
	{L"HDD-3",L"",NULL,NULL},
	{L"HDD-4",L"",NULL,NULL},
	{L"HDD-5",L"",NULL,NULL},
	{L"HDD-6",L"",NULL,NULL},
	{L"HDD-7",L"",NULL,NULL},
	{L"HDD-8",L"",NULL,NULL}
};	

void DrawMenu(UINTN x1,UINTN y1,UINTN Count)
{
	UINTN	i;
	EFI_INPUT_KEY	Key;
	EFI_STATUS		Status;
	int current=0;
	
	ShellPrintEx((UINT32)x1+40,(UINT32)y1  ,L"%S",L"Choose the DISK you want to ERASE");	
	ShellPrintEx((UINT32)x1+40,(UINT32)y1+1,L"%S",L"Arrow Up/Down, Enter Select, ESC Quit");
	ShellPrintEx((UINT32)x1,(UINT32)y1,L"%H%S",Items[0].Name);
	for (i=1;i<Count;i++) {
		ShellPrintEx((UINT32)x1,(UINT32)(y1+i),L"%N%S",Items[i].Name);
	}
	ShellPrintEx(
		0,(UINT32)(y1+Count),
		L"%s \n",
		Items[current].DevicePathString
	);	
	
	Key.ScanCode=SCAN_NULL;
	while (SCAN_ESC!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
		if (Status == EFI_SUCCESS)	{
			ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%N%S",Items[current].Name);
			if (SCAN_UP == Key.ScanCode) {current = (UINT32)(current-1+Count)%Count;}
			if (SCAN_DOWN == Key.ScanCode) {current = (current+1)%Count;}
			ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%H%S",Items[current].Name);			
			/*
			ShellPrintEx(
					x1,y1+Count,
					L"Current[%d] Scancode[%d] UnicodeChar[%x] \n\r",
					current,
					Key.ScanCode,
					Key.UnicodeChar);		
			*/
			ShellPrintEx(
					(UINT32)0,(UINT32)(y1+Count),
					L" ");							
			for (i=0;i<MAXDEVICEPATH-1;i++)	{
				Print(L" ");				
			}
			ShellPrintEx(
					(UINT32)0,(UINT32)(y1+Count),
					L"%s \n",
					Items[current].DevicePathString
					);
					
		}
		if (CHAR_CARRIAGE_RETURN == Key.UnicodeChar) {
			//ShellPrintEx(x1,y1+Count,L"You have chosen: %N%S",Items[current].Name);
			break;
		}
	};	

}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS				Status;
    UINTN					HandleCount,HandleIndex,ItemIndex=0;
    EFI_HANDLE              *BlockControllerHandles = NULL;	
	EFI_BLOCK_IO_PROTOCOL   			*BlockIo;
	EFI_DEVICE_PATH_TO_TEXT_PROTOCOL	*Device2TextProtocol = 0;
	
	//Get the Protcol for DevicePath to String
    Status = gBS->LocateProtocol(
            &gEfiDevicePathToTextProtocolGuid,
            NULL,
            (VOID**)&Device2TextProtocol
            );
	
	//Enumate all the handle which has Block Io Protocol
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiBlockIoProtocolGuid,
            NULL,
            &HandleCount,
            &BlockControllerHandles);  

   if (!EFI_ERROR(Status)) {
        for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
			//Get all the Block Io Protocol
            Status = gBS->HandleProtocol(
                    BlockControllerHandles[HandleIndex],
                    &gEfiBlockIoProtocolGuid,
                    (VOID**)&BlockIo);
			//Check if it's a logical disk, 
			//we only operate on a physical disk		
			if (BlockIo->Media->LogicalPartition == 0) {
				EFI_DEVICE_PATH_PROTOCOL*	DiskDevicePath=NULL;   
				//Get the Block Io Protocol
				Status = gBS->HandleProtocol(
					BlockControllerHandles[HandleIndex],
					&gEfiDevicePathProtocolGuid, 
					(VOID*)&DiskDevicePath
					);
				if (EFI_ERROR(Status)) {
					Print(L"Get device path error [%r]\n",Status);
					continue;
				} 
				{
					CHAR16*	TextDevicePath = 0;
					//Convert Disk path to a human readable string
					TextDevicePath = 
						Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, FALSE, TRUE); 
					//Print(L"%s\n", TextDevicePath);
					
					//Save all the data to a struct
					Items[ItemIndex].Controller=BlockControllerHandles[HandleIndex];
					Items[ItemIndex].BlockIo=BlockIo;
					StrCpy(Items[ItemIndex].DevicePathString,TextDevicePath);
					ItemIndex++;
					
					if(TextDevicePath)gBS->FreePool(TextDevicePath);				
				}
			}//if (BlockIo->Media->LogicalPartition == 0)
        }	//for (HandleIndex = 0;
		
		//You can output all the data you have gotten here
		//UINT32	i;
		//for (i=0;i<ItemIndex;i++)	{
		//	Print(L"%d %s %s\n",i,Items[i].Name,Items[i].DevicePathString);
		//}
		//Print(L"[%d]",ItemIndex);
		
		gST->ConOut->ClearScreen(gST->ConOut);	
		//Set our menu from (0,0) to (21,6), and there should be ItemIndex items
		DrawMenu(0,0,ItemIndex);
		
        gBS->FreePool(BlockControllerHandles);
	
    }			
	
  return EFI_SUCCESS;
}

 

程序在模拟其中运行结果如下,同样的我也在 Intel ApolloLake HDK 平台上试验过,工作也是正常的。

hddmenu

最后,如果真要很完美的话,应该再加入更多的提示信息,比如,对于U盘,要显示名称序列号之类的,这样才便于用户区分当前的操作对象。有兴趣的朋友,可以参考前面几篇文章,加入这个功能。
完整的代码和X64下Application的下载:

hddmenu

Lenorade的按键值

对于 Leonardo 来说,最大的特点就是可以直接模拟USB键盘鼠标。
一般来说,当我们需要发送某一个按键的时候可以直接使用Keyboard.print(‘a’)这样的方式,或者在Keyboard.h 中找到定义好的键值,比如:#define KEY_F1 0xC2。
但是最近我遇到一个问题:这个文件中定义的键值我无法在其他资料上找到,让人非常恼火,例如:没有一份资料上说 CAPS LOCK值为0xC1,但是使用Keyboard.h中定义的 #define KEY_CAPS_LOCK 0xC1 确实是好用的。最后才想起来应该去看一下对应的代码(开源的优点)。最终,找到的代码如下:

size_t Keyboard_::press(uint8_t k) 
{
	uint8_t i;
	if (k >= 136) {			// it's a non-printing key (not a modifier)
		k = k - 136;
	} else if (k >= 128) {	// it's a modifier key
		_keyReport.modifiers |= (1<<(k-128));
		k = 0;
	} else {				// it's a printing key
		k = pgm_read_byte(_asciimap + k);
		if (!k) {
			setWriteError();
			return 0;
		}
		if (k & 0x80) {						// it's a capital letter or other character reached with shift
			_keyReport.modifiers |= 0x02;	// the left shift modifier
			k &= 0x7F;
		}
	}
	
	// Add k to the key report only if it's not already present
	// and if there is an empty slot.
	if (_keyReport.keys[0] != k && _keyReport.keys[1] != k && 
		_keyReport.keys[2] != k && _keyReport.keys[3] != k &&
		_keyReport.keys[4] != k && _keyReport.keys[5] != k) {
		
		for (i=0; i<6; i++) {
			if (_keyReport.keys[i] == 0x00) {
				_keyReport.keys[i] = k;
				break;
			}
		}
		if (i == 6) {
			setWriteError();
			return 0;
		}	
	}
	sendReport(&_keyReport);
	return 1;
}

 

就是说,当我们使用KEY_CAPS_LOCK ==0xC1,会先有一个 193-136=57的动作,最后发送出去的实际上是 57(0x39),在USB HID Usage Tables 有下面的定义:

keydef

同样的文档中我们可以查到 Scroll Lock == 71(0x47) Num Lock == 83 (0x53)。于是,定义如下:

#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB

 

最终,我们编写一个代码,能让键盘上的三个LED逐次亮灭

#include "Keyboard.h"

#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB

void setup() {
  pinMode(A0, INPUT_PULLUP);
  Keyboard.begin();
  while (digitalRead(A0) == HIGH) {
    // do nothing until pin 2 goes low
    delay(500);
  }
  
}

void loop() {
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
   
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);  
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);

  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000);  
  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000); 
  
}

 

Step to UEFI (110)在 Shell 下面使用 Unicode 的制表符

在DOS时代,可以用Ascii制表符做出好看的界面。

tabm

同样的 Unicode 中也有制表符【参考1】

tabn

下面的代码就是枚举输出UEFI 下面定义的全部制表符。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>


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

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	CHAR16 ChrSide[2] = {0,0};
	
ChrSide[0]=BOXDRAW_HORIZONTAL;
Print(L"BOXDRAW_HORIZONTAL [%s]\n",ChrSide);                 
ChrSide[0]=BOXDRAW_VERTICAL;
Print(L"BOXDRAW_VERTICAL [%s]\n",ChrSide);                   
ChrSide[0]=BOXDRAW_DOWN_RIGHT;
Print(L"BOXDRAW_DOWN_RIGHT [%s]\n",ChrSide);                 
ChrSide[0]=BOXDRAW_DOWN_LEFT;
Print(L"BOXDRAW_DOWN_LEFT [%s]\n",ChrSide);                  
ChrSide[0]=BOXDRAW_UP_RIGHT;
Print(L"BOXDRAW_UP_RIGHT [%s]\n",ChrSide);                   
ChrSide[0]=BOXDRAW_UP_LEFT;
Print(L"BOXDRAW_UP_LEFT [%s]\n",ChrSide);                    
ChrSide[0]=BOXDRAW_VERTICAL_RIGHT;
Print(L"BOXDRAW_VERTICAL_RIGHT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_VERTICAL_LEFT;
Print(L"BOXDRAW_VERTICAL_LEFT [%s]\n",ChrSide);              
ChrSide[0]=BOXDRAW_DOWN_HORIZONTAL;
Print(L"BOXDRAW_DOWN_HORIZONTAL [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_HORIZONTAL;
Print(L"BOXDRAW_UP_HORIZONTAL [%s]\n",ChrSide);              
ChrSide[0]=BOXDRAW_VERTICAL_HORIZONTAL;
Print(L"BOXDRAW_VERTICAL_HORIZONTAL [%s]\n",ChrSide);        
ChrSide[0]=BOXDRAW_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_HORIZONTAL [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL;
Print(L"BOXDRAW_DOUBLE_VERTICAL [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_DOWN_RIGHT_DOUBLE;
Print(L"BOXDRAW_DOWN_RIGHT_DOUBLE [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_RIGHT;
Print(L"BOXDRAW_DOWN_DOUBLE_RIGHT [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_RIGHT;
Print(L"BOXDRAW_DOUBLE_DOWN_RIGHT [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOWN_LEFT_DOUBLE;
Print(L"BOXDRAW_DOWN_LEFT_DOUBLE [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_LEFT;
Print(L"BOXDRAW_DOWN_DOUBLE_LEFT [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_LEFT;
Print(L"BOXDRAW_DOUBLE_DOWN_LEFT [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_UP_RIGHT_DOUBLE;
Print(L"BOXDRAW_UP_RIGHT_DOUBLE [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_DOUBLE_RIGHT;
Print(L"BOXDRAW_UP_DOUBLE_RIGHT [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_DOUBLE_UP_RIGHT;
Print(L"BOXDRAW_DOUBLE_UP_RIGHT [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_LEFT_DOUBLE;
Print(L"BOXDRAW_UP_LEFT_DOUBLE [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_UP_DOUBLE_LEFT;
Print(L"BOXDRAW_UP_DOUBLE_LEFT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_DOUBLE_UP_LEFT;
Print(L"BOXDRAW_DOUBLE_UP_LEFT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_VERTICAL_RIGHT_DOUBLE;
Print(L"BOXDRAW_VERTICAL_RIGHT_DOUBLE [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_RIGHT;
Print(L"BOXDRAW_VERTICAL_DOUBLE_RIGHT [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_RIGHT;
Print(L"BOXDRAW_DOUBLE_VERTICAL_RIGHT [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_VERTICAL_LEFT_DOUBLE;
Print(L"BOXDRAW_VERTICAL_LEFT_DOUBLE [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_LEFT;
Print(L"BOXDRAW_VERTICAL_DOUBLE_LEFT [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_LEFT;
Print(L"BOXDRAW_DOUBLE_VERTICAL_LEFT [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOWN_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_DOWN_HORIZONTAL_DOUBLE [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_DOWN_DOUBLE_HORIZONTAL [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_DOWN_HORIZONTAL [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_UP_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_UP_HORIZONTAL_DOUBLE [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_UP_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_UP_DOUBLE_HORIZONTAL [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOUBLE_UP_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_UP_HORIZONTAL [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_VERTICAL_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_VERTICAL_HORIZONTAL_DOUBLE [%s]\n",ChrSide); 
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_VERTICAL_DOUBLE_HORIZONTAL [%s]\n",ChrSide); 
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_VERTICAL_HORIZONTAL [%s]\n",ChrSide); 
ChrSide[0]=BLOCKELEMENT_FULL_BLOCK;
Print(L"BLOCKELEMENT_FULL_BLOCK [%s]\n",ChrSide);            
ChrSide[0]=BLOCKELEMENT_LIGHT_SHADE;
Print(L"BLOCKELEMENT_LIGHT_SHADE [%s]\n",ChrSide);           
ChrSide[0]=GEOMETRICSHAPE_UP_TRIANGLE;
Print(L"GEOMETRICSHAPE_UP_TRIANGLE [%s]\n",ChrSide);         
ChrSide[0]=GEOMETRICSHAPE_RIGHT_TRIANGLE;
Print(L"GEOMETRICSHAPE_RIGHT_TRIANGLE [%s]\n",ChrSide);      
ChrSide[0]=GEOMETRICSHAPE_DOWN_TRIANGLE;
Print(L"GEOMETRICSHAPE_DOWN_TRIANGLE [%s]\n",ChrSide);       
ChrSide[0]=GEOMETRICSHAPE_LEFT_TRIANGLE;
Print(L"GEOMETRICSHAPE_LEFT_TRIANGLE [%s]\n",ChrSide);       
ChrSide[0]=ARROW_LEFT;
Print(L"ARROW_LEFT [%s]\n",ChrSide);                         
ChrSide[0]=ARROW_UP;
Print(L"ARROW_UP [%s]\n",ChrSide);                           
ChrSide[0]=ARROW_RIGHT;
Print(L"ARROW_RIGHT [%s]\n",ChrSide);                        
ChrSide[0]=ARROW_DOWN;
Print(L"ARROW_DOWN [%s]\n",ChrSide);                         

  return EFI_SUCCESS;
}

 

上述代码运行结果:

tab4

tab3

tab2

tab1

完整的代码下载:

tabtest

使用上面的代码,绘制的一个方框:

tab5

参考:
1. https://en.wikipedia.org/wiki/Box-drawing_character

给Arduino 设计一个序列号

之前看到很多朋友询问过如何实现“序列号”的功能。就是对于每一个 Arduino 都有独立的编码,这样可以实现一些身份识别之类的功能。
最容易实现的就是每次都修改程序中的定义。但是这样用起来耗时很长,并且必须有源程序才行,琢磨了一下,提出一种软件的实现方法,欢迎大家一起讨论。
本文是纯软件的序列号实现,因此通用性会很好,这里使用 Arduino Uno 来实现。
第一个问题是:如何在命令行下进行刷写。首先将IDE中的上传调试功能打开

image001

这样你可以看到IDE具体使用了什么上传命令(Arduino IDE 只是一个外壳,具体的编译和上传是由其他程序实现的。IDE只是调用和重定向了运行的结果)

image002

具体的话,上面图片中的烧写代码如下:

C:\Users\labz\Documents\arduino\hardware\tools\avr/bin/avrdude -CC:\Users\ labz \Documents\arduino\hardware\tools\avr/etc/avrdude.conf -v -patmega328p -carduino -PCOM18 -b115200 -D -Uflash:w:C:\Users\ labz \AppData\Local\Temp\build3ee88a1b99cef46fdbee27900bbb7d73.tmp/Blink.ino.hex:i

知道了这一行命令,我们可以直接在CMD窗口中输入这个命令从而完成上传的动作。
第二步,直接修改生成的 HEX文件。这里制作一个带有序列号的DEMO。我们定义程序的序列号为 20170101,从串口输出这个编号以便检查,代码如下:

String sign="LABZ";
String number="20170101";

// the setup function runs once when you press reset or power the board
void setup() {
  Serial.begin(9600);
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
  Serial.print(number);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(2000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
}

 

运行结果如下
image003

我们的目标就是直接用工具修改这个序列号,然后上传到 Arduino 上。同样是根据前面的命令行,我们可以知道上传的HEX文件位置:
image004

用文本编辑软件即可打开这个文件,搜索 4C 41 42 5A (ASCII:LABZ, 这就是为什么我们要在代码中定义String Sign,它能帮助我们定位后面的序列号的位置。其实不用这个作为定位手段直接搜索代码中定义的序列号也是可以的)。

仔细查看,后面的数据 32 30 31 37 30 36 就是我们的序列号 “2017”。之后又遇到的问题是,这个HEX文件是有简单的格式的。根据【参考1】,我们可以得知每行最后一个数值是这一行的校验和。例如

:100CE400FB000F019B014C41425A00323031373036
image005

其中的最后一个 36 是校验和计算方法如下:

10+0C+E4+00+FB+00+0F+01+9B+01+4C+41+42+5A+00+32+30+31+37+30=0x4CA 校验和的意思是加上一个值之后最后2位会变成00,0xCA+0x36=0x100

假设,我们的目标是将序列号修改为 19731023 = 30 39 37 33 31 30 32 33

经过分析,我们修改如下
:100CE400FB000F019B014C41425A0031393733312B
:040CF4003032330067
其中最后的校验和是通过前面的数值计算得到的。当然,更简单的办法是根据第一步,直接烧写,烧录程序会提醒你的校验和不匹配。例如,我使用错误的检验和,0x36 在烧写的时候收到提示,当前计算结果为 0x2B:

image006

修改完成之后,再次查看,序列号就是我新定义的了。
image007

可以看出,使用手工修改还是比较麻烦的,但是掌握了方法,编写自动化的工具也就是分分钟的事情了。
参考:
1. http://www.manysj.com/thread-13501-1-1.html HEX文件格式详解

HSTI 的一个问题

这一切的起因是 MS WHQL测试的要求,为了确保 Connected Standby 功能,需要验证 secure 方面的设定(我目前还很难想象二者之间的关系,也许是在一睡一醒之间会放松警惕,所以MS要求我们特别注意?)

这个测试被称作System.Fundamentals.Firmware.CS.UEFISecureBoot.Provisioning 【参考1】。

为了完成这个测试 MS (我相信这种事情肯定不止MS一个人干的)引入新功能: HSTI Hardware Security Test Interface。

关于这个功能,在【参考2】有如下解释:
“HSTI helps avoid misconfiguration of security features on devices running Windows. Thus, HSTI provides better assurance of compliance with Windows Hardware Security Requirements. HSTI aims to simplify the interface for designing tests to ensure compliance, reducing the effort required to comply with Windows Hardware Security Requirements. The results of HSTI tests will be consumed by Windows Certification Tests and can be used to verify that devices have been properly configured to enable supported security features. These tests may be used to identify unsecure engineering devices in the field; for example, engineering devices which may contain unsecure test keys. The results of these tests may be used by the Windows operating system to display a watermark (or similar indicator) on unsecured devices. The IHV will develop reference security designs for their platforms that comply with the Windows Compatibility Requirements. In addition, IHVs and IBVs will also implement programmatic tests that verify proper enablement of the reference security implementations and report the results via the Hardware Security Test Interface. These tests are delivered to OEMs & ODMs as compiled modules (not source) and should work without modification. If an OEM/ODM deviates from reference security designs, these test modules may report failures, and the OEM/ODM will need to contact Microsoft to review the modifications and implement an additional HSTI instance that reports these exceptions. OEMs should be able to leverage these security modules with no modification required by following the reference design and documentation. OEMs who wish to add additional security modules, or modify the behavior of any security module, must undergo a design review with Microsoft. Silicon suppliers and IBVs who support Connected Standby systems must implement the platform independent interfaces for querying the respective hardware and firmware security states of their reference platforms. These implementations must be delivered as compiled modules. It is recommended that these modules be signed, and that a signature check is performed when they are run. The purpose is to query the hardware and firmware designs and states to report proper security provisioning of the platform. If an OEM wants to provide an alternate implementation of HSTI tested security features the OEM may provide additional tests. OEM security checks must at least fully cover one IHV or IBV security test. Before use, OEMs must submit to a design review by Microsoft and are subject to the same Documentation and Tool disclosure requirements as other HSTI test providers. Upon approval from Microsoft, the OEM may include security tests that extend upon the IHV and IBV tests. Note that OEM attestation is not required as part of the HSTI design. HSTI is not a list of requirements for OEMs; it is an interface to guarantee effective programmatic security testing of firmware, hardware, and configuration parameters. Silicon and firmware suppliers should make available to Microsoft all necessary security-related reference documentation and tools that they provide to OEMs. This documentation and tools should be made available no later than they are provided to Windows OEMs. This should include, but is not limited to, all documentation and tools related to fusing, installing and updating firmware, firmware and boot recovery, hardware diagnostics, firmware diagnostics, & boot diagnostics. This documentation and tools provided should be fully sufficient to perform HSTI checks in a lab environment.”

相信各位和我一样,看完了之后依然一头雾水。

根据我的理解,具体动作如下:BIOS 自己检查安全项目,将结果存放在内存中 HSTI Table,这也是双方协商好确定的 Windows 接口,通过它,Windows可以得知当前系统上的安全设定,比如Boot Guard 是否打开,SPI 是否已经设置保护等等。对于WHQL 来说只是读取这个Table而已。

实例:我拿到的测试报告有下面的Fail项目:

Error 0x00060002 Platform Security Specification – CPU Security Configuration – Pre-production silicon in use

查看代码,是可以在你的 codebase 中找到这个字符串的定义的,比如:

#define LABZ_ERROR_CODE_2 L”0x00060002″
#define LABZ_ERROR_STRING_2 L” Pre-production silicon in use\r\n”

然后再检查引用这个字符串的代码:

DEBUG ((DEBUG_INFO, ” 2. Sample Part \n”));

if ((ProcessorReadMsr64 (CpuX, MSR_LABZ_INFO) & BIT21) != 0) {
DEBUG ((DEBUG_INFO, “Fail: This is a sample part\n”));
HstiErrorString = BuildHstiErrorString (LABZ_ERROR_CODE_2 ,HSTI_CPU_SECURITY_CONFIGURATION, LABZ_ERROR_STRING_2 );
Result = FALSE;
FreePool (HstiErrorString);
}

就是说,BIOS 根据ProcessorReadMsr64 (CpuX, MSR_LABZ_INFO) & BIT21!=0这个条件判断当前系统是否为 Sample。如果是的话,就将这个ERROR 记录下来存放在HSTI 中。

原理就是这样,如果单纯的为了通过测试,可以去修改BIOS,修改自检的动作。比如,修改上面的条件,让BIOS不去记录这个错误从而通过WHQL。特别注意,一个错误可能是很多不同条件检查出来的结果。比如,检查 EC access, CSME access 的时候都会报告下面这个错误

Error 0x0001000A Platform Security Specification – SPI Flash Configuration – SPI Region Access Rights Invalid

下面再介绍如何检查修改是否生效:

1. 最最稳妥的就是拿去重做WHQL测试;
2. 如果细心的话,会注意到上面的代码是有Debug信息输出的,可以对照UART输出的DEBUG信息检查,如果修改后,如果没有这个信息,那么就是正确的了。推荐使用这个方法,查看Log对于定位错误的位置非常有帮助;
3. 需要检查你的codebase是否为最新版,很多检查寄存器的值并非不正确,只是你手中的版本没有包含这个值而已;
4. 因为HSTI是UEFI 定义的结构,还可以在UEFI 下面进行读取。这里推荐jyao1 编写的一个工具【参考3】,能够在Shell下展示这个结构体的信息。我重新在UDK2015上编译了一次,非常好用,这里送上代码和X64的EFI。
5. 如果有问题,直接问Intel。 Insyde 对此不清楚。

上面提到的完整代码和X64的EFI 程序下载 hstiwsmtdump

就是这样,祝好运。

参考:
1. https://msdn.microsoft.com/en-us/library/windows/hardware/mt712332.aspx
2. https://firmwaresecurity.com/2015/08/03/microsoft-windows-hsti-hardware-security-test-interface/ 这里提到的出处是 MSDN,但是原始界面已经不复存在了
3. https://github.com/jyao1/EdkiiShellTool/tree/master/EdkiiShellToolPkg

特别声明:本文是一篇小说,不可作为技术指导以及参考,非专业人士请不要用来检测使用的机器。

Arduino I2C GPIO模块

Arduino Uno 大概是用途最广泛的型号了,美中不足的是它的GPIO 有限,这里介绍一个GPIO的扩展模块。基于 PCA9555芯片的 GPIO模块。
image002
简单的说,这个模块是将I2C转换为 GPIO,一共支持16个GPIO。板子上通过跳线的方式可以选择当前的地址0x20-0x27一共8个,理论上可以级联出来8(个模块)*16(个GPIO)=128个GPIO。

image004
特地我去查了一下电气特性【参考1】:
电源支持2.3到5.5v(正常工作范围),IO 输入5V,支持每一个输出最大50mA,输入时每个最大20mA。电源供电最大输入 160mA(这意味着不能同时有3以上的 GPIO输出50mA,在使用的时候一定要特别注意,最好把它当作只能提供电平的控制模块)。
简单实验这个模块,用4个LED,负极通过一个1K电阻接地,虽然这样会让LED看起来很暗,但是我能够确定不会有过流的问题。然后用扩展版上的 P0.6,P0.7 ,P1.6和P1.7连接LED正极进行供电控制。

运行的代码来自 DFRobot【参考2】(看起来他们在很久之前做过一块类似的板子,但是目前已经停产了)。当I2C地址选择三个Pin都为0时,模块地址是0x20。另外,I2C访问部分,我修改为Wire.write 之前的Wire.send 函数已经取消了。

/****************************************************************************** 
Test Program for the 12C PCA9555 Board part number DFR0013 IIC TO GPIO module from dfrobot.com
16 outputs that I used to drive this relay board made in Bulgaria
http://www.denkovi.com/product/21/16-relay-board-for-your-pic-avr-project-12v.html
it's a great little expansion board that can be used to drive LEDs or anything you want.
made by peter@testelectronics.com
January 07th 2011
My biggest problem was figuring out the I2C address of the PCA9555.
If there are no jumpers the address is 1 0 0 '1 1 1'
Jumpers make the address 1 0 0 '0 0 0'. This is opposite of what I expected.
******************************************************************************/ 

#include <Wire.h> 

//  with no jumpers the full address is   1 0 0 1 1 1    1 0 0 A2 A1 A0  0x27 is the default address for the DFR0013 board with no jumpers.
#define PCA9555 0x20 // 0x27 is default address for the DFR0013 board with no jumpers.
                     // 0x20 is address for the DFR0013 board with all jumpers.
// COMMAND BYTE TO REGISTER RELATIONSHIP FROM PCA9555 DATA SHEET
// At reset, the device's ports are inputs with a high value resistor pull-ups to VDD
// If relays turning on during power up are a problem. Add a pull down resistor to each relay transistor base.

#define IN_P0 0x00 // Read Input port0
#define IN_P1 0x01 // Read Input port1
#define OUT_P0 0x02 // Write Output port0
#define OUT_P1 0x03 // Write Output port1
#define INV_P0 0x04 // Input Port Polarity Inversion port0 if B11111111 is written input polarity is inverted
#define INV_P1 0x05 // Input Port Polarity Inversion port1 if B11111111 is written input polarity is inverted
#define CONFIG_P0 0x06 // Configuration port0 configures the direction of the I/O pins 0 is output 1 is input
#define CONFIG_P1 0x07 // Configuration port1 configures the direction of the I/O pins 0 is output 1 is input

#define PAUSE 200

void setup()
{
  Wire.begin(PCA9555); // join i2c bus (address optional for master) tried to get working
  write_io (CONFIG_P0, B00000000); //defines all pins on Port0 are outputs
  write_io (CONFIG_P1, B00000000); //defines all pins on Port1 are outputs  
  write_io (OUT_P0, B00000000); //clears all relays
  write_io (OUT_P1, B00000000); //clears all relays
  delay (PAUSE);
}


void loop()
{
  write_io (OUT_P0, B00000001);
  delay (PAUSE);
  write_io (OUT_P0, B00000010);
  delay (PAUSE);
  write_io (OUT_P0, B00000100);
  delay (PAUSE);
  write_io (OUT_P0, B00001000);
  delay (PAUSE);
  write_io (OUT_P0, B00010000);
  delay (PAUSE);
  write_io (OUT_P0, B00100000);
  delay (PAUSE);
  write_io (OUT_P0, B01000000);
  delay (PAUSE);
  write_io (OUT_P0, B10000000);
  delay (PAUSE);
  write_io (OUT_P0, B00000000);
 
  
  write_io (OUT_P1, B00000001);
  delay (PAUSE);
  write_io (OUT_P1, B00000010);
  delay (PAUSE);
  write_io (OUT_P1, B00000100);
  delay (PAUSE);
  write_io (OUT_P1, B00001000);
  delay (PAUSE);
  write_io (OUT_P1, B00010000);
  delay (PAUSE);
  write_io (OUT_P1, B00100000);
  delay (PAUSE);
  write_io (OUT_P1, B01000000);
  delay (PAUSE);
  write_io (OUT_P1, B10000000);
  delay (PAUSE);
  write_io (OUT_P1, B00000000);
  
}
 
 
 void write_io(int command, int value)
{
  Wire.beginTransmission(PCA9555);
  Wire.write(command),Wire.write(value);
  Wire.endTransmission();
}

 

最终工作的样子:
image006

最后补充一些:
1. 这个模块在淘宝价格20左右,DFRobot新的替代品的价格在65左右;
2. 模块做工不错,看起来挺舒服,应该是机器焊接的(老婆偶然看到,竟然觉得挺精致)
3. Github 上有人提供了一个库,有兴趣的朋友可以试试。如果能够简化编程,都值得实验 https://github.com/nicoverduin/PCA9555

参考:
1. http://www.nxp.com/documents/data_sheet/PCA9555.pdf
2. http://wiki.dfrobot.com.cn/index.php/(SKU:DFR0013)IIC_TO_GPIO%E6%A8%A1%E5%9D%97