1 #include "topology.hpp" 2 3 #include "phosphor-logging/lg2.hpp" 4 5 const AssocName assocContaining = 6 AssocName("containing", "contained_by", {"Chassis"}, 7 {"Board", "Chassis", "PowerSupply"}); 8 const AssocName assocContainedBy = assocContaining.getReverse(); 9 10 // Topology tests say that a chassis can be powering another chassis. 11 // In case there is any confusion as to why 'Chassis' can have 'powering' 12 // association. 13 const AssocName assocPowering = 14 AssocName("powering", "powered_by", {"Chassis", "PowerSupply"}, 15 {"Board", "Chassis", "PowerSupply"}); 16 const AssocName assocPoweredBy = assocPowering.getReverse(); 17 18 const AssocName assocProbing = AssocName("probing", "probed_by", {}, {}); 19 const AssocName assocProbedBy = assocProbing.getReverse(); 20 21 const std::vector<AssocName> supportedAssocs = { 22 assocContaining, 23 assocContainedBy, 24 assocPowering, 25 assocPoweredBy, 26 }; 27 28 AssocName::AssocName(const std::string& name, const std::string& reverse, 29 const std::set<std::string>& allowedOnBoardTypes, 30 const std::set<std::string>& allowedOnBoardTypesReverse) : 31 name(name), reverse(reverse), allowedOnBoardTypes(allowedOnBoardTypes), 32 allowedOnBoardTypesReverse(allowedOnBoardTypesReverse) 33 {} 34 35 AssocName AssocName::getReverse() const 36 { 37 return {reverse, name, allowedOnBoardTypesReverse, allowedOnBoardTypes}; 38 } 39 40 bool AssocName::operator<(const AssocName& other) const 41 { 42 return name < other.name; 43 } 44 45 std::optional<AssocName> Topology::getAssocByName(const std::string& name) 46 { 47 for (const auto& assoc : supportedAssocs) 48 { 49 if (assoc.name == name) 50 { 51 return assoc; 52 } 53 } 54 return std::nullopt; 55 } 56 57 void Topology::addBoard(const std::string& path, const std::string& boardType, 58 const std::string& boardName, 59 const nlohmann::json& exposesItem) 60 { 61 auto findType = exposesItem.find("Type"); 62 if (findType == exposesItem.end()) 63 { 64 return; 65 } 66 67 boardNames.try_emplace(boardName, path); 68 69 PortType exposesType = findType->get<std::string>(); 70 71 if (exposesType == "DownstreamPort") 72 { 73 addDownstreamPort(path, exposesItem); 74 } 75 else if (exposesType == "Port") 76 { 77 addConfiguredPort(path, exposesItem); 78 } 79 else if (exposesType.ends_with("Port")) 80 { 81 addPort(exposesType, path, assocContaining); 82 83 // this represents the legacy quirk of upstream ports having no choice 84 // in the 85 // powered_by association 86 addPort(exposesType, path, assocPoweredBy); 87 } 88 else 89 { 90 return; 91 } 92 boardTypes[path] = boardType; 93 } 94 95 void Topology::addConfiguredPort(const Path& path, 96 const nlohmann::json& exposesItem) 97 { 98 const auto findConnectsToName = exposesItem.find("Name"); 99 if (findConnectsToName == exposesItem.end()) 100 { 101 lg2::error("Board at path {PATH} is missing 'Name'", "PATH", path); 102 return; 103 } 104 const std::string connectsToName = findConnectsToName->get<std::string>(); 105 106 const auto findPortType = exposesItem.find("PortType"); 107 if (findPortType == exposesItem.end()) 108 { 109 lg2::error("Board at path {PATH} is missing PortType", "PATH", path); 110 return; 111 } 112 const std::string portType = findPortType->get<std::string>(); 113 114 const auto assoc = getAssocByName(portType); 115 if (!assoc.has_value()) 116 { 117 lg2::error("Could not find configured association name {ASSOC}", 118 "ASSOC", portType); 119 return; 120 } 121 122 addPort(connectsToName, path, assoc.value()); 123 } 124 125 void Topology::addDownstreamPort(const Path& path, 126 const nlohmann::json& exposesItem) 127 { 128 auto findConnectsTo = exposesItem.find("ConnectsToType"); 129 if (findConnectsTo == exposesItem.end()) 130 { 131 lg2::error("Board at path {PATH} is missing ConnectsToType", "PATH", 132 path); 133 return; 134 } 135 PortType connectsTo = findConnectsTo->get<std::string>(); 136 137 addPort(connectsTo, path, assocContainedBy); 138 139 auto findPoweredBy = exposesItem.find("PowerPort"); 140 if (findPoweredBy != exposesItem.end()) 141 { 142 addPort(connectsTo, path, assocPowering); 143 } 144 } 145 146 void Topology::addPort(const PortType& port, const Path& path, 147 const AssocName& assocName) 148 { 149 if (!ports.contains(port)) 150 { 151 ports.insert({port, {}}); 152 } 153 if (!ports[port].contains(path)) 154 { 155 ports[port].insert({path, {}}); 156 } 157 ports[port][path].insert(assocName); 158 } 159 160 std::unordered_map<std::string, std::set<Association>> Topology::getAssocs( 161 BoardPathsView boardPaths) 162 { 163 std::unordered_map<std::string, std::set<Association>> result; 164 165 // look at each upstream port type 166 for (const auto& port : ports) 167 { 168 fillAssocsForPortId(result, boardPaths, port.second); 169 } 170 171 for (const auto& [boardPath, probePaths] : probePaths) 172 { 173 if (std::ranges::contains(boardPaths, boardPath)) 174 { 175 for (const auto& path : probePaths) 176 { 177 result[boardPath].insert( 178 {assocProbing.name, assocProbedBy.name, path}); 179 } 180 } 181 } 182 return result; 183 } 184 185 void Topology::fillAssocsForPortId( 186 std::unordered_map<std::string, std::set<Association>>& result, 187 BoardPathsView boardPaths, 188 const std::map<Path, std::set<AssocName>>& pathAssocs) 189 { 190 for (const auto& member : pathAssocs) 191 { 192 for (const auto& other : pathAssocs) 193 { 194 if (other.first == member.first) 195 { 196 continue; 197 } 198 for (const auto& assocName : member.second) 199 { 200 // if the other end of the assocation does not declare 201 // the reverse association, do not associate 202 const bool otherAgrees = 203 other.second.contains(assocName.getReverse()); 204 205 if (!otherAgrees) 206 { 207 continue; 208 } 209 210 fillAssocForPortId(result, boardPaths, member.first, 211 other.first, assocName); 212 } 213 } 214 } 215 } 216 217 void Topology::fillAssocForPortId( 218 std::unordered_map<std::string, std::set<Association>>& result, 219 BoardPathsView boardPaths, const Path& upstream, const Path& downstream, 220 const AssocName& assocName) 221 { 222 if (!assocName.allowedOnBoardTypes.contains(boardTypes[upstream])) 223 { 224 lg2::error( 225 "Cannot create Association Definition {ASSOC} for {PATH} with board type {TYPE}", 226 "ASSOC", assocName.name, "PATH", upstream, "TYPE", 227 boardTypes[upstream]); 228 return; 229 } 230 // The downstream path must be one we care about. 231 if (!std::ranges::contains(boardPaths, upstream)) 232 { 233 return; 234 } 235 236 // quirk: legacy code did not associate from both sides 237 // TODO(alexander): revisit this 238 if (assocName == assocContaining || assocName == assocPoweredBy) 239 { 240 return; 241 } 242 243 result[upstream].insert({assocName.name, assocName.reverse, downstream}); 244 } 245 246 void Topology::remove(const std::string& boardName) 247 { 248 // Remove the board from boardNames, and then using the path 249 // found in boardNames remove it from ports 250 auto boardFind = boardNames.find(boardName); 251 if (boardFind == boardNames.end()) 252 { 253 return; 254 } 255 256 std::string boardPath = boardFind->second; 257 258 boardNames.erase(boardFind); 259 260 for (auto& port : ports) 261 { 262 port.second.erase(boardPath); 263 } 264 265 probePaths.erase(boardName); 266 } 267 268 void Topology::addProbePath(const std::string& boardPath, 269 const std::string& probePath) 270 { 271 lg2::info("Probe path added: {PROBE} probed_by {BOARD} boards config", 272 "PROBE", probePath, "BOARD", boardPath); 273 probePaths[boardPath].emplace(probePath); 274 } 275