1 #pragma once
2 
3 #include "host_interface.hpp"
4 #include "pel.hpp"
5 #include "repository.hpp"
6 
7 #include <sdeventplus/clock.hpp>
8 #include <sdeventplus/source/event.hpp>
9 #include <sdeventplus/utility/timer.hpp>
10 
11 #include <deque>
12 
13 namespace openpower::pels
14 {
15 
16 /**
17  * @class HostNotifier
18  *
19  * This class handles notifying the host firmware of new PELs.
20  *
21  * It uses the Repository class's subscription feature to be
22  * notified about new PELs.
23  *
24  * Some PELs do not need to be sent - see enqueueRequired() and
25  * notifyRequired().
26  *
27  * The high level good path flow for sending a single PEL is:
28  *
29  * 1) Send the ID and size of the new PEL to the host.
30  *   - The command response is asynchronous.
31  *
32  * 2) The host reads the raw PEL data (outside of this class).
33  *
34  * 3) The host sends the PEL to the OS, and then sends an AckPEL
35  *    PLDM command to the PLDM daemon, who makes a D-Bus method
36  *    call to this daemon, which calls HostNotifer::ackPEL().
37  *
38  *    After this, a PEL never needs to be sent again, but if the
39  *    host is rebooted before the ack comes it will.
40  *
41  * The host firmware has a finite amount of space to store PELs before
42  * sending to the OS, and it's possible it will fill up.  In this case,
43  * the AckPEL command will have a special response that will tell the
44  * PLDM daemon to call  HostReject D-Bus method on this daemon instead
45  * which will invoke HostNotifier::setHostFull(). This will stop new
46  * PELs from being sent, and the first PEL that hits this will have
47  * a timer set to retry again later.
48  */
49 class HostNotifier
50 {
51   public:
52     HostNotifier() = delete;
53     HostNotifier(const HostNotifier&) = delete;
54     HostNotifier& operator=(const HostNotifier&) = delete;
55     HostNotifier(HostNotifier&&) = delete;
56     HostNotifier& operator=(HostNotifier&&) = delete;
57 
58     /**
59      * @brief Constructor
60      *
61      * @param[in] repo - The PEL repository object
62      * @param[in] dataIface - The data interface object
63      * @param[in] hostIface - The host interface object
64      */
65     HostNotifier(Repository& repo, DataInterfaceBase& dataIface,
66                  std::unique_ptr<HostInterface> hostIface);
67 
68     /**
69      * @brief Destructor
70      */
71     ~HostNotifier();
72 
73     /**
74      * @brief Returns the PEL queue size.
75      *
76      * For testing.
77      *
78      * @return size_t - The queue size
79      */
queueSize() const80     size_t queueSize() const
81     {
82         return _pelQueue.size();
83     }
84 
85     /**
86      * @brief Specifies if the PEL needs to go onto the queue to be
87      *        set to the host.
88      *
89      * Only returns false if:
90      *  - Already acked by the host (or they didn't like it)
91      *  - Hidden and the HMC already got it
92      *  - The 'do not report to host' bit is set
93      *
94      * @param[in] id - The PEL ID
95      *
96      * @return bool - If enqueue is required
97      */
98     bool enqueueRequired(uint32_t id) const;
99 
100     /**
101      * @brief If the host still needs to be notified of the PEL
102      *        at the time of the notification.
103      *
104      * Only returns false if:
105      *  - Already acked by the host
106      *  - It's hidden, and the HMC already got or will get it.
107      *
108      * @param[in] id - The PEL ID
109      *
110      * @return bool - If the notify is required
111      */
112     bool notifyRequired(uint32_t id) const;
113 
114     /**
115      * @brief Called when the host sends the 'ack' PLDM command.
116      *
117      * This means the PEL never needs to be sent up again.
118      *
119      * If the host was previously full, it is also an indication
120      * it no longer is.
121      *
122      * @param[in] id - The PEL ID
123      */
124     void ackPEL(uint32_t id);
125 
126     /**
127      * @brief Called when the host does not have room for more
128      *        PELs at this time.
129      *
130      * This can happen when an OS isn't running yet, and the
131      * staging area to hold the PELs before sending them up
132      * to the OS is full.  This will stop future PEls from being
133      * sent up, as explained below.
134      *
135      * The PEL with this ID will need to be sent again, so its
136      * state is set back to 'new', and it is removed from the list
137      * of already sent PELs.
138      *
139      * A timer will be started, if it isn't already running, to
140      * issue another send in the hopes that space has been freed
141      * up by then (Receiving an ackPEL response is also an
142      * indication of this if there happened to have been other
143      * PELs in flight).
144      *
145      * @param[in] id - The PEL ID
146      */
147     void setHostFull(uint32_t id);
148 
149     /**
150      * @brief Called when the host receives a malformed PEL.
151      *
152      * Ideally this will never happen, as the Repository
153      * class already purges malformed PELs.
154      *
155      * The PEL should never be sent up again.
156      *
157      * @param[in] id - The PEL ID
158      */
159     void setBadPEL(uint32_t id);
160 
161   private:
162     /**
163      * @brief This function gets called by the Repository class
164      *        when a new PEL is added to it.
165      *
166      * This function puts the PEL on the queue to be sent up if it
167      * needs it, and possibly dispatch the send if the conditions call
168      * for it.
169      *
170      * @param[in] pel - The new PEL
171      */
172     void newLogCallback(const PEL& pel);
173 
174     /**
175      * @brief This function gets called by the Repository class
176      *        when a PEL is deleted.
177      *
178      * The deleted ID will be removed from the PEL queue and the
179      * sent list.
180      *
181      * @param[in] id - The deleted PEL ID
182      */
183     void deleteLogCallback(uint32_t id);
184 
185     /**
186      * @brief This function runs on every existing PEL at startup
187      *        and puts the PEL on the queue to send if necessary.
188      *
189      * @param[in] pel - The PEL
190      *
191      * @return bool - This is an indicator to the Repository::for_each
192      *                function to traverse every PEL.  Always false.
193      */
194     bool addPELToQueue(const PEL& pel);
195 
196     /**
197      * @brief Takes the first PEL from the queue that needs to be
198      *        sent, and issues the send if conditions are right.
199      */
200     void doNewLogNotify();
201 
202     /**
203      * @brief Creates the event object to handle sending the PLDM
204      *        command from the event loop.
205      */
206     void scheduleDispatch();
207 
208     /**
209      * @brief Kicks off the PLDM send, but called from the event
210      *        loop.
211      *
212      * @param[in] source - The event source object
213      */
214     void dispatch(sdeventplus::source::EventBase& source);
215 
216     /**
217      * @brief Called when the host changes state.
218      *
219      * If the new state is host up and there are PELs to send, it
220      * will trigger the first command.  If the new state is off, then
221      * it will transfer any PELs that were sent but not acked yet back
222      * to the queue to be sent again.
223      *
224      * @param[in] hostUp - The new host state
225      */
226     void hostStateChange(bool hostUp);
227 
228     /**
229      * @brief The callback function invoked after the asynchronous
230      *        PLDM receive function is complete.
231      *
232      * If the command was successful, the state of that PEL will
233      * be set to 'sent', and the next send will be triggered.
234      *
235      * If the command failed, a retry timer will be started so it
236      * can be sent again.
237      *
238      * @param[in] status - The response status
239      */
240     void commandResponse(ResponseStatus status);
241 
242     /**
243      * @brief The function called when the command failure retry
244      *        time is up.
245      *
246      * It will issue a send of the previous PEL and increment the
247      * retry count.
248      */
249     void retryTimerExpired();
250 
251     /**
252      * @brief The function called when the 'host full' retry timer
253      *        expires.
254      *
255      * This will re-issue a command to try again with the PEL at
256      * the front of the queue.
257      */
258     void hostFullTimerExpired();
259 
260     /**
261      * @brief The function called when the host up retry timer
262      *        expires.
263      *
264      * It kicks off sending the new log commands.
265      */
266     void hostUpTimerExpired();
267 
268     /**
269      * @brief Stops an in progress command
270      *
271      * In progress meaning after the send but before the response.
272      */
273     void stopCommand();
274 
275     /**
276      * @brief The PEL repository object
277      */
278     Repository& _repo;
279 
280     /**
281      * @brief The data interface object
282      */
283     DataInterfaceBase& _dataIface;
284 
285     /**
286      * @brief Base class pointer for the host command interface
287      */
288     std::unique_ptr<HostInterface> _hostIface;
289 
290     /**
291      * @brief The list of PEL IDs that need to be sent.
292      */
293     std::deque<uint32_t> _pelQueue;
294 
295     /**
296      * @brief The list of IDs that were sent, but not acked yet.
297      *
298      * These move back to _pelQueue on a power off.
299      */
300     std::vector<uint32_t> _sentPELs;
301 
302     /**
303      * @brief The ID the PEL where the notification has
304      *        been kicked off but the asynchronous response
305      *        hasn't been received yet.
306      */
307     uint32_t _inProgressPEL = 0;
308 
309     /**
310      * @brief The command retry count
311      */
312     size_t _retryCount = 0;
313 
314     /**
315      * @brief Indicates if the host has said it is full and does not
316      *        currently have the space for more PELs.
317      */
318     bool _hostFull = false;
319 
320     /**
321      * @brief The command retry timer.
322      */
323     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _retryTimer;
324 
325     /**
326      * @brief The host full timer, used to retry sending a PEL if the host
327      *        said it is full.
328      */
329     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _hostFullTimer;
330 
331     /**
332      * @brief The host up timer, used to kick off sending commands to the
333      *        host after a delay after the host is up.
334      */
335     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _hostUpTimer;
336 
337     /**
338      * @brief The object used to dispatch a new PEL send from the
339      *        event loop, so the calling function can be returned from
340      *        first.
341      */
342     std::unique_ptr<sdeventplus::source::Defer> _dispatcher;
343 };
344 
345 } // namespace openpower::pels
346