power: fix USB power supply type on newer kernels
When initializing a PowerStatus::Port from sysfs, use both the type and
usb_type files to determine the power supply type.
Some drivers in newer kernels (4.19+) report a static type of USB, and
separately report all supported connection types in usb_type, with the
active value in brackets.
Powerd was already partially updated to handle this new file in
crrev.com/c/1684359, but only for dual-role ports. Rework this fix so
that port.type takes both the type and usb_type files into account. To
maintain compatibility across kernel versions, "USB_" is prefixed to the
usb_type when appropriate.
Added a new unit test for usb_type that partially borrows from the
DualRolePowerSources test. Removed one of the values from
kInvalidUsbTypeValues that isn't actually invalid.
BUG=b:204295196
TEST=cros_workon_make --test --install power_manager
TEST=Deploy to a Lenovo X1 Gen8 (reven board) and connect a high-power USB-C adapter.
TEST=In the settings, check that the power source shows as an AC adapter.
Change-Id: I1d507de6aa3cc8c1f9ab62e5373d30a02311faa9
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/3370303
Tested-by: Nicholas Bishop <nicholasbishop@google.com>
Reviewed-by: Prashant Malani <pmalani@chromium.org>
Reviewed-by: Mengqi Guo <mqg@chromium.org>
Reviewed-by: Benson Leung <bleung@google.com>
Commit-Queue: Nicholas Bishop <nicholasbishop@google.com>
diff --git a/power_manager/docs/power_supplies.md b/power_manager/docs/power_supplies.md
index 77c3785..350ada7 100644
--- a/power_manager/docs/power_supplies.md
+++ b/power_manager/docs/power_supplies.md
@@ -35,11 +35,17 @@
port on a pre-USB-C device.
* `USB`, `USB_ACA`, `USB_CDP`, `USB_DCP` - A low-power USB BC1.2 power
supply. USB-C ports with nothing connected to them may report a `USB`
- type in conjunction with an `online` value of `0`.
+ type in conjunction with an `online` value of `0`. Note that some
+ drivers use a static type of `USB` and report the active connection in
+ `usb_type`, described below.
* `USB_C`, `USB_PD` - A USB Type-C power supply.
* `USB_PD_DRP` - A dual-role USB Type-C device (i.e. one capable of either
delivering or receiving power).
* `BrickID` - An Apple legacy USB charger. May be either USB-A or USB-C.
+* `usb_type` is used by some drivers in newer kernels (4.19+) that have a
+ `type` of USB to report all supported connection types, with the active
+ connection value in brackets. For example, a `usb_type` of `C [PD] PD_PPS`
+ is the same as a `type` of `USB_PD`.
* `online` typically describes whether anything is connected to the port; a
value of `1` indicates a connection. If `type` is `USB_PD_DRP`, an `online`
value of `0` indicates that a dual-role device is connected but that it is
diff --git a/power_manager/powerd/system/power_supply.cc b/power_manager/powerd/system/power_supply.cc
index ad7e78c..0a593a3 100644
--- a/power_manager/powerd/system/power_supply.cc
+++ b/power_manager/powerd/system/power_supply.cc
@@ -121,19 +121,35 @@
base::CompareCase::SENSITIVE);
}
-// Returns true if |type|, a power supply type read from a "type" file in
-// sysfs, indicates USB_PD_DRP, meaning a USB Power Delivery Dual Role Port.
-bool IsDualRoleType(const std::string& type, const base::FilePath& path) {
- // 4.19+ kernels have the type as just "USB", and an extra usb_type file
- // in the form:
- // Unknown SDP DCP CDP C PD [PD_DRP] BrickID
- if (type == PowerSupply::kUsbType) {
- std::string usb_type;
- if (ReadBracketSelectedString(path, "usb_type", &usb_type))
- return IsPdDrpType(usb_type);
- }
+// Returns the type of connection for the power supply. If the type
+// cannot be read, kUnknownType is returned.
+std::string ReadPowerSupplyType(const base::FilePath& path) {
+ std::string type;
+ if (!ReadAndTrimString(path, "type", &type))
+ return PowerSupply::kUnknownType;
- return IsPdDrpType(type);
+ if (type != PowerSupply::kUsbType)
+ return type;
+
+ // Some drivers in newer kernels (4.19+) report a static type of USB,
+ // and separately report all supported connection types in a usb_type
+ // file, with the active value in brackets. For example:
+ // "Unknown SDP DCP CDP C PD [PD_DRP] BrickID".
+ std::string usb_type;
+ if (!ReadBracketSelectedString(path, "usb_type", &usb_type))
+ return PowerSupply::kUsbType;
+
+ // The exact type is unknown, but we still know it's USB.
+ if (usb_type == PowerSupply::kUnknownType || usb_type.empty())
+ return PowerSupply::kUsbType;
+
+ // For compatibility with the old dynamic type, prepend "USB_"
+ // to the type. The only exception is for the BrickId type,
+ // which is unprefixed.
+ if (usb_type != PowerSupply::kBrickIdType)
+ usb_type = "USB_" + usb_type;
+
+ return usb_type;
}
// Returns true if |path|, a sysfs directory, corresponds to an external
@@ -947,11 +963,11 @@
status->supports_dual_role_devices = true;
// An "Unknown" type indicates a sink-only device that can't supply power.
- ReadAndTrimString(path, "type", &port->type);
+ port->type = ReadPowerSupplyType(path);
if (port->type == kUnknownType)
return;
- const bool dual_role_connected = IsDualRoleType(port->type, path);
+ const bool dual_role_connected = IsPdDrpType(port->type);
// If "online" is 0, nothing is connected unless it is USB_PD_DRP, in which
// case a value of 0 indicates we're connected to a dual-role device but not
diff --git a/power_manager/powerd/system/power_supply_test.cc b/power_manager/powerd/system/power_supply_test.cc
index 692f6c3..0769fe8 100644
--- a/power_manager/powerd/system/power_supply_test.cc
+++ b/power_manager/powerd/system/power_supply_test.cc
@@ -39,6 +39,7 @@
const char* const kMainsType = PowerSupply::kMainsType;
const char* const kBatteryType = PowerSupply::kBatteryType;
const char* const kUsbType = PowerSupply::kUsbType;
+const char* const kUsbPdType = PowerSupply::kUsbPdType;
const char* const kUsbPdDrpType = PowerSupply::kUsbPdDrpType;
const char* const kUnknownType = PowerSupply::kUnknownType;
@@ -65,6 +66,15 @@
// Starting value used by |power_supply_| as "now".
const base::TimeTicks kStartTime = base::TimeTicks::FromInternalValue(1000);
+// Invalid values for usb_type.
+const char* kInvalidUsbTypeValues[] = {
+ "Unknown SDP DCP CDP C PD PD_DRP BrickID",
+ "Unknown SDP DCP CDP C PD [] BrickID",
+ "Unknown SDP DCP CDP C PD ]PD_DRP[ BrickID",
+ "[",
+ "]",
+ "[]"};
+
class TestObserver : public PowerSupplyObserver {
public:
explicit TestObserver(PowerSupply* power_supply)
@@ -463,6 +473,39 @@
EXPECT_FALSE(power_status.supports_dual_role_devices);
}
+// Test that the supply type is correctly read from usb_type when present.
+TEST_F(PowerSupplyTest, LinePowerWithUsbType) {
+ WriteDefaultValues(PowerSource::AC);
+ UpdatePowerSourceAndBatteryStatus(PowerSource::AC, kUsbType, kCharging);
+ Init();
+
+ // With the type set to USB and no usb_type set, the supply is treated
+ // as a low-power USB connection.
+ PowerStatus power_status;
+ ASSERT_TRUE(UpdateStatus(&power_status));
+ EXPECT_EQ(kUsbType, power_status.ports[0].type);
+ EXPECT_EQ(PowerSupplyProperties_ExternalPower_USB,
+ power_status.external_power);
+
+ // With usb_type set to PD, the supply is treated as AC.
+ WriteValue(ac_dir_, "usb_type", "C [PD] PD_PPS");
+ ASSERT_TRUE(UpdateStatus(&power_status));
+ EXPECT_EQ(kUsbPdType, power_status.ports[0].type);
+ EXPECT_EQ(PowerSupplyProperties_ExternalPower_AC,
+ power_status.external_power);
+
+ // Invalid usb_type values should report as low-power USB.
+ for (size_t i = 0; i < std::size(kInvalidUsbTypeValues); ++i) {
+ const char* kType = kInvalidUsbTypeValues[i];
+ SCOPED_TRACE(kType);
+ WriteValue(ac_dir_, "usb_type", kType);
+ ASSERT_TRUE(UpdateStatus(&power_status));
+ ASSERT_EQ(kUsbType, power_status.ports[0].type);
+ EXPECT_EQ(PowerSupplyProperties_ExternalPower_USB,
+ power_status.external_power);
+ }
+}
+
// Tests that when multiple line power sources are reported (e.g. because both
// the PD and ACPI drivers are present), powerd favors the non-Mains source.
TEST_F(PowerSupplyTest, MultipleLinePowerSources) {
@@ -730,14 +773,6 @@
ASSERT_EQ(Role::DUAL_ROLE, status.ports[1].role);
// USB should not report as dual role if usb_type is craaaazy.
- const char* const kInvalidUsbTypeValues[] = {
- "Unknown SDP DCP CDP C PD PD_DRP BrickID",
- "Unknown SDP DCP CDP C PD [] BrickID",
- "Unknown SDP DCP CDP C [PD] PD_DRP BrickID",
- "Unknown SDP DCP CDP C PD ]PD_DRP[ BrickID",
- "[",
- "]",
- "[]"};
for (size_t i = 0; i < std::size(kInvalidUsbTypeValues); ++i) {
const char* kType = kInvalidUsbTypeValues[i];
SCOPED_TRACE(kType);