最近有朋友询问 Shell 下编写 NSF 批处理的问题. 后来经过研究,他使用的是 AMI 自带的 Shell,在批处理方面存在一些问题,没有办法正确解析 IF 语句。
我在模拟器中确认过,同样的版本(2.50) UDK2015 自带的 SHELL 不存在这样的问题。
最后,他从U盘启动我给他的 SHELL 同样也没有问题,因此判定是 AMI 自带的 Shell 有一些特别的地方。
如果你的批处理遇到类似的状况,不妨尝试一下 UDK 自带的 Shell.
最近有朋友询问 Shell 下编写 NSF 批处理的问题. 后来经过研究,他使用的是 AMI 自带的 Shell,在批处理方面存在一些问题,没有办法正确解析 IF 语句。
我在模拟器中确认过,同样的版本(2.50) UDK2015 自带的 SHELL 不存在这样的问题。
最后,他从U盘启动我给他的 SHELL 同样也没有问题,因此判定是 AMI 自带的 Shell 有一些特别的地方。
如果你的批处理遇到类似的状况,不妨尝试一下 UDK 自带的 Shell.
有朋友留言我才想起来很早之前介绍过 zLib 的编译,但是没有编写一个完整的例子。这次补上这一块。
首先需要安装好zLib(现在都是在 UDK2015下面进行编译),我在 AppPkg.dsc 中加入下面的语句:
CacheMaintenanceLib|MdePkg/Library/BaseCacheMaintenanceLib/BaseCacheMaintenanceLib.inf UefiHandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf zLib|AppPkg/Applications/zsource/zlib.inf ################################################################################################### # # Components Section - list of the modules and components that will be processed by compilation # tools and the EDK II tools to generate PE32/PE32+/Coff image files.
之后就可以直接使用了。
压缩很简单,用下面这个函数即可
int compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen);
特别需要注意的是,压缩是在内存中进行的,所以需要开辟一段用来存放压缩结果的空间,这个空间的大小也就是上面的destLen。因此,在运行这个函数之前最好线运行一下预测压缩之后大小的函数:
uLong compressBound (uLong sourceLen);
压缩之后的结果不会超过这个函数返回值(一般情况下要小得多)
压缩很简单,下面介绍解压缩:
int uncompress (Bytef *dest, uLongf *destLen,const Bytef *source, uLong sourceLen);
和压缩函数非常类似,但是有一点特别注意的:这个函数用到的 destLen 是解压之后的大小,但是zLib没有提供预测解压之后大小的函数。据说原因是因为解压缩是压缩的“反函数”,因此,在压缩的时候应该已经知道解压后的大小。具体在使用的时候需要想办法自己定义结构体之类的将原始大小传递给解压函数以便开辟内存空间。譬如,在存放压缩的buffer前面加上4字节的大小之类的。
基本概念了解完就上代码,例子实现的功能是将 zlibtest.efi 压缩为test.lbz。然后再解压保存为 test.efi。
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Library/MemoryAllocationLib.h> #include <stdio.h> #include <stdlib.h> #include <wchar.h> #include <Library/ShellLib.h> #include "zlib.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 ) { EFI_FILE_HANDLE FileHandle; RETURN_STATUS Status; EFI_FILE_INFO *FileInfo = NULL; UINT32 SourceSize; UINT32 preSize; CHAR8 *source=NULL; CHAR8 *dest=NULL; //Read a source file //Open the file given by the parameter Status = ShellOpenFileByName( L"zlibtest.efi", (SHELL_FILE_HANDLE *)&FileHandle, EFI_FILE_MODE_READ , 0); if(Status != RETURN_SUCCESS) { Print(L"OpenFile failed!\n"); return EFI_SUCCESS; } //Get file size FileInfo = ShellGetFileInfo((SHELL_FILE_HANDLE)FileHandle); //Allocate a memory buffer source = AllocateZeroPool((UINTN) FileInfo-> FileSize); if (source == NULL) { Print(L"Allocate source memory error!\n"); return (SHELL_OUT_OF_RESOURCES); } SourceSize = (UINT32) (FileInfo-> FileSize & 0xFFFFFFFF); //Load the whole file to the buffer Status = ShellReadFile(FileHandle,&SourceSize,source); if (EFI_ERROR(Status)) { Print(L"Read file error [%r]\n",Status); ShellCloseFile(&FileHandle); return (EFI_SUCCESS); } ShellCloseFile(&FileHandle); //Compress preSize = compressBound((UINT32)SourceSize); Print(L"Source file size : %d\n",SourceSize); Print(L"Compress Bound Size: %d\n",preSize); dest = AllocateZeroPool(preSize); if (dest == NULL) { Print(L"Allocate dest memory error!\n"); return (SHELL_OUT_OF_RESOURCES); } //Output Print(L"Compressing.........\n"); Status=compress(dest,&preSize,source,SourceSize); if (Status != Z_OK) { Print(L"Compress error!\n"); return (SHELL_OUT_OF_RESOURCES); } Print(L"Compressing complete!\n"); Print(L"Compressed size: %d\n",preSize); //Save compressed result to a file //Create a new file Status = ShellOpenFileByName(L"test.lbz", (SHELL_FILE_HANDLE *)&FileHandle, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE| EFI_FILE_MODE_CREATE, 0); if(Status != RETURN_SUCCESS) { Print(L"CreatFile failed [%r]!\n",Status); return EFI_SUCCESS; } Status = ShellWriteFile(FileHandle, &preSize, dest ); //Close the source file ShellCloseFile(&FileHandle); //This program is just a demo for showing how to use zlib. //Uncompress test Status=uncompress(source,&SourceSize,dest,preSize); if (Status != Z_OK) { Print(L"Decompress error!\n"); return (SHELL_OUT_OF_RESOURCES); } //Output Print(L"decompress Size: %d\n",SourceSize); //Create a new file Status = ShellOpenFileByName(L"test.efi", (SHELL_FILE_HANDLE *)&FileHandle, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE| EFI_FILE_MODE_CREATE, 0); if(Status != RETURN_SUCCESS) { Print(L"CreatFile failed [%r]!\n",Status); return EFI_SUCCESS; } Status = ShellWriteFile(FileHandle, &SourceSize, source ); //Close the source file ShellCloseFile(&FileHandle); FreePool(dest); FreePool(source); return EFI_SUCCESS; }
运行结果:
我们直接比较压缩前和解压后的结果,是相同的。
完整的代码下载:
参考:
1. http://www.cppblog.com/woaidongmao/archive/2009/09/07/95495.html zlib用法简单说明
最近编写程序,需要用到 CHAR16 的大小写转换,忽然惊奇的发现远比CHAR8的转化复杂,没有可以直接使用的函数。研究了一番,猜测产生这样问题的原因是 CHAR16 是定义给 Unicode 使用的。而Unicode 需要支持多种语言格式,比方说:’a’的大写是’A’,‘一’的大写就是‘壹’了,所以处理上也是复杂的。
从 UEFI Spec 来看,标准的做法要求使用EFI_UNICODE_COLLATION_PROTOCOL 。对应的头文件在 \MdePkg\Include\Protocol\UnicodeCollation.h 中。有下面几个功能:
/// /// The EFI_UNICODE_COLLATION_PROTOCOL is used to perform case-insensitive /// comparisons of strings. /// struct _EFI_UNICODE_COLLATION_PROTOCOL { EFI_UNICODE_COLLATION_STRICOLL StriColl; EFI_UNICODE_COLLATION_METAIMATCH MetaiMatch; EFI_UNICODE_COLLATION_STRLWR StrLwr; EFI_UNICODE_COLLATION_STRUPR StrUpr; // // for supporting fat volumes // EFI_UNICODE_COLLATION_FATTOSTR FatToStr; EFI_UNICODE_COLLATION_STRTOFAT StrToFat; /// /// A Null-terminated ASCII string array that contains one or more language codes. /// When this field is used for UnicodeCollation2, it is specified in RFC 4646 format. /// When it is used for UnicodeCollation, it is specified in ISO 639-2 format. /// CHAR8 *SupportedLanguages; };
StriColl 进行大小写敏感的字符串比较
MetaiMatch 判断字符串是否匹配一个带有通配符的模版
StrLwr 字符串转为小写
StrUpr 字符串转为大写
FatToStr 将8.3格式的OEM字符集的FAT文件名转为字符串
StrToFat 将字符串转为OEM定义的字符集
*SupportedLanguages ASCII给出的语言的列表
上述只是简单的介绍,我也并没有仔细验证,更详细的请阅读UEFI Spec Protocols – String Services 章节。
除了,上面介绍的方法,如果你确定字符串中只有 ASCII , 那么还可以直接处理字符串中的 ASCII ,下面的实例演示了上面的2种做法:
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <Protocol/UnicodeCollation.h> extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; extern EFI_RUNTIME_SERVICES *gRT; EFI_UNICODE_COLLATION_PROTOCOL *mUnicodeCollation = NULL; VOID EFIAPI lowercase ( IN OUT CHAR16 *Str ) { UINT32 i; CHAR8 *p; p=(CHAR8 *)Str; for (i=0;i<StrSize(Str);i++) { //Print(L"%d ",*p); if ((*p>='A')&&(*p<='Z')) { *p=*p+32; } p++; } } int EFIAPI main ( IN int Argc, IN CHAR16 **Argv ) { EFI_STATUS Status; CHAR16 *s1=L"Www.Lab-z.Com 1243"; CHAR16 *s2=L"Www.Lab-z.Com 1243"; if (mUnicodeCollation == NULL) { Status = gBS->LocateProtocol( &gEfiUnicodeCollation2ProtocolGuid, NULL, (VOID**)&mUnicodeCollation); if (EFI_ERROR(Status)) { Print(L"Can't find UnicodeCollation2 Protocol!\n"); return EFI_SUCCESS; } } mUnicodeCollation->StrUpr( mUnicodeCollation, s1); Print(L"%s\n",s1); lowercase (s2); Print(L"%s\n",s2); return EFI_SUCCESS; }
运行结果:
完整的代码下载
很多时候,我们的程序需要和用户进行简单的交互,当然可以设计菜单之类的,但是从代码的角度来说依然很复杂,我们并不希望为了获得一个确认而大费周章。这时候,可以直接使用 Shell 提供的ShellPromptForResponse 函数。
函数原型在ShellLib.h 中:
/** Prompt the user and return the resultant answer to the requestor. This function will display the requested question on the shell prompt and then wait for an apropriate answer to be input from the console. If the SHELL_PROMPT_REQUEST_TYPE is SHELL_PROMPT_REQUEST_TYPE_YESNO, ShellPromptResponseTypeQuitContinue or SHELL_PROMPT_REQUEST_TYPE_YESNOCANCEL then *Response is of type SHELL_PROMPT_RESPONSE. If the SHELL_PROMPT_REQUEST_TYPE is ShellPromptResponseTypeFreeform then *Response is of type CHAR16*. In either case *Response must be callee freed if Response was not NULL; @param Type What type of question is asked. This is used to filter the input to prevent invalid answers to question. @param Prompt The pointer to a string prompt used to request input. @param Response The pointer to Response, which will be populated upon return. @retval EFI_SUCCESS The operation was successful. @retval EFI_UNSUPPORTED The operation is not supported as requested. @retval EFI_INVALID_PARAMETER A parameter was invalid. @return other The operation failed. **/ EFI_STATUS EFIAPI ShellPromptForResponse ( IN SHELL_PROMPT_REQUEST_TYPE Type, IN CHAR16 *Prompt OPTIONAL, IN OUT VOID **Response OPTIONAL );
简单的说这个函数提供了3种用法:
1. 使用ShellPromptResponseTypeFreeform 参数,接收全部输入的字符串;
2. 使用ShellPromptResponseTypeYesNo 这样,用户输入 y 表示 Yes, n 表示 No
3. 使用ShellPromptResponseTypeEnterContinue 进行按键等待的
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include "ShellLib.h" extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; extern EFI_RUNTIME_SERVICES *gRT; SHELL_PROMPT_RESPONSE EFIAPI Resp() { } int EFIAPI main ( IN int Argc, IN CHAR16 **Argv ) { CHAR16 *InputStr = NULL; ShellPromptForResponse( ShellPromptResponseTypeFreeform, L"Please input->", (VOID**)&InputStr); Print(L"Get input by freeform %s\n",InputStr); ShellPromptForResponse( ShellPromptResponseTypeEnterContinue, L"Press Enter to continue...", NULL); ShellPromptForResponse( ShellPromptResponseTypeAnyKeyContinue, L"Press any key to continue...", NULL); SHELL_PROMPT_RESPONSE *Resp=NULL; ShellPromptForResponse(ShellPromptResponseTypeYesNo, L"Type 'y' or 'n'->",(VOID**)&Resp); switch (*(SHELL_PROMPT_RESPONSE*)Resp) { case ShellPromptResponseNo: Print(L"Choose No\n"); break; case ShellPromptResponseYes: Print(L"Choose Yes\n"); break; default: break; } return EFI_SUCCESS; }
运行结果:
可以看到,这个函数还可以用来实现回车键继续或者任意键继续的功能。
关于这个函数,具体的实现代码可以在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c 中看到,有兴趣的朋友可以深入研究一下。
完整的代码下载
InputTest
前面提到过在编写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()
下载:
参考:
1. https://github.com/simonlian/guidref/blob/master/guidxref.py
之前的文章提到过,我注意到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。 得到的结果如下,可以看到,盘符发生了变化:
我们再用十六进制工具打开 shell.efi ,搜索 fsz (特别注意这是unicode不是 ascii)
因此,盘符的前缀就是在这里生成的。
然后我们再返回到 UDK2014中,打开模拟器运行结果如下:
这就是我们一直疑惑的 FsntX: 直接使用十六进制编辑工具打开 Shell_Full.efi ,搜索 Fsnt 。找到之后我们修改为 Fsnz,再保存,重新build Nt32,然后再次运行编译器,结果就发生了变化:
于是,可以解释我们之前的疑惑了:他们 Publish 的 Shell.efi 和我们拿到的并非一套代码,一些细节存在差别。
本文只相当于定性的分析,后面我们还会进行更详细的分析,到底盘符是如何来的,就是这样。
参考:
1. http://www.lab-z.com/how2buildshell/ How to build Shell.efi
最近在查看资料【参考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占空比;”
正好,手上有示波器,随手测量一下,发现效果不错
参考:
1.http://www.arduino.cn/thread-42007-1-1.html 【Arduino101教程】定时器的使用
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。可以看到都是支持的。
下图是BIOS中关闭DEVSLP的截图,可以对照上面查看。
下面是对这块硬盘(Samsung 750 EVO )的简单性能测试:
如果你是专门为 Connected Standby 选购硬盘的话,个人建议最好入手 Intel SSD最保险。
参考:
1. http://www.storageinterface.com/articles/12-sata-devslp
2. http://www.texim.jp/txbenchus.html
最近在尝试编写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 平台上试验过,工作也是正常的。
最后,如果真要很完美的话,应该再加入更多的提示信息,比如,对于U盘,要显示名称序列号之类的,这样才便于用户区分当前的操作对象。有兴趣的朋友,可以参考前面几篇文章,加入这个功能。
完整的代码和X64下Application的下载:
对于 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 有下面的定义:
同样的文档中我们可以查到 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); }