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