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     bool useConfigSpecificFiles = false;
155 
156     if (jsonObj.contains("settle_time"))
157     {
158         _settleTime =
159             std::chrono::seconds(jsonObj.at("settle_time").get<size_t>());
160     }
161 
162     if (jsonObj.contains("use_config_specific_files"))
163     {
164         useConfigSpecificFiles =
165             jsonObj.at("use_config_specific_files").get<bool>();
166     }
167 
168     std::vector<std::string> names;
169     if (useConfigSpecificFiles)
170     {
171         names = phosphor::fan::JsonConfig::getCompatValues();
172     }
173 
174     _cardMetadata = std::make_unique<PCIeCardMetadata>(names);
175 }
176 
177 uint16_t PCIeCardFloors::getPCIeDeviceProperty(const std::string& objectPath,
178                                                const std::string& propertyName)
179 {
180     PropertyVariantType variantValue;
181     uint16_t value{};
182 
183     try
184     {
185         variantValue = Manager::getObjValueVariant(objectPath, pcieDeviceIface,
186                                                    propertyName);
187     }
188     catch (const std::out_of_range& oore)
189     {
190         log<level::ERR>(
191             fmt::format(
192                 "{}: Could not get PCIeDevice property {} {} from cache ",
193                 ActionBase::getName(), objectPath, propertyName)
194                 .c_str());
195         throw;
196     }
197 
198     try
199     {
200         value = std::stoul(std::get<std::string>(variantValue), nullptr, 0);
201         return value;
202     }
203     catch (const std::invalid_argument& e)
204     {
205         log<level::INFO>(
206             fmt::format("{}: {} has invalid PCIeDevice property {} value: {}",
207                         ActionBase::getName(), objectPath, propertyName,
208                         std::get<std::string>(variantValue))
209                 .c_str());
210         throw;
211     }
212 }
213 
214 std::optional<std::variant<int32_t, bool>>
215     PCIeCardFloors::getFloorIndexFromSlot(const std::string& slotPath)
216 {
217     const auto& card = getCardFromSlot(slotPath);
218 
219     try
220     {
221         auto deviceID = getPCIeDeviceProperty(card, deviceIDProp);
222         auto vendorID = getPCIeDeviceProperty(card, vendorIDProp);
223         auto subsystemID = getPCIeDeviceProperty(card, subsystemIDProp);
224         auto subsystemVendorID = getPCIeDeviceProperty(card,
225                                                        subsystemVendorIDProp);
226 
227         return _cardMetadata->lookup(deviceID, vendorID, subsystemID,
228                                      subsystemVendorID);
229     }
230     catch (const std::exception& e)
231     {}
232 
233     return std::nullopt;
234 }
235 
236 const std::string& PCIeCardFloors::getCardFromSlot(const std::string& slotPath)
237 {
238     auto cardIt = _cards.find(slotPath);
239 
240     if (cardIt != _cards.end())
241     {
242         return cardIt->second;
243     }
244 
245     // Just the first time, find all the PCIeDevice objects
246     if (_pcieDevices.empty())
247     {
248         _pcieDevices = util::SDBusPlus::getSubTreePaths(
249             util::SDBusPlus::getBus(), "/", pcieDeviceIface, 0);
250     }
251 
252     // Find the card that plugs in this slot based on if the
253     // slot is part of the path, like slotA/cardA
254     auto it = std::find_if(_pcieDevices.begin(), _pcieDevices.end(),
255                            [slotPath](const auto& path) {
256         return path.find(slotPath + '/') != std::string::npos;
257     });
258 
259     if (it == _pcieDevices.end())
260     {
261         throw std::runtime_error(fmt::format(
262             "Could not find PCIe card object path for slot {}", slotPath));
263     }
264 
265     _cards.emplace(slotPath, *it);
266 
267     return _cards.at(slotPath);
268 }
269 
270 } // namespace phosphor::fan::control::json
271