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