pretty code

2017年8月31日 星期四

UEFI Application - Event

基本上 UEFI 是個單工的系統,它並不支持中斷
而是透過 Event﹝Timer interrupt﹞來達成任務的分配

底下是一個簡單 create Event 的例子
這個 Event 綁定在 1 個 5 秒鐘會觸發 1 次的 Timer 上
不多說,直接來看範例

#include <Library/UefiBootServicesTableLib.h>
#include <stdio.h>
#include <time.h>
//-----------------------------------------------------------------------------
static int g_count = 1;
//-----------------------------------------------------------------------------
void printTime(void)
{
    struct tm *t1;
    time_t ret;

    time(&ret);
    ret += 8 * 60 * 60;
    t1 = gmtime(&ret);

    printf(
        "[%04d-%02d-%02d %02d:%02d:%02d] %02d - in NotifyFunction \n",
        t1->tm_year + 1900,
        t1->tm_mon + 1,
        t1->tm_mday,
        t1->tm_hour,
        t1->tm_min,
        t1->tm_sec,
        g_count
    );
}
//-----------------------------------------------------------------------------
VOID EFIAPI 
NotifyFunction(
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
    printTime();

    g_count++;
}
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
    EFI_EVENT event;

    gBS->CreateEvent(
        EVT_TIMER | EVT_NOTIFY_SIGNAL,
        TPL_NOTIFY,
        NotifyFunction,
        NULL,
        &event
    );

    // every 5 seconds to trigger
    gBS->SetTimer(event, TimerPeriodic, 50000000);

    do {
        // wait NotifyFunction
    } while(g_count < 10);

    gBS->SetTimer(event, TimerCancel, 0);

    gBS->CloseEvent(event);

    return 0;
}

2017年8月30日 星期三

UEFI Application - network support

UEFI 下,想要撰寫 TCP / IP 的程式,說難不是太難,說簡單也不是很簡單。

簡單來說,如果很久以前曾在 Linux or Unix 下寫過 socket 程式,那我想這就不是一件很難的事,只要注意一些 header 檔案的位置跟傳統的不一樣即可。



從上圖可以得知,要在 UEFI Shell 下使用網路,需具備 4 要件:
1. 要有網卡。
2. 要有 UNDI driver。
3. 載入 Simple Network Protocol。
4. 載入 TCP / IP Stack。

註:從 Intel 的文件 "UEFI Driver Development Guide for Network Boot Devices" 來看,SNPTCP / IP Stack 中間似乎還有一層 MNP ( Managed Network Protocol ),不過這邊我們可以先忽略它。

以我的小電腦來說,除了 Simple Network Protocol 以外,其餘皆已內建,故我從 UDK2014 code base 中 build 出此 driver 即可﹝C:\edk2-UDK2014\MdeModulePkg\Universal\Network﹞,接著開機時,使用 "load" command 載入 driver,便可以順利使用 "ifconfig" command 取得 IP。

再來就是跟以前撰寫 socket 程式一樣,呼叫 socket, connect, send, close 等函式完成想要完成的工作,底下為 Client 端發送封包的例子。

2025/06/14 更新

拿掉之前的測試程式,因為只是範例,連 main 都沒有。

TCPClient.c

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

#include <netinet/in.h>
#include <sys/EfiSysCall.h>
//#include <sys/endian.h>
#include <sys/socket.h>
#include <arpa/inet.h>

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

int
testTCPSocket (
  IN unsigned long serverIP,
  IN int serverPort
  )
{
    int rc;
    int sock;
    struct sockaddr_in v4;

    char data[1024];
    size_t sent_bytes;
    size_t sendstrlen;

    sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    //sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP);
    if (sock == -1)
    {
        printf("init socket error\n");
        return -1;
    }

    memset(&v4, 0x00, sizeof(v4));

    v4.sin_len = sizeof(v4);
    v4.sin_family = AF_INET;
    v4.sin_addr.s_addr = serverIP;
    v4.sin_port = htons(serverPort);

    rc = connect(sock, (struct sockaddr*)&v4, sizeof(v4));
    if (rc == -1)
    {
        printf("connect() failed (%d)\n", rc);
        //return -1;
    }

    printf("input string to send or 'q' to exit connection\n");

    while (1)
    {
        memset(data, 0x00, sizeof(data));
        //scanf("%s", data);
        fgets(data, sizeof(data) - 1, stdin);
        if (data[0] == 'q')
        {
            printf("ready to exit connection\n");
            break;
        }

        sendstrlen = strlen(data);

        if (sendstrlen > 0 && sendstrlen < 1023)
        {
            sent_bytes = send(sock, data, sendstrlen, 0);
            printf("\t !!! Sent data: %s(%d) --- \n", data, sent_bytes);
        }
    }

    close(sock);

    return 0;
}

unsigned long
getServerIP (
  IN int Argc,
  IN char **Argv
  )
{
    UINT32 ip[4];
    int rc;
    unsigned long serverIP;

    int i;

    if (Argc < 2)
    {
        return 0;
    }

    rc = sscanf(Argv[1], "%d.%d.%d.%d", &ip[0], &ip[1], &ip[2], &ip[3]);
    if (rc != 4)
    {
        return 0;
    }

    for (i = 0; i < 4; i++)
    {
        if (ip[i] > 255 || ip[i] < 0)
        {
            return 0;
        }
    }

    serverIP = (ip[3] << 24)
             | (ip[2] << 16)
             | (ip[1] << 8)
             |  ip[0];

    return serverIP;
}

int
main (
  IN int Argc,
  IN char **Argv
  )
{
    unsigned long serverIP;
    int serverPort;

    int rc;

    if (Argc != 3)
    {
        printf("error argv \n");
        printf("TCPClient 127.0.0.1 8080\n");
        return 0;
    }

    serverIP = inet_addr(Argv[1]);
    //serverIP = getServerIP(Argc, Argv);

    serverPort = atoi(Argv[2]);

    printf("TCPClient Start \n");

    rc = testTCPSocket(serverIP, serverPort);
    printf("Send Socket (%d)\n", rc);

    return 0;
}

TCPClient.inf

## @file
#   A simple, basic, application showing how the Hello application could be
#   built using the "Standard C Libraries" from StdLib.
#
#  Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.
#
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = TCPClient
  FILE_GUID                      = 60a0b517-eb12-444d-8792-f6368622f9cb
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  TCPClient.c

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

[LibraryClasses]
  BsdSocketLib
  EfiSocketLib
  LibC
  LibNetUtil

[Protocols]

[BuildOptions]
  INTEL:*_*_*_CC_FLAGS = /Qdiag-disable:181,186
   MSFT:*_*_*_CC_FLAGS = /Od
    GCC:*_*_*_CC_FLAGS = -O0 -Wno-unused-variable

2017年8月24日 星期四

UEFI Application - trigger watchdog timer

不多說,直接看程式

xx.inf

[LibraryClasses]
    UefiLib


xx.c

#include <library/efibootservicestablelib.h>

void triggerWatchdog(int sec)
{
    gBS->SetWatchdogTimer(sec, 0x0000, 0x00, NULL);
}

int main(void)
{
    triggerWatchdog(10);
    
    while (1) ;
    
    return 0;
}

UEFI Application - reboot system

想要在 UEFI Application 重開機
其實是一件很簡單的事
只要呼叫 RuntimeServices 去 reset 即可

UEFI 總共支援 3種的 reset ( HW / BIOS 也需支援 )
1. Cold Reset
2. Warm Reset ( 有部份的 RAM 並未清空 )
3. SHUTDOWN


相關程式如下

 xx.inf
[LibraryClasses]
    UefiLib


xx.c

#include <library/efibootservicestablelib.h>
int main(void)
{
    gRT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);    
    return 0;
}

2017年8月23日 星期三

UEFI Application 小小心得

如果有人像我這麼苦命,不是做 BIOS 卻要在 UEFI Shell 開發 Application,強烈建議都用 C 形式去寫,也就是 int main(void),這樣的好處是有些 open source 的 C library 比較容易 porting 到 UEFI 下使用。

相反的,當你寫好的 code,如果反過來想要 porting 回 Windows or Linux 下,也是非常容易。

只要你在寫的時候,把 define 定義好,以總行數約 1500 的 code 來說,可能只要增加幾個 define 就可以讓同樣的 code 在 UEFI 和其他 OS 都通吃,花費的時間可能也不會超過 20 分鐘,甚至 Windows 和 UEFI 用不同的 compiler 也是無痛接軌。

不過今天早上第一次嘗試的時候,還是撞了一下牆,但是搞定後,心情只有一個字 "爽" 可以形容,連日來的不快也隨著 "爽" 而煙消雲散了。

define 示意圖

void mySleep(int retrySleepSecs)
{
#ifdef WINDOWS_PC
        Sleep(retrySleepSecs * 1000);
#else
        sleep(retrySleepSecs);
#endif // WINDOWS_PC
}

2017年8月8日 星期二

Linux strace command

之前在 tune Web Server performance 時,有從書中學到這個 command,此 command 可用來觀看 Web Server 目前瓶頸可能的原因。

底下是關於此 command 的相關文章,非常值得一讀。

https://blogs.oracle.com/ksplice/strace-the-sysadmins-microscope?utm_campaign=CodeTengu&utm_medium=email&utm_source=CodeTengu_99