Skip to content

Commit

Permalink
feat: add support for Linux kernel booting
Browse files Browse the repository at this point in the history
  • Loading branch information
b0bleet authored and b0bleet committed Aug 13, 2024
0 parents commit 716e84d
Show file tree
Hide file tree
Showing 19 changed files with 2,326 additions and 0 deletions.
Binary file added .github/images/linuxboot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/zig-cache/
/zig-out/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "qboot"]
path = qboot
url = [email protected]:bonzini/qboot.git
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Bob Leet

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Zvisor

<p align="center">
<img src="/.github/images/linuxboot.png"
width="800" border="0" alt="zvisor">
</p>

Zvisor is an open-source hypervisor written in the Zig programming language, which provides a modern and efficient approach to systems programming. Zvisor leverages the KVM (Kernel-based Virtual Machine) virtualization technology, which is built into the Linux kernel, to provide a lightweight and flexible virtualization solution.

One of the key benefits of Zvisor is its use of the Zig programming language. Zig provides a modern and efficient approach to systems programming, with features like memory safety, error handling, and compile-time optimization.

## Getting Started

To get started with the hypervisor, you will need to have Zig (>= 0.11.0) installed on your system. Once you have Zig installed, you can build the hypervisor using the following commands:

```bash
git clone https://github.com/b0bleet/zvisor.git
cd zvisor
zig build
```
This will build the hypervisor and create a binary (`./zig-out/bin/zvisor`) that you can use to start the hypervisor.

Zvisor uses qboot minimal x86 firmware to boot the Linux kernel that's why you have to build qboot before running hypervisor. qboot does PCI setup, IDT setup, E820 table extraction, ACPI tables extraction etc.
```
git clone https://github.com/b0bleet/qboot
meson build && ninja -C build
```

## Running Zvisor
To run Zvisor, you'll need to specify the path to the kernel file, the amount of memory to allocate to the virtual machine, and the initrd file to use.
Here's an example command to run a virtual machine with 2GB of memory:
```bash
./zig-out/bin/zvisor --firmware ./qboot/build/bios.bin \
--kernel ./bzImage \
--cmdline 'console=ttyS0,115200,8n1 noapic' \
--initrd ./initrd \
--memory 1G
```
`noapic` option should be passed because, at the moment, Zvisor only supports in-kernel PIC emulation.

## initrd file
An initrd (initial RAM disk) file is a temporary root filesystem that is loaded into memory when the system boots. It's used by the Linux kernel to perform initial tasks like loading necessary drivers, mounting the actual root filesystem, and other early boot tasks. Use BusyBox To generate an initrd file.
68 changes: 68 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

const exe = b.addExecutable(.{
.name = "zvisor",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
exe.linkSystemLibrary("c");

// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
exe.install();

// This *creates* a RunStep in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = exe.run();

// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());

// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}

// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);

// Creates a step for unit testing.
const exe_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});

// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&exe_tests.step);
}
117 changes: 117 additions & 0 deletions src/console_controller.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const std = @import("std");
const builtin = @import("builtin");
const utils = @import("utils.zig");
const os = std.os;
const system = os.system;
const errno = system.getErrno;
const serial = @import("devices/serial.zig");

const SerialDevice = serial.SerialDevice;

pub const ConsoleMode = enum {
Tty,
Pty,
};

const EpollMode = enum {
File,
Unknown,
};

pub const ConsoleController = struct {
const Self = @This();

serial: *SerialDevice,
epollfd: i32,
mode: i32,
handle: std.Thread = undefined,

pub fn init(mode: ConsoleMode, serial_dev: *SerialDevice) !?Self {
const file_mode = switch (mode) {
.Tty => tty_blk: {
const stdin_handle = std.io.getStdIn().handle;
if (os.isatty(stdin_handle)) {
const dup_stdin = try os.dup(stdin_handle);
var flags = try os.fcntl(dup_stdin, os.F.GETFL, 0);
flags |= os.O.NONBLOCK;
_ = try os.fcntl(dup_stdin, os.F.SETFL, flags);

break :tty_blk dup_stdin;
}
return null;
},
else => @panic("unsupported file mode for console device"),
};

switch (builtin.os.tag) {
.linux => {
const epollfd = try os.epoll_create1(os.linux.EPOLL.CLOEXEC);
errdefer os.close(epollfd);

var eventfd_event = os.linux.epoll_event{
.events = os.linux.EPOLL.IN,
.data = .{ .ptr = @enumToInt(EpollMode.File) },
};

try os.epoll_ctl(
epollfd,
os.linux.EPOLL.CTL_ADD,
file_mode,
&eventfd_event,
);

return Self{
.serial = serial_dev,
.epollfd = epollfd,
.mode = file_mode,
};
},
else => @compileError("unsupported Os"),
}
return null;
}

pub fn start_thread(self: *Self) anyerror!void {
self.handle = try std.Thread.spawn(.{}, thread, .{ self.epollfd, self.mode, self.serial });
}

fn thread(epollfd: i32, polledfd: i32, serial_dev: *SerialDevice) !void {
const EpollEventsCount: usize = 3;
const MaxBufBytes: usize = 64;
while (true) {
switch (builtin.os.tag) {
.linux => {
var events: [EpollEventsCount]os.linux.epoll_event = undefined;

const counts = os.epoll_wait(epollfd, events[0..], -1);
const n = switch (std.os.errno(counts)) {
.SUCCESS => counts,
.INTR => continue,
else => |err| return std.os.unexpectedErrno(err),
};

for (events[0..n]) |ev| {
const dispatch = @intToEnum(EpollMode, @intCast(usize, ev.data.ptr));
switch (dispatch) {
.File => {
if (@as(u32, ev.events & os.linux.EPOLL.IN) != 0) {
var bytes: [MaxBufBytes]u8 = undefined;
const count = std.os.read(polledfd, &bytes) catch |err| switch (err) {
error.WouldBlock => {
continue;
},
else => return err,
};

try serial_dev.queue_bytes(&bytes[0..count]);
}
},
else => unreachable,
}
}
},
else => @compileError("unsupported Os"),
}
}
}
};
37 changes: 37 additions & 0 deletions src/devices/i8042.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const std = @import("std");
const io = @import("../io.zig");

const Device = io.Device;

pub const i8042Device = struct {
const Self = @This();

pub fn init() Self {
return Self{};
}

fn read(_: *anyopaque, offset: u64, _: u64, data: []u8) anyerror!void {
if (data.len == 1 and offset == 3) {
data[0] = 0x0;
} else if (data.len == 1 and offset == 0) {
data[0] = 0x20;
}
}

fn write(_: *anyopaque, _: u64, _: u64, _: []u8) !void {}

pub fn dev(
self: *@This(),
) Device {
return Device{
.deinit = null,
.base = 0x61,
.size = 0x65,
.ptr = self,
.vtable = &.{
.read = read,
.write = write,
},
};
}
};
36 changes: 36 additions & 0 deletions src/devices/interrupt_controller.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const std = @import("std");
const io = @import("../io.zig");
const Device = io.Device;

pub const IOAPIC_START: usize = 0xfec0_0000;
pub const IOAPIC_SIZE: usize = 0x20;

pub const APIC_START: usize = 0xfee0_0000;

pub const InterruptController = struct {
id: []const u8,
apic_address: usize,

pub fn init(apic_address: usize) InterruptController {
return InterruptController{ .id = "ioapic", .apic_address = apic_address };
}

pub fn dev(
self: *@This(),
) Device {
return Device{
.deinit = null,
.base = IOAPIC_START,
.size = (IOAPIC_START + IOAPIC_SIZE),
.ptr = self,
.vtable = &.{
.read = read,
.write = write,
},
};
}

fn read(_: *anyopaque, _: u64, _: u64, _: []u8) anyerror!void {}

fn write(_: *anyopaque, _: u64, _: u64, _: []u8) !void {}
};
Loading

0 comments on commit 716e84d

Please sign in to comment.