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