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