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(
203 boost::asio::posix::stream_descriptor::wait_read,
204 [this](const boost::system::error_code& ec) {
205 if (ec == boost::system::errc::bad_file_descriptor)
206 {
207 return; // we're being destroyed
208 }
209
210 if (ec)
211 {
212 std::cerr
213 << "Error on GPIO based intrusion sensor wait event\n";
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 std::cerr << "Error reading status at " << mHwmonPath << "\n";
236 return -1;
237 }
238
239 std::string line;
240 if (!std::getline(stream, line))
241 {
242 std::cerr << "Error reading status at " << mHwmonPath << "\n";
243 return -1;
244 }
245
246 try
247 {
248 value = std::stoi(line);
249 if constexpr (debug)
250 {
251 std::cout << "Hwmon type: raw value is " << value << "\n";
252 }
253 }
254 catch (const std::invalid_argument& e)
255 {
256 std::cerr << "Error reading status at " << mHwmonPath << " : "
257 << e.what() << "\n";
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 std::cerr << "Timer of intrusion sensor is cancelled\n";
279 return;
280 }
281
282 std::shared_ptr<ChassisIntrusionHwmonSensor> self = weakRef.lock();
283 if (!self)
284 {
285 std::cerr << "ChassisIntrusionSensor no self\n";
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<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(
454 "Hwmon file " + mHwmonName + " 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