servo_v4: Support connecting servo-micro to servo v4 through a hub

Change the logic of checking the USB device topology, in order to
validate connecting servo-micro to servo v4 with or without a hub.

BUG=b:37473185
TEST=Connected servo-micro to v4 without a hub and started servod.
TEST=Connected servo-micro to v4 with a hub and started servod.

Change-Id: I8b52abf93a79c0dd9695da57475ff9181a6c0abf
Reviewed-on: https://chromium-review.googlesource.com/516462
Commit-Ready: Wai-Hong Tam <waihong@google.com>
Tested-by: Wai-Hong Tam <waihong@google.com>
Reviewed-by: Kevin Cheng <kevcheng@chromium.org>
diff --git a/servo/servo_postinit.py b/servo/servo_postinit.py
index 35d8a48..a916f09 100644
--- a/servo/servo_postinit.py
+++ b/servo/servo_postinit.py
@@ -90,21 +90,37 @@
     return self._usb_hierarchy.get((str(usb_device.bus),
                                     str(usb_device.address)))
 
-  def share_same_parent(self, usb_device1, usb_device2):
-    """Check if the given two USB devices share the same parent.
+  def share_same_hub(self, usb_servo, usb_candidate):
+    """Check if the given USB device shares the same hub with servo v4.
 
     Args:
-      usb_device1: usb.core.Device object.
-      usb_device2: usb.core.Device object.
+      usb_servo: usb.core.Device object of servo v4.
+      usb_candidate: usb.core.Device object of USB device canditate.
 
     Returns:
-      True if they share the same parent; otherwise, False.
+      True if they share the same hub; otherwise, False.
     """
-    usb_parent1 = self._get_parent_path(usb_device1)
-    usb_parent2 = self._get_parent_path(usb_device2)
-    return (usb_parent1 and usb_parent2
-            and usb_parent1 == usb_parent2)
+    usb_servo_parent = self._get_parent_path(usb_servo)
+    usb_candidate_parent = self._get_parent_path(usb_candidate)
 
+    if usb_servo_parent is None or usb_candidate_parent is None:
+      return False
+
+    # Check the hierarchy:
+    #   internal hub <-- servo v4 mcu
+    #         \--------- USB candidate
+    if usb_servo_parent == usb_candidate_parent:
+      return True
+
+    # Check the hierarchy:
+    #   internal hub <-- servo v4 mcu
+    #         \--------- external hub
+    #                          \--------- USB candidate
+    # Check having '.' to make sure it is one of the ports of the internal hub.
+    if usb_candidate_parent.startswith(usb_servo_parent + '.'):
+      return True
+
+    return False
 
 class BasePostInit(object):
   """Base Class for Post Init classes."""
@@ -254,7 +270,7 @@
       # The micro-servo and the STM chip of servo v4 share the same internal hub
       # on servo v4 board. Check the USB hierarchy to find the micro-servo
       # behind. Assume we have at most one servo micro behind the servo v4.
-      if usb_hierarchy.share_same_parent(servo_v4, servo_micro):
+      if usb_hierarchy.share_same_hub(servo_v4, servo_micro):
         self.prepend_config(self.SERVO_MICRO_CFG)
         self.add_servo_serial(servo_micro, self.servod.MICRO_SERVO_SERIAL)
         self.init_servo_interfaces(servo_micro)
@@ -265,7 +281,7 @@
     ccd_candidates = self.get_ccd_devices()
     for ccd in ccd_candidates:
       # Pick the proper CCD endpoint behind the servo v4.
-      if usb_hierarchy.share_same_parent(servo_v4, ccd):
+      if usb_hierarchy.share_same_hub(servo_v4, ccd):
         self.prepend_config(self.CCD_CFG)
         self.add_servo_serial(ccd, self.servod.CCD_SERIAL)
         self.init_servo_interfaces(ccd)