1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "app.hpp" 6 #include "async_resp.hpp" 7 #include "dbus_utility.hpp" 8 #include "error_messages.hpp" 9 #include "fabric.hpp" 10 #include "generated/enums/port.hpp" 11 #include "generated/enums/resource.hpp" 12 #include "http_request.hpp" 13 #include "logging.hpp" 14 #include "query.hpp" 15 #include "registries/privilege_registry.hpp" 16 17 #include <boost/beast/http/verb.hpp> 18 #include <boost/system/error_code.hpp> 19 #include <nlohmann/json.hpp> 20 #include <sdbusplus/message/native_types.hpp> 21 #include <sdbusplus/unpack_properties.hpp> 22 23 #include <array> 24 #include <cstddef> 25 #include <format> 26 #include <functional> 27 #include <memory> 28 #include <optional> 29 #include <string> 30 #include <string_view> 31 #include <utility> 32 33 static constexpr const char* inventoryPath = "/xyz/openbmc_project/inventory"; 34 static constexpr std::array<std::string_view, 1> portInterface = { 35 "xyz.openbmc_project.Inventory.Connector.Port"}; 36 37 namespace redfish 38 { 39 inline port::PortType dBusSensorPortTypeToRedfish(const std::string& portType) 40 { 41 if (portType == 42 "xyz.openbmc_project.Inventory.Connector.Port.PortType.DownstreamPort") 43 { 44 return port::PortType::DownstreamPort; 45 } 46 47 if (portType == 48 "xyz.openbmc_project.Inventory.Connector.Port.PortType.UpstreamPort") 49 { 50 return port::PortType::UpstreamPort; 51 } 52 53 return port::PortType::Invalid; 54 } 55 56 inline std::string dBusSensorPortProtocolToRedfish( 57 const std::string& portProtocol) 58 { 59 if (portProtocol == 60 "xyz.openbmc_project.Inventory.Connector.Port.PortProtocol.PCIe") 61 { 62 return "PCIe"; 63 } 64 65 return "Unknown"; 66 } 67 68 inline void afterGetFabricSwitchPortInfo( 69 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 70 const boost::system::error_code& ec, 71 const dbus::utility::DBusPropertiesMap& properties) 72 { 73 if (ec) 74 { 75 BMCWEB_LOG_ERROR("DBus response error on GetAllProperties {}", 76 ec.value()); 77 messages::internalError(asyncResp->res); 78 return; 79 } 80 81 std::optional<uint64_t> speed; 82 std::optional<size_t> width; 83 std::optional<std::string> portType; 84 std::optional<std::string> portProtocol; 85 86 const bool success = sdbusplus::unpackPropertiesNoThrow( 87 dbus_utils::UnpackErrorPrinter(), properties, "Speed", speed, "Width", 88 width, "PortType", portType, "PortProtocol", portProtocol); 89 90 if (!success) 91 { 92 messages::internalError(asyncResp->res); 93 return; 94 } 95 96 if (speed.has_value()) 97 { 98 if (*speed != 0 && *speed != std::numeric_limits<uint64_t>::max()) 99 { 100 // Convert from bits per second to Gigabits per second (Gbps) 101 static constexpr int gbpsToBps = 1 << 30; 102 const double convertedSpeed = 103 static_cast<double>(*speed) / gbpsToBps; 104 105 asyncResp->res.jsonValue["CurrentSpeedGbps"] = convertedSpeed; 106 } 107 } 108 if (width.has_value()) 109 { 110 if (*width != 0 && *width != std::numeric_limits<size_t>::max()) 111 { 112 asyncResp->res.jsonValue["ActiveWidth"] = *width; 113 } 114 } 115 if (portType.has_value()) 116 { 117 const port::PortType portTypeEnum = 118 dBusSensorPortTypeToRedfish(*portType); 119 if (portTypeEnum != port::PortType::Invalid) 120 { 121 asyncResp->res.jsonValue["PortType"] = portTypeEnum; 122 } 123 } 124 if (portProtocol.has_value()) 125 { 126 const std::string portProtocolStr = 127 dBusSensorPortProtocolToRedfish(*portProtocol); 128 if (portProtocolStr != "Unknown") 129 { 130 asyncResp->res.jsonValue["PortProtocol"] = portProtocolStr; 131 } 132 } 133 } 134 135 inline void handleFabricSwitchPortPathPortMetricsGet( 136 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 137 const std::string& fabricId, const std::string& switchId, 138 const std::string& portId, [[maybe_unused]] const std::string& portPath, 139 [[maybe_unused]] const std::string& serviceName) 140 { 141 asyncResp->res.jsonValue["@odata.type"] = "#PortMetrics.v1_3_0.PortMetrics"; 142 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 143 "/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}/Metrics", fabricId, 144 switchId, portId); 145 asyncResp->res.jsonValue["Id"] = "Metrics"; 146 asyncResp->res.jsonValue["Name"] = 147 std::format("{} {} Port Metrics", switchId, portId); 148 } 149 150 inline void handleFabricSwitchPortPathPortGet( 151 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 152 const std::string& fabricId, const std::string& switchId, 153 const std::string& portId, const std::string& portPath, 154 const std::string& serviceName) 155 { 156 asyncResp->res.jsonValue["@odata.type"] = "#Port.v1_4_0.Port"; 157 asyncResp->res.jsonValue["@odata.id"] = 158 boost::urls::format("/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}", 159 fabricId, switchId, portId); 160 asyncResp->res.jsonValue["Id"] = portId; 161 asyncResp->res.jsonValue["Name"] = 162 std::format("{} {} Port", switchId, portId); 163 164 nlohmann::json& status = asyncResp->res.jsonValue["Status"]; 165 status["Health"] = resource::Health::OK; 166 status["State"] = resource::State::Enabled; 167 168 asyncResp->res.jsonValue["Metrics"]["@odata.id"] = boost::urls::format( 169 "/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}/Metrics", fabricId, 170 switchId, portId); 171 172 dbus::utility::getAllProperties( 173 serviceName, portPath, std::string{portInterface[0]}, 174 std::bind_front(afterGetFabricSwitchPortInfo, asyncResp)); 175 } 176 177 inline void afterHandleFabricSwitchPortPaths( 178 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 179 const std::string& portId, 180 const std::function<void(const std::string& portPath, 181 const std::string& serviceName)>& callback, 182 const boost::system::error_code& ec, 183 const dbus::utility::MapperGetSubTreeResponse& object) 184 { 185 if (ec) 186 { 187 BMCWEB_LOG_ERROR("DBus response error on GetAssociatedSubTreeById {}", 188 ec); 189 messages::internalError(asyncResp->res); 190 return; 191 } 192 193 std::string portPath; 194 std::string serviceName; 195 for (const auto& [path, service] : object) 196 { 197 std::string portName = sdbusplus::message::object_path(path).filename(); 198 if (portName == portId) 199 { 200 portPath = path; 201 if (service.size() != 1) 202 { 203 messages::internalError(asyncResp->res); 204 return; 205 } 206 serviceName = service.begin()->first; 207 break; 208 } 209 } 210 211 if (portPath.empty()) 212 { 213 messages::resourceNotFound(asyncResp->res, "Port", portId); 214 return; 215 } 216 217 callback(portPath, serviceName); 218 } 219 220 inline void getAssociatedPortPath( 221 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 222 const std::string& portId, 223 std::function<void(const std::string& portPath, 224 const std::string& serviceName)>&& callback, 225 const std::string& switchPath) 226 { 227 std::string associationPath = switchPath + "/connecting"; 228 dbus::utility::getAssociatedSubTree( 229 associationPath, sdbusplus::message::object_path{inventoryPath}, 0, 230 portInterface, 231 std::bind_front(afterHandleFabricSwitchPortPaths, asyncResp, portId, 232 std::move(callback))); 233 } 234 235 inline void handleFabricSwitchPathPortCollection( 236 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 237 const std::string& fabricId, const std::string& switchId, 238 const boost::system::error_code& ec, 239 const dbus::utility::MapperGetSubTreePathsResponse& object) 240 { 241 if (ec) 242 { 243 BMCWEB_LOG_ERROR("DBus response error on GetSubTreePaths {}", ec); 244 messages::internalError(asyncResp->res); 245 return; 246 } 247 248 asyncResp->res.jsonValue["@odata.type"] = "#PortCollection.PortCollection"; 249 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 250 "/redfish/v1/Fabrics/{}/Switches/{}/Ports", fabricId, switchId); 251 asyncResp->res.jsonValue["Name"] = switchId + " Port Collection"; 252 253 asyncResp->res.jsonValue["Members@odata.count"] = object.size(); 254 255 nlohmann::json::array_t members; 256 for (const std::string& path : object) 257 { 258 std::string name = sdbusplus::message::object_path(path).filename(); 259 nlohmann::json::object_t member; 260 member["@odata.id"] = 261 boost::urls::format("/redfish/v1/Fabrics/{}/Switches/{}/Ports/{}", 262 fabricId, switchId, name); 263 members.emplace_back(std::move(member)); 264 } 265 266 asyncResp->res.jsonValue["Members"] = std::move(members); 267 } 268 269 inline void getFabricSwitchPortPaths( 270 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 271 const std::string& fabricId, const std::string& switchId, 272 const std::string& switchPath) 273 { 274 std::string associationPath = switchPath + "/connecting"; 275 dbus::utility::getAssociatedSubTreePaths( 276 associationPath, sdbusplus::message::object_path{inventoryPath}, 0, 277 portInterface, 278 std::bind_front(handleFabricSwitchPathPortCollection, asyncResp, 279 fabricId, switchId)); 280 } 281 282 inline void handleFabricSwitchPortPathsSwitchCollection( 283 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 284 const std::string& fabricId, const boost::system::error_code& ec, 285 const dbus::utility::MapperGetSubTreePathsResponse& object) 286 { 287 if (ec) 288 { 289 BMCWEB_LOG_ERROR("DBus response error on GetSubTreePaths {}", ec); 290 messages::internalError(asyncResp->res); 291 return; 292 } 293 294 asyncResp->res.jsonValue["@odata.id"] = 295 boost::urls::format("/redfish/v1/Fabrics/{}/Switches", fabricId); 296 asyncResp->res.jsonValue["@odata.type"] = 297 "#SwitchCollection.SwitchCollection"; 298 asyncResp->res.jsonValue["Name"] = fabricId + " Switch Collection"; 299 300 asyncResp->res.jsonValue["Members@odata.count"] = object.size(); 301 302 nlohmann::json::array_t members; 303 for (const std::string& path : object) 304 { 305 nlohmann::json::object_t member; 306 std::string name = sdbusplus::message::object_path(path).filename(); 307 member["@odata.id"] = boost::urls::format( 308 "/redfish/v1/Fabrics/{}/Switches/{}", fabricId, name); 309 members.emplace_back(std::move(member)); 310 } 311 312 asyncResp->res.jsonValue["Members"] = std::move(members); 313 } 314 315 inline void handleFabricSwitchPortMetricsGet( 316 App& app, const crow::Request& req, 317 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 318 const std::string& fabricId, const std::string& switchId, 319 const std::string& portId) 320 { 321 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 322 { 323 return; 324 } 325 326 getFabricSwitchPath( 327 asyncResp, fabricId, switchId, 328 std::bind_front( 329 getAssociatedPortPath, asyncResp, portId, 330 std::bind_front(handleFabricSwitchPortPathPortMetricsGet, asyncResp, 331 fabricId, switchId, portId))); 332 } 333 334 inline void handleFabricSwitchPortGet( 335 App& app, const crow::Request& req, 336 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 337 const std::string& fabricId, const std::string& switchId, 338 const std::string& portId) 339 { 340 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 341 { 342 return; 343 } 344 345 getFabricSwitchPath( 346 asyncResp, fabricId, switchId, 347 std::bind_front( 348 getAssociatedPortPath, asyncResp, portId, 349 std::bind_front(handleFabricSwitchPortPathPortGet, asyncResp, 350 fabricId, switchId, portId))); 351 } 352 353 inline void handleFabricSwitchPortsCollectionGet( 354 App& app, const crow::Request& req, 355 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 356 const std::string& fabricId, const std::string& switchId) 357 { 358 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 359 { 360 return; 361 } 362 363 getFabricSwitchPath(asyncResp, fabricId, switchId, 364 std::bind_front(getFabricSwitchPortPaths, asyncResp, 365 fabricId, switchId)); 366 } 367 368 inline void requestRoutesFabricSwitchPort(App& app) 369 { 370 BMCWEB_ROUTE(app, "/redfish/v1/Fabrics/<str>/Switches/<str>/Ports/") 371 .privileges(redfish::privileges::getPortCollection) 372 .methods(boost::beast::http::verb::get)(std::bind_front( 373 handleFabricSwitchPortsCollectionGet, std::ref(app))); 374 375 BMCWEB_ROUTE(app, "/redfish/v1/Fabrics/<str>/Switches/<str>/Ports/<str>/") 376 .privileges(redfish::privileges::getPort) 377 .methods(boost::beast::http::verb::get)( 378 std::bind_front(handleFabricSwitchPortGet, std::ref(app))); 379 380 BMCWEB_ROUTE( 381 app, "/redfish/v1/Fabrics/<str>/Switches/<str>/Ports/<str>/Metrics/") 382 .privileges(redfish::privileges::getPortMetrics) 383 .methods(boost::beast::http::verb::get)( 384 std::bind_front(handleFabricSwitchPortMetricsGet, std::ref(app))); 385 } 386 } // namespace redfish 387