Skip to content

Commit bbf09a2

Browse files
committed
Fix:PD Discovery and USB capabilities
Introduces static, platform-specific capability tables for USB-C expansion slots and expansion bay ports. The EC does not report these details, so this provides documented information on data lanes, DisplayPort versions, and maximum charging wattages. This also correctly determines the number of USB-C slots for each platform (e.g., Framework 16 has 6, Framework Desktop has 2, others 4) and clears live PD state for slots that do not support Power Delivery. Bumps `framework_lib_ffi` to reflect this ABI-breaking change.
1 parent df136e7 commit bbf09a2

10 files changed

Lines changed: 322 additions & 7 deletions

framework-dotnet-cli-test/Program.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,37 @@ private static void AppendExpansionBayGroup(StringBuilder content, string groupN
199199
}
200200
}
201201

202+
if (expansionBay.HasUsbCPort && expansionBay.UsbCPort is { } bayPort)
203+
{
204+
content.Append($", USB-C Port=[CState={bayPort.CState}, Role={bayPort.PowerRole}");
205+
206+
if (bayPort.HasPowerDeliveryContract)
207+
{
208+
content.Append($", {bayPort.Voltage.ToString(CultureInfo.InvariantCulture)} @ {bayPort.Current.ToString(CultureInfo.InvariantCulture)}");
209+
}
210+
211+
content.Append(']');
212+
213+
if (expansionBay.UsbCCapability is { } bayCapability)
214+
{
215+
content.Append($", Capability=[{bayCapability.DataLane}");
216+
217+
if (bayCapability.DisplayPort != FrameworkDisplayPortCapability.None)
218+
{
219+
content.Append($", DP={bayCapability.DisplayPort}");
220+
}
221+
222+
if (bayCapability.SupportsPowerDelivery)
223+
{
224+
content.Append(bayCapability.MaxChargePower.Watts > 0
225+
? $", Charging up to {bayCapability.MaxChargePower.ToString(CultureInfo.InvariantCulture)}"
226+
: ", Charging");
227+
}
228+
229+
content.Append(']');
230+
}
231+
}
232+
202233
if (!string.IsNullOrEmpty(expansionBay.SerialNumber))
203234
{
204235
content.Append($", Serial={expansionBay.SerialNumber}");
@@ -249,6 +280,35 @@ private static void AppendUsbCSlotGroup(StringBuilder content, string groupName,
249280

250281
content.Append(']');
251282

283+
FrameworkUsbCPortCapabilitySnapshot capability = slot.Capability;
284+
if (capability.IsDocumented)
285+
{
286+
content.Append($", Capability=[{capability.DataLane}");
287+
288+
if (capability.DisplayPort != FrameworkDisplayPortCapability.None)
289+
{
290+
content.Append($", DP={capability.DisplayPort}");
291+
}
292+
293+
if (capability.SupportsPowerDelivery)
294+
{
295+
content.Append(capability.MaxChargePower.Watts > 0
296+
? $", Charging up to {capability.MaxChargePower.ToString(CultureInfo.InvariantCulture)}"
297+
: ", Charging");
298+
}
299+
else
300+
{
301+
content.Append(", No PD charging (power-limited)");
302+
}
303+
304+
if (capability.UsbAHighPowerDraw)
305+
{
306+
content.Append(", USB-A high power");
307+
}
308+
309+
content.Append(']');
310+
}
311+
252312
if (slot.Flags != FrameworkModuleFlags.None)
253313
{
254314
content.Append($", Flags={slot.Flags}");
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace FrameworkDotnet.Enums;
2+
3+
/// <summary>
4+
/// Represents the static DisplayPort alt-mode capability and version of a numbered expansion card slot (board
5+
/// specification, not the live alt-mode state reported in the Power Delivery alt-mode flags).
6+
/// </summary>
7+
public enum FrameworkDisplayPortCapability : byte
8+
{
9+
/// <summary>
10+
/// No DisplayPort alt-mode on this slot.
11+
/// </summary>
12+
None = 0,
13+
14+
/// <summary>
15+
/// DisplayPort 1.4 / 1.4a (HBR3).
16+
/// </summary>
17+
Dp14Hbr3 = 1,
18+
19+
/// <summary>
20+
/// DisplayPort 2.0 (UHBR qualifier not documented).
21+
/// </summary>
22+
Dp20 = 2,
23+
24+
/// <summary>
25+
/// DisplayPort 2.0 UHBR10.
26+
/// </summary>
27+
Dp20Uhbr10 = 3,
28+
29+
/// <summary>
30+
/// DisplayPort 2.0 UHBR20.
31+
/// </summary>
32+
Dp20Uhbr20 = 4,
33+
34+
/// <summary>
35+
/// DisplayPort 2.1 (UHBR qualifier not documented).
36+
/// </summary>
37+
Dp21 = 5,
38+
39+
/// <summary>
40+
/// DisplayPort 2.1 UHBR10.
41+
/// </summary>
42+
Dp21Uhbr10 = 6,
43+
44+
/// <summary>
45+
/// DisplayPort 2.1 UHBR20.
46+
/// </summary>
47+
Dp21Uhbr20 = 7,
48+
49+
/// <summary>
50+
/// DisplayPort supported, but the version is not documented in the source matrix.
51+
/// </summary>
52+
Supported = 8,
53+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
namespace FrameworkDotnet.Enums;
2+
3+
/// <summary>
4+
/// Represents the static USB-C data-lane capability of a numbered expansion card slot (board specification, not
5+
/// the live negotiated link).
6+
/// </summary>
7+
public enum FrameworkUsbCDataLane : byte
8+
{
9+
/// <summary>
10+
/// Capability is not documented for this slot or platform.
11+
/// </summary>
12+
Unknown = 0,
13+
14+
/// <summary>
15+
/// USB 2.0 only.
16+
/// </summary>
17+
Usb2 = 1,
18+
19+
/// <summary>
20+
/// USB 3.2 (generation unspecified).
21+
/// </summary>
22+
Usb32 = 2,
23+
24+
/// <summary>
25+
/// USB 3.2 Gen 2x1 (10 Gbps).
26+
/// </summary>
27+
Usb32Gen2x1 = 3,
28+
29+
/// <summary>
30+
/// USB 3.2 Gen 2x2 (20 Gbps).
31+
/// </summary>
32+
Usb32Gen2x2 = 4,
33+
34+
/// <summary>
35+
/// USB4.
36+
/// </summary>
37+
Usb4 = 5,
38+
39+
/// <summary>
40+
/// Thunderbolt 4.
41+
/// </summary>
42+
Thunderbolt4 = 6,
43+
}

framework-dotnet/Generated/FrameworkEcExpansionBayStatus.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ internal readonly ManagedExpansionBaySnapshot ToManagedSnapshot()
2626
ToManagedBoard(),
2727
ToManagedVendor(),
2828
ToManagedPcieConfiguration(),
29-
serialNumber.ToUtf8String());
29+
serialNumber.ToUtf8String())
30+
{
31+
HasUsbCPort = has_usb_c_port != 0,
32+
UsbCPort = has_usb_c_port != 0 ? pd.ToManagedSnapshot() : null,
33+
UsbCCapability = has_usb_c_port != 0 ? capability.ToManagedSnapshot() : null,
34+
};
3035
}
3136
finally
3237
{

framework-dotnet/Generated/FrameworkExpansionCardModuleDescriptor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ internal readonly ManagedExpansionCardSlotSnapshot ToManagedSnapshot()
2121
board_id,
2222
pd.ToManagedSnapshot(),
2323
ToManagedCardType(),
24-
ToManagedCardConfidence());
24+
ToManagedCardConfidence(),
25+
capability.ToManagedSnapshot());
2526
}
2627

2728
private readonly FrameworkDotnet.Enums.FrameworkModuleIdentity ToManagedIdentity()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using UnitsNet;
2+
3+
using ManagedUsbCPortCapabilitySnapshot = FrameworkDotnet.Snapshots.FrameworkUsbCPortCapabilitySnapshot;
4+
5+
namespace Framework.System.Interop;
6+
7+
internal unsafe partial struct FrameworkUsbCPortCapability
8+
{
9+
internal readonly ManagedUsbCPortCapabilitySnapshot ToManagedSnapshot()
10+
{
11+
return new ManagedUsbCPortCapabilitySnapshot(
12+
known != 0,
13+
ToManagedDataLane(),
14+
ToManagedDisplayPort(),
15+
supports_pd != 0,
16+
Power.FromWatts(max_charge_watts),
17+
usb_a_high_power != 0);
18+
}
19+
20+
private readonly FrameworkDotnet.Enums.FrameworkUsbCDataLane ToManagedDataLane()
21+
{
22+
return data_lane switch
23+
{
24+
FrameworkUsbCDataLane.Unknown => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Unknown,
25+
FrameworkUsbCDataLane.Usb2 => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Usb2,
26+
FrameworkUsbCDataLane.Usb32 => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Usb32,
27+
FrameworkUsbCDataLane.Usb32Gen2x1 => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Usb32Gen2x1,
28+
FrameworkUsbCDataLane.Usb32Gen2x2 => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Usb32Gen2x2,
29+
FrameworkUsbCDataLane.Usb4 => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Usb4,
30+
FrameworkUsbCDataLane.Thunderbolt4 => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Thunderbolt4,
31+
_ => FrameworkDotnet.Enums.FrameworkUsbCDataLane.Unknown,
32+
};
33+
}
34+
35+
private readonly FrameworkDotnet.Enums.FrameworkDisplayPortCapability ToManagedDisplayPort()
36+
{
37+
return displayport switch
38+
{
39+
FrameworkDisplayPortCapability.None => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.None,
40+
FrameworkDisplayPortCapability.Dp14Hbr3 => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Dp14Hbr3,
41+
FrameworkDisplayPortCapability.Dp20 => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Dp20,
42+
FrameworkDisplayPortCapability.Dp20Uhbr10 => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Dp20Uhbr10,
43+
FrameworkDisplayPortCapability.Dp20Uhbr20 => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Dp20Uhbr20,
44+
FrameworkDisplayPortCapability.Dp21 => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Dp21,
45+
FrameworkDisplayPortCapability.Dp21Uhbr10 => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Dp21Uhbr10,
46+
FrameworkDisplayPortCapability.Dp21Uhbr20 => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Dp21Uhbr20,
47+
FrameworkDisplayPortCapability.Supported => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.Supported,
48+
_ => FrameworkDotnet.Enums.FrameworkDisplayPortCapability.None,
49+
};
50+
}
51+
}

framework-dotnet/Snapshots/FrameworkExpansionBaySnapshot.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ protected FrameworkExpansionBaySnapshot(FrameworkExpansionBaySnapshot snapshot,
8585
Vendor = snapshot.Vendor;
8686
RawPcieConfiguration = snapshot.RawPcieConfiguration;
8787
SerialNumber = snapshot.SerialNumber;
88+
HasUsbCPort = snapshot.HasUsbCPort;
89+
UsbCPort = snapshot.UsbCPort;
90+
UsbCCapability = snapshot.UsbCCapability;
8891
}
8992

9093
/// <summary>
@@ -132,8 +135,26 @@ protected FrameworkExpansionBaySnapshot(FrameworkExpansionBaySnapshot snapshot,
132135
/// </summary>
133136
internal FrameworkExpansionBayPcieConfiguration RawPcieConfiguration { get; init; }
134137

138+
/// <summary>
139+
/// Gets a value indicating whether the installed bay module exposes a USB-C port (the Framework 16 graphics
140+
/// modules do). When <see langword="true"/>, <see cref="UsbCPort"/> and <see cref="UsbCCapability"/> describe it.
141+
/// </summary>
142+
public bool HasUsbCPort { get; init; }
143+
144+
/// <summary>
145+
/// Gets the live USB Power Delivery state of the bay module's USB-C port, or <see langword="null"/> when the
146+
/// module has no USB-C port.
147+
/// </summary>
148+
public FrameworkPowerDeliveryPortStateSnapshot? UsbCPort { get; init; }
149+
150+
/// <summary>
151+
/// Gets the static capability of the bay module's USB-C port, or <see langword="null"/> when the module has no
152+
/// USB-C port.
153+
/// </summary>
154+
public FrameworkUsbCPortCapabilitySnapshot? UsbCCapability { get; init; }
155+
135156
public override string ToString()
136157
{
137-
return $"Expansion Bay Snapshot: Identity: {Identity}, Present: {IsPresent}, Enabled: {IsEnabled}, Fault: {HasFault}, Door Closed: {IsDoorClosed}, Board: {Board}, Vendor: {Vendor}, Serial Number: {SerialNumber}";
158+
return $"Expansion Bay Snapshot: Identity: {Identity}, Present: {IsPresent}, Enabled: {IsEnabled}, Fault: {HasFault}, Door Closed: {IsDoorClosed}, Board: {Board}, Vendor: {Vendor}, USB-C Port: {HasUsbCPort}, Serial Number: {SerialNumber}";
138159
}
139160
}

framework-dotnet/Snapshots/FrameworkExpansionCardSlotSnapshot.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Globalization;
1+
using System.Globalization;
22

33
using FrameworkDotnet.Enums;
44

@@ -25,7 +25,8 @@ public sealed record FrameworkExpansionCardSlotSnapshot
2525
/// <param name="powerDelivery">The full USB Power Delivery port state for this slot.</param>
2626
/// <param name="cardType">The identified card type.</param>
2727
/// <param name="cardConfidence">The confidence in the card-type identification.</param>
28-
public FrameworkExpansionCardSlotSnapshot(FrameworkModuleIdentity identity, FrameworkModuleBus bus, FrameworkModuleSlotKind slotKind, FrameworkModuleConfidence confidence, bool isPresent, int slotIndex, FrameworkModuleFlags flags, uint vendorId, uint productId, int boardId, FrameworkPowerDeliveryPortStateSnapshot powerDelivery, FrameworkExpansionCardType cardType, FrameworkModuleConfidence cardConfidence)
28+
/// <param name="capability">The static, board-defined USB-C capability of this slot.</param>
29+
public FrameworkExpansionCardSlotSnapshot(FrameworkModuleIdentity identity, FrameworkModuleBus bus, FrameworkModuleSlotKind slotKind, FrameworkModuleConfidence confidence, bool isPresent, int slotIndex, FrameworkModuleFlags flags, uint vendorId, uint productId, int boardId, FrameworkPowerDeliveryPortStateSnapshot powerDelivery, FrameworkExpansionCardType cardType, FrameworkModuleConfidence cardConfidence, FrameworkUsbCPortCapabilitySnapshot capability)
2930
{
3031
Identity = identity;
3132
Bus = bus;
@@ -40,6 +41,7 @@ public FrameworkExpansionCardSlotSnapshot(FrameworkModuleIdentity identity, Fram
4041
PowerDelivery = powerDelivery;
4142
CardType = cardType;
4243
CardConfidence = cardConfidence;
44+
Capability = capability;
4345
}
4446

4547
/// <summary>
@@ -107,8 +109,14 @@ public FrameworkExpansionCardSlotSnapshot(FrameworkModuleIdentity identity, Fram
107109
/// </summary>
108110
public FrameworkModuleConfidence CardConfidence { get; init; }
109111

112+
/// <summary>
113+
/// Gets the static, board-defined USB-C capability of this slot (data lane, DisplayPort version, charging),
114+
/// independent of the live <see cref="PowerDelivery"/> negotiation.
115+
/// </summary>
116+
public FrameworkUsbCPortCapabilitySnapshot Capability { get; init; }
117+
110118
public override string ToString()
111119
{
112-
return $"Expansion Card Slot {SlotIndex.ToString(CultureInfo.InvariantCulture)}: Present: {IsPresent}, Card Type: {CardType}, Identity: {Identity}, Confidence: {Confidence}, Power Delivery: [{PowerDelivery}]";
120+
return $"Expansion Card Slot {SlotIndex.ToString(CultureInfo.InvariantCulture)}: Present: {IsPresent}, Card Type: {CardType}, Identity: {Identity}, Confidence: {Confidence}, Capability: [{Capability}], Power Delivery: [{PowerDelivery}]";
113121
}
114122
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Globalization;
2+
3+
using FrameworkDotnet.Enums;
4+
5+
using UnitsNet;
6+
7+
namespace FrameworkDotnet.Snapshots;
8+
9+
/// <summary>
10+
/// Represents the static, board-defined USB-C capability of a numbered expansion card slot — data lane,
11+
/// DisplayPort version, and charging support. Sourced from Framework's published per-platform expansion-card
12+
/// matrices, not from the live Power Delivery negotiation (which is exposed separately as
13+
/// <see cref="FrameworkPowerDeliveryPortStateSnapshot"/>).
14+
/// </summary>
15+
public sealed record FrameworkUsbCPortCapabilitySnapshot
16+
{
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="FrameworkUsbCPortCapabilitySnapshot"/> class.
19+
/// </summary>
20+
/// <param name="isDocumented">Whether a documented capability matrix covers this slot and platform.</param>
21+
/// <param name="dataLane">The USB-C data-lane capability.</param>
22+
/// <param name="displayPort">The DisplayPort alt-mode capability and version.</param>
23+
/// <param name="supportsPowerDelivery">Whether the slot supports USB Power Delivery charging.</param>
24+
/// <param name="maxChargePower">The maximum charge power, or zero when undocumented / not a charging port.</param>
25+
/// <param name="usbAHighPowerDraw">Whether the "higher power consumption" USB-A note applies to this slot.</param>
26+
public FrameworkUsbCPortCapabilitySnapshot(bool isDocumented, FrameworkUsbCDataLane dataLane, FrameworkDisplayPortCapability displayPort, bool supportsPowerDelivery, Power maxChargePower, bool usbAHighPowerDraw)
27+
{
28+
IsDocumented = isDocumented;
29+
DataLane = dataLane;
30+
DisplayPort = displayPort;
31+
SupportsPowerDelivery = supportsPowerDelivery;
32+
MaxChargePower = maxChargePower;
33+
UsbAHighPowerDraw = usbAHighPowerDraw;
34+
}
35+
36+
/// <summary>
37+
/// Gets a value indicating whether a documented capability matrix covers this slot and platform. When
38+
/// <see langword="false"/>, the remaining fields are defaults and the live PD state should be used alone.
39+
/// </summary>
40+
public bool IsDocumented { get; init; }
41+
42+
/// <summary>
43+
/// Gets the USB-C data-lane capability of the slot.
44+
/// </summary>
45+
public FrameworkUsbCDataLane DataLane { get; init; }
46+
47+
/// <summary>
48+
/// Gets the DisplayPort alt-mode capability and version of the slot.
49+
/// </summary>
50+
public FrameworkDisplayPortCapability DisplayPort { get; init; }
51+
52+
/// <summary>
53+
/// Gets a value indicating whether the slot supports USB Power Delivery charging. Slots that are wired for a
54+
/// fixed low-power limit (e.g. 900 mA) report <see langword="false"/>.
55+
/// </summary>
56+
public bool SupportsPowerDelivery { get; init; }
57+
58+
/// <summary>
59+
/// Gets the maximum charge power, or <see cref="Power.Zero"/> when the slot is not a charging port or the
60+
/// wattage is not documented in the source matrix.
61+
/// </summary>
62+
public Power MaxChargePower { get; init; }
63+
64+
/// <summary>
65+
/// Gets a value indicating whether the "higher power consumption" USB-A note applies to this slot.
66+
/// </summary>
67+
public bool UsbAHighPowerDraw { get; init; }
68+
69+
public override string ToString()
70+
{
71+
return $"USB-C Capability: Documented: {IsDocumented}, Data Lane: {DataLane}, DisplayPort: {DisplayPort}, Supports PD: {SupportsPowerDelivery}, Max Charge: {MaxChargePower.ToString(CultureInfo.InvariantCulture)}, USB-A High Power: {UsbAHighPowerDraw}";
72+
}
73+
}

0 commit comments

Comments
 (0)