Skip to content

Commit cb6586f

Browse files
committed
Support for parent serial number
Sometimes on Windows the serial number has to be obtained from a parent node
1 parent 9fb12b0 commit cb6586f

File tree

2 files changed

+53
-20
lines changed

2 files changed

+53
-20
lines changed

examples/list_port_info.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ pub fn main() !u8 {
1111
std.debug.print(" - Friendly name: {s}\n", .{info.friendly_name});
1212
std.debug.print(" - Description: {s}\n", .{info.description});
1313
std.debug.print(" - Manufacturer: {s}\n", .{info.manufacturer});
14-
std.debug.print(" - Serial #: {s}\n", .{info.serial_number});
14+
std.debug.print(" - Serial #: {s}\n", .{info.serial_number});
15+
std.debug.print(" - HW ID: {s}\n", .{info.hw_id});
1516
std.debug.print(" - VID: 0x{X:0>4} PID: 0x{X:0>4}\n", .{ info.vid, info.pid });
1617
}
1718

src/serial.zig

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ pub const PortInformation = struct {
3636
description: []const u8,
3737
manufacturer: []const u8,
3838
serial_number: []const u8,
39+
// TODO: review whether to remove `hw_id`.
40+
// Is this useless/being used in a Windows-only way?
41+
hw_id: []const u8,
3942
vid: u16,
4043
pid: u16,
4144
};
@@ -172,6 +175,7 @@ const WindowsInformationIterator = struct {
172175
defer self.index += 1;
173176

174177
var info: PortInformation = std.mem.zeroes(PortInformation);
178+
@memset(&self.hw_id, 0);
175179

176180
// NOTE: have not handled if port startswith("LPT")
177181
var length = getPortName(&self.device_info_set, &device_info_data, &self.port_buffer);
@@ -192,10 +196,16 @@ const WindowsInformationIterator = struct {
192196
self.device_info_set,
193197
&device_info_data,
194198
@ptrCast(&self.hw_id),
195-
256,
199+
255,
196200
null,
197201
) == std.os.windows.TRUE) {
198-
length = parseSerialNumber(device_info_data.devInst, &self.hw_id, &self.serial_buffer) catch 0;
202+
length = @as(u32, @truncate(std.mem.indexOfSentinel(u8, 0, &self.hw_id)));
203+
info.hw_id = self.hw_id[0..length];
204+
205+
length = parseSerialNumber(&self.hw_id, &self.serial_buffer) catch 0;
206+
if (length == 0) {
207+
length = getParentSerialNumber(device_info_data.devInst, &self.hw_id, &self.serial_buffer) catch 0;
208+
}
199209
info.serial_number = self.serial_buffer[0..length];
200210
info.vid = parseVendorId(&self.hw_id) catch 0;
201211
info.pid = parseProductId(&self.hw_id) catch 0;
@@ -220,8 +230,6 @@ const WindowsInformationIterator = struct {
220230
_ = std.os.windows.advapi32.RegCloseKey(hkey);
221231
}
222232

223-
// if (hkey == std.os.windows.INVALID_HANDLE_VALUE) return error.std.os.Windows;
224-
225233
inline for (.{ "PortName", "PortNumber" }) |key_token| {
226234
var port_length: std.os.windows.DWORD = std.os.windows.NAME_MAX;
227235
var data_type: std.os.windows.DWORD = 0;
@@ -266,38 +274,62 @@ const WindowsInformationIterator = struct {
266274
return bytes_required;
267275
}
268276

269-
fn parseSerialNumber(devinst: DEVINST, devid: []const u8, serial_number: [*]u8) !std.os.windows.DWORD {
277+
fn getParentSerialNumber(devinst: DEVINST, devid: []const u8, serial_number: [*]u8) !std.os.windows.DWORD {
278+
if (std.mem.startsWith(u8, devid, "FTDI")) {
279+
// Should not be called on "FTDI" so just return the serial number.
280+
return try parseSerialNumber(devid, serial_number);
281+
} else if (std.mem.startsWith(u8, devid, "USB")) {
282+
// taken from pyserial
283+
const max_usb_device_tree_traversal_depth = 5;
284+
const start_vidpid = std.mem.indexOf(u8, devid, "VID") orelse return error.WindowsError;
285+
const vidpid_slice = devid[start_vidpid .. start_vidpid + 17]; // "VIDxxxx&PIDxxxx"
286+
287+
// keep looping over parent device to extract serial number if it contains the target VID and PID.
288+
var depth: u8 = 0;
289+
var child_inst: DEVINST = devinst;
290+
while (depth <= max_usb_device_tree_traversal_depth) : (depth += 1) {
291+
var parent_id: DEVINST = undefined;
292+
var local_buffer: [256:0]u8 = std.mem.zeroes([256:0]u8);
293+
294+
if (CM_Get_Parent(&parent_id, child_inst, 0) != 0) return error.WindowsError;
295+
if (CM_Get_Device_IDA(parent_id, @ptrCast(&local_buffer), 256, 0) != 0) return error.WindowsError;
296+
defer child_inst = parent_id;
297+
298+
if (!std.mem.containsAtLeast(u8, local_buffer[0..255], 1, vidpid_slice)) continue;
299+
300+
const length = try parseSerialNumber(local_buffer[0..255], serial_number);
301+
if (length > 0) return length;
302+
}
303+
}
304+
305+
return error.WindowsError;
306+
}
307+
308+
fn parseSerialNumber(devid: []const u8, serial_number: [*]u8) !std.os.windows.DWORD {
270309
var delimiter: ?[]const u8 = undefined;
271-
var slice: []const u8 = undefined;
272310

273311
if (std.mem.startsWith(u8, devid, "USB")) {
274312
delimiter = "\\&";
275-
276-
var parent_id: DEVINST = undefined;
277-
var local_buffer: [256:0]u8 = std.mem.zeroes([256:0]u8);
278-
279-
// It appears other approaches recursively scan through parent-child IDs to
280-
// find a serial number. This may need further investigation to understand
281-
// when/if this is required.
282-
if (CM_Get_Parent(&parent_id, devinst, 0) != 0) return error.WindowsError;
283-
if (CM_Get_Device_IDA(parent_id, @ptrCast(&local_buffer), 256, 0) != 0) return error.WindowsError;
284-
285-
slice = local_buffer[0..256];
286313
} else if (std.mem.startsWith(u8, devid, "FTDI")) {
287314
delimiter = "\\+";
288-
slice = devid;
289315
} else {
316+
// What to do here?
290317
delimiter = null;
291318
}
292319

293320
if (delimiter) |del| {
294-
var it = std.mem.tokenize(u8, slice, del);
321+
var it = std.mem.tokenize(u8, devid, del);
295322

296323
// throw away the start
297324
_ = it.next();
298325
while (it.next()) |segment| {
299326
if (std.mem.startsWith(u8, segment, "VID_")) continue;
300327
if (std.mem.startsWith(u8, segment, "PID_")) continue;
328+
329+
// If "MI_{d}{d}", this is an interface number. The serial number will have to be
330+
// sourced from the parent node. Probably do not have to check all these conditions.
331+
if (segment.len == 5 and std.mem.eql(u8, "MI_", segment[0..3]) and std.ascii.isDigit(segment[3]) and std.ascii.isDigit(segment[4])) return 0;
332+
301333
@memcpy(serial_number, segment);
302334
return @as(std.os.windows.DWORD, @truncate(segment.len));
303335
}

0 commit comments

Comments
 (0)