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 Stops an in progress command
261      *
262      * In progress meaning after the send but before the response.
263      */
264     void stopCommand();
265 
266     /**
267      * @brief The PEL repository object
268      */
269     Repository& _repo;
270 
271     /**
272      * @brief The data interface object
273      */
274     DataInterfaceBase& _dataIface;
275 
276     /**
277      * @brief Base class pointer for the host command interface
278      */
279     std::unique_ptr<HostInterface> _hostIface;
280 
281     /**
282      * @brief The list of PEL IDs that need to be sent.
283      */
284     std::deque<uint32_t> _pelQueue;
285 
286     /**
287      * @brief The list of IDs that were sent, but not acked yet.
288      *
289      * These move back to _pelQueue on a power off.
290      */
291     std::vector<uint32_t> _sentPELs;
292 
293     /**
294      * @brief The ID the PEL where the notification has
295      *        been kicked off but the asynchronous response
296      *        hasn't been received yet.
297      */
298     uint32_t _inProgressPEL = 0;
299 
300     /**
301      * @brief The command retry count
302      */
303     size_t _retryCount = 0;
304 
305     /**
306      * @brief Indicates if the host has said it is full and does not
307      *        currently have the space for more PELs.
308      */
309     bool _hostFull = false;
310 
311     /**
312      * @brief The command retry timer.
313      */
314     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _retryTimer;
315 
316     /**
317      * @brief The host full timer, used to retry sending a PEL if the host
318      *        said it is full.
319      */
320     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _hostFullTimer;
321 
322     /**
323      * @brief The object used to dispatch a new PEL send from the
324      *        event loop, so the calling function can be returned from
325      *        first.
326      */
327     std::unique_ptr<sdeventplus::source::Defer> _dispatcher;
328 };
329 
330 } // namespace openpower::pels
331