1 /*
2 // Copyright (c) 2018 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 "ChassisIntrusionSensor.hpp"
18
19 #include <fcntl.h>
20 #include <linux/i2c.h>
21 #include <sys/ioctl.h>
22 #include <sys/syslog.h>
23 #include <systemd/sd-journal.h>
24 #include <unistd.h>
25
26 #include <Utils.hpp>
27 #include <boost/asio/error.hpp>
28 #include <boost/asio/io_context.hpp>
29 #include <boost/asio/posix/stream_descriptor.hpp>
30 #include <gpiod.hpp>
31 #include <phosphor-logging/lg2.hpp>
32 #include <sdbusplus/asio/object_server.hpp>
33
34 #include <chrono>
35 #include <cstddef>
36 #include <cstdint>
37 #include <filesystem>
38 #include <fstream>
39 #include <iostream>
40 #include <memory>
41 #include <stdexcept>
42 #include <string>
43 #include <utility>
44 #include <vector>
45
46 extern "C"
47 {
48 #include <i2c/smbus.h>
49 #include <linux/i2c-dev.h>
50 }
51
52 static constexpr bool debug = false;
53
54 static constexpr unsigned int defaultPollSec = 1;
55 static constexpr unsigned int sensorFailedPollSec = 5;
56 static unsigned int intrusionSensorPollSec = defaultPollSec;
57 static constexpr const char* hwIntrusionValStr =
58 "xyz.openbmc_project.Chassis.Intrusion.Status.HardwareIntrusion";
59 static constexpr const char* normalValStr =
60 "xyz.openbmc_project.Chassis.Intrusion.Status.Normal";
61 static constexpr const char* manualRearmStr =
62 "xyz.openbmc_project.Chassis.Intrusion.RearmMode.Manual";
63 static constexpr const char* autoRearmStr =
64 "xyz.openbmc_project.Chassis.Intrusion.RearmMode.Automatic";
65
66 // SMLink Status Register
67 const static constexpr size_t pchStatusRegIntrusion = 0x04;
68
69 // Status bit field masks
70 const static constexpr size_t pchRegMaskIntrusion = 0x01;
71
72 // Value to clear intrusion status hwmon file
73 const static constexpr size_t intrusionStatusHwmonClearValue = 0;
74
updateValue(const size_t & value)75 void ChassisIntrusionSensor::updateValue(const size_t& value)
76 {
77 std::string newValue = value != 0 ? hwIntrusionValStr : normalValStr;
78
79 // Take no action if the hardware status does not change
80 // Same semantics as Sensor::updateValue(const double&)
81 if (newValue == mValue)
82 {
83 return;
84 }
85
86 if constexpr (debug)
87 {
88 lg2::info("Update value from '{VALUE}' to '{NEWVALUE}'", "VALUE",
89 mValue, "NEWVALUE", newValue);
90 }
91
92 // Automatic Rearm mode allows direct update
93 // Manual Rearm mode requires a rearm action to clear the intrusion
94 // status
95 if (!mAutoRearm)
96 {
97 if (newValue == normalValStr)
98 {
99 // Chassis is first closed from being open. If it has been
100 // rearmed externally, reset the flag, update mValue and
101 // return, without having to write "Normal" to DBus property
102 // (because the rearm action already did).
103 // Otherwise, return with no more action.
104 if (mRearmFlag)
105 {
106 mRearmFlag = false;
107 mValue = newValue;
108 }
109 return;
110 }
111 }
112
113 // Flush the rearm flag everytime it allows an update to Dbus
114 mRearmFlag = false;
115
116 // indicate that it is internal set call
117 mOverridenState = false;
118 mInternalSet = true;
119 mIface->set_property("Status", newValue);
120 mInternalSet = false;
121
122 mValue = newValue;
123 }
124
readSensor()125 int ChassisIntrusionPchSensor::readSensor()
126 {
127 int32_t statusMask = pchRegMaskIntrusion;
128 int32_t statusReg = pchStatusRegIntrusion;
129
130 int32_t value = i2c_smbus_read_byte_data(mBusFd, statusReg);
131 if constexpr (debug)
132 {
133 lg2::info("Pch type: raw value is '{VALUE}'", "VALUE", value);
134 }
135
136 if (value < 0)
137 {
138 lg2::error("i2c_smbus_read_byte_data failed");
139 return -1;
140 }
141
142 // Get status value with mask
143 value &= statusMask;
144
145 if constexpr (debug)
146 {
147 lg2::info("Pch type: masked raw value is '{VALUE}'", "VALUE", value);
148 }
149 return value;
150 }
151
pollSensorStatus()152 void ChassisIntrusionPchSensor::pollSensorStatus()
153 {
154 std::weak_ptr<ChassisIntrusionPchSensor> weakRef = weak_from_this();
155
156 // setting a new experation implicitly cancels any pending async wait
157 mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec));
158
159 mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) {
160 // case of being canceled
161 if (ec == boost::asio::error::operation_aborted)
162 {
163 lg2::error("Timer of intrusion sensor is cancelled");
164 return;
165 }
166
167 std::shared_ptr<ChassisIntrusionPchSensor> self = weakRef.lock();
168 if (!self)
169 {
170 lg2::error("ChassisIntrusionSensor no self");
171 return;
172 }
173
174 int value = self->readSensor();
175 if (value < 0)
176 {
177 intrusionSensorPollSec = sensorFailedPollSec;
178 }
179 else
180 {
181 intrusionSensorPollSec = defaultPollSec;
182 self->updateValue(value);
183 }
184
185 // trigger next polling
186 self->pollSensorStatus();
187 });
188 }
189
readSensor()190 int ChassisIntrusionGpioSensor::readSensor()
191 {
192 mGpioLine.event_read();
193 auto value = mGpioLine.get_value();
194 if constexpr (debug)
195 {
196 lg2::info("Gpio type: raw value is '{VALUE}'", "VALUE", value);
197 }
198 return value;
199 }
200
pollSensorStatus()201 void ChassisIntrusionGpioSensor::pollSensorStatus()
202 {
203 mGpioFd.async_wait(
204 boost::asio::posix::stream_descriptor::wait_read,
205 [this](const boost::system::error_code& ec) {
206 if (ec == boost::system::errc::bad_file_descriptor)
207 {
208 return; // we're being destroyed
209 }
210
211 if (ec)
212 {
213 lg2::error("Error on GPIO based intrusion sensor wait event");
214 }
215 else
216 {
217 int value = readSensor();
218 if (value >= 0)
219 {
220 updateValue(value);
221 }
222 // trigger next polling
223 pollSensorStatus();
224 }
225 });
226 }
227
readSensor()228 int ChassisIntrusionHwmonSensor::readSensor()
229 {
230 int value = 0;
231
232 std::fstream stream(mHwmonPath, std::ios::in | std::ios::out);
233 if (!stream.good())
234 {
235 lg2::error("Error reading status at '{PATH}'", "PATH", mHwmonPath);
236 return -1;
237 }
238
239 std::string line;
240 if (!std::getline(stream, line))
241 {
242 lg2::error("Error reading status at '{PATH}'", "PATH", mHwmonPath);
243 return -1;
244 }
245
246 try
247 {
248 value = std::stoi(line);
249 if constexpr (debug)
250 {
251 lg2::info("Hwmon type: raw value is '{VALUE}'", "VALUE", value);
252 }
253 }
254 catch (const std::invalid_argument& e)
255 {
256 lg2::error("Error reading status at '{PATH}': '{ERR}'", "PATH",
257 mHwmonPath, "ERR", e);
258 return -1;
259 }
260
261 // Reset chassis intrusion status after every reading
262 stream << intrusionStatusHwmonClearValue;
263
264 return value;
265 }
266
pollSensorStatus()267 void ChassisIntrusionHwmonSensor::pollSensorStatus()
268 {
269 std::weak_ptr<ChassisIntrusionHwmonSensor> weakRef = weak_from_this();
270
271 // setting a new experation implicitly cancels any pending async wait
272 mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec));
273
274 mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) {
275 // case of being canceled
276 if (ec == boost::asio::error::operation_aborted)
277 {
278 lg2::error("Timer of intrusion sensor is cancelled");
279 return;
280 }
281
282 std::shared_ptr<ChassisIntrusionHwmonSensor> self = weakRef.lock();
283 if (!self)
284 {
285 lg2::error("ChassisIntrusionSensor no self");
286 return;
287 }
288
289 int value = self->readSensor();
290 if (value < 0)
291 {
292 intrusionSensorPollSec = sensorFailedPollSec;
293 }
294 else
295 {
296 intrusionSensorPollSec = defaultPollSec;
297 self->updateValue(value);
298 }
299
300 // trigger next polling
301 self->pollSensorStatus();
302 });
303 }
304
setSensorValue(const std::string & req,std::string & propertyValue)305 int ChassisIntrusionSensor::setSensorValue(const std::string& req,
306 std::string& propertyValue)
307 {
308 if (!mInternalSet)
309 {
310 /*
311 1. Assuming that setting property in Automatic mode causes
312 no effect but only event logs and propertiesChanged signal
313 (because the property will be updated continuously to the
314 current hardware status anyway), only update Status property
315 and raise rearm flag in Manual rearm mode.
316 2. Only accept Normal value from an external call.
317 */
318 if (!mAutoRearm && req == normalValStr)
319 {
320 mRearmFlag = true;
321 propertyValue = req;
322 mOverridenState = true;
323 }
324 }
325 else if (!mOverridenState)
326 {
327 propertyValue = req;
328 }
329 else
330 {
331 return 1;
332 }
333 // Send intrusion event to Redfish
334 if (mValue == normalValStr && propertyValue != normalValStr)
335 {
336 sd_journal_send("MESSAGE=%s", "Chassis intrusion assert event",
337 "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
338 "OpenBMC.0.1.ChassisIntrusionDetected", NULL);
339 }
340 else if (mValue == hwIntrusionValStr && propertyValue == normalValStr)
341 {
342 sd_journal_send("MESSAGE=%s", "Chassis intrusion de-assert event",
343 "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
344 "OpenBMC.0.1.ChassisIntrusionReset", NULL);
345 }
346 return 1;
347 }
348
start()349 void ChassisIntrusionSensor::start()
350 {
351 mIface->register_property(
352 "Status", mValue,
353 [&](const std::string& req, std::string& propertyValue) {
354 return setSensorValue(req, propertyValue);
355 });
356 std::string rearmStr = mAutoRearm ? autoRearmStr : manualRearmStr;
357 mIface->register_property("Rearm", rearmStr);
358 mIface->initialize();
359 pollSensorStatus();
360 }
361
ChassisIntrusionSensor(bool autoRearm,sdbusplus::asio::object_server & objServer)362 ChassisIntrusionSensor::ChassisIntrusionSensor(
363 bool autoRearm, sdbusplus::asio::object_server& objServer) :
364 mValue(normalValStr), mAutoRearm(autoRearm), mObjServer(objServer)
365 {
366 mIface = mObjServer.add_interface("/xyz/openbmc_project/Chassis/Intrusion",
367 "xyz.openbmc_project.Chassis.Intrusion");
368 }
369
ChassisIntrusionPchSensor(bool autoRearm,boost::asio::io_context & io,sdbusplus::asio::object_server & objServer,int busId,int slaveAddr)370 ChassisIntrusionPchSensor::ChassisIntrusionPchSensor(
371 bool autoRearm, boost::asio::io_context& io,
372 sdbusplus::asio::object_server& objServer, int busId, int slaveAddr) :
373 ChassisIntrusionSensor(autoRearm, objServer), mPollTimer(io)
374 {
375 if (busId < 0 || slaveAddr <= 0)
376 {
377 throw std::invalid_argument(
378 "Invalid i2c bus " + std::to_string(busId) + " address " +
379 std::to_string(slaveAddr) + "\n");
380 }
381
382 mSlaveAddr = slaveAddr;
383
384 std::string devPath = "/dev/i2c-" + std::to_string(busId);
385 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
386 mBusFd = open(devPath.c_str(), O_RDWR | O_CLOEXEC);
387 if (mBusFd < 0)
388 {
389 throw std::invalid_argument("Unable to open " + devPath + "\n");
390 }
391
392 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
393 if (ioctl(mBusFd, I2C_SLAVE_FORCE, mSlaveAddr) < 0)
394 {
395 throw std::runtime_error("Unable to set device address\n");
396 }
397
398 unsigned long funcs = 0;
399
400 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
401 if (ioctl(mBusFd, I2C_FUNCS, &funcs) < 0)
402 {
403 throw std::runtime_error("Don't support I2C_FUNCS\n");
404 }
405
406 if ((funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA) == 0U)
407 {
408 throw std::runtime_error(
409 "Do not have I2C_FUNC_SMBUS_READ_BYTE_DATA \n");
410 }
411 }
412
ChassisIntrusionGpioSensor(bool autoRearm,boost::asio::io_context & io,sdbusplus::asio::object_server & objServer,bool gpioInverted)413 ChassisIntrusionGpioSensor::ChassisIntrusionGpioSensor(
414 bool autoRearm, boost::asio::io_context& io,
415 sdbusplus::asio::object_server& objServer, bool gpioInverted) :
416 ChassisIntrusionSensor(autoRearm, objServer), mGpioInverted(gpioInverted),
417 mGpioFd(io)
418 {
419 mGpioLine = gpiod::find_line(mPinName);
420 if (!mGpioLine)
421 {
422 throw std::invalid_argument(
423 "Error finding gpio pin name: " + mPinName + "\n");
424 }
425 mGpioLine.request(
426 {"ChassisIntrusionSensor", gpiod::line_request::EVENT_BOTH_EDGES,
427 mGpioInverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
428
429 auto gpioLineFd = mGpioLine.event_get_fd();
430 if (gpioLineFd < 0)
431 {
432 throw std::invalid_argument("Failed to get " + mPinName + " fd\n");
433 }
434
435 mGpioFd.assign(gpioLineFd);
436 }
437
ChassisIntrusionHwmonSensor(bool autoRearm,boost::asio::io_context & io,sdbusplus::asio::object_server & objServer,std::string hwmonName)438 ChassisIntrusionHwmonSensor::ChassisIntrusionHwmonSensor(
439 bool autoRearm, boost::asio::io_context& io,
440 sdbusplus::asio::object_server& objServer, std::string hwmonName) :
441 ChassisIntrusionSensor(autoRearm, objServer),
442 mHwmonName(std::move(hwmonName)), mPollTimer(io)
443 {
444 std::vector<std::filesystem::path> paths;
445
446 if (!findFiles(std::filesystem::path("/sys/class/hwmon"), mHwmonName,
447 paths))
448 {
449 throw std::invalid_argument("Failed to find hwmon path in sysfs\n");
450 }
451
452 if (paths.empty())
453 {
454 throw std::invalid_argument(
455 "Hwmon file " + mHwmonName + " can't be found in sysfs\n");
456 }
457
458 if (paths.size() > 1)
459 {
460 lg2::error("Found more than 1 hwmon file to read chassis intrusion"
461 " status. Taking the first one.");
462 }
463
464 // Expecting only one hwmon file for one given chassis
465 mHwmonPath = paths[0].string();
466
467 if constexpr (debug)
468 {
469 lg2::info(
470 "Found '{NUM_PATHS}' paths for intrusion status. The first path is: '{PATH}'",
471 "NUM_PATHS", paths.size(), "PATH", mHwmonPath);
472 }
473 }
474
~ChassisIntrusionSensor()475 ChassisIntrusionSensor::~ChassisIntrusionSensor()
476 {
477 mObjServer.remove_interface(mIface);
478 }
479
~ChassisIntrusionPchSensor()480 ChassisIntrusionPchSensor::~ChassisIntrusionPchSensor()
481 {
482 mPollTimer.cancel();
483 if (close(mBusFd) < 0)
484 {
485 lg2::error("Failed to close fd '{FD}'", "FD", mBusFd);
486 }
487 }
488
~ChassisIntrusionGpioSensor()489 ChassisIntrusionGpioSensor::~ChassisIntrusionGpioSensor()
490 {
491 mGpioFd.close();
492 if (mGpioLine)
493 {
494 mGpioLine.release();
495 }
496 }
497
~ChassisIntrusionHwmonSensor()498 ChassisIntrusionHwmonSensor::~ChassisIntrusionHwmonSensor()
499 {
500 mPollTimer.cancel();
501 }
502