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