#include <windows.h>
#include <iostream>
// 功能:获取指定物理驱动器的ProductId
std::string GetStorageDeviceProductId(const std::string& drivePath) {
// 打开物理驱动器
HANDLE hDevice = CreateFileA(drivePath.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to open device: " << drivePath << std::endl;
return "";
}
STORAGE_PROPERTY_QUERY query = {};
query.PropertyId = StorageDeviceProperty;
query.QueryType = PropertyStandardQuery;
// 分配足够大的缓冲区来存储STORAGE_DEVICE_DESCRIPTOR及其附加数据
BYTE buffer[1024] = {};
DWORD bytesReturned = 0;
// 查询存储设备属性
BOOL result = DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), &buffer, sizeof(buffer), &bytesReturned, NULL);
if (!result) {
std::cerr << "Failed to query storage device properties." << std::endl;
CloseHandle(hDevice);
return "";
}
// 获取STORAGE_DEVICE_DESCRIPTOR结构
STORAGE_DEVICE_DESCRIPTOR* deviceDescriptor = reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(buffer);
// ProductId是一个以NULL结尾的字符串,位于STORAGE_DEVICE_DESCRIPTOR之后
// 确保ProductIdOffset不为0
std::string productId = "";
if (deviceDescriptor->ProductIdOffset != 0) {
productId = reinterpret_cast<const char*>(buffer + deviceDescriptor->ProductIdOffset);
}
CloseHandle(hDevice);
return productId;
}
int main() {
// 示例:获取PhysicalDrive0的ProductId
std::string productId = GetStorageDeviceProductId("\\\\.\\PhysicalDrive0");
std::cout << "ProductId: " << productId << std::endl;
return 0;
}
分类: Funny
检查 PhysicalDrive 类型
具体的类型定义在【参考1】:
typedef enum _STORAGE_BUS_TYPE {
BusTypeUnknown = 0x00,
BusTypeScsi,
BusTypeAtapi,
BusTypeAta,
BusType1394,
BusTypeSsa,
BusTypeFibre,
BusTypeUsb,
BusTypeRAID,
BusTypeiScsi,
BusTypeSas,
BusTypeSata,
BusTypeSd,
BusTypeMmc,
BusTypeVirtual,
BusTypeFileBackedVirtual,
BusTypeSpaces,
BusTypeNvme,
BusTypeSCM,
BusTypeUfs,
BusTypeNvmeof,
BusTypeMax,
BusTypeMaxReserved = 0x7F
} STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;
示例代码:
#include <windows.h>
#include <iostream>
#include <winioctl.h>
void QueryDriveInterfaceType(int driveNumber) {
HANDLE hDrive;
char drivePath[256];
sprintf_s(drivePath, "\\\\.\\PhysicalDrive%d", driveNumber);
// 打开物理驱动器
hDrive = CreateFileA(drivePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDrive == INVALID_HANDLE_VALUE) {
std::cerr << "Unable to open " << drivePath << std::endl;
return;
}
// 准备查询
STORAGE_PROPERTY_QUERY query;
memset(&query, 0, sizeof(query));
query.PropertyId = StorageDeviceProperty;
query.QueryType = PropertyStandardQuery;
// 接收数据的缓冲区
BYTE buffer[1024];
DWORD bytesRead;
// 查询设备属性
BOOL result = DeviceIoControl(hDrive, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query),
&buffer, sizeof(buffer), &bytesRead, NULL);
if (result) {
STORAGE_DEVICE_DESCRIPTOR* deviceDescriptor = (STORAGE_DEVICE_DESCRIPTOR*)buffer;
switch (deviceDescriptor->BusType) {
case BusTypeScsi:
std::cout << drivePath << " is SCSI" << std::endl;
break;
case BusTypeAtapi:
std::cout << drivePath << " is ATAPI" << std::endl;
break;
case BusTypeAta:
std::cout << drivePath << " is ATA" << std::endl;
break;
case BusTypeSata:
std::cout << drivePath << " is SATA" << std::endl;
break;
// 添加其他需要的接口类型
default:
std::cout << drivePath << " has an unknown interface type: " << (int)deviceDescriptor->BusType << std::endl;
break;
}
} else {
std::cerr << "Failed to query storage properties for " << drivePath << std::endl;
}
CloseHandle(hDrive);
}
int main() {
// 示例:查询PhysicalDrive0的接口类型
QueryDriveInterfaceType(0);
return 0;
}
参考:
1.https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ne-winioctl-storage_bus_type
Step to UEFI (295)手工给 EFI 文件插入代码的试验 (下)
我们继续之前的话题:在一个已经编译成功的 SimpleTest.EFI 中,加入另外一个 Hello.EFI 程序,最终实现Shell 下输入 SimpleTest.EFI, 实际上运行了SimpleTest.EFI 和 Hello.EFI。
前面的代码我们已经实现了大部分,还有一些细节需要处理。
第一个需要注意的是,我们Hello.EFI 是 NASM 生成的,其中有很多对于寄存器和堆栈的操作,这个操作会破坏SimpleTest.efi 需要的运行环境,导致无法跳入后执行出错。
仔细观察 SimpleTest.EFI 反编译结果,在开始处从 Shell 下收到的参数需是放在 rbx 和 rsi 寄存器中的,我们必须妥善保存这两个寄存器才能保证后续的正确运行。
代码头部修改如下:
_start:
push rdx
push rcx
push rdi
push rbx
push rsi
push rax ;ConOut requires a push here. I don't know why
; reserve space for 4 arguments
sub rsp, 4 * 8
代码尾部修改如下:
add rsp, 4 * 8
pop rax
pop rsi
pop rbx
pop rdi
pop rcx
pop rdx
times 20 nop
再次生成一个新的 hello.efi(注意,这样修改之后的代码无法像之前的 EFI Application一样运行了), 用HXD 打开后拷贝代码区放置到 SimpleText.EFI 中。
此外,还有两个位置需要修改:
1.在拷贝到到 SimpleTest.EFI 的带末尾放上 mov [rsp+0x08],rbx/mov [rsp+x010],rsi 两个操作;
2.跳转回文件头部的指令:
经过这样的改造,在模拟器中测试可以看到:
执行 SimpleTest.EFI 得到了2个输出,这个说明确实运行了2个EFI 。
修改后的 Hello.ASM 相关程序:
修改后的 SimpleTestM.EFI 文件
本文特别感谢Windows 专家天杀提供帮助。他对于 WinPE 结构的非常了解,帮助解决了修改EFI后, 使用模拟器测试崩溃的问题(Section Header 中 .TEXT 的大小需要更新)。
枚举系统中全部 PhysicalDrive
#include <windows.h>
#include <iostream>
void EnumeratePhysicalDrives() {
HANDLE hDrive;
DWORD bytesReturned;
char driveName[24];
STORAGE_DEVICE_NUMBER deviceNumber;
// 尝试打开每个可能的物理驱动器
for (int i = 0; i < 16; i++) {
// 构造物理驱动器的名称
sprintf_s(driveName, "\\\\.\\PhysicalDrive%d", i);
// 尝试打开物理驱动器
hDrive = CreateFileA(driveName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDrive == INVALID_HANDLE_VALUE) {
// 如果无法打开驱动器,可能是因为驱动器不存在
continue;
}
// 尝试获取设备编号
if (!DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &deviceNumber, sizeof(deviceNumber), &bytesReturned, NULL)) {
std::cout << "Failed to get device number for " << driveName << std::endl;
}
else {
std::cout << "Found Physical Drive: " << driveName << ", Device Type: " << deviceNumber.DeviceType << ", Device Number: " << deviceNumber.DeviceNumber << std::endl;
}
CloseHandle(hDrive);
}
}
int main() {
EnumeratePhysicalDrives();
return 0;
}
Step to UEFI (294)手工给 EFI 文件插入代码的试验(上)
编译好的 EFI 文件本质上是一个 WinPE 文件,因此我们有机会在文件开始处加入一些我们需要的代码。这次介绍的就是一个手工在 EFI 入口处插入另外一个EFI 代码的试验。
这次进行一个特别的实验。基本的原理是:
- 编写一个在屏幕上输出字符串简单的程序,这样我们能得到一段EFI Shell下对屏幕输出字符串的机器码;
- 编写一个宿主程序,这个程序编译后的 EFI 文件使用 4K 对齐,这样话,存放代码的.text段会有足够的空间能够存放下步骤1生成的机器码;
- 修改生成的EFI文件的 Section Headers 中给出的.text 大小,保证足够放下我们增加的机器码;
- 将步骤1生成的代码,插入在步骤3生成的EFI中。
最终,我们得到一个新的 EFI 程序,运行之后它会先执行步骤1 的代码,然后再执行步骤2的代码。
步骤1:这里使用 NASM 汇编语言来完成。
根据之前的文章【参考1】,编写一个程序实现在屏幕上输出字符串的代码。代码有部分修改,主要是将输出的字符串和代码放在了一起:
bits 64
; contains the code that will run
section .text
; allows the linker to see this symbol
global _start
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001729
struc EFI_TABLE_HEADER
.Signature RESQ 1
.Revision RESD 1
.HeaderSize RESD 1
.CRC32 RESD 1
.Reserved RESD 1
endstruc
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G8.1001773
struc EFI_SYSTEM_TABLE
.Hdr RESB EFI_TABLE_HEADER_size
.FirmwareVendor RESQ 1
.FirmwareRevision RESD 1
.ConsoleInHandle RESQ 1
.ConIn RESQ 1
.ConsoleOutHandle RESQ 1
.ConOut RESQ 1
.StandardErrorHandle RESQ 1
.StdErr RESQ 1
.RuntimeServices RESQ 1
.BootServices RESQ 1
.NumberOfTableEntries RESQ 1
.ConfigurationTable RESQ 1
endstruc
; see http://www.uefi.org/sites/default/files/resources/UEFI Spec 2_7_A Sept 6.pdf#G16.1016807
struc EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
.Reset RESQ 1
.OutputString RESQ 1
.TestString RESQ 1
.QueryMode RESQ 1
.SetMode RESQ 1
.SetAttribute RESQ 1
.ClearScreen RESQ 1
.SetCursorPosition RESQ 1
.EnableCursor RESQ 1
.Mode RESQ 1
endstruc
_start:
push rax ;ConOut requires a push here. I don't know why
; reserve space for 4 arguments
sub rsp, 4 * 8
; rdx points to the EFI_SYSTEM_TABLE structure
; which is the 2nd argument passed to us by the UEFI firmware
; adding 64 causes rcx to point to EFI_SYSTEM_TABLE.ConOut
mov rcx, [rdx + 64]
; load the address of our string into rdx
lea rdx, [rel strHello]
; EFI_SYSTEM_TABLE.ConOut points to EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
; call OutputString on the value in rdx
call [rcx + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.OutputString]
add rsp, 4 * 8
pop rax
ret
strHello db __utf16__ `Hello World from LAB-Z.COM!\n\r\0`
codesize equ $ - $$
; contains nothing - but it is required by UEFI
section .reloc
编译命令如下:
c:\nasm\nasm -f win64 hello.asm -l hello.lst
link /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /DLL /ENTRY:_start /SUBSYSTEM:EFI_APPLICATION /SAFESEH:NO /DRIVER Hello.obj
除了正常生成的 EFI 文件之外,还生成了 lst 文件,在其中能够看到代码生成的机器码用于对照参考:
在模拟器中测试可以正常执行。
根据【参考2】,具体对应如下:
.text:(代码段),可读、可执行
.data:(数据段),存放全局变量、全局常量等
.idata:(数据段),导入函数的代码段,存放外部函数地址。(当然还有 edata ,导出函数代码段,但不常用)
.rdata:(数据段),资源数据段,程序用到什么资源数据都在这里(包括自己打包的,还有开发工具打包的)
使用 CFF Explorer查看,我们需要的机器码就在.text段中:
步骤2:我们根据【参考3】,编写一个简单的代码,功能上只是向屏幕输出字符串,然后使用 EDK2 进行开发。因为默认情况下,.text 空余空调很小,所以在编译完成后我们再打开 下面这个 makefile文件
edk2\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\SimpleTest\SimpleTest\Makefile
将下面的一行中修改为 /ALIGN:0x1000
DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4281 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
然后进入对应目录,输入 nmake 重新编译,这样就得到一个段以4K 对齐的 EFI 文件。
查看 .text段,这里有足够的空间插入我们的代码:
步骤3,修改Section Headers 中的.text Size,这里我们修改为 1600.
步骤4,我们手工插入。
1.SimpleTestM.EFI的入口地址在 0x400处:
开始处对应的是 _ModuleEntryPoint() 函数,我们程序代码是从ShellAppMain() 函数开始的。
打开hello.efi ,拷贝这一段:
插入(Paste Write)到 SimpleTest.efi 中:
然后手工修改SimpleTest.efi 如下:
入口处修改为跳转指令, 这里使用的是一个相对跳转指令【参考4】
运行之后结果如下:
就是说我们通过 SimpleTestM 运行了hello.efi 中的内容。
当然这里和我们的预期还有差别
当然,这样的代码并不是我们期待的最终结果,我们期望两个程序能够同时运行。
这次只做到了在一个A.EFI中插入另外一个B.EFI 文件,然后运行A.EFI 实际执行B.EFI的代码。
参考:
- https://www.lab-z.com/stu207/
- https://blog.csdn.net/Simon798/article/details/96876910
- https://www.lab-z.com/stu260/
- https://www.felixcloutier.com/x86/jmp
给 EXE 加入 Resource
最近忽然想起来一个问题:如何给一个做好的 EXE 加入其他的内容?比如,我编写一个 EXE 需要更改内容而又不想重新 Build 代码。
经过研究,可以通过给EXE 添加 Resource 的方法来实现这一目标。在 https://github.com/tc-hib/go-winres 这里有一个从命令行给 EXE 添加Resource 的项目。配合这个项目可以实现前述目标。
首先,编写一个测试代码,使用 VC 编写在 VS2019 下编译通过:
#include <windows.h>
#include <iostream>
// 回调函数用于枚举资源
BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam) {
// 每找到一个资源,就增加计数
(*(int*)lParam)++;
return TRUE; // 继续枚举
}
int main()
{
HMODULE hModule = GetModuleHandle(NULL); // 获取当前模块句柄
int resourceCount = 0; // 用于计数的变量
// 枚举所有RT_RCDATA类型的资源
EnumResourceNames(hModule, RT_RCDATA, EnumResNameProc, (LONG_PTR)&resourceCount);
std::cout << "Number of RT_RCDATA resources: " << resourceCount << std::endl;
return 0;
}
代码非常简单,单纯的输出当前EXE RT_RCDATA类型的 Resource 数量。
接下来将go-winres.exe放在同一个目录下,然后运行下面的命令
go-winres.exe init
对应的会生成 winres 目录,其中有下面三个文件
前面2个是可以作为EXE 的图标的,winres.json是配置文件。例如,我们对这个目录放置一个 png 文件,然后修改如下,增加 RT_RCDATA的部分:
"RT_GROUP_ICON": {
"APP": {
"0000": [
"icon.png",
"icon16.png"
]
}
},
"RT_RCDATA": {
"OTHER": {
"0000": "2.png"
}
},
"RT_MANIFEST": {
"#1": {
"0409": {
"identity": {
"name": "",
"version": ""
运行如下命令:
go-winres.exe patch ResourceTest.exe
工具会自动给 ResourceTest.exe 添加内容,之后再次运行:
如果使用 CFF 工具还可以看到多了一个 Resource。这样,你可以在代码中先判断Resource数量,然后再进行动作。
官方提供的版本(和 Github上的相同,0.3.3版本)
VC 编写的WAVE测试文件生成器
最近为了调试,写了一个 WAVE 的生成器,能够生成指定采样率,指定格式的 WAVE文件,这样可以在输出端直接查看输出是否是指定的数据。特别注意的是,WAVE 中,使用 int16 (有符号十六进制)来表示当前的信号,更具体来说,负数使用补码来表示。代码如下:
// WaveGenerator.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#pragma pack(push, 1)
struct Chunk1 { // 'R', 'I', 'F', 'F'
char chunk_id[4];
uint32_t chunk_size;
char format[4]; // 'W', 'A', 'V', 'E'
};
struct Chunk2 {
char chunk_id[4];
uint32_t chunk_size;
int16_t wFormatTag;
int16_t nChannels;
int32_t nSamplesPerSec;
int32_t nAvgBytesPerSec;
int16_t nBlockAlign; // => num_channels * bits_per_sample / 8
int16_t wBitsPerSample;
};
struct Chunk3 {
char chunk_id[4];
uint32_t chunk_size;
};
#pragma pack(pop) // 恢复到之前的对齐设置
#define AUDIODURATION 5 // 音频时长,以秒为单位
#define SAMPLEBITS 16 // 采样位
#define SAMPLERATE 96000 // 采样率
#define CHANNELNUM 2 // Channel 数量
int main()
{
Chunk1 chunk1;
chunk1.chunk_id[0] = 'R'; chunk1.chunk_id[1] = 'I'; chunk1.chunk_id[2] = 'F'; chunk1.chunk_id[3] = 'F';
// 数据长度= Chunk123 总长 + RAW 数据长度(双声道,SAMPLEBITS Bits)
chunk1.chunk_size = sizeof(Chunk1)-8 + sizeof(Chunk2) + sizeof(Chunk3)+ AUDIODURATION* CHANNELNUM *(SAMPLEBITS/8)* SAMPLERATE;
chunk1.format[0] = 'W'; chunk1.format[1] = 'A'; chunk1.format[2] = 'V'; chunk1.format[3] = 'E';
Chunk2 chunk2;
chunk2.chunk_id[0] = 'f'; chunk2.chunk_id[1] = 'm'; chunk2.chunk_id[2] = 't'; chunk2.chunk_id[3] = ' ';
chunk2.chunk_size = sizeof(Chunk2)-8;
chunk2.wFormatTag = 0x0001; //PCM 格式
chunk2.nChannels = CHANNELNUM;
chunk2.nSamplesPerSec = SAMPLERATE;
chunk2.nAvgBytesPerSec = CHANNELNUM * SAMPLERATE * (SAMPLEBITS / 8);
chunk2.nBlockAlign = CHANNELNUM * (SAMPLEBITS / 8);
chunk2.wBitsPerSample = SAMPLEBITS;
Chunk3 chunk3;
chunk3.chunk_id[0] = 'd'; chunk3.chunk_id[1] = 'a'; chunk3.chunk_id[2] = 't'; chunk3.chunk_id[3] = 'a';
chunk3.chunk_size = AUDIODURATION * CHANNELNUM * SAMPLERATE * (SAMPLEBITS / 8);
// 打开文件用于写入,以二进制模式
FILE* file = fopen("C:\\WaveGenerator\\Debug\\example.wav", "wb");
fwrite(&chunk1, sizeof(chunk1), 1, file);
fwrite(&chunk2, sizeof(chunk2), 1, file);
fwrite(&chunk3, sizeof(chunk3), 1, file);
short int s=0,e=0;
for (int i = 0; i < AUDIODURATION* SAMPLERATE; i++) {
//s = sin(i * 2 * 3.1415 / (SAMPLERATE/1000)) * (65535 / 2);
// 左声道
s++;
fwrite(&s, sizeof(s), 1, file);
// 右声道
e = 0;
fwrite(&e, sizeof(e), 1, file);
}
fclose(file);
//getchar();
}
对于 FPF/FIT/BootGuard 的介绍
小米2代体脂秤维修
最近给一个朋友维修了一下小米2代体脂秤。他遇到的问题是:插入电池后电池发热。拿到手后,看到了现象:装入电池后,电池组和线都会发烫。
使用万用表进行测量,直接测量电池盒两端电阻为 170欧姆,装入电池后测量发现输出电流达到3A左右,这就是发热原因。
根据部分资料【参考1】,上图中红色框芯片为CST34M96,用于蓝牙通讯;绿色框中芯片为CS1256,这是用于获得体脂数据并且处理的单片机;紫色方框中是稳压(降压)芯片,型号未知丝印D3E1,照片是拆除这个芯片后拍摄。
不上电测量正常,上电后有问题猜测是电源芯片出了问题。于是拆除了稳压芯片。这部分电路如下:
VDD 是电池盒输入;经过芯片降压输出为 AVCC,最终提供给后端使用。万用表测试表明AGND 和 GND 是连通的。
拆除芯片后,从 AVCC 送入 3.3V,数码管会有动作,证明后面的芯片都是能够正常工作的,所以选择一个稳压芯片替换。
最终,替换稳压/降压芯片后,外加更换C4电容(测量发现C4短路了,可能被击穿了?)后,可以正常工作。
参考:
1. https://www.mydigit.cn/forum.php?mod=viewthread&tid=286533&page=1
SlimBootLoader 在 LattePanda Mu 上启动 Ubuntu
这次带来是全网唯一的开源 X86 BIOS 方案,通过它能够在 Latte Panda Mu上引导启动 Ubuntu。
为了在Latte Panda Mu使用SlimBootLoader,还需要额外准备如下硬件:
- 烧写IFWI的硬件工具,比如:SF100烧录器;
- 查看串口Log 的工具,比如:CH343 串口转 USB工具,用于配置安装Ubuntu;SBL 的Shell 没有提供字符或者图形界面,所有的数据都是通过串口进行交互的,所以需要准备串口工具;
- 准备2个U盘,一个用于制作 Ubuntu安装盘,我这边使用64GB 的金邦U盘,使用DiskGuinus格式化为 FAT32(注意不要使用 exFat)。将 Ubuntu 解压后全部放入 U盘根目录即可;另外U盘一个用于制作引导脚本。
第一步,生成能够启动的 Latte Panda Mu 的 IFWI。根据【参考1】,配置编译环境,首先确保能够编译通过 QEMU。
a.编译 BIOS 代码:
Python BuildLoader.py build adln
b.生成IFWI,这里没有使用 mFIT 而是直接使用代码替换原代码中的 BIOS Region。
python Platform/AlderlakeBoardPkg/Script/StitchLoader.py -i LattePanda.bin -s Outputs/adln/SlimBootloader.bin -o LattePanda_ifwi.bin -p 0xAA000007
上述的命令中,LattePanda.bin 是DFRobot 提供的原始的IFWI,其中的BIOS是AMI CodeBase;SlimBootloader.bin 是前面从 SBL Build出来的BIOS;最后的LattePanda_ifwi.bin就是生成的新的 IFWI。
d.关机状态下,将USB 转串口工具连接到LattePanda Mu 的PCH UART0 上
c.使用 SF100或者其他烧录器将LattePanda_ifwi.bin烧录到 LattePanda Mu上
d.启动LattePanda Mu后,会很快完成启动显示SBL Logo。 这时候 SBL 并不会从屏幕和键盘进行交互,而是通过串口。在另外一台电脑上打开串口工具即可看到一个文本的Shell界面,输入 boot 命令,然后调整从USB 设备启动例如:
至此,Firmware 方面的准备已经完成。
第二步,安装 Ubuntu 以及设定【参考2】。
- 其中需要一些时间,请耐心等待,之后进入下面这个界面。左边是直接在U盘提供的Live Linux 中工作,右边是进行安装,这里需要选择左侧按钮“Try Ubuntu”
从左上角的 Activities 打开一个 Terminal
e.使用 lsblk列出当前的设备,LattePanda Mu 内置了 eMMC,这里是mmcblk0设备
c.使用 sudo disk /dev/mmcblk0擦除硬盘,命令如下:
sudo gdisk /dev/mmcblk0
x
z
y
y
d. 创建GPT分区
sudo gdisk /dev/mmcblk0
o
y
w
y
e.接下来即可进行Ubuntu安装了
f.分区需要特别处理,划分出一个500MB 的FAT32分区用于引导
g.剩余容量减去4GB 左右,设定为 Ext4 分区
h.最后创建一个 4GB 的分区作为 SWAP 分区
i.接下来都选择 Continue
j.安装结束后,选择 Continue Testing
k.接下来使用 lsblk
l.输入如下命令:
Mkdir root
Sudo mount /dev/mmcblk0 root/
m.插入另外的U盘其中放入三个脚本以及内容如下的 cmdline.txt
echo "root=/dev/ mmcblk0 ro quiet splash" > cmdline.txt
n.运行如下批处理(其中的PEM文件来自编译SBL的代码中 )。
python3 GenContainer.py create -cl CMDL:cmdline.txt KRNL:./root/boot/vmlinuz-<kernel-version> INRD:./root/boot/initrd.img-<kernel-version> -k ./SblKeys/OS1_TestKey_Priv_RSA3072.pem -a RSA3072_PKCS1_SHA2_384 -t CLASSIC -o sbl_os
sudo cp sbl_os ./root/boot
sudo umount ./root
这里提供一个下载方便直接测试:
o.最终,拔掉U盘,即可启动
上述操作的完整视频:
这里只是简单的第一步,自行编译BIOS并且启动了X86的 Ubuntu,很多功能还没有调试,可能在使用中遇到问题(比如, X4 PCIE 支持, Audio 的支持)。如果你对性能和体积有一些要求,或者是对于BIOS定制有一些特别要求,不妨考虑 DFRobot推出的 LattePanda MU,它由一块核心板以及底板构成,厂家提供了电路图以及对应接口,可以很方便的实现定制。
本文提到的代码可以在 https://gitee.com/willok/slimbootloader 下载到,由天杀提供。
参考: