xref: /openbmc/dbus-sensors/src/intrusion/ChassisIntrusionSensor.cpp (revision 1e47dcf2577670a56f7552261f35492bdcfae637)
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