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
AssocName(const std::string & name,const std::string & reverse,const std::set<std::string> & allowedOnBoardTypes,const std::set<std::string> & allowedOnBoardTypesReverse)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
getReverse() const35 AssocName AssocName::getReverse() const
36 {
37 return {reverse, name, allowedOnBoardTypesReverse, allowedOnBoardTypes};
38 }
39
operator <(const AssocName & other) const40 bool AssocName::operator<(const AssocName& other) const
41 {
42 return name < other.name;
43 }
44
getAssocByName(const std::string & name)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
addBoard(const std::string & path,const std::string & boardType,const std::string & boardName,const nlohmann::json & exposesItem)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
addConfiguredPort(const Path & path,const nlohmann::json & exposesItem)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
addDownstreamPort(const Path & path,const nlohmann::json & exposesItem)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
addPort(const PortType & port,const Path & path,const AssocName & assocName)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
getAssocs(BoardPathsView boardPaths)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
fillAssocsForPortId(std::unordered_map<std::string,std::set<Association>> & result,BoardPathsView boardPaths,const std::map<Path,std::set<AssocName>> & pathAssocs)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
fillAssocForPortId(std::unordered_map<std::string,std::set<Association>> & result,BoardPathsView boardPaths,const Path & upstream,const Path & downstream,const AssocName & assocName)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
remove(const std::string & boardName)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
addProbePath(const std::string & boardPath,const std::string & probePath)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