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