| // 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 |