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