After my last question, UEFI Bootloader Correct Types, I got the bootloader finally compiling. I also made a boot_info that can be passed to the kernel.
But there is one problem: The bootloader is not able to get information about the kernel image (file). It exits with uefi.Status.BufferTooSmall.
Below the relevant code (whole file).
const std = @import("std");
const uefi = std.os.uefi;
const puts = @import("./puts.zig").puts;
pub fn loadKernel(path: [*:0]const u16, image: *[]const u8) uefi.Status {
// locate file system protocol
const boot_services = uefi.system_table.boot_services.?;
var file_system_protocol: ?*uefi.protocol.SimpleFileSystem = undefined;
var status: uefi.Status = uefi.Status.Success;
puts("DEBUG: Locating file system protocol\r\n");
status = boot_services.locateProtocol(&uefi.protocol.SimpleFileSystem.guid, null, @as(*?*anyopaque, @ptrCast(&file_system_protocol)));
if (status != uefi.Status.Success) {
puts("ERROR: Cannot locate file system protocol\r\n");
return status;
}
// open file system root
puts("DEBUG: Opening file system root\r\n");
var root: *const uefi.protocol.File = undefined;
status = file_system_protocol.?.openVolume(&root);
if (status != uefi.Status.Success) {
puts("ERROR: Cannot open root volume\r\n");
return status;
}
// locate file
puts("DEBUG: Locating file\r\n");
var file: *const uefi.protocol.File = undefined;
status = root.open(&file, path, uefi.protocol.File.efi_file_mode_read, uefi.protocol.File.efi_file_read_only);
if (status != uefi.Status.Success) {
puts("ERROR: Cannot locate file\r\n");
return status;
}
// get file info
puts("DEBUG: Getting file info\r\n");
var file_info: *uefi.FileInfo = undefined;
var file_info_size: usize = @sizeOf(uefi.FileInfo);
status = file.getInfo(&uefi.FileInfo.guid, &file_info_size, @as([*]u8, @ptrCast(file_info)));
// PROBLEM
if (status != uefi.Status.Success) {
puts("ERROR: Cannot get file info\r\n");
return status;
}
var image_buffer: [*]u8 = undefined;
// allocate space
puts("DEBUG: Allocating space for file\r\n");
status = boot_services.allocatePool(uefi.tables.MemoryType.LoaderData, @as(u64, @intCast(file_info.file_size)), @as(*[*]align(8) u8, @ptrCast(&image_buffer)));
if (status != uefi.Status.Success) {
puts("ERROR: Cannot allocate RAM for file\r\n");
return status;
}
// read file
puts("DEBUG: Reading file\r\n");
status = file.read(@as(*usize, @ptrFromInt(@as(usize, @intCast(file_info.file_size)))), image_buffer);
if (status != uefi.Status.Success) {
puts("ERROR: Cannot read file\r\n");
return status;
}
// save in pointers
image.* = image_buffer[0 .. file_info.file_size];
return status;
}
How could I solve the problem (also marked with a comment)?
First, you are providing the pointer itself as the buffer, not a buffer that points to @sizeOf(uefi.FileInfo) bytes. You should be doing this instead:
var file_info: uefi.FileInfo = undefined;
// ...
status = file.getInfo(&uefi.FileInfo.guid, &file_info_size, &file_info);
However, the FileInfo struct expects trailing data that it uses to store the filename. So, you need to provide a buffer that is large enough to contain both the FileInfo struct + that trailing data. See UEFI File access APIs | Welcome to the Mike’s homepage! for a more complete explanation.
So in your case you’d actually want to do something like this (untested):
const some_reasonable_max_path_size = 260; // maybe? not sure what this should actually be
// path size * 2 because file name is encoded as UTF-16 according to `std.uefi.FileInfo.getFileName()`
const file_info_buf_len = @sizeOf(uefi.FileInfo) + (some_reasonable_max_path_size * 2);
var file_info_buf: [file_info_buf_len]u8 align(@alignOf(uefi.FileInfo)) = undefined;
// mutable because if getInfo returns BUFFER_OVERFLOW it will contain the size needed
var file_info_size: usize = file_info_buf_len;
status = file.getInfo(&uefi.FileInfo.guid, &file_info_size, &file_info_buf);
// handle status
const file_info: *uefi.FileInfo = @ptrCast(@alignCast(&file_info_buf));
// ...
There’s an additional caveat, though, because if it works like the Windows APIs that also use trailing data like this, then it returning BUFFER_OVERFLOW can be treated as success if you don’t care about the trailing data. For example, this is what’s done in std.fs.File.stat:
You’d have to test to see if the fields of FileInfo get set to the correct values after a return of BUFFER_OVERFLOW (the UEFI spec doesn’t mention it). If they do, then you can use the same strategy.
thank you a lot for your answer. Now it just works.
But as we all know: when one problem just ended, a new one pops up.
In my build.zig, I have the kernel (just REALLY simple) as b.addExecutable. Now, how can I set the binary format to raw binary instead of elf?
Or should I post that in a new topic?
I found this github issue. But I just tried ObjCopyStep right now and it throwed me an error that a target directory (in zig-cache…) is not available.
But I just think about to make an ELF bootloader, not a raw binary bootloader.
That just points me to the next issue. I have two functions for COM1 communication, inb and outb. They look as follows:
But when I try to build the kernel, it says me somehow:
src/kernel/port_io.zig:11:5: error: TODO (LLVM): unsupported output constraint on output type 'a'
pub fn outb(port: u16, val: u8) void {
~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Does that mean that the feature that I need is not done right now? Or what am I doing wrong?
(I took one github repo as example for a kernel base; and the bootloader idea was also from there, but with raw binary instead of ELF).
I just found the errors for the Port IO by myself. Now it compiles fine and the objcopy thing is done via a shell script.
But as everything in the life, nothing has even the chance to work that easy. So while reading the file, UEFI greets me with BdsDxe: failed to start Boot0001 'UEFI QEMU HARDDISK QM00004 '' from PciRoot(0x0) and Pci(0x1,0x1)/Ata(Secondary,Slave,0x0): Aborted. After that, it tries to start PXE via IPv4 and after that IPv6 and then it starts the UEFI Interactive Shell.
My current code for the file reading:
const std = @import("std");
const uefi = std.os.uefi;
const puts = @import("./puts.zig").puts;
pub fn usizeToString(value: usize) [20]u8 {
var buffer: [20]u8 = undefined;
var index: u8 = 19;
var value_working = value;
while (value_working > 0) : (index -= 1) {
const digit = @as(u8, @intCast(value_working % 10));
buffer[index] = digit + '0';
value_working /= 10;
}
return buffer;
}
pub fn loadKernel(path: [*:0]const u16, image: *[]const u8) uefi.Status {
// locate file system protocol
const boot_services = uefi.system_table.boot_services.?;
var file_system_protocol: ?*uefi.protocol.SimpleFileSystem = undefined;
var status: uefi.Status = uefi.Status.Success;
puts("DEBUG: Locating file system protocol\r\n");
status = boot_services.locateProtocol(&uefi.protocol.SimpleFileSystem.guid, null, @as(*?*anyopaque, @ptrCast(&file_system_protocol)));
if (status != uefi.Status.Success) {
puts("ERROR: Cannot locate file system protocol\r\n");
return status;
}
// open file system root
puts("DEBUG: Opening file system root\r\n");
var root: *const uefi.protocol.File = undefined;
status = file_system_protocol.?.openVolume(&root);
if (status != uefi.Status.Success) {
puts("ERROR: Cannot open root volume\r\n");
return status;
}
// locate file
puts("DEBUG: Locating file\r\n");
var file: *const uefi.protocol.File = undefined;
status = root.open(&file, path, uefi.protocol.File.efi_file_mode_read, uefi.protocol.File.efi_file_read_only);
if (status != uefi.Status.Success) {
puts("ERROR: Cannot locate file\r\n");
return status;
}
// get file info
puts("DEBUG: Getting file info\r\n");
const file_info_buf_len = @sizeOf(uefi.FileInfo) + 512;
var file_info_size: usize = file_info_buf_len;
var file_info_buf: [file_info_buf_len]u8 align(@alignOf(uefi.FileInfo)) = undefined;
status = file.getInfo(&uefi.FileInfo.guid, &file_info_size, &file_info_buf);
switch (status) {
uefi.Status.Success => {
puts("DEBUG: Getting file info worked\r\n");
},
uefi.Status.BufferTooSmall => {
puts("DEBUG: BufferTooSmall; Handling comes later\r\n");
},
else => {
puts("ERROR: Cannot get file info\r\n");
return status;
}
}
// if (status != uefi.Status.Success) {
// puts("ERROR: Cannot get file info\r\n");
// return status;
// }
const file_info: *uefi.FileInfo = @ptrCast(@alignCast(&file_info_buf));
var image_buffer: [*]u8 = undefined;
// allocate space
puts("DEBUG: Allocating space for file\r\n");
status = boot_services.allocatePool(uefi.tables.MemoryType.LoaderData, @as(u64, @intCast(file_info.file_size)), @as(*[*]align(8) u8, @ptrCast(&image_buffer)));
puts("DEBUG: Allocated ");
puts(&usizeToString(file_info.file_size));
puts(" bytes of RAM for the file\r\n");
if (status != uefi.Status.Success) {
puts("ERROR: Cannot allocate RAM for file\r\n");
return status;
}
// read file
puts("DEBUG: Reading file\r\n");
status = file.read(@as(*usize, @ptrFromInt(@as(usize, @intCast(file_info.file_size)))), image_buffer);
if (status != uefi.Status.Success) {
puts("ERROR: Cannot read file\r\n");
return status;
}
// save in pointers
image.* = image_buffer[0 .. file_info.file_size];
return status;
}
In the main function, the return code of the bootloader function (which starts the loadKernel function) is put out. But somehow, the bootloader does not even get there (it also would not get there if everything would work, but then it would display some other messages). So just while reading the file something strange happens…
What am I doing wrong?