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 <sdbusplus/asio/connection.hpp>
29 #include <sdbusplus/asio/object_server.hpp>
30 #include <sdbusplus/bus.hpp>
31 #include <sdbusplus/bus/match.hpp>
32 #include <sdbusplus/message.hpp>
33 #include <sdbusplus/message/native_types.hpp>
34 
35 #include <algorithm>
36 #include <array>
37 #include <chrono>
38 #include <cstddef>
39 #include <cstdint>
40 #include <filesystem>
41 #include <functional>
42 #include <iostream>
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         std::cerr << "could not determine bus number for " << path << "\n";
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         std::cerr << "could not determine slave address for " << path << "\n"
80                   << "using default as specified in nvme-mi"
81                   << "\n";
82         return nvmeMiDefaultSlaveAddr;
83     }
84 
85     return std::visit(VariantToUnsignedIntVisitor(), findSlaveAddr->second);
86 }
87 
extractSensorName(const std::string & path,const SensorBaseConfigMap & properties)88 static std::optional<std::string> extractSensorName(
89     const std::string& path, const SensorBaseConfigMap& properties)
90 {
91     auto findSensorName = properties.find("Name");
92     if (findSensorName == properties.end())
93     {
94         std::cerr << "could not determine configuration name for " << path
95                   << "\n";
96         return std::nullopt;
97     }
98 
99     return std::get<std::string>(findSensorName->second);
100 }
101 
deriveRootBusPath(int busNumber)102 static std::filesystem::path deriveRootBusPath(int busNumber)
103 {
104     return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
105            "/mux_device";
106 }
107 
deriveRootBus(std::optional<int> busNumber)108 static std::optional<int> deriveRootBus(std::optional<int> busNumber)
109 {
110     if (!busNumber)
111     {
112         return std::nullopt;
113     }
114 
115     std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
116 
117     if (!std::filesystem::is_symlink(muxPath))
118     {
119         return busNumber;
120     }
121 
122     std::string rootName = std::filesystem::read_symlink(muxPath).filename();
123     size_t dash = rootName.find('-');
124     if (dash == std::string::npos)
125     {
126         std::cerr << "Error finding root bus for " << rootName << "\n";
127         return std::nullopt;
128     }
129 
130     return std::stoi(rootName.substr(0, dash));
131 }
132 
provideRootBusContext(boost::asio::io_context & io,NVMEMap & map,int rootBus)133 static std::shared_ptr<NVMeContext> provideRootBusContext(
134     boost::asio::io_context& io, NVMEMap& map, int rootBus)
135 {
136     auto findRoot = map.find(rootBus);
137     if (findRoot != map.end())
138     {
139         return findRoot->second;
140     }
141 
142     std::shared_ptr<NVMeContext> context =
143         std::make_shared<NVMeBasicContext>(io, rootBus);
144     map[rootBus] = context;
145 
146     return context;
147 }
148 
handleSensorConfigurations(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const ManagedObjectType & sensorConfigurations)149 static void handleSensorConfigurations(
150     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
151     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
152     const ManagedObjectType& sensorConfigurations)
153 {
154     // todo: it'd be better to only update the ones we care about
155     for (const auto& [_, nvmeContextPtr] : nvmeDeviceMap)
156     {
157         if (nvmeContextPtr)
158         {
159             nvmeContextPtr->close();
160         }
161     }
162     nvmeDeviceMap.clear();
163 
164     // iterate through all found configurations
165     for (const auto& [interfacePath, sensorData] : sensorConfigurations)
166     {
167         // find base configuration
168         auto sensorBase =
169             sensorData.find(configInterfaceName(NVMeSensor::sensorType));
170         if (sensorBase == sensorData.end())
171         {
172             continue;
173         }
174 
175         const SensorBaseConfigMap& sensorConfig = sensorBase->second;
176         std::optional<int> busNumber =
177             extractBusNumber(interfacePath, sensorConfig);
178         std::optional<std::string> sensorName =
179             extractSensorName(interfacePath, sensorConfig);
180         uint8_t slaveAddr = extractSlaveAddr(interfacePath, sensorConfig);
181         std::optional<int> rootBus = deriveRootBus(busNumber);
182 
183         if (!(busNumber && sensorName && rootBus))
184         {
185             continue;
186         }
187 
188         std::vector<thresholds::Threshold> sensorThresholds;
189         if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
190         {
191             std::cerr << "error populating thresholds for " << *sensorName
192                       << "\n";
193         }
194 
195         try
196         {
197             // May throw for an invalid rootBus
198             std::shared_ptr<NVMeContext> context =
199                 provideRootBusContext(io, nvmeDeviceMap, *rootBus);
200 
201             // Construct the sensor after grabbing the context so we don't
202             // glitch D-Bus May throw for an invalid busNumber
203             std::shared_ptr<NVMeSensor> sensorPtr =
204                 std::make_shared<NVMeSensor>(
205                     objectServer, io, dbusConnection, *sensorName,
206                     std::move(sensorThresholds), interfacePath, *busNumber,
207                     slaveAddr);
208 
209             context->addSensor(sensorPtr);
210         }
211         catch (const std::invalid_argument& ex)
212         {
213             std::cerr << "Failed to add sensor for "
214                       << std::string(interfacePath) << ": " << ex.what()
215                       << "\n";
216         }
217     }
218     for (const auto& [_, context] : nvmeDeviceMap)
219     {
220         context->pollNVMeDevices();
221     }
222 }
223 
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)224 void createSensors(boost::asio::io_context& io,
225                    sdbusplus::asio::object_server& objectServer,
226                    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
227 {
228     auto getter = std::make_shared<GetSensorConfiguration>(
229         dbusConnection, [&io, &objectServer, &dbusConnection](
230                             const ManagedObjectType& sensorConfigurations) {
231             handleSensorConfigurations(io, objectServer, dbusConnection,
232                                        sensorConfigurations);
233         });
234     getter->getConfiguration(std::vector<std::string>{NVMeSensor::sensorType});
235 }
236 
interfaceRemoved(sdbusplus::message_t & message,NVMEMap & contexts)237 static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& contexts)
238 {
239     if (message.is_method_error())
240     {
241         std::cerr << "interfacesRemoved callback method error\n";
242         return;
243     }
244 
245     sdbusplus::message::object_path path;
246     std::vector<std::string> interfaces;
247 
248     message.read(path, interfaces);
249 
250     for (auto& [_, context] : contexts)
251     {
252         std::optional<std::shared_ptr<NVMeSensor>> sensor =
253             context->getSensorAtPath(path);
254         if (!sensor)
255         {
256             continue;
257         }
258 
259         auto interface = std::find(interfaces.begin(), interfaces.end(),
260                                    (*sensor)->configInterface);
261         if (interface == interfaces.end())
262         {
263             continue;
264         }
265 
266         context->removeSensor(sensor.value());
267     }
268 }
269 
main()270 int main()
271 {
272     boost::asio::io_context io;
273     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
274     systemBus->request_name("xyz.openbmc_project.NVMeSensor");
275     sdbusplus::asio::object_server objectServer(systemBus, true);
276     objectServer.add_manager("/xyz/openbmc_project/sensors");
277 
278     boost::asio::post(io,
279                       [&]() { createSensors(io, objectServer, systemBus); });
280 
281     boost::asio::steady_timer filterTimer(io);
282     std::function<void(sdbusplus::message_t&)> eventHandler =
283         [&filterTimer, &io, &objectServer, &systemBus](sdbusplus::message_t&) {
284             // this implicitly cancels the timer
285             filterTimer.expires_after(std::chrono::seconds(1));
286 
287             filterTimer.async_wait([&](const boost::system::error_code& ec) {
288                 if (ec == boost::asio::error::operation_aborted)
289                 {
290                     return; // we're being canceled
291                 }
292 
293                 if (ec)
294                 {
295                     std::cerr << "Error: " << ec.message() << "\n";
296                     return;
297                 }
298 
299                 createSensors(io, objectServer, systemBus);
300             });
301         };
302 
303     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
304         setupPropertiesChangedMatches(
305             *systemBus, std::to_array<const char*>({NVMeSensor::sensorType}),
306             eventHandler);
307 
308     // Watch for entity-manager to remove configuration interfaces
309     // so the corresponding sensors can be removed.
310     auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
311         static_cast<sdbusplus::bus_t&>(*systemBus),
312         "type='signal',member='InterfacesRemoved',arg0path='" +
313             std::string(inventoryPath) + "/'",
314         [](sdbusplus::message_t& msg) {
315             interfaceRemoved(msg, nvmeDeviceMap);
316         });
317 
318     setupManufacturingModeMatch(*systemBus);
319     io.run();
320 }
321