Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

of: property: Improve cycle detection when one of the devices is never added

Consider this example where -> means LHS device is a consumer of RHS
device and indentation represents "child of" of the previous device.

Device A -> Device C

Device B -> Device A
Device C

Without this commit:
1. Device A is added.
2. Device A is added to waiting for supplier list (Device C)
3. Device B is added
4. Device B is linked as a consumer to Device A
5. Device A doesn't probe because it's waiting for Device C to be added.
6. Device B doesn't probe because Device A hasn't probed.
7. Device C will never be added because it's parent hasn't probed.

So, Device A, B and C will be in a probe/add deadlock.

This commit detects this scenario and stops trying to create a device
link between Device A and Device C since doing so would create the
following cycle:
Device A -> Devic C -(parent)-> Device B -> Device A.

With this commit:
1. Device A is added.
3. Device B is added
4. Device B is linked as a consumer to Device A
5. Device A probes.
6. Device B probes because Device A has probed.
7. Device C is added and probed.

Signed-off-by: Saravana Kannan <saravanak@google.com>
Link: https://lore.kernel.org/r/20200610011934.49795-3-saravanak@google.com
Signed-off-by: Rob Herring <robh@kernel.org>

authored by

Saravana Kannan and committed by
Rob Herring
bb278b14 7d34ca38

+56 -6
+56 -6
drivers/of/property.c
··· 1015 1015 } 1016 1016 1017 1017 /** 1018 + * of_get_next_parent_dev - Add device link to supplier from supplier phandle 1019 + * @np: device tree node 1020 + * 1021 + * Given a device tree node (@np), this function finds its closest ancestor 1022 + * device tree node that has a corresponding struct device. 1023 + * 1024 + * The caller of this function is expected to call put_device() on the returned 1025 + * device when they are done. 1026 + */ 1027 + static struct device *of_get_next_parent_dev(struct device_node *np) 1028 + { 1029 + struct device *dev = NULL; 1030 + 1031 + of_node_get(np); 1032 + do { 1033 + np = of_get_next_parent(np); 1034 + if (np) 1035 + dev = get_dev_from_fwnode(&np->fwnode); 1036 + } while (np && !dev); 1037 + of_node_put(np); 1038 + return dev; 1039 + } 1040 + 1041 + /** 1018 1042 * of_link_to_phandle - Add device link to supplier from supplier phandle 1019 1043 * @dev: consumer device 1020 1044 * @sup_np: phandle to supplier device tree node ··· 1059 1035 static int of_link_to_phandle(struct device *dev, struct device_node *sup_np, 1060 1036 u32 dl_flags) 1061 1037 { 1062 - struct device *sup_dev; 1038 + struct device *sup_dev, *sup_par_dev; 1063 1039 int ret = 0; 1064 1040 struct device_node *tmp_np = sup_np; 1065 - int is_populated; 1066 1041 1067 1042 of_node_get(sup_np); 1068 1043 /* ··· 1098 1075 return -EINVAL; 1099 1076 } 1100 1077 sup_dev = get_dev_from_fwnode(&sup_np->fwnode); 1101 - is_populated = of_node_check_flag(sup_np, OF_POPULATED); 1102 - of_node_put(sup_np); 1103 - if (!sup_dev && is_populated) { 1078 + if (!sup_dev && of_node_check_flag(sup_np, OF_POPULATED)) { 1104 1079 /* Early device without struct device. */ 1105 1080 dev_dbg(dev, "Not linking to %pOFP - No struct device\n", 1106 1081 sup_np); 1082 + of_node_put(sup_np); 1107 1083 return -ENODEV; 1108 1084 } else if (!sup_dev) { 1109 - return -EAGAIN; 1085 + /* 1086 + * DL_FLAG_SYNC_STATE_ONLY doesn't block probing and supports 1087 + * cycles. So cycle detection isn't necessary and shouldn't be 1088 + * done. 1089 + */ 1090 + if (dl_flags & DL_FLAG_SYNC_STATE_ONLY) { 1091 + of_node_put(sup_np); 1092 + return -EAGAIN; 1093 + } 1094 + 1095 + sup_par_dev = of_get_next_parent_dev(sup_np); 1096 + 1097 + if (sup_par_dev && device_is_dependent(dev, sup_par_dev)) { 1098 + /* Cyclic dependency detected, don't try to link */ 1099 + dev_dbg(dev, "Not linking to %pOFP - cycle detected\n", 1100 + sup_np); 1101 + ret = -EINVAL; 1102 + } else { 1103 + /* 1104 + * Can't check for cycles or no cycles. So let's try 1105 + * again later. 1106 + */ 1107 + ret = -EAGAIN; 1108 + } 1109 + 1110 + of_node_put(sup_np); 1111 + put_device(sup_par_dev); 1112 + return ret; 1110 1113 } 1114 + of_node_put(sup_np); 1111 1115 if (!device_link_add(dev, sup_dev, dl_flags)) 1112 1116 ret = -EINVAL; 1113 1117 put_device(sup_dev);