1 #include <fcntl.h>
2 #include <unistd.h>
3 #include <sys/ioctl.h>
4 #include <errno.h>
5 #include <phosphor-logging/log.hpp>
6 #include <phosphor-logging/elog.hpp>
7 #include <xyz/openbmc_project/Common/error.hpp>
8 #include <org/open_power/OCC/Device/error.hpp>
9 #include "occ_errors.hpp"
10 #include "elog-errors.hpp"
11 namespace open_power
12 {
13 namespace occ
14 {
15 
16 // Value in error file indicating success
17 constexpr auto NO_ERROR = '0';
18 
19 using namespace phosphor::logging;
20 using namespace sdbusplus::org::open_power::OCC::Device::Error;
21 using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
22                                 Error::InternalFailure;
23 
24 // Populate the file descriptor on the error file
25 void Error::openFile()
26 {
27     using namespace phosphor::logging;
28 
29     fd = open(file.c_str(), O_RDONLY | O_NONBLOCK);
30     if (fd < 0)
31     {
32         elog<OpenFailure>(
33             phosphor::logging::org::open_power::OCC::Device::
34                 OpenFailure::CALLOUT_ERRNO(errno),
35             phosphor::logging::org::open_power::OCC::Device::
36                 OpenFailure::CALLOUT_DEVICE_PATH(file.c_str()));
37     }
38 }
39 
40 // Attaches the FD to event loop and registers the callback handler
41 void Error::registerCallBack()
42 {
43     decltype(eventSource.get()) sourcePtr = nullptr;
44     auto r = sd_event_add_io(event.get(), &sourcePtr, fd,
45                              EPOLLPRI | EPOLLERR, processEvents, this);
46     eventSource.reset(sourcePtr);
47 
48     if (r < 0)
49     {
50         log<level::ERR>("Failed to register callback handler",
51                 entry("ERROR=%s", strerror(-r)));
52         elog<InternalFailure>();
53     }
54 }
55 
56 // Starts to watch for errors
57 void Error::addWatch()
58 {
59     if (!watching)
60     {
61         // Open the file
62         openFile();
63 
64         // register the callback handler
65         registerCallBack();
66 
67         // Set we are watching the error
68         watching = true;
69     }
70 }
71 
72 // Stops watching for errors
73 void Error::removeWatch()
74 {
75     if (watching)
76     {
77         // Close the file
78         if (fd >= 0)
79         {
80             close(fd);
81         }
82 
83         // Reduce the reference count. Since there is only one instances
84         // of add_io, this will result empty loop
85         eventSource.reset();
86 
87         // We are no more watching the error
88         watching = false;
89     }
90 }
91 
92 // Callback handler when there is an activity on the FD
93 int Error::processEvents(sd_event_source* es, int fd,
94                          uint32_t revents, void* userData)
95 {
96     auto error = static_cast<Error*>(userData);
97 
98     error->analyzeEvent();
99     return 0;
100 }
101 
102 // Reads the error file and analyzes the data
103 void Error::analyzeEvent()
104 {
105     // Get the number of bytes to read
106     int len = -1;
107     auto r = ioctl(fd, FIONREAD, &len);
108     if (r < 0)
109     {
110         elog<ConfigFailure>(
111             phosphor::logging::org::open_power::OCC::Device::
112                 ConfigFailure::CALLOUT_ERRNO(errno),
113             phosphor::logging::org::open_power::OCC::Device::
114                 ConfigFailure::CALLOUT_DEVICE_PATH(file.c_str()));
115     }
116 
117     // A non-zero data indicates an error condition
118     // Let the caller take appropriate action on this
119     auto data = readFile(len);
120     bool error = !(data.empty() || data.front() == NO_ERROR);
121     if (callBack)
122     {
123         callBack(error);
124     }
125     return;
126 }
127 
128 // Reads so many bytes as passed in
129 std::string Error::readFile(int len) const
130 {
131     auto data = std::make_unique<char[]>(len+1);
132     auto retries = 3;
133     auto delay = std::chrono::milliseconds{100};
134 
135     // OCC / FSI have intermittent issues so retry all reads
136     while (true)
137     {
138         // This file get created soon after binding. A value of 0 is
139         // deemed success and anything else is a Failure
140         // Since all the sysfs files would have size of 4096, if we read 0
141         // bytes -or- value '0', then it just means we are fine
142         auto r = read(fd, data.get(), len);
143         if (r < 0)
144         {
145             retries--;
146             if (retries == 0)
147             {
148                 elog<ReadFailure>(
149                     phosphor::logging::org::open_power::OCC::Device::
150                         ReadFailure::CALLOUT_ERRNO(errno),
151                     phosphor::logging::org::open_power::OCC::Device::
152                         ReadFailure::CALLOUT_DEVICE_PATH(file.c_str()));
153                 break;
154             }
155             std::this_thread::sleep_for(delay);
156             continue;
157         }
158         break;
159     }
160     // Need to seek to START, else the poll returns immediately telling
161     // there is data to be read
162     auto r = lseek(fd, 0, SEEK_SET);
163     if (r < 0)
164     {
165         log<level::ERR>("Failure seeking error file to START");
166         elog<ConfigFailure>(
167             phosphor::logging::org::open_power::OCC::Device::
168                 ConfigFailure::CALLOUT_ERRNO(errno),
169             phosphor::logging::org::open_power::OCC::Device::
170                 ConfigFailure::CALLOUT_DEVICE_PATH(file.c_str()));
171     }
172     return std::string(data.get());
173 }
174 
175 } // namespace occ
176 } // namespace open_power
177