1 /**
2  * Copyright © 2022 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 "pcie_card_floors.hpp"
17 
18 #include "../manager.hpp"
19 #include "json_config.hpp"
20 #include "sdbusplus.hpp"
21 #include "sdeventplus.hpp"
22 
23 namespace phosphor::fan::control::json
24 {
25 
26 constexpr auto floorIndexParam = "pcie_floor_index";
27 constexpr auto pcieDeviceIface =
28     "xyz.openbmc_project.Inventory.Item.PCIeDevice";
29 constexpr auto powerStateIface =
30     "xyz.openbmc_project.State.Decorator.PowerState";
31 constexpr auto deviceIDProp = "Function0DeviceId";
32 constexpr auto vendorIDProp = "Function0VendorId";
33 constexpr auto subsystemIDProp = "Function0SubsystemId";
34 constexpr auto subsystemVendorIDProp = "Function0SubsystemVendorId";
35 
36 PCIeCardFloors::PCIeCardFloors(const json& jsonObj,
37                                const std::vector<Group>& groups) :
38     ActionBase(jsonObj, groups)
39 {
40     loadCardJSON(jsonObj);
41 }
42 
43 void PCIeCardFloors::run(Zone& zone)
44 {
45     if (_settleTimer)
46     {
47         _settleTimer->setEnabled(false);
48     }
49     else
50     {
51         _settleTimer =
52             std::make_unique<Timer>(util::SDEventPlus::getEvent(),
53                                     [&zone, this](Timer&) { execute(); });
54     }
55     _settleTimer->restartOnce(_settleTime);
56 }
57 
58 void PCIeCardFloors::execute()
59 {
60     size_t hotCards = 0;
61     size_t numTempSensorCards = 0;
62     size_t uninterestingCards = 0;
63     int32_t floorIndex = -1;
64 
65     for (const auto& group : _groups)
66     {
67         if (group.getInterface() != powerStateIface)
68         {
69             log<level::DEBUG>(
70                 fmt::format("Wrong interface {} in PCIe card floor group",
71                             group.getInterface())
72                     .c_str());
73             continue;
74         }
75 
76         for (const auto& slotPath : group.getMembers())
77         {
78             PropertyVariantType powerState;
79 
80             try
81             {
82                 powerState = Manager::getObjValueVariant(
83                     slotPath, group.getInterface(), group.getProperty());
84             }
85             catch (const std::out_of_range& oore)
86             {
87                 log<level::ERR>(
88                     fmt::format("Could not get power state for {}", slotPath)
89                         .c_str());
90                 continue;
91             }
92 
93             if (std::get<std::string>(powerState) !=
94                 "xyz.openbmc_project.State.Decorator.PowerState.State.On")
95             {
96                 continue;
97             }
98 
99             auto floorIndexOrTempSensor = getFloorIndexFromSlot(slotPath);
100             if (floorIndexOrTempSensor)
101             {
102                 if (std::holds_alternative<int32_t>(*floorIndexOrTempSensor))
103                 {
104                     hotCards++;
105                     floorIndex = std::max(
106                         floorIndex, std::get<int32_t>(*floorIndexOrTempSensor));
107                 }
108                 else
109                 {
110                     numTempSensorCards++;
111                 }
112             }
113             else
114             {
115                 uninterestingCards++;
116             }
117         }
118     }
119 
120     auto status = fmt::format(
121         "Found {} hot cards, {} with temp sensors, {} uninteresting", hotCards,
122         numTempSensorCards, uninterestingCards);
123     if (status != _lastStatus)
124     {
125         record(status);
126         _lastStatus = status;
127     }
128 
129     int32_t origIndex = -1;
130     auto origIndexVariant = Manager::getParameter(floorIndexParam);
131     if (origIndexVariant)
132     {
133         origIndex = std::get<int32_t>(*origIndexVariant);
134     }
135 
136     if (floorIndex != -1)
137     {
138         if (origIndex != floorIndex)
139         {
140             record(fmt::format("Setting {} parameter to {}", floorIndexParam,
141                                floorIndex));
142             Manager::setParameter(floorIndexParam, floorIndex);
143         }
144     }
145     else if (origIndexVariant)
146     {
147         record(fmt::format("Removing parameter {}", floorIndexParam));
148         Manager::setParameter(floorIndexParam, std::nullopt);
149     }
150 }
151 
152 void PCIeCardFloors::loadCardJSON(const json& jsonObj)
153 {
154     std::string baseConfigFile;
155     bool useConfigSpecificFiles = false;
156 
157     if (jsonObj.contains("settle_time"))
158     {
159         _settleTime =
160             std::chrono::seconds(jsonObj.at("settle_time").get<size_t>());
161     }
162 
163     if (jsonObj.contains("use_config_specific_files"))
164     {
165         useConfigSpecificFiles =
166             jsonObj.at("use_config_specific_files").get<bool>();
167     }
168 
169     std::vector<std::string> names;
170     if (useConfigSpecificFiles)
171     {
172         names = phosphor::fan::JsonConfig::getCompatValues();
173     }
174 
175     _cardMetadata = std::make_unique<PCIeCardMetadata>(names);
176 }
177 
178 uint16_t PCIeCardFloors::getPCIeDeviceProperty(const std::string& objectPath,
179                                                const std::string& propertyName)
180 {
181     PropertyVariantType variantValue;
182     uint16_t value{};
183 
184     try
185     {
186         variantValue = Manager::getObjValueVariant(objectPath, pcieDeviceIface,
187                                                    propertyName);
188     }
189     catch (const std::out_of_range& oore)
190     {
191         log<level::ERR>(
192             fmt::format(
193                 "{}: Could not get PCIeDevice property {} {} from cache ",
194                 ActionBase::getName(), objectPath, propertyName)
195                 .c_str());
196         throw;
197     }
198 
199     try
200     {
201         value = std::stoul(std::get<std::string>(variantValue), nullptr, 0);
202         return value;
203     }
204     catch (const std::invalid_argument& e)
205     {
206         log<level::INFO>(
207             fmt::format("{}: {} has invalid PCIeDevice property {} value: {}",
208                         ActionBase::getName(), objectPath, propertyName,
209                         std::get<std::string>(variantValue))
210                 .c_str());
211         throw;
212     }
213 }
214 
215 std::optional<std::variant<int32_t, bool>>
216     PCIeCardFloors::getFloorIndexFromSlot(const std::string& slotPath)
217 {
218     const auto& card = getCardFromSlot(slotPath);
219 
220     try
221     {
222         auto deviceID = getPCIeDeviceProperty(card, deviceIDProp);
223         auto vendorID = getPCIeDeviceProperty(card, vendorIDProp);
224         auto subsystemID = getPCIeDeviceProperty(card, subsystemIDProp);
225         auto subsystemVendorID =
226             getPCIeDeviceProperty(card, subsystemVendorIDProp);
227 
228         return _cardMetadata->lookup(deviceID, vendorID, subsystemID,
229                                      subsystemVendorID);
230     }
231     catch (const std::exception& e)
232     {}
233 
234     return std::nullopt;
235 }
236 
237 const std::string& PCIeCardFloors::getCardFromSlot(const std::string& slotPath)
238 {
239     auto cardIt = _cards.find(slotPath);
240 
241     if (cardIt != _cards.end())
242     {
243         return cardIt->second;
244     }
245 
246     // Just the first time, find all the PCIeDevice objects
247     if (_pcieDevices.empty())
248     {
249         _pcieDevices = util::SDBusPlus::getSubTreePaths(
250             util::SDBusPlus::getBus(), "/", pcieDeviceIface, 0);
251     }
252 
253     // Find the card that plugs in this slot based on if the
254     // slot is part of the path, like slotA/cardA
255     auto it = std::find_if(
256         _pcieDevices.begin(), _pcieDevices.end(), [slotPath](const auto& path) {
257             return path.find(slotPath + '/') != std::string::npos;
258         });
259 
260     if (it == _pcieDevices.end())
261     {
262         throw std::runtime_error(fmt::format(
263             "Could not find PCIe card object path for slot {}", slotPath));
264     }
265 
266     _cards.emplace(slotPath, *it);
267 
268     return _cards.at(slotPath);
269 }
270 
271 } // namespace phosphor::fan::control::json
272