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