xref: /openbmc/entity-manager/src/entity_manager/topology.cpp (revision f850ecad00900a9d338950e28506c04af42b8883)
1 #include "topology.hpp"
2 
3 #include "phosphor-logging/lg2.hpp"
4 
5 const AssocName assocContaining = AssocName("containing", "contained_by");
6 const AssocName assocContainedBy = assocContaining.getReverse();
7 const AssocName assocPowering = AssocName("powering", "powered_by");
8 const AssocName assocPoweredBy = assocPowering.getReverse();
9 
10 AssocName::AssocName(const std::string& name, const std::string& reverse) :
11     name(name), reverse(reverse)
12 {}
13 
14 AssocName AssocName::getReverse() const
15 {
16     return {reverse, name};
17 }
18 
19 bool AssocName::operator<(const AssocName& other) const
20 {
21     return name < other.name;
22 }
23 
24 void Topology::addBoard(const std::string& path, const std::string& boardType,
25                         const std::string& boardName,
26                         const nlohmann::json& exposesItem)
27 {
28     auto findType = exposesItem.find("Type");
29     if (findType == exposesItem.end())
30     {
31         return;
32     }
33 
34     boardNames.try_emplace(boardName, path);
35 
36     PortType exposesType = findType->get<std::string>();
37 
38     if (exposesType == "DownstreamPort")
39     {
40         addDownstreamPort(path, exposesItem);
41     }
42     else if (exposesType.ends_with("Port"))
43     {
44         addPort(exposesType, path, assocContaining);
45     }
46     else
47     {
48         return;
49     }
50     boardTypes[path] = boardType;
51 }
52 
53 void Topology::addDownstreamPort(const Path& path,
54                                  const nlohmann::json& exposesItem)
55 {
56     auto findConnectsTo = exposesItem.find("ConnectsToType");
57     if (findConnectsTo == exposesItem.end())
58     {
59         lg2::error("Board at path {PATH} is missing ConnectsToType", "PATH",
60                    path);
61         return;
62     }
63     PortType connectsTo = findConnectsTo->get<std::string>();
64 
65     addPort(connectsTo, path, assocContainedBy);
66 
67     auto findPoweredBy = exposesItem.find("PowerPort");
68     if (findPoweredBy != exposesItem.end())
69     {
70         addPort(connectsTo, path, assocPowering);
71     }
72 }
73 
74 void Topology::addPort(const PortType& port, const Path& path,
75                        const AssocName& assocName)
76 {
77     if (!ports.contains(port))
78     {
79         ports.insert({port, {}});
80     }
81     if (!ports[port].contains(path))
82     {
83         ports[port].insert({path, {}});
84     }
85     ports[port][path].insert(assocName);
86 }
87 
88 std::unordered_map<std::string, std::set<Association>> Topology::getAssocs(
89     BoardPathsView boardPaths)
90 {
91     std::unordered_map<std::string, std::set<Association>> result;
92 
93     // look at each upstream port type
94     for (const auto& port : ports)
95     {
96         fillAssocsForPortId(result, boardPaths, port.second);
97     }
98     return result;
99 }
100 
101 void Topology::fillAssocsForPortId(
102     std::unordered_map<std::string, std::set<Association>>& result,
103     BoardPathsView boardPaths,
104     const std::map<Path, std::set<AssocName>>& pathAssocs)
105 {
106     for (const auto& member : pathAssocs)
107     {
108         for (const auto& other : pathAssocs)
109         {
110             if (other.first == member.first)
111             {
112                 continue;
113             }
114             for (const auto& assocName : member.second)
115             {
116                 // if the other end of the assocation does not declare
117                 // the reverse association, do not associate
118                 const bool otherAgrees =
119                     other.second.contains(assocName.getReverse());
120 
121                 // quirk: since the other side of the association cannot declare
122                 // to be powered_by in the legacy schema, in case of "powering",
123                 // the two associations do not have to agree.
124                 if (!otherAgrees && assocName != assocPowering)
125                 {
126                     continue;
127                 }
128 
129                 fillAssocForPortId(result, boardPaths, member.first,
130                                    other.first, assocName);
131             }
132         }
133     }
134 }
135 
136 void Topology::fillAssocForPortId(
137     std::unordered_map<std::string, std::set<Association>>& result,
138     BoardPathsView boardPaths, const Path& upstream, const Path& downstream,
139     const AssocName& assocName)
140 {
141     if (boardTypes[upstream] != "Chassis" && boardTypes[upstream] != "Board")
142     {
143         return;
144     }
145     // The downstream path must be one we care about.
146     if (!std::ranges::contains(boardPaths, upstream))
147     {
148         return;
149     }
150 
151     // quirk: legacy code did not associate from both sides
152     // TODO(alexander): revisit this
153     if (assocName == assocContaining || assocName == assocPoweredBy)
154     {
155         return;
156     }
157 
158     result[upstream].insert({assocName.name, assocName.reverse, downstream});
159 }
160 
161 void Topology::remove(const std::string& boardName)
162 {
163     // Remove the board from boardNames, and then using the path
164     // found in boardNames remove it from ports
165     auto boardFind = boardNames.find(boardName);
166     if (boardFind == boardNames.end())
167     {
168         return;
169     }
170 
171     std::string boardPath = boardFind->second;
172 
173     boardNames.erase(boardFind);
174 
175     for (auto& port : ports)
176     {
177         port.second.erase(boardPath);
178     }
179 }
180