1 /** 2 * Copyright © 2022 IBM Corporation 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 #pragma once 17 18 #include "action.hpp" 19 #include "event.hpp" 20 #include "group.hpp" 21 #include "json_config.hpp" 22 #include "power_state.hpp" 23 #include "profile.hpp" 24 #include "sdbusplus.hpp" 25 #include "utils/flight_recorder.hpp" 26 #include "zone.hpp" 27 28 #include <fmt/format.h> 29 30 #include <nlohmann/json.hpp> 31 #include <phosphor-logging/log.hpp> 32 #include <sdbusplus/bus.hpp> 33 #include <sdbusplus/server/manager.hpp> 34 #include <sdeventplus/event.hpp> 35 #include <sdeventplus/source/event.hpp> 36 #include <sdeventplus/utility/timer.hpp> 37 38 #include <chrono> 39 #include <map> 40 #include <memory> 41 #include <optional> 42 #include <tuple> 43 #include <utility> 44 #include <vector> 45 46 namespace phosphor::fan::control::json 47 { 48 49 using json = nlohmann::json; 50 using namespace phosphor::logging; 51 52 /* Application name to be appended to the path for loading a JSON config file */ 53 constexpr auto confAppName = "control"; 54 55 /* Type of timers supported */ 56 enum class TimerType 57 { 58 oneshot, 59 repeating, 60 }; 61 62 /** 63 * Package of data required when a timer expires 64 * Tuple constructed of: 65 * std::string = Timer package unique identifier 66 * std::vector<std::unique_ptr<ActionBase>> = List of pointers to actions 67 * that run when the timer expires 68 * const std::vector<Group> = List of groups 69 * bool = If groups should be preloaded before actions are run 70 */ 71 using TimerPkg = 72 std::tuple<std::string, std::vector<std::unique_ptr<ActionBase>>&, 73 const std::vector<Group>&, bool>; 74 /** 75 * Data associated with a running timer that's used when it expires 76 * Pair constructed of: 77 * TimerType = Type of timer to manage expired timer instances 78 * TimerPkg = Package of data required when the timer expires 79 */ 80 using TimerData = std::pair<TimerType, TimerPkg>; 81 /* Dbus event timer */ 82 using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>; 83 84 /* Dbus signal object */ 85 constexpr auto Path = 0; 86 constexpr auto Intf = 1; 87 constexpr auto Prop = 2; 88 using SignalObject = std::tuple<std::string, std::string, std::string>; 89 /* Dbus signal actions */ 90 using TriggerActions = 91 std::vector<std::reference_wrapper<std::unique_ptr<ActionBase>>>; 92 /** 93 * Signal handler function that handles parsing a signal's message for a 94 * particular signal object and stores the results in the manager 95 */ 96 using SignalHandler = 97 std::function<bool(sdbusplus::message_t&, const SignalObject&, Manager&)>; 98 /** 99 * Package of data required when a signal is received 100 * Tuple constructed of: 101 * SignalHandler = Signal handler function 102 * SignalObject = Dbus signal object 103 * TriggerActions = List of actions that are run when the signal is received 104 */ 105 using SignalPkg = std::tuple<SignalHandler, SignalObject, TriggerActions>; 106 /** 107 * Data associated to a subscribed signal 108 * Tuple constructed of: 109 * std::unique_ptr<std::vector<SignalPkg>> = 110 * Pointer to list of the signal's packages 111 * std::unique_ptr<sdbusplus::bus::match_t> = 112 * Pointer to match holding the subscription to a signal 113 */ 114 using SignalData = std::tuple<std::unique_ptr<std::vector<SignalPkg>>, 115 std::unique_ptr<sdbusplus::bus::match_t>>; 116 117 /** 118 * Package of data from a D-Bus call to get managed objects 119 * Tuple constructed of: 120 * std::map<Path, // D-Bus Path 121 * std::map<Intf, // D-Bus Interface 122 * std::map<Property, // D-Bus Property 123 * std::variant>>> // Variant value of that property 124 */ 125 using Path_v = sdbusplus::message::object_path; 126 using Intf_v = std::string; 127 using Prop_v = std::string; 128 using ManagedObjects = 129 std::map<Path_v, std::map<Intf_v, std::map<Prop_v, PropertyVariantType>>>; 130 131 /** 132 * Actions to run when a parameter trigger runs. 133 */ 134 using ParamTriggerData = std::vector< 135 std::reference_wrapper<const std::vector<std::unique_ptr<ActionBase>>>>; 136 137 /** 138 * @class Manager - Represents the fan control manager's configuration 139 * 140 * A fan control manager configuration is optional, therefore the "manager.json" 141 * file is also optional. The manager configuration is used to populate 142 * fan control's manager parameters which are used in how the application 143 * operates, not in how the fans are controlled. 144 * 145 * When no manager configuration exists, the fan control application starts, 146 * processes any configured events and then begins controlling fans according 147 * to those events. 148 */ 149 class Manager 150 { 151 public: 152 Manager() = delete; 153 Manager(const Manager&) = delete; 154 Manager(Manager&&) = delete; 155 Manager& operator=(const Manager&) = delete; 156 Manager& operator=(Manager&&) = delete; 157 ~Manager() = default; 158 159 /** 160 * Constructor 161 * Parses and populates the fan control manager attributes from a json file 162 * 163 * @param[in] event - sdeventplus event loop 164 */ 165 Manager(const sdeventplus::Event& event); 166 167 /** 168 * @brief Callback function to handle receiving a HUP signal to reload the 169 * JSON configurations. 170 */ 171 void sighupHandler(sdeventplus::source::Signal&, 172 const struct signalfd_siginfo*); 173 174 /** 175 * @brief Callback function to handle receiving a USR1 signal to dump 176 * the flight recorder. 177 */ 178 void sigUsr1Handler(sdeventplus::source::Signal&, 179 const struct signalfd_siginfo*); 180 181 /** 182 * @brief Get the active profiles of the system where an empty list 183 * represents that only configuration entries without a profile defined will 184 * be loaded. 185 * 186 * @return - The list of active profiles 187 */ 188 static const std::vector<std::string>& getActiveProfiles(); 189 190 /** 191 * @brief Load the configuration of a given JSON class object based on the 192 * active profiles 193 * 194 * @param[in] isOptional - JSON configuration file is optional or not 195 * @param[in] args - Arguments to be forwarded to each instance of `T` 196 * (*Note that a sdbusplus bus object is required as the first argument) 197 * 198 * @return Map of configuration entries 199 * Map of configuration keys to their corresponding configuration object 200 */ 201 template <typename T, typename... Args> 202 static std::map<configKey, std::unique_ptr<T>> getConfig(bool isOptional, 203 Args&&... args) 204 { 205 std::map<configKey, std::unique_ptr<T>> config; 206 207 auto confFile = fan::JsonConfig::getConfFile( 208 confAppName, T::confFileName, isOptional); 209 210 if (!confFile.empty()) 211 { 212 FlightRecorder::instance().log( 213 "main", fmt::format("Loading configuration from {}", 214 confFile.string())); 215 for (const auto& entry : fan::JsonConfig::load(confFile)) 216 { 217 if (entry.contains("profiles")) 218 { 219 std::vector<std::string> profiles; 220 for (const auto& profile : entry["profiles"]) 221 { 222 profiles.emplace_back( 223 profile.template get<std::string>()); 224 } 225 // Do not create the object if its profiles are not in the 226 // list of active profiles 227 if (!profiles.empty() && 228 !std::any_of(profiles.begin(), profiles.end(), 229 [](const auto& name) { 230 return std::find( 231 getActiveProfiles().begin(), 232 getActiveProfiles().end(), 233 name) != 234 getActiveProfiles().end(); 235 })) 236 { 237 continue; 238 } 239 } 240 auto obj = 241 std::make_unique<T>(entry, std::forward<Args>(args)...); 242 config.emplace( 243 std::make_pair(obj->getName(), obj->getProfiles()), 244 std::move(obj)); 245 } 246 log<level::INFO>( 247 fmt::format("Configuration({}) loaded successfully", 248 T::confFileName) 249 .c_str()); 250 FlightRecorder::instance().log( 251 "main", fmt::format("Configuration({}) loaded successfully", 252 T::confFileName)); 253 } 254 return config; 255 } 256 257 /** 258 * @brief Check if the given input configuration key matches with another 259 * configuration key that it's to be included in 260 * 261 * @param[in] input - Config key to be included in another config object 262 * @param[in] comp - Config key of the config object to compare with 263 * 264 * @return Whether the configuration object should be included 265 */ 266 static bool inConfig(const configKey& input, const configKey& comp); 267 268 /** 269 * @brief Check if the given path and inteface is owned by a dbus service 270 * 271 * @param[in] path - Dbus object path 272 * @param[in] intf - Dbus object interface 273 * 274 * @return - Whether the service has an owner for the given object path and 275 * interface 276 */ 277 static bool hasOwner(const std::string& path, const std::string& intf); 278 279 /** 280 * @brief Sets the dbus service owner state for all entries in the _servTree 281 * cache and removes associated objects from the _objects cache 282 * 283 * @param[in] serv - Dbus service name 284 * @param[in] hasOwner - Dbus service owner state 285 */ 286 void setOwner(const std::string& serv, bool hasOwner); 287 288 /** 289 * @brief Sets the dbus service owner state of a given object 290 * 291 * @param[in] path - Dbus object path 292 * @param[in] serv - Dbus service name 293 * @param[in] intf - Dbus object interface 294 * @param[in] isOwned - Dbus service owner state 295 */ 296 void setOwner(const std::string& path, const std::string& serv, 297 const std::string& intf, bool isOwned); 298 299 /** 300 * @brief Add a set of services for a path and interface by retrieving all 301 * the path subtrees to the given depth from root for the interface 302 * 303 * @param[in] intf - Interface to add services for 304 * @param[in] depth - Depth of tree traversal from root path 305 * 306 * @throws - DBusMethodError 307 * Throws a DBusMethodError when the `getSubTree` method call fails 308 */ 309 static void addServices(const std::string& intf, int32_t depth); 310 311 /** 312 * @brief Get the service for a given path and interface from cached 313 * dataset and attempt to add all the services for the given path/interface 314 * when it's not found 315 * 316 * @param[in] path - Path to get service for 317 * @param[in] intf - Interface to get service for 318 * 319 * @return - The now cached service name 320 * 321 * @throws - DBusMethodError 322 * Ripples up a DBusMethodError exception from calling addServices 323 */ 324 static const std::string& getService(const std::string& path, 325 const std::string& intf); 326 327 /** 328 * @brief Get all the object paths for a given service and interface from 329 * the cached dataset and try to add all the services for the given 330 * interface when no paths are found and then attempt to get all the object 331 * paths again 332 * 333 * @param[in] serv - Service name to get paths for 334 * @param[in] intf - Interface to get paths for 335 * 336 * @return The cached object paths 337 */ 338 std::vector<std::string> getPaths(const std::string& serv, 339 const std::string& intf); 340 341 /** 342 * @brief Add objects to the cached dataset by first using 343 * `getManagedObjects` for the same service providing the given path and 344 * interface or just add the single object of the given path, interface, and 345 * property if that fails. 346 * 347 * @param[in] path - Dbus object's path 348 * @param[in] intf - Dbus object's interface 349 * @param[in] prop - Dbus object's property 350 * 351 * @throws - DBusMethodError 352 * Throws a DBusMethodError when the the service is failed to be found or 353 * when the `getManagedObjects` method call fails 354 */ 355 void addObjects(const std::string& path, const std::string& intf, 356 const std::string& prop) 357 { 358 addObjects(path, intf, prop, std::string{}); 359 } 360 361 /** 362 * @copydoc Manager::addObjects() 363 * 364 * If the service is known, then it can be used to add all objects 365 * in that service with the interface passed in to the cache instead of 366 * having to look it up. This is done so objects can still be 367 * added even when the D-Bus path passed in doesn't exist so it 368 * can't be used to get a service name. 369 * 370 * @param[in] path - Dbus object's path 371 * @param[in] intf - Dbus object's interface 372 * @param[in] prop - Dbus object's property 373 * @param[in] serviceName - The service of the path/intf/prop if known 374 */ 375 void addObjects(const std::string& path, const std::string& intf, 376 const std::string& prop, const std::string& serviceName); 377 378 /** 379 * @brief Get an object's property value 380 * 381 * @param[in] path - Dbus object's path 382 * @param[in] intf - Dbus object's interface 383 * @param[in] prop - Dbus object's property 384 */ 385 const std::optional<PropertyVariantType> 386 getProperty(const std::string& path, const std::string& intf, 387 const std::string& prop); 388 389 /** 390 * @brief Set/update an object's property value 391 * 392 * @param[in] path - Dbus object's path 393 * @param[in] intf - Dbus object's interface 394 * @param[in] prop - Dbus object's property 395 * @param[in] value - Dbus object's property value 396 */ 397 void setProperty(const std::string& path, const std::string& intf, 398 const std::string& prop, PropertyVariantType value); 399 400 /** 401 * @brief Remove an object's interface 402 * 403 * @param[in] path - Dbus object's path 404 * @param[in] intf - Dbus object's interface 405 */ 406 inline void removeInterface(const std::string& path, 407 const std::string& intf) 408 { 409 auto itPath = _objects.find(path); 410 if (itPath != std::end(_objects)) 411 { 412 _objects[path].erase(intf); 413 } 414 } 415 416 /** 417 * @brief Get the object's property value as a variant 418 * 419 * @param[in] path - Path of the object containing the property 420 * @param[in] intf - Interface name containing the property 421 * @param[in] prop - Name of property 422 * 423 * @return - The object's property value as a variant 424 */ 425 static inline auto getObjValueVariant(const std::string& path, 426 const std::string& intf, 427 const std::string& prop) 428 { 429 return _objects.at(path).at(intf).at(prop); 430 }; 431 432 /** 433 * @brief Add a dbus timer 434 * 435 * @param[in] type - Type of timer 436 * @param[in] interval - Timer interval in microseconds 437 * @param[in] pkg - Packaged data for when timer expires 438 */ 439 void addTimer(const TimerType type, 440 const std::chrono::microseconds interval, 441 std::unique_ptr<TimerPkg> pkg); 442 443 /** 444 * @brief Callback when a timer expires 445 * 446 * @param[in] data - Data to be used when the timer expired 447 */ 448 void timerExpired(TimerData& data); 449 450 /** 451 * @brief Get the signal data for a given match string 452 * 453 * @param[in] sigMatch - Signal match string 454 * 455 * @return - Reference to the signal data for the given match string 456 */ 457 std::vector<SignalData>& getSignal(const std::string& sigMatch) 458 { 459 return _signals[sigMatch]; 460 } 461 462 /** 463 * @brief Handle receiving signals 464 * 465 * @param[in] msg - Signal message containing the signal's data 466 * @param[in] pkgs - Signal packages associated to the signal being handled 467 */ 468 void handleSignal(sdbusplus::message_t& msg, 469 const std::vector<SignalPkg>* pkgs); 470 471 /** 472 * @brief Get the sdbusplus bus object 473 */ 474 inline auto& getBus() 475 { 476 return _bus; 477 } 478 479 /** 480 * @brief Is the power state on 481 * 482 * @return Current power state of the system 483 */ 484 inline bool isPowerOn() const 485 { 486 return _powerState->isPowerOn(); 487 } 488 489 /** 490 * @brief Load all the fan control JSON configuration files 491 * 492 * This is where all the fan control JSON configuration files are parsed and 493 * loaded into their associated objects. Anything that needs to be done when 494 * the Manager object is constructed or handling a SIGHUP to reload the 495 * configurations needs to be done here. 496 */ 497 void load(); 498 499 /** 500 * @brief Sets a value in the parameter map. 501 * 502 * If it's a std::nullopt, it will be deleted instead. 503 * 504 * @param[in] name - The parameter name 505 * @param[in] value - The parameter value 506 */ 507 static void setParameter(const std::string& name, 508 const std::optional<PropertyVariantType>& value) 509 { 510 if (value) 511 { 512 auto it = _parameters.find(name); 513 auto changed = (it == _parameters.end()) || 514 ((it != _parameters.end()) && it->second != *value); 515 _parameters[name] = *value; 516 517 if (changed) 518 { 519 runParameterActions(name); 520 } 521 } 522 else 523 { 524 size_t deleted = _parameters.erase(name); 525 526 if (deleted) 527 { 528 runParameterActions(name); 529 } 530 } 531 } 532 533 /** 534 * @brief Returns a value from the parameter map 535 * 536 * @param[in] name - The parameter name 537 * 538 * @return The parameter value, or std::nullopt if not found 539 */ 540 static std::optional<PropertyVariantType> 541 getParameter(const std::string& name) 542 { 543 auto it = _parameters.find(name); 544 if (it != _parameters.end()) 545 { 546 return it->second; 547 } 548 549 return std::nullopt; 550 } 551 552 /** 553 * @brief Runs the actions registered to a parameter 554 * trigger with this name. 555 * 556 * @param[in] name - The parameter name 557 */ 558 static void runParameterActions(const std::string& name); 559 560 /** 561 * @brief Adds a parameter trigger 562 * 563 * @param[in] name - The parameter name 564 * @param[in] actions - The actions to run on the trigger 565 */ 566 static void 567 addParameterTrigger(const std::string& name, 568 std::vector<std::unique_ptr<ActionBase>>& actions); 569 570 /* The name of the dump file */ 571 static const std::string dumpFile; 572 573 private: 574 /** 575 * @brief Helper to detect when a property's double contains a NaN 576 * (not-a-number) value. 577 * 578 * @param[in] value - The property to test 579 */ 580 static bool PropertyContainsNan(const PropertyVariantType& value) 581 { 582 return (std::holds_alternative<double>(value) && 583 std::isnan(std::get<double>(value))); 584 } 585 586 /** 587 * @brief Insert managed objects into cache, but filter out properties 588 * containing unwanted NaN (not-a-number) values and properties that 589 * are on D-Bus paths that aren't in an existing Group object. 590 * 591 * @param[in] ref - The map of ManagedObjects to insert into cache 592 */ 593 void insertFilteredObjects(ManagedObjects& ref); 594 595 /* The sdbusplus bus object to use */ 596 sdbusplus::bus_t& _bus; 597 598 /* The sdeventplus even loop to use */ 599 sdeventplus::Event _event; 600 601 /* The sdbusplus manager object to set the ObjectManager interface */ 602 sdbusplus::server::manager_t _mgr; 603 604 /* Whether loading the config files is allowed or not */ 605 bool _loadAllowed; 606 607 /* The system's power state determination object */ 608 std::unique_ptr<PowerState> _powerState; 609 610 /* List of profiles configured */ 611 std::map<configKey, std::unique_ptr<Profile>> _profiles; 612 613 /* List of active profiles */ 614 static std::vector<std::string> _activeProfiles; 615 616 /* Subtree map of paths to services of interfaces(with ownership state) */ 617 static std::map< 618 std::string, 619 std::map<std::string, std::pair<bool, std::vector<std::string>>>> 620 _servTree; 621 622 /* Object map of paths to interfaces of properties and their values */ 623 static std::map< 624 std::string, 625 std::map<std::string, std::map<std::string, PropertyVariantType>>> 626 _objects; 627 628 /* List of timers and their data to be processed when expired */ 629 std::vector<std::pair<std::unique_ptr<TimerData>, Timer>> _timers; 630 631 /* Map of signal match strings to a list of signal handler data */ 632 std::unordered_map<std::string, std::vector<SignalData>> _signals; 633 634 /* List of zones configured */ 635 std::map<configKey, std::unique_ptr<Zone>> _zones; 636 637 /* List of events configured */ 638 std::map<configKey, std::unique_ptr<Event>> _events; 639 640 /* The sdeventplus wrapper around sd_event_add_defer to dump debug 641 * data from the event loop after the USR1 signal. */ 642 std::unique_ptr<sdeventplus::source::Defer> debugDumpEventSource; 643 644 /** 645 * @brief A map of parameter names and values that are something 646 * other than just D-Bus property values that other actions 647 * can set and use. 648 */ 649 static std::unordered_map<std::string, PropertyVariantType> _parameters; 650 651 /** 652 * @brief Map of parameter names to the actions to run when their 653 * values change. 654 */ 655 static std::unordered_map<std::string, TriggerActions> _parameterTriggers; 656 657 /** 658 * @brief Callback for power state changes 659 * 660 * @param[in] powerStateOn - Whether the power state is on or not 661 * 662 * Callback function bound to the PowerState object instance to handle each 663 * time the power state changes. 664 */ 665 void powerStateChanged(bool powerStateOn); 666 667 /** 668 * @brief Find the service name for a given path and interface from the 669 * cached dataset 670 * 671 * @param[in] path - Path to get service for 672 * @param[in] intf - Interface to get service for 673 * 674 * @return - The cached service name 675 */ 676 static const std::string& findService(const std::string& path, 677 const std::string& intf); 678 679 /** 680 * @brief Find all the paths for a given service and interface from the 681 * cached dataset 682 * 683 * @param[in] serv - Service name to get paths for 684 * @param[in] intf - Interface to get paths for 685 * 686 * @return - The cached object paths 687 */ 688 std::vector<std::string> findPaths(const std::string& serv, 689 const std::string& intf); 690 691 /** 692 * @brief Parse and set the configured profiles from the profiles JSON file 693 * 694 * Retrieves the optional profiles JSON configuration file, parses it, and 695 * creates a list of configured profiles available to the other 696 * configuration files. These profiles can be used to remove or include 697 * entries within the other configuration files. 698 */ 699 void setProfiles(); 700 701 /** 702 * @brief Callback from debugDumpEventSource to dump debug data 703 */ 704 void dumpDebugData(sdeventplus::source::EventBase&); 705 706 /** 707 * @brief Dump the _objects, _servTree, and _parameters maps to JSON 708 * 709 * @param[out] data - The JSON that will be filled in 710 */ 711 void dumpCache(json& data); 712 713 /** 714 * @brief Add a list of groups to the cache dataset. 715 * 716 * @param[in] groups - The groups to add 717 */ 718 void addGroups(const std::vector<Group>& groups); 719 }; 720 721 } // namespace phosphor::fan::control::json 722