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