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