diff options
Diffstat (limited to 'extensions/openpower-pels/host_notifier.hpp')
-rw-r--r-- | extensions/openpower-pels/host_notifier.hpp | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/extensions/openpower-pels/host_notifier.hpp b/extensions/openpower-pels/host_notifier.hpp new file mode 100644 index 0000000..cad2b36 --- /dev/null +++ b/extensions/openpower-pels/host_notifier.hpp @@ -0,0 +1,319 @@ +#pragma once + +#include "host_interface.hpp" +#include "pel.hpp" +#include "repository.hpp" + +#include <deque> +#include <sdeventplus/clock.hpp> +#include <sdeventplus/source/event.hpp> +#include <sdeventplus/utility/timer.hpp> + +namespace openpower::pels +{ + +/** + * @class HostNotifier + * + * This class handles notifying the host firmware of new PELs. + * + * It uses the Repository class's subscription feature to be + * notified about new PELs. + * + * Some PELs do not need to be sent - see enqueueRequired() and + * notifyRequired(). + * + * The high level good path flow for sending a single PEL is: + * + * 1) Send the ID and size of the new PEL to the host. + * - The command response is asynchronous. + * + * 2) The host reads the raw PEL data (outside of this class). + * + * 3) The host sends the PEL to the OS, and then sends an AckPEL + * PLDM command to the PLDM daemon, who makes a D-Bus method + * call to this daemon, which calls HostNotifer::ackPEL(). + * + * After this, a PEL never needs to be sent again, but if the + * host is rebooted before the ack comes it will. + * + * The host firmware has a finite amount of space to store PELs before + * sending to the OS, and it's possible it will fill up. In this case, + * the AckPEL command will have a special response that will tell the + * PLDM daemon to call HostReject D-Bus method on this daemon instead + * which will invoke HostNotifier::setHostFull(). This will stop new + * PELs from being sent, and the first PEL that hits this will have + * a timer set to retry again later. + */ +class HostNotifier +{ + public: + HostNotifier() = delete; + HostNotifier(const HostNotifier&) = delete; + HostNotifier& operator=(const HostNotifier&) = delete; + HostNotifier(HostNotifier&&) = delete; + HostNotifier& operator=(HostNotifier&&) = delete; + + /** + * @brief Constructor + * + * @param[in] repo - The PEL repository object + * @param[in] dataIface - The data interface object + * @param[in] hostIface - The host interface object + */ + HostNotifier(Repository& repo, DataInterfaceBase& dataIface, + std::unique_ptr<HostInterface> hostIface); + + /** + * @brief Destructor + */ + ~HostNotifier(); + + /** + * @brief Returns the PEL queue size. + * + * For testing. + * + * @return size_t - The queue size + */ + size_t queueSize() const + { + return _pelQueue.size(); + } + + /** + * @brief Specifies if the PEL needs to go onto the queue to be + * set to the host. + * + * Only returns false if: + * - Already acked by the host (or they didn't like it) + * - Hidden and the HMC already got it + * - The 'do not report to host' bit is set + * + * @param[in] id - The PEL ID + * + * @return bool - If enqueue is required + */ + bool enqueueRequired(uint32_t id) const; + + /** + * @brief If the host still needs to be notified of the PEL + * at the time of the notification. + * + * Only returns false if: + * - Already acked by the host + * - It's hidden, and the HMC already got or will get it. + * + * @param[in] id - The PEL ID + * + * @return bool - If the notify is required + */ + bool notifyRequired(uint32_t id) const; + + /** + * @brief Called when the host sends the 'ack' PLDM command. + * + * This means the PEL never needs to be sent up again. + * + * If the host was previously full, it is also an indication + * it no longer is. + * + * @param[in] id - The PEL ID + */ + void ackPEL(uint32_t id); + + /** + * @brief Called when the host does not have room for more + * PELs at this time. + * + * This can happen when an OS isn't running yet, and the + * staging area to hold the PELs before sending them up + * to the OS is full. This will stop future PEls from being + * sent up, as explained below. + * + * The PEL with this ID will need to be sent again, so its + * state is set back to 'new', and it is removed from the list + * of already sent PELs. + * + * A timer will be started, if it isn't already running, to + * issue another send in the hopes that space has been freed + * up by then (Receiving an ackPEL response is also an + * indication of this if there happened to have been other + * PELs in flight). + * + * @param[in] id - The PEL ID + */ + void setHostFull(uint32_t id); + + /** + * @brief Called when the host receives a malformed PEL. + * + * Ideally this will never happen, as the Repository + * class already purges malformed PELs. + * + * The PEL should never be sent up again. + * + * @param[in] id - The PEL ID + */ + void setBadPEL(uint32_t id); + + private: + /** + * @brief This function gets called by the Repository class + * when a new PEL is added to it. + * + * This function puts the PEL on the queue to be sent up if it + * needs it, and possibly dispatch the send if the conditions call + * for it. + * + * @param[in] pel - The new PEL + */ + void newLogCallback(const PEL& pel); + + /** + * @brief This function runs on every existing PEL at startup + * and puts the PEL on the queue to send if necessary. + * + * @param[in] pel - The PEL + * + * @return bool - This is an indicator to the Repository::for_each + * function to traverse every PEL. Always false. + */ + bool addPELToQueue(const PEL& pel); + + /** + * @brief Takes the first PEL from the queue that needs to be + * sent, and issues the send if conditions are right. + */ + void doNewLogNotify(); + + /** + * @brief Creates the event object to handle sending the PLDM + * command from the event loop. + */ + void scheduleDispatch(); + + /** + * @brief Kicks off the PLDM send, but called from the event + * loop. + * + * @param[in] source - The event source object + */ + void dispatch(sdeventplus::source::EventBase& source); + + /** + * @brief Called when the host changes state. + * + * If the new state is host up and there are PELs to send, it + * will trigger the first command. If the new state is off, then + * it will transfer any PELs that were sent but not acked yet back + * to the queue to be sent again. + * + * @param[in] hostUp - The new host state + */ + void hostStateChange(bool hostUp); + + /** + * @brief The callback function invoked after the asynchronous + * PLDM receive function is complete. + * + * If the command was successful, the state of that PEL will + * be set to 'sent', and the next send will be triggered. + * + * If the command failed, a retry timer will be started so it + * can be sent again. + * + * @param[in] status - The response status + */ + void commandResponse(ResponseStatus status); + + /** + * @brief The function called when the command failure retry + * time is up. + * + * It will issue a send of the previous PEL and increment the + * retry count. + */ + void retryTimerExpired(); + + /** + * @brief The function called when the 'host full' retry timer + * expires. + * + * This will re-issue a command to try again with the PEL at + * the front of the queue. + */ + void hostFullTimerExpired(); + + /** + * @brief Stops an in progress command + * + * In progress meaning after the send but before the response. + */ + void stopCommand(); + + /** + * @brief The PEL repository object + */ + Repository& _repo; + + /** + * @brief The data interface object + */ + DataInterfaceBase& _dataIface; + + /** + * @brief Base class pointer for the host command interface + */ + std::unique_ptr<HostInterface> _hostIface; + + /** + * @brief The list of PEL IDs that need to be sent. + */ + std::deque<uint32_t> _pelQueue; + + /** + * @brief The list of IDs that were sent, but not acked yet. + * + * These move back to _pelQueue on a power off. + */ + std::vector<uint32_t> _sentPELs; + + /** + * @brief The ID the PEL where the notification has + * been kicked off but the asynchronous response + * hasn't been received yet. + */ + uint32_t _inProgressPEL = 0; + + /** + * @brief The command retry count + */ + size_t _retryCount = 0; + + /** + * @brief Indicates if the host has said it is full and does not + * currently have the space for more PELs. + */ + bool _hostFull = false; + + /** + * @brief The command retry timer. + */ + sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _retryTimer; + + /** + * @brief The host full timer, used to retry sending a PEL if the host + * said it is full. + */ + sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _hostFullTimer; + + /** + * @brief The object used to dispatch a new PEL send from the + * event loop, so the calling function can be returned from + * first. + */ + std::unique_ptr<sdeventplus::source::Defer> _dispatcher; +}; + +} // namespace openpower::pels |