xref: /openbmc/entity-manager/src/entity_manager/topology.cpp (revision 9a5eec91664d61ed47630a616c2e187c7c6a69bc)
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