1 /* 2 * Copyright 2019 Google Inc. 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 17 #include "handler.hpp" 18 19 #include "errors.hpp" 20 21 #include <ipmid/api.h> 22 23 #include <cinttypes> 24 #include <cstdio> 25 #include <filesystem> 26 #include <fstream> 27 #include <map> 28 #include <nlohmann/json.hpp> 29 #include <phosphor-logging/elog-errors.hpp> 30 #include <phosphor-logging/log.hpp> 31 #include <sdbusplus/bus.hpp> 32 #include <sstream> 33 #include <string> 34 #include <tuple> 35 #include <xyz/openbmc_project/Common/error.hpp> 36 37 // The phosphor-host-ipmi daemon requires a configuration that maps 38 // the if_name to the IPMI LAN channel. However, that doesn't strictly 39 // define which is meant to be used for NCSI. 40 #ifndef NCSI_IPMI_CHANNEL 41 #define NCSI_IPMI_CHANNEL 1 42 #endif 43 44 #ifndef NCSI_IF_NAME 45 #define NCSI_IF_NAME eth0 46 #endif 47 48 // To deal with receiving a string without quotes. 49 #define QUOTE(name) #name 50 #define STR(macro) QUOTE(macro) 51 #define NCSI_IF_NAME_STR STR(NCSI_IF_NAME) 52 53 namespace google 54 { 55 namespace ipmi 56 { 57 namespace fs = std::filesystem; 58 using Json = nlohmann::json; 59 using namespace phosphor::logging; 60 using InternalFailure = 61 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 62 63 std::tuple<std::uint8_t, std::string> Handler::getEthDetails() const 64 { 65 return std::make_tuple(NCSI_IPMI_CHANNEL, NCSI_IF_NAME_STR); 66 } 67 68 std::int64_t Handler::getRxPackets(const std::string& name) const 69 { 70 std::ostringstream opath; 71 opath << "/sys/class/net/" << name << "/statistics/rx_packets"; 72 std::string path = opath.str(); 73 74 // Minor sanity & security check (of course, I'm less certain if unicode 75 // comes into play here. 76 // 77 // Basically you can't easily inject ../ or /../ into the path below. 78 if (name.find("/") != std::string::npos) 79 { 80 std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str()); 81 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 82 } 83 84 std::error_code ec; 85 if (!fs::exists(path, ec)) 86 { 87 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str()); 88 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 89 } 90 // We're uninterested in the state of ec. 91 92 int64_t count = 0; 93 std::ifstream ifs; 94 ifs.exceptions(std::ifstream::failbit); 95 try 96 { 97 ifs.open(path); 98 ifs >> count; 99 } 100 catch (std::ios_base::failure& fail) 101 { 102 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 103 } 104 105 return count; 106 } 107 108 VersionTuple Handler::getCpldVersion(unsigned int id) const 109 { 110 std::ostringstream opath; 111 opath << "/run/cpld" << id << ".version"; 112 // Check for file 113 114 std::error_code ec; 115 if (!fs::exists(opath.str(), ec)) 116 { 117 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", 118 opath.str().c_str()); 119 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 120 } 121 // We're uninterested in the state of ec. 122 123 // If file exists, read. 124 std::ifstream ifs; 125 ifs.exceptions(std::ifstream::failbit); 126 std::string value; 127 try 128 { 129 ifs.open(opath.str()); 130 ifs >> value; 131 } 132 catch (std::ios_base::failure& fail) 133 { 134 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 135 } 136 137 // If value parses as expected, return version. 138 VersionTuple version = std::make_tuple(0, 0, 0, 0); 139 140 int num_fields = 141 std::sscanf(value.c_str(), "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8, 142 &std::get<0>(version), &std::get<1>(version), 143 &std::get<2>(version), &std::get<3>(version)); 144 if (num_fields == 0) 145 { 146 std::fprintf(stderr, "Invalid version.\n"); 147 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 148 } 149 150 return version; 151 } 152 153 static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay"; 154 static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 155 static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; 156 static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 157 static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target"; 158 159 void Handler::psuResetDelay(std::uint32_t delay) const 160 { 161 std::ofstream ofs; 162 ofs.open(TIME_DELAY_FILENAME, std::ofstream::out); 163 if (!ofs.good()) 164 { 165 std::fprintf(stderr, "Unable to open file for output.\n"); 166 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 167 } 168 169 ofs << "PSU_HARDRESET_DELAY=" << delay << std::endl; 170 if (ofs.fail()) 171 { 172 std::fprintf(stderr, "Write failed\n"); 173 ofs.close(); 174 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 175 } 176 177 // Write succeeded, please continue. 178 ofs.flush(); 179 ofs.close(); 180 181 auto bus = sdbusplus::bus::new_default(); 182 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 183 SYSTEMD_INTERFACE, "StartUnit"); 184 185 method.append(PSU_HARDRESET_TARGET); 186 method.append("replace"); 187 188 try 189 { 190 bus.call_noreply(method); 191 } 192 catch (const sdbusplus::exception::SdBusError& ex) 193 { 194 log<level::ERR>("Failed to call PSU hard reset"); 195 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 196 } 197 } 198 199 static Json config{}; 200 static bool parsed = false; 201 static const std::map<uint8_t, std::string> entityIdToName{ 202 {0x03, "cpu"}, 203 {0x04, "storage_device"}, 204 {0x06, "system_management_module"}, 205 {0x08, "memory_module"}, 206 {0x0B, "add_in_card"}, 207 {0x17, "system_chassis"}, 208 {0x20, "memory_device"}}; 209 static constexpr auto configFile = 210 "/usr/share/ipmi-entity-association/entity_association_map.json"; 211 212 Json parse_config() 213 { 214 std::ifstream jsonFile(configFile); 215 if (!jsonFile.is_open()) 216 { 217 log<level::ERR>("Entity association JSON file not found"); 218 elog<InternalFailure>(); 219 } 220 221 auto data = Json::parse(jsonFile, nullptr, false); 222 if (data.is_discarded()) 223 { 224 log<level::ERR>("Entity association JSON parser failure"); 225 elog<InternalFailure>(); 226 } 227 228 return data; 229 } 230 231 std::string read(const std::string& type, uint8_t instance, const Json& config) 232 { 233 static const std::vector<Json> empty{}; 234 std::vector<Json> readings = config.value(type, empty); 235 std::string name = ""; 236 for (const auto& j : readings) 237 { 238 uint8_t instanceNum = j.value("instance", 0); 239 // Not the instance we're interested in 240 if (instanceNum != instance) 241 { 242 continue; 243 } 244 245 // Found the instance we're interested in 246 name = j.value("name", ""); 247 248 break; 249 } 250 return name; 251 } 252 253 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance) 254 { 255 // Check if we support this Entity ID. 256 auto it = entityIdToName.find(id); 257 if (it == entityIdToName.end()) 258 { 259 log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id)); 260 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 261 } 262 263 std::string entityName; 264 try 265 { 266 // Parse the JSON config file. 267 if (!parsed) 268 { 269 config = parse_config(); 270 parsed = true; 271 } 272 273 // Find the "entity id:entity instance" mapping to entity name. 274 entityName = read(it->second, instance, config); 275 if (entityName.empty()) 276 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 277 } 278 catch (InternalFailure& e) 279 { 280 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 281 } 282 283 return entityName; 284 } 285 286 Handler handlerImpl; 287 288 } // namespace ipmi 289 } // namespace google 290