blob: 35b6a6f381f02c9fa9fa1565e11766f320dc628e [file] [log] [blame]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "typecd/port.h"
#include <vector>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/task/single_thread_task_runner.h>
#include <re2/re2.h>
#include "typecd/pd_vdo_constants.h"
#include "typecd/peripheral.h"
#include "typecd/utils.h"
namespace {
constexpr char kDualRoleRegex[] = R"(\[(\w+)\])";
// Give a delay before reporting metrics to finish PD negotiation. Calculated
// as follows:
// nCapsCount (50) * tTypeCSendSourceCap (100ms ~ 200ms)
// which gives 10000ms or 10 sec to officially declare non-PD.
constexpr uint32_t kPDNegotiationDelayMs = 10000;
} // namespace
namespace typecd {
Port::Port(const base::FilePath& syspath, int port_num)
: syspath_(syspath),
port_num_(port_num),
user_active_on_mode_entry_(false),
current_mode_(TypeCMode::kNone),
metrics_reported_(false),
supports_usb4_(true),
data_role_(DataRole::kNone),
power_role_(PowerRole::kNone),
panel_(Panel::kUnknown),
horizontal_position_(HorizontalPosition::kUnknown),
vertical_position_(VerticalPosition::kUnknown) {
PortChanged();
ParsePhysicalLocation();
LOG(INFO) << "Port " << port_num_ << " enumerated.";
}
void Port::AddCable(const base::FilePath& path) {
if (cable_) {
LOG(WARNING) << "Cable already exists for port " << port_num_;
return;
}
cable_ = std::make_unique<Cable>(path);
LOG(INFO) << "Cable enumerated for port " << port_num_;
}
void Port::RemoveCable() {
if (!cable_) {
LOG(WARNING) << "No partner present for port " << port_num_;
return;
}
cable_.reset();
LOG(INFO) << "Cable removed for port " << port_num_;
}
void Port::AddCablePlug(const base::FilePath& syspath) {
if (!cable_) {
LOG(WARNING) << "No cable present for port " << port_num_;
return;
}
cable_->RegisterCablePlug(syspath);
}
void Port::AddPartner(const base::FilePath& path) {
if (partner_) {
LOG(WARNING) << "Partner already exists for port " << port_num_;
return;
}
partner_ = std::make_unique<Partner>(path, this);
LOG(INFO) << "Partner enumerated for port " << port_num_;
}
void Port::RemovePartner() {
if (!partner_) {
LOG(WARNING) << "No partner present for port " << port_num_;
return;
}
partner_.reset();
// Since a partner is disconnected, we should reset the |metrics_reported_|
// flag so that metrics can be reported on the next connect.
metrics_reported_ = false;
LOG(INFO) << "Partner removed for port " << port_num_;
}
bool Port::HasPartner() {
return partner_.get() != nullptr;
}
void Port::AddRemovePartnerAltMode(const base::FilePath& path, bool added) {
if (!partner_) {
LOG(WARNING) << "Trying to add alt mode for non-existent partner on port "
<< port_num_;
return;
}
if (added) {
if (!partner_->AddAltMode(path))
LOG(ERROR) << "Failed to add alt mode for port " << port_num_
<< " at path " << path;
} else {
partner_->RemoveAltMode(path);
}
}
void Port::AddRemovePartnerPowerProfile(bool added) {
if (!partner_) {
LOG(WARNING)
<< "Trying to modify power profile for non-existent partner on port "
<< port_num_;
return;
}
if (added)
partner_->AddPowerProfile();
else
partner_->RemovePowerProfile();
}
void Port::AddCableAltMode(const base::FilePath& path) {
if (!cable_) {
LOG(WARNING) << "Trying to add alt mode for non-existent cable on port "
<< port_num_;
return;
}
if (!cable_->AddAltMode(path)) {
LOG(ERROR) << "Failed to add SOP' alt mode for port " << port_num_
<< " at path " << path;
}
}
void Port::PartnerChanged() {
if (!partner_) {
LOG(WARNING) << "Trying to update a non-existent partner on port "
<< port_num_;
return;
}
partner_->UpdatePDInfoFromSysfs();
}
void Port::PortChanged() {
ParseDataRole();
ParsePowerRole();
}
std::vector<AltMode*> Port::GetAltModes(uint32_t recipient) {
switch (recipient) {
case ((uint32_t)Recipient::kPartner):
if (partner_)
return partner_->GetAltModes();
break;
case ((uint32_t)Recipient::kCable):
if (cable_)
return cable_->GetAltModes();
break;
default:
break;
}
return {};
}
std::vector<uint32_t> Port::GetIdentity(uint32_t recipient) {
switch (recipient) {
case ((uint32_t)Recipient::kPartner):
if (partner_)
return partner_->GetIdentity();
break;
case ((uint32_t)Recipient::kCable):
if (cable_)
return cable_->GetIdentity();
break;
default:
break;
}
return {};
}
PDRevision Port::GetPDRevision(uint32_t recipient) {
switch (recipient) {
case ((uint32_t)Recipient::kPartner):
if (partner_)
return partner_->GetPDRevision();
break;
case ((uint32_t)Recipient::kCable):
if (cable_)
return cable_->GetPDRevision();
break;
default:
break;
}
return PDRevision::kNone;
}
DataRole Port::GetDataRole() {
return data_role_;
}
PowerRole Port::GetPowerRole() {
return power_role_;
}
Panel Port::GetPanel() {
return panel_;
}
HorizontalPosition Port::GetHorizontalPosition() {
return horizontal_position_;
}
VerticalPosition Port::GetVerticalPosition() {
return vertical_position_;
}
bool Port::CanEnterDPAltMode(bool* invalid_dpalt_cable_ptr) {
if (!partner_->SupportsDp())
return false;
bool partner_is_receptacle = false;
for (int i = 0; i < partner_->GetNumAltModes(); i++) {
auto alt_mode = partner_->GetAltMode(i);
if (!alt_mode || alt_mode->GetSVID() != kDPAltModeSID)
continue;
if (alt_mode->GetVDO() & kDPModeReceptacle)
partner_is_receptacle = true;
}
// Partner supports DPAltMode. Clear the invalid_dpalt_cable_ptr flag to
// assume the cable can support driving displays.
if (invalid_dpalt_cable_ptr != nullptr)
*invalid_dpalt_cable_ptr = false;
// Only check the cable if the partner is a receptacle.
if (partner_is_receptacle) {
// Non-emarked cables will not prevent mode entry, but may not support
// displays. Return true, but set the invalid_dpalt_cable_ptr flag to
// warn the user.
if (!cable_ || !(cable_->GetIdHeaderVDO())) {
if (invalid_dpalt_cable_ptr != nullptr)
*invalid_dpalt_cable_ptr = true;
return true;
}
// If the cable is emarked, check it's identity to determine mode entry
// support. If the idenity check fails, prevent mode entry and set the
// invalid_dpalt_cable_ptr flag to warn the user.
if (!cable_->DPPDIdentityCheck()) {
if (invalid_dpalt_cable_ptr != nullptr)
*invalid_dpalt_cable_ptr = true;
return false;
}
}
// Partner and cable support DPAltMode entry.
return true;
}
// Mode entry check for TBT compatibility mode.
// Ref:
// USB Type-C Connector Spec, release 2.0
// Figure F-1.
ModeEntryResult Port::CanEnterTBTCompatibilityMode() {
if (!supports_usb4_) {
LOG(ERROR) << "TBT Compat mode not supported on port: " << port_num_;
return ModeEntryResult::kPortError;
}
// Check if the partner supports Modal Operation
// Ref:
// USB PD spec, rev 3.0, v2.0.
// Table 6-29
if (!partner_) {
LOG(ERROR) << "No partner object registered, can't enter TBT Compat mode.";
return ModeEntryResult::kPartnerError;
}
auto partner_idh = partner_->GetIdHeaderVDO();
if (!(partner_idh & kIDHeaderVDOModalOperationBitField)) {
return ModeEntryResult::kPartnerError;
}
// Check if the partner supports TBT compatibility mode.
if (!partner_->SupportsTbt()) {
LOG(INFO) << "TBT Compat mode not supported by partner.";
return ModeEntryResult::kPartnerError;
}
if (!cable_) {
LOG(ERROR) << "No cable object registered, can't enter TBT Compat mode.";
return ModeEntryResult::kCableError;
}
// Check if the Cable meets TBT3 speed requirements.
// NOTE: Since we aren't configuring the TBT3 entry speed, we don't
// need to check for the existence of TBT3 alt mode in the SOP' discovery.
if (!cable_->TBT3PDIdentityCheck())
return ModeEntryResult::kCableError;
return ModeEntryResult::kSuccess;
}
// Follow the USB4 entry checks as per:
// Figure 5-1: USB4 Discovery and Entry Flow Model
// USB Type-C Cable & Connector Spec Rel 2.0.
ModeEntryResult Port::CanEnterUSB4() {
if (!supports_usb4_) {
LOG(ERROR) << "USB4 not supported on port: " << port_num_;
return ModeEntryResult::kPortError;
}
if (!partner_) {
LOG(ERROR) << "Attempting USB4 entry without a registered partner on port: "
<< port_num_;
return ModeEntryResult::kPartnerError;
}
if (!partner_->SupportsUsb4())
return ModeEntryResult::kPartnerError;
if (!cable_) {
LOG(ERROR) << "Attempting USB4 entry without a registered cable on port: "
<< port_num_;
return ModeEntryResult::kCableError;
}
// Check if the cable meets USB4 requirements.
if (!cable_->USB4PDIdentityCheck()) {
LOG(ERROR) << "Cable does not support USB4 entry on port: " << port_num_;
return ModeEntryResult::kCableError;
}
return ModeEntryResult::kSuccess;
}
bool Port::IsPartnerDiscoveryComplete() {
if (!partner_) {
LOG(INFO)
<< "Trying to check discovery complete for a non-existent partner.";
return false;
}
return partner_->DiscoveryComplete();
}
bool Port::PartnerSupportsPD() {
if (!partner_) {
LOG(INFO) << "Trying to check supports PD for a non-existent partner.";
return false;
}
return partner_->GetSupportsPD();
}
bool Port::IsCableAltModePresent(uint16_t altmode_sid) {
return cable_->IsAltModeSVIDPresent(altmode_sid);
}
bool Port::IsCableDiscoveryComplete() {
if (!cable_) {
LOG(INFO) << "Trying to check discovery complete for a non-existent cable.";
return false;
}
return cable_->DiscoveryComplete();
}
void Port::ParseDataRole() {
DataRole role = DataRole::kNone;
std::string role_str;
std::string sysfs_str;
auto path = syspath_.Append("data_role");
if (!base::ReadFileToString(path, &sysfs_str)) {
LOG(ERROR) << "Couldn't read sysfs path " << path;
goto end;
}
// First check for a dual role port, in which case the current role is in
// box-brackets. For example: [host] device
if (!RE2::PartialMatch(sysfs_str, kDualRoleRegex, &role_str)) {
LOG(INFO)
<< "Couldn't determine role, assuming DRP(Dual Role Port) for port "
<< port_num_;
}
if (role_str == "")
role_str = sysfs_str;
base::TrimWhitespaceASCII(role_str, base::TRIM_ALL, &role_str);
if (role_str == "host")
role = DataRole::kHost;
else if (role_str == "device")
role = DataRole::kDevice;
end:
data_role_ = role;
}
void Port::ParsePowerRole() {
PowerRole role = PowerRole::kNone;
std::string role_str;
std::string sysfs_str;
auto path = syspath_.Append("power_role");
if (!base::ReadFileToString(path, &sysfs_str)) {
LOG(ERROR) << "Couldn't read sysfs path " << path;
goto end;
}
// First check for a dual role port, in which case the current role is in
// box-brackets. For example: [source] sink
if (!RE2::PartialMatch(sysfs_str, kDualRoleRegex, &role_str)) {
LOG(INFO)
<< "Couldn't determine role, assuming DRP(Dual Role Port) for port "
<< port_num_;
}
if (role_str == "")
role_str = sysfs_str;
base::TrimWhitespaceASCII(role_str, base::TRIM_ALL, &role_str);
if (role_str == "source")
role = PowerRole::kSource;
else if (role_str == "sink")
role = PowerRole::kSink;
end:
power_role_ = role;
}
void Port::ParsePhysicalLocation() {
Panel panel = Panel::kUnknown;
HorizontalPosition hpos = HorizontalPosition::kUnknown;
VerticalPosition vpos = VerticalPosition::kUnknown;
std::string panel_str, hpos_str, vpos_str;
auto panel_path = syspath_.Append("physical_location/panel");
auto hpos_path = syspath_.Append("physical_location/horizontal_position");
auto vpos_path = syspath_.Append("physical_location/vertical_position");
if (!base::ReadFileToString(panel_path, &panel_str) ||
!base::ReadFileToString(hpos_path, &hpos_str) ||
!base::ReadFileToString(vpos_path, &vpos_str)) {
// No error logged since kernel v5.4 or older does not expose
// physical_location to sysfs.
goto end;
}
base::TrimWhitespaceASCII(panel_str, base::TRIM_ALL, &panel_str);
if (panel_str == "top")
panel = Panel::kTop;
else if (panel_str == "bottom")
panel = Panel::kBottom;
else if (panel_str == "left")
panel = Panel::kLeft;
else if (panel_str == "right")
panel = Panel::kRight;
else if (panel_str == "front")
panel = Panel::kFront;
else if (panel_str == "back")
panel = Panel::kBack;
base::TrimWhitespaceASCII(hpos_str, base::TRIM_ALL, &hpos_str);
if (hpos_str == "left")
hpos = HorizontalPosition::kLeft;
else if (hpos_str == "center")
hpos = HorizontalPosition::kCenter;
else if (hpos_str == "right")
hpos = HorizontalPosition::kRight;
base::TrimWhitespaceASCII(vpos_str, base::TRIM_ALL, &vpos_str);
if (vpos_str == "upper")
vpos = VerticalPosition::kUpper;
else if (vpos_str == "center")
vpos = VerticalPosition::kCenter;
else if (vpos_str == "lower")
vpos = VerticalPosition::kLower;
end:
panel_ = panel;
horizontal_position_ = hpos;
vertical_position_ = vpos;
}
bool Port::CableLimitingUSBSpeed(bool tbt3_alt_mode) {
if (!partner_ || !cable_)
return false;
// Initialize cable speeds from USB PD identity response.
auto cable_speed = cable_->GetProductTypeVDO1() & kUSBSpeedBitMask;
auto partner_speed = partner_->GetProductTypeVDO1() & kUSBSpeedBitMask;
// Check for cable product type.
auto cable_type =
(cable_->GetIdHeaderVDO() >> kIDHeaderVDOProductTypeBitOffset) &
kIDHeaderVDOProductTypeMask;
if (cable_type != kIDHeaderVDOProductTypeCableActive &&
cable_type != kIDHeaderVDOProductTypeCablePassive)
return false;
// Check for captive cable.
if (IsCaptiveCableConnected())
return false;
// In Thunderbolt 3 alternate mode, the partner will support 40 Gbps.
// Otherwise, check partner type to confirm product_type_vdo1 speed is
// accurate.
if (tbt3_alt_mode) {
partner_speed = kUSB40SuperSpeedGen3;
} else {
// Check for partner product type and PD revision.
auto partner_type =
(partner_->GetIdHeaderVDO() >> kIDHeaderVDOProductTypeBitOffset) &
kIDHeaderVDOProductTypeMask;
auto partner_pd_revision = partner_->GetPDRevision();
if (partner_pd_revision == PDRevision::k20) {
// PD rev 2.0, v 1.3
// Table 6-24 Product Types (UFP)
// Only AMAs use a product type VDO.
if (partner_type != kIDHeaderVDOProductTypeUFPAMA)
return false;
} else if (partner_pd_revision == PDRevision::k30) {
// PD rev 3.0, v 2.0
// Table 6-30 Product Types (UFP)
// Only PDUSB hubs, PDUSB peripherals and AMAs use a product type VDO with
// USB speed.
if (partner_type != kIDHeaderVDOProductTypeUFPHub &&
partner_type != kIDHeaderVDOProductTypeUFPPeripheral &&
partner_type != kIDHeaderVDOProductTypeUFPAMA)
return false;
} else if (partner_pd_revision == PDRevision::k31 ||
partner_pd_revision == PDRevision::k32) {
// PD rev 3.1, v1.8 Table 6-36 Product Types (UFP)
// PD rev 3.2, v1.0 Table 6-35 Product Types (UFP)
// Only PDUSB hubs, PDUSB peripherals use a product type VDO with
// USB speed.
if (partner_type != kIDHeaderVDOProductTypeUFPHub &&
partner_type != kIDHeaderVDOProductTypeUFPPeripheral) {
return false;
}
} else {
// Return false on undetermined PD revision.
return false;
}
// In USB PD Rev 2.0 and 3.0, 0x3 in the AMA VDO USB Highest speed field
// represents billboard only, and should not be compared against cable
// speed.
if ((partner_pd_revision == PDRevision::k20 ||
partner_pd_revision == PDRevision::k30) &&
partner_type == kIDHeaderVDOProductTypeUFPAMA &&
partner_speed == kAMAVDOUSBSpeedBillboard) {
return false;
}
}
// Check for TBT3 cables supporting USB4 speeds.
// USB Type-C Cable & Connector spec release 2.1
// Figure 5-1 USB4 Discovery and Entry Flow Model (passive cables)
// Section 5.4.3.2 (active cables)
for (int i = 0; i < cable_->GetNumAltModes(); i++) {
auto alt_mode = cable_->GetAltMode(i);
if (!alt_mode || alt_mode->GetSVID() != kTBTAltModeVID)
continue;
// Return false after finding TBT3 cable in TBT3 mode.
if (tbt3_alt_mode)
return false;
auto cable_tbt_mode =
(alt_mode->GetVDO() >> kTBT3CableDiscModeVDOModeOffset) &
kTBT3CableDiscModeVDOModeMask;
auto cable_tbt_speed =
(alt_mode->GetVDO() >> kTBT3CableDiscModeVDOSpeedOffset) &
kTBT3CableDiscModeVDOSpeedMask;
auto cable_tbt_rounded_support =
(alt_mode->GetVDO() >> kTBT3CableDiscModeVDORoundedSupportOffset) &
kTBT3CableDiscModeVDORoundedSupportMask;
if (cable_tbt_mode == kTBT3CableDiscModeVDOModeTBT &&
cable_tbt_speed == kTBT3CableDiscModeVDOSpeed10G20G &&
(cable_type == kIDHeaderVDOProductTypeCablePassive ||
(cable_type == kIDHeaderVDOProductTypeCableActive &&
cable_tbt_rounded_support ==
kTBT3CableDiscModeVDO_3_4_Gen_Rounded_Non_Rounded)))
cable_speed = kUSB40SuperSpeedGen3;
break;
}
return partner_speed > cable_speed;
}
void Port::ReportPartnerMetrics(Metrics* metrics) {
if (!partner_) {
LOG(INFO) << "Trying to report metrics for non-existent partner.";
return;
}
partner_->ReportMetrics(metrics);
}
void Port::ReportCableMetrics(Metrics* metrics, bool captive) {
if (!cable_) {
LOG(INFO) << "Trying to report metrics for non-existent cable.";
return;
}
cable_->ReportMetrics(metrics, captive);
}
void Port::ReportPortMetrics(Metrics* metrics) {
if (!metrics || metrics_reported_)
return;
if (!IsCableDiscoveryComplete() || !IsPartnerDiscoveryComplete())
return;
// Check cable for tracking DPAltMode cable metrics.
bool invalid_dpalt_cable = false;
bool can_enter_dpalt_mode = CanEnterDPAltMode(&invalid_dpalt_cable);
bool cable_limiting_speed_mode =
CanEnterUSB4() != ModeEntryResult::kSuccess &&
CanEnterTBTCompatibilityMode() == ModeEntryResult::kSuccess;
if (CanEnterUSB4() == ModeEntryResult::kCableError)
metrics->ReportWrongCableError(WrongConfigurationMetric::kUSB4WrongCable);
else if (CanEnterTBTCompatibilityMode() == ModeEntryResult::kCableError)
metrics->ReportWrongCableError(WrongConfigurationMetric::kTBTWrongCable);
else if (can_enter_dpalt_mode && invalid_dpalt_cable)
metrics->ReportWrongCableError(WrongConfigurationMetric::kDPAltWrongCable);
else if (CableLimitingUSBSpeed(cable_limiting_speed_mode))
metrics->ReportWrongCableError(
WrongConfigurationMetric::kSpeedLimitingCable);
else
metrics->ReportWrongCableError(WrongConfigurationMetric::kNone);
metrics_reported_ = true;
return;
}
bool Port::GetDpEntryState(DpSuccessMetric& result) {
bool hpd;
if (!ec_util_->HpdState(port_num_, &hpd))
return false;
bool dp;
if (!ec_util_->DpState(port_num_, &dp))
return false;
if (dp) {
if (hpd)
result = DpSuccessMetric::kSuccessHpd;
else
result = DpSuccessMetric::kSuccessNoHpd;
} else {
result = DpSuccessMetric::kFail;
}
return true;
}
bool Port::GetModeEntryResult(ModeEntryMetric& result,
bool mode_entry_supported) {
result = ModeEntryMetric::kUnknown;
if (!mode_entry_supported) {
result = ModeEntryMetric::kModeEntryUnsupported;
return true;
}
switch (current_mode_) {
case TypeCMode::kNone:
result = ModeEntryMetric::kModeEntryNotAttempted;
break;
case TypeCMode::kDP:
bool dp;
if (!ec_util_->DpState(port_num_, &dp))
return false;
if (dp)
result = ModeEntryMetric::kDpSuccess;
else
result = ModeEntryMetric::kDpFailure;
break;
case TypeCMode::kTBT:
if (GetTbtDeviceCount() > tbt_device_count_)
result = ModeEntryMetric::kTbtSuccess;
else
result = ModeEntryMetric::kTbtFailure;
break;
case TypeCMode::kUSB4:
if (GetTbtDeviceCount() > tbt_device_count_)
result = ModeEntryMetric::kUsb4Success;
else
result = ModeEntryMetric::kUsb4Failure;
break;
default:
break;
}
return true;
}
void Port::ReportDpMetric(Metrics* metrics) {
DpSuccessMetric result;
if (!GetDpEntryState(result))
return;
metrics->ReportDpSuccess(result);
}
void Port::ReportQualityMetrics(Metrics* metrics, bool mode_entry_supported) {
if (!metrics)
return;
std::string boot_id;
if (!base::ReadFileToString(base::FilePath("/proc/sys/kernel/random/boot_id"),
&boot_id)) {
boot_id = "";
}
base::TrimWhitespaceASCII(boot_id, base::TRIM_ALL, &boot_id);
int vid = 0;
int pid = 0;
std::string usb2_id = "";
std::string usb3_id = "";
PartnerTypeMetric partner_type = PartnerTypeMetric::kOther;
if (partner_) {
vid = partner_->GetVendorId();
pid = partner_->GetProductId();
if (!DeviceInMetricsAllowlist(vid, pid)) {
vid = 0;
pid = 0;
}
partner_type = partner_->GetPartnerTypeMetric();
base::FilePath usb2_device, usb3_device;
if (!boot_id.empty() && partner_->GetUsbDevice(0, 480, &usb2_device))
usb2_id = GetConnectionId(boot_id, usb2_device);
if (!boot_id.empty() && partner_->GetUsbDevice(5000, 20000, &usb3_device))
usb3_id = GetConnectionId(boot_id, usb3_device);
}
CableSpeedMetric cable_speed = CableSpeedMetric::kOther;
if (cable_) {
cable_speed = cable_->GetCableSpeedMetric(IsCaptiveCableConnected());
}
ModeEntryMetric mode_entry;
GetModeEntryResult(mode_entry, mode_entry_supported);
metrics->ReportModeEntry(mode_entry);
metrics->ReportPdConnect(boot_id, usb2_id, usb3_id, vid, pid, partner_type,
cable_speed, mode_entry);
}
void Port::ReportMetrics(Metrics* metrics, bool mode_entry_supported) {
if (!metrics)
return;
ReportPartnerMetrics(metrics);
ReportCableMetrics(metrics, IsCaptiveCableConnected());
ReportQualityMetrics(metrics, mode_entry_supported);
if (mode_entry_supported)
ReportPortMetrics(metrics);
if (CanEnterDPAltMode(nullptr))
ReportDpMetric(metrics);
}
void Port::EnqueueMetricsTask(Metrics* metrics, bool mode_entry_supported) {
report_metrics_callback_.Reset(base::BindOnce(&Port::ReportMetrics,
base::Unretained(this), metrics,
mode_entry_supported));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, report_metrics_callback_.callback(),
base::Milliseconds(kPDNegotiationDelayMs));
}
void Port::CancelMetricsTask() {
report_metrics_callback_.Cancel();
}
bool Port::IsCaptiveCableConnected() {
if (!partner_)
return false;
// If cable enumerated, check cable plug type.
if (cable_) {
auto cable_plug_type =
(cable_->GetProductTypeVDO1() >> kCableVDO1VDOPlugTypeOffset) &
kCableVDO1VDOPlugTypeBitMask;
if (cable_plug_type == kCableVDO1VDOPlugTypeCaptive)
return true;
}
// Check partner ID Header VDO connector type.
auto partner_connector_type =
(partner_->GetIdHeaderVDO() >> kIDHeaderVDOConnectorTypeBitOffset) &
kIDHeaderVDOConnectorTypeMask;
if (partner_connector_type == kIDHeaderVDOConnectorTypePlug)
return true;
// Check partners DP alt mode connector type.
if (partner_->SupportsDp()) {
for (int i = 0; i < partner_->GetNumAltModes(); i++) {
auto alt_mode = partner_->GetAltMode(i);
if (!alt_mode || alt_mode->GetSVID() != kDPAltModeSID)
continue;
if (!(alt_mode->GetVDO() & kDPModeReceptacle))
return true;
}
}
return false;
}
} // namespace typecd