Step to UEFI (27) —– Shell GetCurDir

翻看 Shell Specification【参考 1】,发现上面介绍了一个Shell下取得当前目录的函数

getcurdir

看起来这个函数很简单,于是动手写程序。注意到 \ShellPkg\Library\UefiShellLib\UefiShellLib.c 中有声明,于是直接 extern EFI_SHELL_PROTOCOL *gEfiShellProtocol; 过来用。之所以能这样做,是因为这个位置是 Shell 程序在编译过程中特别加入的头,具体请参考之前的文章。

编译完成之后,进入虚拟环境实验,非常奇怪,每次运行都会出现错误提示,然后NT32就会崩溃掉。

getcurdirerror

真相到底是什么呢?一检查,gEfiShellProtocol == 0 。还要追根溯源,到定义他的地方去查看。

代码在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

为了Debug,在关键的地方添加上的一点代码,输出信息

  Status = gBS->OpenProtocol(
    ImageHandle,
    &gEfiShellParametersProtocolGuid,
    (VOID **)&gEfiShellParametersProtocol,
    ImageHandle,
    NULL,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
   );
  if (EFI_ERROR(Status)) {
    gEfiShellParametersProtocol = NULL;
  }
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestA\n\r"); //LabZDebug
  if (gEfiShellParametersProtocol == NULL || gEfiShellProtocol == NULL) {
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestB\n\r"); //LabZDebug
    //
    // Moved to seperate function due to complexity
    //
    Status = ShellFindSE2(ImageHandle);

    if (EFI_ERROR(Status)) {
      DEBUG((DEBUG_ERROR, "Status: 0x%08x\r\n", Status));
      mEfiShellEnvironment2 = NULL;
    }
    Status = gBS->OpenProtocol(ImageHandle,
                               &gEfiShellInterfaceGuid,
                               (VOID **)&mEfiShellInterface,
                               ImageHandle,
                               NULL,
                               EFI_OPEN_PROTOCOL_GET_PROTOCOL
                              );
    if (EFI_ERROR(Status)) {
      mEfiShellInterface = NULL;
    }
  }
if (0==gEfiShellProtocol) { 
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestE\n\r"); //LabZDebug
  }

SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestC\n\r"); //LabZDebug

 

输出的结果是: TestA TestB TestE 和 TestC。 就是说这段代码尝试取 Shell Protocol 但是没有取到。再仔细看一下代码,这里没有取得到。因此,此路不通。再向下看发现初始化了EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2,再看了一下他的定义,在\ShellPkg\Include\Protocol\EfiShellEnvironment2.h 中

/// EFI_SHELL_ENVIRONMENT2 protocol structure.
typedef struct {
  SHELLENV_EXECUTE                        Execute;
  SHELLENV_GET_ENV                        GetEnv;
  SHELLENV_GET_MAP                        GetMap;
  SHELLENV_ADD_CMD                        AddCmd;
  SHELLENV_ADD_PROT                       AddProt;
  SHELLENV_GET_PROT                       GetProt;
  SHELLENV_CUR_DIR                        CurDir;
  SHELLENV_FILE_META_ARG                  FileMetaArg;
  SHELLENV_FREE_FILE_LIST                 FreeFileList;

  //
  // The following services are only used by the shell itself.
  //
  SHELLENV_NEW_SHELL                      NewShell;
  SHELLENV_BATCH_IS_ACTIVE                BatchIsActive;

  SHELLENV_FREE_RESOURCES                 FreeResources;

  //
  // GUID to differentiate ShellEnvironment2 from ShellEnvironment.
  //
  EFI_GUID                                SESGuid;
  //
  // Major Version grows if shell environment interface has been changes.
  //
  UINT32                                  MajorVersion;
  UINT32                                  MinorVersion;
  SHELLENV_ENABLE_PAGE_BREAK              EnablePageBreak;
  SHELLENV_DISABLE_PAGE_BREAK             DisablePageBreak;
  SHELLENV_GET_PAGE_BREAK                 GetPageBreak;

  SHELLENV_SET_KEY_FILTER                 SetKeyFilter;
  SHELLENV_GET_KEY_FILTER                 GetKeyFilter;

  SHELLENV_GET_EXECUTION_BREAK            GetExecutionBreak;
  SHELLENV_INCREMENT_SHELL_NESTING_LEVEL  IncrementShellNestingLevel;
  SHELLENV_DECREMENT_SHELL_NESTING_LEVEL  DecrementShellNestingLevel;
  SHELLENV_IS_ROOT_SHELL                  IsRootShell;

  SHELLENV_CLOSE_CONSOLE_PROXY            CloseConsoleProxy;
  HANDLE_ENUMERATOR                       HandleEnumerator;
  PROTOCOL_INFO_ENUMERATOR                ProtocolInfoEnumerator;
  GET_DEVICE_NAME                         GetDeviceName;
  GET_SHELL_MODE                          GetShellMode;
  SHELLENV_NAME_TO_PATH                   NameToPath;
  SHELLENV_GET_FS_NAME                    GetFsName;
  SHELLENV_FILE_META_ARG_NO_WILDCARD      FileMetaArgNoWildCard;
  SHELLENV_DEL_DUP_FILE                   DelDupFileArg;
  SHELLENV_GET_FS_DEVICE_PATH             GetFsDevicePath;
} EFI_SHELL_ENVIRONMENT2;

因此,尝试一下SHELLENV_CUR_DIR. 这个函数头文件也在同一个文件中

/**
  This function returns a string array containing the current directory on
  a given device.

  If DeviceName is specified, then return the current shell directory on that
  device.  If DeviceName is NULL, then return the current directory on the
  current device.  The caller us responsible to free the returned string when
  no longer required.

  @param[in] DeviceName         The name of the device to get the current
                                directory on, or NULL for current device.

  @return String array with the current directory on the current or specified device.

**/
typedef
CHAR16*
(EFIAPI *SHELLENV_CUR_DIR) (
  IN CHAR16 *DeviceName OPTIONAL
  );

 

最后的 C 代码如下

#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_ENVIRONMENT2        *mEfiShellEnvironment2;

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  Print(L"%s",mEfiShellEnvironment2->CurDir(0));
  return EFI_SUCCESS;
}

 

对应的 INF 代码如下

## @file
#   A simple, basic, application showing how the Hello application could be
#   built using the "Standard C Libraries" from StdLib.
#
#  Copyright (c) 2010 - 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.
#
#  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                    = 0x00010006
  BASE_NAME                      = getcurdir
  FILE_GUID                      = 4ea97c46-7491-4dfd-0027-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  GetCurDir.c

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

[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib

[Protocols]

[BuildOptions]

 

运行结果

getcurdir1

 

说明工作正常。

完整下载

GetCurDir

参考:

1.UEFI Shell Specification July 2, 2014 EFI_SHELL_PROTOCOL.GetCurDir() P24

Step to UEFI (26) —– Shell下Reboot System

研究一下如何 RESETSYSTEM,直接使用 RUNTIMESERVICES 是很好的选择。首先是查看资料 UEFI 2.4 是不二的选择。

7.5.1 Reset System

具体参数解释:EFI_RESET_TYPE 有: EfiResetCold,EfiResetWarm, EfiResetShutdown,和EfiResetPlatformSpecific。Cold是系统级别的完全重启。 Warm也是系统级别的重启,但主要是CPU级别的重启。(具体的话,以前确实有Warm Reset和Cold Reset的差别,但是我感觉实际BIOS设计上,这两个并不是区分的特别清晰。传统上的CPU的Warm Reset有可能导致几百次重启之后系统挂掉,而测试人员根本不会听取你关于他们差别的解释,于是最好的办法仍然是都用Cold Reset来解决)。Shutdown的话,就是关机了。EfiResetPlatformSpecific从解释上来看好像是将一个EFI_GUID指定的Reset类型存放在ResetData中(类似记录重启原因吗?)

EfiResetPlatformSpecific 太复杂,这里只是研究一下简单的 Reset 最后编写程序如下

 

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

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

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

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  gRT -> ResetSystem(EfiResetCold,EFI_SUCCESS,0,NULL);
  return EFI_SUCCESS;
}

运行之后系统就重启了。

下载   MainRST

Step to UEFI (25) —– 命令行参数为什么是Unicode

之前的文章【参考1】,提出了一个问题:为什么 CLIB 下面收到的参数 IN char **Argv 实际上是一个 Unicode ?

为了回答这个问题,还要在代码中寻找答案。同样,追踪一下当我们使用 CLIB 的时候,编译过程中程序被添加了什么。分析方法和之前的类似,我们最终得到下面这个结果:

_ModuleEntryPoint:\MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c 入口还是他

框架没变

ProcessLibraryConstructorList (1)
ProcessModuleEntryPointList (2)
ProcessLibraryDestructorList (3)

(1) ProcessLibraryConstructorList:\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c

  Status = UefiRuntimeServicesTableLibConstructor (ImageHandle, SystemTable);  (1.1)
  Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);(1.2)
  Status = UefiLibConstructor (ImageHandle, SystemTable);	(1.3)
  Status = ShellLibConstructor (ImageHandle, SystemTable);	(1.4)
  Status = UefiHiiServicesLibConstructor (ImageHandle, SystemTable); 	(1.5)
  Status = __wchar_construct (ImageHandle, SystemTable);	(1.6)

 

(1.1) UefiRuntimeServicesTableLibConstructor :\MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c

(1.2) UefiBootServicesTableLibConstructor :\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c

(1.3) UefiLibConstructor :\MdePkg\Library\UefiLib\UefiLib.c

(1.4) ShellLibConstructor :\ShellPkg\Library\UefiShellLib\UefiShellLib.c

ShellLibConstructorWorker //加载一些 Shell Protocol

(1.5) UefiHiiServicesLibConstructor :\MdeModulePkg\Library\UefiHiiServicesLib\UefiHiiServicesLib.c

(1.6) __wchar_construct :\StdLib\LibC\Wchar\ConsDecons.c

(2) ProcessModuleEntryPointList :\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c

	return ShellCEntryLib (ImageHandle, SystemTable); (2.1)

 

(2.1) ShellCEntryLib : \ShellPkg\Library\UefiShellCEntryLib\UefiShellCEntryLib.c

ReturnFromMain = ShellAppMain (
                       EfiShellParametersProtocol->Argc,
                       EfiShellParametersProtocol->Argv
                      );  

 

(2.1.1) ShellAppMain : \StdLib\LibC\Main\Main.c

	ExitVal = (INTN)main( (int)Argc, (wchar_t **)Argv);

 

对照 map 文件可以看到这个main就是我们写的 ArgTest中的Main

0001:000007d0 _ShellGetEnvironmentVariable 00000a50 f UefiShellLib:UefiShellLib.obj
0001:000007eb _ShellIsFile 00000a6b f UefiShellLib:UefiShellLib.obj
0001:00000830 _UefiHiiServicesLibConstructor 00000ab0 f UefiHiiServicesLib:UefiHiiServicesLib.obj
0001:000008a8 _main 00000b28 f ArgTest:ArgTest.obj
0001:00000962 _GetPerformanceCounter 00000be2 f BaseTimerLibNullTemplate:TimerLibNull.obj
0001:00000967 _GetPerformanceCounterProperties 00000be7 f BaseTimerLibNullTemplate:TimerLibNull.obj

(3) ProcessLibraryDestructorList :\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c

	  Status = __wchar_deconstruct (ImageHandle, SystemTable); (3.1)
	  Status = ShellLibDestructor (ImageHandle, SystemTable);  (3.2)

 

(3.1) __wchar_deconstruct :\StdLib\LibC\Wchar\ConsDecons.c
(3.2) ShellLibDestructor :\ShellPkg\Library\UefiShellLib\UefiShellLib.c

上面的调用关系可以用下面的图来简单总结一下

UEFICLIBAPP

====================================分割线====================================

我们在运行期确定是下面的代码来取得参数的

    //
    // try to get shell 1.0 interface instead.
    //
    Status = SystemTable->BootServices->OpenProtocol(ImageHandle,
                               &gEfiShellInterfaceGuid,
                               (VOID **)&EfiShellInterface,
                               ImageHandle,
                               NULL,
                               EFI_OPEN_PROTOCOL_GET_PROTOCOL
                              );
    if (!EFI_ERROR(Status)) {
      //
      // use shell 1.0 interface
      //
      ReturnFromMain = ShellAppMain (
                         EfiShellInterface->Argc,
                         EfiShellInterface->Argv
                        );
    } else {
      ASSERT(FALSE);
    }

 

对于这个EfiShellInterface ,我们可以在 《EFI Shell Developer’s Guide》 找到。

25

因此,可以看到,取出来的Argc就是CHAR16.

参考:

1.http://www.lab-z.com/step-to-uefi-15%EF%BC%89-%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0-again/

Step to UEFI (23) —– ConOut ->OutputString 的换行问题

前面的一篇文章遇到了奇怪的问题,字符串输出看起来很不规整。于是研究一下为什么。

首先,试试 Application 是否也会有这样的显示问题,修改程序如下

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

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

//
// Entry point function 
//
EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"www.lab-z.com\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n");  
 
  return EFI_SUCCESS;
}

 

我们看到有同样的现象。

23a
为了查看汇编级别的程序,我们可以在 ClsTest.inf 加入下面的代码

[BuildOptions]
  MSFT:*_*_IA32_CC_FLAGS  = /Oi- /FAcs

 

真正有效的成分是 /FAcs,这让编译器在编译过程中生成C语言和汇编代码对应的中间文件。

再次编译之后我们可以在 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest 找到 ClsTest.cod

文件。这就是我们想要的。它的内容如下:

; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01 

	TITLE	c:\edk2\AppPkg\Applications\ClsTest\ClsTest.c
	.686P
	.XMM
	include listing.inc
	.model	flat

INCLUDELIB OLDNAMES

PUBLIC	??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ ; `string'
PUBLIC	??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ ; `string'
PUBLIC	??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ ; `string'
PUBLIC	??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ ; `string'
;	COMDAT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '2', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '1', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ DB 'w'
	DB	00H, 'w', 00H, 'w', 00H, '.', 00H, 'l', 00H, 'a', 00H, 'b', 00H
	DB	'-', 00H, 'z', 00H, '.', 00H, 'c', 00H, 'o', 00H, 'm', 00H, 0aH
	DB	00H, 00H, 00H				; `string'
PUBLIC	_UefiMain
; Function compile flags: /Ogspy
; File c:\edk2\apppkg\applications\clstest\clstest.c
;	COMDAT _UefiMain
_TEXT	SEGMENT
_UefiMain PROC						; COMDAT

; 22   :   Print(L"www.lab-z.com\n");

  00000	68 00 00 00 00	 push	 OFFSET ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@
  00005	e8 00 00 00 00	 call	 _Print

; 23   :   gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\n");

  0000a	a1 00 00 00 00	 mov	 eax, DWORD PTR _gST
  0000f	8b 40 2c	 mov	 eax, DWORD PTR [eax+44]
  00012	c7 04 24 00 00
	00 00		 mov	 DWORD PTR [esp], OFFSET ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@
  00019	50		 push	 eax
  0001a	ff 50 04	 call	 DWORD PTR [eax+4]

; 24   :   gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\n");

  0001d	a1 00 00 00 00	 mov	 eax, DWORD PTR _gST
  00022	8b 40 2c	 mov	 eax, DWORD PTR [eax+44]
  00025	68 00 00 00 00	 push	 OFFSET ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@
  0002a	50		 push	 eax
  0002b	ff 50 04	 call	 DWORD PTR [eax+4]

; 25   :   gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n");  

  0002e	a1 00 00 00 00	 mov	 eax, DWORD PTR _gST
  00033	8b 40 2c	 mov	 eax, DWORD PTR [eax+44]
  00036	68 00 00 00 00	 push	 OFFSET ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@
  0003b	50		 push	 eax
  0003c	ff 50 04	 call	 DWORD PTR [eax+4]
  0003f	83 c4 18	 add	 esp, 24			; 00000018H

; 26   :   
; 27   :   return EFI_SUCCESS;

  00042	33 c0		 xor	 eax, eax

; 28   : }

  00044	c3		 ret	 0
_UefiMain ENDP
END

 

特别注意到,编译后,我们定义的字符串汇编写成下面这样形式的Unicode字符串

CONST	SEGMENT
??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS

 

可以看到上面只有 0ah 这是换行的意思,并没有“换行然后切换到下行行首”的意思。

找到原因,我们可以加上切换到行首,就是下面这个样子

  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test4\n\r");    
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test5\n\r");   

 

再编译查看生成的 COD 文件

CONST	SEGMENT
??_C@_1BK@FBECEOIH@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA5?$AA?6?$AA?$AN?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '5', 00H, 0aH, 00H, 0dH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BK@JNOIEOBJ@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA4?$AA?6?$AA?$AN?$AA?$AA@
CONST	SEGMENT
??_C@_1BK@JNOIEOBJ@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA4?$AA?6?$AA?$AN?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '4', 00H, 0aH, 00H, 0dH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '2', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ DB 'L'
	DB	00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H
	DB	's', 00H, 't', 00H, '1', 00H, 0aH, 00H, 00H, 00H ; `string'
CONST	ENDS
;	COMDAT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@
CONST	SEGMENT
??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ DB 'w'
	DB	00H, 'w', 00H, 'w', 00H, '.', 00H, 'l', 00H, 'a', 00H, 'b', 00H
	DB	'-', 00H, 'z', 00H, '.', 00H, 'c', 00H, 'o', 00H, 'm', 00H, 0aH
	DB	00H, 00H, 00H				; `string'

 

运行结果:

23b

最后,提一个问题,如果程序写成这个样子

EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"www.lab-z.com\n");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\r");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\r");
  gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n");  
  
  return EFI_SUCCESS;
}

 

输出结果应该是什么样的呢?

Step to UEFI (22) —– Application的入口分析

研究一下:UEFI APP 在编译的时候会加入什么头。

使用上一次的示例程序 ClrTest。稍微修改一下,去掉清屏的调用以便我们能看清结果:

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

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

//
// Entry point function 
//
EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
   Print(L"www.lab-z.com\n");
   return EFI_SUCCESS;
}

编译命令是 build -p AppPkg\AppPkg.dsc

首先查看生成的 Makefile

在 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\Makefile

下面的语句指定了入口函数

DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP
/ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /DLL
/ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO
/BASE:0 /DRIVER /DEBUG

同样的 Makefile中,给出入口函数

IMAGE_ENTRY_POINT = _ModuleEntryPoint

顺便看一眼 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\OUTPUT\ClsTest.map

__ModuleEntryPoint 00000260 f UefiApplicationEntryPoint:ApplicationEntryPoint.obj

就是说 ModuleEntryPoint 是从 ApplicationEntryPoint.obj 中链接进来的

这个函数的头文件在 \MdePkg\Include\Library\UefiApplicationEntryPoint.h

再进一步查找 \MdePkg\Library\UefiApplicationEntryPoint\UefiApplicationEntryPoint.inf 其中给出了对应的函数体的位置

[Sources]
ApplicationEntryPoint.c

打开看看 \MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c

EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
...............
//
// Call constructor for all libraries.
//
ProcessLibraryConstructorList (ImageHandle, SystemTable);
//
// Call the module's entry point
//
Status = ProcessModuleEntryPointList (ImageHandle, SystemTable);
//
// Process destructor for all libraries.
//
ProcessLibraryDestructorList (ImageHandle, SystemTable);

其中调用了4个函数,下面分别按图索骥

  1. ProcessLibraryConstructorList 函数,他在

\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c

追进去,看一下

VOID
EFIAPI
ProcessLibraryConstructorList (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);
ASSERT_EFI_ERROR (Status);
Status = UefiRuntimeServicesTableLibConstructor (ImageHandle,SystemTable);
ASSERT_EFI_ERROR (Status);
Status = UefiLibConstructor (ImageHandle, SystemTable);
ASSERT_EFI_ERROR (Status);
}

 

1.1 追一下UefiBootServicesTableLibConstructor  发现它在\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c

EFI_STATUS
EFIAPI
UefiBootServicesTableLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Cache the Image Handle
//
gImageHandle = ImageHandle;  //看到这里也能明白之前文章中Extern 的gImageHandle哪里来了
ASSERT (gImageHandle != NULL);
//
// Cache pointer to the EFI System Table
//
gST = SystemTable;
ASSERT (gST != NULL);

//
// Cache pointer to the EFI Boot Services Table
//
gBS = SystemTable->BootServices;
ASSERT (gBS != NULL);

return EFI_SUCCESS;
}

 

1.2   再看看UefiRuntimeServicesTableLibConstructor 在 \MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c

EFI_STATUS
EFIAPI
UefiRuntimeServicesTableLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  //
  // Cache pointer to the EFI Runtime Services Table
  //
  gRT = SystemTable->RuntimeServices;
  ASSERT (gRT != NULL);
  return EFI_SUCCESS;
}

 

1.3   UefiLibConstructor  在\MdePkg\Library\UefiLib\UefiLib.c

EFI_STATUS
EFIAPI
UefiLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  return EFI_SUCCESS;
}

 

2. ProcessModuleEntryPointList在

\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c

EFI_STATUS
EFIAPI
ProcessModuleEntryPointList (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
return UefiMain (ImageHandle, SystemTable); //至此,马上进入到了我们写的函数中
}

 

3.继续追ProcessLibraryDestructorList 这里应该是收尾的一些工作了

\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c

VOID
EFIAPI
ProcessLibraryDestructorList (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{

}

 

上面很罗嗦的说了很多,总结一下可以用下面的图表示调用过程

AppEntry

 

当然,说了很多如果没有实验不能保证正确性

验证的办法是在上述提到的过程里面插入下面的语句

SystemTable->ConOut->OutputString(SystemTable->ConOut,L"LABZ Test X.X\n");

如果输出结果能够正常输出全部插入的,那么表示找到的点是正确的。

结果

appentry2

============================================================

2024年10月9日

还可以在 UefiApplicationEntryPoint.inf文件中加入如下编译指令:


[BuildOptions]
  MSFT:*_*_X64_CC_FLAGS  = /FAsc /Od
  

之后,可以在对应的 Application 的 Build 目录下看到生成的 .COD 文件中出现的代码。

Step to UEFI (21) —– 清屏

想实现一个清屏的功能,刚开始在CLIB中翻了半天没找到,用工具直接搜索了一下 clrscr (应该在 conio.h 中)压根儿没找到。估计是 CLIB没有支持,只好换个方法。想起来Syetem Table中有 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 拿着SPEC翻了【参考1】一下,发现可以使用它的 ClearScreen 函数。

ClearScreen

写一个简单的程序验证之:

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

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

//
// Entry point function 
//
EFI_STATUS
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
   INTN	i;

  for (i=0;i<1000;i++)
    {
	Print(L".");
    }
  Print(L".\n");

  gST -> ConOut -> SetAttribute(gST->ConOut,0x1);
  gST -> ConOut -> ClearScreen(gST->ConOut);
 
  //SystemTable ->ConOut ->ClearScreen(SystemTable ->ConOut);
  //Print(L"%lX\n",SystemTable ->ConOut ->ClearScreen);
  //Print(L"%lX\n",gST -> ConOut -> ClearScreen);
  //Print(L"%lX\n",gST->ConOut);
  return EFI_SUCCESS;
}

 

工作正常,能够清屏。

程序下载 ClsTest

参考:

1.UEFI Spec 2.4 P459

2.http://biosren.com/viewthread.php?tid=3050&highlight=clearscreen 关于SHELL下面修改(前)背景色

Step to UEFI (20) —– 再论“ CLib 获得 ImageHandle”的问题

在翻看之前写的 《Step to UEFI (16) —– CLIB下获得 SystemTable》 【参考1】的时候偶然注意到:

加入头文件 #include Library/UefiBootServicesTableLib.h ,这个头文件的内容是

#ifndef __UEFI_BOOT_SERVICES_TABLE_LIB_H__
#define __UEFI_BOOT_SERVICES_TABLE_LIB_H__

///
/// Cache the Image Handle
///
extern EFI_HANDLE gImageHandle;

///
/// Cache pointer to the EFI System Table
///
extern EFI_SYSTEM_TABLE *gST;

///
/// Cache pointer to the EFI Boot Services Table
///
extern EFI_BOOT_SERVICES *gBS;

#endif

除了之前关注到的各种Table,居然还有一个 gImageHandle !!!

马上动手写了一个程序验证:

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

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

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

//SimpleTextInputEx.harderr
//#define	EFI_SHIFT_STATE_VALID		0x80000000
//#define EFI_LEFT_CONTROL_PRESSED	0x00000002

EFI_STATUS
EFIAPI
NotificationFunction(
	IN EFI_KEY_DATA	*KeyData
)
{
	printf("This is a test from www.lab-z.com \n");		
	return(EFI_SUCCESS);
}

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
	EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleEx;
	EFI_KEY_DATA	KeyData;
	EFI_STATUS		Status;
	EFI_HANDLE		CtrlCNotifyHandle = NULL;
	INTN			i;

 	Status = gBS -> OpenProtocol(
		gST -> ConsoleInHandle,
		&gEfiSimpleTextInputExProtocolGuid,
		(VOID**)&SimpleEx,
		gImageHandle,
		NULL,
		EFI_OPEN_PROTOCOL_GET_PROTOCOL);

	if (EFI_ERROR(Status)) {
		printf("OpenProtocol ERROR!!\n");
	}		

	KeyData.KeyState.KeyToggleState = 0;
	KeyData.Key.ScanCode			= 0;
	KeyData.KeyState.KeyShiftState 	= EFI_SHIFT_STATE_VALID | EFI_LEFT_CONTROL_PRESSED;
	KeyData.Key.UnicodeChar			= L's';

	Status = SimpleEx -> RegisterKeyNotify (
			SimpleEx,
			&KeyData,
			NotificationFunction,
			&CtrlCNotifyHandle);

	for (i=0;i<200;i++)
	 {
		printf("Test\n");		
		gBS -> Stall (5000);
      }

	Status = SimpleEx -> UnregisterKeyNotify(SimpleEx, CtrlCNotifyHandle);

  return EFI_SUCCESS;
}

运行结果和之前文章一样,就是说这个方法是OK的!

mainh2

 

程序下载 Mainh2

==============================================================================

这样的话,之前提到的方法完全就是“脱了裤子放屁—-多此一举”。

另外,再说点好玩的事情,前几天朋友圈里有人转了一篇文章,吹嘘他的“重要”发明“竖版世界地图”。文章的观点基本上是:中国最大的敌人是美国。美国和中国有多远?正常人看普通的世界地图会被误导,以为要横跨太平洋。但是实际上中国和美国如果从北极的方向走才是最近的。结论:看普通的世界地图会导致如此不堪的错误,用我发明的竖版世界地图就万事大吉。文章还在暗示设计北斗导航系统,差点因为看普通的地图而导致大错。

这里放一个“竖版的世界地图”【来自红网,Baidu搜出来的结果】

U9383P704DT20140623085032

怎么说呢,看着是挺别扭的,地球仿佛将菊花对准了你…………

我想看国家疆域,你把一个水球给我干嘛?

上小学和初中时学过一点地理知识。对于地图来说,基本上就是准确的看着不舒服,舒服看着不准确(投影决定的),根本原因是地球不是立方体,再仔细追究丫也不是标准的球体。

又想起来看过一篇文章,经常出国的人看每个国家的地图都觉得很别扭“为毛中国不是在中间?”

除了习惯的力量,还有这种竖版的地图,上面是北还是南?另外,莫非搞卫星的人不知道世界上还有一种“用空间中到定点的距离小于或等于定长的所有点组成图形的结构”结合“通用标记地面地理特性的方法”贴图在一起的通常我们称之为“地球仪”的东西吗?

说到这里,我也想有一个“伟大”的发明,那就是按照地球表面的凸凹形状做一个地球仪—-已经有了?没关系,那我就按照表面G的不同绘制吧。我也编一个故事:NK发射火箭没人考虑G点差别,一直无法满意。后来偶尔最高统帅看到我的地球仪,茅塞顿开………现在我的地球仪对NK是禁运的………

建议将 Google Earth 禁掉,我相信我的发明能更加“伟大”。

参考:

1.http://www.lab-z.com/step-to-uefi-16%EF%BC%89-clib%E4%B8%8B%E8%8E%B7%E5%BE%97-systemtable/ Step to UEFI (16) —– CLIB下获得 SystemTable

USB 控制七段数码管(II)

换个免驱的方式。前面之所以能免驱动,是因为我走的是USB HID协议。除了这个,更常见的是USB Mass Storage协议,就是我们常见的U盘走的协议。对于这个协议在《圈圈教你玩USB》中有详细介绍。下面就讲一下如何把我这个设备移植到MSD协议上。

首先说一下Firmware设计上的改变。

驱动数码管同样使用之前HID中的那种,用一个Timer不断循环点亮每一个数码管。因此,在Main.c中加入Timer0的初始化和中断服务程序。它会按照zBuf中给出来的依次点亮四个数码管。

volatile uint8 zBuf[]={1,2,3,4}; //用来保存8字节的输出报告。

/********************************************************************
函数功能:定时器0初始化,用来做键盘扫描。
入口参数:无。
返    回:无。
备    注:无。
********************************************************************/
void InitTimer0(void)
{
 TMOD&=0xF0;
 TMOD|=0x01;
 ET0=1;
 TR0=1;
}

/********************************************************************
函数功能:定时器0中断处理。
入口参数:无。
返    回:无。
备    注:22.1184M晶体约5ms中断一次。
********************************************************************/
void Timer0Isr(void) interrupt 1
{ 
   static i=0; 

//定时器0重装,定时间隔为5ms,加15是为了修正重装所花费时间
//这个值可以通过软件仿真来确定,在这里设置断点,调整使两次运行
//时间差刚好为5ms即可。
 TH0=(65536-Fclk/1000/12*5+15)/256;	 
 TL0=(65536-Fclk/1000/12*5+15)%256;   // 

	P2=1 << i;
	P1=zBuf[i];
	i++;
	if (i==4) {i=0;}
}
/*******************************************************************/

 

PC对USB Mass Storage 设备传数据,使用的是 WRITE(10) 命令,具体的数据会在批量传输协议中的批量数据阶段传输给设备。具体对应在代码中 SCSI.C 中的 procScsiOutData 中,我们在这里将收到的输局赋值给 zBuf.

/********************************************************************
函数功能:处理输出数据。
入口参数:无。
返    回:无。
备    注:无。
********************************************************************/
void ProcScsiOutData(void)
{
 uint8 Len;
 int i;

 //读端点2数据
 Len=D12ReadEndpointBuffer(4,EP2_SIZE,Ep2Buffer);

 //LabzDebug_Start 
 zBuf[0]=Ep2Buffer[0]; zBuf[1]=Ep2Buffer[1]; zBuf[2]=Ep2Buffer[2]; zBuf[3]=Ep2Buffer[3];

  PrintHex(zBuf[0]);
  PrintHex(zBuf[1]);
  PrintHex(zBuf[2]);
  PrintHex(zBuf[3]);
  Prints("\r\n");

 #ifdef DEBUG1
 Prints("收到 \r\n");
 for ( i=0; i <4;i++)
 {
  PrintHex(*(Ep2Buffer+i));  //如果需要显示调试信息,则显示发送的数据
  if(((i+1)%16)==0)Prints("\r\n"); //每16字节换行一次
  PrintHex(zBuf[0]);
  PrintHex(zBuf[1]);
  PrintHex(zBuf[2]);
  PrintHex(zBuf[3]);
  if((Len%16)!=0)Prints("\r\n"); //换行
 }
 #endif
 //LabZDebug_End
 Ep2DataLength-=Len;
 //清除端点缓冲区
 D12ClearBuffer();
 //由于没有存储器,这里将缓冲区清0模拟数据处理
 while(Len)
 {
  Ep2Buffer[Len]=0; //缓冲区清0
  Len--;
 }
 
 //数据传输完毕,进入到状态阶段
 if(Ep2DataLength==0)
 {
  //此时Ep2DataLength为0,并且处于数据阶段,调用发送数据函数将返回CSW
  Ep2SendData();
 }
}

 

只要做这一点Change,Firmware即可支持。

其次说一下上位机程序的设计。

因为模拟成一个U盘,因此,按照访问硬盘的方式即可传输数据。先是使用CreateFile打开PhysicalDriverX (特别注意:从PhysicalDriver0开始),之后就用 WriteFile 进行写入,特别注意,写入的最小单位是512字节,不能小于这个值。最后CloseHandle关闭即可。

最初,程序设计出来是这样的

CreateFile(PhysicalDrive3)

while Keypressed=false
{
WriteFile (512 Byte)
}

CloseHandle

结果发现非常奇怪,只有第一次能够顺利写入512Bytes,之后会不断出现 Error 5 ERROR_ACCESS_DENIED 5 (0x5) Access is denied. 的问题。为了防止是我设备不稳定造成的,我还用一个U盘进行试验(特别注意,操作会导致U盘内容不可读!!!)在Windows 7 x64和WindowsXP 32 都有同样的现象。请教天杀,他回复“想起来了,Win7的确有这个问题,可以写分区表,但是不能写后续的分区位置,是会被拒绝的。要写数据必须通过分区去写,或者将分区删除。你可以把设备识别成USB HDD,然后没有分区,这样应该可以全盘去写。如果是没有分区的那种U盘形式,是可能会被禁止写入的。”

这样的话,第一个方案就出来了,每次都要用CreateFile打开硬盘,写入之后再Close。

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>

int main(int argc, char *argv[])
{
   HANDLE hFile;
   BOOL fSuccess;
   char txchar[512];
   DWORD iBytesWritten;
   byte Seven_Dig[10]={   0x40,  // = 0
						  0x79,  // = 1	
						  0x24,  // = 2
						  0x30,  // = 3
                          0x19,  // = 4	
						  0x12,  // = 5
						  0x02,  // = 6
                          0x78,  // = 7	
						  0x00,  // = 8
						  0x10,  // = 9
                             }; 



 int n=1234;	
 while (kbhit()==0)
  {
	  hFile = CreateFile(L"\\\\.\\PhysicalDrive2",
                    GENERIC_WRITE,
                    FILE_SHARE_WRITE,    // must be opened with exclusive-access
                    NULL, // no security attributes
                    OPEN_EXISTING, // must use OPEN_EXISTING
                    0,    // not overlapped I/O
                    NULL  // hTemplate must be NULL for comm devices
                    );

   if (hFile == INVALID_HANDLE_VALUE) 
   {
       // Handle the error.
        printf ("CreateFile failed with error %d.\n", GetLastError());
		system("PAUSE");
        return (1);
   }

   txchar[0]=Seven_Dig[n / 1000 % 10];
   txchar[1]=Seven_Dig[n / 100 % 10];
   txchar[2]=Seven_Dig[n / 10 % 10];
   txchar[3]=Seven_Dig[n %10];
								
  for (int i=1;i<512 /4;i++) 
   {
 txchar[i*4]=txchar[0];
 txchar[i*4+1]=txchar[1];
  txchar[i*4+2]=txchar[2];
  txchar[i*4+3]=txchar[3];
   }

fSuccess=WriteFile(hFile, &txchar, 512, &iBytesWritten,NULL);
printf ("done send %d bytes.Status [%d] \n", iBytesWritten,fSuccess);
printf ("Get lasterror %d\n", GetLastError());
printf("%d \n",n);

n=(n++)%10000;
FlushFileBuffers(hFile);
Sleep(1000);
 CloseHandle(hFile);
}//while (kbhit()==0)

   system("PAUSE");


   return (0);
}

 

第二个方案就是每次用WriteFile写入之后,我们再重置指针,再从最开始写入。类似下面的方案。

CreateFile(PhysicalDrive3)

while Keypressed=false
{
SetFilePointer(hFile,0,0,FILE_BEGIN);
WriteFile (512 Byte)
}

CloseHandle

实际的效果和之前的HID的方式相同。

Firmware下载 USB7SegFW

第一种上位机程序下载
exp1

第二种上位机程序下载
exp2

通过WriteFile对硬盘发送数据

下面这个程序首先用 CreateFile 打开 PhysicalDiskX 然后使用 CreateFile 向里面写入数据。

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <conio.h>

int main(int argc, char *argv[])
{
   HANDLE hFile;
   BOOL fSuccess;
   char txchar[512];
   DWORD iBytesWritten;

   hFile = CreateFile(L"\\\\.\\PhysicalDrive1",
                    GENERIC_WRITE,
                    FILE_SHARE_WRITE,    // must be opened with exclusive-access
                    NULL, // no security attributes
                    OPEN_EXISTING, // must use OPEN_EXISTING
                    0,    // not overlapped I/O
                    NULL  // hTemplate must be NULL for comm devices
                    );

   if (hFile == INVALID_HANDLE_VALUE) 
   {
       // Handle the error.
        printf ("CreateFile failed with error %d.\n", GetLastError());
		system("PAUSE");
        return (1);
   }

   txchar[0]='l'; txchar[1]='a';txchar[2]='b';txchar[3]='z';

   //有资料说用这个函数对磁盘写的最小值是 512 bytes 我实验了一下如果小于此会出现error
   fSuccess=WriteFile(hFile, &txchar, 512, &iBytesWritten,NULL);
   printf ("done send %d bytes.Status [%d] \n", iBytesWritten,fSuccess,iBytesWritten);
   printf ("Get lasterror %d\n", GetLastError());

   system("PAUSE");
   CloseHandle(hFile);

   return (0);
}

 

我的实验是在虚拟机中进行的。特别提醒:本次实验可能会损害你的数据,请务必小心。如果系统有安装杀毒软件,有可能被判定为病毒行为。实验的目标盘是一个U盘,盘符为 G:

a

之后,先用 Winhex 打开,我们可以看到开始的几个字符是 abcd

b

运行我们的程序(可以看到发送 512 Bytes Status=1 表示 Success GetLastError=0 表示无错误)
c

我们需要重新打开一次硬盘(WinHex有磁盘修改感知功能,但是在这里不好用)
e

我们可以看到最开始的几个字符被修改了,这说明我们的程序是由效果的的。
d

完整的程序下载:

sendbywritefile

参考:

1.http://msdn.microsoft.com/en-us/library/windows/desktop/aa365747(v=vs.85).aspx MSDN WriteFile function

2.http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx MSDN System Error 对于确定 GetLastError 返回值很有用

3.http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx MSDN CreateFile function

小问题:UEFI 下,Print 怎样显示十进制的64位的无符号数呢?

有人【参考1】,提了一个问题:UEFI 下,Print 怎样显示十进制的64位的无符号数呢?

首先查了一下资料【参考2】

printfll

于是,试试 %llu 看看, ll 表示 long long, u表示十进制无符号数,代码

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{

  printf("%llu\n",0xffffffffL);  

  return EFI_SUCCESS;
}

 

执行结果

quest1

参考:

1.http://biosren.com/viewthread.php?tid=7419&highlight= UEFI 下,Print 怎样显示十进制的64位的无符号数呢?

2.http://www.cplusplus.com/reference/cstdio/printf/