几款常见播放器的功耗比较

最近看到实验室有测试功耗的设备,于是进行了一下几款常见播放器的功耗比较。

先说说试验设备,用的是下面这款设备

YOKOGAWA WT310E Digital Power Meter
通过 WT310E 给设备(WHL HDK)提供 220V的电源,然后功耗信息通过 USB接口传输到另外的笔记本电脑上进行记录。测试机台通过 HDMI 显示器输出显示。显示器额外供电。

测试结果如下:

IDLE 是静止在桌面的情况下的功耗

可以看到,Windows 自带播放器表现最好。表现最差的是 QQPlayer 和 AiQiyi。产生的原因可能是 Windows 和硬件结合紧密能够充分发挥省电的功能。 QQPlayer 和 AiQiyi 应该是CPU解码。这样能够实现跨平台,一套代码到处运行。

UEFI Tips: PciIO.Read 的问题

最近在编写一个小程序,需要读取PCI Configuration Space 上的寄存器。但是发现运行之后会死机,死机的位置在下面的调用中:

           Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        Index*4,
                        Sizeof(tmp),
                        &tmp);

但是如果使用EfiPciIoWidthUint8, EfiPciIoWidthUint16都是能够正常工作的。
经过一番研究,找到了问题所在,首先看一下EFI PCI IO PROTOCOL 的定义【参考1】:

typedef struct _EFI_PCI_IO_PROTOCOL {
  EFI_PCI_IO_PROTOCOL_POLL_IO_MEM        PollMem;
  EFI_PCI_IO_PROTOCOL_POLL_IO_MEM        PollIo;
  EFI_PCI_IO_PROTOCOL_ACCESS             Mem;
  EFI_PCI_IO_PROTOCOL_ACCESS             Io;
  EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS      Pci;
  EFI_PCI_IO_PROTOCOL_COPY_MEM           CopyMem;
  EFI_PCI_IO_PROTOCOL_MAP                Map;
  EFI_PCI_IO_PROTOCOL_UNMAP              Unmap;
  EFI_PCI_IO_PROTOCOL_ALLOCATE_BUFFER    AllocateBuffer;
  EFI_PCI_IO_PROTOCOL_FREE_BUFFER        FreeBuffer;
  EFI_PCI_IO_PROTOCOL_FLUSH              Flush;
  EFI_PCI_IO_PROTOCOL_GET_LOCATION       GetLocation;
  EFI_PCI_IO_PROTOCOL_ATTRIBUTES         Attributes;
  EFI_PCI_IO_PROTOCOL_GET_BAR_ATTRIBUTES GetBarAttributes;
  EFI_PCI_IO_PROTOCOL_SET_BAR_ATTRIBUTES SetBarAttributes;
  UINT64                                 RomSize;
  VOID                                   *RomImage;
} EFI_PCI_IO_PROTOCOL;

我们需要关注的是EFI_PCI_IO_PROTOCOL_CONFIG_ACCESS Pci;

typedef
EFI_STATUS
(EFIAPI *EFI_PCI_IO_PROTOCOL_MEM) (
IN EFI_PCI_IO_PROTOCOL *This,
IN EFI_PCI_IO_PROTOCOL_WIDTH Width,
IN UINT8 BarIndex,
IN UINT64 Offset,
IN UINTN Count,
IN OUT VOID *Buffer
);

其中的参数定义如下:

仔细阅读会发现, Count的意思是有多少个 Width大小的元素,对于上面的代码来说,我只是想要1个32bits长度的INTU32。因此,Count 应该是1而不是4。
最终,完整的代码:

#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/PciIo.h>
#include <IndustryStandard/Pci22.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_BOOT_SERVICES       *gBS;
extern EFI_HANDLE               gImageHandle;
       
        
/***
  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.
***/
int
main (
  IN int Argc,
  IN char **Argv
  )
{

    EFI_STATUS  Status;
    EFI_PCI_IO_PROTOCOL       *PciIo;
    UINTN       Seg,Bus,Dev,Fun;
    UINT32      Index;
    UINT32     tmp;
     
    Status = gBS->LocateProtocol(
                        &gEfiPciIoProtocolGuid,
                        NULL,
                        (VOID **) &PciIo);

    if (EFI_ERROR(Status)) {
        Print(L"Couldn't find PCIIO Protocol\n");
        return EFI_SUCCESS;
    } 

    PciIo->GetLocation(PciIo,&Seg,&Bus,&Dev,&Fun);
    Print(L"Found PCI controller Bus[%d] Dev[%d] Fun[%d]\n",
          Bus,Dev,Fun);
    
    for (Index = 0; Index < 256/4; Index++) {
           Status = PciIo->Pci.Read (
                        PciIo,
                        EfiPciIoWidthUint32,
                        Index*4,
                        1,
                        &tmp);
           Print(L"%08X ",tmp);
           if ((Index+1)%4==0) {Print(L"\n");}
    }        
  return 0;
}

运行结果:

完整代码下载:
PCIIOTest


参考:
1. http://wiki.phoenix.com/wiki/index.php/EFI_PCI_IO_PROTOCOL

Arduino 使用 AT24C02 存储

通常情况下我们无需使用外部EEPROM 存储数据,因为 Arduino 本身有自带的。也许是因为这个原因我在网上搜索 Arduino 使用 AT24C02 的资料也很少。

最终在 https://github.com/tardate/Littl … 4C02/BasicReadWrite 搜索到了一个例子。

先说连接方法:

元件引脚如下:

A0-A2 用于选择设备的地址,如果都为LOW,那么地址是 0x50

WP 是写保护,接HIGH 之后才能进行写入

/*
  EEPROM/AT24C02/BasicReadWrite
  Basic read/write operations to external EEPROM (AT24C02) with the Wire library
  Note: the addressing protocol used here is specific to AT24 models under 16k
  For info and circuit diagrams see https://github.com/tardate/Littl ... 4C02/BasicReadWrite
 */
 
#include <Wire.h>
 
#define DEVICE_ADDRESS 0x50 // must match AT24C02(A0,A1,A2) wiring
#define MEMORY_ADDRESS 0
 
byte data;
 
void setup() {
  Serial.begin(115200);
  Wire.begin();
 
  data = load_or_init_byte(MEMORY_ADDRESS);
  Serial.print("Memory on startup: ");
  Serial.println(data, DEC);
}
 
void loop() {
  data++;
  store_byte(MEMORY_ADDRESS, data);
  Serial.print("Storing: ");
  Serial.println(data, DEC);
  Serial.print("*Stored: ");
  Serial.println(load_byte(MEMORY_ADDRESS), DEC);
  delay(1000);
}
 
byte load_or_init_byte(uint8_t eeaddress) {
  byte data = load_byte(eeaddress);
  if(data==0xFF) {
    data = 0;
    store_byte(eeaddress, data);
  }
  return data;
}
 
// store +data+ byte at +eeaddress+
void store_byte(uint8_t eeaddress, byte data) {
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write(eeaddress);
  Wire.write(data);
  Wire.endTransmission();
 
  delay(20);
}
 
byte load_byte(uint8_t eeaddress) {
  byte data = 0xFF;
 
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write(eeaddress);
  Wire.endTransmission();
 
  Wire.requestFrom(DEVICE_ADDRESS,1);
 
  if (Wire.available()) data = Wire.read();
 
  return data;
}

UEFI Tips 千万不要在代码中写中文注释

理论上写中文注释是没问题的,因为 VS 是支持中文的。但是,很多用于 Build 的工具并没有考虑这种情况,因此会导致稀奇古怪的问题。最近我遇到了一个编译错误

C:\BuildBs\201903>build -a X64
Build environment: Windows-10-10.0.16299-SP0
Build start time: 09:15:25, Jun.02 2019

WORKSPACE        = c:\buildbs\201903
EDK_TOOLS_PATH   = c:\buildbs\201903\basetools
EDK_TOOLS_BIN    = c:\buildbs\201903\basetools\bin\win32
CONF_PATH        = c:\buildbs\201903\conf
PYTHON_COMMAND   = py -3


Architecture(s)  = X64
Build target     = DEBUG
Toolchain        = VS2015x86

Active Platform          = c:\buildbs\201903\Nt32Pkg\Nt32Pkg.dsc
Flash Image Definition   = c:\buildbs\201903\Nt32Pkg\Nt32Pkg.fdf

Processing meta-data .....


build.py...
 : error C0DE: Unknown fatal error when processing [c:\buildbs\201903\MdeModulePkg\Application\UiApp\UiApp.inf]

(Please send email to edk2-devel@lists.01.org for help, attaching following call stack trace!)

(Python 3.6.6 on win32) Traceback (most recent call last):
  File "C:\BuildBs\201903\BaseTools\Source\Python\build\build.py", line 2387, in Main
    MyBuild.Launch()
  File "C:\BuildBs\201903\BaseTools\Source\Python\build\build.py", line 2141, in Launch
    self._MultiThreadBuildPlatform()
  File "C:\BuildBs\201903\BaseTools\Source\Python\build\build.py", line 1967, in _MultiThreadBuildPlatform
    Ma.CreateCodeFile(True)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\AutoGen.py", line 4042, in CreateCodeFile
    for File in self.AutoGenFileList:
  File "C:\BuildBs\201903\BaseTools\Source\Python\Common\caching.py", line 34, in __get__
    Value = obj.__dict__[self._function.__name__] = self._function(obj)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\AutoGen.py", line 3295, in AutoGenFileList
    GenC.CreateCode(self, AutoGenC, AutoGenH, StringH, AutoGenUniIdf, UniStringBinBuffer, StringIdf, AutoGenUniIdf, IdfGenBinBuffer)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\GenC.py", line 2048, in CreateCode
    CreateUnicodeStringCode(Info, AutoGenC, StringH, UniGenCFlag, UniGenBinBuffer)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\GenC.py", line 1711, in CreateUnicodeStringCode
    Header, Code = GetStringFiles(Info.UnicodeFileList, SrcList, IncList, Info.IncludePathList, ['.uni', '.inf'], Info.Name, CompatibleMode, ShellMode, UniGenCFlag, UniGenBinBuffer, FilterInfo)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\StrGather.py", line 565, in GetStringFiles
    Uni = SearchString(Uni, sorted (FileList), IsCompatibleMode)
  File "C:\BuildBs\201903\BaseTools\Source\Python\AutoGen\StrGather.py", line 537, in SearchString
    for Line in Lines:
  File "C:\Python36\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 134: character maps to &lt;undefined>

从错误上来看,发生在 c:\buildbs\201903\MdeModulePkg\Application\UiApp\UiApp.inf 文件中,反复检查该文件无法确定问题点。

最终比对相同文件的不同版本,确定错误是由于我在 \MdeModulePkg\Application\UiApp\FrontPage.c 函数 InitializeUserInterface 添加的一行注释导致的

EFI_STATUS
EFIAPI
InitializeUserInterface (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_HII_HANDLE                     HiiHandle;
  EFI_STATUS                         Status;
  EFI_GRAPHICS_OUTPUT_PROTOCOL       *GraphicsOutput;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL    *SimpleTextOut;
  UINTN                              BootTextColumn;
  UINTN                              BootTextRow;
//生成一个随机的字符串
  if (!mModeInitialized) {

猜测可能的原因是 build 工具(不是VS 编译工具),在预处理 FrontPage.c 时无法正确处理注释中的中文导致的。因此,在编写代码的时候,尽量不要使用中文作为注释,以免遇到这样的问题。

Step to UEFI (182)UEFI 下计算 Checksum 的最简单方法

BaseLib 提供了一些计算CheckSum 的函数,用这些可以让我们方便的计算一些协议要求的校验码。

下面编写一个简单的例子:

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

extern  EFI_SYSTEM_TABLE    *gST;
extern  EFI_BOOT_SERVICES   *gBS;

UINT8   Buffer[]={1,2,3,4,5};

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        Print(L"[CalculateSum8]     = %x\n",CalculateSum8((UINT8 *)Buffer,sizeof(Buffer)));
        Print(L"[CalculateCheckSum8]= %x\n",CalculateCheckSum8((UINT8 *)Buffer,sizeof(Buffer)));
        Print(L"[CalculateCrc32]    = %x\n",CalculateCrc32((UINT8 *)Buffer,sizeof(Buffer)));
        return(0);
}

运行结果:

使用在线 CRC32 计算工具【参考1】来验证,结果一致。

上述函数源代码可以在 \MdePkg\Library\BaseLib\CheckSum.c 看到。

参考:

1. http://www.ip33.com/crc.html

Arduino 心率带测试

目前市面上能够测量心率的设备很多。有腕带腕表式的,也有夹在耳朵或者手指末端的。从准确性上来说,腕带式的容易松动因此没有胸带式的准确。同时,胸带式的对于运动统计来说也是最好的选择。

前一段入手了三根心率带和一个接收模块。其中的心率带是带有编码的,因此在接收端可以很容易的区分数据来源。当然,与之对应的还有不带编码的心率带,无法在多个的情况下使用。

先说说心率带模块:

  1. 内部使用 CR2032 纽扣电池,据说每天工作1小时可以撑9个月;
  2. 发送频率为 2.4Ghz;
  3. 平时处于睡眠模式,戴上之后才开始发送数据
  4. 发射数据每次 4 Bytes,3 Bytes ID+ 1 Byte 心率。

接收模块:

  1. 模块三个引脚,GND  VCC (特别强调是 3.3V供电) TX 。使用串口通讯。波特率 9600 bps。比如:DD 20 03 04 50(个人感觉有点低,设想如果很多根在同一个空间内使用不知道会有什么问题);
  2. 上面有一个LED,当收到有效数据后会闪动一次;
  3. 板子上的孔间距是 2.0mm 不是 2.54,使用杜邦线会很别扭,我直接焊接上导线来使用;

下面是一个完整的例子,使用 Arduino Leonardo 接收数据,将收到的数据总结为 A + 每分钟心跳数(bpm),或者  B +每分钟心跳数上传到USB串口,然后在上位机上显示出来。试验中使用的心率带心率带 A  ID :22 05 28   心率带 B  ID :22 06 58

  1. Arduino代码
void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
}

boolean status1=false;
boolean status2=false;
boolean status3=false;
byte    addr[3];
byte    pos=0;
char    outadd;
void loop() {
  byte c;
  while (Serial1.available()) 
   {
      c=Serial1.read()&0xFF;
           Serial.write(c);
      if ((status1==false)&&(c==0xdd)) {
            status1=true;
            status2=true;
        //         Serial.print("st1");
        }
      else if (status2) {
            addr[pos]=c;
            pos++;
            if (pos==3) {
                 status2=false;
                 status3=true;   
              }
          //  Serial.print("st2");  
        }
      else if (status3) {
              //Serial.print("st3");
              if ((addr[0]==0x22)&&(addr[1]==0x05)&&(addr[2]==0x28)) {
                 Serial.write('A');
                 Serial.write(c);
                 Serial.println("");
               }
              else
              if ((addr[0]==0x22)&&(addr[1]==0x06)&&(addr[2]==0x58)) {
                 Serial.write('B');
                 Serial.write(c);
                 Serial.println("");
               }
             status1=false;
             status2=false;
             status3=false;
             pos=0;  
     }   
   } //while

  • C# 编写上位机程序,在界面上绘制当前心率曲线

完整代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace WindowsFormsApplication9
{
    public partial class Form1 : Form
    {
        private Queue<double> dataQueue = new Queue<double>(100);
        private Queue<double> dataQueue2 = new Queue<double>(100);

        private int num = 5;//每次删除增加几个点
        private int heartA;
        private int heartB;
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //textBox1.AppendText("start");
            Form2 f2 = new Form2();
            f2.ShowDialog();
            serialPort1.PortName = "COM" + f2.SelectedPort.ToString();
           // MessageBox.Show(f2.SelectedPort.ToString());
            button3.Enabled = true;
            button4.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //设置窗体为无边框样式
            this.FormBorderStyle = FormBorderStyle.None;
            //最大化窗体    
            this.WindowState = FormWindowState.Maximized;     

            InitChart();
            chart1.Width = this.Width - 20;
            chart1.Height = this.Height - 100;
            button3.Enabled = false;
            button4.Enabled = false;

            //如果没有这句话,串口收到的只有0-128 的 ASCII
            serialPort1.Encoding = System.Text.Encoding.GetEncoding(28591);
            serialPort1.DtrEnable = true;
            serialPort1.RtsEnable = true;
        }

        /// <summary>
        /// 初始化图表
        /// </summary>
        private void InitChart()
        {
            //定义图表区域
            this.chart1.ChartAreas.Clear();
            ChartArea chartArea1 = new ChartArea("C1");
            this.chart1.ChartAreas.Add(chartArea1);
            //定义存储和显示点的容器
            this.chart1.Series.Clear();
            Series series1 = new Series("S1");
            series1.ChartArea = "C1";
            this.chart1.Series.Add(series1);

            Series series2 = new Series("S2");
            this.chart1.Series.Add(series2);
            //设置图表显示样式
            this.chart1.ChartAreas[0].AxisY.Minimum = 50;
            this.chart1.ChartAreas[0].AxisY.Maximum = 170;
            this.chart1.ChartAreas[0].AxisY.Interval = 10;
            this.chart1.ChartAreas[0].AxisX.Interval = 5;
            this.chart1.ChartAreas[0].AxisX.MajorGrid.LineColor = System.Drawing.Color.Silver;
            this.chart1.ChartAreas[0].AxisY.MajorGrid.LineColor = System.Drawing.Color.Silver;
            //设置标题
            this.chart1.Titles.Clear();
            this.chart1.Titles.Add("Red");
            this.chart1.Titles[0].Text = "Heart";
            this.chart1.Titles[0].Font = new System.Drawing.Font("Microsoft Sans Serif", 12F);
            this.chart1.Titles[0].ForeColor = Color.Red;
            //设置图表显示样式
            this.chart1.Series[0].Color = Color.Red;
            this.chart1.Series[0].ChartType = SeriesChartType.Line;
            this.chart1.Series[0].Points.Clear();

            this.chart1.Series[1].Color = Color.Blue;
            this.chart1.Series[1].ChartType = SeriesChartType.Line;

        }

        private void button2_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            UpdateQueueValue();
            this.chart1.Series[0].Points.Clear();
            this.chart1.Series[1].Points.Clear();
            for (int i = 0; i < dataQueue.Count; i++)
            {
                this.chart1.Series[0].Points.AddXY((i + 1), dataQueue.ElementAt(i));
                this.chart1.Series[1].Points.AddXY((i + 1), dataQueue2.ElementAt(i));
            }
        }

        //更新队列中的值
        private void UpdateQueueValue()
        {
            int heart=0;
            if (dataQueue.Count >= 100)
            {
                //先出列
                for (int i = 0; i < num; i++)
                {
                    dataQueue.Dequeue();
                    dataQueue2.Dequeue();
                }
            }

                //Random r = new Random();
                for (int i = 0; i < num; i++)
                {
                    //heart = r.Next(50, 170);
                    dataQueue.Enqueue(heartA);
                    //heart = r.Next(50, 170);
                    dataQueue2.Enqueue(heartB);
                }

                chart1.Titles[0].Text = "Heart"+ heart.ToString();

        }

        private void button3_Click(object sender, EventArgs e)
        {
            this.serialPort1.Open();
            this.timer1.Enabled = true;
            this.timer2.Enabled = true;
        }

        private void button4_Click(object sender, EventArgs e)
        {
            this.timer1.Enabled = false;
            this.timer2.Enabled = false;
            serialPort1.Close();
            this.timer1.Stop();
            this.timer2.Stop();
        }

        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {

        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            if (serialPort1.BytesToRead!=0) {
                
                String s=serialPort1.ReadLine();
                if (s.Length>=2) { 
                if (s[0] == 'A') {
                        heartA = s[1]; // Convert.ToInt32(s[1]);
                    textBox1.AppendText(heartA.ToString());
                }
                else
                    if (s[0] == 'B')
                      {
                        heartB = s[1];
                        //textBox1.AppendText(heartB.ToString("X2"));
                }
                }
            }
        }
    }
}

运行结果:

工作的视频在  https://zhuanlan.zhihu.com/p/56291800

Step to UEFI (181)GetTime 研究

最近在编写一个需要随机生成数值的代码,使用之前的 rand 函数【参考1】发现每次生成的随机数是相同的,忽然意识到这是因为代码里面的随机种子是固定值导致的,如果使用当前时间作为随机种子那么每次生成的数值将会是不同的。

可以使用 Runtime Service 中的 GetTime 来取得时间作为种子,返回的时间格式如下:

//
// EFI Time Abstraction:
//  Year:       2000 - 20XX
//  Month:      1 - 12
//  Day:        1 - 31
//  Hour:       0 - 23
//  Minute:     0 - 59
//  Second:     0 - 59
//  Nanosecond: 0 - 999,999,999
//  TimeZone:   -1440 to 1440 or 2047
//
typedef struct {
  UINT16  Year;
  UINT8   Month;
  UINT8   Day;
  UINT8   Hour;
  UINT8   Minute;
  UINT8   Second;
  UINT8   Pad1;
  UINT32  Nanosecond;
  INT16   TimeZone;
  UINT8   Daylight;
  UINT8   Pad2;
} EFI_TIME;

根据上面的结构编写测试代码如下:

/** @file
    A simple, basic, EDK II native, "hello" application to verify that
    we can build applications without LibC.

    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  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle
#include  <Library/ShellLib.h>

extern EFI_RUNTIME_SERVICES      *gRT;

EFI_TIME        ET;

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        gRT->GetTime(&ET,NULL);
        
        Print(L"Hour  [%d]\n",ET.Hour);
        Print(L"Minute[%d]\n",ET.Minute);
        Print(L"Second[%d]\n",ET.Second);
        Print(L"Nano  [%d]\n",ET.Nanosecond);
       
        return(0);
}

在NT32 虚拟机中运行结果如下:

但是实体机上(KBL-R)跑出来的结果显示 Nanosecond 始终为 0。于是开始研究这个代码。

首先,在 NT32 环境下,GetTime 函数具体实现在  \Nt32Pkg\RealTimeClockRuntimeDxe\RealTimeClock.c 中。

EFI_STATUS
EFIAPI
InitializeRealTimeClock (
  IN EFI_HANDLE                            ImageHandle,
  IN EFI_SYSTEM_TABLE                      *SystemTable
  )
  SystemTable->RuntimeServices->GetTime       = WinNtGetTime;
  SystemTable->RuntimeServices->SetTime       = WinNtSetTime;

具体实现

EFI_STATUS
EFIAPI
WinNtGetTime (
  OUT EFI_TIME                                 *Time,
  OUT EFI_TIME_CAPABILITIES                    *Capabilities OPTIONAL
  )
/*++

Routine Description:
  Service routine for RealTimeClockInstance->GetTime 

Arguments:

  Time          - A pointer to storage that will receive a snapshot of the current time.

  Capabilities  - A pointer to storage that will receive the capabilities of the real time clock
                  in the platform. This includes the real time clock's resolution and accuracy.  
                  All reported device capabilities are rounded up.  This is an OPTIONAL argument.

Returns:

  EFI_SUCEESS   - The underlying GetSystemTime call occurred and returned
                  Note that in the NT32 emulation, the GetSystemTime call has no return value
                  thus you will always receive a EFI_SUCCESS on this.

--*/
// TODO:    EFI_INVALID_PARAMETER - add return value to function comment
{
  SYSTEMTIME            SystemTime;
  TIME_ZONE_INFORMATION TimeZone;

  //
  // Check parameter for null pointer
  //
  if (Time == NULL) {
    return EFI_INVALID_PARAMETER;

  }

  gWinNt->GetLocalTime (&SystemTime);
  gWinNt->GetTimeZoneInformation (&TimeZone);

  Time->Year        = (UINT16) SystemTime.wYear;
  Time->Month       = (UINT8) SystemTime.wMonth;
  Time->Day         = (UINT8) SystemTime.wDay;
  Time->Hour        = (UINT8) SystemTime.wHour;
  Time->Minute      = (UINT8) SystemTime.wMinute;
  Time->Second      = (UINT8) SystemTime.wSecond;
  Time->Nanosecond  = (UINT32) (SystemTime.wMilliseconds * 1000000);
  Time->TimeZone    = (INT16) TimeZone.Bias;

  if (Capabilities != NULL) {
    Capabilities->Resolution  = 1;
    Capabilities->Accuracy    = 50000000;
    Capabilities->SetsToZero  = FALSE;
  }

  Time->Daylight = 0;
  if (TimeZone.StandardDate.wMonth) {
    Time->Daylight = (UINT8) TimeZone.StandardDate.wMonth;
  }

  return EFI_SUCCESS;
}

从上面可以看到  Time->Nanosecond  来自  (SystemTime.wMilliseconds * 1000000)。

接下来,在实体机BIOS代码中检查,发现赋值函数在 \PcAtChipsetPkg\PcatRealTimeClockRuntimeDxe\PcRtc.c 文件中:

/**
  Converts time read from RTC to EFI_TIME format defined by UEFI spec.

  This function converts raw time data read from RTC to the EFI_TIME format
  defined by UEFI spec.
  If data mode of RTC is BCD, then converts it to decimal,
  If RTC is in 12-hour format, then converts it to 24-hour format.

  @param   Time       On input, the time data read from RTC to convert
                      On output, the time converted to UEFI format
  @param   RegisterB  Value of Register B of RTC, indicating data mode
                      and hour format.

  @retval  EFI_INVALID_PARAMETER  Parameters passed in are invalid.
  @retval  EFI_SUCCESS            Convert RTC time to EFI time successfully.

**/
EFI_STATUS
ConvertRtcTimeToEfiTime (
  IN OUT EFI_TIME        *Time,
  IN     RTC_REGISTER_B  RegisterB
  )
{
  BOOLEAN IsPM;
  UINT8   Century;

  if ((Time->Hour & 0x80) != 0) {
    IsPM = TRUE;
  } else {
    IsPM = FALSE;
  }

  Time->Hour = (UINT8) (Time->Hour & 0x7f);

  if (RegisterB.Bits.Dm == 0) {
    Time->Year    = CheckAndConvertBcd8ToDecimal8 ((UINT8) Time->Year);
    Time->Month   = CheckAndConvertBcd8ToDecimal8 (Time->Month);
    Time->Day     = CheckAndConvertBcd8ToDecimal8 (Time->Day);
    Time->Hour    = CheckAndConvertBcd8ToDecimal8 (Time->Hour);
    Time->Minute  = CheckAndConvertBcd8ToDecimal8 (Time->Minute);
    Time->Second  = CheckAndConvertBcd8ToDecimal8 (Time->Second);
  }

  if (Time->Year == 0xff || Time->Month == 0xff || Time->Day == 0xff ||
      Time->Hour == 0xff || Time->Minute == 0xff || Time->Second == 0xff) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // For minimal/maximum year range [1970, 2069],
  //   Century is 19 if RTC year >= 70,
  //   Century is 20 otherwise.
  //
  Century = (UINT8) (PcdGet16 (PcdMinimalValidYear) / 100);
  if (Time->Year < PcdGet16 (PcdMinimalValidYear) % 100) {
    Century++;
  }
  Time->Year = (UINT16) (Century * 100 + Time->Year);

  //
  // If time is in 12 hour format, convert it to 24 hour format
  //
  if (RegisterB.Bits.Mil == 0) {
    if (IsPM && Time->Hour < 12) {
      Time->Hour = (UINT8) (Time->Hour + 12);
    }

    if (!IsPM && Time->Hour == 12) {
      Time->Hour = 0;
    }
  }

  Time->Nanosecond  = 0;

  return EFI_SUCCESS;
}

其中给定的Time->Nanosecond  始终为 0. 这就是为什么我们在实体机看到0的原因。

完整的代码如下:

最终,我使用 (ET.Hour* ET.Minute* ET.Second) 作为种子。当调用间隔时间足够长(至少大于1秒),这种方式能够正常工作并且足够简单。如果你对安全性有特别要求,建议使用 RDRand 来取得随机数。

参考:

1. http://www.lab-z.com/clibrand/ CLIB:RAND 随机数生成

Step to UEFI (183)SetJump() 和LongJump()

单纯的函数调用从动作上来说,Call 指令可以看作保存寄存器压栈外加 JMP 的过程(复杂的说调用 Call 会有有实模式,保护模式, Near/Far,gated等等差别,在 IA32 手册上描述足有8页之多)。因此,可以使用压栈保存寄存器外加一个跳转来实现。在 UEFI 中可以通过SetJump() 和 LongJump()函数组合来实现。

在 \MdePkg\Include\Library\BaseLib.h 可以看到下面的定义。

/**
  Saves the current CPU context that can be restored with a call to LongJump()
  and returns 0.

  Saves the current CPU context in the buffer specified by JumpBuffer and
  returns 0. The initial call to SetJump() must always return 0. Subsequent
  calls to LongJump() cause a non-zero value to be returned by SetJump().

  If JumpBuffer is NULL, then ASSERT().
  For Itanium processors, if JumpBuffer is not aligned on a 16-byte boundary, then ASSERT().

  NOTE: The structure BASE_LIBRARY_JUMP_BUFFER is CPU architecture specific.
  The same structure must never be used for more than one CPU architecture context.
  For example, a BASE_LIBRARY_JUMP_BUFFER allocated by an IA-32 module must never be used from an x64 module.
  SetJump()/LongJump() is not currently supported for the EBC processor type.

  @param  JumpBuffer  A pointer to CPU context buffer.

  @retval 0 Indicates a return from SetJump().

**/
RETURNS_TWICE
UINTN
EFIAPI
SetJump (
  OUT     BASE_LIBRARY_JUMP_BUFFER  *JumpBuffer
  );

从介绍上来看,SetJump能保存调用处的全部寄存器,然后返回到调用的位置。

/**
  Restores the CPU context that was saved with SetJump().

  Restores the CPU context from the buffer specified by JumpBuffer. This
  function never returns to the caller. Instead is resumes execution based on
  the state of JumpBuffer.

  If JumpBuffer is NULL, then ASSERT().
  For Itanium processors, if JumpBuffer is not aligned on a 16-byte boundary, then ASSERT().
  If Value is 0, then ASSERT().

  @param  JumpBuffer  A pointer to CPU context buffer.
  @param  Value       The value to return when the SetJump() context is
                      restored and must be non-zero.

**/
VOID
EFIAPI
LongJump (
  IN      BASE_LIBRARY_JUMP_BUFFER  *JumpBuffer,
  IN      UINTN                     Value
  );

从介绍上来看,LongJump恢复SetJump函数能保存调用处的全部寄存器,然后返回到调用SetJump函数的下一条指令的位置。

为了验证上面的说法,编写一个Application。主要代码如下:

UINTN RunMark   =       0;

void
ShowString()
{
        Print(L"www.lab-z.com [%d]\n",RunMark);
}

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        BASE_LIBRARY_JUMP_BUFFER  JumpBuffer;

        SetJump(&JumpBuffer);
        RunMark++;
        ShowString();
        if (RunMark==1) {
                LongJump (&JumpBuffer, (UINTN)-1);
        }
        
        return(0);
}

运行结果如下:

第一次运行 SetJump() 会将当前寄存器存放在 JumpBuffer 中。然后 RunMark 加一,从0变成1.接着,判断这个值等于1,再执行 LongJump,执行的结果会回到  SetJump(); 的下一条指令,也就是 RunMark++。这样,RunMark 变成了2,不会运行 LongJump() 接下来顺利退出了。

最后一个问题,SetJump 和  LongJump的具体实现在哪里?我这边通过 Build 目录里面 MakeFile 内容来确认。

1.找到Application 的 MakeFile 在 \AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\JumpCall\JumpCall\Makefile 其中有如下代码

#
# Build Macro
#
STATIC_LIBRARY_FILES =  \
    $(BIN_DIR)\MdePkg\Library\BaseDebugPrintErrorLevelLib\BaseDebugPrintErrorLevelLib\OUTPUT\BaseDebugPrintErrorLevelLib.lib \
    $(BIN_DIR)\MdePkg\Library\BasePrintLib\BasePrintLib\OUTPUT\BasePrintLib.lib \
    $(BIN_DIR)\MdePkg\Library\BasePcdLibNull\BasePcdLibNull\OUTPUT\BasePcdLibNull.lib \
$(BIN_DIR)\MdePkg\Library\BaseLib\BaseLib\OUTPUT\BaseLib.lib \

2.确定 BaseLib.Lib 的生成方式,在C:\BuildBs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\Makefile 可以看到

$(OUTPUT_DIR)\X64\SetJump.obj : $(MAKE_FILE)
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\Base.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\X64\Nasm.inc
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\X64\ProcessorBind.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Include\Library\PcdLib.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(DEBUG_DIR)\AutoGen.h
$(OUTPUT_DIR)\X64\SetJump.obj : $(WORKSPACE)\MdePkg\Library\BaseLib\X64\SetJump.nasm
	"$(PP)" $(PP_FLAGS) $(INC) c:\buildbs\201903\MdePkg\Library\BaseLib\X64\SetJump.nasm > c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.i
	Trim --trim-long --source-code -o c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.iii c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.i
	"$(NASM)" -Ic:\buildbs\201903\MdePkg\Library\BaseLib\X64\ $(NASM_INC) $(NASM_FLAGS) -o c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.obj c:\buildbs\201903\Build\AppPkg\DEBUG_VS2015x86\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\X64\SetJump.iii
就是说使用了c:\buildbs\201903\MdePkg\Library\BaseLib\X64\SetJump.nasm 进行编译。

3.具体代码在C:\BuildBs\201903\MdePkg\Library\BaseLib\X64\SetJump.nasm

;------------------------------------------------------------------------------
; UINTN
; EFIAPI
; SetJump (
;   OUT     BASE_LIBRARY_JUMP_BUFFER  *JumpBuffer
;   );
;------------------------------------------------------------------------------
global ASM_PFX(SetJump)
ASM_PFX(SetJump):
    push    rcx
    add     rsp, -0x20
    call    ASM_PFX(InternalAssertJumpBuffer)

完整的例子代码在这里下载

批处理请求管理员权限

当我们使用 RW Everything 这样的软件时,会自动请求管理员权限。

相比之下,我们使用批处理文件调用FITW 刷写 BIOS工具的时候不会出现这样的提示,又忘记使用管理员权限打开 CMD 窗口,这样会导致执行失败。最近看到了一个好用的批处理,可以在批处理文件中直接像 RW 这样来请求管理员权限,这样能够避免忘记使用管理员权限打开的问题。

批处理来自 “在批处理中提升权限 (UAC开启状态下)”【参考1】,根据作者的原文应该是翻译自https://sites.google.com/site/eneerge/home/BatchGotAdmin

@echo off

:: BatchGotAdmin
:-------------------------------------
REM  --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"

REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
    echo Requesting administrative privileges...
    goto UACPrompt
) else ( goto gotAdmin )

:UACPrompt
    echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
    echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"

    "%temp%\getadmin.vbs"
    exit /B

:gotAdmin
    if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
    pushd "%CD%"
    CD /D "%~dp0"
:--------------------------------------

测试结果:

参考:

1. https://blog.csdn.net/wangjia184/article/details/7488341