1 /**
2  * Copyright © 2020 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "service_indicators.hpp"
17 
18 #include <fmt/format.h>
19 
20 #include <bitset>
21 #include <phosphor-logging/log.hpp>
22 
23 namespace openpower::pels::service_indicators
24 {
25 
26 using namespace phosphor::logging;
27 
28 static constexpr auto platformSaiLedGroup =
29     "/xyz/openbmc_project/led/groups/platform_system_attention_indicator";
30 
31 std::unique_ptr<Policy> getPolicy(const DataInterfaceBase& dataIface)
32 {
33     // At the moment there is just one type of policy.
34     return std::make_unique<LightPath>(dataIface);
35 }
36 
37 bool LightPath::ignore(const PEL& pel) const
38 {
39     auto creator = pel.privateHeader().creatorID();
40 
41     // Don't ignore serviceable BMC or hostboot errors
42     if ((static_cast<CreatorID>(creator) == CreatorID::openBMC) ||
43         (static_cast<CreatorID>(creator) == CreatorID::hostboot))
44     {
45         std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
46         if (actionFlags.test(serviceActionFlagBit))
47         {
48             return false;
49         }
50     }
51 
52     return true;
53 }
54 
55 void LightPath::activate(const PEL& pel)
56 {
57     if (ignore(pel))
58     {
59         return;
60     }
61 
62     // Now that we've gotten this far, we'll need to turn on
63     // the system attention indicator if we don't find other
64     // indicators to turn on.
65     bool sai = true;
66     auto src = pel.primarySRC();
67     const auto& calloutsObj = (*src)->callouts();
68 
69     if (calloutsObj && !calloutsObj->callouts().empty())
70     {
71         const auto& callouts = calloutsObj->callouts();
72 
73         // From the callouts, find the location codes whose
74         // LEDs need to be turned on.
75         auto locCodes = getLocationCodes(callouts);
76         if (!locCodes.empty())
77         {
78             // Find the inventory paths for those location codes.
79             auto paths = getInventoryPaths(locCodes);
80             if (!paths.empty())
81             {
82                 setNotFunctional(paths);
83                 sai = false;
84             }
85         }
86     }
87 
88     if (sai)
89     {
90         try
91         {
92             _dataIface.assertLEDGroup(platformSaiLedGroup, true);
93         }
94         catch (const std::exception& e)
95         {
96             log<level::ERR>(
97                 fmt::format("Failed to assert platform SAI LED group: {}",
98                             e.what())
99                     .c_str());
100         }
101     }
102 }
103 
104 std::vector<std::string> LightPath::getLocationCodes(
105     const std::vector<std::unique_ptr<src::Callout>>& callouts) const
106 {
107     std::vector<std::string> locCodes;
108     bool firstCallout = true;
109     uint8_t firstCalloutPriority;
110 
111     // Collect location codes for the first group of callouts,
112     // where a group can be:
113     //  * a single medium priority callout
114     //  * one or more high priority callouts
115     //  * one or more medium group a priority callouts
116     //
117     // All callouts in the group must be hardware callouts.
118 
119     for (const auto& callout : callouts)
120     {
121         if (firstCallout)
122         {
123             firstCallout = false;
124 
125             firstCalloutPriority = callout->priority();
126 
127             // If the first callout is High, Medium, or Medium
128             // group A, and is a hardware callout, then we
129             // want it.
130             if (isRequiredPriority(firstCalloutPriority) &&
131                 isHardwareCallout(*callout))
132             {
133                 locCodes.push_back(callout->locationCode());
134             }
135             else
136             {
137                 break;
138             }
139 
140             // By definition a medium priority callout can't be part
141             // of a group, so no need to look for more.
142             if (static_cast<CalloutPriority>(firstCalloutPriority) ==
143                 CalloutPriority::medium)
144             {
145                 break;
146             }
147         }
148         else
149         {
150             // Only continue while the callouts are the same
151             // priority as the first callout.
152             if (callout->priority() != firstCalloutPriority)
153             {
154                 break;
155             }
156 
157             // If any callout in the group isn't a hardware callout,
158             // then don't light up any LEDs at all.
159             if (!isHardwareCallout(*callout))
160             {
161                 locCodes.clear();
162                 break;
163             }
164 
165             locCodes.push_back(callout->locationCode());
166         }
167     }
168 
169     return locCodes;
170 }
171 
172 bool LightPath::isRequiredPriority(uint8_t priority) const
173 {
174     auto calloutPriority = static_cast<CalloutPriority>(priority);
175     return (calloutPriority == CalloutPriority::high) ||
176            (calloutPriority == CalloutPriority::medium) ||
177            (calloutPriority == CalloutPriority::mediumGroupA);
178 }
179 
180 bool LightPath::isHardwareCallout(const src::Callout& callout) const
181 {
182     const auto& fruIdentity = callout.fruIdentity();
183     if (fruIdentity)
184     {
185         return (callout.locationCodeSize() != 0) &&
186                ((fruIdentity->failingComponentType() ==
187                  src::FRUIdentity::hardwareFRU) ||
188                 (fruIdentity->failingComponentType() ==
189                  src::FRUIdentity::symbolicFRUTrustedLocCode));
190     }
191 
192     return false;
193 }
194 
195 std::vector<std::string> LightPath::getInventoryPaths(
196     const std::vector<std::string>& locationCodes) const
197 {
198     std::vector<std::string> paths;
199     std::string inventoryPath;
200 
201     for (const auto& locCode : locationCodes)
202     {
203         try
204         {
205             auto inventoryPath =
206                 _dataIface.getInventoryFromLocCode(locCode, 0, true);
207             paths.push_back(std::move(inventoryPath));
208         }
209         catch (const std::exception& e)
210         {
211             log<level::ERR>(fmt::format("Could not get inventory path for "
212                                         "location code {} ({}).",
213                                         locCode, e.what())
214                                 .c_str());
215 
216             // Unless we can set the LEDs for all FRUs, we can't turn
217             // on any of them, so clear the list and quit.
218             paths.clear();
219             break;
220         }
221     }
222 
223     return paths;
224 }
225 
226 void LightPath::setNotFunctional(
227     const std::vector<std::string>& inventoryPaths) const
228 {
229     for (const auto& path : inventoryPaths)
230     {
231         try
232         {
233             _dataIface.setFunctional(path, false);
234         }
235         catch (const std::exception& e)
236         {
237             log<level::INFO>(
238                 fmt::format("Could not write Functional property on {} ({})",
239                             path, e.what())
240                     .c_str());
241         }
242     }
243 }
244 
245 } // namespace openpower::pels::service_indicators
246