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(boost::asio::posix::stream_descriptor::wait_read,
203                        [this](const boost::system::error_code& ec) {
204         if (ec == boost::system::errc::bad_file_descriptor)
205         {
206             return; // we're being destroyed
207         }
208 
209         if (ec)
210         {
211             std::cerr << "Error on GPIO based intrusion sensor wait event\n";
212         }
213         else
214         {
215             int value = readSensor();
216             if (value >= 0)
217             {
218                 updateValue(value);
219             }
220             // trigger next polling
221             pollSensorStatus();
222         }
223     });
224 }
225 
readSensor()226 int ChassisIntrusionHwmonSensor::readSensor()
227 {
228     int value = 0;
229 
230     std::fstream stream(mHwmonPath, std::ios::in | std::ios::out);
231     if (!stream.good())
232     {
233         std::cerr << "Error reading status at " << mHwmonPath << "\n";
234         return -1;
235     }
236 
237     std::string line;
238     if (!std::getline(stream, line))
239     {
240         std::cerr << "Error reading status at " << mHwmonPath << "\n";
241         return -1;
242     }
243 
244     try
245     {
246         value = std::stoi(line);
247         if constexpr (debug)
248         {
249             std::cout << "Hwmon type: raw value is " << value << "\n";
250         }
251     }
252     catch (const std::invalid_argument& e)
253     {
254         std::cerr << "Error reading status at " << mHwmonPath << " : "
255                   << e.what() << "\n";
256         return -1;
257     }
258 
259     // Reset chassis intrusion status after every reading
260     stream << intrusionStatusHwmonClearValue;
261 
262     return value;
263 }
264 
pollSensorStatus()265 void ChassisIntrusionHwmonSensor::pollSensorStatus()
266 {
267     std::weak_ptr<ChassisIntrusionHwmonSensor> weakRef = weak_from_this();
268 
269     // setting a new experation implicitly cancels any pending async wait
270     mPollTimer.expires_after(std::chrono::seconds(intrusionSensorPollSec));
271 
272     mPollTimer.async_wait([weakRef](const boost::system::error_code& ec) {
273         // case of being canceled
274         if (ec == boost::asio::error::operation_aborted)
275         {
276             std::cerr << "Timer of intrusion sensor is cancelled\n";
277             return;
278         }
279 
280         std::shared_ptr<ChassisIntrusionHwmonSensor> self = weakRef.lock();
281         if (!self)
282         {
283             std::cerr << "ChassisIntrusionSensor no self\n";
284             return;
285         }
286 
287         int value = self->readSensor();
288         if (value < 0)
289         {
290             intrusionSensorPollSec = sensorFailedPollSec;
291         }
292         else
293         {
294             intrusionSensorPollSec = defaultPollSec;
295             self->updateValue(value);
296         }
297 
298         // trigger next polling
299         self->pollSensorStatus();
300     });
301 }
302 
setSensorValue(const std::string & req,std::string & propertyValue)303 int ChassisIntrusionSensor::setSensorValue(const std::string& req,
304                                            std::string& propertyValue)
305 {
306     if (!mInternalSet)
307     {
308         /*
309            1. Assuming that setting property in Automatic mode causes
310            no effect but only event logs and propertiesChanged signal
311            (because the property will be updated continuously to the
312            current hardware status anyway), only update Status property
313            and raise rearm flag in Manual rearm mode.
314            2. Only accept Normal value from an external call.
315         */
316         if (!mAutoRearm && req == normalValStr)
317         {
318             mRearmFlag = true;
319             propertyValue = req;
320             mOverridenState = true;
321         }
322     }
323     else if (!mOverridenState)
324     {
325         propertyValue = req;
326     }
327     else
328     {
329         return 1;
330     }
331     // Send intrusion event to Redfish
332     if (mValue == normalValStr && propertyValue != normalValStr)
333     {
334         sd_journal_send("MESSAGE=%s", "Chassis intrusion assert event",
335                         "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
336                         "OpenBMC.0.1.ChassisIntrusionDetected", NULL);
337     }
338     else if (mValue == hwIntrusionValStr && propertyValue == normalValStr)
339     {
340         sd_journal_send("MESSAGE=%s", "Chassis intrusion de-assert event",
341                         "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
342                         "OpenBMC.0.1.ChassisIntrusionReset", NULL);
343     }
344     return 1;
345 }
346 
start()347 void ChassisIntrusionSensor::start()
348 {
349     mIface->register_property(
350         "Status", mValue,
351         [&](const std::string& req, std::string& propertyValue) {
352         return setSensorValue(req, propertyValue);
353     });
354     std::string rearmStr = mAutoRearm ? autoRearmStr : manualRearmStr;
355     mIface->register_property("Rearm", rearmStr);
356     mIface->initialize();
357     pollSensorStatus();
358 }
359 
ChassisIntrusionSensor(bool autoRearm,sdbusplus::asio::object_server & objServer)360 ChassisIntrusionSensor::ChassisIntrusionSensor(
361     bool autoRearm, sdbusplus::asio::object_server& objServer) :
362     mValue(normalValStr),
363     mAutoRearm(autoRearm), mObjServer(objServer)
364 {
365     mIface = mObjServer.add_interface("/xyz/openbmc_project/Chassis/Intrusion",
366                                       "xyz.openbmc_project.Chassis.Intrusion");
367 }
368 
ChassisIntrusionPchSensor(bool autoRearm,boost::asio::io_context & io,sdbusplus::asio::object_server & objServer,int busId,int slaveAddr)369 ChassisIntrusionPchSensor::ChassisIntrusionPchSensor(
370     bool autoRearm, boost::asio::io_context& io,
371     sdbusplus::asio::object_server& objServer, int busId, int slaveAddr) :
372     ChassisIntrusionSensor(autoRearm, objServer),
373     mPollTimer(io)
374 {
375     if (busId < 0 || slaveAddr <= 0)
376     {
377         throw std::invalid_argument("Invalid i2c bus " + std::to_string(busId) +
378                                     " address " + std::to_string(slaveAddr) +
379                                     "\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),
417     mGpioInverted(gpioInverted), mGpioFd(io)
418 {
419     mGpioLine = gpiod::find_line(mPinName);
420     if (!mGpioLine)
421     {
422         throw std::invalid_argument("Error finding gpio pin name: " + mPinName +
423                                     "\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("Hwmon file " + mHwmonName +
454                                     " 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