Step to UEFI (162)UEFI Shell 下的俄罗斯方块

1984年,在当时还叫做苏联的那个国家的首都莫斯科,俄罗斯科学院(当然那时它也还叫做苏联科学院)总部计算机中心的一位工程师阿列克谢·帕基特诺夫开始考虑在计算机上自行开发一些简单的游戏,经过一段时间的尝试后,他通过另一款拼图游戏得到灵感,考虑让不同形状的图形依次下落,在矩形底部堆叠起来使之排列成完整的一行后消除,在另外两位同伴的协助下,他最终完成了这款被他命名为“Tetris”(俄语:Тетрис)的游戏,而我们今天更习惯叫它为“俄罗斯方块”。


1984年6月6日,是公认的俄罗斯方块诞生纪念日,游戏最初由苏联科学院计算中心的工程师阿列克谢·帕基特诺夫开发,他当时从另一款拼图游戏得到灵感。

  根据另一位当事人的回忆,“Tetris”这个单词是阿列克谢自己发明并坚持使用的,来自反映俄罗斯方块图案基本结构的“四”(希腊语:tetra)和阿列克谢自己最喜爱的运动“网球”(tennis)的组合。

《俄罗斯方块》的原名“Tetris”(俄语:Тетрис)是发明者阿列克谢·帕基特诺夫自己生造出来的单词,来自反映俄罗斯方块图案基本结构的“四”(希腊语:tetra)和阿列克谢自己最喜爱的运动“网球”(tennis)的组合。

  公认的第一款俄罗斯方块原始程序诞生于1984年6月6日,在当时苏联仿制的Elektronika 60计算机上运行,因为这款计算机不能显示色块图案,原始版本只能用字符串来表示图形,但即便这样它也体现出了游戏本身的魅力,阿列克谢和他身边的朋友们很快都开始为之沉迷。1985年,开发同伴之一的瓦丁·格拉西莫夫在MS-DOS下移植了俄罗斯方块,让更多的个人电脑可以运行,游戏得以迅速的普及。

  阿列克谢起初希望能合法贩卖俄罗斯方块游戏,但在苏联当时的制度下这十分困难,几经尝试都失败后,阿列克谢表示可以考虑把游戏版权交给国家——当时的苏联科学院。而在诞生后的数年时间里,俄罗斯方块一直都以免费拷贝的形式传播,从苏联扩展到了整个欧洲,也引起了更多人的注意。

第一个俄罗斯方块的程序在苏联仿制的Elektronika 60计算机上运行,因为无法显示色块只能用字符来表示图形,之后一年游戏移植了MS-DOS版,而这一版首次呈现出的图案画面也成为之后三十年来的游戏基础,甚至没有太多变化。

  1986年匈牙利的程序员在Apple II和Commodore 64上移植了游戏,英国游戏公司Andromeda的一位经理人罗伯特·斯坦恩注意到了这个商机,他开始联系阿列克谢以及匈牙利的程序员试图购买俄罗斯方块的版权,并在确定到手之前就把它分别卖给了英国的游戏公司Mirrorsoft和美国的游戏公司Spectrum Holobyte,从而导致了接下来整整十余年时间关于俄罗斯方块的版权之争,甚至可以说改变了游戏发展史的一连串事件。”  上述文字节选自《三十年成就经典传奇 <俄罗斯方块>发展史》【参考1】

之前网上有一份开源的 Shell 版本的俄罗斯方块游戏,但是我试验发现做的太糟糕,没有办法玩。然后经过在网上搜索到了一份Windows API 编写的俄罗斯方块游戏【参考2】,放在 VS2015 中很快就可以编译成功,运行起来也没有大问题,于是在这个代码基础上移植到UEFI Shell 下(程序整体非常清晰,移植到其他平台也绝无难度)。
其中比较有意思的是代码中定义的方块有下面六种方块,都是4个格子组成的

在代码头部定义了Blocks[][4] 对应了每种方块的各种变换,比如下面这种 Z 字

定义方法是给出每个黑块的坐标(左上角为 0,0): 0,0, 1,0, 1,1, 2,1
旋转变换后的一个结果如下:2, 0, 1, 1, 2, 1, 1, 2,

看懂了上面就能搞清楚代码。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>

#define NUMLINEBLOCKS   18  //行数
#define NUMCOLUMNBLOCKS 10  //列数
#define BLOCKSTYLES     (sizeof (Blocks) / sizeof (Blocks[0]))  //方块的种类数

//定时器触发时间,单位是100ns
#define TIMER_PERIOD_INIT       10000000UL

BOOLEAN                pause = FALSE;  //暂停

EFI_EVENT       TimerEvent;

//是否要重绘界面的标志
BOOLEAN         ReDraw=TRUE;

//退出标志
BOOLEAN         quit=FALSE;

struct POINT
{
        int x;
        int y;
};

//游戏区各方格顶点布尔值,代表该方格是否有方块
BOOLEAN        GameClient[NUMCOLUMNBLOCKS][NUMLINEBLOCKS];
int                F, S, cF, cS;        //随机方块图形对应的第一、二纬
int                Score;  //得分
struct POINT Block[4],NextBlock[4];


//定义各方块形状,以点表示
struct {
        struct POINT    pt[4];
}

Blocks[][4] =
{
        //正7
        0, 0, 1, 0, 1, 1, 1, 2,  2, 0, 0, 1, 1, 1, 2, 1,  1, 0, 1, 1, 1, 2, 2, 2,  0, 1, 1, 1, 2, 1, 0, 2,
        //反7
        1, 0, 2, 0, 1, 1, 1, 2,  0, 1, 1, 1, 2, 1, 2, 2,  1, 0, 1, 1, 0, 2, 1, 2,  0, 0, 0, 1, 1, 1, 2, 1,
        //1
        1, 0, 1, 1, 1, 2, 1, 3,  0, 1, 1, 1, 2, 1, 3, 1,  1, 0, 1, 1, 1, 2, 1, 3,  0, 1, 1, 1, 2, 1, 3, 1,
        //Z
        0, 0, 1, 0, 1, 1, 2, 1,  2, 0, 1, 1, 2, 1, 1, 2,  0, 0, 1, 0, 1, 1, 2, 1,  2, 0, 1, 1, 2, 1, 1, 2,
        //反Z
        1, 0, 2, 0, 0, 1, 1, 1,  1, 0, 1, 1, 2, 1, 2, 2,  1, 0, 2, 0, 0, 1, 1, 1,  1, 0, 1, 1, 2, 1, 2, 2,
        //田字
        0, 0, 1, 0, 0, 1, 1, 1,  0, 0, 1, 0, 0, 1, 1, 1,  0, 0, 1, 0, 0, 1, 1, 1,  0, 0, 1, 0, 0, 1, 1, 1,
        //尖头
        1, 0, 0, 1, 1, 1, 2, 1,  0, 0, 0, 1, 1, 1, 0, 2,  0, 0, 1, 0, 2, 0, 1, 1,  1, 0, 0, 1, 1, 1, 1, 2
};

/** Expands to an integer constant expression that is the maximum value
    returned by the rand function.
**/
#define RAND_MAX  0x7fffffff
static UINT32 next = 1;

//判断方块是否可以下落
BOOLEAN CanDown(struct POINT pt[])
{
        BOOLEAN result = TRUE;
        
        //将方块所在格子先假设指定为无方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = FALSE;
        
        for (int i = 0; i < 4; ++i)
        {
                //假如继续落下超过下底边界,返回false;或者假如该小方块下落一格已经有方块,结果为false
                if (pt[i].y + 1 == NUMLINEBLOCKS || GameClient[pt[i].x][pt[i].y + 1])
                {
                        result = FALSE;
                        break;
                }
        }
        
        //恢复方块所在格子为有方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = TRUE;
        
        return result;
}

//判断是否可以左移
BOOLEAN CanLeft(struct POINT pt[])
{
        BOOLEAN result = TRUE;
        //将方块所在格子先假设指定为无方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = FALSE;
        for (int i = 0; i < 4; ++i)
        {
                //假如继续左移超过左边边界,返回false;或者假如该小方块左移一格已经有方块,结果为false
                if (!pt[i].x || GameClient[pt[i].x - 1][pt[i].y])
                {
                        result = FALSE;
                        break;
                }
        }
        //恢复方块所在格子为有方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = TRUE;
        return result;
}

//判断是否可以右移
BOOLEAN CanRight(struct POINT pt[])
{
        BOOLEAN result = TRUE;
        //将方块所在格子先假设指定为无方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = FALSE;
        for (int i = 0; i < 4; ++i)
        {
                //假如继续左移超过左边边界,返回false;或者假如该小方块左移一格已经有方块,结果为false
                if (pt[i].x + 1 == NUMCOLUMNBLOCKS || GameClient[pt[i].x + 1][pt[i].y])
                {
                        result = FALSE;
                        break;
                }
        }
        //恢复方块所在格子为有方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = TRUE;
        return result;
}

//判断是否可以旋转
BOOLEAN CanChange(struct POINT pt[])
{
        BOOLEAN result = TRUE;
        //将方块所在格子先假设指定为无方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = FALSE;
        int t = (cS + 1) % 4;
        for (int k = 0; k < 4; ++k)
        {
                int x = Blocks[cF][t].pt[k].x - Blocks[cF][cS].pt[k].x,
                        y = Blocks[cF][t].pt[k].y - Blocks[cF][cS].pt[k].y;
                if (GameClient[pt[k].x + x][pt[k].y + y] ||  //该方格已经有方块
                        pt[k].x + x > NUMCOLUMNBLOCKS - 1 ||  //x坐标超越了右边界
                        pt[k].x + x < 0 ||   //x坐标超越了左边界
                        pt[k].y + y > NUMLINEBLOCKS - 1)  //y坐标超越了下底边界
                {
                        result = FALSE;
                        break;
                }
        }

        //恢复方块所在格子为有方块
        for (int i = 0; i < 4; ++i)
                GameClient[pt[i].x][pt[i].y] = TRUE;
        return result;
}

//实现旋转
void Change(struct POINT pt[])
{
        int t = (cS + 1) % 4;
        for (int i = 0; i < 4; ++i)
        {
                int x = Blocks[cF][t].pt[i].x - Blocks[cF][cS].pt[i].x,
                        y = Blocks[cF][t].pt[i].y - Blocks[cF][cS].pt[i].y;
                GameClient[pt[i].x][pt[i].y] = FALSE;
                pt[i].x += x;
                pt[i].y += y;
                GameClient[pt[i].x][pt[i].y] = TRUE;
        }
        cS = t;
}

//实现右移
void Right(struct POINT pt[])
{
        for (int i = 0; i < 4; ++i)
        {
                GameClient[pt[i].x][pt[i].y] = FALSE;
                ++pt[i].x;
        }
        for (int k = 0; k < 4; ++k)
                GameClient[pt[k].x][pt[k].y] = TRUE;
}

//实现左移
void Left(struct POINT pt[])
{
        for (int i = 0; i < 4; ++i)
        {
                GameClient[pt[i].x][pt[i].y] = FALSE;
                --pt[i].x;
        }
        for (int k = 0; k < 4; ++k)
                GameClient[pt[k].x][pt[k].y] = TRUE;
}

//实现方块的下落
void Down(struct POINT pt[])
{
        for (int i = 0; i < 4; ++i)
        {
                GameClient[pt[i].x][pt[i].y] = FALSE;
                ++pt[i].y;
        }
        for (int k = 0; k < 4; ++k)
                GameClient[pt[k].x][pt[k].y] = TRUE;
}

//消行处理以及分数结算
void DelSqure()
{
        int line = 0, temp;
        for (int x = NUMLINEBLOCKS - 1; x >= 0; --x)
        {
                BOOLEAN result = TRUE;
                for (int y = 0; y < NUMCOLUMNBLOCKS; ++y)
                {
                        if (!GameClient[y][x])
                        {
                                result = FALSE;
                                break;
                        }
                }
                //判断是否可以消行
                if (result)
                {
                        temp = x;
                        ++line;
                        while (x > 0)
                        {
                                for (int y = 0; y < NUMCOLUMNBLOCKS; ++y)
                                {
                                        GameClient[y][x] = GameClient[y][x - 1];
                                }
                                --x;
                        }
                        for (int y = 0; y < NUMCOLUMNBLOCKS; ++y)
                                GameClient[y][0] = FALSE;
                        x = temp + 1;
                }
        }
        if (line)
                Score += (line - 1) * 2 + 1;
        //要求重绘
        ReDraw=TRUE;
}


/** Compute a pseudo-random number.
  *
  * Compute x = (7^5 * x) mod (2^31 - 1)
  * without overflowing 31 bits:
  *      (2^31 - 1) = 127773 * (7^5) + 2836
  * From "Random number generators: good ones are hard to find",
  * Park and Miller, Communications of the ACM, vol. 31, no. 10,
  * October 1988, p. 1195.
**/
int
rand()
{
  INT32 hi, lo, x;

  /* Can't be initialized with 0, so use another value. */
  if (next == 0)
    next = 123459876;
  hi = next / 127773;
  lo = next % 127773;
  x = 16807 * lo - 2836 * hi;
  if (x < 0)
    x += 0x7fffffff;
  return ((next = x) % ((UINT32)RAND_MAX + 1));
}

//触发时间中断
VOID TimerCallback( EFI_EVENT Event, VOID *Context )
{
        //如果当前已经暂停,那么直接退出
        if (pause) return ;
        //判断是否可以下落
        if (CanDown(Block))
        {       //可以下落,处理  
                Down(Block);
        }
        //不能下移,需要处理消行判断(结合分数),还需要处理下一个显示,和当前显示的方块
        else
        {
                DelSqure();
                for (int i = 0; i < 4; ++i)
                  {
                     Block[i].x = NextBlock[i].x + 4;
                     Block[i].y = NextBlock[i].y;
                     if (GameClient[Block[i].x][Block[i].y])
                                {
                                   // Stop the Periodic Timer 
                                   gBS->SetTimer(TimerEvent, TimerCancel, TIMER_PERIOD_INIT);   
                                }
                                else
                                   GameClient[Block[i].x][Block[i].y] = TRUE;
                  }
                  
                  cS = S;  cF = F;
                  S = rand()%4;
                  F = rand() % BLOCKSTYLES;
                  for (int i = 0; i < 4; ++i)
                    {
                         NextBlock[i].x = Blocks[F][S].pt[i].x;
                         NextBlock[i].y = Blocks[F][S].pt[i].y;
                    }
        }
        //要求重绘
        ReDraw=TRUE;
}

void ConstructGame()
{
        EFI_STATUS      Status;
        CHAR16          ChrSide[2]={0,0};
            
        gST->ConOut->ClearScreen(gST->ConOut);
        gST->ConOut->EnableCursor(gST->ConOut, FALSE);
        gST->ConOut->SetCursorPosition(gST->ConOut, 0, 0);

        //初始化第一个出现的方块,随机生成
        cS = rand() % 4;
        cF = rand()% BLOCKSTYLES;
        for (int i = 0; i < 4; ++i)
          {
                Block[i].x = Blocks[cF][cS].pt[i].x + 4;
                Block[i].y = Blocks[cF][cS].pt[i].y;
                GameClient[Block[i].x][Block[i].y] = TRUE;
          }
        //生成下一个
        S = rand() % 4;
        F = rand()% BLOCKSTYLES;
        for (int i = 0; i < 4; ++i)
          {
               NextBlock[i].x = Blocks[F][S].pt[i].x;
               NextBlock[i].y = Blocks[F][S].pt[i].y;
          }
        //绘制外围
        ChrSide[0]=  BOXDRAW_DOUBLE_DOWN_RIGHT;        
        Print(L"%S",ChrSide); 
        ChrSide[0]=  BOXDRAW_DOUBLE_HORIZONTAL;
        for (UINT16 i=0;i<NUMCOLUMNBLOCKS;i++) {Print(L"%S",ChrSide);}
        ChrSide[0]=BOXDRAW_DOUBLE_DOWN_LEFT;
        Print(L"%S\n",ChrSide); 

        ChrSide[0]=  BOXDRAW_DOUBLE_VERTICAL;  
      
        for (UINT16 j=0;j<NUMLINEBLOCKS;j++) 
        {
                Print(L"%S",ChrSide);
                for (UINT16 i=0;i<NUMCOLUMNBLOCKS;i++) {Print(L" ");}  
                Print(L"%S\n",ChrSide); 
        }

        ChrSide[0]=  BOXDRAW_DOUBLE_UP_RIGHT;        
        Print(L"%S",ChrSide); 
        ChrSide[0]=  BOXDRAW_DOUBLE_HORIZONTAL;
        for (UINT16 i=0;i<NUMCOLUMNBLOCKS;i++) {Print(L"%S",ChrSide);}
        ChrSide[0]=BOXDRAW_DOUBLE_UP_LEFT;
        Print(L"%S\n",ChrSide); 
      
        Status = gBS->CreateEvent( 
                EVT_TIMER | EVT_NOTIFY_SIGNAL,
                TPL_CALLBACK,
                TimerCallback, 
                NULL,
                &TimerEvent);
        ASSERT_EFI_ERROR(Status);
    
        //设置定时触发 
        Status = gBS->SetTimer(TimerEvent, TimerPeriodic, TIMER_PERIOD_INIT);       
}     

void DestructGame()
{
        gBS->SetTimer(TimerEvent, TimerCancel, TIMER_PERIOD_INIT);  
        gBS->CloseEvent(TimerEvent);   
        gST->ConOut->ClearScreen(gST->ConOut);                
        gST->ConOut->EnableCursor(gST->ConOut, TRUE);
}        

/**
  The user Entry Point for Application. 
  The user code starts with this function as the real entry point for the application.
  @param  UINTN Argc     the arguments amount
  @param  CHAR16 **Argv  arguments list
  @return INTN 
**/
INTN ShellAppMain( UINTN Argc, CHAR16 **Argv )
{
      EFI_STATUS Status;
      EFI_INPUT_KEY Key;
        
      ConstructGame();
  
      // The main loop of the game
      while(quit==FALSE) {
         Status= gST->ConIn->ReadKeyStroke(gST->ConIn,&Key);
         if (Status == EFI_SUCCESS) {
                if (Key.ScanCode==0x17) {quit=TRUE;}
                switch (Key.ScanCode)
                {
                case 0x04: //左键
                        if (CanLeft(Block))
                            Left(Block);
                        ReDraw=TRUE;
                        break;

                case 0x03: //右键
                        if (CanRight(Block))
                                Right(Block);
                        ReDraw=TRUE;
                        break;

                case 0x01: //上键
                        if (CanChange(Block))
                                Change(Block);
                        ReDraw=TRUE;
                        break;

                case 0x02: //下键
                        while (CanDown(Block))
                                Down(Block);
                        ReDraw=TRUE;
                        break;

                default:
                        break;
                } //End of switch
                
                //p键 可以起用来暂停
                if (Key.UnicodeChar == 'p')
                        pause = !pause;
                else if (Key.UnicodeChar == 'r')
                {   // r 键用来重置游戏
                        Score = 0;
                        for (int x = 0; x < NUMCOLUMNBLOCKS; ++x)
                        {
                                for (int y = 0; y < NUMLINEBLOCKS; ++y)
                                        GameClient[x][y] = FALSE;
                        }
                        cS = rand() % 4;
                        cF = rand() % BLOCKSTYLES;
                        for (int i = 0; i < 4; ++i)
                        {
                                Block[i].x = Blocks[cF][cS].pt[i].x + 4;
                                Block[i].y = Blocks[cF][cS].pt[i].y;
                                GameClient[Block[i].x][Block[i].y] = TRUE;
                        }
                        S = rand() % 4;
                        F = rand() % BLOCKSTYLES;
                        for (int i = 0; i < 4; ++i)
                        {
                                NextBlock[i].x = Blocks[F][S].pt[i].x;
                                NextBlock[i].y = Blocks[F][S].pt[i].y;
                        }
                        gBS->SetTimer(TimerEvent, TimerPeriodic, TIMER_PERIOD_INIT); 
                        pause = FALSE;
                        ReDraw=TRUE;
                }                
        }
        
        //绘制界面
        if (ReDraw) {

                //显示游戏区的方块
                for (int y = 0; y < NUMLINEBLOCKS; ++y)
                {
                        gST->ConOut->SetCursorPosition(gST->ConOut, 1, y+1);
                        for (int x = 0; x < NUMCOLUMNBLOCKS; ++x)
                        {
                                if (GameClient[x][y])
                                {       
                                        Print(L"O");
                                }
                                else Print(L" ");
                        }
                        Print(L"\n");
                }
                
                for (int j=0;j<4;j++)
                {
                   gST->ConOut->SetCursorPosition(gST->ConOut, (NUMCOLUMNBLOCKS + 20), 6+j);
                   for (int i=0;i<4;i++) {
                           Print(L" ");
                   }
                }  
                
                //显示下一个方块区域的方块
                for (int i = 0; i < 4; ++i)
                {
                        gST->ConOut->SetCursorPosition(gST->ConOut, (NextBlock[i].x + NUMCOLUMNBLOCKS + 20), NextBlock[i].y +6);
                        Print(L"O");
                }
                ReDraw=FALSE;
        }
        
    }

    DestructGame();  

    return 0;
}

 

运行结果:

这个只是一个简单的框架,没有实现升级也没有华丽的界面,但是已经具备了俄罗斯方块的基本理念,有兴趣的朋友请继续完善它吧。

参考:
1.https://www.gamersky.com/wenku/201406/369340.shtml 三十年成就经典传奇 《俄罗斯方块》发展史
2. https://blog.csdn.net/zxlstudio/article/details/8899776 C语言俄罗斯方块(简易版) 写的非常漂亮,可以作为Windows SDK 编程的典范。

Step to UEFI (161)Print 直接输出 ASCII 的String

之前我在处理 ASCII 的 String 时,都是进行格式化后再输出的,最近偶然间看到了 Print 有一个 %a 参数可以用来直接输出 CHAR8 这样定义的字符串。实验代码如下:

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

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

    CHAR8      Buffer[]="This is a simple test\n\r from www.lab-z.com\n\r";

    Print(L"%a", Buffer);
  return EFI_SUCCESS;
}

 

运行结果:

完整的代码下载:
Printa

关于 Print 函数的具体实现,可以在 MdePkg\Library\BasePrintLib\PrintLibInternal.c 中看到。

Step to UEFI (163)替换已经存在Protocol中函数的实验

熟悉汇编语言的朋友都知道,DOS下面有很多INT服务,通过这样的入口能够实现硬件相关的服务。比如:INT 15 中可以支持关闭显示。与之类似,UEFI 里面是通过Protocol来实现这样的功能的。找到需要的 Protocol 即可 Call 到其提供的函数。这样的设计也给我们一个机会,可以用自己编写的函数来替换真实的函数。为此,设计了一个实验替换掉 Simple File System 中提供的 Read函数,这样每次 Application 读取到的都会是我们提供的数据。
代码比较长,从原理的角度解释具体流程:
首先,编写测试用的 Application,用 LocateHandleBuffer 枚举出来所有的 Simple File System Protocol,然后在找到的 Handle 上用 OpenVolume 打开 Root Directory。打开之后再用Open 函数打开文件,最后用 Read 来读取文件内容并且显示出来。实验中读取的是一个 44 Bytes 长的 Test.txt 文件。
实验环境是 NT32模拟器,我们只在 fs0: 上放置了 test.txt,因此,能找到2个SimpleFileSystemProtcol,第一个能够读取并且显示内容,第二个会出现Open File error 的错误。

接下来,编写我们的“驱动”,为了简单起见,这里并不是一个正经的驱动。对于我们来说,只是需要程序常驻内存,结束之后不希望被释放掉,否则会出现其他程序调用而无法找到函数的情况。因此,我们在 MdeModulePkg 中写程序,编译之后使用 Load 来加载。代码流程如下:
1. 在入口 MyEntryPoint 中查找一个 SimpleFileSystemProtocol,先保存OpenVolume 的实际入口,再用自己编写的 MySimpleFileSystemOpenVolume替换掉这个入口;
2. Application 在调用 OpenVolume 函数的时候,实际上是会进入MySimpleFileSystemOpenVolume 中。在这个函数中,使用之前保存的实际OpenVolume完成工作,然后将 OpenVolume返回的EFI_FILE_PROTOCOL替换为 MySimpleFileSystemOpen;
3. Application在调用 Open 函数的时候,实际上是会进入MySimpleFileSystemOpen。我们在这里检查参数判断要打开的是否为我们指定的 test.txt 文件,如果是,那么用 MySimpleFileSystemOpen来替换实际的 Open 函数;
4. 这样,当 Application 要Read test.txt 的时候,我们就可以送一个假的值出去。这样就实现了替换的功能。

完整的代码:
用于测试的 Application

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/BaseMemoryLib.h>
#include  <Protocol/SimpleFileSystem.h>
#include  <Library/MemoryAllocationLib.h>
                        
extern EFI_RUNTIME_SERVICES      *gRT;
extern EFI_BOOT_SERVICES         *gBS;

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

    EFI_STATUS Status;
    EFI_HANDLE  *HandleBuffer = NULL;
    UINTN       NumHandles;
    UINTN       Index; 
    EFI_FILE_IO_INTERFACE *ioDevice; 
    EFI_FILE_HANDLE handleRoots;
    EFI_FILE_PROTOCOL *TestFile;
    CHAR8      Buffer[4*1024];
    UINTN      BufferSize;
    
    //Find all Simnple File System Protocol Handle
    Status = gBS->LocateHandleBuffer(
        ByProtocol,
        &gEfiSimpleFileSystemProtocolGuid,
        NULL,
        &NumHandles,
        &HandleBuffer);
        
    Print(L"Walk handles %ld\n", NumHandles);
    for(Index =0; Index<NumHandles; Index++){
        //Get one Simple File Protocol from a Handle    
        Status = gBS->HandleProtocol (
                        HandleBuffer[Index],
                        &gEfiSimpleFileSystemProtocolGuid,
                        (VOID **) &ioDevice
                        );
        //Opens the root directory on the volume                
        Status = ioDevice->OpenVolume( ioDevice, &handleRoots );   
        if (EFI_ERROR(Status)) {
                Print(L"OpenVolume error \n");
        }           
        //Open a file
        Status = handleRoots->Open(handleRoots,&TestFile,L"test.txt",EFI_FILE_MODE_READ, 0);
        if (!EFI_ERROR(Status)) {
                //The size of "Test.txt" is 44bytes, I use hard code here
                BufferSize=44;
                TestFile->Read(TestFile,&BufferSize,&Buffer);
                Print(L"%a",Buffer);
                TestFile->Close(TestFile);
        }
        else
        {
                Print(L"Open file error [%r]\n",Status);    
        }
    }
    
  FreePool (HandleBuffer);
  
  return EFI_SUCCESS;
}

 

简单的“驱动” 代码

#include <PiDxe.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>

extern EFI_SYSTEM_TABLE         *gST;

EFI_GUID        gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2, 
                        { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};

//Backup of Read function                        
EFI_FILE_READ OldSimpleFileSystemRead;
//Backup of Open function
EFI_FILE_OPEN OldSimpleFileSystemOpen;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME         OldSimpleFileSystemOpenVolume;

//This one will replace the Read function of Read in Simple File System
EFI_STATUS
EFIAPI
MySimpleFileSystemRead (
  IN     EFI_FILE_PROTOCOL  *This,
  IN OUT UINTN              *BufferSize,
  OUT    VOID               *Buffer 
)
{
     CHAR8   TestBuffer[]="Replaced buffer\n\r";
     AsciiStrCpyS(Buffer,AsciiStrnLenS(TestBuffer,255)+1,TestBuffer);
     return EFI_SUCCESS;
}

//This one will replace the Open function of Open in Simple File System
EFI_STATUS
EFIAPI
MySimpleFileSystemOpen (
  IN  EFI_FILE_PROTOCOL  *This,
  OUT EFI_FILE_PROTOCOL  **NewHandle,
  IN  CHAR16             *FileName,
  IN  UINT64             OpenMode,
  IN  UINT64             Attributes
  )
{
        EFI_STATUS Status;

        //Call into Open function in the Simple File system
        Status=(*OldSimpleFileSystemOpen)(This,NewHandle,FileName,OpenMode,Attributes);
        if (!EFI_ERROR(Status)) {
                //Check the filename to make sure it's the one we will replace
                if (StrnCmp(FileName,L"test.txt",StrLen(FileName))==0) {
                        if ((**NewHandle).Read!=MySimpleFileSystemRead) {
                                //Backup the Read Function
                                OldSimpleFileSystemRead=(**NewHandle).Read;
                                //Replace the Read Function
                                (**NewHandle).Read=MySimpleFileSystemRead;
                        }
                        
                }
        }
       return Status;
} 


EFI_STATUS
EFIAPI
MySimpleFileSystemOpenVolume (
  IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL  *This,
  OUT EFI_FILE_PROTOCOL               **Root
  )
{
        EFI_STATUS Status;
        
        Status=(*OldSimpleFileSystemOpenVolume)(This,Root);
         
        if (!EFI_ERROR(Status)) {
                if ((**Root).Open!=MySimpleFileSystemOpen) {
                        OldSimpleFileSystemOpen=(**Root).Open;
                        (**Root).Open=MySimpleFileSystemOpen;
                }        
        }
       return Status;
}  
EFI_STATUS
EFIAPI
MyEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
        EFI_STATUS        Status;
        //EFI_FILE_PROTOCOL *Root;
        EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFile;
        
        //Look for one Simple File System Protocol 
        Status = gBS->LocateProtocol (
                        &gEfiSimpleFileSystemProtocolGuid,
                        NULL,
                        &SimpleFile);
        if (EFI_ERROR(Status)) {
           gST->ConOut->OutputString(gST->ConOut,L"Can't find Simple File PROTOCOL\n");
           return Status;
        }
        OldSimpleFileSystemOpenVolume=SimpleFile->OpenVolume;
        SimpleFile->OpenVolume=MySimpleFileSystemOpenVolume;
        
        return Status;
}

 

运行结果:

完整的代码下载:

repfun

Devcon 下载

最近需要使用 DEVCON 又不想下载庞大的 WDK ,网上搜索了一下,找到如下的方法:

Windows 10 version 1803 Redstone 4
(April 2018 Update)
Windows Build: 10.0.17134
Driver Kit Build: 10.0.17134

X86

AMD64

下载之后,用7Z 之类的工具进行解压,在解压之后的文件中查找 filbad6e2cce5ebc45a401e19c613d0a28f 文件,改名为 devcon.exe 即可。

参考:
1.http://www.cnblogs.com/litifeng/p/9211792.html 如何安全的下载Devcon.exe文件
2.https://superuser.com/questions/1002950/quick-method-to-install-devcon-exe

Intel WHL HDK 来了

Whiskey Lake 是Intel 2018年推出的更新8代酷睿CPU,我最近拿到了一块六联智能(SIXUNITED)推出的 Whiskey Lake HDK 平台,这也是和我之前使用的 KabyLake-R HDK 属于同一系列的开发板。

相比之下,WHL HDK 的板子比之前 KBL-R 要小很多,有如下接口:
1. MIPI Camera
2. M.2 Sata/PCIE NVME
3. USB 接口,支持DCI 调试
4. M.2 PCIE 接口,支持WIFI、CNVI
5. PCIE x4 Slot
6. WWAN 接口 + SIM卡接口
7. Intel Sensor Hub 接口
8. HDMI 接口

开发板仍然采用 IO Board + Core Board 的设计方式,二者通过中间的 DIMM 插槽进行连接(去掉散热器的 Core Board 可以看到 SoC 还是挺大的):

此外,该板加入了检测功耗的功能,能够在本机或者远端机器上读取当前的各路电流功耗信息,从而能够实现监视每个设备的功耗的功能。下图是配套软件在另外的机器上读取功耗的情况,因为我不是硬件工程师,所以没做更详细的测试。

工作的照片,板载数码管直接输出80Port值,这样能够很方便的让用户识别当前状态。右下角的USB是提供给其他机器读取当前功耗信息的。

整体来说吗,感觉 WHL 平台功耗挺低的,在测试过程中,风扇转动的次数不多。

官网为http://www.hdkboards.com/ 有兴趣的朋友可以上去查看更多信息。

常见电容参数识别

常见电容器参数识别方法

电容单位是F

1法拉(F)= 1000毫法(mF)=1000000微法(μF)

1微法(μF)= 1000纳法(nF)= 1000000皮法(pF)   【参考1】

常见的电容标注:

  1. 直接在电容器上标注的,比如510P 10%。这种很容易识别。
  2. 三位数表示方法,前两位表示有效数字,第三位表示倍乘,有时候后面跟着一个字母表示允许误差,整体单位是 pf。比如:102表示 10X10^2pf。常见的标注如下
1PF 20PF(200) 100PF(101) 510PF(511) 10NF(103) 220NF(224)
4.7PF 22P(220) 150PF(151) 680PF(681) 22NF(223) 330NF(334)
6.8PF 30PF(300) 180PF(181) 1NF(102) 33NF(333) 470NF(474)
10PF(100) 33PF(330) 220PF(221) 2.2NF(222) 47NF(473) 1UF(105)
15PF(150) 47PF(470) 330PF(331) 3.3NF(332) 68NF(683) 2.2UF(225)
18PF(180) 68PF(680) 470PF(471PF) 4.7NF(472) 100NF(104) 4.7UF(475)
3.3UF(335) 10UF(106)
  1. 四位数字表示方法,有下面2中情况
    1. 4位整数,此时单位是pf。比如:6800 表示 6800pf
    2. 用小数(有时不足4位),此时单位是 uf。比如:47表示 0.47uf。【参考2】

参考:

  1. https://baike.baidu.com/item/%E7%94%B5%E5%AE%B9/146658?fr=aladdin#2
  2. 电子工程师必备—-元器件应用宝典

Step to UEFI (160)UEFI 下的 GIF 解码

现在最常见的 GIF 就是各种动图了,因为它足够小,无需支付版权费用和不用安装额外的插件,使得它成为分享动图的首选。
这次介绍来自https://github.com/lecram/gifdec 的 GIF 解码库,可以用来解码静态和动态GIF格式。
需要注意的是,这个库有下面2个局限(GIF 最多支持256色,这个限制是来自他的调色板只有256色。通常情况下,一幅有很多帧的动图都会共享同一个调色板。特殊情况下,每一帧都有一个调色板。这个库不支持后面那种情况):
* no support for GIF files that don’t have a global color table
* no direct support for the plain text extension (rarely used)

实例程序如下,我在 IA32 和 X64 NT32环境下测试过,都是可以正常工作的:

#include <stdlib.h>
#include <stdio.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include "gifdec.h"

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

#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
  { \
    0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a } \
  }
  
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;

int
main (
  IN int Argc,
  IN char *Argv[]
  )
{
    EFI_STATUS    Status;
   EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
    int i,j;

    UINT8* RGB32;
    UINT8* frame;

    if (Argc<2) {
            printf("Please input file name\n");
            return 0;
    }

    gd_GIF* gif = gd_open_gif(Argv[1]);
    if (gif == NULL) {
        printf("Open file Error\n");
        return -1;
    }

    printf("width %d height %d\n",gif->width,gif->height);
    printf("number of colors: %d\n", gif->palette->size);
    printf("number of frames: %d\n", gif->loop_count);

    Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
    if (EFI_ERROR(Status)) {
                GraphicsOutput = NULL;
                printf("Loading Graphics_Output_Protocol error!\n");
                return EFI_SUCCESS;}

                
    frame=(UINT8*)AllocatePool(gif->width*gif->height*3);
    RGB32 = (UINT8*)AllocatePool(gif->width*gif->height*4); 

       while (1)
       {
        int retx = gd_get_frame(gif);
        if (retx == -1) 
            break;
        gd_render_frame(gif, frame);
                    for (j=0;j<gif->height;j++) {
                            for (i=0;i<gif->width;i++) {
                                RGB32[(j*gif->width+i)*4]  = frame[(j*gif->width+i)*3+2]; //Blue   
                                RGB32[(j*gif->width+i)*4+1]= frame[(j*gif->width+i)*3+1]; //Green 
                                RGB32[(j*gif->width+i)*4+2]= frame[(j*gif->width+i)*3]; //Red  
                                RGB32[(j*gif->width+i)*4+3]=0;
                            }
                    GraphicsOutput->Blt(
                                GraphicsOutput, 
                                (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) RGB32,
                                EfiBltBufferToVideo,
                                0, 0, 
                                0, 0, 
                                gif->width, gif->height, 0);    
                              }                          

        if (retx == 0) 
                break;
            //gd_rewind(gif);
   }

   free(RGB32);  
    free(frame);

    gd_close_gif(gif);    

    return 0;
}

 

工作截图:

完整代码下载:
giftest

简单说一下工作流程,首先gd_open_gif() 函数打开 GIF,这步可以获得尺寸信息,但是从我实验来看,无法取得帧数信息(loop_count),但是readme 中说是可以的;接下来使用gd_get_frame() 函数检查当前GIF播放的位置,返回1表示还有下一帧,0表示达到尾部,-1表示出现错误;然后,gd_render_frame()函数取出来解码后的图像信息,经过格式变换即可通过 BLT显示出来;最后,检查是否播放完毕,完毕之后退出。
代码写的比较简单,效率不高,在处理较大 GIF 的时候,还是能够感受到刷新的。有兴趣的朋友可以琢磨一下如何提速,我猜测瓶颈应该是在处理解码后的数据RGB顺序的那里。

=======================================================================================
2018年11月23日 感谢 Cai 3023772977@qq.com 在评论中之处问题,代码中 GraphicsOutput() 函数,写在循环内了,这是导致播放缓慢的原因,将它移动到循环外面就可以正常工作了。

for (j=0;jheight;j++) {
for (i=0;iwidth;i++) {
RGB32[(j*gif->width+i)*4] = frame[(j*gif->width+i)*3+2]; //Blue
RGB32[(j*gif->width+i)*4+1]= frame[(j*gif->width+i)*3+1]; //Green
RGB32[(j*gif->width+i)*4+2]= frame[(j*gif->width+i)*3]; //Red
RGB32[(j*gif->width+i)*4+3]=0;
}

GraphicsOutput->Blt(
GraphicsOutput,
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) RGB32,
EfiBltBufferToVideo,
0, 0,
0, 0,
gif->width, gif->height, 0);

}

Teensy 3.2 GPIO 速度测试

做了一个简单的实验,测试 Teensy 3.2 板子IO 的速度,具体代码如下,就是使用digitalWrite进行 GPIO反转,然后示波器查看结果。

void setup() {
  // put your setup code here, to run once:
  pinMode(14,OUTPUT);
  digitalWrite(14,HIGH);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
  digitalWrite(14,LOW);
  digitalWrite(14,HIGH);
}

 

速度首先和主频有关系,可以在下面的位置找到,我这边测试72Mhz和96Mhz的情况。

另外,还和编译选项有关系。

1.72Mhz+Faster 测试结果是 1.21Mhz (光标测量,下同)

2.72Mhz + Faster with LTO 测试结果是 2.34Mhz (光标测量,下同)

3.96Mhz+Faster 测试结果是 1.61Mhz


4. 96Mhz+Faster with LTO 测试结果是 3.07Mhz