经过两年的开发,龙芯平台的固件已经实现了 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/BOOTLOONGARCH64.EFI
,当写入固件的启动项失败时,可以将 efi 文件放在这个路径,以让固件自动加载。
注:
未提交上游的旧固件版本,默认的EFI文件加载路径是 <esp>/EFI/BOOT/BOOTLOONGARCH.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 服务,如增加、删除引导项,调整启动顺序等。
要使用操作系统的这个功能,需要:
- 在编译内核时,打开
CONFIG_EFIVAR_FS=m
配置项,在make menuconfig
时,显示为:
<M> EFI Variable Support via sysfs
- 挂载
efivarfs
文件系统
如果操作系统使用了 systemd
,在启动时会自动挂载,挂载位置如下:
efivarfs on /sys/firmware/efi/efivars type efivarfs (rw,nosuid,nodev,noexec,relatime)
- 编译并安装
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 文件位置,非原始默认位置。
- 查看当前启动项信息
$ 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)。
- 调整启动顺序
交换arch和rEFInd Boot Manager:
# efibootmgr -o 0004,0005,0001,0002,0003,0000
- 添加新启动项
将文件 /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
- 删除启动项
删除刚才添加的启动项:
$ 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 界面。