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