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 <phosphor-logging/log.hpp>
19 
20 #include <bitset>
21 #include <format>
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                 createCriticalAssociation(paths);
84                 sai = false;
85             }
86         }
87     }
88 
89     if (sai)
90     {
91         try
92         {
93             _dataIface.assertLEDGroup(platformSaiLedGroup, true);
94         }
95         catch (const std::exception& e)
96         {
97             log<level::ERR>(
98                 std::format("Failed to assert platform SAI LED group: {}",
99                             e.what())
100                     .c_str());
101         }
102     }
103 }
104 
105 std::vector<std::string> LightPath::getLocationCodes(
106     const std::vector<std::unique_ptr<src::Callout>>& callouts) const
107 {
108     std::vector<std::string> locCodes;
109     bool firstCallout = true;
110     uint8_t firstCalloutPriority;
111 
112     // Collect location codes for the first group of callouts,
113     // where a group can be:
114     //  * a single medium priority callout
115     //  * one or more high priority callouts
116     //  * one or more medium group a priority callouts
117     //
118     // All callouts in the group must be hardware callouts.
119 
120     for (const auto& callout : callouts)
121     {
122         if (firstCallout)
123         {
124             firstCallout = false;
125 
126             firstCalloutPriority = callout->priority();
127 
128             // If the first callout is High, Medium, or Medium
129             // group A, and is a hardware callout, then we
130             // want it.
131             if (isRequiredPriority(firstCalloutPriority) &&
132                 isHardwareCallout(*callout))
133             {
134                 locCodes.push_back(callout->locationCode());
135             }
136             else
137             {
138                 break;
139             }
140 
141             // By definition a medium priority callout can't be part
142             // of a group, so no need to look for more.
143             if (static_cast<CalloutPriority>(firstCalloutPriority) ==
144                 CalloutPriority::medium)
145             {
146                 break;
147             }
148         }
149         else
150         {
151             // Only continue while the callouts are the same
152             // priority as the first callout.
153             if (callout->priority() != firstCalloutPriority)
154             {
155                 break;
156             }
157 
158             // If any callout in the group isn't a hardware callout,
159             // then don't light up any LEDs at all.
160             if (!isHardwareCallout(*callout))
161             {
162                 locCodes.clear();
163                 break;
164             }
165 
166             locCodes.push_back(callout->locationCode());
167         }
168     }
169 
170     return locCodes;
171 }
172 
173 bool LightPath::isRequiredPriority(uint8_t priority) const
174 {
175     auto calloutPriority = static_cast<CalloutPriority>(priority);
176     return (calloutPriority == CalloutPriority::high) ||
177            (calloutPriority == CalloutPriority::medium) ||
178            (calloutPriority == CalloutPriority::mediumGroupA);
179 }
180 
181 bool LightPath::isHardwareCallout(const src::Callout& callout) const
182 {
183     const auto& fruIdentity = callout.fruIdentity();
184     if (fruIdentity)
185     {
186         return (callout.locationCodeSize() != 0) &&
187                ((fruIdentity->failingComponentType() ==
188                  src::FRUIdentity::hardwareFRU) ||
189                 (fruIdentity->failingComponentType() ==
190                  src::FRUIdentity::symbolicFRUTrustedLocCode));
191     }
192 
193     return false;
194 }
195 
196 std::vector<std::string> LightPath::getInventoryPaths(
197     const std::vector<std::string>& locationCodes) const
198 {
199     std::vector<std::string> paths;
200 
201     for (const auto& locCode : locationCodes)
202     {
203         try
204         {
205             auto inventoryPaths = _dataIface.getInventoryFromLocCode(locCode, 0,
206                                                                      true);
207             for (const auto& path : inventoryPaths)
208             {
209                 if (std::find(paths.begin(), paths.end(), path) == paths.end())
210                 {
211                     paths.push_back(path);
212                 }
213             }
214         }
215         catch (const std::exception& e)
216         {
217             log<level::ERR>(std::format("Could not get inventory path for "
218                                         "location code {} ({}).",
219                                         locCode, e.what())
220                                 .c_str());
221 
222             // Unless we can set the LEDs for all FRUs, we can't turn
223             // on any of them, so clear the list and quit.
224             paths.clear();
225             break;
226         }
227     }
228 
229     return paths;
230 }
231 
232 void LightPath::setNotFunctional(
233     const std::vector<std::string>& inventoryPaths) const
234 {
235     for (const auto& path : inventoryPaths)
236     {
237         try
238         {
239             _dataIface.setFunctional(path, false);
240         }
241         catch (const std::exception& e)
242         {
243             log<level::INFO>(
244                 std::format("Could not write Functional property on {} ({})",
245                             path, e.what())
246                     .c_str());
247         }
248     }
249 }
250 
251 void LightPath::createCriticalAssociation(
252     const std::vector<std::string>& inventoryPaths) const
253 {
254     for (const auto& path : inventoryPaths)
255     {
256         try
257         {
258             _dataIface.setCriticalAssociation(path);
259         }
260         catch (const std::exception& e)
261         {
262             log<level::INFO>(
263                 std::format(
264                     "Could not set critical association on object path {} ({})",
265                     path, e.what())
266                     .c_str());
267         }
268     }
269 }
270 
271 } // namespace openpower::pels::service_indicators
272