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