xref: /openbmc/entity-manager/src/entity_manager/topology.cpp (revision daa4ac08f9d5b852417446e86655d59b9149bd24)
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 
AssocName(const std::string & name,const std::string & reverse,const std::set<std::string> & allowedOnBoardTypes,const std::set<std::string> & allowedOnBoardTypesReverse)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 
getReverse() const32 AssocName AssocName::getReverse() const
33 {
34     return {reverse, name, allowedOnBoardTypesReverse, allowedOnBoardTypes};
35 }
36 
operator <(const AssocName & other) const37 bool AssocName::operator<(const AssocName& other) const
38 {
39     return name < other.name;
40 }
41 
getAssocByName(const std::string & name)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 
addBoard(const std::string & path,const std::string & boardType,const std::string & boardName,const nlohmann::json & exposesItem)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 
addConfiguredPort(const Path & path,const nlohmann::json & exposesItem)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 
addDownstreamPort(const Path & path,const nlohmann::json & exposesItem)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 
addPort(const PortType & port,const Path & path,const AssocName & assocName)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 
getAssocs(BoardPathsView boardPaths)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 
fillAssocsForPortId(std::unordered_map<std::string,std::set<Association>> & result,BoardPathsView boardPaths,const std::map<Path,std::set<AssocName>> & pathAssocs)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 
fillAssocForPortId(std::unordered_map<std::string,std::set<Association>> & result,BoardPathsView boardPaths,const Path & upstream,const Path & downstream,const AssocName & assocName)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 
remove(const std::string & boardName)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