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