UEFI Tips 用按键做Pause

很多年前,我去AMI学习,偶然间看到他们在代码中加入通过 60/61 Port来读取键盘按键信息实现一个按需Delay ,深以为意。今天偶然间想起来,在调试Application 的时候,配合屏幕输出也可以用这样的方式来进行Debug。

下面是一个例子:

#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;

#define SCAN_NULL       0x0000
#define SCAN_UP         0x0001
#define SCAN_DOWN       0x0002
#define SCAN_ESC        0x0017

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_INPUT_KEY	Key;
  EFI_STATUS		Status;  
  gST->ConOut->OutputString(gST->ConOut,L"Test Starting.....\n\r");
  gST->ConOut->OutputString(gST->ConOut,L"Waiting for UP_KEY\n\r");
  
  Key.ScanCode=SCAN_NULL;
  while (SCAN_UP!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
	}
  
  gST->ConOut->OutputString(gST->ConOut,L"Waiting for DOWN_KEY\n\r");	
  Key.ScanCode=SCAN_NULL;
  while (SCAN_DOWN!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
	}
  return EFI_SUCCESS;
}

 

通过按光标向上和向下继续运行,运行结果:

keypasue

完整文件下载

KeyPause

如何在Processing中导入Opencv库

国内玩 Processing 的就很少,玩 OpenCv的更少,有人问到了这里我抽空研究了一下。

首先,要下载OpenCv for Processing,官方网站是https://github.com/atduskgreg/opencv-processing。我是在这个页面下载的https://github.com/atduskgreg/opencv-processing/releases 0.5.2 的版本。
下载之后直接打开是这样的:

image001

然后,还是和之前文章【参考1】提到的安装方式相同,检查 Preferences 设定,特别提醒,目录不可以有空格或者中文。

image003

之后,打开d:\prcdir\libraries 目录,把前面的全部内容都丢进去

image005
再次打开 processing ,library目录下出现 OpenCv 即正确。

image007

可以直接运行这个库自带的各种例子。
我上传了本文提到的库到 baidu云上,有需要的朋友可以下载,如果有问题也可以直接给我发eMail。
链接:http://pan.baidu.com/s/1jHSPV74 密码:hanc
参考:
1. http://www.lab-z.com/promodel/ Processing导入模型

Step to UEFI (84) ShellExecute 的使用

之前提到过,使用 LoadImage 和 StartImage 无法加载 CLib Build出来的 EFI Application。一种变通的方法是通过 ShellLib 下面的 ShellExcute 来调用其他的 EFI Application。

具体定义在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

**
  Cause the shell to parse and execute a command line.

  This function creates a nested instance of the shell and executes the specified
  command (CommandLine) with the specified environment (Environment). Upon return,
  the status code returned by the specified command is placed in StatusCode.
  If Environment is NULL, then the current environment is used and all changes made
  by the commands executed will be reflected in the current environment. If the
  Environment is non-NULL, then the changes made will be discarded.
  The CommandLine is executed from the current working directory on the current
  device.

  The EnvironmentVariables pararemeter is ignored in a pre-UEFI Shell 2.0
  environment.  The values pointed to by the parameters will be unchanged by the
  ShellExecute() function.  The Output parameter has no effect in a
  UEFI Shell 2.0 environment.

  @param[in] ParentHandle         The parent image starting the operation.
  @param[in] CommandLine          The pointer to a NULL terminated command line.
  @param[in] Output               True to display debug output.  False to hide it.
  @param[in] EnvironmentVariables Optional pointer to array of environment variables
                                  in the form "x=y".  If NULL, the current set is used.
  @param[out] Status              The status of the run command line.

  @retval EFI_SUCCESS             The operation completed sucessfully.  Status
                                  contains the status code returned.
  @retval EFI_INVALID_PARAMETER   A parameter contains an invalid value.
  @retval EFI_OUT_OF_RESOURCES    Out of resources.
  @retval EFI_UNSUPPORTED         The operation is not allowed.
**/
EFI_STATUS
EFIAPI
ShellExecute (
  IN EFI_HANDLE                 *ParentHandle,
  IN CHAR16                     *CommandLine OPTIONAL,
  IN BOOLEAN                    Output OPTIONAL,
  IN CHAR16                     **EnvironmentVariables OPTIONAL,
  OUT EFI_STATUS                *Status OPTIONAL
  )
{
  EFI_STATUS                CmdStatus;
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    //
    // Call UEFI Shell 2.0 version (not using Output parameter)
    //
    return (gEfiShellProtocol->Execute(ParentHandle,
                                      CommandLine,
                                      EnvironmentVariables,
                                      Status));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    //
    // Call EFI Shell version.
    // Due to oddity in the EFI shell we want to dereference the ParentHandle here
    //
    CmdStatus = (mEfiShellEnvironment2->Execute(*ParentHandle,
                                          CommandLine,
                                          Output));
    //
    // No Status output parameter so just use the returned status
    //
    if (Status != NULL) {
      *Status = CmdStatus;
    }
    //
    // If there was an error, we can't tell if it was from the command or from
    // the Execute() function, so we'll just assume the shell ran successfully
    // and the error came from the command.
    //
    return EFI_SUCCESS;
  }

  return (EFI_UNSUPPORTED);
}

 

调用参数如下:
ParentHandle 执行操作的父进程的Handle
CommandLine 要执行的命令行
Output 是否输出 Debug 信息(这里我没有搞明白,如果有清楚的朋友望不吝赐教)
EnvironmentVariables 环境变量

因为已经在头文件中定义过,所以我们可以直接调用。

比如用下面的方式可以执行 ls 命令:

Shell command
  CHAR16	  *S=L"ls";
  OpStat = ShellExecute( &MyHandle, S, FALSE, NULL, &CmdStat);

 

我们再编写一个简单的程序输出当前收到的命令行参数

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

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
	int	i;
	
	for (i=0;i<Argc; i++)	{
		Print(L"%S\n",Argv[i]);
	}
		
  return EFI_SUCCESS;
}

 

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Hello1
  FILE_GUID                      = 4ea97c46-7491-4dfd-0048-747010f3ce51
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  Hello1.c

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


[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib
  BaseLib
  BaseMemoryLib
  UefiLib

  
[Protocols]

  
[BuildOptions]

 

运行结果

se1

我们使用 ShellExecute 的代码

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

#include <Library/ShellLib.h>

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

extern EFI_SHELL_PROTOCOL            *gEfiShellProtocol;
extern EFI_SHELL_ENVIRONMENT2 		 *mEfiShellEnvironment2;
extern EFI_HANDLE 					 gImageHandle;

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS  CmdStat;
  EFI_STATUS  OpStat;
  EFI_HANDLE  MyHandle = gImageHandle;
  CHAR16	  *S=L"hello1.efi a1 b2 c3";
  
  OpStat = ShellExecute( &MyHandle, S, FALSE, NULL, &CmdStat);
  
  return EFI_SUCCESS;
}

 

最后运行结果
se2

可以看到,能够调用hello1.efi 并且正确的传递了参数。

本文提到的完整代码下载:

shellexecdemo

和技嘉的板子搏斗了一天

前年配了一个台式机,16G 内存 ,Intel I7 4790S的 CPU。当时装的是 GigaByte 的 G1 Sniper b5的板子。我的要求就是:稳定。 CPU 和 内存都是降频运行的。最近感觉硬盘不太稳定,又买了一个新的硬盘,不料安装系统给我折腾的够呛。

装系统需要重启,大约是因为我做的启动盘格式有问题,一直没有办法进去安装盘,我只得Reset重启。没想到这样几次之后,主板就开始抽风:启动,能看到光标,然后关机一直反复这个动作。折腾了很久都没有搞定。最后网上搜索到这块主板是双BIOS(Dual BIOS/Dual SPI),关机状态下长按Power Button开机,开机之后继续按住可以进入Recovery Mode…….此外还可以短接主板上一个 SPI NOR 的 Pin 1 和 Pin 8 强制进入,但是动硬件的话难免风险,按 Button 更简单稳妥一些。

经过无数次折腾,终于进入了这个 Mode,主板从另外一个 SPI NOR 读取出来数据覆盖到主 SPI 上才恢复。

看起来 GigaByte 主板在设计或者说测试阶段应该没有发现:当无法启动Leagcy OS后,主动关机会导致 BIOS 进入某种特殊状态的 Bug。万幸 Dual BIOS 功能做的还不错。

最后经过无数次的努力终于把系统装上,告一段落。

Step to UEFI (83) BlockIo Protocol

“因为硬盘是一种块设备,所以每个硬盘设备(硬盘设备包括分区设备)控制器都安装有一个 BlockIo 实例,一个 BlockIo2实例。BlockIo 提供了访问设备的阻塞函数,BlockIo2提供了访问设备的异步函数”【参考1】

blk2

blk1

这里提供一个枚举BlockIo,然后显示每一个 Media 属性的例子:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Protocol/BlockIo.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_STATUS				Status;
    UINTN					HandleCount,HandleIndex;
    EFI_HANDLE              *BlockControllerHandles = NULL;	
	EFI_BLOCK_IO_PROTOCOL   *BlockIo;
	
	//找到全部有 BlockIo Protocol 的Device
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiBlockIoProtocolGuid,
            NULL,
            &HandleCount,
            &BlockControllerHandles);  

   if (!EFI_ERROR(Status)) {
        //逐个打开 
        for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
            /*打开EFI_BLOCK_IO_PROTOCOL  */ 
            Status = gBS->HandleProtocol(
                    BlockControllerHandles[HandleIndex],
                    &gEfiBlockIoProtocolGuid,
                    (VOID**)&BlockIo);
			//显示信息		
			Print(L"-->[Device]<--:%d\n",HandleIndex);
			Print(L"MediaId       :%0x\n",BlockIo->Media->MediaId);
    		Print(L"RemovableMedia:%0x\n",BlockIo->Media->RemovableMedia);
    		Print(L"MediaPresent  :%0x\n",BlockIo->Media->MediaPresent);
    		Print(L"ReadOnly      :%0x\n",BlockIo->Media->ReadOnly); 
    		Print(L"WriteCaching  :%0x\n",BlockIo->Media->WriteCaching);
    		Print(L"BlockSize     :%0x\n",BlockIo->Media->BlockSize);
    		Print(L"IoAlign       :%0x\n",BlockIo->Media->IoAlign);
    		Print(L"LastBlock     :%0x\n",BlockIo->Media->LastBlock);
    		Print(L"LogicalPartition :%0x\n",BlockIo->Media->LogicalPartition);
    		Print(L"LowestAlignedLba :%0x\n",BlockIo->Media->LowestAlignedLba);
    		Print(L"LogicalBlocksPerPhysicalBlock   : %0x\n",
			BlockIo->Media->LogicalBlocksPerPhysicalBlock);
    		Print(L"OptimalTransferLengthGranularity: %0x\n",
			BlockIo->Media->OptimalTransferLengthGranularity);
        }	//for (HandleIndex = 0;
		
        gBS->FreePool(BlockControllerHandles);
    }			
  return EFI_SUCCESS;
}

 

在 Nt32 虚拟机中运行的结果

blockioa

完整的代码和程序下载:

BIOTest

参考
1. UEFI 原理与编程 P139

一个计算时间的批处理

有时候,我们需要简单的测量一下某个程序经过的时间,经过搜索在网上【参考1】,找到下面的批处理可以完成这个要求。

@echo off
set ns=0
rem Show start time
set time1=%time%
echo Start time is %time1%
call :time2sec %time1%
set t1=%ns%


rem Do what you want


rem Show end time
set time2=%time%
echo End time is ?%time2%
call :time2sec %time2%
set t2=%ns%
rem Calculate
set /a tdiff=%t2% - %t1%
echo diff %time1% from %time2% = %tdiff% seconds.
goto :eof

:time2sec
rem CDonvert time to seconds. Save to ns
set tt=%1
set hh=%tt:~0,2%
set mm=%tt:~3,2%
set ss=%tt:~6,2%
set /a ns=(%hh%*60+%mm%)*60+%ss%


goto :eof

 

比如,我用这个工具来测试Build BIOS 的时间:

CLean 之后,build 要花费5分钟
clean

再次编译,build只需要不到一分钟
noclean

参考:
1.http://zhidao.baidu.com/link?url=dL8oq_ik4tX5St4YkwQk4vz8HjYGIeFa6ybUs21BI9h6VdSdx7B7BpqdaiVIsNr8rKNlYoRJZ4rIGpxwRfCB3a

Step to UEFI (82) NT32Pkg的Debug Message

最近在查看EDKII代码的时候忽然有一个奇怪的想法:在运行模拟器的时候(Build Run),我们可以在编译窗口看到很多输出的Debug 信息,那么我们是否可以在自己编写的Application中输出这样的信息?

最直接的想法是在 Application中调用 gWinNt ,但是如果要用这个东西,需要定义很多文件头,最麻烦的是这些头文件最后都要使用 Windows.h ,编译的时候总是无法通过。另外的方法是,在 Application 中调用诸如 WinNtThunkDxe 或者 WinNtSerialIoDxe 这样的Protocol,但是在编译使同样会遇到有上面的问题。我尝试了很多次都没有成功,最后只好放弃。

换一种思路,我们可以在NT32Pkg中留下可供调用的函数,然后在 Application 中Call这个函数。

最简单的功能就是用于系统重启的 gST->Reset 。经过查找,模拟器中实现这个功能的代码在 \Nt32Pkg\ResetRuntimeDxe\reset.c 。头定义如下

VOID
EFIAPI
WinNtResetSystem (
  IN EFI_RESET_TYPE   ResetType,
  IN EFI_STATUS       ResetStatus,
  IN UINTN            DataSize,
  IN VOID             *ResetData OPTIONAL
  )
/*++

Routine Description:

  TODO: Add function description

Arguments:

  ResetType   - TODO: add argument description
  ResetStatus - TODO: add argument description
  DataSize    - TODO: add argument description
  ResetData   - TODO: add argument description

Returns:

  EFI_SUCCESS - TODO: Add description for return value

--*/

 

实现功能的代码只有一行 gWinNt->ExitProcess (0)。只要把这些语句注释掉,替换为我们的输出代码即可。参考 \Nt32Pkg\Library\PeiNt32OemHookStatusCodeLib\Nt32OemHookStatusCodeLib.c
,对编译窗口输出的语句如下:

 //
  // Callout to standard output.
  //
  mWinNt->WriteFile (
            mStdOut,
            Buffer,
            (DWORD)CharCount,
            (LPDWORD)&CharCount,
            NULL
            );

 

搬过来,写成下面的形式:

VOID
EFIAPI
WinNtResetSystem (
  IN EFI_RESET_TYPE   ResetType,
  IN EFI_STATUS       ResetStatus,
  IN UINTN            DataSize,
  IN VOID             *ResetData OPTIONAL
  )
/*++

Routine Description:

  TODO: Add function description

Arguments:

  ResetType   - TODO: add argument description
  ResetStatus - TODO: add argument description
  DataSize    - TODO: add argument description
  ResetData   - TODO: add argument description

Returns:

  EFI_SUCCESS - TODO: Add description for return value

--*/
{
  CHAR8			 *R="www.lab-z.com \n\r";	
  UINTN           CharCount=AsciiStrLen(R);
  
  //
  // Cache of standard output handle .
  //
	HANDLE                      mStdOut;
  
  //
  // Cache standard output handle.
  //
  mStdOut = gWinNt->GetStdHandle (STD_OUTPUT_HANDLE);  
  
  //
  // Callout to standard output.
  //
  gWinNt->WriteFile (
            mStdOut,
            R,
            (DWORD)CharCount,
            (LPDWORD)&CharCount,
            NULL
            );
			
  //
  // BUGBUG Need to kill all console windows later
  //
  //
  // Discard ResetType, always return 0 as exit code
  //
  //gWinNt->ExitProcess (0);

  //
  // Should never go here
  //
  //ASSERT (FALSE);
}

 

特别注意:字符串是 Ascii 定义的,末尾必须是 \r\n ,否则不会立即显示。

最后运行结果,只要在模拟器中输入 reset 即可看到字符串,正常的动作应该是退出模拟器。

nt32a

nt32b

因为我修改掉了退出的方法,每次需要在编译窗口用 ctrl+c来结束模拟器了。

这里只是一个Demo,最好还是重新在EDKII代码中定义一个用来直接输出的接口。

介绍一个MP3模块

之前入手了一个 MP3 播放模块(名称是 Arduino TTL串口语音模块 Mini Voice MP3语音音乐播放器),自带 SD 卡槽还有小喇叭。

image002

image004

image006

指令:
1.播放 play,0001,$
2.播放/暂停 pap,$,pla,$
3.停止播放 stop,$
4.上一曲 previous,$
5.下一曲 next,$
6.音量加 vol+,$
7.音量减 vol-,$
8.音量大小 vol,A,$ (从 vol,1,$到vol,F,$ 十六个级别)
9复位模块 reset,$
10.波特率设置 baud,9600,$ (支持 1200,2400,4800,7200,9600,14400,19200,38400,57600,115200 十种)
测试程序:

boolean pause=true;

void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
  while (!Serial) 
    {;}
}

void loop() {
  Serial.println("(0) Play 0001");
  Serial.println("(1) Play 0002");
  Serial.println("(2) Play/Pause");
  Serial.println("(3) Stop");
  Serial.println("(4) Previuos");  
  Serial.println("(5) Next"); 
  Serial.println("(6) Volume +");  
  Serial.println("(7) Volume -");  
  Serial.println("(9) Reset");  
  Serial.println("");

  while (!Serial.available()) {;}

  switch (Serial.parseInt())
  {
      case 0:  Serial1.print("play,0001,$");      
      case 1:  Serial1.print("play,0002,$"); break;
      case 2:  if (pause==true) {
                    Serial1.print("pla,$");
                   }
               else {
                    Serial1.print("pap,$");
                   }
               pause=!pause;     
                break;
      case 3:  Serial1.print("stop,$");break;
      case 4:  Serial1.print("previous,$");break;
      case 5:  Serial1.print("next,$");break;
      case 6:  Serial1.print("vol+,$");break;
      case 7:  Serial1.print("vol-,$");break;
      case 9:  Serial1.print("reset,$");break;  
      default: Serial1.println("Menu item does not exist.");
  }
}

 

运行结果

image008

另外,模块上面带有USB接口,然后可以将 SD 卡插在上面,用USB线直接连接到电脑上即可充当读卡器。测试中我将 MP3分别命名为 0001.MP3 0002.MP3 0003.MP3。
image010

参考:
1. https://item.taobao.com/item.htm?spm=a1z09.2.0.0.iedIEI&id=45599999495&_u=ckf8s90790 Arduino TTL串口语音模块 Mini Voice MP3语音音乐播放器

Compiler Warning C4131

在尝试编译老旧的代码时,你可能遇到 C4131 Warning, 给出来的提示是 “uses old-style declarator”。 这是因为你的代码使用了旧式的生命方式

比如:

// C4131.c
// compile with: /W4 /c
void addrec( name, id ) // C4131 expected
char *name;
int id;
{ }

 

这个定义会导致上面的 warning。修改为下面的即可

 void addrec( char *name, int id )
 { }

 

更多信息可以在
https://msdn.microsoft.com/en-us/library/b92s55e9(v=vs.90).aspx 看到

Debug Message 标记工具

对于庞大的代码,串口 Message 提供了最好的追踪方法。但是很多时候,我们的代码会有重复的文件,在对照阅读的时候就会非常麻烦。一种方法是之前文章介绍过的使用 __FILE__ 的方法,唯一的问题是这个宏经常导致代码在Debug 模式下爆掉(很大原因是因为Debug模式下这个宏会加入路径信息)。因此,编写这个工具,具体的原理是:打开所有的 C 文件,查找 DEBUG宏,然后在它的字符串起始处加入 “(序号)”这样的标记。序号是由 0-9和A-Z组成的。更改代码之后,再次编译,串口输出的内容就有序号+原先的字符串,能够很方便的分析出来源。

特别注意:某些情况下可能会导致源文件的错误,这是目前分析的手法决定的。不过从我的实践来看,EDK代码不会有问题,某些工程文件可能会有1到2处标记错误,手动修改去掉即可。

这是我在某个BIOS工程上直接实验的结果:

dm

可执行程序和完整代码下载

DebugMark