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