3A5000之UEFI

经过两年的开发,龙芯平台的固件已经实现了 UEFI 规范,从 3A5000 开始默认使用支持 UEFI 的固件。

简介

UEFI(统一可扩展固件接口),英文全称为 Unified Extensible Firmware Interface,是一种个人电脑系统规格,用来定义操作系统与系统固件之间的软件界面,作为 BIOS 的替代方案。可扩展固件接口负责加电自检(POST)、联系操作系统以及提供连接操作系统与硬件的接口。

UEFI 的前身是 Intel 在1998年开始开发的 Intel Boot Initiative,后来被重命名为可扩展固件接口(Extensible Firmware Interface,缩写EFI)。Intel在2005年将其交由统一可扩展固件接口论坛(Unified EFI Forum)来推广与发展,为了凸显这一点,EFI 也更名为UEFI(Unified EFI)。UEFI 论坛的创始者是11家知名电脑公司,包括 Intel、IBM 等硬件厂商,软件厂商 Microsoft,及 BIOS 厂商 AMI、Insyde 及 Phoenix。

目前主流的电脑都已经提供支持 UEFI 的固件,龙芯从 2018 年开始对edk2进行移植,并在3A4000上进行了试验性使用,从 3A5000 开始,将默认使用支持 UEFI 的固件。

GPT 分区表

GPT: GUID(Globals Unique Identifiers)Partition Table 全局唯一标识,是一个较新的分区机制,解决了MBR很多缺点。

使用128位 UUID 表示磁盘和分区 GPT 分区表自动备份在头和尾两份,并有CRC校验位

支持超过2T的磁盘(64位寻址空间),使用64位,支持128个分区,支持8Z(512Byte/block )64Z(4096Byte/block)。fdisk 最大只能建立2TB大小的分区,创建一个大于2TB的分区使用 parted,gdisk 分区工具。

特点:

  • 向后兼容MBR
  • 必须在支持 UEFI 的硬件上才能使用(Intel提出,用于取代BIOS)
  • 必须使用64位系统
  • Mac、Linux 系统都能支持 GPT 分区格式
  • Windows 7/8 64bit、Windows Server 2008 64bit 支持 GPT

要使用 UEFI,硬盘需要使用 GPT 分区表。

EFI 系统分区

UEFI规范里,在GPT分区表的基础上,规定了一个EFI系统分区(EFI System Partition,ESP),ESP要格式化成FAT32,EFI启动文件要放在 <esp分区>/EFI 目录下面。

在龙芯固件中,默认的EFI文件加载路径是<esp>/EFI/BOOT/BOOTLOONGARCH.EFI,当写入固件的启动项失败时,可以将efi文件放在这个路径,以让固件自动加载。

EFI 系统分区,通过挂载在 /boot/efi 目录中:

/dev/sda1 on /boot/efi type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=936,iocharset=cp936,shortname=mixed,utf8,errors=remount-ro)

固件启动项

启动过程中,当显示红色 LOONSON 界面时,按下F12键,会出现固件启动项:

/-----------------------------------\
|    Please select boot device:     |
|-----------------------------------|
| arch                              |
| rEFInd Boot Manager               |
| UEFI S5170-256                    |
| UEFI Shell                        |
| Enter Setup                       |
|-----------------------------------|
|     ^ and v to move selection     |
|    ENTER to select boot device    |
|            ESC to exit            |
\-----------------------------------/

选择 arch 将进入 grub,(Archlinux发行版会修改grub安装后的名称)。

选择 rEFInd Boot Managerarch 将进入refind界面。

选择 UEFI S5170-256 将加载硬盘上的默认efi文件。

选择 UEFI Shell 将进入固件交互命令行界面。

选择 Enter Setup 将进入固件设置界面。

UEFI Shell

  • help: 显示内置到 UEFI Shell 的命令列表。
Shell> help
alias         - Displays, creates, or deletes UEFI Shell aliases.
attrib        - Displays or modifies the attributes of files or directories.
bcfg          - Manages the boot and driver options that are stored in NVRAM.
cd            - Displays or changes the current directory.
cls           - Clears the console output and optionally changes the background and foreground color.
comp          - Compares the contents of two files on a byte-for-byte basis.
connect       - Binds a driver to a specific device and starts the driver.
cp            - Copies one or more files or directories to another location.
date          - Displays and sets the current date for the system.
dblk          - Displays one or more blocks from a block device.
devices       - Displays the list of devices managed by UEFI drivers.
devtree       - Displays the UEFI Driver Model compliant device tree.
dh            - Displays the device handles in the UEFI environment.
disconnect    - Disconnects one or more drivers from the specified devices.
dmem          - Displays the contents of system or device memory.
dmpstore      - Manages all UEFI variables.
drivers       - Displays the UEFI driver list.
drvcfg        - Invokes the driver configuration.
drvdiag       - Invokes the Driver Diagnostics Protocol.
echo          - Controls script file command echoing or displays a message.
edit          - Provides a full screen text editor for ASCII or UCS-2 files.
eficompress   - Compresses a file using UEFI Compression Algorithm.
efidecompress - Decompresses a file using UEFI Decompression Algorithm.
else          - Identifies the code executed when 'if' is FALSE.
endfor        - Ends a 'for' loop.
endif         - Ends the block of a script controlled by an 'if' statement.
exit          - Exits the UEFI Shell or the current script.
for           - Starts a loop based on 'for' syntax.
getmtc        - Gets the MTC from BootServices and displays it.
goto          - Moves around the point of execution in a script.
help          - Displays the UEFI Shell command list or verbose command help.
hexedit       - Provides a full screen hex editor for files, block devices, or memory.
if            - Executes commands in specified conditions.
ifconfig      - Modifies the default IP address of the UEFI IPv4 Network Stack.
load          - Loads a UEFI driver into memory.
loadpcirom    - Loads a PCI Option ROM.
ls            - Lists the contents of a directory or file information.
map           - Displays or defines file system mappings.
memmap        - Displays the memory map maintained by the UEFI environment.
mkdir         - Creates one or more new directories.
mm            - Displays or modifies MEM/MMIO/IO/PCI/PCIE address space.
mode          - Displays or changes the console output device mode.
mv            - Moves one or more files to a destination within or between file systems.
openinfo      - Displays the protocols and agents associated with a handle.
parse         - Retrieves a value from a standard format output file.
pause         - Pauses a script and waits for an operator to press a key.
pci           - Displays PCI device list or PCI function configuration space and PCIe extended
configuration space.
pcieswitch    - PCIE signal test switch.
pcietest      - PCIE signal test.
ping          - Ping the target host with an IPv4 stack.
reconnect     - Reconnects drivers to the specific device.
reset         - Resets the system.
rm            - Deletes one or more files or directories.
rtcWakeup     - Test rtc wakeup after Shutdown.
run           - Load and run the linux kernal.
satatest      - SATA signal test.
sermode       - Sets serial port attributes.
set           - Displays or modifies UEFI Shell environment variables.
setsize       - Adjusts the size of a file.
setvar        - Displays or modifies a UEFI variable.
shift         - Shifts in-script parameter positions.
smbiosview    - Displays SMBIOS information.
spi           - Read or write spi flash.
stall         - Stalls the operation for a specified number of microseconds.
time          - Displays or sets the current time for the system.
timezone      - Displays or sets time zone information.
touch         - Updates the filename timestamp with the current system date and time.
type          - Sends the contents of a file to the standard output device.
unload        - Unloads a driver image that was already loaded.
usbtest       - USB signal test.
ver           - Displays UEFI Firmware version information.
vers          - Displays Loongson UEFI build version information.
vol           - Displays or modifies information about a disk volume.

常用命令说明:

  • set: 创建、显示、更改或删除 UEFI Shell 环境变量。
Shell> set
    path = FS0:\efi\tools\;FS0:\efi\boot\;FS0:\;FS1:\efi\tools\;FS1:\efi\boot\;FS1:\;FS2:\efi\tools\
;FS2:\efi\boot\;FS2:\;FS3:\efi\tools\;FS3:\efi\boot\;FS3:\
nonesting = False
debuglasterror = 0x0
lasterror = 0x0
profiles = ;Driver1;Debug1;network1;spi;signal test;LoongsonTest;vers;
uefishellsupport = 3
uefishellversion = 2.2
uefiversion = 2.70
  • ver: 显示固件版本信息
Shell> ver
UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)
  • cd: 显示或更改当前目录。
Shell> help cd
Displays or changes the current directory.

CD [path]

  path - Specifies the relative or absolute directory path.

NOTES:
  1. This command changes the current working directory that is used by the
     UEFI Shell environment. If a file system mapping is specified, then the
     current working directory is changed for that device. Otherwise, the
     current working directory is changed for the current device.
  2. If path is not present, then the current working directory (including
     file system mapping) is displayed to standard out.
  3. The table below describes the conventions that are used to refer to the
     directory, its parent, and the root directory in the UEFI Shell
     environment.
     Convention  Description
      '.'        Refers to the current directory.
      '..'       Refers to the directory's parent.
      '\'        Refers to the root of the current file system.
  4. The current working directory is maintained in the environment
     variable %cwd%.

EXAMPLES:
  * 将当前文件系统变为映射的 fs0 文件系统:
    Shell> fs0:

  * 将当前目录变为 efi 子目录:
    fs0:\> cd efi

  * 将当前目录变为父目录 (fs0:\):
    fs0:\efi\> cd ..

  * 将当前目录变为 'fs0:\efi\Tools':
    fs0:\> cd efi\Tools

  * 将当前目录变为当前 fs 的根目录 (fs0):
    fs0:\efi\Tools\> cd \
Shell>
  • reset: 重启或关机
Shell> help reset
Resets the system.

RESET [-w [string]]
RESET [-s [string]]
RESET [-c [string]]

  -s     - 关机
  -w     - 热启动
  -c     - 冷启动
  string - 给出一个原因并重启

NOTES:
  1. This command resets the system.
  2. The default is to perform a cold reset unless the -w parameter is
     specified.
  3. If a reset string is specified, it is passed into the Reset()
     function, and the system records the reason for the system reset.
  • spi: 固件刷新
Shell> help spi
Read or write spi flash.

SPI [-r StartAddr Size]/[-w data/-w StartAddr data]/[-u file]/[-e]/[-l file StartAddr Size]

  -r           - read ls7a spi from address.
  -w           - write ls7a spi data.
  -u           - update ls3a spi flash.
  -e           - erase ls7a spi flash by Sector.
  -x           - download correct firmware to Serial ROM for xhci.
  -l           - write ls7a spi data from start address to end address

NOTES:
  1. This command can update flash you should be careful.

  * 刷新固件
    fs0:\> spi -u uefi

  * write the ls7a spi flash:
    fs0:\> spi -w 00:11:22:33:44:55
    fs0:\> spi -w 0x10 aa:bb:cc:dd:ee:ff

  * write the ls7a spi flash:
    fs0:\> spi -l vbios.bin 0x1000 0x20000
Shell> 

操作系统支持

基于 3A5000 平台的操作系统内核已经增加了对固件 Variable Runtime Services 的支持,可以在操作系统中直接操作固件提供的 Runtime 服务,如增加、删除引导项,调整启动顺序等。

要使用操作系统的这个功能,需要:

  1. 在编译内核时,打开 CONFIG_EFIVAR_FS=m 配置项,在 make menuconfig 时,显示为:
<M> EFI Variable Support via sysfs
  1. 挂载 efivarfs 文件系统

如果操作系统使用了 systemd,在启动时会自动挂载,挂载位置如下:

efivarfs on /sys/firmware/efi/efivars type efivarfs (rw,nosuid,nodev,noexec,relatime)
  1. 编译并安装 efibootmgr 软件包。

efibootmgr 命令用法如下:

$ efibootmgr -h
efibootmgr version 17
usage: efibootmgr [options]
        -a | --active         sets bootnum active
        -A | --inactive       sets bootnum inactive
        -b | --bootnum XXXX   modify BootXXXX (hex)
        -B | --delete-bootnum delete bootnum
        -c | --create         create new variable bootnum and add to bootorder
        -C | --create-only      create new variable bootnum and do not add to bootorder
        -D | --remove-dups      remove duplicate values from BootOrder
        -d | --disk disk       (defaults to /dev/sda) containing loader
        -r | --driver         Operate on Driver variables, not Boot Variables.
        -e | --edd [1|3|-1]   force EDD 1.0 or 3.0 creation variables, or guess
        -E | --device num      EDD 1.0 device number (defaults to 0x80)
        -g | --gpt            force disk with invalid PMBR to be treated as GPT
        -i | --iface name     create a netboot entry for the named interface
        -l | --loader name     (defaults to "\EFI\arch\grub.efi") [1]
        -L | --label label     Boot manager display label (defaults to "Linux")
        -m | --mirror-below-4G t|f mirror memory below 4GB
        -M | --mirror-above-4G X percentage memory to mirror above 4GB
        -n | --bootnext XXXX   set BootNext to XXXX (hex)
        -N | --delete-bootnext delete BootNext
        -o | --bootorder XXXX,YYYY,ZZZZ,...     explicitly set BootOrder (hex)
        -O | --delete-bootorder delete BootOrder
        -p | --part part        partition containing loader (defaults to 1 on partitioned devices)
        -q | --quiet            be quiet
        -t | --timeout seconds  set boot manager timeout waiting for user input.
        -T | --delete-timeout   delete Timeout.
        -u | --unicode | --UCS-2  handle extra args as UCS-2 (default is ASCII)
        -v | --verbose          print additional information
        -V | --version          return version and exit
        -w | --write-signature  write unique sig to MBR if needed
        -y | --sysprep          Operate on SysPrep variables, not Boot Variables.
        -@ | --append-binary-args file  append extra args from file (use "-" for stdin)
        -h | --help             show help/usage

注: [1] 这里显示的 “\EFI\arch\grub.efi” 是当前启动项所使用的 efi 文件位置,非原始默认位置。

  1. 查看当前启动项信息
$ efibootmgr -v
BootCurrent: 0005
Timeout: 3 seconds
BootOrder: 0005,0004,0001,0002,0003,0000
Boot0000* Enter Setup	FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(462caa21-7614-4503-836e-8ab6f4662331)
Boot0001  UEFI BootManagerMenuApp	FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(eec25bdc-67f2-4d95-b1d5-f81b2039d11d)
Boot0002* UEFI S5170-256	PciRoot(0x0)/Pci(0x8,0x0)/Sata(0,65535,0)N.....YM....R,Y.
Boot0003* UEFI Shell	FvVol(7cb8bdc9-f8eb-4f34-aaea-3ee4af6516a1)/FvFile(7c04a583-9e3e-4f1c-ad65-e05268d0b4d1)
Boot0004* rEFInd Boot Manager	HD(1,GPT,125d060a-911a-4a4f-ba44-7035cb7bad01,0x800,0xfa000)/File(\EFI\refind\refind_loongarch64.efi)
Boot0005* arch	HD(1,GPT,125d060a-911a-4a4f-ba44-7035cb7bad01,0x800,0xfa000)/File(\EFI\arch\grubloongarch64.efi)

当前启动项是0005,即arch,efi文件是 <esp>/EFI/arch/grub/loongarch64.efi

启动顺序是0005(arch),0004(rEFInd Boot Manager),0001(UEFI BootManagerMenuApp),0002(UEFI S5170-256),0003(UEFI Shell),0000(Enter Setup)。

  1. 调整启动顺序

交换arch和rEFInd Boot Manager:

# efibootmgr -o 0004,0005,0001,0002,0003,0000
  1. 添加新启动项

将文件 /boot/efi/EFI/BOOT/BOOTLA64.EFI 添加为新的启动项Linux Boot Manager

# efibootmgr -c -w -L "Linux Boot Manager" -d /dev/sda -p 1 -l \EFI\BOOT\BOOTLA64.EFI
BootCurrent: 0005
Timeout: 3 seconds
BootOrder: 0006,0005,0004,0001,0002,0003,0000
Boot0000* Enter Setup
Boot0001  UEFI BootManagerMenuApp
Boot0002* UEFI S5170-256
Boot0003* UEFI Shell
Boot0004* rEFInd Boot Manager
Boot0005* arch
Boot0006* Linux Boot Manager
  1. 删除启动项

删除刚才添加的启动项:

$ efibootmgr -b 0006 -B
BootCurrent: 0005
Timeout: 3 seconds
BootOrder: 0005,0004,0001,0002,0003,0000
Boot0000* Enter Setup
Boot0001  UEFI BootManagerMenuApp
Boot0002* UEFI S5170-256
Boot0003* UEFI Shell
Boot0004* rEFInd Boot Manager
Boot0005* arch

efibootmgr 工具还有许多功能(比如隐藏启动项、修改启动项、删除无用的启动项、超时时间等),这里不再一一介绍,有兴趣的朋友可以自行研究测试。

helloworld 应用

UEFI 支持用户自行开发efi application,如比较常见的bootloader、memtest、uefi shell等等,都是一个个UEFI Application。

开发应用可以使用UEFI提供的头文件和库,也可以使用 gnu-efi 封装的专用于 linux 工具链的开发库,我们以使用 gnu-efi 为例,编写一个能输出 “Hello, world!” 的 应用。

编写 main.c 文件,内容如下:

#include <efi.h>
#include <efilib.h>

EFI_STATUS
EFIAPI
efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) {
   InitializeLib(ImageHandle, SystemTable);
   Print(L"Hello, world!\n");

   return EFI_SUCCESS;
}

编写 Makefile 文件,内容如下:

ARCH            = $(shell uname -m | sed s,i[3456789]86,ia32,)
OBJS            = main.o
TARGET          = hello.efi
EFIINC          = /usr/include/efi
EFIINCS         = -I$(EFIINC) -I$(EFIINC)/$(ARCH) -I$(EFIINC)/protocol
LIB             = /usr/lib
EFI_CRT_OBJS    = $(LIB)/crt0-efi-$(ARCH).o
EFI_LDS         = $(LIB)/elf_$(ARCH)_efi.lds
CFLAGS          = $(EFIINCS) -fno-stack-protector -fpic \
                  -fshort-wchar -Wall
ifeq ($(ARCH),loongarch64)
    CFLAGS += -march=loongarch64 -mabi=lp64 -g -O2 \
              -Wextra -Werror -fno-strict-aliasing \
              -ffreestanding -fno-stack-check \
              $(if $(findstring gcc,$(CC)),-fno-merge-all-constants,)
endif

LDFLAGS         = -nostdlib --warn-common --no-undefined --fatal-warnings -shared \
                  --build-id=sha1 -Bsymbolic --defsym=EFI_SUBSYSTEM=0xa -T $(EFI_LDS) -L $(LIB) $(EFI_CRT_OBJS) \

all: $(TARGET)

hello.so: $(OBJS)
	ld $(LDFLAGS) $(OBJS) -o $@ -lefi -lgnuefi

%.efi: %.so
	@objcopy -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel \
            -j .rela -j .rel.* -j .rela.* -j .rel* -j .rela* \
            -j .reloc -O binary $^ $@

clean:
	@rm -f *.o *.efi *.so

编译

$ make

复制 efi 到 esp 分区:

$ cp hello.efi /boot/efi/hello.efi

重启系统,进入 UEFI Shell 进行测试:

Shell> fs0:\hello.efi
Hello, world!
Shell> 

更多测试请 clone https://github.com/loongarch64/gnu-efi 仓库,切换到 dev-la64/master 分支,编译运行。

$ git clone https://github.com/loongarch64/gnu-efi
$ git checkout dev-la64/master
$ make
$ make apps
$ ls loongarch64/apps/
AllocPages.efi  debughook.efi        drv0.efi      exit.efi       lfbgrid.efi   printenv.efi  setdbg.efi  t2.efi  t4.efi  t6.efi  t8.efi   t.efi
bltgrid.efi     debughook.efi.debug  drv0_use.efi  FreePages.efi  modelist.efi  route80h.efi  setjmp.efi  t3.efi  t5.efi  t7.efi  tcc.efi  unsetdbg.efi

生成的 efi 文件位于 loongarch64/apps 目录中,可以通过 UEFI Shell 来加载这些 efi 文件进行测试和学习编写固件 efi 应用。

startup.nsh 脚本

UEFI 固件支持自动加载脚本来执行一系列自动化任务,将编写好的 startup.nsh 脚本复制到 <esp>/efi/tools/startup.nsh,重启系统可自动执行。

下面是一个脚本示例:

cat /boot/efi/tools/startup.nsh

cls
echo "Press any key to continue..."
pause

if exist fs0:\hello.efi then
  echo "Run hello.efi..."
  fs0:\hello.efi
endif

if exist fs0:\EFI\arch\grubloongarch64.efi then
  echo "Run Grub..."
  fs0:\EFI\arch\grubloongarch64.efi
endif

脚本先进行清屏,然后显示Press any key to continue..., 并等待用户按键。

如果 /boot/efi/hello.efi 文件存在,则显示 Run hello.efi... 并加载执行 hello.efi

如果 /boot/efi/EFI/arch/grubloongarch64.efi 文件存在,则显示 Run Grub... 并加载执行,进行 Grub 界面。

版权

本作品采用 CC BY-NC-ND 4.0 授权。