blob: 175a6441aca1bdec6e798ca9b52d6e5bca34c925 [file] [log] [blame]
Matt Spinlerf60ac272019-12-11 13:47:50 -06001/**
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
20namespace openpower::pels
21{
22
23const auto subscriptionName = "PELHostNotifier";
Matt Spinlerf77debb2019-12-12 10:04:33 -060024const size_t maxRetryAttempts = 15;
Matt Spinlerf60ac272019-12-11 13:47:50 -060025
26using namespace phosphor::logging;
27
28HostNotifier::HostNotifier(Repository& repo, DataInterfaceBase& dataIface,
29 std::unique_ptr<HostInterface> hostIface) :
30 _repo(repo),
Matt Spinlerf869fcf2019-12-11 15:02:20 -060031 _dataIface(dataIface), _hostIface(std::move(hostIface)),
32 _retryTimer(_hostIface->getEvent(),
Matt Spinler41293cb2019-12-12 13:11:09 -060033 std::bind(std::mem_fn(&HostNotifier::retryTimerExpired), this)),
34 _hostFullTimer(
35 _hostIface->getEvent(),
36 std::bind(std::mem_fn(&HostNotifier::hostFullTimerExpired), this))
Matt Spinlerf60ac272019-12-11 13:47:50 -060037{
38 // Subscribe to be told about new PELs.
39 _repo.subscribeToAdds(subscriptionName,
40 std::bind(std::mem_fn(&HostNotifier::newLogCallback),
41 this, std::placeholders::_1));
42
43 // Add any existing PELs to the queue to send them if necessary.
44 _repo.for_each(std::bind(std::mem_fn(&HostNotifier::addPELToQueue), this,
45 std::placeholders::_1));
46
47 // Subscribe to be told about host state changes.
48 _dataIface.subscribeToHostStateChange(
49 subscriptionName,
50 std::bind(std::mem_fun(&HostNotifier::hostStateChange), this,
51 std::placeholders::_1));
52
53 // Set the function to call when the async reponse is received.
54 _hostIface->setResponseFunction(
55 std::bind(std::mem_fn(&HostNotifier::commandResponse), this,
56 std::placeholders::_1));
57
58 // Start sending logs if the host is running
59 if (!_pelQueue.empty() && _dataIface.isHostUp())
60 {
61 doNewLogNotify();
62 }
63}
64
65HostNotifier::~HostNotifier()
66{
67 _repo.unsubscribeFromAdds(subscriptionName);
68 _dataIface.unsubscribeFromHostStateChange(subscriptionName);
69}
70
71bool HostNotifier::addPELToQueue(const PEL& pel)
72{
73 if (enqueueRequired(pel.id()))
74 {
75 _pelQueue.push_back(pel.id());
76 }
77
78 // Return false so that Repo::for_each keeps going.
79 return false;
80}
81
82bool HostNotifier::enqueueRequired(uint32_t id) const
83{
84 bool required = true;
Matt Spinlera943b152019-12-11 14:44:50 -060085 Repository::LogID i{Repository::LogID::Pel{id}};
86
Matt Spinler24a85582020-01-27 16:40:21 -060087 // Manufacturing testing may turn off sending up PELs
88 if (!_dataIface.getHostPELEnablement())
89 {
90 return false;
91 }
92
Matt Spinlera943b152019-12-11 14:44:50 -060093 if (auto attributes = _repo.getPELAttributes(i); attributes)
94 {
95 auto a = attributes.value().get();
96
97 if ((a.hostState == TransmissionState::acked) ||
98 (a.hostState == TransmissionState::badPEL))
99 {
100 required = false;
101 }
102 else if (a.actionFlags.test(hiddenFlagBit) &&
103 (a.hmcState == TransmissionState::acked))
104 {
105 required = false;
106 }
107 else if (a.actionFlags.test(dontReportToHostFlagBit))
108 {
109 required = false;
110 }
111 }
112 else
113 {
114 using namespace phosphor::logging;
115 log<level::ERR>("Host Enqueue: Unable to find PEL ID in repository",
116 entry("PEL_ID=0x%X", id));
117 required = false;
118 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600119
120 return required;
121}
122
Matt Spinlerf77debb2019-12-12 10:04:33 -0600123bool HostNotifier::notifyRequired(uint32_t id) const
124{
125 bool notify = true;
126 Repository::LogID i{Repository::LogID::Pel{id}};
127
128 if (auto attributes = _repo.getPELAttributes(i); attributes)
129 {
130 // If already acked by the host, don't send again.
131 // (A safety check as it shouldn't get to this point.)
132 auto a = attributes.value().get();
133 if (a.hostState == TransmissionState::acked)
134 {
135 notify = false;
136 }
137 else if (a.actionFlags.test(hiddenFlagBit))
138 {
139 // If hidden and acked (or will be) acked by the HMC,
140 // also don't send it. (HMC management can come and
141 // go at any time)
142 if ((a.hmcState == TransmissionState::acked) ||
143 _dataIface.isHMCManaged())
144 {
145 notify = false;
146 }
147 }
148 }
149 else
150 {
151 // Must have been deleted since put on the queue.
152 notify = false;
153 }
154
155 return notify;
156}
157
Matt Spinlerf60ac272019-12-11 13:47:50 -0600158void HostNotifier::newLogCallback(const PEL& pel)
159{
160 if (!enqueueRequired(pel.id()))
161 {
162 return;
163 }
164
165 _pelQueue.push_back(pel.id());
166
Matt Spinler41293cb2019-12-12 13:11:09 -0600167 // Notify shouldn't happen if host is down or full
168 if (!_dataIface.isHostUp() || _hostFull)
Matt Spinler7d800a42019-12-12 10:35:01 -0600169 {
170 return;
171 }
172
173 // Dispatch a command now if there isn't currently a command
174 // in progress and this is the first log in the queue or it
175 // previously gave up from a hard failure.
176 auto inProgress = (_inProgressPEL != 0) || _hostIface->cmdInProgress() ||
177 _retryTimer.isEnabled();
178
179 auto firstPEL = _pelQueue.size() == 1;
180 auto gaveUp = _retryCount >= maxRetryAttempts;
181
182 if (!inProgress && (firstPEL || gaveUp))
183 {
184 _retryCount = 0;
185
186 // Send a log, but from the event loop, not from here.
187 scheduleDispatch();
188 }
189}
190
191void HostNotifier::scheduleDispatch()
192{
193 _dispatcher = std::make_unique<sdeventplus::source::Defer>(
194 _hostIface->getEvent(), std::bind(std::mem_fn(&HostNotifier::dispatch),
195 this, std::placeholders::_1));
196}
197
198void HostNotifier::dispatch(sdeventplus::source::EventBase& source)
199{
200 _dispatcher.reset();
201
202 doNewLogNotify();
Matt Spinlerf60ac272019-12-11 13:47:50 -0600203}
204
205void HostNotifier::doNewLogNotify()
206{
Matt Spinler41293cb2019-12-12 13:11:09 -0600207 if (!_dataIface.isHostUp() || _retryTimer.isEnabled() ||
208 _hostFullTimer.isEnabled())
Matt Spinlerf77debb2019-12-12 10:04:33 -0600209 {
210 return;
211 }
212
213 if (_retryCount >= maxRetryAttempts)
214 {
215 // Give up until a new log comes in.
216 if (_retryCount == maxRetryAttempts)
217 {
218 // If this were to really happen, the PLDM interface
219 // would be down and isolating that shouldn't left to
220 // a logging daemon, so just trace. Also, this will start
221 // trying again when the next new log comes in.
222 log<level::ERR>(
223 "PEL Host notifier hit max retry attempts. Giving up for now.",
224 entry("PEL_ID=0x%X", _pelQueue.front()));
Matt Spinler829b0522020-03-04 08:38:46 -0600225
226 // Tell the host interface object to clean itself up, especially to
227 // release the PLDM instance ID it's been using.
228 _hostIface->cancelCmd();
Matt Spinlerf77debb2019-12-12 10:04:33 -0600229 }
230 return;
231 }
232
233 bool doNotify = false;
234 uint32_t id = 0;
235
236 // Find the PEL to send
237 while (!doNotify && !_pelQueue.empty())
238 {
239 id = _pelQueue.front();
240 _pelQueue.pop_front();
241
242 if (notifyRequired(id))
243 {
244 doNotify = true;
245 }
246 }
247
248 if (doNotify)
249 {
250 // Get the size using the repo attributes
251 Repository::LogID i{Repository::LogID::Pel{id}};
252 if (auto attributes = _repo.getPELAttributes(i); attributes)
253 {
254 auto size = static_cast<size_t>(
255 std::filesystem::file_size((*attributes).get().path));
256 auto rc = _hostIface->sendNewLogCmd(id, size);
257
258 if (rc == CmdStatus::success)
259 {
260 _inProgressPEL = id;
261 }
262 else
263 {
264 // It failed. Retry
265 log<level::ERR>("PLDM send failed", entry("PEL_ID=0x%X", id));
266 _pelQueue.push_front(id);
267 _inProgressPEL = 0;
268 _retryTimer.restartOnce(_hostIface->getSendRetryDelay());
269 }
270 }
271 else
272 {
273 log<level::ERR>("PEL ID not in repository. Cannot notify host",
274 entry("PEL_ID=0x%X", id));
275 }
276 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600277}
278
279void HostNotifier::hostStateChange(bool hostUp)
280{
Matt Spinler3019c6f2019-12-11 15:24:45 -0600281 _retryCount = 0;
Matt Spinler41293cb2019-12-12 13:11:09 -0600282 _hostFull = false;
Matt Spinler3019c6f2019-12-11 15:24:45 -0600283
284 if (hostUp && !_pelQueue.empty())
285 {
286 doNewLogNotify();
287 }
288 else if (!hostUp)
289 {
290 stopCommand();
291
292 // Reset the state on any PELs that were sent but not acked back
293 // to new so they'll get sent again.
294 for (auto id : _sentPELs)
295 {
296 _pelQueue.push_back(id);
297 _repo.setPELHostTransState(id, TransmissionState::newPEL);
298 }
299
300 _sentPELs.clear();
Matt Spinler41293cb2019-12-12 13:11:09 -0600301
302 if (_hostFullTimer.isEnabled())
303 {
304 _hostFullTimer.setEnabled(false);
305 }
Matt Spinler3019c6f2019-12-11 15:24:45 -0600306 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600307}
308
309void HostNotifier::commandResponse(ResponseStatus status)
310{
Matt Spinlerf869fcf2019-12-11 15:02:20 -0600311 auto id = _inProgressPEL;
312 _inProgressPEL = 0;
313
314 if (status == ResponseStatus::success)
315 {
316 _retryCount = 0;
317
318 _sentPELs.push_back(id);
319
320 _repo.setPELHostTransState(id, TransmissionState::sent);
321
Matt Spinler41293cb2019-12-12 13:11:09 -0600322 // If the host is full, don't send off the next PEL
323 if (!_hostFull && !_pelQueue.empty())
Matt Spinlerf869fcf2019-12-11 15:02:20 -0600324 {
325 doNewLogNotify();
326 }
327 }
328 else
329 {
330 log<level::ERR>("PLDM command response failure",
331 entry("PEL_ID=0x%X", id));
332 // Retry
333 _pelQueue.push_front(id);
334 _retryTimer.restartOnce(_hostIface->getReceiveRetryDelay());
335 }
336}
337
338void HostNotifier::retryTimerExpired()
339{
340 if (_dataIface.isHostUp())
341 {
342 log<level::INFO>("Attempting command retry",
343 entry("PEL_ID=0x%X", _pelQueue.front()));
344 _retryCount++;
345 doNewLogNotify();
346 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600347}
348
Matt Spinler41293cb2019-12-12 13:11:09 -0600349void HostNotifier::hostFullTimerExpired()
350{
351 doNewLogNotify();
352}
353
Matt Spinler3019c6f2019-12-11 15:24:45 -0600354void HostNotifier::stopCommand()
355{
356 _retryCount = 0;
357
358 if (_inProgressPEL != 0)
359 {
360 _pelQueue.push_front(_inProgressPEL);
361 _inProgressPEL = 0;
362 }
363
364 if (_retryTimer.isEnabled())
365 {
366 _retryTimer.setEnabled(false);
367 }
368
Matt Spinler829b0522020-03-04 08:38:46 -0600369 // Ensure the PLDM instance ID is released
370 _hostIface->cancelCmd();
Matt Spinler3019c6f2019-12-11 15:24:45 -0600371}
372
Matt Spinlercc3b64a2019-12-12 11:27:10 -0600373void HostNotifier::ackPEL(uint32_t id)
374{
375 _repo.setPELHostTransState(id, TransmissionState::acked);
376
377 // No longer just 'sent', so remove it from the sent list.
378 auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
379 if (sent != _sentPELs.end())
380 {
381 _sentPELs.erase(sent);
382 }
Matt Spinler41293cb2019-12-12 13:11:09 -0600383
384 // An ack means the host is no longer full
385 if (_hostFullTimer.isEnabled())
386 {
387 _hostFullTimer.setEnabled(false);
388 }
389
390 if (_hostFull)
391 {
392 _hostFull = false;
393
394 // Start sending PELs again, from the event loop
395 if (!_pelQueue.empty())
396 {
397 scheduleDispatch();
398 }
399 }
400}
401
402void HostNotifier::setHostFull(uint32_t id)
403{
404 log<level::INFO>("Received Host full indication", entry("PEL_ID=0x%X", id));
405
406 _hostFull = true;
407
408 // This PEL needs to get re-sent
409 auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
410 if (sent != _sentPELs.end())
411 {
412 _sentPELs.erase(sent);
413 _repo.setPELHostTransState(id, TransmissionState::newPEL);
414
415 if (std::find(_pelQueue.begin(), _pelQueue.end(), id) ==
416 _pelQueue.end())
417 {
418 _pelQueue.push_front(id);
419 }
420 }
421
422 // The only PELs that will be sent when the
423 // host is full is from this timer callback.
424 if (!_hostFullTimer.isEnabled())
425 {
426 _hostFullTimer.restartOnce(_hostIface->getHostFullRetryDelay());
427 }
Matt Spinlercc3b64a2019-12-12 11:27:10 -0600428}
429
Matt Spinlera19b6232019-12-12 13:30:14 -0600430void HostNotifier::setBadPEL(uint32_t id)
431{
432 log<level::ERR>("PEL rejected by the host", entry("PEL_ID=0x%X", id));
433
434 auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
435 if (sent != _sentPELs.end())
436 {
437 _sentPELs.erase(sent);
438 }
439
440 _repo.setPELHostTransState(id, TransmissionState::badPEL);
441}
442
Matt Spinlerf60ac272019-12-11 13:47:50 -0600443} // namespace openpower::pels