ILI9341实现模拟时钟

参考  http://embedded-lab.com/blog/tutorial-8-esp8266-internet-clock/ 这个代码,在 ILI9431 上实现模拟时钟的显示。

/***************************************************
  This is our GFX example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/


#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

// For the Adafruit shield, these are the default.
//#define ILI9341_DC 9
//#define ILI9341_CS 10

// For the Adafruit shield, these are the default.
#define ILI9341_DC D9
#define ILI9341_CS D8
#define ILI9341_MOSI MOSI
#define ILI9341_CLK SCK
#define ILI9341_RST D2
#define ILI9341_MISO MISO

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(ILI9341_CS, ILI9341_DC, ILI9341_RST);

float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0;    // Saved H, M, S x & y multipliers
float sdeg=0, mdeg=0, hdeg=0;
uint16_t osx=120, osy=120, omx=120, omy=120, ohx=120, ohy=120;  // Saved H, M, S x & y coords
uint16_t x0=0, x1=0, yy0=0, yy1=0;
uint8_t hh, mm, ss;
void setup() {
  Serial.begin(9600);
  Serial.println("ILI9341 Test!"); 
 
  tft.begin(2000000UL);


  tft.fillScreen(ILI9341_NAVY);
  
  tft.setTextColor(ILI9341_WHITE, ILI9341_DARKCYAN);  // Adding a background colour erases previous text automatically
  
  // Draw clock face
  tft.fillCircle(120, 120, 118, ILI9341_GREEN);
  tft.fillCircle(120, 120, 110, ILI9341_BLACK);

  // Draw 12 lines
  for(int i = 0; i<360; i+= 30) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*114+120;
    yy0 = sy*114+120;
    x1 = sx*100+120;
    yy1 = sy*100+120;

    tft.drawLine(x0, yy0, x1, yy1, ILI9341_GREEN);
  }

  // Draw 60 dots
  for(int i = 0; i<360; i+= 6) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*102+120;
    yy0 = sy*102+120;
    // Draw minute markers
    tft.drawPixel(x0, yy0, ILI9341_WHITE);
    
    // Draw main quadrant dots
    if(i==0 || i==180) tft.fillCircle(x0, yy0, 2, ILI9341_WHITE);
    if(i==90 || i==270) tft.fillCircle(x0, yy0, 2, ILI9341_WHITE);
  }

  tft.fillCircle(120, 121, 3, ILI9341_WHITE);
}

void loop(void) {
  for (hh=0;hh<12;hh++)
    for (mm=0;mm<60;mm++)
      for (ss=0;ss<60;ss++)
        {

    // Pre-compute hand degrees, x & y coords for a fast screen update
    sdeg = ss*6;                  // 0-59 -> 0-354
    mdeg = mm*6+sdeg*0.01666667;  // 0-59 -> 0-360 - includes seconds
    hdeg = hh*30+mdeg*0.0833333;  // 0-11 -> 0-360 - includes minutes and seconds
    hx = cos((hdeg-90)*0.0174532925);    
    hy = sin((hdeg-90)*0.0174532925);
    mx = cos((mdeg-90)*0.0174532925);    
    my = sin((mdeg-90)*0.0174532925);
    sx = cos((sdeg-90)*0.0174532925);    
    sy = sin((sdeg-90)*0.0174532925);

    if (ss==0) {
      // Erase hour and minute hand positions every minute
      tft.drawLine(ohx, ohy, 120, 121, ILI9341_BLACK);
      ohx = hx*62+121;    
      ohy = hy*62+121;
      tft.drawLine(omx, omy, 120, 121, ILI9341_BLACK);
      omx = mx*84+120;    
      omy = my*84+121;
    }

      // Redraw new hand positions, hour and minute hands not erased here to avoid flicker
      tft.drawLine(osx, osy, 120, 121, ILI9341_BLACK);
      osx = sx*90+121;    
      osy = sy*90+121;
      tft.drawLine(osx, osy, 120, 121, ILI9341_RED);
      tft.drawLine(ohx, ohy, 120, 121, ILI9341_WHITE);
      tft.drawLine(omx, omy, 120, 121, ILI9341_WHITE);
      tft.drawLine(osx, osy, 120, 121, ILI9341_RED);

      tft.fillCircle(120, 121, 3, ILI9341_RED);
      tft.setCursor(25, 245);
      if(hh <10) tft.print(0);
      tft.print(hh);
      tft.print(':');
      if(mm <10) tft.print(0);
      tft.print(mm);
      tft.print(':');
      if(ss <10) tft.print(0);
      tft.print(ss);
      tft.setCursor(65, 285);
//      tft.print(tzone);
    //tft.drawCentreString("Time flies",120,260,4);
 // delay(1000);
        }
} 

运行结果:

ILI9431 模拟时钟

文件下载:

Intel 平台减少 MRC Debug 信息的方法

默认情况下,当我们打开 Debug 功能编译代码后, MRC 部分会输出全部 Debug 信息会导致耗费很长时间才能启动。如果板子散热不好经常会在打印过程中直接断电和重启。为了避免这样的问题,我们需要尽量减少串口的 Debug 信息。作为输出大户,MRC 中的 Debug 信息非常多,所以需要关闭之。

在 MrcDebugPrint.h 中有如下代码,删除 define MRC_DEBUG_PRINT (1) 这一行即可。

#ifndef MDEPKG_NDEBUG
#ifndef MRC_DEBUG_PRINT
#define MRC_DEBUG_PRINT (1)
#endif
#endif // MDEPKG_NDEBUG

在软件工程上有一个特别重要的概念:Design for Debug。对于主板开发来说,同样需要从整体上来考虑。比如,主板需要预留烧写BIOS的接口,否则每次只能用夹子去夹起来刷。对于 Firmware 来说,要考虑准备Debug BIOS 还有 DCI 的配置,随时准备Debug。

另外,不要得罪 HW 工程师,否则他既不会给你预留少些口,也不会给你预留输出 Debug 信息的串口,就是这样。

Step to UEFI (222)BmpSupportLib

很早之前的文章介绍过如何在 Shell 下实实现 BMP 的显示【参考1】。从原理上来说就是读取 BMP 文件,然后进行解析,最后按照 BLT 要求的格式重新排列,最终用EfiBltBufferToVideo就可以显示出来。

最近在查看 EDK202008 的代码时,偶然发现了2个新增的函数在BmpSupportLib.h 文件中。从名称上看一个是将 BMP 转化为 BLT要求的GopBlt格式,另外一个是将 GopBlt 转为 BMP 的。

/**
  Translate a *.BMP graphics image to a GOP blt buffer. If a NULL Blt buffer
  is passed in a GopBlt buffer will be allocated by this routine using
  EFI_BOOT_SERVICES.AllocatePool(). If a GopBlt buffer is passed in it will be
  used if it is big enough.

  @param [in]      BmpImage      Pointer to BMP file.
  @param [in]      BmpImageSize  Number of bytes in BmpImage.
  @param [in, out] GopBlt        Buffer containing GOP version of BmpImage.
  @param [in, out] GopBltSize    Size of GopBlt in bytes.
  @param [out]     PixelHeight   Height of GopBlt/BmpImage in pixels.
  @param [out]     PixelWidth    Width of GopBlt/BmpImage in pixels.

  @retval RETURN_SUCCESS            GopBlt and GopBltSize are returned.
  @retval RETURN_INVALID_PARAMETER  BmpImage is NULL.
  @retval RETURN_INVALID_PARAMETER  GopBlt is NULL.
  @retval RETURN_INVALID_PARAMETER  GopBltSize is NULL.
  @retval RETURN_INVALID_PARAMETER  PixelHeight is NULL.
  @retval RETURN_INVALID_PARAMETER  PixelWidth is NULL.
  @retval RETURN_UNSUPPORTED        BmpImage is not a valid *.BMP image.
  @retval RETURN_BUFFER_TOO_SMALL   The passed in GopBlt buffer is not big
                                    enough.  The required size is returned in
                                    GopBltSize.
  @retval RETURN_OUT_OF_RESOURCES   The GopBlt buffer could not be allocated.

**/
RETURN_STATUS
EFIAPI
TranslateBmpToGopBlt (
  IN     VOID                           *BmpImage,
  IN     UINTN                          BmpImageSize,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL  **GopBlt,
  IN OUT UINTN                          *GopBltSize,
  OUT    UINTN                          *PixelHeight,
  OUT    UINTN                          *PixelWidth
  );

/**
  Translate a GOP blt buffer to an uncompressed 24-bit per pixel BMP graphics
  image. If a NULL BmpImage is passed in a BmpImage buffer will be allocated by
  this routine using EFI_BOOT_SERVICES.AllocatePool(). If a BmpImage buffer is
  passed in it will be used if it is big enough.

  @param [in]      GopBlt        Pointer to GOP blt buffer.
  @param [in]      PixelHeight   Height of GopBlt/BmpImage in pixels.
  @param [in]      PixelWidth    Width of GopBlt/BmpImage in pixels.
  @param [in, out] BmpImage      Buffer containing BMP version of GopBlt.
  @param [in, out] BmpImageSize  Size of BmpImage in bytes.

  @retval RETURN_SUCCESS            BmpImage and BmpImageSize are returned.
  @retval RETURN_INVALID_PARAMETER  GopBlt is NULL.
  @retval RETURN_INVALID_PARAMETER  BmpImage is NULL.
  @retval RETURN_INVALID_PARAMETER  BmpImageSize is NULL.
  @retval RETURN_UNSUPPORTED        GopBlt cannot be converted to a *.BMP image.
  @retval RETURN_BUFFER_TOO_SMALL   The passed in BmpImage buffer is not big
                                    enough.  The required size is returned in
                                    BmpImageSize.
  @retval RETURN_OUT_OF_RESOURCES   The BmpImage buffer could not be allocated.

**/
RETURN_STATUS
EFIAPI
TranslateGopBltToBmp (
  IN     EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *GopBlt,
  IN     UINT32                         PixelHeight,
  IN     UINT32                         PixelWidth,
  IN OUT VOID                           **BmpImage,
  IN OUT UINT32                         *BmpImageSize
  );

为此,编写一个测试程序进行测试,因为编译环境是 EDK201903 其中并没有对应的库,所以直接将上面提到的库从 EDK202008 中提取出来直接使用,同时还有一个SafeIntLib的库:

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

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

#include "BmpSupportLib.h"

extern  EFI_BOOT_SERVICES   *gBS;

static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;

INTN
EFIAPI
main (
  IN UINTN Argc,
  IN CHAR8 **Argv
  )
{
        FILE            *fp;
        long            BmpSize;
        char            *pBmpImage;
        
        UINTN           BltSize;
        UINTN           Height;
        UINTN           Width;
        EFI_STATUS      Status;
        EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt;
        EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
        
        // Check parameter
        if (Argc<2) {
                printf("Please input a file name\n");
        } else {
                // Open file
                fp=fopen(Argv[1],"rb");
                if (fp==NULL) {
                        printf("Can't open file [%s]\n",Argv[1]);
                        return EFI_SUCCESS;
                }
                
                // Get file size
                fseek(fp,0,SEEK_END);
                BmpSize=ftell(fp);
                printf("File size is [%d]\n",BmpSize);
                
                // Create a memory for BMP file
                pBmpImage=malloc(BmpSize);
                if (pBmpImage==NULL) {
                        printf("Memory allocate error\n");
                        fclose(fp);
                        return EFI_SUCCESS;
                }
                
                // Load BMP to memory
                fseek(fp,0,SEEK_SET);
                fread(pBmpImage, sizeof(char), BmpSize, fp);
                fclose(fp);
                
                Status = gBS->LocateProtocol(
                            &GraphicsOutputProtocolGuid, 
                            NULL,
                            (VOID **) &GraphicsOutput);
                if (EFI_ERROR(Status)) {
                    GraphicsOutput = NULL;
                    Print(L"Loading Graphics_Output_Protocol error!\n");
                    return EFI_SUCCESS;
                }
    
                // Translate BMP to  GopBlt
                Blt = NULL;
                Width = 0;
                Height = 0;
                Status = TranslateBmpToGopBlt (
                           pBmpImage,
                           BmpSize,
                           &Blt,
                           &BltSize,
                           &Height,
                           &Width
                           );
                if (EFI_ERROR (Status)) {
                  Print(L"TranslateBmpToGopBlt error!\n");
                  return Status;
                }

                // Show the GopBlt to screen
                Status = GraphicsOutput->Blt (
                             GraphicsOutput,
                             Blt,
                             EfiBltBufferToVideo,
                             0, // Source X
                             0, // Source Y
                             0x140, // Destination X
                             0, // Destination Y
                             Width,
                             Height,
                             Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL)
                             );
                if (EFI_ERROR (Status)) {
                  Print(L"GraphicsOutput->Blt error! [%r]\n",Status);
                  return Status;
                }
                free(pBmpImage);
    }

    return EFI_SUCCESS;
}

测试结果(NT32虚拟机)

NT32 模拟环境下测试 BmpSupportLib

源代码和编译后的 X64 代码可以在这里下载:

参考:

1. https://www.lab-z.com/showbmp/

Boot Guard 简介

As defined by Wikipedia: “Intel Boot Guard is a processor feature that prevents the computer from running firmware images not released by the system manufacturer. When turned on, the processors verifies a signature contained in the firmware image before executing it, using the hash of the public half of the signing key, which is fused into the system’s Platform Controller Hub (PCH) by the system manufacturer (not by Intel). Intel Boot Guard is an optional processor feature, meaning that it does not need to be activated during the system manufacturing. As a result, Intel Boot Guard, when activated, makes it impossible for end users to install replacement firmware such as Coreboot.” 来自: https://firmwaresecurity.com/2015/08/12/intel-boot-guard/

这是一个安全相关的功能,能够阻止第三方 Firmware运行在你的主板上。具体是做法是使用私钥对于客户自己的 Firmware 签名,然后在主板的生产过程中将公钥的 hash 写入 PCH,写入之后就无法修改(Fused) 。 之后每次上电的时候,PCH 会首先校验 Firmware 的签名,如果不符合就不会加载。

老狼在《什么是Boot Guard?电脑启动中的信任链条解析》文章中对此做了更详细的介绍,有兴趣的朋友可以在下面看到:

https://zhuanlan.zhihu.com/p/116740555?from_voters_page=true

Step to UEFI (220)微软提供的 UEFI Shell 截图工具

微软的 MU 项目提供了一个 Shell 下的截图工具:PrintScreenLogger,具体的介绍可以在下面看到:

https://microsoft.github.io/mu/dyn/mu_plus/MsGraphicsPkg/PrintScreenLogger/Readme/#printscreenlogger-operation

我尝试直接在 AppPkg 下编译了一下,可以通过编译。然后在实体机上进行了测试工作正常:

PrintScreenLogger.efi 运行结果

使用方法:

  1. 在你需要存放截图的盘上放置名为 PrintScreenEnable.txt 的文件(空文件即可),运行之后这个工具会将截图结果放置在存着这个文件的盘上;
  2. 使用 Load PrintScreenLogger.efi 加载(因为这个是一个 Driver);
  3. 截图快捷键是 ctrl + screen print ;

源代码来自 https://github.com/microsoft/mu_plus/tree/release/202005/MsGraphicsPkg/PrintScreenLogger

/** @file
PrintScreenLogger.c

PrintScreen logger to capture UEFI menus into a BMP written to a USB key

Copyright (C) Microsoft Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "PrintScreenLogger.h"

typedef struct {
    EFI_KEY_DATA KeyData;
    EFI_HANDLE   NotifyHandle;
} PRINT_SCREEN_KEYS;
//
// PrtScreen comes in as an EFI_SYS_REQUEST shift state.
//
// Register two notifications, one for a RightCtrl-PrtScn and one for a LeftCtrl-PrtScn
//      
STATIC PRINT_SCREEN_KEYS  gPrtScnKeys[] = {
    {
        {
            {0,0},
            {EFI_SHIFT_STATE_VALID | EFI_LEFT_CONTROL_PRESSED  | EFI_SYS_REQ_PRESSED, 0}
        },
        NULL
    },
    {
        {
            {0,0},
            {EFI_SHIFT_STATE_VALID | EFI_RIGHT_CONTROL_PRESSED | EFI_SYS_REQ_PRESSED, 0}
        },
        NULL
    }
};

#define NUMBER_KEY_NOTIFIES (sizeof(gPrtScnKeys)/sizeof(PRINT_SCREEN_KEYS)) 

// Global variables.
//
STATIC EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *gTxtInEx    = NULL;
STATIC EFI_EVENT                          gTimerEvent = NULL;

/**

  Scan USB Drives looking for a file named PrintScreenEnable.txt.  The presence
  of this file indicates it is OK to write print screen files to this drive.

  @param    Fs_Handle       Handle to the opened volume.

  @retval   EFI_SUCCESS     The FS volume was opened successfully.
  @retval   Others          The operation failed.

**/
EFI_STATUS
FindUsbDriveForPrintScreen (
  OUT EFI_FILE_PROTOCOL  **VolumeHandle
  )
{
    EFI_FILE_PROTOCOL               *FileHandle;
    EFI_FILE_PROTOCOL               *VolHandle;
    EFI_HANDLE                      *HandleBuffer;
    UINTN                            Index;
    UINTN                            NumHandles;
    EFI_STATUS                       Status;
    EFI_STATUS                       Status2;
    EFI_DEVICE_PATH_PROTOCOL        *BlkIoDevicePath;
    EFI_DEVICE_PATH_PROTOCOL        *UsbDevicePath;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SfProtocol;
    EFI_HANDLE                       Handle;

    NumHandles = 0;
    HandleBuffer = NULL;
    SfProtocol = NULL;

    //
    // Locate all handles that are using the SFS protocol.
    //
    Status = gBS->LocateHandleBuffer(ByProtocol,
                                     &gEfiSimpleFileSystemProtocolGuid,
                                     NULL,
                                     &NumHandles,
                                     &HandleBuffer);

    if (EFI_ERROR(Status) != FALSE) {
        DEBUG((DEBUG_ERROR, "%a: failed to locate any handles using the Simple FS protocol (%r)\n", __FUNCTION__, Status));
        goto CleanUp;
    }

    //
    // Search the handles to find one that has has a USB node in the device path.
    //
    for (Index = 0; (Index < NumHandles); Index += 1) {
        //
        // Insure this device is on a USB controller
        //
        UsbDevicePath = DevicePathFromHandle(HandleBuffer[Index]);
        if (UsbDevicePath == NULL) {
            continue;
        }
        Status = gBS->LocateDevicePath (&gEfiUsbIoProtocolGuid,
                                        &UsbDevicePath,
                                        &Handle);
        if (EFI_ERROR(Status)) {
            // Device is not USB;
            continue;
        }

        //
        // Check if this is a block IO device path. 
        //
        BlkIoDevicePath = DevicePathFromHandle(HandleBuffer[Index]);
        if (BlkIoDevicePath == NULL) {
            continue;
        }
        Status = gBS->LocateDevicePath(&gEfiBlockIoProtocolGuid, 
                                       &BlkIoDevicePath, 
                                       &Handle);
        if (EFI_ERROR(Status)) {
            // Device is not BlockIo;
            continue;
        }

        Status = gBS->HandleProtocol(HandleBuffer[Index],
                                     &gEfiSimpleFileSystemProtocolGuid,
                                     (VOID**)&SfProtocol);

        if (EFI_ERROR(Status)) {
            DEBUG((DEBUG_ERROR, "%a: Failed to locate Simple FS protocol. %r\n", __FUNCTION__, Status));
            continue;
        }

        //
        // Open the volume/partition.
        //
        Status = SfProtocol->OpenVolume(SfProtocol, &VolHandle);
        if (EFI_ERROR(Status) != FALSE) {
            DEBUG((DEBUG_ERROR,"%a: Unable to open SimpleFileSystem. Code = %r\n", __FUNCTION__, Status));
            continue;
        }

        //
        // Insure the PrinteScreenEnable.txt file is present
        //
        Status = VolHandle->Open (VolHandle, &FileHandle, PRINT_SCREEN_ENABLE_FILENAME, EFI_FILE_MODE_READ, 0);
        if (EFI_ERROR(Status)) {
            DEBUG((DEBUG_INFO,"%a: Print Screen not supported to this device. Code = %r\n", __FUNCTION__, Status));
            Status2 = VolHandle->Close (VolHandle);
            if (EFI_ERROR(Status2)) {
                DEBUG((DEBUG_ERROR,"%a: Error closing Vol Handle. Code = %r\n", __FUNCTION__, Status2));
            }
            continue;
        }

        FileHandle->Close (FileHandle);
        *VolumeHandle = VolHandle;
        Status = EFI_SUCCESS;
        break;
    }

CleanUp:
    if (HandleBuffer != NULL) {
        FreePool(HandleBuffer);
    }

    return Status;
}

/**
  Convert a Gop 32 bits per pixel video frame buffer to a 
  24 bits per pixel *.BMP graphics image

  @param  BmpFileName   Name of file to create
  @param  Gop           GRAPHICS_OUTPUT_PROTOCOL
  @param  BltBuffer     Buffer containing GOP version of BmpImage.

  @retval EFI_SUCCESS           GopBlt and GopBltSize are returned.
  @retval EFI_UNSUPPORTED       BmpImage is not a valid *.BMP image
  @retval EFI_BUFFER_TOO_SMALL  The passed in GopBlt buffer is not big enough.
                                GopBltSize will contain the required size.
  @retval EFI_OUT_OF_RESOURCES  No enough buffer to allocate.

**/
EFI_STATUS
WriteBmpToFile (
  IN EFI_FILE_PROTOCOL             *FileHandle
) {

    EFI_STATUS                     Status;
    BMP_IMAGE_HEADER              *BmpHeader;
    UINTN                          DataSizePerLine;
    UINTN                          BmpBufferSize;
    UINT8                         *Image;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL *Blt;
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer;
    UINT32                         Height;
    UINT32                         Width;
    UINT64                         WriteSize;

    EFI_GRAPHICS_OUTPUT_PROTOCOL   *Gop;

#define BMP_BITS_PER_PIXEL  24

    BmpHeader = NULL;
    BltBuffer = NULL;

    Status = gBS->LocateProtocol (&gEfiGraphicsOutputProtocolGuid,
                                  NULL,
                                  (VOID **)&Gop
                                 );
    if (EFI_ERROR(Status)) {
        DEBUG((DEBUG_ERROR, "Unable to locate Gop protocol\n"));
        return Status;
    }

    if ((Gop->Mode->Info->PixelFormat != PixelRedGreenBlueReserved8BitPerColor) &&
        (Gop->Mode->Info->PixelFormat != PixelBlueGreenRedReserved8BitPerColor)) {
        DEBUG((DEBUG_ERROR, "%a: Unsupported video mode\n", __FUNCTION__));
        return EFI_UNSUPPORTED;
    }

    BltBuffer = (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)  AllocatePool (Gop->Mode->FrameBufferSize); 
    if (NULL == BltBuffer) {
        return EFI_OUT_OF_RESOURCES;
    }

    Height = Gop->Mode->Info->VerticalResolution;
    Width = Gop->Mode->Info->HorizontalResolution;

    Status = Gop->Blt (Gop,
                       BltBuffer,
                       EfiBltVideoToBltBuffer,
                       0,
                       0,
                       0,
                       0,
                       Width,
                       Height,
                       0
                      );
    if (EFI_ERROR(Status)) {
        DEBUG((DEBUG_ERROR, "Unable to BLt video to buffer, code=%r\n",Status));
        goto ErrorExit;
    }

    DataSizePerLine = ((Gop->Mode->Info->HorizontalResolution * BMP_BITS_PER_PIXEL + 31) >> 3) & (~0x3);
    BmpBufferSize = MultU64x32 (DataSizePerLine, Gop->Mode->Info->VerticalResolution) + sizeof(BMP_IMAGE_HEADER) + ((sizeof(BMP_IMAGE_HEADER) + 3) & ~0x03);

    if (BmpBufferSize > (UINT32) ~0) {
        Status = EFI_INVALID_PARAMETER;
        goto ErrorExit;
    }

    BmpHeader = AllocateZeroPool (BmpBufferSize); // Insure unfilled area is zeroed
    if (NULL == BmpHeader) {
        Status = EFI_OUT_OF_RESOURCES;
        goto ErrorExit;
    }

    Status = EFI_SUCCESS;

    BmpHeader->CharB = 'B';           // Header flag
    BmpHeader->CharM = 'M';
    BmpHeader->Size = (UINT32) BmpBufferSize;
    BmpHeader->Reserved[0] = 0;
    BmpHeader->Reserved[1] = 0;
    BmpHeader->ImageOffset = (sizeof(BMP_IMAGE_HEADER) + 3) & ~0x03;  // Start first row on 4 byte boundary
    BmpHeader->HeaderSize = sizeof (BMP_IMAGE_HEADER) - OFFSET_OF(BMP_IMAGE_HEADER, HeaderSize);
    BmpHeader->PixelWidth = Width;
    BmpHeader->PixelHeight = Height;
    BmpHeader->Planes = 1;
    BmpHeader->BitPerPixel = 24;
    BmpHeader->CompressionType = 0;   // Not Compressed
    BmpHeader->ImageSize = 0;
    BmpHeader->XPixelsPerMeter = 11000;  // Approximately 300 dpi
    BmpHeader->YPixelsPerMeter = 11000;
    BmpHeader->NumberOfColors = 0;
    BmpHeader->ImportantColors = 0;

    Image = ((UINT8 *) BmpHeader) + BmpHeader->ImageOffset;

    for (Height = 0; Height < BmpHeader->PixelHeight; Height++) {
        Blt = &BltBuffer[(BmpHeader->PixelHeight - Height - 1) * BmpHeader->PixelWidth];
        for (Width = 0; Width < BmpHeader->PixelWidth; Width++, Blt++) {
            if (Gop->Mode->Info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) {
                *Image++ = Blt->Red;
                *Image++ = Blt->Green;
                *Image++ = Blt->Blue;
            } else {    // PixelBlueGreenRedReserved8BitPerColor
                *Image++ = Blt->Blue;
                *Image++ = Blt->Green;
                *Image++ = Blt->Red;
            }   
        }
        Image = (UINT8 *)(  ((UINT64)Image + 3) & ~0x03);  // Start next row on 4 byte boundary.
    }

    WriteSize = BmpBufferSize;
    Status = FileHandle->Write (FileHandle, &WriteSize, BmpHeader);
    if (EFI_ERROR(Status)) {
        DEBUG((DEBUG_ERROR, "Error writing Bmp file. Code=%r\n", Status));
    }
    if (WriteSize != BmpBufferSize) {
        DEBUG((DEBUG_ERROR, "Wrong number of bytes written.  S/B=%ld, Actual=%ld\n", BmpBufferSize, WriteSize));
        Status = EFI_BAD_BUFFER_SIZE;
    }

ErrorExit:
    if (BltBuffer != NULL) {
        FreePool (BltBuffer);
    }

    if (BmpHeader != NULL) {
        FreePool (BmpHeader );
    }

    return Status;
}

/**
  Handler for hot key notification

  @param KeyData         A pointer to a buffer that is filled in with the keystroke
                         information for the key that was pressed.

  @retval  EFI_SUCCESS   Always - Return code is not used by SimpleText providers.

**/
EFI_STATUS
EFIAPI
PrintScreenCallback (
  IN EFI_KEY_DATA     *KeyData
)
{   
    EFI_FILE_PROTOCOL *FileHandle;
    UINTN              Index;
    CHAR16             PrtScrnFileName[] = L"PrtScreen####.bmp";
    EFI_STATUS         Status;
    EFI_STATUS         Status2;
    EFI_FILE_PROTOCOL *VolumeHandle;

    // We only register two keys - LeftCtrl-PrtScn and RightCtrl-PrtScn.  
    // Assume print screen function if this function is called.
    DEBUG((DEBUG_INFO,"%a: Starting PrintScreen capture. Sc=%x, Uc=%x, Sh=%x, Ts=%x\n",
        __FUNCTION__,
        KeyData->Key.ScanCode,
        KeyData->Key.UnicodeChar,
        KeyData->KeyState.KeyShiftState,
        KeyData->KeyState.KeyToggleState));

    Status = gBS->CheckEvent (gTimerEvent);

    if (Status == EFI_NOT_READY) {
        DEBUG((DEBUG_INFO,"Print Screen request ignored\n"));
        return EFI_SUCCESS;
    }

    //
    // 1. Find a suitable USB drive - one that has PrintScreenEnable.txt on it.
    //
    Status = FindUsbDriveForPrintScreen(&VolumeHandle);

    if (!EFI_ERROR(Status)) {
        //
        // 2. Find the first value of PrtScreen#### that is available 
        //
        Index = 0;

        do {
            Index++;
            if (Index > MAX_PRINT_SCREEN_FILES) {
                goto Exit;
            }

            UnicodeSPrint (PrtScrnFileName, sizeof (PrtScrnFileName), L"PrtScreen%04d.bmp", Index);
            Status = VolumeHandle->Open (VolumeHandle, &FileHandle, PrtScrnFileName, EFI_FILE_MODE_READ, 0);
            if (!EFI_ERROR(Status)) {
                if (Index % PRINT_SCREEN_DEBUG_WARNING == 0) {
                    DEBUG((DEBUG_INFO,"%a: File %s exists.  Trying again\n", __FUNCTION__, PrtScrnFileName));                    
                }
                Status2 = FileHandle->Close (FileHandle);
                if (EFI_ERROR(Status2)) {
                    DEBUG((DEBUG_ERROR,"%a: Error closing File Handle. Code = %r\n", __FUNCTION__, Status2));
                }
                continue;
            }
            if (Status == EFI_NOT_FOUND) {
                break;
            }
        } while (TRUE); 

        //
        // 3. Create the new file that will contain the bitmap
        //
        Status = VolumeHandle->Open (VolumeHandle, &FileHandle, PrtScrnFileName, EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, EFI_FILE_ARCHIVE);
        if (EFI_ERROR(Status)) {
            DEBUG((DEBUG_ERROR,"%a: Unable to create file %s. Code = %r\n", __FUNCTION__, PrtScrnFileName, Status));
            goto Exit;
        }

        //
        // 4. Write the contents of the display to the new file
        //
        Status = WriteBmpToFile (FileHandle);
        if (!EFI_ERROR(Status)) {
            DEBUG((DEBUG_INFO,"%a: Screen captured to file %s.\n", __FUNCTION__, PrtScrnFileName));
        }
        //
        // 4. Close the bitmap file
        //
        Status2 = FileHandle->Close (FileHandle);
        if (EFI_ERROR(Status2)) {
            DEBUG((DEBUG_ERROR,"%a: Error closing bit map file %s. Code = %r\n", __FUNCTION__, PrtScrnFileName, Status2));
        }
Exit:
        //
        // 5. Close the USB volume
        //
        Status2 = VolumeHandle->Close (VolumeHandle);
        if (EFI_ERROR(Status2)) {
            DEBUG((DEBUG_ERROR,"%a: Error closing Vol Handle. Code = %r\n", __FUNCTION__, Status2));
        }
    }

    // Ignore future PrtScn requests for some period.  This is due to the make
    // and break of PrtScn being identical, and it takes a few seconds to complete
    // a single screen capture.
    Status = gBS->SetTimer (gTimerEvent, TimerRelative, PRINT_SCREEN_DELAY);
   
    return EFI_SUCCESS;
}

/**
  Unregister TxtIn callbacks and end the timer

**/
VOID
UnRegisterNotifications ( 
    VOID
    ) {
    INTN       i;
    EFI_STATUS Status;

    for (i = 0; i < NUMBER_KEY_NOTIFIES; i++) {
        if (gPrtScnKeys[i].NotifyHandle != NULL) {
            Status = gTxtInEx->UnregisterKeyNotify (gTxtInEx,  gPrtScnKeys[i].NotifyHandle);
            if (EFI_ERROR(Status)) {
                DEBUG((DEBUG_ERROR, "%a: Unable to uninstall TxtIn Notify. Code = %r\n", __FUNCTION__, Status));
            }        
        }    
    }

    if (gTimerEvent != NULL) {
        gBS->SetTimer (gTimerEvent, TimerCancel, 0);
        gBS->CloseEvent (gTimerEvent);

    }
}

/**

  Callback to cleanup the driver on unload.

  @param    Event           Not Used.
  @param    Context         Not Used.
  
  @retval   None
  
**/
EFI_STATUS
EFIAPI
PrintScreenLoggerUnload (
  IN  EFI_HANDLE   ImageHandle
  )
{

    DEBUG((DEBUG_INFO, "%a: unloading...\n", __FUNCTION__));

    UnRegisterNotifications ();

    return EFI_SUCCESS;
}

/**
  Main entry point for this driver.

  @param    ImageHandle     Image handle of this driver.
  @param    SystemTable     Pointer to the system table.

  @retval   EFI_STATUS      Always returns EFI_SUCCESS.
  
**/
EFI_STATUS
EFIAPI
PrintScreenLoggerEntry (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
    EFI_STATUS      Status = EFI_NOT_FOUND;
    INTN            i;

    DEBUG((DEBUG_LOAD, "%a: enter...\n", __FUNCTION__));

    //
    // 1. Get access to ConSplitter's TextInputEx protocol
    //
    if (gST->ConsoleInHandle != NULL) {
        Status = gBS->OpenProtocol (
                        gST->ConsoleInHandle,
                        &gEfiSimpleTextInputExProtocolGuid,
                        (VOID **) &gTxtInEx,
                        ImageHandle,
                        NULL,
                        EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
    } 

    if (EFI_ERROR(Status)) {
        DEBUG((DEBUG_ERROR, "%a: Unable to access TextInputEx protocol. Code = %r\n", __FUNCTION__, Status));
    }  else {

        //
        // 2.  Register for PrtScn callbacks
        //
        for (i = 0; i < NUMBER_KEY_NOTIFIES; i++) {
             Status = gTxtInEx->RegisterKeyNotify (
                          gTxtInEx,
                          &gPrtScnKeys[i].KeyData,
                          PrintScreenCallback,
                          &gPrtScnKeys[i].NotifyHandle);
            if (EFI_ERROR (Status)) {
                 DEBUG ((DEBUG_ERROR, "%a: Error registering key %d. Code = %r\n", __FUNCTION__, i, Status));
                 break;
            }
        }

        if (!EFI_ERROR(Status)) {
            //
            // 3. Create the PrtScn hold off timer
            //
            Status = gBS->CreateEvent(
                                EVT_TIMER,
                                0,
                                NULL,
                                NULL,
                                &gTimerEvent);
            if (!EFI_ERROR(Status)) {
                //
                // 4. Place event into the signaled state indicating PrtScn is active.
                //
                Status = gBS->SignalEvent (gTimerEvent);                
            }
        }
 
        if (!EFI_ERROR(Status)) {
            DEBUG((DEBUG_INFO, "%a: exit. Ready for Ctl-PrtScn operation\n", __FUNCTION__));                
        } else {
            UnRegisterNotifications ();
            DEBUG((DEBUG_ERROR, "%a: exit with errors. Ctl-PrtScn not operational. Code=%r\n", __FUNCTION__, Status));                
        }
    }

    return EFI_SUCCESS;
}

完整代码下载:

编译后的 X64 EFI 下载:

Step to UEFI (221)FASM 编译生成 EFI

之前介绍过使用 Nasm 生成 EFI 程序,这次介绍如何使用 FASM 来生成。

首先,准备 FASM 编译器,可以在 http://flatassembler.net/download.php  下载 Windows版本,例如:flat assembler 1.73.25 for Windows。这个工具不需要安装,解压之后就可以使用。解压后放在C:\BuildBs\fasmw17325目录下。

接下来编译测试 https://github.com/manusov/UEFIusbScan 这个项目的代码,它是 FASM 编写的 Shell 下显示本机 USB Host 和连接情况的工具。

编译命令如下:

C:\BuildBs\fasmw17325\UEFIusbScan-master\source>C:\BuildBs\fasmw17325\FASM.EXE ScanXhci.asm
flat assembler  version 1.73.25  (1048576 kilobytes memory)
4 passes, 4096 bytes.

编译后就能生成 ScanXhci.efi ,在实体机上运行结果如下:

完整代码和 X64 EFI 下载:

在作者的GitHub 页面上还有很多 FASM 编写的UEFI Application :  https://github.com/manusov  有兴趣的朋友可以去看看。

ESP32开发板 WIFI+蓝牙 物联网 智能家居 ESP-WROOM-32 ESP-32S

前一段使用DFRobot的 FireBeelte ,后来有了问题,于是入手 Taobao 便宜的 ESP32 来使用。

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.58602e8dzr7xuq&id=547069527125&_u=fkf8s93c8b
https://github.com/Nicholas3388/LuaNode/blob/master/images/ESP32_dimension.png

https://microcontrollerslab.com/esp32-pinout-use-gpio-pins/

在Board Manager 中选择NodeMCU-32S 编译上传。

EDK2 202008 来了

今天偶然注意到edk2 的最新版本:edk2-stable202008

https://github.com/tianocore/edk2/releases/tag/edk2-stable202008 可以下载到。

从资料上看,目前 Windows 下的编译工具已经切换到了 VS2019。

EDK2 202008 Windows VS2019 测试结果

这个版本是 2020 九月 四日 Release 的,改动如下:

下载代码进行简单的测试(我仍然使用 VS2015):

第一步,在 VS2015 X86 Native 窗口下,使用 edksetup forcerebuild 命令编译 build 中使用到的工具。但是编译过程中会报错,错误指向  Brotli ,这是一个压缩算法, Github 给出的代码只是给出了指向它的链接,所以下载到的代码中并不包括,所以需要我们手工补充之。

EDK2 代码没有直接包括 Brotli

下载到指定的版本(这里是 66C328),放置在edk202008\BaseTools\Source\C\BrotliCompress 目录下,再次编译即可通过:

EDK2 编译工具编译正常

第二步,编译模拟器的代码。在 edk2/MdeModulePkg/Library/BrotliCustomDecompressLib/ 同样需要放置Brotli的代码。编译命令:

build -p EmulatorPkg\EmulatorPkg.dsc -t VS2015x86 -a X64

EDK2 Emulator 编译正常

编译结果是edk202008\Build\EmulatorX64\DEBUG_VS2015x86\X64\ WinHost.exe

Emulator 运行正常

结论:使用 VS2015 仍然可以正常编译 EDK2 202008。

完整的代码我在Baidu网盘中放置了一份,有需要的朋友可以下载:

链接: https://pan.baidu.com/s/14luXRtDvtfx0zR9-8_WmNQ 提取码: 6i33

推荐《从零开始的UEFI裸机编程》

最近偶然看到这本书,大概浏览了一下,非常适合初学者阅读。

从零开始的UEFI裸机编程
フルスクラッチで作る!UEFIベアメタルプログラミング

大神 祐真 著, 神楽坂琴梨 译

Gitbub 页面的简单介绍

本书的目录:

  1. 从零开始的UEFI裸机编程
  2. 译者的话
  3. 1. 第一部分
    1. 1.1. 引言
    2. 1.2. Hello UEFI!
      1. 1.2.1. 遵循UEFI标准编写程序
      2. 1.2.2. 交叉编译为UEFI可执行格式
      3. 1.2.3. 引导并运行UEFI应用程序
    3. 1.3. 获取按键输入
      1. 1.3.1. 简单文本输入协议
      2. 1.3.2. 编写一个回显程序
      3. 1.3.3. 编写一个简单的Shell
    4. 1.4. 在屏幕上绘制图形
      1. 1.4.1. 图形输出协议
      2. 1.4.2. 在屏幕上画一个矩形
      3. 1.4.3. 制作一个简单的GUI
    5. 1.5. 获取鼠标输入
      1. 1.5.1. 简单指针协议
      2. 1.5.2. 查看鼠标状态
      3. 1.5.3. 实现鼠标指针
    6. 1.6. 文件读写
      1. 1.6.1. 文件相关的协议
      2. 1.6.2. 列出目录下文件
      3. 1.6.3. GUI下列出文件
      4. 1.6.4. 读取文本文件
      5. 1.6.5. GUI下浏览文本文件
      6. 1.6.6. 编辑文本文件
      7. 1.6.7. GUI下编辑文本文件
    7. 1.7. 扩展poiOS的功能
      1. 1.7.1. 显示图片
      2. 1.7.2. 实现退出功能
      3. 1.7.3. 更大的鼠标指针
      4. 1.7.4. 功能展示
    8. 1.8. 后记
    9. 1.9. 参考资料
  4. 2. 第二部分
    1. 2.1. 引言
    2. 2.2. 控制台输出
      1. 2.2.1. 设置文字颜色和背景色
      2. 2.2.2. 检测字符串是否能被显示
      3. 2.2.3. 获取支持的文本显示模式
      4. 2.2.4. 设置文本显示模式
    3. 2.3. 键盘输入
      1. 2.3.1. 绑定按键事件
    4. 2.4. 加载和执行UEFI应用程序
      1. 2.4.1. 查看当前设备路径
      2. 2.4.2. 创建设备路径
      3. 2.4.3. 从设备路径加载镜像
      4. 2.4.4. 设备路径的“设备”
      5. 2.4.5. 创建完整的设备路径
      6. 2.4.6. 从完整的设备路径加载镜像
      7. 2.4.7. 运行加载的镜像
      8. 2.4.8. 启动Linux: 编译并启动内核
      9. 2.4.9. 启动Linux: 内核启动参数
    5. 2.5. 计时器事件
      1. 2.5.1. 暂停程序执行
      2. 2.5.2. 利用事件处理函数异步操作
    6. 2.6. 其他功能
      1. 2.6.1. 内存分配器
      2. 2.6.2. 软关机
    7. 2.7. 后记
    8. 2.8. 参考资料
  5. 关于本书

中译本在 https://kagurazakakotori.github.io/ubmp-cn/index.html 可以看到,有兴趣的朋友可以查看。

Step to UEFI (219)Windows 下的 IoApic

前面介绍了 Shell 下的 IoApic 的读写,这次研究一下 Windows 下面的。在 Windows 下面,我们可以直接使用 RW_Everything 来进行读取,设定如下:

RW_Everything 读取 IoApic

读取结果如下:

RW_Everything 读取 IoApic 结果

我们特别关注 0x12 偏移处的值为 0x980 (这台机器和之前的 WinDBG 调试的不是同一个机器,IoApic 不同)。我猜测这里是PS/2 keyboard 的 IRQ0。参照资料修改 Bit16 (Interrupt Mask),设置为1会 Disable 这个中断。于是 x012 会被写为 0x0001 0980,这时候我发现键盘无法工作了。

IoApic Interrupt Mask

测试的这个机器是笔记本,键盘是挂在EC 下面的,EC汇报给系统的是PS/2键盘。

Device Manager PS/2 Keyboard

于是,通过这样的方式我阻止了 IRQ1 的产生,因此按键会无效。接下来,再将这个恢复为之前的 0x980,但是笔记本键盘是不工作的。我猜测应该有可能是按键堵住了 Buffer,所以再使用 RW 读取 0x60 IO port(就是打开 0x60 IO Port 页面看一下),键盘即恢复了工作。有兴趣的朋友可以自行尝试,记得需要连接一个 USB 键盘。

结论:我们现在谈论的IRQ 只的是针对 IOAPIC的Number, 比如,IRQ1 位于 I/O Redirection Table Register 1 的位置,同时这个 Register 会给出对应的 Vector。当中断发生时,CPU 会根据 Vector 找到 IDT 中给出来的ISR (Interrupt Service Routine)。

额外的实验,我们可以编写一个 Python Script 来配合 Dbc实现 IoApic 的读取,代码如下:

def Test():
    def get_APIC_DAT(index):
        _base.mem(0xfec00000 , 1, index)
        return _base.mem(0xfec00010, 4)
    import common.baseaccess as _baseaccess
    _base = _baseaccess.getglobalbase()    
    print("Script from www.lab-z.com")
    for i in range(0,0x77):
        print("%02x:%08X %08X" %(i,get_APIC_DAT(0x10+i*2),get_APIC_DAT(0x10+i*2+1)))        

上述代码命名为 myscript.py, 运行结果如下:

PythonSv 读取 IoApic

20220114 RW_Everything 访问 0x60 port 的方法如下:

RW_Everything 访问 IO 60Port