-
Notifications
You must be signed in to change notification settings - Fork 22
/
pci.zig
358 lines (309 loc) · 12.5 KB
/
pci.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
const std = @import("std");
const builtin = @import("builtin");
const is_test = builtin.is_test;
const expectEqual = std.testing.expectEqual;
const build_options = @import("build_options");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const log = std.log.scoped(.pci);
const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig");
/// The port address for selecting a 32bit register in the PCI configuration space.
const CONFIG_ADDRESS: u16 = 0x0CF8;
/// The port address for read/writing to the selected address.
const CONFIG_DATA: u16 = 0x0CFC;
/// The register offsets for PCI. Currently there is no check for valid register offsets for the
/// header type. The names are self explanatory. Further information can be found here:
/// https://wiki.osdev.org/PCI.
const PciRegisters = enum(u8) {
VenderId = 0x00,
DeviceId = 0x02,
Command = 0x04,
Status = 0x06,
RevisionId = 0x08,
ProgrammingInterface = 0x09,
Subclass = 0x0A,
ClassCode = 0x0B,
CacheLineSize = 0x0C,
LatencyTimer = 0x0D,
HeaderType = 0x0E,
BIST = 0x0F,
// The next set of registers are for the 0x00 (standard) header.
// This currently uses only the common registers above that are available to all header types.
BaseAddr0 = 0x10,
BaseAddr1 = 0x14,
BaseAddr2 = 0x18,
BaseAddr3 = 0x1C,
BaseAddr4 = 0x20,
BaseAddr5 = 0x24,
CardbusCISPtr = 0x28,
SubsystemVenderId = 0x2C,
SubsystemId = 0x2E,
ExpansionROMBaseAddr = 0x30,
CapabilitiesPtr = 0x34,
InterruptLine = 0x3C,
InterruptPin = 0x3D,
MinGrant = 0x3E,
MaxLatency = 0x3F,
///
/// Get the type the represents the width of the register. This can be either u8, u16 or u32.
///
/// Argument:
/// IN comptime pci_reg: PciRegisters - The register to get the width for.
///
/// Return: type
/// The width type.
///
pub fn getWidth(comptime pci_reg: PciRegisters) type {
return switch (pci_reg) {
.RevisionId, .ProgrammingInterface, .Subclass, .ClassCode, .CacheLineSize, .LatencyTimer, .HeaderType, .BIST, .InterruptLine, .InterruptPin, .MinGrant, .MaxLatency, .CapabilitiesPtr => u8,
.VenderId, .DeviceId, .Command, .Status, .SubsystemVenderId, .SubsystemId => u16,
.BaseAddr0, .BaseAddr1, .BaseAddr2, .BaseAddr3, .BaseAddr4, .BaseAddr5, .CardbusCISPtr, .ExpansionROMBaseAddr => u32,
};
}
};
/// The PCI address used for sending to the address port.
const PciAddress = packed struct {
register_offset: u8,
function: u3,
device: u5,
bus: u8,
reserved: u7 = 0,
enable: u1 = 1,
};
/// A PCI device. This will be unique to a bus and device number.
const PciDevice = struct {
/// The bus on which the device is on
bus: u8,
/// The device number.
device: u5,
const Self = @This();
///
/// Get the PCI address for this device and for a function and register.
///
/// Argument:
/// IN self: Self - This device.
/// IN function: u3 - The function.
/// IN comptime pci_reg: PciRegisters - The register.
///
/// Return: PciAddress
/// The PCI address that can be used to read the register offset for this device and function.
///
pub fn getAddress(self: Self, function: u3, comptime pci_reg: PciRegisters) PciAddress {
return PciAddress{
.bus = self.bus,
.device = self.device,
.function = function,
.register_offset = @enumToInt(pci_reg),
};
}
///
/// Read the configuration register data from this device, function and register. PCI configure
/// reads will return a u32 value, but the register may not be u32 is size so this will return
/// the correctly typed value depending on the size of the register.
///
/// Argument:
/// IN self: Self - This device.
/// IN function: u3 - The function.
/// IN comptime pci_reg: PciRegisters - The register.
///
/// Return: PciRegisters.getWidth()
/// Depending on the register, the type of the return value maybe u8, u16 or u32. See
/// PciRegisters.getWidth().
///
pub fn configReadData(self: Self, function: u3, comptime pci_reg: PciRegisters) pci_reg.getWidth() {
const address = self.getAddress(function, pci_reg);
// Last 2 bits of offset must be zero
// This is because we are requesting a integer (4 bytes) and cannot request a
// single byte that isn't 4 bytes aligned
// Write the address
arch.out(CONFIG_ADDRESS, @bitCast(u32, address) & 0xFFFFFFFC);
// Read the data
const result = arch.in(u32, CONFIG_DATA);
// Return the size the user wants
const shift = switch (pci_reg.getWidth()) {
u8 => (@intCast(u5, address.register_offset & 0x3)) * 8,
u16 => (@intCast(u5, address.register_offset & 0x2)) * 8,
u32 => 0,
else => @compileError("Invalid read size. Only u8, u16 and u32 allowed."),
};
return @truncate(pci_reg.getWidth(), (result >> shift));
}
test "configReadData u8" {
arch.initTest();
defer arch.freeTest();
// The bus, device and function values can be any value as we are testing the shifting and masking
// Have chosen bus = 0, device = 1 and function = 2.
// We only change the register as they will have different but widths.
{
const device = PciDevice{
.bus = 0,
.device = 1,
};
arch.addTestParams("out", .{ CONFIG_ADDRESS, @bitCast(u32, device.getAddress(2, .RevisionId)) & 0xFFFFFFFC });
arch.addTestParams("in", .{ CONFIG_DATA, @as(u32, 0xABCDEF12) });
// RevisionId is a u8 width, offset 0
const res = device.configReadData(2, .RevisionId);
try expectEqual(res, 0x12);
}
{
const device = PciDevice{
.bus = 0,
.device = 1,
};
arch.addTestParams("out", .{ CONFIG_ADDRESS, @bitCast(u32, device.getAddress(2, .ProgrammingInterface)) & 0xFFFFFFFC });
arch.addTestParams("in", .{ CONFIG_DATA, @as(u32, 0xABCDEF12) });
// ProgrammingInterface is a u8 width, offset 8
const res = device.configReadData(2, .ProgrammingInterface);
try expectEqual(res, 0xEF);
}
{
const device = PciDevice{
.bus = 0,
.device = 1,
};
arch.addTestParams("out", .{ CONFIG_ADDRESS, @bitCast(u32, device.getAddress(2, .Subclass)) & 0xFFFFFFFC });
arch.addTestParams("in", .{ CONFIG_DATA, @as(u32, 0xABCDEF12) });
// Subclass is a u8 width, offset 16
const res = device.configReadData(2, .Subclass);
try expectEqual(res, 0xCD);
}
{
const device = PciDevice{
.bus = 0,
.device = 1,
};
arch.addTestParams("out", .{ CONFIG_ADDRESS, @bitCast(u32, device.getAddress(2, .ClassCode)) & 0xFFFFFFFC });
arch.addTestParams("in", .{ CONFIG_DATA, @as(u32, 0xABCDEF12) });
// ClassCode is a u8 width, offset 24
const res = device.configReadData(2, .ClassCode);
try expectEqual(res, 0xAB);
}
}
test "configReadData u16" {
arch.initTest();
defer arch.freeTest();
// The bus, device and function values can be any value as we are testing the shifting and masking
// Have chosen bus = 0, device = 1 and function = 2.
// We only change the register as they will have different but widths.
{
const device = PciDevice{
.bus = 0,
.device = 1,
};
arch.addTestParams("out", .{ CONFIG_ADDRESS, @bitCast(u32, device.getAddress(2, .VenderId)) & 0xFFFFFFFC });
arch.addTestParams("in", .{ CONFIG_DATA, @as(u32, 0xABCDEF12) });
// VenderId is a u16 width, offset 0
const res = device.configReadData(2, .VenderId);
try expectEqual(res, 0xEF12);
}
{
const device = PciDevice{
.bus = 0,
.device = 1,
};
arch.addTestParams("out", .{ CONFIG_ADDRESS, @bitCast(u32, device.getAddress(2, .DeviceId)) & 0xFFFFFFFC });
arch.addTestParams("in", .{ CONFIG_DATA, @as(u32, 0xABCDEF12) });
// DeviceId is a u16 width, offset 16
const res = device.configReadData(2, .DeviceId);
try expectEqual(res, 0xABCD);
}
}
test "configReadData u32" {
arch.initTest();
defer arch.freeTest();
// The bus, device and function values can be any value as we are testing the shifting and masking
// Have chosen bus = 0, device = 1 and function = 2.
// We only change the register as they will have different but widths.
{
const device = PciDevice{
.bus = 0,
.device = 1,
};
arch.addTestParams("out", .{ CONFIG_ADDRESS, @bitCast(u32, device.getAddress(2, .BaseAddr0)) & 0xFFFFFFFC });
arch.addTestParams("in", .{ CONFIG_DATA, @as(u32, 0xABCDEF12) });
// BaseAddr0 is a u32 width, offset 0
const res = device.configReadData(2, .BaseAddr0);
try expectEqual(res, 0xABCDEF12);
}
}
};
pub const PciDeviceInfo = struct {
pci_device: PciDevice,
function: u3,
vender_id: u16,
device_id: u16,
subclass: u8,
class_code: u8,
/// The error set.
pub const Error = error{
/// There is no functions available for the given function number for a given PCI device.
NoFunction,
};
pub fn create(pci_device: PciDevice, function: u3) Error!PciDeviceInfo {
const vender_id = pci_device.configReadData(function, .VenderId);
// No function available, try the next
if (vender_id == 0xFFFF) {
return Error.NoFunction;
}
return PciDeviceInfo{
.pci_device = pci_device,
.function = function,
.vender_id = vender_id,
.device_id = pci_device.configReadData(function, .DeviceId),
.subclass = pci_device.configReadData(function, .Subclass),
.class_code = pci_device.configReadData(function, .ClassCode),
};
}
pub fn print(device: arch.Device) void {
log.info("BUS: 0x{X}, DEV: 0x{X}, FUN: 0x{X}, VID: 0x{X}, DID: 0x{X}, SC: 0x{X}, CC: 0x{X}\n", .{
device.pci_device.bus,
device.pci_device.device,
device.function,
device.vender_id,
device.device_id,
device.subclass,
device.class_code,
});
}
};
///
/// Get a list of all the PCI device. The returned list will needed to be freed by the caller.
///
/// Arguments:
/// IN allocator: Allocator - An allocator used for creating the list.
///
/// Return: []PciDeviceInfo
/// The list of PCI devices information.
///
/// Error: Allocator.Error
/// error.OutOfMemory - If there isn't enough memory to create the info list.
///
pub fn getDevices(allocator: Allocator) Allocator.Error![]PciDeviceInfo {
// Create an array list for the devices.
var pci_device_infos = ArrayList(PciDeviceInfo).init(allocator);
defer pci_device_infos.deinit();
// Iterate through all the possible devices
var _bus: u32 = 0;
while (_bus < 8) : (_bus += 1) {
const bus = @intCast(u8, _bus);
var _device: u32 = 0;
while (_device < 32) : (_device += 1) {
const device = @intCast(u5, _device);
// Devices have at least 1 function
const pci_device = PciDevice{
.bus = bus,
.device = device,
};
var num_functions: u32 = if (pci_device.configReadData(0, .HeaderType) & 0x80 != 0) 8 else 1;
var _function: u32 = 0;
while (_function < num_functions) : (_function += 1) {
const function = @intCast(u3, _function);
const device_info = PciDeviceInfo.create(pci_device, function) catch |e| switch (e) {
error.NoFunction => continue,
};
try pci_device_infos.append(device_info);
}
}
}
return pci_device_infos.toOwnedSlice();
}