| 1 | const std = @import("../../../std.zig"); |
| 2 | const mem = std.mem; |
| 3 | const uefi = std.os.uefi; |
| 4 | const Allocator = mem.Allocator; |
| 5 | const Guid = uefi.Guid; |
| 6 | const 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 | |
| 11 | pub 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 subtype: uefi.DevicePath.End.Subtype = @fromBackingInt(@intCast(self.subtype)); |
| 30 | if (self.type == .end and subtype == .end_entire) return null; |
| 31 | const bytes: [*]const u8 = @ptrCast(self); |
| 32 | return @ptrCast(bytes + self.length); |
| 33 | } |
| 34 | |
| 35 | /// Calculates the total length of the device path structure in bytes, including the end of device path node. |
| 36 | pub fn size(self: *const DevicePath) usize { |
| 37 | var node = self; |
| 38 | |
| 39 | while (node.next()) |next_node| { |
| 40 | node = next_node; |
| 41 | } |
| 42 | |
| 43 | return (@intFromPtr(node) + node.length) - @intFromPtr(self); |
| 44 | } |
| 45 | |
| 46 | /// Creates a file device path from the existing device path and a file path. |
| 47 | pub fn createFileDevicePath( |
| 48 | self: *const DevicePath, |
| 49 | allocator: Allocator, |
| 50 | path: []const u16, |
| 51 | ) CreateFileDevicePathError!*const DevicePath { |
| 52 | const path_size = self.size(); |
| 53 | |
| 54 | // 2 * (path.len + 1) for the path and its null terminator, which are u16s |
| 55 | // DevicePath for the extra node before the end |
| 56 | var buf = try allocator.alloc(u8, path_size + 2 * (path.len + 1) + @sizeOf(DevicePath)); |
| 57 | |
| 58 | @memcpy(buf[0..path_size], @as([*]const u8, @ptrCast(self))[0..path_size]); |
| 59 | |
| 60 | // Pointer to the copy of the end node of the current chain, which is - 4 from the buffer |
| 61 | // as the end node itself is 4 bytes (type: u8 + subtype: u8 + length: u16). |
| 62 | var new = @as(*uefi.DevicePath.Media.FilePathDevicePath, @ptrCast(buf.ptr + path_size - 4)); |
| 63 | |
| 64 | new.type = .media; |
| 65 | new.subtype = .file_path; |
| 66 | new.length = @sizeOf(uefi.DevicePath.Media.FilePathDevicePath) + 2 * (@as(u16, @intCast(path.len)) + 1); |
| 67 | |
| 68 | // The same as new.getPath(), but not const as we're filling it in. |
| 69 | var ptr = @as([*:0]align(1) u16, @ptrCast(@as([*]u8, @ptrCast(new)) + @sizeOf(uefi.DevicePath.Media.FilePathDevicePath))); |
| 70 | |
| 71 | for (path, 0..) |s, i| |
| 72 | ptr[i] = s; |
| 73 | |
| 74 | ptr[path.len] = 0; |
| 75 | |
| 76 | var end = @as(*uefi.DevicePath.End.EndEntireDevicePath, @ptrCast(@constCast(@as(*DevicePath, @ptrCast(new)).next().?))); |
| 77 | end.type = .end; |
| 78 | end.subtype = .end_entire; |
| 79 | end.length = @sizeOf(uefi.DevicePath.End.EndEntireDevicePath); |
| 80 | |
| 81 | return @as(*DevicePath, @ptrCast(buf.ptr)); |
| 82 | } |
| 83 | |
| 84 | pub fn getDevicePath(self: *const DevicePath) ?uefi.DevicePath { |
| 85 | const u_info = @typeInfo(uefi.DevicePath).@"union"; |
| 86 | inline for (u_info.field_names, u_info.field_types) |ufield_name, ufield_type| { |
| 87 | const enum_value = std.meta.stringToEnum(uefi.DevicePath.Type, ufield_name); |
| 88 | |
| 89 | // Got the associated union type for self.type, now |
| 90 | // we need to initialize it and its subtype |
| 91 | if (self.type == enum_value) { |
| 92 | const subtype = self.initSubtype(ufield_type); |
| 93 | if (subtype) |sb| { |
| 94 | // e.g. return .{ .hardware = .{ .pci = @ptrCast(...) } } |
| 95 | return @unionInit(uefi.DevicePath, ufield_name, sb); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | return null; |
| 101 | } |
| 102 | |
| 103 | pub fn initSubtype(self: *const DevicePath, comptime TUnion: type) ?TUnion { |
| 104 | const type_info = @typeInfo(TUnion).@"union"; |
| 105 | const TTag = type_info.tag_type.?; |
| 106 | |
| 107 | inline for (type_info.field_names, type_info.field_types) |subtype_name, subtype_type| { |
| 108 | // The tag names match the union names, so just grab that off the enum |
| 109 | const tag_val: u8 = @backingInt(@field(TTag, subtype_name)); |
| 110 | |
| 111 | if (self.subtype == tag_val) { |
| 112 | // e.g. expr = .{ .pci = @ptrCast(...) } |
| 113 | return @unionInit(TUnion, subtype_name, @as(subtype_type, @ptrCast(self))); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | return null; |
| 118 | } |
| 119 | }; |
| 120 | |
| 121 | comptime { |
| 122 | assert(4 == @sizeOf(DevicePath)); |
| 123 | assert(1 == @alignOf(DevicePath)); |
| 124 | |
| 125 | assert(0 == @offsetOf(DevicePath, "type")); |
| 126 | assert(1 == @offsetOf(DevicePath, "subtype")); |
| 127 | assert(2 == @offsetOf(DevicePath, "length")); |
| 128 | } |