xref: /openbmc/dbus-sensors/src/nvme/NVMeSensorMain.cpp (revision 76e0dd56c340cf11344e038979f88f59b71bff46)
1 /*
2 // Copyright (c) 2019 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "NVMeBasicContext.hpp"
18 #include "NVMeContext.hpp"
19 #include "NVMeSensor.hpp"
20 #include "Thresholds.hpp"
21 #include "Utils.hpp"
22 #include "VariantVisitors.hpp"
23 
24 #include <boost/asio/error.hpp>
25 #include <boost/asio/io_context.hpp>
26 #include <boost/asio/post.hpp>
27 #include <boost/asio/steady_timer.hpp>
28 #include <phosphor-logging/lg2.hpp>
29 #include <sdbusplus/asio/connection.hpp>
30 #include <sdbusplus/asio/object_server.hpp>
31 #include <sdbusplus/bus.hpp>
32 #include <sdbusplus/bus/match.hpp>
33 #include <sdbusplus/message.hpp>
34 #include <sdbusplus/message/native_types.hpp>
35 
36 #include <algorithm>
37 #include <array>
38 #include <chrono>
39 #include <cstddef>
40 #include <cstdint>
41 #include <filesystem>
42 #include <functional>
43 #include <memory>
44 #include <optional>
45 #include <stdexcept>
46 #include <string>
47 #include <utility>
48 #include <variant>
49 #include <vector>
50 
51 static constexpr uint8_t nvmeMiDefaultSlaveAddr = 0x6A;
52 
53 static NVMEMap nvmeDeviceMap;
54 
getNVMEMap()55 NVMEMap& getNVMEMap()
56 {
57     return nvmeDeviceMap;
58 }
59 
extractBusNumber(const std::string & path,const SensorBaseConfigMap & properties)60 static std::optional<int> extractBusNumber(
61     const std::string& path, const SensorBaseConfigMap& properties)
62 {
63     auto findBus = properties.find("Bus");
64     if (findBus == properties.end())
65     {
66         lg2::error("could not determine bus number for '{PATH}'", "PATH", path);
67         return std::nullopt;
68     }
69 
70     return std::visit(VariantToIntVisitor(), findBus->second);
71 }
72 
extractSlaveAddr(const std::string & path,const SensorBaseConfigMap & properties)73 static uint8_t extractSlaveAddr(const std::string& path,
74                                 const SensorBaseConfigMap& properties)
75 {
76     auto findSlaveAddr = properties.find("Address");
77     if (findSlaveAddr == properties.end())
78     {
79         lg2::error(
80             "could not determine slave address for '{PATH} 'using default as "
81             "specified in nvme-mi",
82             "PATH", path);
83         return nvmeMiDefaultSlaveAddr;
84     }
85 
86     return std::visit(VariantToUnsignedIntVisitor(), findSlaveAddr->second);
87 }
88 
extractSensorName(const std::string & path,const SensorBaseConfigMap & properties)89 static std::optional<std::string> extractSensorName(
90     const std::string& path, const SensorBaseConfigMap& properties)
91 {
92     auto findSensorName = properties.find("Name");
93     if (findSensorName == properties.end())
94     {
95         lg2::error("could not determine configuration name for '{PATH}'",
96                    "PATH", path);
97         return std::nullopt;
98     }
99 
100     return std::get<std::string>(findSensorName->second);
101 }
102 
extractPEC(const SensorBaseConfigMap & properties)103 static std::optional<std::string> extractPEC(
104     const SensorBaseConfigMap& properties)
105 {
106     auto findPEC = properties.find("PEC");
107     if (findPEC == properties.end())
108     {
109         return std::nullopt;
110     }
111 
112     return std::visit(VariantToStringVisitor(), findPEC->second);
113 }
114 
deriveRootBusPath(int busNumber)115 static std::filesystem::path deriveRootBusPath(int busNumber)
116 {
117     return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
118            "/mux_device";
119 }
120 
deriveRootBus(std::optional<int> busNumber)121 static std::optional<int> deriveRootBus(std::optional<int> busNumber)
122 {
123     if (!busNumber)
124     {
125         return std::nullopt;
126     }
127 
128     std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
129 
130     if (!std::filesystem::is_symlink(muxPath))
131     {
132         return busNumber;
133     }
134 
135     std::string rootName = std::filesystem::read_symlink(muxPath).filename();
136     size_t dash = rootName.find('-');
137     if (dash == std::string::npos)
138     {
139         lg2::error("Error finding root bus for '{NAME}'", "NAME", rootName);
140         return std::nullopt;
141     }
142 
143     return std::stoi(rootName.substr(0, dash));
144 }
145 
provideRootBusContext(boost::asio::io_context & io,NVMEMap & map,int rootBus)146 static std::shared_ptr<NVMeContext> provideRootBusContext(
147     boost::asio::io_context& io, NVMEMap& map, int rootBus)
148 {
149     auto findRoot = map.find(rootBus);
150     if (findRoot != map.end())
151     {
152         return findRoot->second;
153     }
154 
155     std::shared_ptr<NVMeContext> context =
156         std::make_shared<NVMeBasicContext>(io, rootBus);
157     map[rootBus] = context;
158 
159     return context;
160 }
161 
handleSensorConfigurations(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const ManagedObjectType & sensorConfigurations)162 static void handleSensorConfigurations(
163     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
164     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
165     const ManagedObjectType& sensorConfigurations)
166 {
167     // todo: it'd be better to only update the ones we care about
168     for (const auto& [_, nvmeContextPtr] : nvmeDeviceMap)
169     {
170         if (nvmeContextPtr)
171         {
172             nvmeContextPtr->close();
173         }
174     }
175     nvmeDeviceMap.clear();
176 
177     // iterate through all found configurations
178     for (const auto& [interfacePath, sensorData] : sensorConfigurations)
179     {
180         // find base configuration
181         auto sensorBase =
182             sensorData.find(configInterfaceName(NVMeSensor::sensorType));
183         if (sensorBase == sensorData.end())
184         {
185             continue;
186         }
187 
188         const SensorBaseConfigMap& sensorConfig = sensorBase->second;
189         std::optional<int> busNumber =
190             extractBusNumber(interfacePath, sensorConfig);
191         std::optional<std::string> sensorName =
192             extractSensorName(interfacePath, sensorConfig);
193         uint8_t slaveAddr = extractSlaveAddr(interfacePath, sensorConfig);
194         std::optional<int> rootBus = deriveRootBus(busNumber);
195 
196         if (!(busNumber && sensorName && rootBus))
197         {
198             continue;
199         }
200 
201         std::vector<thresholds::Threshold> sensorThresholds;
202         if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
203         {
204             lg2::error("error populating thresholds for '{NAME}'", "NAME",
205                        *sensorName);
206         }
207 
208         bool smbusPEC = false;
209         std::optional<std::string> smbusPECStr = extractPEC(sensorConfig);
210         if (smbusPECStr)
211         {
212             smbusPEC = (*smbusPECStr == "Required");
213         }
214 
215         try
216         {
217             // May throw for an invalid rootBus
218             std::shared_ptr<NVMeContext> context =
219                 provideRootBusContext(io, nvmeDeviceMap, *rootBus);
220 
221             // Construct the sensor after grabbing the context so we don't
222             // glitch D-Bus May throw for an invalid busNumber
223             std::shared_ptr<NVMeSensor> sensorPtr =
224                 std::make_shared<NVMeSensor>(
225                     objectServer, io, dbusConnection, *sensorName,
226                     std::move(sensorThresholds), interfacePath, *busNumber,
227                     slaveAddr, smbusPEC);
228 
229             context->addSensor(sensorPtr);
230         }
231         catch (const std::invalid_argument& ex)
232         {
233             lg2::error("Failed to add sensor for '{PATH}': '{ERROR}'", "PATH",
234                        interfacePath.str, "ERROR", ex);
235         }
236     }
237     for (const auto& [_, context] : nvmeDeviceMap)
238     {
239         context->pollNVMeDevices();
240     }
241 }
242 
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)243 void createSensors(boost::asio::io_context& io,
244                    sdbusplus::asio::object_server& objectServer,
245                    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
246 {
247     auto getter = std::make_shared<GetSensorConfiguration>(
248         dbusConnection, [&io, &objectServer, &dbusConnection](
249                             const ManagedObjectType& sensorConfigurations) {
250             handleSensorConfigurations(io, objectServer, dbusConnection,
251                                        sensorConfigurations);
252         });
253     getter->getConfiguration(std::vector<std::string>{NVMeSensor::sensorType});
254 }
255 
interfaceRemoved(sdbusplus::message_t & message,NVMEMap & contexts)256 static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& contexts)
257 {
258     sdbusplus::message::object_path path;
259     std::vector<std::string> interfaces;
260 
261     message.read(path, interfaces);
262 
263     for (auto& [_, context] : contexts)
264     {
265         std::optional<std::shared_ptr<NVMeSensor>> sensor =
266             context->getSensorAtPath(path);
267         if (!sensor)
268         {
269             continue;
270         }
271 
272         auto interface = std::find(interfaces.begin(), interfaces.end(),
273                                    (*sensor)->configInterface);
274         if (interface == interfaces.end())
275         {
276             continue;
277         }
278 
279         context->removeSensor(sensor.value());
280     }
281 }
282 
main()283 int main()
284 {
285     boost::asio::io_context io;
286     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
287     systemBus->request_name("xyz.openbmc_project.NVMeSensor");
288     sdbusplus::asio::object_server objectServer(systemBus, true);
289     objectServer.add_manager("/xyz/openbmc_project/sensors");
290 
291     boost::asio::post(io,
292                       [&]() { createSensors(io, objectServer, systemBus); });
293 
294     boost::asio::steady_timer filterTimer(io);
295     std::function<void(sdbusplus::message_t&)> eventHandler =
296         [&filterTimer, &io, &objectServer, &systemBus](sdbusplus::message_t&) {
297             // this implicitly cancels the timer
298             filterTimer.expires_after(std::chrono::seconds(1));
299 
300             filterTimer.async_wait([&](const boost::system::error_code& ec) {
301                 if (ec == boost::asio::error::operation_aborted)
302                 {
303                     return; // we're being canceled
304                 }
305 
306                 if (ec)
307                 {
308                     lg2::error("Error: '{ERROR_MESSAGE}'", "ERROR_MESSAGE",
309                                ec.message());
310                     return;
311                 }
312 
313                 createSensors(io, objectServer, systemBus);
314             });
315         };
316 
317     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
318         setupPropertiesChangedMatches(
319             *systemBus, std::to_array<const char*>({NVMeSensor::sensorType}),
320             eventHandler);
321 
322     // Watch for entity-manager to remove configuration interfaces
323     // so the corresponding sensors can be removed.
324     auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
325         static_cast<sdbusplus::bus_t&>(*systemBus),
326         "type='signal',member='InterfacesRemoved',arg0path='" +
327             std::string(inventoryPath) + "/'",
328         [](sdbusplus::message_t& msg) {
329             interfaceRemoved(msg, nvmeDeviceMap);
330         });
331 
332     setupManufacturingModeMatch(*systemBus);
333     io.run();
334 }
335