blob: 2e132b982e87e9c618ad647a449aa7135df7663d [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
87 if (auto attributes = _repo.getPELAttributes(i); attributes)
88 {
89 auto a = attributes.value().get();
90
91 if ((a.hostState == TransmissionState::acked) ||
92 (a.hostState == TransmissionState::badPEL))
93 {
94 required = false;
95 }
96 else if (a.actionFlags.test(hiddenFlagBit) &&
97 (a.hmcState == TransmissionState::acked))
98 {
99 required = false;
100 }
101 else if (a.actionFlags.test(dontReportToHostFlagBit))
102 {
103 required = false;
104 }
105 }
106 else
107 {
108 using namespace phosphor::logging;
109 log<level::ERR>("Host Enqueue: Unable to find PEL ID in repository",
110 entry("PEL_ID=0x%X", id));
111 required = false;
112 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600113
114 return required;
115}
116
Matt Spinlerf77debb2019-12-12 10:04:33 -0600117bool HostNotifier::notifyRequired(uint32_t id) const
118{
119 bool notify = true;
120 Repository::LogID i{Repository::LogID::Pel{id}};
121
122 if (auto attributes = _repo.getPELAttributes(i); attributes)
123 {
124 // If already acked by the host, don't send again.
125 // (A safety check as it shouldn't get to this point.)
126 auto a = attributes.value().get();
127 if (a.hostState == TransmissionState::acked)
128 {
129 notify = false;
130 }
131 else if (a.actionFlags.test(hiddenFlagBit))
132 {
133 // If hidden and acked (or will be) acked by the HMC,
134 // also don't send it. (HMC management can come and
135 // go at any time)
136 if ((a.hmcState == TransmissionState::acked) ||
137 _dataIface.isHMCManaged())
138 {
139 notify = false;
140 }
141 }
142 }
143 else
144 {
145 // Must have been deleted since put on the queue.
146 notify = false;
147 }
148
149 return notify;
150}
151
Matt Spinlerf60ac272019-12-11 13:47:50 -0600152void HostNotifier::newLogCallback(const PEL& pel)
153{
154 if (!enqueueRequired(pel.id()))
155 {
156 return;
157 }
158
159 _pelQueue.push_back(pel.id());
160
Matt Spinler41293cb2019-12-12 13:11:09 -0600161 // Notify shouldn't happen if host is down or full
162 if (!_dataIface.isHostUp() || _hostFull)
Matt Spinler7d800a42019-12-12 10:35:01 -0600163 {
164 return;
165 }
166
167 // Dispatch a command now if there isn't currently a command
168 // in progress and this is the first log in the queue or it
169 // previously gave up from a hard failure.
170 auto inProgress = (_inProgressPEL != 0) || _hostIface->cmdInProgress() ||
171 _retryTimer.isEnabled();
172
173 auto firstPEL = _pelQueue.size() == 1;
174 auto gaveUp = _retryCount >= maxRetryAttempts;
175
176 if (!inProgress && (firstPEL || gaveUp))
177 {
178 _retryCount = 0;
179
180 // Send a log, but from the event loop, not from here.
181 scheduleDispatch();
182 }
183}
184
185void HostNotifier::scheduleDispatch()
186{
187 _dispatcher = std::make_unique<sdeventplus::source::Defer>(
188 _hostIface->getEvent(), std::bind(std::mem_fn(&HostNotifier::dispatch),
189 this, std::placeholders::_1));
190}
191
192void HostNotifier::dispatch(sdeventplus::source::EventBase& source)
193{
194 _dispatcher.reset();
195
196 doNewLogNotify();
Matt Spinlerf60ac272019-12-11 13:47:50 -0600197}
198
199void HostNotifier::doNewLogNotify()
200{
Matt Spinler41293cb2019-12-12 13:11:09 -0600201 if (!_dataIface.isHostUp() || _retryTimer.isEnabled() ||
202 _hostFullTimer.isEnabled())
Matt Spinlerf77debb2019-12-12 10:04:33 -0600203 {
204 return;
205 }
206
207 if (_retryCount >= maxRetryAttempts)
208 {
209 // Give up until a new log comes in.
210 if (_retryCount == maxRetryAttempts)
211 {
212 // If this were to really happen, the PLDM interface
213 // would be down and isolating that shouldn't left to
214 // a logging daemon, so just trace. Also, this will start
215 // trying again when the next new log comes in.
216 log<level::ERR>(
217 "PEL Host notifier hit max retry attempts. Giving up for now.",
218 entry("PEL_ID=0x%X", _pelQueue.front()));
219 }
220 return;
221 }
222
223 bool doNotify = false;
224 uint32_t id = 0;
225
226 // Find the PEL to send
227 while (!doNotify && !_pelQueue.empty())
228 {
229 id = _pelQueue.front();
230 _pelQueue.pop_front();
231
232 if (notifyRequired(id))
233 {
234 doNotify = true;
235 }
236 }
237
238 if (doNotify)
239 {
240 // Get the size using the repo attributes
241 Repository::LogID i{Repository::LogID::Pel{id}};
242 if (auto attributes = _repo.getPELAttributes(i); attributes)
243 {
244 auto size = static_cast<size_t>(
245 std::filesystem::file_size((*attributes).get().path));
246 auto rc = _hostIface->sendNewLogCmd(id, size);
247
248 if (rc == CmdStatus::success)
249 {
250 _inProgressPEL = id;
251 }
252 else
253 {
254 // It failed. Retry
255 log<level::ERR>("PLDM send failed", entry("PEL_ID=0x%X", id));
256 _pelQueue.push_front(id);
257 _inProgressPEL = 0;
258 _retryTimer.restartOnce(_hostIface->getSendRetryDelay());
259 }
260 }
261 else
262 {
263 log<level::ERR>("PEL ID not in repository. Cannot notify host",
264 entry("PEL_ID=0x%X", id));
265 }
266 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600267}
268
269void HostNotifier::hostStateChange(bool hostUp)
270{
Matt Spinler3019c6f2019-12-11 15:24:45 -0600271 _retryCount = 0;
Matt Spinler41293cb2019-12-12 13:11:09 -0600272 _hostFull = false;
Matt Spinler3019c6f2019-12-11 15:24:45 -0600273
274 if (hostUp && !_pelQueue.empty())
275 {
276 doNewLogNotify();
277 }
278 else if (!hostUp)
279 {
280 stopCommand();
281
282 // Reset the state on any PELs that were sent but not acked back
283 // to new so they'll get sent again.
284 for (auto id : _sentPELs)
285 {
286 _pelQueue.push_back(id);
287 _repo.setPELHostTransState(id, TransmissionState::newPEL);
288 }
289
290 _sentPELs.clear();
Matt Spinler41293cb2019-12-12 13:11:09 -0600291
292 if (_hostFullTimer.isEnabled())
293 {
294 _hostFullTimer.setEnabled(false);
295 }
Matt Spinler3019c6f2019-12-11 15:24:45 -0600296 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600297}
298
299void HostNotifier::commandResponse(ResponseStatus status)
300{
Matt Spinlerf869fcf2019-12-11 15:02:20 -0600301 auto id = _inProgressPEL;
302 _inProgressPEL = 0;
303
304 if (status == ResponseStatus::success)
305 {
306 _retryCount = 0;
307
308 _sentPELs.push_back(id);
309
310 _repo.setPELHostTransState(id, TransmissionState::sent);
311
Matt Spinler41293cb2019-12-12 13:11:09 -0600312 // If the host is full, don't send off the next PEL
313 if (!_hostFull && !_pelQueue.empty())
Matt Spinlerf869fcf2019-12-11 15:02:20 -0600314 {
315 doNewLogNotify();
316 }
317 }
318 else
319 {
320 log<level::ERR>("PLDM command response failure",
321 entry("PEL_ID=0x%X", id));
322 // Retry
323 _pelQueue.push_front(id);
324 _retryTimer.restartOnce(_hostIface->getReceiveRetryDelay());
325 }
326}
327
328void HostNotifier::retryTimerExpired()
329{
330 if (_dataIface.isHostUp())
331 {
332 log<level::INFO>("Attempting command retry",
333 entry("PEL_ID=0x%X", _pelQueue.front()));
334 _retryCount++;
335 doNewLogNotify();
336 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600337}
338
Matt Spinler41293cb2019-12-12 13:11:09 -0600339void HostNotifier::hostFullTimerExpired()
340{
341 doNewLogNotify();
342}
343
Matt Spinler3019c6f2019-12-11 15:24:45 -0600344void HostNotifier::stopCommand()
345{
346 _retryCount = 0;
347
348 if (_inProgressPEL != 0)
349 {
350 _pelQueue.push_front(_inProgressPEL);
351 _inProgressPEL = 0;
352 }
353
354 if (_retryTimer.isEnabled())
355 {
356 _retryTimer.setEnabled(false);
357 }
358
359 if (_hostIface->cmdInProgress())
360 {
361 _hostIface->cancelCmd();
362 }
363}
364
Matt Spinlercc3b64a2019-12-12 11:27:10 -0600365void HostNotifier::ackPEL(uint32_t id)
366{
367 _repo.setPELHostTransState(id, TransmissionState::acked);
368
369 // No longer just 'sent', so remove it from the sent list.
370 auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
371 if (sent != _sentPELs.end())
372 {
373 _sentPELs.erase(sent);
374 }
Matt Spinler41293cb2019-12-12 13:11:09 -0600375
376 // An ack means the host is no longer full
377 if (_hostFullTimer.isEnabled())
378 {
379 _hostFullTimer.setEnabled(false);
380 }
381
382 if (_hostFull)
383 {
384 _hostFull = false;
385
386 // Start sending PELs again, from the event loop
387 if (!_pelQueue.empty())
388 {
389 scheduleDispatch();
390 }
391 }
392}
393
394void HostNotifier::setHostFull(uint32_t id)
395{
396 log<level::INFO>("Received Host full indication", entry("PEL_ID=0x%X", id));
397
398 _hostFull = true;
399
400 // This PEL needs to get re-sent
401 auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
402 if (sent != _sentPELs.end())
403 {
404 _sentPELs.erase(sent);
405 _repo.setPELHostTransState(id, TransmissionState::newPEL);
406
407 if (std::find(_pelQueue.begin(), _pelQueue.end(), id) ==
408 _pelQueue.end())
409 {
410 _pelQueue.push_front(id);
411 }
412 }
413
414 // The only PELs that will be sent when the
415 // host is full is from this timer callback.
416 if (!_hostFullTimer.isEnabled())
417 {
418 _hostFullTimer.restartOnce(_hostIface->getHostFullRetryDelay());
419 }
Matt Spinlercc3b64a2019-12-12 11:27:10 -0600420}
421
Matt Spinlera19b6232019-12-12 13:30:14 -0600422void HostNotifier::setBadPEL(uint32_t id)
423{
424 log<level::ERR>("PEL rejected by the host", entry("PEL_ID=0x%X", id));
425
426 auto sent = std::find(_sentPELs.begin(), _sentPELs.end(), id);
427 if (sent != _sentPELs.end())
428 {
429 _sentPELs.erase(sent);
430 }
431
432 _repo.setPELHostTransState(id, TransmissionState::badPEL);
433}
434
Matt Spinlerf60ac272019-12-11 13:47:50 -0600435} // namespace openpower::pels