birth/src/common.zig
2023-07-10 17:44:11 -06:00

504 lines
13 KiB
Zig

const compiler_builtin = @import("builtin");
pub const cpu = compiler_builtin.cpu;
pub const os = compiler_builtin.os.tag;
pub const build_mode = compiler_builtin.mode;
pub const is_test = compiler_builtin.is_test;
pub const kb = 1024;
pub const mb = kb * 1024;
pub const gb = mb * 1024;
pub const tb = gb * 1024;
pub const SizeUnit = enum(u64) {
byte = 1,
kilobyte = 1024,
megabyte = 1024 * 1024,
gigabyte = 1024 * 1024 * 1024,
terabyte = 1024 * 1024 * 1024 * 1024,
};
pub const std = @import("std");
pub const Target = std.Target;
pub const Cpu = Target.Cpu;
pub const CrossTarget = std.zig.CrossTarget;
pub const log = std.log;
pub const Atomic = std.atomic.Atomic;
pub const Reader = std.io.Reader;
pub const Writer = std.io.Writer;
pub const FixedBufferStream = std.io.FixedBufferStream;
pub const fixedBufferStream = std.io.fixedBufferStream;
pub fn assert(ok: bool) void {
if (!ok) {
if (@inComptime()) {
@compileError("Assert failed!");
} else {
@panic("Assert failed!");
}
}
}
pub const deflate = std.compress.deflate;
const debug = std.debug;
pub const print = debug.print;
pub const StackIterator = debug.StackIterator;
pub const dwarf = std.dwarf;
pub const ModuleDebugInfo = std.debug.ModuleDebugInfo;
pub const elf = std.elf;
const fmt = std.fmt;
pub const format = std.fmt.format;
pub const FormatOptions = fmt.FormatOptions;
pub const bufPrint = fmt.bufPrint;
pub const allocPrint = fmt.allocPrint;
pub const comptimePrint = fmt.comptimePrint;
pub const parseUnsigned = fmt.parseUnsigned;
const heap = std.heap;
pub const FixedBufferAllocator = heap.FixedBufferAllocator;
pub const json = std.json;
const mem = std.mem;
pub const ZigAllocator = mem.Allocator;
pub const equal = mem.eql;
pub const length = mem.len;
pub const startsWith = mem.startsWith;
pub const endsWith = mem.endsWith;
pub const indexOf = mem.indexOf;
// Ideal for small inputs
pub const indexOfPosLinear = mem.indexOfPosLinear;
pub const lastIndexOf = mem.lastIndexOf;
pub const asBytes = mem.asBytes;
pub const readIntBig = mem.readIntBig;
pub const readIntSliceBig = mem.readIntSliceBig;
pub const concat = mem.concat;
pub const sliceAsBytes = mem.sliceAsBytes;
pub const bytesAsSlice = mem.bytesAsSlice;
pub const alignForward = mem.alignForward;
pub const alignBackward = mem.alignBackward;
pub const isAligned = mem.isAligned;
pub const isAlignedGeneric = mem.isAlignedGeneric;
pub const reverse = mem.reverse;
pub const tokenize = mem.tokenize;
pub const containsAtLeast = mem.containsAtLeast;
pub const sliceTo = mem.sliceTo;
pub const swap = mem.swap;
pub const random = std.rand;
pub const testing = std.testing;
pub const sort = std.sort;
pub fn fieldSize(comptime T: type, field_name: []const u8) comptime_int {
var foo: T = undefined;
return @sizeOf(@TypeOf(@field(foo, field_name)));
}
const DiffError = error{
diff,
};
pub fn diff(file1: []const u8, file2: []const u8) !void {
assert(file1.len == file2.len);
var different_bytes: u64 = 0;
for (file1, 0..) |byte1, index| {
const byte2 = file2[index];
const is_different_byte = byte1 != byte2;
different_bytes += @intFromBool(is_different_byte);
if (is_different_byte) {
log.debug("Byte [0x{x}] is different: 0x{x} != 0x{x}", .{ index, byte1, byte2 });
}
}
if (different_bytes != 0) {
log.debug("Total different bytes: 0x{x}", .{different_bytes});
return DiffError.diff;
}
}
pub fn zeroes(comptime T: type) T {
var result: T = undefined;
const slice = asBytes(&result);
@memset(slice, 0);
return result;
}
const ascii = std.ascii;
pub const upperString = ascii.upperString;
pub const isUpper = ascii.isUpper;
pub const isAlphabetic = ascii.isAlphabetic;
const std_builtin = std.builtin;
pub const AtomicRmwOp = std_builtin.AtomicRmwOp;
pub const AtomicOrder = std_builtin.AtomicOrder;
pub const Type = std_builtin.Type;
pub const StackTrace = std_builtin.StackTrace;
pub const SourceLocation = std_builtin.SourceLocation;
pub fn FieldType(comptime T: type, comptime name: []const u8) type {
return @TypeOf(@field(@as(T, undefined), name));
}
// META PROGRAMMING
pub const AutoEnumArray = std.enums.EnumArray;
pub const fields = std.meta.fields;
pub const IntType = std.meta.Int;
pub const enumFromInt = std.meta.enumFromInt;
pub const stringToEnum = std.meta.stringToEnum;
pub const Tag = std.meta.Tag;
const math = std.math;
pub const maxInt = math.maxInt;
pub const min = math.min;
pub const divCeil = math.divCeil;
pub const clamp = math.clamp;
pub const isPowerOfTwo = math.isPowerOfTwo;
pub const mul = math.mul;
pub const cast = math.cast;
pub const unicode = std.unicode;
pub const uefi = std.os.uefi;
pub const DiskType = enum(u32) {
virtio = 0,
nvme = 1,
ahci = 2,
ide = 3,
memory = 4,
bios = 5,
pub const count = enumCount(@This());
};
pub const FilesystemType = enum(u32) {
birth = 0,
ext2 = 1,
fat32 = 2,
pub const count = enumCount(@This());
};
pub fn enumFields(comptime E: type) []const Type.EnumField {
return @typeInfo(E).Enum.fields;
}
pub const enumValues = std.enums.values;
pub fn enumCount(comptime E: type) usize {
return enumFields(E).len;
}
pub const PartitionTableType = enum {
mbr,
gpt,
};
pub const supported_architectures = [_]Cpu.Arch{
.x86_64,
//.aarch64,
//.riscv64,
};
pub fn architectureIndex(comptime arch: Cpu.Arch) comptime_int {
inline for (supported_architectures, 0..) |architecture, index| {
if (arch == architecture) return index;
}
@compileError("Architecture not found");
}
pub const architecture_bootloader_map = blk: {
var array: [supported_architectures.len][]const ArchitectureBootloader = undefined;
array[architectureIndex(.x86_64)] = &.{
.{
.id = .birth,
.protocols = &.{ .bios, .uefi },
},
.{
.id = .limine,
.protocols = &.{ .bios, .uefi },
},
};
// array[architectureIndex(.aarch64)] = &.{
// .{
// .id = .birth,
// .protocols = &.{.uefi},
// },
// .{
// .id = .limine,
// .protocols = &.{.uefi},
// },
// };
// array[architectureIndex(.riscv64)] = &.{
// .{
// .id = .birth,
// .protocols = &.{.uefi},
// },
// };
break :blk array;
};
pub const Bootloader = enum(u32) {
birth,
limine,
pub const Protocol = enum(u32) {
bios,
uefi,
};
};
pub const ArchitectureBootloader = struct {
id: Bootloader,
protocols: []const Bootloader.Protocol,
};
pub const TraditionalExecutionMode = enum(u1) {
privileged = 0,
user = 1,
};
pub const ExecutionEnvironment = enum {
qemu,
};
pub const ImageConfig = struct {
sector_count: u64,
sector_size: u16,
partition_table: PartitionTableType,
partition: PartitionConfig,
pub const default_path = "config/image_config.json";
pub fn get(allocator: ZigAllocator, path: []const u8) !ImageConfig {
const image_config_file = try std.fs.cwd().readFileAlloc(allocator, path, maxInt(usize));
const parsed_image_configuration = try std.json.parseFromSlice(ImageConfig, allocator, image_config_file, .{});
return parsed_image_configuration.value;
}
};
pub const PartitionConfig = struct {
name: []const u8,
filesystem: FilesystemType,
first_lba: u64,
};
pub const QEMU = extern struct {
pub const isa_debug_exit = ISADebugExit{};
pub const ISADebugExit = extern struct {
io_base: u8 = 0xf4,
io_size: u8 = @sizeOf(u32),
};
pub const ExitCode = enum(u32) {
success = 0x10,
failure = 0x11,
_,
};
};
pub const OptimizeMode = std.builtin.OptimizeMode;
pub const Configuration = struct {
architecture: Cpu.Arch,
bootloader: Bootloader,
boot_protocol: Bootloader.Protocol,
execution_environment: ExecutionEnvironment,
optimize_mode: OptimizeMode,
execution_type: ExecutionType,
executable_kind: std.Build.CompileStep.Kind,
};
pub const QEMUOptions = packed struct {
is_test: bool,
is_debug: bool,
};
pub const ExecutionType = enum {
emulated,
accelerated,
};
pub const Suffix = enum {
bootloader,
cpu_driver,
image,
complete,
pub fn fromConfiguration(suffix: Suffix, allocator: ZigAllocator, configuration: Configuration, prefix: ?[]const u8) ![]const u8 {
const cpu_driver_suffix = [_][]const u8{
@tagName(configuration.optimize_mode),
"_",
@tagName(configuration.architecture),
"_",
@tagName(configuration.executable_kind),
};
const bootloader_suffix = [_][]const u8{
@tagName(configuration.architecture),
"_",
@tagName(configuration.bootloader),
"_",
@tagName(configuration.boot_protocol),
};
const image_suffix = [_][]const u8{
@tagName(configuration.optimize_mode),
"_",
} ++ bootloader_suffix ++ [_][]const u8{
"_",
@tagName(configuration.executable_kind),
};
const complete_suffix = image_suffix ++ [_][]const u8{
"_",
@tagName(configuration.execution_type),
"_",
@tagName(configuration.execution_environment),
};
return try std.mem.concat(allocator, u8, &switch (suffix) {
.cpu_driver => if (prefix) |pf| [1][]const u8{pf} ++ cpu_driver_suffix else cpu_driver_suffix,
.bootloader => if (prefix) |pf| [1][]const u8{pf} ++ bootloader_suffix else bootloader_suffix,
.image => if (prefix) |pf| [1][]const u8{pf} ++ image_suffix else image_suffix,
.complete => if (prefix) |pf| [1][]const u8{pf} ++ complete_suffix else complete_suffix,
});
}
};
pub const Module = struct {
program: UserProgram,
name: []const u8,
};
pub const UserProgram = struct {
kind: Kind,
dependencies: []const Dependency,
pub const Kind = enum {
zig_exe,
};
pub const Dependency = struct {
foo: u64 = 0,
};
};
pub const BirthProgram = enum {
bootloader,
cpu,
user,
host,
};
pub fn canVirtualizeWithQEMU(architecture: Cpu.Arch, ci: bool) bool {
if (architecture != cpu.arch) return false;
if (ci) return false;
return switch (os) {
.linux => blk: {
const uname = std.os.uname();
const release = &uname.release;
break :blk !containsAtLeast(u8, release, 1, "WSL") and !containsAtLeast(u8, release, 1, "microsoft");
},
else => false,
};
}
pub const default_cpu_name = "/cpu";
pub const default_init_file = "/init";
pub const ArgumentParser = struct {
pub const null_specifier = "-";
pub const DiskImageBuilder = struct {
argument_index: usize = 0,
pub const ArgumentType = enum {
disk_image_path,
configuration,
image_configuration_path,
bootloader,
cpu,
user_programs,
};
pub const Result = struct {
bootloader: []const u8,
disk_image_path: []const u8,
image_configuration_path: []const u8,
cpu: []const u8,
user_programs: []const []const u8,
configuration: Configuration,
};
pub fn next(argument_parser: *ArgumentParser.DiskImageBuilder) ?ArgumentType {
if (argument_parser.argument_index < enumCount(ArgumentType)) {
const result: ArgumentType = @enumFromInt(argument_parser.argument_index);
argument_parser.argument_index += 1;
return result;
}
return null;
}
};
pub const Runner = struct {
argument_index: usize = 0,
pub fn next(argument_parser: *ArgumentParser.Runner) ?ArgumentType {
if (argument_parser.argument_index < enumCount(ArgumentType)) {
const result: ArgumentType = @enumFromInt(argument_parser.argument_index);
argument_parser.argument_index += 1;
return result;
}
return null;
}
pub const ArgumentType = enum {
configuration,
disk_image_path,
image_configuration_path,
cpu_driver,
loader_path,
qemu_options,
ci,
debug_user,
debug_loader,
init,
ovmf_path,
};
pub const Result = struct {
configuration: Configuration,
disk_image_path: []const u8,
image_configuration_path: []const u8,
cpu_driver: []const u8,
loader_path: []const u8,
qemu_options: QEMUOptions,
ci: bool,
debug_user: bool,
debug_loader: bool,
init: []const u8,
ovmf_path: []const u8,
};
};
};
pub const default_disk_size = 64 * 1024 * 1024;
pub const default_sector_size = 0x200;