1 /** 2 * Copyright © 2019 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 "config.h" 17 18 #include "version.hpp" 19 20 #include "pmbus.hpp" 21 #include "utility.hpp" 22 #include "utils.hpp" 23 24 #include <nlohmann/json.hpp> 25 #include <phosphor-logging/log.hpp> 26 27 #include <exception> 28 #include <tuple> 29 30 using json = nlohmann::json; 31 32 using namespace utils; 33 using namespace phosphor::logging; 34 using namespace phosphor::power::util; 35 36 namespace version 37 { 38 39 namespace internal 40 { 41 42 // PsuInfo contains the device path, PMBus access type, and sysfs file name 43 using PsuVersionInfo = 44 std::tuple<std::string, phosphor::pmbus::Type, std::string>; 45 46 /** 47 * @brief Get PSU version information 48 * 49 * @param[in] psuInventoryPath - The PSU inventory path. 50 * 51 * @return tuple - device path, PMBus access type, and sysfs file name 52 */ 53 PsuVersionInfo getVersionInfo(const std::string& psuInventoryPath) 54 { 55 auto data = loadJSONFromFile(PSU_JSON_PATH); 56 57 if (data == nullptr) 58 { 59 return {}; 60 } 61 62 auto devices = data.find("psuDevices"); 63 if (devices == data.end()) 64 { 65 log<level::WARNING>("Unable to find psuDevices"); 66 return {}; 67 } 68 auto devicePath = devices->find(psuInventoryPath); 69 if (devicePath == devices->end()) 70 { 71 log<level::WARNING>("Unable to find path for PSU", 72 entry("PATH=%s", psuInventoryPath.c_str())); 73 return {}; 74 } 75 76 auto type = getPMBusAccessType(data); 77 78 std::string fileName; 79 for (const auto& fru : data["fruConfigs"]) 80 { 81 if (fru.contains("propertyName") && 82 (fru["propertyName"] == "Version") && fru.contains("fileName")) 83 { 84 fileName = fru["fileName"]; 85 break; 86 } 87 } 88 if (fileName.empty()) 89 { 90 log<level::WARNING>("Unable to find Version file"); 91 return {}; 92 } 93 return std::make_tuple(*devicePath, type, fileName); 94 } 95 96 /** 97 * @brief Get the PSU version from sysfs. 98 * 99 * Obtain PSU information from the PSU JSON file. 100 * 101 * Throws an exception if an error occurs. 102 * 103 * @param[in] psuInventoryPath - PSU D-Bus inventory path 104 * 105 * @return PSU version, or "" if none found 106 */ 107 std::string getVersionJson(const std::string& psuInventoryPath) 108 { 109 // Get PSU device path, PMBus access type, and sysfs file name from JSON 110 const auto [devicePath, type, fileName] = getVersionInfo(psuInventoryPath); 111 112 // Read version from sysfs file 113 std::string version; 114 if (!devicePath.empty() && !fileName.empty()) 115 { 116 phosphor::pmbus::PMBus pmbus(devicePath); 117 version = pmbus.readString(fileName, type); 118 } 119 return version; 120 } 121 122 /** 123 * @brief Get the PSU version from sysfs. 124 * 125 * Obtain PSU information from D-Bus. 126 * 127 * Throws an exception if an error occurs. 128 * 129 * @param[in] bus - D-Bus connection 130 * @param[in] psuInventoryPath - PSU D-Bus inventory path 131 * 132 * @return PSU version, or "" if none found 133 */ 134 std::string getVersionDbus(sdbusplus::bus_t& bus, 135 const std::string& psuInventoryPath) 136 { 137 // Get PSU I2C bus/address and create PMBus interface 138 const auto [i2cbus, i2caddr] = getPsuI2c(bus, psuInventoryPath); 139 auto pmbus = getPmbusIntf(i2cbus, i2caddr); 140 141 // Read version from sysfs file 142 std::string name = "fw_version"; 143 auto type = phosphor::pmbus::Type::HwmonDeviceDebug; 144 std::string version = pmbus->readString(name, type); 145 return version; 146 } 147 148 /** 149 * @brief Get firmware latest version 150 * 151 * @param[in] versions - String of versions 152 * 153 * @return version - latest firmware level 154 */ 155 std::string getLatestDefault(const std::vector<std::string>& versions) 156 { 157 std::string latest; 158 for (const auto& version : versions) 159 { 160 if (latest < version) 161 { 162 latest = version; 163 } 164 } 165 return latest; 166 } 167 168 } // namespace internal 169 170 std::string getVersion(sdbusplus::bus_t& bus, 171 const std::string& psuInventoryPath) 172 { 173 std::string version; 174 try 175 { 176 if (usePsuJsonFile()) 177 { 178 // Obtain PSU information from JSON file 179 version = internal::getVersionJson(psuInventoryPath); 180 } 181 else 182 { 183 // Obtain PSU information from D-Bus 184 version = internal::getVersionDbus(bus, psuInventoryPath); 185 } 186 } 187 catch (const std::exception& e) 188 { 189 log<level::ERR>(std::format("Error: {}", e.what()).c_str()); 190 } 191 return version; 192 } 193 194 std::string getLatest(const std::vector<std::string>& versions) 195 { 196 // TODO: when multiple PSU/Machines are supported, add configuration options 197 // to implement machine-specific logic. 198 // For now IBM AC Servers and Inspur FP5280G2 are supported. 199 // 200 // IBM AC servers' PSU version has two types: 201 // * XXXXYYYYZZZZ: XXXX is the primary version 202 // YYYY is the secondary version 203 // ZZZZ is the communication version 204 // 205 // * XXXXYYYY: XXXX is the primary version 206 // YYYY is the seconday version 207 // 208 // Inspur FP5280G2 PSU version is human readable text and a larger string 209 // means a newer version. 210 // 211 // So just compare by strings is OK for these cases 212 return internal::getLatestDefault(versions); 213 } 214 215 } // namespace version 216