typecd: Implement Cable DiscoveryComplete command

In order to run mode entry code, the PortManager needs to verify that
cable discovery parsing is complete. Implement the DiscoveryComplete
function for the Cable class and have the PortManager call it (via the
Port object) when it runs the mode entry code.

Also, update the CableTest code to:
- Use ScopedTempDir, which ensures the temp files are cleaned up after
  the test execution.
- Correct the SOP' plug alt mode paths to have a prefix for the SOP'
  plug device; this will lay the groundwork to update the test to check
  for "number_of_alternate_modes" once the patches which implement that
  parsing are submitted.

BUG=b:172097194
TEST=Unit tests still pass.

Change-Id: I98e4c33ff91b341cc2c3f38b7678d22ab4e315dc
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2601504
Reviewed-by: Mengqi Guo <mqg@chromium.org>
Reviewed-by: Prashant Malani <pmalani@chromium.org>
Commit-Queue: Prashant Malani <pmalani@chromium.org>
Tested-by: Prashant Malani <pmalani@chromium.org>
diff --git a/typecd/cable.cc b/typecd/cable.cc
index 8b90617..c2760c1 100644
--- a/typecd/cable.cc
+++ b/typecd/cable.cc
@@ -138,4 +138,8 @@
   return usb_speed == kUSBSuperSpeed31Gen1 || usb_speed == kUSBSuperSpeed31Gen2;
 }
 
+bool Cable::DiscoveryComplete() {
+  return num_alt_modes_ == alt_modes_.size();
+}
+
 }  // namespace typecd
diff --git a/typecd/cable.h b/typecd/cable.h
index c623daa..b3a4e5f 100644
--- a/typecd/cable.h
+++ b/typecd/cable.h
@@ -66,6 +66,12 @@
   // Check whether the cable supports Thunderbolt3 speed requirements.
   bool TBT3PDIdentityCheck();
 
+  // Check whether SOP' PD discovery is complete (and we have all the PD
+  // information that the kernel can provide). To determine this, we check
+  // whether the number of registered altmodes equals the |num_alt_modes_| value
+  // which is read from sysfs.
+  bool DiscoveryComplete();
+
  private:
   // Map representing all SOP' alternate modes.
   // The key is the index of the alternate mode as determined
diff --git a/typecd/cable_test.cc b/typecd/cable_test.cc
index ee46e7f..85a76fe 100644
--- a/typecd/cable_test.cc
+++ b/typecd/cable_test.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include <base/files/scoped_temp_dir.h>
 #include <base/strings/stringprintf.h>
 #include <gtest/gtest.h>
 
@@ -76,24 +77,33 @@
 // Check that calls of AddAltMode() done explicitly function correctly. Also
 // check that trying to add the same alt mode twice fails.
 TEST_F(CableTest, TestAltModeManualAddition) {
+  // Set up a temp dir.
+  base::ScopedTempDir scoped_temp_dir;
+  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
+  base::FilePath temp_dir = scoped_temp_dir.GetPath();
+
+  // Create the sysfs path for the cable and plug.
+  auto cable_path = temp_dir.Append(std::string("port0-cable"));
+  ASSERT_TRUE(base::CreateDirectory(cable_path));
   Cable cable((base::FilePath(kFakePort0CableSysPath)));
 
-  // Set up fake sysfs paths.
-  base::FilePath temp_dir;
-  ASSERT_TRUE(base::CreateNewTempDirectory("", &temp_dir));
+  // Create sysfs path for SOP' plug.
+  auto sop_plug_path = temp_dir.Append(std::string("port0-plug0"));
+  ASSERT_TRUE(base::CreateDirectory(sop_plug_path));
 
+  // TODO(b/172097194): Modify the test to check for DiscoveryComplete().
+
+  // Set up fake sysfs paths for alternate modes.
   std::string mode0_dirname =
       base::StringPrintf("port%d-plug0.%d", 0, kDPAltModeIndex);
-  auto mode0_path = temp_dir.Append(mode0_dirname);
+  auto mode0_path = sop_plug_path.Append(mode0_dirname);
   ASSERT_TRUE(CreateFakeAltMode(mode0_path, kDPSVID, kDPVDO, kDPVDOIndex));
-
   EXPECT_TRUE(cable.AddAltMode(mode0_path));
 
   std::string mode1_dirname =
       base::StringPrintf("port%d-plug0.%d", 0, kTBTAltModeIndex);
-  auto mode1_path = temp_dir.Append(mode1_dirname);
+  auto mode1_path = sop_plug_path.Append(mode1_dirname);
   ASSERT_TRUE(CreateFakeAltMode(mode1_path, kTBTSVID, kTBTVDO, kTBTVDOIndex));
-
   EXPECT_TRUE(cable.AddAltMode(mode1_path));
   // Trying to add an existing alt mode again should fail.
   EXPECT_FALSE(cable.AddAltMode(mode1_path));
diff --git a/typecd/port.cc b/typecd/port.cc
index e9eff1a..a5d95ce 100644
--- a/typecd/port.cc
+++ b/typecd/port.cc
@@ -308,4 +308,13 @@
   return false;
 }
 
+bool Port::IsCableDiscoveryComplete() {
+  if (!cable_) {
+    LOG(INFO) << "Trying to check discovery complete for a non-existent cable.";
+    return false;
+  }
+
+  return cable_->DiscoveryComplete();
+}
+
 }  // namespace typecd
diff --git a/typecd/port.h b/typecd/port.h
index d455662..16b2ecb 100644
--- a/typecd/port.h
+++ b/typecd/port.h
@@ -66,6 +66,13 @@
   // returns true.
   bool IsPartnerDiscoveryComplete();
 
+  // Return true when all PD discovery information (PD Identity VDOs, all
+  // Discover Mode data) for a cable has been processed.
+  //
+  // NOTE: Any mode entry decision logic should only run if this function
+  // returns true.
+  bool IsCableDiscoveryComplete();
+
  private:
   friend class PortTest;
   FRIEND_TEST(PortTest, TestBasicAdd);
diff --git a/typecd/port_manager.cc b/typecd/port_manager.cc
index 8b85c99..d94a387 100644
--- a/typecd/port_manager.cc
+++ b/typecd/port_manager.cc
@@ -143,8 +143,8 @@
     return;
   }
 
-  // TODO(b/152251292): Check for Cable Discovery complete too.
-  if (!port->IsPartnerDiscoveryComplete()) {
+  if (!port->IsPartnerDiscoveryComplete() ||
+      !port->IsCableDiscoveryComplete()) {
     LOG(WARNING)
         << "Can't switch modes Partner/Cable discovery not complete for port "
         << port_num;