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