1 /**
2  * Copyright © 2019 IBM 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 #include "host_notifier.hpp"
17 
18 #include <phosphor-logging/log.hpp>
19 
20 namespace openpower::pels
21 {
22 
23 const auto subscriptionName = "PELHostNotifier";
24 const size_t maxRetryAttempts = 15;
25 
26 using namespace phosphor::logging;
27 
28 HostNotifier::HostNotifier(Repository& repo, DataInterfaceBase& dataIface,
29                            std::unique_ptr<HostInterface> hostIface) :
30     _repo(repo),
31     _dataIface(dataIface), _hostIface(std::move(hostIface)),
32     _retryTimer(_hostIface->getEvent(),
33                 std::bind(std::mem_fn(&HostNotifier::retryTimerExpired), this))
34 {
35     // Subscribe to be told about new PELs.
36     _repo.subscribeToAdds(subscriptionName,
37                           std::bind(std::mem_fn(&HostNotifier::newLogCallback),
38                                     this, std::placeholders::_1));
39 
40     // Add any existing PELs to the queue to send them if necessary.
41     _repo.for_each(std::bind(std::mem_fn(&HostNotifier::addPELToQueue), this,
42                              std::placeholders::_1));
43 
44     // Subscribe to be told about host state changes.
45     _dataIface.subscribeToHostStateChange(
46         subscriptionName,
47         std::bind(std::mem_fun(&HostNotifier::hostStateChange), this,
48                   std::placeholders::_1));
49 
50     // Set the function to call when the async reponse is received.
51     _hostIface->setResponseFunction(
52         std::bind(std::mem_fn(&HostNotifier::commandResponse), this,
53                   std::placeholders::_1));
54 
55     // Start sending logs if the host is running
56     if (!_pelQueue.empty() && _dataIface.isHostUp())
57     {
58         doNewLogNotify();
59     }
60 }
61 
62 HostNotifier::~HostNotifier()
63 {
64     _repo.unsubscribeFromAdds(subscriptionName);
65     _dataIface.unsubscribeFromHostStateChange(subscriptionName);
66 }
67 
68 bool HostNotifier::addPELToQueue(const PEL& pel)
69 {
70     if (enqueueRequired(pel.id()))
71     {
72         _pelQueue.push_back(pel.id());
73     }
74 
75     // Return false so that Repo::for_each keeps going.
76     return false;
77 }
78 
79 bool HostNotifier::enqueueRequired(uint32_t id) const
80 {
81     bool required = true;
82     Repository::LogID i{Repository::LogID::Pel{id}};
83 
84     if (auto attributes = _repo.getPELAttributes(i); attributes)
85     {
86         auto a = attributes.value().get();
87 
88         if ((a.hostState == TransmissionState::acked) ||
89             (a.hostState == TransmissionState::badPEL))
90         {
91             required = false;
92         }
93         else if (a.actionFlags.test(hiddenFlagBit) &&
94                  (a.hmcState == TransmissionState::acked))
95         {
96             required = false;
97         }
98         else if (a.actionFlags.test(dontReportToHostFlagBit))
99         {
100             required = false;
101         }
102     }
103     else
104     {
105         using namespace phosphor::logging;
106         log<level::ERR>("Host Enqueue: Unable to find PEL ID in repository",
107                         entry("PEL_ID=0x%X", id));
108         required = false;
109     }
110 
111     return required;
112 }
113 
114 bool HostNotifier::notifyRequired(uint32_t id) const
115 {
116     bool notify = true;
117     Repository::LogID i{Repository::LogID::Pel{id}};
118 
119     if (auto attributes = _repo.getPELAttributes(i); attributes)
120     {
121         // If already acked by the host, don't send again.
122         // (A safety check as it shouldn't get to this point.)
123         auto a = attributes.value().get();
124         if (a.hostState == TransmissionState::acked)
125         {
126             notify = false;
127         }
128         else if (a.actionFlags.test(hiddenFlagBit))
129         {
130             // If hidden and acked (or will be) acked by the HMC,
131             // also don't send it. (HMC management can come and
132             // go at any time)
133             if ((a.hmcState == TransmissionState::acked) ||
134                 _dataIface.isHMCManaged())
135             {
136                 notify = false;
137             }
138         }
139     }
140     else
141     {
142         // Must have been deleted since put on the queue.
143         notify = false;
144     }
145 
146     return notify;
147 }
148 
149 void HostNotifier::newLogCallback(const PEL& pel)
150 {
151     if (!enqueueRequired(pel.id()))
152     {
153         return;
154     }
155 
156     _pelQueue.push_back(pel.id());
157 
158     // TODO: Check if a send is needed now
159 }
160 
161 void HostNotifier::doNewLogNotify()
162 {
163     if (!_dataIface.isHostUp() || _retryTimer.isEnabled())
164     {
165         return;
166     }
167 
168     if (_retryCount >= maxRetryAttempts)
169     {
170         // Give up until a new log comes in.
171         if (_retryCount == maxRetryAttempts)
172         {
173             // If this were to really happen, the PLDM interface
174             // would be down and isolating that shouldn't left to
175             // a logging daemon, so just trace.  Also, this will start
176             // trying again when the next new log comes in.
177             log<level::ERR>(
178                 "PEL Host notifier hit max retry attempts. Giving up for now.",
179                 entry("PEL_ID=0x%X", _pelQueue.front()));
180         }
181         return;
182     }
183 
184     bool doNotify = false;
185     uint32_t id = 0;
186 
187     // Find the PEL to send
188     while (!doNotify && !_pelQueue.empty())
189     {
190         id = _pelQueue.front();
191         _pelQueue.pop_front();
192 
193         if (notifyRequired(id))
194         {
195             doNotify = true;
196         }
197     }
198 
199     if (doNotify)
200     {
201         // Get the size using the repo attributes
202         Repository::LogID i{Repository::LogID::Pel{id}};
203         if (auto attributes = _repo.getPELAttributes(i); attributes)
204         {
205             auto size = static_cast<size_t>(
206                 std::filesystem::file_size((*attributes).get().path));
207             auto rc = _hostIface->sendNewLogCmd(id, size);
208 
209             if (rc == CmdStatus::success)
210             {
211                 _inProgressPEL = id;
212             }
213             else
214             {
215                 // It failed.  Retry
216                 log<level::ERR>("PLDM send failed", entry("PEL_ID=0x%X", id));
217                 _pelQueue.push_front(id);
218                 _inProgressPEL = 0;
219                 _retryTimer.restartOnce(_hostIface->getSendRetryDelay());
220             }
221         }
222         else
223         {
224             log<level::ERR>("PEL ID not in repository.  Cannot notify host",
225                             entry("PEL_ID=0x%X", id));
226         }
227     }
228 }
229 
230 void HostNotifier::hostStateChange(bool hostUp)
231 {
232     _retryCount = 0;
233 
234     if (hostUp && !_pelQueue.empty())
235     {
236         doNewLogNotify();
237     }
238     else if (!hostUp)
239     {
240         stopCommand();
241 
242         // Reset the state on any PELs that were sent but not acked back
243         // to new so they'll get sent again.
244         for (auto id : _sentPELs)
245         {
246             _pelQueue.push_back(id);
247             _repo.setPELHostTransState(id, TransmissionState::newPEL);
248         }
249 
250         _sentPELs.clear();
251     }
252 }
253 
254 void HostNotifier::commandResponse(ResponseStatus status)
255 {
256     auto id = _inProgressPEL;
257     _inProgressPEL = 0;
258 
259     if (status == ResponseStatus::success)
260     {
261         _retryCount = 0;
262 
263         _sentPELs.push_back(id);
264 
265         _repo.setPELHostTransState(id, TransmissionState::sent);
266 
267         if (!_pelQueue.empty())
268         {
269             doNewLogNotify();
270         }
271     }
272     else
273     {
274         log<level::ERR>("PLDM command response failure",
275                         entry("PEL_ID=0x%X", id));
276         // Retry
277         _pelQueue.push_front(id);
278         _retryTimer.restartOnce(_hostIface->getReceiveRetryDelay());
279     }
280 }
281 
282 void HostNotifier::retryTimerExpired()
283 {
284     if (_dataIface.isHostUp())
285     {
286         log<level::INFO>("Attempting command retry",
287                          entry("PEL_ID=0x%X", _pelQueue.front()));
288         _retryCount++;
289         doNewLogNotify();
290     }
291 }
292 
293 void HostNotifier::stopCommand()
294 {
295     _retryCount = 0;
296 
297     if (_inProgressPEL != 0)
298     {
299         _pelQueue.push_front(_inProgressPEL);
300         _inProgressPEL = 0;
301     }
302 
303     if (_retryTimer.isEnabled())
304     {
305         _retryTimer.setEnabled(false);
306     }
307 
308     if (_hostIface->cmdInProgress())
309     {
310         _hostIface->cancelCmd();
311     }
312 }
313 
314 } // namespace openpower::pels
315