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