#pragma once #include "host_interface.hpp" #include "pel.hpp" #include "repository.hpp" #include #include #include #include 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 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 _hostIface; /** * @brief The list of PEL IDs that need to be sent. */ std::deque _pelQueue; /** * @brief The list of IDs that were sent, but not acked yet. * * These move back to _pelQueue on a power off. */ std::vector _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 _retryTimer; /** * @brief The host full timer, used to retry sending a PEL if the host * said it is full. */ sdeventplus::utility::Timer _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 _dispatcher; }; } // namespace openpower::pels