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
deriveRootBusPath(int busNumber)103 static std::filesystem::path deriveRootBusPath(int busNumber)
104 {
105 return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) +
106 "/mux_device";
107 }
108
deriveRootBus(std::optional<int> busNumber)109 static std::optional<int> deriveRootBus(std::optional<int> busNumber)
110 {
111 if (!busNumber)
112 {
113 return std::nullopt;
114 }
115
116 std::filesystem::path muxPath = deriveRootBusPath(*busNumber);
117
118 if (!std::filesystem::is_symlink(muxPath))
119 {
120 return busNumber;
121 }
122
123 std::string rootName = std::filesystem::read_symlink(muxPath).filename();
124 size_t dash = rootName.find('-');
125 if (dash == std::string::npos)
126 {
127 lg2::error("Error finding root bus for '{NAME}'", "NAME", rootName);
128 return std::nullopt;
129 }
130
131 return std::stoi(rootName.substr(0, dash));
132 }
133
provideRootBusContext(boost::asio::io_context & io,NVMEMap & map,int rootBus)134 static std::shared_ptr<NVMeContext> provideRootBusContext(
135 boost::asio::io_context& io, NVMEMap& map, int rootBus)
136 {
137 auto findRoot = map.find(rootBus);
138 if (findRoot != map.end())
139 {
140 return findRoot->second;
141 }
142
143 std::shared_ptr<NVMeContext> context =
144 std::make_shared<NVMeBasicContext>(io, rootBus);
145 map[rootBus] = context;
146
147 return context;
148 }
149
handleSensorConfigurations(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const ManagedObjectType & sensorConfigurations)150 static void handleSensorConfigurations(
151 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
152 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
153 const ManagedObjectType& sensorConfigurations)
154 {
155 // todo: it'd be better to only update the ones we care about
156 for (const auto& [_, nvmeContextPtr] : nvmeDeviceMap)
157 {
158 if (nvmeContextPtr)
159 {
160 nvmeContextPtr->close();
161 }
162 }
163 nvmeDeviceMap.clear();
164
165 // iterate through all found configurations
166 for (const auto& [interfacePath, sensorData] : sensorConfigurations)
167 {
168 // find base configuration
169 auto sensorBase =
170 sensorData.find(configInterfaceName(NVMeSensor::sensorType));
171 if (sensorBase == sensorData.end())
172 {
173 continue;
174 }
175
176 const SensorBaseConfigMap& sensorConfig = sensorBase->second;
177 std::optional<int> busNumber =
178 extractBusNumber(interfacePath, sensorConfig);
179 std::optional<std::string> sensorName =
180 extractSensorName(interfacePath, sensorConfig);
181 uint8_t slaveAddr = extractSlaveAddr(interfacePath, sensorConfig);
182 std::optional<int> rootBus = deriveRootBus(busNumber);
183
184 if (!(busNumber && sensorName && rootBus))
185 {
186 continue;
187 }
188
189 std::vector<thresholds::Threshold> sensorThresholds;
190 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
191 {
192 lg2::error("error populating thresholds for '{NAME}'", "NAME",
193 *sensorName);
194 }
195
196 try
197 {
198 // May throw for an invalid rootBus
199 std::shared_ptr<NVMeContext> context =
200 provideRootBusContext(io, nvmeDeviceMap, *rootBus);
201
202 // Construct the sensor after grabbing the context so we don't
203 // glitch D-Bus May throw for an invalid busNumber
204 std::shared_ptr<NVMeSensor> sensorPtr =
205 std::make_shared<NVMeSensor>(
206 objectServer, io, dbusConnection, *sensorName,
207 std::move(sensorThresholds), interfacePath, *busNumber,
208 slaveAddr);
209
210 context->addSensor(sensorPtr);
211 }
212 catch (const std::invalid_argument& ex)
213 {
214 lg2::error("Failed to add sensor for '{PATH}': '{ERROR}'", "PATH",
215 interfacePath.str, "ERROR", ex);
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 lg2::error("interfacesRemoved callback method error");
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 lg2::error("Error: '{ERROR_MESSAGE}'", "ERROR_MESSAGE",
296 ec.message());
297 return;
298 }
299
300 createSensors(io, objectServer, systemBus);
301 });
302 };
303
304 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
305 setupPropertiesChangedMatches(
306 *systemBus, std::to_array<const char*>({NVMeSensor::sensorType}),
307 eventHandler);
308
309 // Watch for entity-manager to remove configuration interfaces
310 // so the corresponding sensors can be removed.
311 auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
312 static_cast<sdbusplus::bus_t&>(*systemBus),
313 "type='signal',member='InterfacesRemoved',arg0path='" +
314 std::string(inventoryPath) + "/'",
315 [](sdbusplus::message_t& msg) {
316 interfaceRemoved(msg, nvmeDeviceMap);
317 });
318
319 setupManufacturingModeMatch(*systemBus);
320 io.run();
321 }
322