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 else 81 { 82 return; 83 } 84 boardTypes[path] = boardType; 85 } 86 87 void Topology::addConfiguredPort(const Path& path, 88 const nlohmann::json& exposesItem) 89 { 90 const auto findConnectsToName = exposesItem.find("Name"); 91 if (findConnectsToName == exposesItem.end()) 92 { 93 lg2::error("Board at path {PATH} is missing 'Name'", "PATH", path); 94 return; 95 } 96 const std::string connectsToName = findConnectsToName->get<std::string>(); 97 98 const auto findPortType = exposesItem.find("PortType"); 99 if (findPortType == exposesItem.end()) 100 { 101 lg2::error("Board at path {PATH} is missing PortType", "PATH", path); 102 return; 103 } 104 const std::string portType = findPortType->get<std::string>(); 105 106 const auto assoc = getAssocByName(portType); 107 if (!assoc.has_value()) 108 { 109 lg2::error("Could not find configured association name {ASSOC}", 110 "ASSOC", portType); 111 return; 112 } 113 114 addPort(connectsToName, path, assoc.value()); 115 } 116 117 void Topology::addDownstreamPort(const Path& path, 118 const nlohmann::json& exposesItem) 119 { 120 auto findConnectsTo = exposesItem.find("ConnectsToType"); 121 if (findConnectsTo == exposesItem.end()) 122 { 123 lg2::error("Board at path {PATH} is missing ConnectsToType", "PATH", 124 path); 125 return; 126 } 127 PortType connectsTo = findConnectsTo->get<std::string>(); 128 129 addPort(connectsTo, path, assocContainedBy); 130 131 auto findPoweredBy = exposesItem.find("PowerPort"); 132 if (findPoweredBy != exposesItem.end()) 133 { 134 addPort(connectsTo, path, assocPowering); 135 } 136 } 137 138 void Topology::addPort(const PortType& port, const Path& path, 139 const AssocName& assocName) 140 { 141 if (!ports.contains(port)) 142 { 143 ports.insert({port, {}}); 144 } 145 if (!ports[port].contains(path)) 146 { 147 ports[port].insert({path, {}}); 148 } 149 ports[port][path].insert(assocName); 150 } 151 152 std::unordered_map<std::string, std::set<Association>> Topology::getAssocs( 153 BoardPathsView boardPaths) 154 { 155 std::unordered_map<std::string, std::set<Association>> result; 156 157 // look at each upstream port type 158 for (const auto& port : ports) 159 { 160 fillAssocsForPortId(result, boardPaths, port.second); 161 } 162 return result; 163 } 164 165 void Topology::fillAssocsForPortId( 166 std::unordered_map<std::string, std::set<Association>>& result, 167 BoardPathsView boardPaths, 168 const std::map<Path, std::set<AssocName>>& pathAssocs) 169 { 170 for (const auto& member : pathAssocs) 171 { 172 for (const auto& other : pathAssocs) 173 { 174 if (other.first == member.first) 175 { 176 continue; 177 } 178 for (const auto& assocName : member.second) 179 { 180 // if the other end of the assocation does not declare 181 // the reverse association, do not associate 182 const bool otherAgrees = 183 other.second.contains(assocName.getReverse()); 184 185 // quirk: since the other side of the association cannot declare 186 // to be powered_by in the legacy schema, in case of "powering", 187 // the two associations do not have to agree. 188 if (!otherAgrees && assocName != assocPowering) 189 { 190 continue; 191 } 192 193 fillAssocForPortId(result, boardPaths, member.first, 194 other.first, assocName); 195 } 196 } 197 } 198 } 199 200 void Topology::fillAssocForPortId( 201 std::unordered_map<std::string, std::set<Association>>& result, 202 BoardPathsView boardPaths, const Path& upstream, const Path& downstream, 203 const AssocName& assocName) 204 { 205 if (!assocName.allowedOnBoardTypes.contains(boardTypes[upstream])) 206 { 207 lg2::error( 208 "Cannot create Association Definition {ASSOC} for {PATH} with board type {TYPE}", 209 "ASSOC", assocName.name, "PATH", upstream, "TYPE", 210 boardTypes[upstream]); 211 return; 212 } 213 // The downstream path must be one we care about. 214 if (!std::ranges::contains(boardPaths, upstream)) 215 { 216 return; 217 } 218 219 // quirk: legacy code did not associate from both sides 220 // TODO(alexander): revisit this 221 if (assocName == assocContaining || assocName == assocPoweredBy) 222 { 223 return; 224 } 225 226 result[upstream].insert({assocName.name, assocName.reverse, downstream}); 227 } 228 229 void Topology::remove(const std::string& boardName) 230 { 231 // Remove the board from boardNames, and then using the path 232 // found in boardNames remove it from ports 233 auto boardFind = boardNames.find(boardName); 234 if (boardFind == boardNames.end()) 235 { 236 return; 237 } 238 239 std::string boardPath = boardFind->second; 240 241 boardNames.erase(boardFind); 242 243 for (auto& port : ports) 244 { 245 port.second.erase(boardPath); 246 } 247 } 248