master
  1const std = @import("../../../std.zig");
  2const mem = std.mem;
  3const uefi = std.os.uefi;
  4const Allocator = mem.Allocator;
  5const Guid = uefi.Guid;
  6const assert = std.debug.assert;
  7
  8// All Device Path Nodes are byte-packed and may appear on any byte boundary.
  9// All code references to device path nodes must assume all fields are unaligned.
 10
 11pub const DevicePath = extern struct {
 12    type: uefi.DevicePath.Type,
 13    subtype: u8,
 14    length: u16 align(1),
 15
 16    pub const CreateFileDevicePathError = Allocator.Error;
 17
 18    pub const guid align(8) = Guid{
 19        .time_low = 0x09576e91,
 20        .time_mid = 0x6d3f,
 21        .time_high_and_version = 0x11d2,
 22        .clock_seq_high_and_reserved = 0x8e,
 23        .clock_seq_low = 0x39,
 24        .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b },
 25    };
 26
 27    /// Returns the next DevicePath node in the sequence, if any.
 28    pub fn next(self: *const DevicePath) ?*const DevicePath {
 29        const bytes: [*]const u8 = @ptrCast(self);
 30        const next_node: *const DevicePath = @ptrCast(bytes + self.length);
 31        if (next_node.type == .end and @as(uefi.DevicePath.End.Subtype, @enumFromInt(self.subtype)) == .end_entire)
 32            return null;
 33
 34        return next_node;
 35    }
 36
 37    /// Calculates the total length of the device path structure in bytes, including the end of device path node.
 38    pub fn size(self: *const DevicePath) usize {
 39        var node = self;
 40
 41        while (node.next()) |next_node| {
 42            node = next_node;
 43        }
 44
 45        return (@intFromPtr(node) + node.length) - @intFromPtr(self);
 46    }
 47
 48    /// Creates a file device path from the existing device path and a file path.
 49    pub fn createFileDevicePath(
 50        self: *const DevicePath,
 51        allocator: Allocator,
 52        path: []const u16,
 53    ) CreateFileDevicePathError!*const DevicePath {
 54        const path_size = self.size();
 55
 56        // 2 * (path.len + 1) for the path and its null terminator, which are u16s
 57        // DevicePath for the extra node before the end
 58        var buf = try allocator.alloc(u8, path_size + 2 * (path.len + 1) + @sizeOf(DevicePath));
 59
 60        @memcpy(buf[0..path_size], @as([*]const u8, @ptrCast(self))[0..path_size]);
 61
 62        // Pointer to the copy of the end node of the current chain, which is - 4 from the buffer
 63        // as the end node itself is 4 bytes (type: u8 + subtype: u8 + length: u16).
 64        var new = @as(*uefi.DevicePath.Media.FilePathDevicePath, @ptrCast(buf.ptr + path_size - 4));
 65
 66        new.type = .media;
 67        new.subtype = .file_path;
 68        new.length = @sizeOf(uefi.DevicePath.Media.FilePathDevicePath) + 2 * (@as(u16, @intCast(path.len)) + 1);
 69
 70        // The same as new.getPath(), but not const as we're filling it in.
 71        var ptr = @as([*:0]align(1) u16, @ptrCast(@as([*]u8, @ptrCast(new)) + @sizeOf(uefi.DevicePath.Media.FilePathDevicePath)));
 72
 73        for (path, 0..) |s, i|
 74            ptr[i] = s;
 75
 76        ptr[path.len] = 0;
 77
 78        var end = @as(*uefi.DevicePath.End.EndEntireDevicePath, @ptrCast(@constCast(@as(*DevicePath, @ptrCast(new)).next().?)));
 79        end.type = .end;
 80        end.subtype = .end_entire;
 81        end.length = @sizeOf(uefi.DevicePath.End.EndEntireDevicePath);
 82
 83        return @as(*DevicePath, @ptrCast(buf.ptr));
 84    }
 85
 86    pub fn getDevicePath(self: *const DevicePath) ?uefi.DevicePath {
 87        inline for (@typeInfo(uefi.DevicePath).@"union".fields) |ufield| {
 88            const enum_value = std.meta.stringToEnum(uefi.DevicePath.Type, ufield.name);
 89
 90            // Got the associated union type for self.type, now
 91            // we need to initialize it and its subtype
 92            if (self.type == enum_value) {
 93                const subtype = self.initSubtype(ufield.type);
 94                if (subtype) |sb| {
 95                    // e.g. return .{ .hardware = .{ .pci = @ptrCast(...) } }
 96                    return @unionInit(uefi.DevicePath, ufield.name, sb);
 97                }
 98            }
 99        }
100
101        return null;
102    }
103
104    pub fn initSubtype(self: *const DevicePath, comptime TUnion: type) ?TUnion {
105        const type_info = @typeInfo(TUnion).@"union";
106        const TTag = type_info.tag_type.?;
107
108        inline for (type_info.fields) |subtype| {
109            // The tag names match the union names, so just grab that off the enum
110            const tag_val: u8 = @intFromEnum(@field(TTag, subtype.name));
111
112            if (self.subtype == tag_val) {
113                // e.g. expr = .{ .pci = @ptrCast(...) }
114                return @unionInit(TUnion, subtype.name, @as(subtype.type, @ptrCast(self)));
115            }
116        }
117
118        return null;
119    }
120};
121
122comptime {
123    assert(4 == @sizeOf(DevicePath));
124    assert(1 == @alignOf(DevicePath));
125
126    assert(0 == @offsetOf(DevicePath, "type"));
127    assert(1 == @offsetOf(DevicePath, "subtype"));
128    assert(2 == @offsetOf(DevicePath, "length"));
129}