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