Step to UEFI (45) —– 在程序中执行另外的程序

某些情况下,我们有在自己的程序中调用另外一个 EFI 程序的需求。

关于这个问题【参考1】建议参考Shell的源程序。如果有时间,建议阅读这一段代码,相信对于具体的实现很有帮助。

“RunCommandOrFile()
==> case Efi_Application:
InternalShellExecuteDevicePath()
==> Status = gBS->LoadImage( … ) ”

另外,【参考2】介绍了一下调用的流程:

1. BS->LoadImage 加载你要调用的 EFI 到内存
2. BS->StartImage 执行你加载的EFI程序
3. BS->UnLoadImage 执行完成之后释放EFI

了解了基本流程原理,下面就要认真阅读函数的原型。

LoadImage的原型如下,来自【参考3】

loadimage

BootPolicy 告诉加载的EFI是否为可启动的选项
ParentImageHandle 是调用者的Handle
DevicePath 告诉要调用的EFI文件的位置
SourceBuffer 可选如果不为NULL的话,是指向内存中的要加载的EFI的指针
SourceSize 如果上面这个指针存在的话,给出指向内存的大小
EFI_HANDLE 加载之后Image的Handle

StartImage 原型

startimage

ImageHandle  前面LoadImage给出来的EFI Image Handle
ExitDataSize 下面ExitData的大小
ExitData 看起来在一个 EFI 结束的时候,可以返回一些内容

UnLoadImage 原型

unimage

给出要释放的EFI的Handle即可

根据上面的介绍,再结合Shell.c中的具体实现,编写程序如下。为了方便验证和保持整个程序的简洁,程序固定调用“HellowWorld.efi”。这个程序的作用是输出一段String。

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

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

#include <Protocol/EfiShell.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;
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;
  EFI_HANDLE	NewHandle;
  EFI_STATUS	Status;
  UINTN			ExitDataSizePtr;
  CHAR16 *R=L"HelloWorld.efi";
  
  Print(L"File [%s]\n",R);

  DevicePath=ShellGetDevicePath(R);

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    gImageHandle,
    DevicePath,
    NULL,
    0,
    &NewHandle);  

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during LoadImage [%X]\n",Status);
    return (Status);
  }

  //
  // now start the image, passing up exit data if the caller requested it
  //
  Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during StartImage [%X]\n",Status);
    return (Status);
  }
  

  gBS->UnloadImage (NewHandle);  
  return EFI_SUCCESS;
}

 

运行结果:

execres

工作的视频:

http://www.tudou.com/programs/view/92MTmguSCZk/?resourceId=414535982_06_02_99

代码下载

exec

最后,如果你只是想简单的执行一个程序,可以考虑直接使用 EFI_SHELL_PROTOCOL 的 EfiShellExecute 或者 EFI_SHELL_ENVIRONMENT2的 Execute ,这样会简单许多。

参考:
1. http://biosren.com/viewthread.php?tid=7440&highlight=%BC%D3%D4%D8%2B%B3%CC%D0%F2 请问:在shell下,应用程序的.efi文件被加载到内存的基地址为多少?
2. http://blog.csdn.net/kaven128708/article/details/6042307 EFI Load Image
3. UEFI Spec 2.4 P196

推荐绘制电路图的软件 TinyCAD

之前我尝试用 Fritzing 绘制短路图一段时间。这个软件有直观方便的特点。问题是大多数我用到的原件他的库中没有,如果自己绘制的话非常复杂,修改元件管脚也异常麻烦。

http://sourceforge.net/projects/tinycad/

tinycada

用这个很容易就绘制一个 Arduino Pro Micro的电路

tinycadb

对应文件可以在这里下载
Arduino-Pro-Micro

小水泵套件

最近从淘宝上买了一个小水泵和水管给Arduino 用,效果还不错

螃蟹王国 隔膜泵 抽水机 水泵 茶具功夫茶配件 烧水茶具 385水泵 9.9元
http://detail.tmall.com/item.htm?spm=a1z10.3-b.w4011-2934116009.41.OZz7kv&id=27069408842&;rn=19abc97d3a3f333b0532d77acf876c95&abbucket=10

poop
螃蟹王国 硅胶管 耐高温 食品级 水管 多规格 6元

http://detail.tmall.com/item.htm?spm=a220o.1000855.0.0.aUdENI&id=16273533392&;rn=5ae68cf7c122dcfc1738eafece3afc9c&abbucket=0

同样他家选购了一个6V的适配器。

螃蟹王国 电源适配器6V2A 高性能 路由器电源 适配器 充电器 9.9元
http://detail.tmall.com/item.htm?spm=a1z10.3-b.w4011-2934116009.58.24xZpI&id=20618420536&;rn=6257e27c013737241e82d5c120284aaa&abbucket=10

螃蟹王国 DC电源插座 5.5*2.1MM 直流电源接口 DC-005 铜脚 3.95元
http://detail.tmall.com/item.htm?spm=a1z10.3-b.w4011-2934116009.27.pZwIWV&id=20623496826&;rn=1bc29be0dbbabd7042db076efd6ce381&abbucket=10

需要注意的是,这种插座有2个参数,5.5是直径,2.1是长度。也有2.5mm长度的,如果座和插头不匹配的话,插入之后会有部分露在外面。

下面是它工作视频以便参考

http://www.tudou.com/programs/view/7diay5i7Z-c/?resourceId=0_06_02_99

Step to UEFI (44) —– 获得按键

在Shell下面编写工具程序,我们经常需要和用户进行交互,需要取得客户按键的信息。

对于这个问题,可以使用 EFI_SIMPLE_TEXT_INPUT_PROTOCOL 的 ReadKeyStroke 来解决【参考1】

readkeystroke

写一个小程序来验证一下

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

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>
#include  <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

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

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;


int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_INPUT_KEY	Key;
  EFI_STATUS Status;
  
  while (1)
	{
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
		if (Status == EFI_SUCCESS)	{
			break;
		}
		gST -> ConOut -> OutputString(gST->ConOut,L"Test......");
		gBS->Stall(500);
	}  
  Print(L"\n\r Scancode [%d], UnicodeChar [%c] \n\r",Key.ScanCode,Key.UnicodeChar);
  return EFI_SUCCESS;
}

 

按下 F1 检测结果:

F1key

按下 q键 检测结果:

qkey

代码下载

KeyTest1

参考:

1.UEFI 2.4 P445

Arduino Pro Micro 打造PPT遥控器

之前,我们介绍过如何用Arduino Uno打造一个PPT遥控器【参考1】,缺点是制作过程复杂,用到的器件较多导致整体稳定性不好。这里介绍使用 Arduino Pro Micro来做一个同样的PPT遥控器。

原理上和之前的并没有多少差别,都是通过模拟USB键盘的方式来进行控制。差别在于Pro Micro是缩小版的 Leonardo ,内部集成了USB Slave控制器,我们不需要再花费精力模拟自己为USB Keyboard.
硬件连接示意图:

ppt1

代码是直接修改自示例“KeyboardMessage”

ppt2

/* 
 Keyboard Button test
 
 For the Arduino Leonardo and Micro.
 
 Sends a text string when a button is pressed.
 
 The circuit:
 * pushbutton attached from pin 2 to +5V
 * 10-kilohm resistor attached from pin 4 to ground
 
 created 24 Oct 2011
 modified 27 Mar 2012
 by Tom Igoe
 
 This example code is in the public domain.
 
 http://www.arduino.cc/en/Tutorial/KeyboardButton
 */

const int buttonPinA = A1;          // input pin for A
const int buttonPinB = A2;          // input pin for B
int previousButtonStateA = HIGH;   // for checking the state of a pushButton
int previousButtonStateB = HIGH;   // for checking the state of a pushButton

void setup() {
  // make the pushButtonA pin an input:
  pinMode(buttonPinA, INPUT);
  // make the pushButtonB pin an input:
  pinMode(buttonPinB, INPUT);
  // initialize control over the keyboard:
  Keyboard.begin();
  delay(10000);
}

void loop() {
  // read the pushbutton:
  int buttonStateA = digitalRead(buttonPinA);
  int buttonStateB = digitalRead(buttonPinB);  
  // if the button state has changed, 
  if ((buttonStateA != previousButtonStateA) 
    // and it's currently pressed:
  && (buttonStateA == HIGH)) {
    // type out a message
    //Keyboard.print("A");
    Keyboard.press(KEY_LEFT_ARROW);
    Keyboard.releaseAll();
  }
  // if the button state has changed, 
  if ((buttonStateB != previousButtonStateB) 
    // and it's currently pressed:
  && (buttonStateB == HIGH)) {
    // type out a message
    //Keyboard.print("B");
    Keyboard.press(KEY_RIGHT_ARROW);
    Keyboard.releaseAll();
  }  
  // save the current button state for comparison next time:
  previousButtonStateA = buttonStateA; 
  previousButtonStateB = buttonStateB;   
}

 

实物图

ppt3

工作的视频

http://www.tudou.com/listplay/mAjgE5Ac_Sg/Fz0piTvQ9Cs.html?resourceId=414535982_06_02_99

参考:

1. http://www.lab-z.com/%E7%94%A8-arduino-%E6%89%93%E9%80%A0ppt%E9%81%A5%E6%8E%A7%E5%99%A8/ 用 Arduino 打造PPT遥控器
2. http://arduino.cc/en/Reference/KeyboardModifiers 键值定义

Step to UEFI (43) —– 取得当前目录的 EFI_DEVICE_PATH_PROTOCOL

在 EFI_SHELL_PROTOCOL 中提供了 EfiShellGetDevicePathFromFilePath 函数

具体可以在 \ShellPkg\Application\Shell\ShellProtocol.h 看到原型

/**
  Converts a file system style name to a device path.

  This function converts a file system style name to a device path, by replacing any
  mapping references to the associated device path.

  @param[in] Path               The pointer to the path.

  @return                       The pointer of the file path. The file path is callee
                                allocated and should be freed by the caller.
  @retval NULL                  The path could not be found.
  @retval NULL                  There was not enough available memory.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
EfiShellGetDevicePathFromFilePath(
  IN CONST CHAR16 *Path
  )

 

在【参考1】中,可以看到下面的介绍

getcurdp

经过试验,这个函数结合之前我们获得当前目录的函数完全可以达到目标。但是,如同【参考2】提到的,某些情况下,因为 Shell 不提供 EFI_SHELL_PROTOCOL,这个函数无法使用。

再查查 EFI_SHELL_ENVIRONMENT2 发现其中有功能类似的 NameToPath 函数

Convert a file system style name to a device path.

This function will convert a shell path name to a Device Path Protocol path. This function will allocate any required memory for this operation and it is the responsibility of the caller to free that memory when no longer required.

If anything prevents the complete conversion free any allocated memory and return NULL.

Parameters
[in]	Path	The path to convert.
Return values
!NULL	A pointer to the callee allocated Device Path.
NULL	The operation could not be completed.

 

最后,决定仿照 ShellLib.h 中的 ShellGetCurrentDir,写一个混合上述两种函数的函数。最后的代码就是这样

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

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

#include <Protocol/EfiShell.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;
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;
  CHAR16 *R;
  
  R=(CHAR16 *)ShellGetCurrentDir(NULL);
  
  Print(L"Current dir [%s]\n",ShellGetCurrentDir(0));
  
  DevicePath=ShellGetDevicePath(R);
  
  Print(L"Type        [%d]\nSub-Type    [%d]\nLength      [%d]\n",
				DevicePath->Type,
				DevicePath->SubType,
				DevicePath->Length[0] + DevicePath->Length[1]*256);
  
  return EFI_SUCCESS;
}

 

运行结果

getCurDP

代码下载

GetCurDP

参考:

1.UEFI_Shell_Spec_2_1_July02release P28
2.http://www.lab-z.com/getcurdir2/ Shell GetCurDir 补遗
3.http://www.bluestop.org/edk2/docs/UDK2010.SR1/struct_e_f_i___s_h_e_l_l___e_n_v_i_r_o_n_m_e_n_t2.html EFI_SHELL_ENVIRONMENT2 Struct Reference

Step to UEFI (42) —– 给Shell加一个时间显示功能

目标是:在Shell的右上角实现一个时间显示,类似DOS下的那种内存驻留程序,不影响其他程序的运行一直显示时间。

方法:首先想到的最简单方法是修改Shell.bin的代码,在开始的地方加入一个定时器,触发之后,首先保存当前光标位置,然后移动光标到右上角,自动更新显示时间,最后再将光标移回原位置。

需要修改的文件是 \ShellPkg\Application\Shell 下面的 Shell.C

定时器触发之后动作的代码如下:

//LabZDebug_Start
/**
  The callback function for the timer event used to get map.

  @param[in] Event    The event this function is registered to.
  @param[in] Context  The context registered to the event.
**/
VOID
EFIAPI
Timeout (
  IN EFI_EVENT      Event,
  IN VOID           *Context
  )
{
  EFI_TIME   ET;
  UINTN		x;
  UINTN		y;
  

  //Get cursor postion
  x = gST->ConOut->Mode->CursorColumn;
  y = gST->ConOut->Mode->CursorRow;
  //Move cursor to Up-Left 
  gST -> ConOut -> SetCursorPosition(gST -> ConOut,70,0);  
  //Output current time
  gRT->GetTime(&ET, NULL);  
  Print(L"%2d:%2d:%2d",ET.Hour,ET.Minute,ET.Second);
  //Move cursor back
  gST -> ConOut -> SetCursorPosition(gST -> ConOut,x,y);  

  return ;
}
//LabZDebug_End

 

设置定时器的代码如下

	//LabZDebug_Start
	Status  = gBS->CreateEvent (
                    EVT_NOTIFY_SIGNAL | EVT_TIMER,
                    TPL_CALLBACK,
                    Timeout,
                    NULL,
                    &TimerOne
                    );

    if (EFI_ERROR (Status)) {
        Print(L"Create Event Error! \r\n");
    }
	else {
		Status = gBS->SetTimer (
                   TimerOne,
                   TimerPeriodic,
                   MultU64x32 (1000, 1)
                   );

    if (EFI_ERROR (Status)) {
        Print(L"Set Timer Error! \r\n");
		}
	}
	//LabZDebug_End

 

最后还要销毁定时器,否则会发生 exit 到 setup 之后崩溃的情况

FreeResources:
	//LabZDebug_Start
	    gBS->SetTimer (TimerOne, TimerCancel, 0);
		gBS->CloseEvent (TimerOne);	
	//LabZDebug_End

 

之后需要重新编译出来 Shell.efi 替换原先的文件。运行结果如下

stim

工作的视频:

http://www.tudou.com/programs/view/DcPCkOrO2lQ/?resourceId=414535982_06_02_99

完整的代码下载

ShellC

Arduino 打造一个音量计

根据上一次的LM386的设计【参考1】,以及网上的设计【参考2】,用Arduino做一个音量计。首先,音频从MIC进入,经过LM386的放大后接入到Arduino的模拟输入上,经过 DAC 量化之后显示在 1602上。

电路方面,和【参考1】差别在于我们不再使用喇叭而是直接将放大后的OUT信号接入到 Arduino的A5上。

显示方面,我们使用【参考3】提到的方法来自定义字符充当强度指示。

下面的图片与其说是原理图不如说是连接图更合适

vu1

//www.lab-z.com 
//在 1602 上显示音量的小程序

#include <Wire.h> 
#include "LiquidCrystal_I2C.h"

int value=100;

// custom charaters

LiquidCrystal_I2C lcd(0x27,16,2);

//定义进度块
byte p1[8] = {
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10};

byte p2[8] = {
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18};

byte p3[8] = {
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C};

byte p4[8] = {
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E};

byte p5[8] = {
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F};

void setup() {
               lcd.init();           //初始化LCD
               lcd.backlight();		 //打开背光
				
			//将自定义的字符块发送给LCD
			//P1 是第一个,P2 是第二个,以此类推
                lcd.createChar(0, p1);			 
                lcd.createChar(1, p2);
                lcd.createChar(2, p3);
                lcd.createChar(3, p4);
                lcd.createChar(4, p5);
            //MIC输入放大之后在 A0 输入Arduino    
                pinMode(A0, INPUT);
}

//显示音量强度
//从左到右一共有 5 * 16 =80 点,一共是 80+1=81 个状态
void showprg(int value)
{
        //第一行显示当前VU值
                lcd.setCursor(0,0);
                lcd.print("     VU=");
                lcd.print(value);

                
		//移动光标到第二行
                lcd.setCursor(0,1);

        //显示全黑的块
                for (int i=1;i<value / 5;i++) {
                                lcd.write(4);
                        } //for (int i=1;i<a;i++) 


                // drawing charater's colums
		// 显示除去全黑块之后的零头
                switch (value % 5) {
                  case 0:
                        break;
                  case 1:
                        lcd.write(0);
                        break;
                  case 2:
                        lcd.write(1);
                        break;
                  case 3:
                        lcd.write(2);
                        break;
                  case 4:
                        lcd.write(3);
                        break;
                  } //switch (peace)

	       // 用空格填充剩下的位置
              for (int i =0;i<(16-value / 5);i++) {
                lcd.print(" ");   }
}

void loop()
{
     //输入的是0-1023,用函数将这个值对应到[0,80]上 
        showprg(map(analogRead(A0),0,1023,0,80));
		delay(100);
              
}

 

测试方法,在右边用一个手机播放声音,MIC将音频信号转化为电平信号,经过LM386放大后,通过Arduino A5进行量化,最终显示在LCD1602上。

vu2

工作视频:

http://www.tudou.com/programs/view/oW_isUBnIEU/?resourceId=414535982_06_02_99

这个还只是一个模型,很粗糙,对于VU的动态显示范围不大,如果想让人类更容易理解需要考虑将现在的线性显示改为非线性的。

另外,工作时我发现比较奇怪的事情,就是如果静音的时候,会显示UV 大约 35左右,但是如果有动态声音播放的时候反而会出现 16 这样值,不清楚原因。

==============================================================================
另外的另外,研究了一下前面那个口哨开关的模拟输出。下面是电路图,右下角红色框起来的是模拟输出电路部分

vu3

查了一下,发现这种用法是电压跟随器,下图来自【参考4】。

vu4

电压跟随器,顾名思义,是实现输出电压跟随输入电压的变化的一类电子元件。也就是说,电压跟随器的电压放大倍数恒小于且接近1【参考5】。
多少输入就是多少输出…….这也就是为什么这个模块驱动能力很差带不动喇叭的原因。
参考:

1. http://www.lab-z.com/lm386-with-mic/ LM386 with MIC
2.http://www.arduino-hacks.com/arduino-vu-meter-lm386electret-microphone-condenser/ Arduino VU meter – LM386+electret microphone condenser
3. http://www.lab-z.com/1602progressbar/ 用 1602实现进度条
4. http://www.geek-workshop.com/thread-551-1-1.html LM358双运算放大器
5. http://baike.baidu.com/link?url=85VzUTT6n3IrCaAATZSg5jsg_A-zQYFrizj6jqGP7PzRg1aJe2yTddMihqJ7_UgRMa_GxnAYakSmmjM-9nhsz_ 电压跟随器

Step to UEFI (41) —– x64 的 FreqCalc程序

之前的一篇文章《Step to UEFI (9)—-使用RDTSC计算当前CPU 频率》【参考1】给出了一个计算当前CPU频率的方法。不过 Tim 给我留言,他说这篇文章的程序无法在 x64下正常编译:

freq

我猜测原因是因为我的程序使用的内嵌汇编,内嵌汇编无法被X64的编译器正常编译的。关于这个说法可以看【参考2】。

动手在 UDK2014 下面实验,不过在看到描述的问题之前还是要走一段路的。

使用编译命令 build -a X64 -p AppPkg\AppPkg.dsc 得到下面的错误信息

c:\edk\AppPkg\AppPkg.dsc(94): error 000E: File/directory not found in workspace
        c:\edk\PerformancePkg\Library\DxeTscTimerLib\DxeTscTimerLib.inf

 

检查 AppPkg.dsc

修改

[LibraryClasses.X64]
  TimerLib|PerformancePkg/Library/DxeTscTimerLib/DxeTscTimerLib.inf

 

修改为

[LibraryClasses.X64]
  TimerLib|PerformancePkg/Library/TscTimerLib/DxeTscTimerLib.inf

 

再次编译,有下面的错误信息

Processing meta-data .. done!
Building ... c:\edk\MdePkg\Library\BaseLib\BaseLib.inf [X64]
        "C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe" /Foc:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\.\CheckSum.obj /nologo /c /WX /GS- /W4 /Gs32768 /Gy /D UNICODE /O1ib2 /GL /FIAutoG
en.h /EHs-c- /GR- /GF /Zi /Gm /X /Zc:wchar_t /X /Zc:wchar_t /GL- /Ic:\edk\MdePkg\Library\BaseLib\X64  /Ic:\edk\MdePkg\Library\BaseLib  /Ic:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\MdePkg\Library\BaseLib\BaseLib\DEBUG  /Ic:\edk\MdePkg  /Ic:\edk\M
dePkg\Include  /Ic:\edk\MdePkg\Include\X64 c:\edk\MdePkg\Library\BaseLib\CheckSum.c
'C:\Program' 不是内部或外部命令,也不是可运行的程序或批处理文件。
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe' : return code '0x1' Stop.


build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 

build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 


build...
 : error F002: Failed to build module
        c:\edk\MdePkg\Library\BaseLib\BaseLib.inf [X64, MYTOOLS, DEBUG]

- Failed -
Build end time: 11:22:09, Mar.09 2015
Build total time: 00:00:03

 

尝试在命令行窗口直接运行”C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe” 错误提示是:

C:\EDK>"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe"
系统找不到指定的路径。

 

这是因为目前的编译环境缺少 64位编译器。我使用的是 vs2008 express 版本,默认是没有 64位的cl.exe的。

找到 ‘x86_amd64’ 拷贝到 “C:\Program Files\Microsoft Visual Studio 9.0\VC\bin”

需要的朋友可以在这里下载到 x86_amd64

再次编译,然后就能看到 Tim 说的问题啦

C:\Program Files\Microsoft Visual Studio 9.0\VC\bin

Building ... c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.inf [X64]
        "C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe" /Foc:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\AppPkg\Applications\FreqCalc\FreqCalc\OUTPUT\.\FreqCalc.obj /nologo /c /WX /GS- /W4 /Gs32768 /Gy /D UNICODE /O1ib2 /GL /
FIAutoGen.h /EHs-c- /GR- /GF /Zi /Gm /X /Zc:wchar_t /Ic:\edk\AppPkg\Applications\FreqCalc  /Ic:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\AppPkg\Applications\FreqCalc\FreqCalc\DEBUG  /Ic:\edk\MdePkg  /Ic:\edk\MdePkg\Include  /Ic:\edk\MdePkg\Includ
e\X64  /Ic:\edk\ShellPkg  /Ic:\edk\ShellPkg\Include  /Ic:\edk\MdeModulePkg  /Ic:\edk\MdeModulePkg\Include c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c
FreqCalc.c
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(15) : error C4235: nonstandard extension used : '__asm' keyword not supported on this architecture
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'mov'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : warning C4550: expression evaluates to a function which is missing an argument list
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'mov' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'dword'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'dword' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'ptr'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'ptr' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'value'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'eax' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'mov'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'mov' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'dword'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'dword' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'ptr'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'ptr' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2109: subscript requires array or pointer type
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(20) : error C2065: 'edx' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(20) : error C2143: syntax error : missing ';' before '}'
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe"' : return code '0x2'
Stop.


build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 


build...
 : error F002: Failed to build module
        c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.inf [X64, MYTOOLS, DEBUG]

- Failed -
Build end time: 11:26:31, Mar.09 2015
Build total time: 00:01:16

 

然后就考虑如何解决,网上搜索了一下,有人遇到同样的问题【参考3】,具体的解决方法在【参考4】

简单的说,就是单独写一个 asm 然后在对应的 Inf中声明一下即可。

针对我遇到的问题,程序如下

FreqCalc.inf

## @file
#  Sample UEFI Application Reference EDKII Module
#
#  This is a sample shell application that will print "UEFI Hello World!" to the 
#  UEFI Console based on PCD setting.
#
#  It demos how to use EDKII PCD mechanism to make code more flexible.
#
#  Copyright (c) 2008 - 2011, Intel Corporation. All rights reserved.<BR>
#
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.php
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
#
##

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = FreqCalc
  FILE_GUID                      = 6987936E-ED34-44db-AE97-2FA5E4ED2216
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain 

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#

[Sources]
  FreqCalc.c
  ReadTsc1.asm

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

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  PcdLib
  ShellCEntryLib
  ShellLib


[FeaturePcd]


[Pcd]

 

然后在FreqCalc.c 写成下面这样,特别注意要声明一下你用的那个汇编语言中的函数名

//
// FreqCalc.C
//

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

EFI_SYSTEM_TABLE	*gST;
EFI_BOOT_SERVICES  	*gBS;

UINT64
EFIAPI
zReadTsc (
  VOID
);

//
// Entry point function - ShowVersion
//
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{

  UINT64  elsp;

  gST = SystemTable;
  gBS = SystemTable->BootServices;

  elsp=zReadTsc();

  gBS -> Stall(1000000);  
  Print(L"CPU Frequency: %ld \n",zReadTsc() - elsp);

  return EFI_SUCCESS;
}

 

然后还有一个ReadTsc1.asm (其实 uefi 里面提供了一个 x64的 ReadTsc.asm,可以直接使用,这里是为了让读者看的更清楚,取了一个不会重复的名称)

    .code

;------------------------------------------------------------------------------
; UINT64
; EFIAPI
; zReadTsc (
;   VOID
;   );
;------------------------------------------------------------------------------
zReadTsc  PROC
    rdtsc
    shl     rdx, 20h
    or      rax, rdx
    ret
zReadTsc  ENDP

    END

 

接下来就可以正常编译了,生成的 efi 文件会比 x64的大一些。然后我在实体机的 x64 shell下面运行成功。就是说上面的方法是没问题的。

FreqCalc

参考:

1.http://www.lab-z.com/rdtsc/ Step to UEFI (9)—-使用RDTSC计算当前CPU 频率

2.http://stackoverflow.com/questions/1295452/why-does-msvc-not-support-inline-assembly-for-amd64-and-itanium-targets 简单解释,说x64下无法支持 _ASM汇编

3.http://biosren.com/viewthread.php?tid=6822&highlight=%C4%DA%C7%B6%2B%BB%E3%B1%E0 請問如何在EDK2的AppPkg裡面使用asm (有人问同样的问题)

4.http://www.biosren.com/thread-6632-1-1.html 給個UEFI 內嵌彙編的小程序吧? 本文的主要参考,其中介绍了如何写单独写一个ASM文件