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 
23 #include <phosphor-logging/log.hpp>
24 
25 #include <tuple>
26 
27 using json = nlohmann::json;
28 
29 using namespace phosphor::logging;
30 
31 // PsuInfo contains the device path, pmbus read type, and the version string
32 using PsuVersionInfo =
33     std::tuple<std::string, phosphor::pmbus::Type, std::string>;
34 
35 namespace utils
36 {
37 PsuVersionInfo getVersionInfo(const std::string& psuInventoryPath)
38 {
39     auto data = phosphor::power::util::loadJSONFromFile(PSU_JSON_PATH);
40 
41     if (data == nullptr)
42     {
43         return {};
44     }
45 
46     auto devices = data.find("psuDevices");
47     if (devices == data.end())
48     {
49         log<level::WARNING>("Unable to find psuDevices");
50         return {};
51     }
52     auto devicePath = devices->find(psuInventoryPath);
53     if (devicePath == devices->end())
54     {
55         log<level::WARNING>("Unable to find path for PSU",
56                             entry("PATH=%s", psuInventoryPath.c_str()));
57         return {};
58     }
59 
60     auto type = phosphor::power::util::getPMBusAccessType(data);
61 
62     std::string versionStr;
63     for (const auto& fru : data["fruConfigs"])
64     {
65         if (fru["propertyName"] == "Version")
66         {
67             versionStr = fru["fileName"].get<std::string>();
68             break;
69         }
70     }
71     if (versionStr.empty())
72     {
73         log<level::WARNING>("Unable to find Version file");
74         return {};
75     }
76     return std::make_tuple(*devicePath, type, versionStr);
77 }
78 
79 // A default implemention compare the string itself
80 std::string getLatestDefault(const std::vector<std::string>& versions)
81 {
82     std::string latest;
83     for (const auto& version : versions)
84     {
85         if (latest < version)
86         {
87             latest = version;
88         }
89     }
90     return latest;
91 }
92 
93 } // namespace utils
94 
95 namespace version
96 {
97 
98 std::string getVersion(const std::string& psuInventoryPath)
99 {
100     const auto& [devicePath, type,
101                  versionStr] = utils::getVersionInfo(psuInventoryPath);
102     if (devicePath.empty() || versionStr.empty())
103     {
104         return {};
105     }
106     std::string version;
107     try
108     {
109         phosphor::pmbus::PMBus pmbus(devicePath);
110         version = pmbus.readString(versionStr, type);
111     }
112     catch (const std::exception& ex)
113     {
114         log<level::ERR>(ex.what());
115     }
116     return version;
117 }
118 
119 std::string getLatest(const std::vector<std::string>& versions)
120 {
121     // TODO: when multiple PSU/Machines are supported, add configuration options
122     // to implement machine-specific logic.
123     // For now IBM AC Servers and Inspur FP5280G2 are supported.
124     //
125     // IBM AC servers' PSU version has two types:
126     // * XXXXYYYYZZZZ: XXXX is the primary version
127     //                 YYYY is the secondary version
128     //                 ZZZZ is the communication version
129     //
130     // * XXXXYYYY:     XXXX is the primary version
131     //                 YYYY is the seconday version
132     //
133     // Inspur FP5280G2 PSU version is human readable text and a larger string
134     // means a newer version.
135     //
136     // So just compare by strings is OK for these cases
137     return utils::getLatestDefault(versions);
138 }
139 
140 } // namespace version
141