1 #include "MCTPEndpoint.hpp"
2 #include "MCTPReactor.hpp"
3 #include "Utils.hpp"
4 
5 #include <boost/asio/io_context.hpp>
6 #include <boost/asio/post.hpp>
7 #include <boost/asio/steady_timer.hpp>
8 #include <phosphor-logging/lg2.hpp>
9 #include <sdbusplus/asio/connection.hpp>
10 #include <sdbusplus/asio/object_server.hpp>
11 #include <sdbusplus/bus.hpp>
12 #include <sdbusplus/bus/match.hpp>
13 #include <sdbusplus/message.hpp>
14 #include <sdbusplus/message/native_types.hpp>
15 
16 #include <chrono>
17 #include <cstdlib>
18 #include <format>
19 #include <functional>
20 #include <map>
21 #include <memory>
22 #include <optional>
23 #include <set>
24 #include <stdexcept>
25 #include <system_error>
26 #include <vector>
27 
28 PHOSPHOR_LOG2_USING;
29 
30 class DBusAssociationServer : public AssociationServer
31 {
32   public:
33     DBusAssociationServer() = delete;
34     DBusAssociationServer(const DBusAssociationServer&) = delete;
35     DBusAssociationServer(DBusAssociationServer&&) = delete;
DBusAssociationServer(const std::shared_ptr<sdbusplus::asio::connection> & connection)36     explicit DBusAssociationServer(
37         const std::shared_ptr<sdbusplus::asio::connection>& connection) :
38         server(connection)
39     {
40         server.add_manager("/xyz/openbmc_project/mctp");
41     }
42     ~DBusAssociationServer() override = default;
43     DBusAssociationServer& operator=(const DBusAssociationServer&) = delete;
44     DBusAssociationServer& operator=(DBusAssociationServer&&) = delete;
45 
associate(const std::string & path,const std::vector<Association> & associations)46     void associate(const std::string& path,
47                    const std::vector<Association>& associations) override
48     {
49         auto [entry, _] = objects.emplace(
50             path, server.add_interface(path, association::interface));
51         std::shared_ptr<sdbusplus::asio::dbus_interface> iface = entry->second;
52         iface->register_property("Associations", associations);
53         iface->initialize();
54     }
55 
disassociate(const std::string & path)56     void disassociate(const std::string& path) override
57     {
58         const auto entry = objects.find(path);
59         if (entry == objects.end())
60         {
61             throw std::logic_error(std::format(
62                 "Attempted to untrack path that was not tracked: {}", path));
63         }
64         std::shared_ptr<sdbusplus::asio::dbus_interface> iface = entry->second;
65         server.remove_interface(entry->second);
66         objects.erase(entry);
67     }
68 
69   private:
70     std::shared_ptr<sdbusplus::asio::connection> connection;
71     sdbusplus::asio::object_server server;
72     std::map<std::string, std::shared_ptr<sdbusplus::asio::dbus_interface>>
73         objects;
74 };
75 
deviceFromConfig(const std::shared_ptr<sdbusplus::asio::connection> & connection,const SensorData & config)76 static std::shared_ptr<MCTPDevice> deviceFromConfig(
77     const std::shared_ptr<sdbusplus::asio::connection>& connection,
78     const SensorData& config)
79 {
80     try
81     {
82         std::optional<SensorBaseConfigMap> iface;
83         // NOLINTNEXTLINE(bugprone-assignment-in-if-condition)
84         if ((iface = I2CMCTPDDevice::match(config)))
85         {
86             return I2CMCTPDDevice::from(connection, *iface);
87         }
88     }
89     catch (const std::invalid_argument& ex)
90     {
91         error("Unable to create device: {EXCEPTION}", "EXCEPTION", ex);
92     }
93 
94     return {};
95 }
96 
addInventory(const std::shared_ptr<sdbusplus::asio::connection> & connection,const std::shared_ptr<MCTPReactor> & reactor,sdbusplus::message_t & msg)97 static void addInventory(
98     const std::shared_ptr<sdbusplus::asio::connection>& connection,
99     const std::shared_ptr<MCTPReactor>& reactor, sdbusplus::message_t& msg)
100 {
101     auto [path,
102           exposed] = msg.unpack<sdbusplus::message::object_path, SensorData>();
103     try
104     {
105         reactor->manageMCTPDevice(path, deviceFromConfig(connection, exposed));
106     }
107     catch (const std::logic_error& e)
108     {
109         error(
110             "Addition of inventory at '{INVENTORY_PATH}' caused an invalid program state: {EXCEPTION}",
111             "INVENTORY_PATH", path, "EXCEPTION", e);
112     }
113     catch (const std::system_error& e)
114     {
115         error(
116             "Failed to manage device described by inventory at '{INVENTORY_PATH}: {EXCEPTION}'",
117             "INVENTORY_PATH", path, "EXCEPTION", e);
118     }
119 }
120 
removeInventory(const std::shared_ptr<MCTPReactor> & reactor,sdbusplus::message_t & msg)121 static void removeInventory(const std::shared_ptr<MCTPReactor>& reactor,
122                             sdbusplus::message_t& msg)
123 {
124     auto [path, removed] =
125         msg.unpack<sdbusplus::message::object_path, std::set<std::string>>();
126     try
127     {
128         if (I2CMCTPDDevice::match(removed))
129         {
130             reactor->unmanageMCTPDevice(path.str);
131         }
132     }
133     catch (const std::logic_error& e)
134     {
135         error(
136             "Removal of inventory at '{INVENTORY_PATH}' caused an invalid program state: {EXCEPTION}",
137             "INVENTORY_PATH", path, "EXCEPTION", e);
138     }
139     catch (const std::system_error& e)
140     {
141         error(
142             "Failed to unmanage device described by inventory at '{INVENTORY_PATH}: {EXCEPTION}'",
143             "INVENTORY_PATH", path, "EXCEPTION", e);
144     }
145 }
146 
manageMCTPEntity(const std::shared_ptr<sdbusplus::asio::connection> & connection,const std::shared_ptr<MCTPReactor> & reactor,ManagedObjectType & entities)147 static void manageMCTPEntity(
148     const std::shared_ptr<sdbusplus::asio::connection>& connection,
149     const std::shared_ptr<MCTPReactor>& reactor, ManagedObjectType& entities)
150 {
151     for (const auto& [path, config] : entities)
152     {
153         try
154         {
155             reactor->manageMCTPDevice(path,
156                                       deviceFromConfig(connection, config));
157         }
158         catch (const std::logic_error& e)
159         {
160             error(
161                 "Addition of inventory at '{INVENTORY_PATH}' caused an invalid program state: {EXCEPTION}",
162                 "INVENTORY_PATH", path, "EXCEPTION", e);
163         }
164         catch (const std::system_error& e)
165         {
166             error(
167                 "Failed to manage device described by inventory at '{INVENTORY_PATH}: {EXCEPTION}'",
168                 "INVENTORY_PATH", path, "EXCEPTION", e);
169         }
170     }
171 }
172 
exitReactor(boost::asio::io_context * io,sdbusplus::message_t & msg)173 static void exitReactor(boost::asio::io_context* io, sdbusplus::message_t& msg)
174 {
175     auto name = msg.unpack<std::string>();
176     info("Shutting down mctpreactor, lost dependency '{SERVICE_NAME}'",
177          "SERVICE_NAME", name);
178     io->stop();
179 }
180 
main()181 int main()
182 {
183     constexpr std::chrono::seconds period(5);
184 
185     boost::asio::io_context io;
186     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
187     DBusAssociationServer associationServer(systemBus);
188     auto reactor = std::make_shared<MCTPReactor>(associationServer);
189     boost::asio::steady_timer clock(io);
190 
191     std::function<void(const boost::system::error_code&)> alarm =
192         [&](const boost::system::error_code& ec) {
193             if (ec)
194             {
195                 return;
196             }
197             clock.expires_after(period);
198             clock.async_wait(alarm);
199             reactor->tick();
200         };
201     clock.expires_after(period);
202     clock.async_wait(alarm);
203 
204     using namespace sdbusplus::bus::match;
205 
206     const std::string entityManagerNameLostSpec =
207         rules::nameOwnerChanged("xyz.openbmc_project.EntityManager");
208 
209     auto entityManagerNameLostMatch = sdbusplus::bus::match_t(
210         static_cast<sdbusplus::bus_t&>(*systemBus), entityManagerNameLostSpec,
211         std::bind_front(exitReactor, &io));
212 
213     const std::string mctpdNameLostSpec =
214         rules::nameOwnerChanged("xyz.openbmc_project.MCTP");
215 
216     auto mctpdNameLostMatch = sdbusplus::bus::match_t(
217         static_cast<sdbusplus::bus_t&>(*systemBus), mctpdNameLostSpec,
218         std::bind_front(exitReactor, &io));
219 
220     const std::string interfacesRemovedMatchSpec =
221         rules::sender("xyz.openbmc_project.EntityManager") +
222         // Trailing slash on path: Listen for signals on the inventory subtree
223         rules::interfacesRemovedAtPath("/xyz/openbmc_project/inventory/");
224 
225     auto interfacesRemovedMatch = sdbusplus::bus::match_t(
226         static_cast<sdbusplus::bus_t&>(*systemBus), interfacesRemovedMatchSpec,
227         std::bind_front(removeInventory, reactor));
228 
229     const std::string interfacesAddedMatchSpec =
230         rules::sender("xyz.openbmc_project.EntityManager") +
231         // Trailing slash on path: Listen for signals on the inventory subtree
232         rules::interfacesAddedAtPath("/xyz/openbmc_project/inventory/");
233 
234     auto interfacesAddedMatch = sdbusplus::bus::match_t(
235         static_cast<sdbusplus::bus_t&>(*systemBus), interfacesAddedMatchSpec,
236         std::bind_front(addInventory, systemBus, reactor));
237 
238     systemBus->request_name("xyz.openbmc_project.MCTPReactor");
239 
240     boost::asio::post(io, [reactor, systemBus]() {
241         auto gsc = std::make_shared<GetSensorConfiguration>(
242             systemBus, std::bind_front(manageMCTPEntity, systemBus, reactor));
243         gsc->getConfiguration({"MCTPI2CTarget"});
244     });
245 
246     io.run();
247 
248     return EXIT_SUCCESS;
249 }
250