blob: f2d951fc4e4455c0caba8a2e3dfb5f64de09031d [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(),
33 std::bind(std::mem_fn(&HostNotifier::retryTimerExpired), this))
Matt Spinlerf60ac272019-12-11 13:47:50 -060034{
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
62HostNotifier::~HostNotifier()
63{
64 _repo.unsubscribeFromAdds(subscriptionName);
65 _dataIface.unsubscribeFromHostStateChange(subscriptionName);
66}
67
68bool 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
79bool HostNotifier::enqueueRequired(uint32_t id) const
80{
81 bool required = true;
Matt Spinlera943b152019-12-11 14:44:50 -060082 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 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600110
111 return required;
112}
113
Matt Spinlerf77debb2019-12-12 10:04:33 -0600114bool 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
Matt Spinlerf60ac272019-12-11 13:47:50 -0600149void HostNotifier::newLogCallback(const PEL& pel)
150{
151 if (!enqueueRequired(pel.id()))
152 {
153 return;
154 }
155
156 _pelQueue.push_back(pel.id());
157
Matt Spinler7d800a42019-12-12 10:35:01 -0600158 if (!_dataIface.isHostUp())
159 {
160 return;
161 }
162
163 // Dispatch a command now if there isn't currently a command
164 // in progress and this is the first log in the queue or it
165 // previously gave up from a hard failure.
166 auto inProgress = (_inProgressPEL != 0) || _hostIface->cmdInProgress() ||
167 _retryTimer.isEnabled();
168
169 auto firstPEL = _pelQueue.size() == 1;
170 auto gaveUp = _retryCount >= maxRetryAttempts;
171
172 if (!inProgress && (firstPEL || gaveUp))
173 {
174 _retryCount = 0;
175
176 // Send a log, but from the event loop, not from here.
177 scheduleDispatch();
178 }
179}
180
181void HostNotifier::scheduleDispatch()
182{
183 _dispatcher = std::make_unique<sdeventplus::source::Defer>(
184 _hostIface->getEvent(), std::bind(std::mem_fn(&HostNotifier::dispatch),
185 this, std::placeholders::_1));
186}
187
188void HostNotifier::dispatch(sdeventplus::source::EventBase& source)
189{
190 _dispatcher.reset();
191
192 doNewLogNotify();
Matt Spinlerf60ac272019-12-11 13:47:50 -0600193}
194
195void HostNotifier::doNewLogNotify()
196{
Matt Spinlerf77debb2019-12-12 10:04:33 -0600197 if (!_dataIface.isHostUp() || _retryTimer.isEnabled())
198 {
199 return;
200 }
201
202 if (_retryCount >= maxRetryAttempts)
203 {
204 // Give up until a new log comes in.
205 if (_retryCount == maxRetryAttempts)
206 {
207 // If this were to really happen, the PLDM interface
208 // would be down and isolating that shouldn't left to
209 // a logging daemon, so just trace. Also, this will start
210 // trying again when the next new log comes in.
211 log<level::ERR>(
212 "PEL Host notifier hit max retry attempts. Giving up for now.",
213 entry("PEL_ID=0x%X", _pelQueue.front()));
214 }
215 return;
216 }
217
218 bool doNotify = false;
219 uint32_t id = 0;
220
221 // Find the PEL to send
222 while (!doNotify && !_pelQueue.empty())
223 {
224 id = _pelQueue.front();
225 _pelQueue.pop_front();
226
227 if (notifyRequired(id))
228 {
229 doNotify = true;
230 }
231 }
232
233 if (doNotify)
234 {
235 // Get the size using the repo attributes
236 Repository::LogID i{Repository::LogID::Pel{id}};
237 if (auto attributes = _repo.getPELAttributes(i); attributes)
238 {
239 auto size = static_cast<size_t>(
240 std::filesystem::file_size((*attributes).get().path));
241 auto rc = _hostIface->sendNewLogCmd(id, size);
242
243 if (rc == CmdStatus::success)
244 {
245 _inProgressPEL = id;
246 }
247 else
248 {
249 // It failed. Retry
250 log<level::ERR>("PLDM send failed", entry("PEL_ID=0x%X", id));
251 _pelQueue.push_front(id);
252 _inProgressPEL = 0;
253 _retryTimer.restartOnce(_hostIface->getSendRetryDelay());
254 }
255 }
256 else
257 {
258 log<level::ERR>("PEL ID not in repository. Cannot notify host",
259 entry("PEL_ID=0x%X", id));
260 }
261 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600262}
263
264void HostNotifier::hostStateChange(bool hostUp)
265{
Matt Spinler3019c6f2019-12-11 15:24:45 -0600266 _retryCount = 0;
267
268 if (hostUp && !_pelQueue.empty())
269 {
270 doNewLogNotify();
271 }
272 else if (!hostUp)
273 {
274 stopCommand();
275
276 // Reset the state on any PELs that were sent but not acked back
277 // to new so they'll get sent again.
278 for (auto id : _sentPELs)
279 {
280 _pelQueue.push_back(id);
281 _repo.setPELHostTransState(id, TransmissionState::newPEL);
282 }
283
284 _sentPELs.clear();
285 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600286}
287
288void HostNotifier::commandResponse(ResponseStatus status)
289{
Matt Spinlerf869fcf2019-12-11 15:02:20 -0600290 auto id = _inProgressPEL;
291 _inProgressPEL = 0;
292
293 if (status == ResponseStatus::success)
294 {
295 _retryCount = 0;
296
297 _sentPELs.push_back(id);
298
299 _repo.setPELHostTransState(id, TransmissionState::sent);
300
301 if (!_pelQueue.empty())
302 {
303 doNewLogNotify();
304 }
305 }
306 else
307 {
308 log<level::ERR>("PLDM command response failure",
309 entry("PEL_ID=0x%X", id));
310 // Retry
311 _pelQueue.push_front(id);
312 _retryTimer.restartOnce(_hostIface->getReceiveRetryDelay());
313 }
314}
315
316void HostNotifier::retryTimerExpired()
317{
318 if (_dataIface.isHostUp())
319 {
320 log<level::INFO>("Attempting command retry",
321 entry("PEL_ID=0x%X", _pelQueue.front()));
322 _retryCount++;
323 doNewLogNotify();
324 }
Matt Spinlerf60ac272019-12-11 13:47:50 -0600325}
326
Matt Spinler3019c6f2019-12-11 15:24:45 -0600327void HostNotifier::stopCommand()
328{
329 _retryCount = 0;
330
331 if (_inProgressPEL != 0)
332 {
333 _pelQueue.push_front(_inProgressPEL);
334 _inProgressPEL = 0;
335 }
336
337 if (_retryTimer.isEnabled())
338 {
339 _retryTimer.setEnabled(false);
340 }
341
342 if (_hostIface->cmdInProgress())
343 {
344 _hostIface->cancelCmd();
345 }
346}
347
Matt Spinlerf60ac272019-12-11 13:47:50 -0600348} // namespace openpower::pels