顺丰历险记

我对顺丰的印象很好,很多重要的东西都是用他们来发送。2017年末,给妈妈买了一个手机,12月30日发出,突发奇想,用微信中的顺丰公众号下单,使用蜂巢快递箱发了一次(快递箱是按照大小收费的,小的20中的40大的要60,保价2500收费13元,总共是53,微信支付了),第二天,想查查到哪里了,结果惊奇的发现微信公众号竟然查不到。29日的快递能看到,但是30日是空白。

这个就很奇怪了,急忙给顺丰打电话,接通之后对方就问快递号,我是不知道的,因为从始至终没有收到过,只有一个寄件码,对方说查不到,因为我是用蜂巢发送的,让我找蜂巢;

我打给蜂巢的客服,对方也是先问快递号,我说没有,只有寄件码,对方又问我手机号,一番查询之后又让我联系顺丰,这下我有点着急了,按照这样扯皮,保价也没有用啊;蜂巢随后又打来电话,特别说明了一下:“没有,真没有”。

v2-4a4db82058fd27da225013994c8cd39a_hd

再次打给顺丰,顺丰接通之后对方就问快递号,我是不知道的,因为从始至终没有收到过,只有一个寄件码,对方说查不到。又查询我的手机号码依然没有,刹那间我有了记忆欺骗了我的感觉,但是微信钱包中的支付记录告诉我,一切都是真实的。

v2-b15a8bb6e8f64aded1a38d9dcf9b999f_hd

顺丰客服又给我本地分部的电话,我打过去半天才有人接听。对方还是问了一遍快递号,得知我没有快递号之后,对方直接拒绝了。

没办法又打顺丰客服,接下来的客服又详细问了我收件人的信息,然后告诉我找到了,前面无法找到的原因是:手机号码给我搞错了。这个听起来很奇怪,因为我是微信下单的,除非我选错了发件人,否则不可能出现填写错误手机号的情况。当然,客服也只能给出含糊的解释。终于,我拿到了快递单号,对方也答应给我更改。

高高兴兴以为万事大吉之后又在网上查询了一下那个单号,惊奇的发现,空号。再次拨打顺丰客服,接电话的是一个男的,告知单号之后,他回答,此单正在修改手机号码,需要和发件人核实。剩下的问题就是上面的号码是错的,不知道他怎么核实。我咨询了一下为什么网上查不到单号,他问我“你是用百度查询的吧?”想象中小哥都是下面的表情了:

v2-1b80fa1589ec6472fba3aacdb5bcd573_hd

听到这句话,我简直惊呆了,急忙说是。他回答,现在百度查不到了。要查询,请用微信或者顺丰官网,欢迎来电…….

v2-33e8cfed77f5e3f3e4563eb9da7d9a58_hd

终于,放下电话,我再次到顺丰官网查询,结果却是“快件被退回”,上海到上海。我之前有同事发快递到上海华山路,结果包裹跑到华山旅游了一圈,但是这是要让我的包裹上海郊区游的节奏吗?

v2-2cf4784f45ea10f52dedd750958e3e3c_hd

于是,又打了一次客服,这次对方给我解释,这个状态不是给客人看的,等等等就是相安无事万事大吉的意思。只是除了这个消息还有一个坏消息:顺丰可以修改收件人姓名,发件人姓名还有收件人地址手机号,唯独不能修改发件人手机号。意思是:我还是没有办法直接在微信中跟踪这个包裹。

就这样吧……

v2-581203645f87b37c08d0a8ec98ca82dc_hd

结论:即使发顺丰,也不要使用快递柜。多叫上门,和人类接触很有好处。

v2-24353868722a03863a47fb306e6e5313_hd

v2-7174add2aa9dcbb9c37f3e598755a3e8_hd

TTS 真人发音 SYN6288 模块

通常合成语音技术被称作 TTS (Text To Speech),这样的功能在报站,排队叫号等等场合有着广泛的应用。最近我入手了一个SYN6288的TTS模块,价格50元送喇叭。

image001

具体的说明可以在芯片手册中找到,对于我们来说,最直接的用法就是播放语音。推荐刚拿到模块的时候使用USB串口直接对模块发送下面的数据可以测试是否正常。
image002

从上面也能看出,命令的构成。需要特别注意的是命令参数指定了文本的编码方式,对于 Windows来说默认通常都是 Unicode。另外就是最后用于校验的值,这是对每一个 byte 异或运算获得的。计算方式是: buffer[0]+ buffer[1]+…+ buffer[n]。

我们还需要了解的是 Arduino 中的汉字使用的是UTF8编码 (因为 IDE 是 Unicode),例如:用下面的代码输出“宇音天下”。

char buffer[]="宇音天下";

void setup() {

  Serial1.begin(9600);

  Serial.begin(9600);

}



void loop() {



  for (int i =0;i<sizeof(buffer);i++) {

      Serial.print(buffer[i]&0xFF,HEX);

      Serial.print(' ');

    }



    Serial.println(' ');



    delay(15000);

}

 

结果如下:

E5 AE 87 E9 9F B3 E5 A4 A9 E4 B8 8B 0

其中“宇”UTF8编码:E5AE87,“音” UTF8编码:E99FB3….. 最后还有一个表示结尾的0【参考1】。

然后这个地方就让人晕掉了,为什么资料中给出来的 Unicode 是2Bytes一个汉字而上面给出来的是3个bytes?

终于我在【参考2】找到了答案,原来 Unicode规定了编码方式,但是没有规定如何存储,比如:高位在前还是在后,具体要存储多长。所以具体落地实现有 UTF8 UTF16 等等。对于我们这个情况,Arduino Java 使用的是UTF8,但是模块需要你以Unicode的编号通知它。接下来的问题就是,我有UTF8,如何转化为 Unicode?

先说Unicode转UTF8, 有下面这样的表格

Unicode符号范围     |        UTF-8编码方式

(十六进制)        |              (二进制)

———————-+———————————————

0000 0000-0000 007F | 0xxxxxxx

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

 

还是以汉字严为例,演示如何实现 UTF-8 编码。

严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 – 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。

上面的资料依然来自【参考2】。

然后我们要尝试将E5AE87 转换为 Unicode:

首先,E5AE87 写成二进制就是下面这样
image003

根据上面的表格,切分一下(我们已经知道一个汉字是3 Bytes,所以直接使用第三行)
image004

就是说上面红色框中的对我们来说是多余的,去掉之后我们只剩下下面的数值
image005

这就是 5B87, 网页上查到的也是这样:
image006

接下来的问题就是:为什么上面给出来的0x5B87 而资料给出来的是0x8BED,对于这个问题我还真没有找到答案,0x8BED是“语”的 Unicode。只能猜测一下,要么是写手册的不小心犯错,毕竟发出来声音没人听的出来这两个字的差别;要么就是故意设计成这样。

因此,简单起见,我们可以直接计算要输出的字符,将Arduino 定义的字符串每三个bytes一组,掐头重新拼接成2个bytes。‘

//要输出的字符串

char buffer[]="宇音天下";

//实际汉字长度

#define BSIZE (sizeof(buffer)/3)

//存放转化后的汉字 Unicode值

char character[BSIZE * 2];

&nbsp;

void setup() {

Serial1.begin(9600);

Serial.begin(9600);

delay(3000);

}

&nbsp;

void loop() {

//首先输出一次Arduino 原始字符串 UTF8 的值

for (int i =0;i&lt;BSIZE*3;i++) {

Serial.print(buffer[i]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println(' ');

&nbsp;

//将 UTF8 转化为 Unicode

for (int i =0;i&lt;BSIZE;i=i+1) {

character[i*2]=((buffer[i*3]&amp;0xF)&lt;&lt;4)+((buffer[i*3+1]&gt;&gt;2)&amp;0xF);

character[i*2+1]=((buffer[i*3+1]&amp;0x3)&lt;&lt;6)+(buffer[i*3+2]&amp;0x3F);

Serial.print(character[i*2]&amp;0xFF,HEX);

Serial.print(' ');

Serial.print(character[i*2+1]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println(' ');

delay(15000);

}

 

实验结果就是:
image007

接下来在上面代码的基础上继续修改,集成直接串口发送

//要输出的字符串

char buffer[]="宇音天下";

//实际汉字长度

#define BSIZE (sizeof(buffer)/3)

//存放转化后的汉字 Unicode值

char character[BSIZE * 2];

&nbsp;

//根据字符串计算计算出来的送到串口的值

char output[BSIZE*2+6];

&nbsp;

void setup() {

Serial1.begin(9600);

Serial.begin(9600);

delay(5000);

}

&nbsp;

void loop() {

//首先输出一次Arduino 原始字符串 UTF8 的值

for (int i =0;i&lt;BSIZE*3;i++) {

Serial.print(buffer[i]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println(' ');

//将 UTF8 转化为 Unicode

for (int i =0;i&lt;BSIZE;i=i+1) {

character[i*2]=((buffer[i*3]&amp;0xF)&lt;&lt;4)+((buffer[i*3+1]&gt;&gt;2)&amp;0xF);

character[i*2+1]=((buffer[i*3+1]&amp;0x3)&lt;&lt;6)+(buffer[i*3+2]&amp;0x3F);

Serial.print(character[i*2]&amp;0xFF,HEX);

Serial.print(' ');

Serial.print(character[i*2+1]&amp;0xFF,HEX);

Serial.print(' ');

}

Serial.println("");

&nbsp;

output[0]=0xFD;

output[1]=(BSIZE*2+3)&gt;&gt;8;

output[2]=((BSIZE*2+3)&amp;0xFF);

output[3]=0x01;

output[4]=0x03;

//把字符串定义搬过去

for (int i=0;i&lt;BSIZE*2;i++) {

output[i+5]=character[i];

}

//计算一个校验和

output[BSIZE*2+5]=output[0];

for (int i=1;i&lt;BSIZE*2+5;i++) {

output[BSIZE*2+5]=output[BSIZE*2+5] ^ output[i];

}

&nbsp;

for (int i =0;i&lt;BSIZE*2+6;i++) {

Serial.print(output[i]&amp;0xFF,HEX);

Serial.print(' ');

Serial1.write(output[i]);

}

&nbsp;

Serial.println(' ');

&nbsp;

delay(15000);

}

 

运行结果
image008

工作的视频可以在知乎专栏中看到 https://zhuanlan.zhihu.com/p/32856362

从上面可以看到,Arduino中是有机会将指定定义的中文字符串转换后发送出去的。但是更多时候,我们直接定义每一个字符对应的Unicode即可,虽然不是很直观,但是在编码上会省很多力气。

特别提一下:模块上面有耳机插孔,我插入了一个没有声音,并且芯片迅速发热,怀疑是兼容性上的问题。如果有朋友需要用耳机或者功放,需要特别注意一下(我怀疑是耳机插头什么地方导致短路)。

参考:

  1. http://www.qqxiuzi.cn/bianma/Unicode-UTF.php Unicode和UTF编码转换
  2. http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

20200625 补充

用 FirBeetle (ESP32) 测试,模块使用 3.3v 供电,使用第二个串口的 TX (IO10,D6)对模块发送数据。 具体代码如下:

//要输出的字符串
char buffer[]="七点五十一分";
//实际汉字长度
#define BSIZE (sizeof(buffer)/3)
//存放转化后的汉字 Unicode值
char character[BSIZE * 2];
#include &lt;HardwareSerial.h>

HardwareSerial Serial1(1);

//根据字符串计算计算出来的送到串口的值
char output[BSIZE*2+6];

void setup() {
  Serial1.begin(9600);
      delay(5000);
}

void loop() {
  //首先输出一次Arduino 原始字符串 UTF8 的值
  for (int i =0;i&lt;BSIZE*3;i++) {
     Serial.print(buffer[i]&amp;0xFF,HEX);
      Serial.print(' ');
  }
  Serial.println(' ');
  //将 UTF8 转化为 Unicode
    for (int i =0;i&lt;BSIZE;i=i+1) {
      character[i*2]=((buffer[i*3]&amp;0xF)&lt;&lt;4)+((buffer[i*3+1]>>2)&amp;0xF);
      character[i*2+1]=((buffer[i*3+1]&amp;0x3)&lt;&lt;6)+(buffer[i*3+2]&amp;0x3F);
      Serial.print(character[i*2]&amp;0xFF,HEX);
      Serial.print(' ');
      Serial.print(character[i*2+1]&amp;0xFF,HEX);
      Serial.print(' ');      
    } 
  Serial.println(""); 

  output[0]=0xFD;
  output[1]=(BSIZE*2+3)>>8;
  output[2]=((BSIZE*2+3)&amp;0xFF);
  output[3]=0x01;
  output[4]=0x03;
  //把字符串定义搬过去
  for (int i=0;i&lt;BSIZE*2;i++) {
      output[i+5]=character[i];
    } 
  //计算一个校验和  
  output[BSIZE*2+5]=output[0];
  for (int i=1;i&lt;BSIZE*2+5;i++) {
      output[BSIZE*2+5]=output[BSIZE*2+5] ^ output[i];
    }   
  
  for (int i =0;i&lt;BSIZE*2+6;i++) {
      Serial.print(output[i]&amp;0xFF,HEX);
      Serial.print(' ');
      Serial1.write(output[i]);
    }     
  
Serial.println(' ');

    delay(3000);
}

Step to UEFI (137) 通过 BGRT 取得当前系统的 LOGO

对于BIOS来说,用户能够看到的是非常重要的事情。开机Logo就是这样。自从 Win8.1开始,Windows在进入桌面的时候可以显示用户自定义的Logo,而不是Microsoft自定义的图片,这是很有意思的事情。
最近看了一篇介绍的文章,恍然大悟,原来是BIOS解压自己的Logo在内存中,然后通过ACPI Table将这个Logo传递给Windows,于是开机Logo比以前显示的时间更长更持久。
具体的Table就是 Boot Graphics Resource Table。在 ACPI 6.1的5.2.22有专门的介绍。

bgrt1

bgrt11

根据上面的原理,我们可以编写一个UEFI Application,将内存存放的 Logo Dump出来。具体操作:

1. 找到RSDP,找到 XSDT

bgrt3

2. 在XSDT中检查每一个Entry,根据 Signature 找到BGRT
3. 解析 BGRT ,得到Logo 图像的地址,这个 Logo一定是 BMP格式的
4. 根据BMP格式能够解析出文件大小,直接Memory Dump即可得到结果

完整的代码:

/** @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.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include "acpi.h"
#include "acpi61.h"

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

#pragma pack(push, 1)
/** BGRT structure */
typedef struct {
        EFI_ACPI_DESCRIPTION_HEADER header;
        UINT16 version;
        UINT8 status;
        UINT8 image_type;
        UINT64 image_address;
        UINT32 image_offset_x;
        UINT32 image_offset_y;
} ACPI_BGRT;

/** Bitmap file header */
typedef struct {
        UINT8 magic_BM[2];
        UINT32 file_size;
        UINT8 unused_0x06[4];
        UINT32 pixel_data_offset;
        UINT32 dib_header_size;
        UINT32 width;
        UINT32 height;
        UINT16 planes;
        UINT16 bpp;
} BMP;
#pragma pack(pop)

EFI_GUID        gEfiAcpi20TableGuid =   { 0x8868E871, 0xE4F1, 0x11D3, 
                        { 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};
EFI_GUID        gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2, 
                        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
                        
EFI_STATUS 
SaveToFile(
        IN UINT8 *FileData, 
        IN UINTN FileDataLength)
{
    EFI_STATUS          Status;
    EFI_FILE_PROTOCOL   *FileHandle;
    UINTN               BufferSize;
    EFI_FILE_PROTOCOL   *Root;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;

    Status = gBS->LocateProtocol(
                &gEfiSimpleFileSystemProtocolGuid, 
                NULL,
                (VOID **)&SimpleFileSystem);
                
    if (EFI_ERROR(Status)) {
            Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
            return Status;
    }

    Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"OpenVolume error \r\n");
        return Status;
    }
    Status = Root->Open(
                Root, 
                &FileHandle, 
                L"BIOSLogo.bmp",
                EFI_FILE_MODE_READ |
                EFI_FILE_MODE_WRITE | 
                EFI_FILE_MODE_CREATE, 
                0);
    if (EFI_ERROR(Status)){
        Print(L"Error Open NULL  [%r]\n",Status);
        return Status;
    }
    
    BufferSize = FileDataLength;
    Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
    FileHandle->Close(FileHandle);
    
    return Status;
}
                        
/***
  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;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        EFI_STATUS      Status;
        EFI_ACPI_DESCRIPTION_HEADER                     *XSDT;
        EFI_ACPI_6_1_ROOT_SYSTEM_DESCRIPTION_POINTER    *RSDP;
        UINT8           *p;
        UINTN           Index;
        UINT64          *Entry;
        ACPI_BGRT       *pBGRT;
        BMP             *pBMP;
        
        //1. Find RSDP
        Status=EfiGetSystemConfigurationTable(&gEfiAcpi20TableGuid,(VOID**)&RSDP);
        if(EFI_ERROR(Status)) {
                Print(L"Can't find Acpi Table\n");
                return 0;
        }
        
        //2. Find XSDT
        Print(L"RSDP address [%X]\n",RSDP);
        Print(L"XSDT address [%X]\n",RSDP->XsdtAddress);
        XSDT=(EFI_ACPI_DESCRIPTION_HEADER*)RSDP->XsdtAddress;
        Print(L"XSDT information\n");
        p=(UINT8*)XSDT;
        
        //Show some DSDT information
        Print(L" Signature [%c%c%c%c]\n",*p,*(p+1),*(p+2),*(p+3));
        
        //3.Find entries
        Entry=(UINT64*)&XSDT[1];
        Print(L" Entry 0 @[0x%x]\n",Entry);
        for (Index=0;Index<(XSDT->Length-sizeof(EFI_ACPI_DESCRIPTION_HEADER))/8;Index++) {
           //Print(L" Entry [0x%x]",Index);
           p=(UINT8*)(*Entry);
           //You can show every signature here
           //Print(L" [%x][%c%c%c%c]\n",*Entry,*p,*(p+1),*(p+2),*(p+3));
           if ((*p=='B')&&(*(p+1)=='G')&&(*(p+2)=='R')&&(*(p+3)=='T')) {
                   pBGRT=(ACPI_BGRT*)p;
                   Print(L"  Found BGRT @[0x%X]\n",*Entry);
                   Print(L"  Image address @[0x%X]\n",pBGRT->image_address);
                   //Get BMP address
                   pBMP=(BMP*)(pBGRT->image_address);
                   Print(L"     [0x%X]\n",pBMP);
                   Print(L"     Image size  [0x%X]\n",pBMP->file_size);
                   Print(L"     Data offset [0x%X]\n",pBMP->pixel_data_offset);
                   Print(L"     Header size [0x%X]\n",pBMP->dib_header_size);
                   Print(L"           Width [0x%X]\n",pBMP->width);
                   Print(L"           Height[0x%X]\n",pBMP->height);
                   Print(L"           Planes[0x%X]\n",pBMP->planes);
                   Print(L"           BPP   [0x%X]\n",pBMP->bpp);                   
                    
                   SaveToFile((UINT8*)pBMP,pBMP->file_size);
                   Print(L"BIOS logo has been saved to 'BIOSLogo.bmp'\n");
           }
           Entry++;
        }
        return 0;
}

 

运行结果,测试平台为 Intel Kabylake-R HDK,使用的是 Byo BIOS

bgrt4

(不知道为啥,他家的 Shell分辨率很高, 字体极小,看起来简直要瞎)
取得的 BIOSLogo.bmp 结果如下
bgrt5

完整的代码下载:

FindBGRT

X64 Application下载:

fdefi

特别鸣谢sssky307在之前的文章中给出了EfiGetSystemConfigurationTable函数使得代码能够能够大幅度化简。

最新版本的 RU

本文提供的下载来自:https://firmwaresecurity.com/tag/ru-efi/

d8ec4-20171204183355-bmp

作者Blog 在: http://ruexe.blogspot.tw/ (需要翻墙)
Release 在 https://github.com/JamesAmiTw/ru-uefi

下载 5.20.0328

提起来这个工具的原因是有朋友给我留言说 RU 有查看 ACPI Table 的功能,虽然我几乎天天都在使用但是无印象,于是特地去找了验证一下,真的没有。不过这个版本和之前的相比增加了下面的功能,有需要的朋友可以直接使用,附件中有三个版本:32位、64位、DOS。

1.查看 UEFI Variable
2.AHCI MMIO
3.USB MMIO
4.Mass storage 设备的编辑
5.截图

最后特别感谢作者,来自 AMI 的 James Wang
======================================================================================================
2018年1月12日 来自微信的朋友“耳溫”,在公众号上留言,表示 ALT+F6可以实现 ACPI Table的读取,在此特别感谢指导

UEFI4BIOS_20180110_213822

Step to UEFI (136)哪里来的的 memset

最近编写一个非常简单的代码,遇到奇怪的问题,有兴趣的朋友可以先自己猜一下,答案在后面。
代码如下:

/** @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.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

void prt(IN UINT16 *p)
{
       UINT8   i;
        for (i=0;i<256;i++) {
           Print(L"%c",*p);
           p++;
        }
}
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        CHAR8   buffer[256];
        UINT16  i;
        
        for (i=0;i<256;i++) {
                buffer[i]='L';
        }
        prt((UINT16*)buffer);
        return 0;
}

 

错误信息如下:

“C:\Program Files (x86)\Microsoft Visual Studio 12.0\Vc\bin\x86_amd64\li
b.exe” /NOLOGO /LTCG /OUT:d:\udk2017\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\App
lications\SimpleDemo\SimpleDemo\OUTPUT\wft.lib @d:\udk2017\Build\AppPkg\DEBUG_VS
2013×86\X64\AppPkg\Applications\SimpleDemo\SimpleDemo\OUTPUT\object_files.lst
“C:\Program Files (x86)\Microsoft Visual Studio 12.0\Vc\bin\x86_amd64\li
nk.exe” /OUT:d:\udk2017\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applications\Sim
pleDemo\SimpleDemo\DEBUG\wft.dll /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OP
T:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /
DLL /ENTRY:_ModuleEntryPoint /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BAS
E:0 /DRIVER /DEBUG @d:\udk2017\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applicat
ions\SimpleDemo\SimpleDemo\OUTPUT\static_library_files.lst
Generating code
Finished generating code
UefiApplicationEntryPoint.lib(ApplicationEntryPoint.obj) : error LNK2001: unreso
lved external symbol memset
d:\udk2017\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applications\SimpleDemo\Simpl
eDemo\DEBUG\wft.dll : fatal error LNK1120: 1 unresolved externals
NMAKE : fatal error U1077: ‘”C:\Program Files (x86)\Microsoft Visual Studio 12.0
\Vc\bin\x86_amd64\link.exe”‘ : return code ‘0x460’
Stop.

build…
: error 7000: Failed to execute command
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Vc\bin\nmake.exe /no
logo tbuild [d:\udk2017\Build\AppPkg\DEBUG_VS2013x86\X64\AppPkg\Applications\Sim
pleDemo\SimpleDemo]

build…
: error F002: Failed to build module
d:\udk2017\AppPkg\Applications\SimpleDemo\SimpleDemo.inf [X64, VS2013x86
, DEBUG]

– Failed –
Build end time: 11:17:59, Dec.12 2017
Build total time: 00:00:12

上面就是完整的代码和现象,有兴趣的朋友可以琢磨一下,答案在下面。

 

kenan

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

刚开始看到错误提示我还非常疑惑”为啥没有具体错误信息“。仔细观察才发现错误信息是无法Link到 memset上。但是我的代码没有调用 memset。带着疑惑,我在 Inf 中刚加入 /FAcs /Od 准备查看汇编级代码。但是加入之后错误就会消失。
[BuildOptions]
MSFT:*_*_X64_CC_FLAGS = /FAcs /Od
再仔细研究,原来是 /Od 会让错误消失,接下来就简单了,比较只有 /FAcs 和带有 /Od的汇编代码,在 Simpledemo.cod 文件中找到了答案。左边是没有加入 /Od 的,右边是加入 /Od 的。这个参数的意思是关闭优化。比较打开和关闭优化的结果:

mms

因此,问题就清楚了,因为我的赋值是对 buffer[] 全部写入 “L”,所以编译器会自作主张的用memset 来对内存直接赋值,但是恰好我们没有定义 memset,所以就会出现错误。

介绍一个查看ACPI Table 的工具

Windows下查看 ACPI Table首推的工具当然是 RW_Everything。不过除此之外,今天又发现了一个工具,在 http://www.nirsoft.net/utils/firmware_tables_view.html。

我在 Windows 10 下面测试过,很好用。每种不足的是只列出值,并没有进一步的解析。有兴趣的朋友可以下载。

rdacpi

有32位和64位两个版本:

firmwaretablesview

firmwaretablesview-x64

从原理上说,这样的工具不是从内存中读取,所以使用的时候也无需管理员权限。后面有机会会仔细分析一下。

=======================================================================
2018年1月31日 1.01 版本发现一个 Bug: 如果系统中有多个 SSDT ,那么只能显示第一个。我给作者写邮件了,报告了这个问题。

器材:硕飞 SPI 烧写器

作为一名BIOS工程师,长期以来使用的都是 DediProg 推出的 SF 系列。比如,很多年来,我一直使用的是 SF100,售价在 1800元(5年前的价格,不含税,现在好像是贵了一点)。不过随着BIOS SIZE的增大,烧写时间变得难以忍受,以我日常的经验,16MB 的SPI ROM,烧写时间差不多在 110s左右。
image001

最近,有朋友入手了新型号的刷写工具,据说速度还不错,特地请他拍了一些照片,下面做一些简单的介绍。朋友入手的是硕飞SP16-F 高速量产编程器:
image002

开箱照如下:
image003

产品清单:
编程器主机 1台
脱机专用电源1个
USB连接线 1条
ISP连接线 1条
驱动光盘 1张
上述配件对于日常使用已经足够,此外,最好再选购一个DIP转接座:
image004

主要设备的正面照
image005

和 SF系列不同,采用引脚转出来的方式,上面的是刷写SPI 的排针,右边是 ISP 下载线(可以给诸如 Arduino 之类的单片机系统进行下载)
image006

使用夹子可以直接对主板SPI ROM进行编程。
image007

上面是硬件部分,接下来介绍软件相关内容。第一点:这套工具驱动有数字签名,无需担心安装不上(淘宝上的很多SPI 烧写器居然都没有数字签名,这样在 Win7 64位/ Win 8 /Win 10 下使用都需要关闭数字签名)。
软件界面可以说是中规中矩,各种功能一目了然:
image008

对于芯片有放置提示
image009

实际测试,还可以看到当前芯片的配置信息,
image011

image011

请朋友帮忙测试了一下 SPI CLOCK,在20Mhz:
image012

总体来说速度上应该和 SF 系列的有的一拼,但是个人觉得还是有潜力可挖。比如,下面参数来自GigaDevice 的GD25LQ256D,可以看到正常已经支持到 120Mhz(不知道比 20Mhz 快到哪里去了)。如果能够实现这样的速度,对于BIOS研发是一大福音。
image013

这个烧录器的手册可以在下面下载,有兴趣的朋友可以看看。

链接: https://pan.baidu.com/s/1pLQgdZ1 密码: rx68

参考:
1. http://www.gigadevice.com/product/detail/6/551.html?locale=en_US

============================================================================================================================
2018年2月9日 找了一个 SF600 测试了一下,确实比 SF100快。刷写 16MB 加校验 62s,SF100通常要100s 甚至更长。

IMG_20180209_131133

sf600

Step to UEFI (135)EFI_CPU_ARCH_PROTOCOL 注册的 INTx

前面提到的 EFI_CPU_ARCH_PROTOCOL ,这次试试这个Protocol RegisterInterrupt,注册一个我们自己的中断试试。

rd3

为了完成这个目标,我们设计下面的方案:
1. 编写一个驱动,在这个驱动里面注册我们自己的 Interrupt,我使用的是 0x41 作为中断向量(经过检查,实际机器上使用了0x40作为给HPET Timer的Interrupt,这里我们选择一个不冲突的即可)。方便起见,选择使用之前我们设计的 Print9 Protocol 的代码,这个代码会在系统中注册我们自己定义的 Protocol,然后这个 Protocol 中留出来一个输出变量(Var2)的函数,以便调用。我们的InterruptHandler也在这个文件中,内容很简单,就是将 Var2 加一;
2. 编写一个 Application 来在系统中查找上面驱动注册的Protocol,找到之后调用输出变量的函数,这样我们可以知道这个函数是否有变化;
3. 最后再编写一个 Application 用来产生 int 0x41。
首先是第一个的代码,这个需要放置在 MdeModule 中编译而不是我们经常用的 AppPkg中,插入在 MdeModulePkg.dsc 文件中

  MdeModulePkg/Library/DxeCapsuleLibFmp/DxeCapsuleLib.inf
  MdeModulePkg/Library/DxeCapsuleLibFmp/DxeRuntimeCapsuleLib.inf

#LabZDebug_Start
  MdeModulePkg/PrintDriver3/PrintDriver3.inf
#LabZDebug_End

 [Components.IA32, Components.X64, Components.IPF, Components.AARCH64]
  MdeModulePkg/Universal/Network/UefiPxeBcDxe/UefiPxeBcDxe.inf
  MdeModulePkg/Universal/DebugSupportDxe/DebugSupportDxe.inf

 

代码如下:

/** @file
  This driver produces Print9 protocol layered on top of the PrintLib from the MdePkg.

Copyright (c) 2009, 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.

**/

#include <PiDxe.h>
#include <Library/UefiLib.h>

#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
#include "Print9.h"

#include <Protocol/Cpu.h>
#include <Library/CpuLib.h>

extern EFI_SYSTEM_TABLE         *gST;

EFI_GUID gEfiPrint9ProtocolGuid =
                { 0xf05976ef, 0x83f1, 0x4f3d, 
                  { 0x86, 0x19, 0xf7, 0x59,0x5d, 0x41, 0xe5, 0x61 } };

EFI_GUID gEfiCpuArchProtocolGuid = 
                { 0x26BACCB1, 0x6F42, 0x11D4, 
                  { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};

EFI_PRINT9_PRIVATE_DATA         *Image;
EFI_HANDLE  mPrintThunkHandle   = NULL;

//Copied from \MdeModulePkg\Library\DxePrintLibPrint2Protocol\PrintLib.c
UINTN
EFIAPI
MyPrint ()
{
  CHAR16  *Buffer=L"1 2 3 4 5 6 7 8 9 0 A B C E D F ";
  
  UnicodeSPrint(Buffer,16,L"%d\r\n",Image->Var2);
  gST->ConOut->OutputString(gST->ConOut,Buffer); 
  
  return 0;
}

VOID
EFIAPI
MyInterruptHandler (
  IN EFI_EXCEPTION_TYPE   InterruptType,
  IN EFI_SYSTEM_CONTEXT   SystemContext
  )
{
       Image->Var2++; 
}  
/**
  The user Entry Point for Print module.

  This is the entry point for Print DXE Driver. It installs the Print2 Protocol.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval Others            Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
PrintEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
        EFI_STATUS              Status;
        EFI_CPU_ARCH_PROTOCOL  *Cpu;
        
        //
        // Allocate a new image structure
        //
        Image = AllocateZeroPool (sizeof(EFI_PRINT9_PRIVATE_DATA));
        if (Image == NULL) {
                Status = EFI_OUT_OF_RESOURCES;
                ASSERT_EFI_ERROR (Status);
        }

        Image->Signature         = PRINT9_PRIVATE_DATA_SIGNATURE;
  
        Image->PRINT9.UnicodeBSPrint=UnicodeBSPrint;
        Image->PRINT9.UnicodeSPrint=UnicodeSPrint;
        Image->PRINT9.UnicodeBSPrintAsciiFormat=UnicodeBSPrintAsciiFormat;
        Image->PRINT9.UnicodeSPrintAsciiFormat=MyPrint;
        //Image->PRINT9.UnicodeValueToString=UnicodeValueToString;
        Image->PRINT9.AsciiBSPrint=AsciiBSPrint;
        Image->PRINT9.AsciiSPrint=AsciiSPrint;        
        Image->PRINT9.AsciiBSPrintUnicodeFormat=AsciiBSPrintUnicodeFormat;
        Image->PRINT9.AsciiSPrintUnicodeFormat=AsciiSPrintUnicodeFormat;
        //Image->PRINT9.AsciiValueToString=AsciiValueToString;

        Image->Var2=1984;
        
        Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrintThunkHandle,
                  &gEfiPrint9ProtocolGuid, 
                  &Image->PRINT9,
                  NULL
                );
        ASSERT_EFI_ERROR (Status);

        //
        // Locate the Cpu Arch Protocol.
        //
        Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, &Cpu);
        ASSERT_EFI_ERROR (Status);

        Status = Cpu->RegisterInterruptHandler (Cpu, 0x41, MyInterruptHandler);
        ASSERT_EFI_ERROR (Status);
        
  return Status;
}

 

上述代码有两个需要特别注意的地方:
1. 之前的 Print9代码是在 UDK2014中编译通过的,但是在 UDK2017中无法编译通过,根本原因是 UDK2017中因为安全原因删除了 UnicodeValueToString 和AsciiValueToString两个函数,对我们来说,在代码中注视掉这两个函数不使用即可;
2. MyPrint 是我们输出函数,他会输出 Image->Var2 的值;
3. MyInterruptHandler 是我们的中断函数,里面只是简单的对 Image->Var2 加一。

第二个代码,在系统中查找我们自定义的 Print9Protocol,相对来说简单多了:

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

#include "Print9.h"

EFI_GUID gEfiPrint9ProtocolGuid =
                { 0xf05976ef, 0x83f1, 0x4f3d, 
                  { 0x86, 0x19, 0xf7, 0x59, 
                    0x5d, 0x41, 0xe5, 0x61 } };

extern EFI_BOOT_SERVICES         *gBS;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
        EFI_PRINT9_PROTOCOL     *Print9Protocol;
        EFI_STATUS              Status;

        // Search for the Print9 Protocol
        Status = gBS->LocateProtocol(
                        &gEfiPrint9ProtocolGuid,
                        NULL,
                        (VOID **)&Print9Protocol
                );
        if (EFI_ERROR(Status)) {
                Print(L"Can't find Print9Protocol.\n");
                return EFI_SUCCESS;
        }
        
        Print(L"Find Print9Protocol.\n"); 
        Print9Protocol->UnicodeSPrintAsciiFormat();
        Print(L"\n"); 
        return EFI_SUCCESS;
}

 

第三个代码,发出 Int 0x41 中断。起初我打算使用 int al 这样的指令,后来查了一下手册,原来 int 后面只能接立即数,于是直接写成 int 0x41 。因为,Vistual Studio 的 X64无法使用内嵌汇编,我们只好单独写一个 asm 出来。

intx1

IntX.c:

/** @file
  Simple interrupt test.

Copyright (c) 2013, 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.

**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>

#include "IntX.h"

/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        Print (L"Generate a Interrupt\n");
        
        SimpleInterrupt();
        
        return EFI_SUCCESS;
}

 

IntDemo.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                      = IntDemo
  FILE_GUID                      = 4ea97c01-7491-4dfd-0090-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = UefiMain

#   
#  VALID_ARCHITECTURES           = X64
#

[Sources.common]
  IntX.c
  IntX.h

[Sources.IA32]

[Sources.X64]
  X64/AsmInt.asm

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

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

 

用来定义 Int X 的源代码 \x64\AsmInt.asm

;------------------------------------------------------------------------------
;
; Copyright (c) 2013, 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.
;
; Module Name:
;
;   AsmRdRand.Asm
;
; Abstract:
;
;   Generate an Interrupt by INT x
;
; Notes:
;
;   Visual Studio coding practices do not use inline asm since multiple compilers and 
;   architectures are supported assembler not recognizing rdrand instruction so using DB's.
;
;------------------------------------------------------------------------------

    .code
 
;------------------------------------------------------------------------------
;  Generates a interrupt by Int(CD).
;------------------------------------------------------------------------------
SimpleInterrupt  PROC
    int         0x41
    ret
SimpleInterrupt ENDP

    END

 

最终运行结果,可以看到每次运行 IntDemo之后,会有中断触发,数值不断变大。
intx2
完整的代码和编译后的 efi 文件下载(我只在X64 上测试过,NT32模拟环境不支持)
IntTest

Step to UEFI (134)多一个时间中断

前面的实验已经证明:UEFI 架构本身只有一个中断,那就是时间中断,在实体机上测试显示使用的是 HPET 的时间中断。那么我们是否可以再给UEFI多加一个中断?答案是肯定的,只要硬件上有支持,我们就可以多加入一个中断。这次我们实验的就是 8254 上面的中断。8254 根据我的理解,就是一个能够发出脉冲的时间芯片。具体脉冲发出来了,还需要有一个中断芯片来管理这些中断,在 Leagcy 的X86中,这个工作是 8259 来完成的,下面就是各种微机原理都会展示的图片。因为中断比较多,所以需要2片来级联进行扩展。对于我们的想法来说,只要知道8254 是接在第一个(主,Master) 8259的Pin0 上(IRQ0)即可。
mi1

一直困扰我的问题是 IRQx对应在IDT 中的哪一个,直到最近看到了 EFI_LEGACY_8259_PROTOCOL 中的 GetVector 函数【参考1】:
mi2
mi3

这个函数的作用就是返回当前IRQ 的Vector,也是就是IDT中的入口号。
了解了上面一些,就可以动手写程序了。使用我们之前的 PrintDriver3 的架构,使用 8259 的 Protocol来完成 8254 的初始化,设定 Interrupt 的Handler 是 MyInterruptHander,在这个函数中对 Var2 自增,最后使用之前的 PDT3 Application 打印 Var2 的数值。运行起来我们的Driver 之后,8254会不断发送中断出来,直观的说就是代码会运行到MyInterruptHander中,我们在这个函数中对于做标记的变量自增,最后用另外的 Protocol 找到这个变量并且打印出来。
完整代码如下:

/** @file
  This driver produces Print9 protocol layered on top of the PrintLib from the MdePkg.

Copyright (c) 2009, 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.

**/

#include <PiDxe.h>
#include <Library/UefiLib.h>

#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
#include "Print9.h"

#include <Library/IoLib.h>
#include <Protocol/Cpu.h>
#include <Library/CpuLib.h>
#include "Legacy8259.h"

extern EFI_SYSTEM_TABLE         *gST;

EFI_GUID gEfiPrint9ProtocolGuid =
                { 0xf05976ef, 0x83f1, 0x4f3d, 
                  { 0x86, 0x19, 0xf7, 0x59,0x5d, 0x41, 0xe5, 0x61 } };

EFI_GUID gEfiCpuArchProtocolGuid = 
                { 0x26BACCB1, 0x6F42, 0x11D4, 
                  { 0xBC, 0xE7, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};

// Include/Protocol/Legacy8259.h
EFI_GUID gEfiLegacy8259ProtocolGuid     = 
                { 0x38321dba, 0x4fe0, 0x4e17, 
                  { 0x8a, 0xec, 0x41, 0x30, 0x55, 0xea, 0xed, 0xc1 }};

//\PcAtChipsetPkg\8254TimerDxe\Timer.h
//
// The maximum tick duration for 8254 timer
//
#define MAX_TIMER_TICK_DURATION     549254
//
// The default timer tick duration is set to 10 ms = 100000 100 ns units
//
#define DEFAULT_TIMER_TICK_DURATION 100000
#define TIMER_CONTROL_PORT          0x43
#define TIMER0_COUNT_PORT           0x40

//
// The current period of the timer interrupt
//
volatile UINT64           mTimerPeriod = 0;
                  
EFI_PRINT9_PRIVATE_DATA         *Image;
EFI_HANDLE  mPrintThunkHandle   = NULL;
//
// Pointer to the Legacy 8259 Protocol instance
//
EFI_LEGACY_8259_PROTOCOL  *mLegacy8259;

//Copied from \MdeModulePkg\Library\DxePrintLibPrint2Protocol\PrintLib.c
UINTN
EFIAPI
MyPrint ()
{
  CHAR16  *Buffer=L"1 2 3 4 5 6 7 8 9 0 A B C E D F ";
  
  UnicodeSPrint(Buffer,16,L"%d\r\n",Image->Var2);
  gST->ConOut->OutputString(gST->ConOut,Buffer); 
  
  return 0;
}

//
// Worker Functions
//
/**
  Sets the counter value for Timer #0 in a legacy 8254 timer.

  @param Count    The 16-bit counter value to program into Timer #0 of the legacy 8254 timer.
**/
VOID
SetPitCount (
  IN UINT16  Count
  )
{
  IoWrite8 (TIMER_CONTROL_PORT, 0x36);
  IoWrite8 (TIMER0_COUNT_PORT, (UINT8)(Count & 0xff));
  IoWrite8 (TIMER0_COUNT_PORT, (UINT8)((Count >> 8) & 0xff));
}

/**

  This function adjusts the period of timer interrupts to the value specified
  by TimerPeriod.  If the timer period is updated, then the selected timer
  period is stored in EFI_TIMER.TimerPeriod, and EFI_SUCCESS is returned.  If
  the timer hardware is not programmable, then EFI_UNSUPPORTED is returned.
  If an error occurs while attempting to update the timer period, then the
  timer hardware will be put back in its state prior to this call, and
  EFI_DEVICE_ERROR is returned.  If TimerPeriod is 0, then the timer interrupt
  is disabled.  This is not the same as disabling the CPU's interrupts.
  Instead, it must either turn off the timer hardware, or it must adjust the
  interrupt controller so that a CPU interrupt is not generated when the timer
  interrupt fires.


  @param This            The EFI_TIMER_ARCH_PROTOCOL instance.
  @param TimerPeriod     The rate to program the timer interrupt in 100 nS units.  If
                         the timer hardware is not programmable, then EFI_UNSUPPORTED is
                         returned.  If the timer is programmable, then the timer period
                         will be rounded up to the nearest timer period that is supported
                         by the timer hardware.  If TimerPeriod is set to 0, then the
                         timer interrupts will be disabled.

  @retval        EFI_SUCCESS       The timer period was changed.
  @retval        EFI_UNSUPPORTED   The platform cannot change the period of the timer interrupt.
  @retval        EFI_DEVICE_ERROR  The timer period could not be changed due to a device error.

**/
EFI_STATUS
EFIAPI
TimerDriverSetTimerPeriod (
  IN UINT64                   TimerPeriod
  )
{
  UINT64  TimerCount;

  //
  //  The basic clock is 1.19318 MHz or 0.119318 ticks per 100 ns.
  //  TimerPeriod * 0.119318 = 8254 timer divisor. Using integer arithmetic
  //  TimerCount = (TimerPeriod * 119318)/1000000.
  //
  //  Round up to next highest integer. This guarantees that the timer is
  //  equal to or slightly longer than the requested time.
  //  TimerCount = ((TimerPeriod * 119318) + 500000)/1000000
  //
  // Note that a TimerCount of 0 is equivalent to a count of 65,536
  //
  // Since TimerCount is limited to 16 bits for IA32, TimerPeriod is limited
  // to 20 bits.
  //
  if (TimerPeriod == 0) {
    //
    // Disable timer interrupt for a TimerPeriod of 0
    //
    mLegacy8259->DisableIrq (mLegacy8259, Efi8259Irq0);
  } else {

    //
    // Convert TimerPeriod into 8254 counts
    //
    TimerCount = DivU64x32 (MultU64x32 (119318, (UINT32) TimerPeriod) + 500000, 1000000);

    //
    // Check for overflow
    //
    if (TimerCount >= 65536) {
      TimerCount = 0;
      TimerPeriod = MAX_TIMER_TICK_DURATION;
    }
    //
    // Program the 8254 timer with the new count value
    //
    SetPitCount ((UINT16) TimerCount);

    //
    // Enable timer interrupt
    //
    mLegacy8259->EnableIrq (mLegacy8259, Efi8259Irq0, FALSE);
  }
  //
  // Save the new timer period
  //
  mTimerPeriod = TimerPeriod;

  return EFI_SUCCESS;
}


VOID
EFIAPI
MyInterruptHandler (
  IN EFI_EXCEPTION_TYPE   InterruptType,
  IN EFI_SYSTEM_CONTEXT   SystemContext
  )
{
          EFI_TPL OriginalTPL;

  OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);

  mLegacy8259->EndOfInterrupt (mLegacy8259, Efi8259Irq0);

       Image->Var2++; 

  gBS->RestoreTPL (OriginalTPL);

}  
/**
  The user Entry Point for Print module.

  This is the entry point for Print DXE Driver. It installs the Print2 Protocol.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval Others            Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
PrintEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
        EFI_STATUS              Status;
        EFI_CPU_ARCH_PROTOCOL  *mCpu;
        UINT32                  TimerVector;

        //
        // Allocate a new image structure
        //
        Image = AllocateZeroPool (sizeof(EFI_PRINT9_PRIVATE_DATA));
        if (Image == NULL) {
                Status = EFI_OUT_OF_RESOURCES;
                ASSERT_EFI_ERROR (Status);
        }

        Image->Signature         = PRINT9_PRIVATE_DATA_SIGNATURE;
  
        Image->PRINT9.UnicodeBSPrint=UnicodeBSPrint;
        Image->PRINT9.UnicodeSPrint=UnicodeSPrint;
        Image->PRINT9.UnicodeBSPrintAsciiFormat=UnicodeBSPrintAsciiFormat;
        Image->PRINT9.UnicodeSPrintAsciiFormat=MyPrint;
        //Image->PRINT9.UnicodeValueToString=UnicodeValueToString;
        Image->PRINT9.AsciiBSPrint=AsciiBSPrint;
        Image->PRINT9.AsciiSPrint=AsciiSPrint;        
        Image->PRINT9.AsciiBSPrintUnicodeFormat=AsciiBSPrintUnicodeFormat;
        Image->PRINT9.AsciiSPrintUnicodeFormat=AsciiSPrintUnicodeFormat;
        //Image->PRINT9.AsciiValueToString=AsciiValueToString;

        Image->Var2=1984;
        
        Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrintThunkHandle,
                  &gEfiPrint9ProtocolGuid, 
                  &Image->PRINT9,
                  NULL
                );
        ASSERT_EFI_ERROR (Status);

        //
        // Locate the Cpu Arch Protocol.
        //
        Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, &mCpu);
        ASSERT_EFI_ERROR (Status);

        //
        // Find the Legacy8259 protocol.
        //
        Status = gBS->LocateProtocol (&gEfiLegacy8259ProtocolGuid, NULL, (VOID **) &mLegacy8259);
        ASSERT_EFI_ERROR (Status);        
        
        //
        // Force the timer to be disabled
        //
        Status = TimerDriverSetTimerPeriod (0);
        ASSERT_EFI_ERROR (Status);        
        
        //
        // Get the interrupt vector number corresponding to IRQ0 from the 8259 driver
        //
        TimerVector = 0;
        Status      = mLegacy8259->GetVector (mLegacy8259, Efi8259Irq0, (UINT8 *) &TimerVector);
        ASSERT_EFI_ERROR (Status);

        //
        // Install interrupt handler for 8254 Timer #0 (ISA IRQ0)
        //
        Status = mCpu->RegisterInterruptHandler (mCpu, TimerVector, MyInterruptHandler);
        ASSERT_EFI_ERROR (Status);

        //
        // Force the timer to be enabled at its default period
        //
        Status = TimerDriverSetTimerPeriod (DEFAULT_TIMER_TICK_DURATION);
        ASSERT_EFI_ERROR (Status);        
        
  return Status;
}

 

运行结果(实体机):
mi4
从运行结果来看,代码中设定的定时器能够正常工作和我们的预期一致。
8254初始化部分代码拷贝自 8254TimerDxe\Timer.c ,很多具体操作的含义并不清楚,有兴趣的朋友可以找一本《微机原理》的书籍对照研究一下。X86架构依然兼容很久之前的 8254、8259的设计。这样的兼容性既是X86的优点,保证了过去的代码依然能够运行;同时也是X86沉重的历史包袱,而后面这一点更加是BIOS存在的意义。

EFI (X64)下载
printdriver4
源代码
PrintDriver4SRC

参考:
1. Intel® Platform Innovation Framework for UEFI Compatibility Support Module Specification