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 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance) 203 { 204 // Check if we support this Entity ID. 205 auto it = _entityIdToName.find(id); 206 if (it == _entityIdToName.end()) 207 { 208 log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id)); 209 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 210 } 211 212 std::string entityName; 213 try 214 { 215 // Parse the JSON config file. 216 if (!_entityConfigParsed) 217 { 218 _entityConfig = parseConfig(_configFile); 219 _entityConfigParsed = true; 220 } 221 222 // Find the "entity id:entity instance" mapping to entity name. 223 entityName = readNameFromConfig(it->second, instance, _entityConfig); 224 if (entityName.empty()) 225 { 226 throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST); 227 } 228 } 229 catch (InternalFailure& e) 230 { 231 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 232 } 233 234 return entityName; 235 } 236 237 std::string Handler::getMachineName() 238 { 239 const char* path = "/etc/os-release"; 240 std::ifstream ifs(path); 241 if (ifs.fail()) 242 { 243 std::fprintf(stderr, "Failed to open: %s\n", path); 244 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 245 } 246 247 std::string line; 248 while (true) 249 { 250 std::getline(ifs, line); 251 if (ifs.eof()) 252 { 253 std::fprintf(stderr, "Failed to find OPENBMC_TARGET_MACHINE: %s\n", 254 path); 255 throw IpmiException(IPMI_CC_INVALID); 256 } 257 if (ifs.fail()) 258 { 259 std::fprintf(stderr, "Failed to read: %s\n", path); 260 throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR); 261 } 262 std::string_view lineView(line); 263 constexpr std::string_view prefix = "OPENBMC_TARGET_MACHINE="; 264 if (lineView.substr(0, prefix.size()) != prefix) 265 { 266 continue; 267 } 268 lineView.remove_prefix(prefix.size()); 269 lineView.remove_prefix( 270 std::min(lineView.find_first_not_of('"'), lineView.size())); 271 lineView.remove_suffix( 272 lineView.size() - 1 - 273 std::min(lineView.find_last_not_of('"'), lineView.size() - 1)); 274 return std::string(lineView); 275 } 276 } 277 278 std::string readNameFromConfig(const std::string& type, uint8_t instance, 279 const Json& config) 280 { 281 static const std::vector<Json> empty{}; 282 std::vector<Json> readings = config.value(type, empty); 283 std::string name = ""; 284 285 for (const auto& j : readings) 286 { 287 uint8_t instanceNum = j.value("instance", 0); 288 // Not the instance we're interested in 289 if (instanceNum != instance) 290 { 291 continue; 292 } 293 294 // Found the instance we're interested in 295 name = j.value("name", ""); 296 297 break; 298 } 299 300 return name; 301 } 302 303 void Handler::buildI2cPcieMapping() 304 { 305 _pcie_i2c_map = buildPcieMap(); 306 } 307 308 size_t Handler::getI2cPcieMappingSize() const 309 { 310 return _pcie_i2c_map.size(); 311 } 312 313 std::tuple<std::uint32_t, std::string> 314 Handler::getI2cEntry(unsigned int entry) const 315 { 316 return _pcie_i2c_map[entry]; 317 } 318 319 const std::string defaultConfigFile = 320 "/usr/share/ipmi-entity-association/entity_association_map.json"; 321 322 } // namespace ipmi 323 } // namespace google 324