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 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
121comptime {
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}