1 /*
2 // Copyright (c) 2018 Intel 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 
17 #include "oemcommands.hpp"
18 
19 #include <boost/algorithm/string.hpp>
20 #include <ipmid/api.hpp>
21 #include <ipmid/utils.hpp>
22 #include <phosphor-logging/log.hpp>
23 #include <variant>
24 
25 namespace ipmi
26 {
27 void register_netfn_bmc_control_functions() __attribute__((constructor));
28 
29 static constexpr uint8_t rmcpServiceBitPos = 3;
30 static constexpr uint8_t webServiceBitPos = 5;
31 static constexpr uint8_t solServiceBitPos = 6;
32 static constexpr uint8_t kvmServiceBitPos = 15;
33 
34 static const std::unordered_map<uint8_t, std::string> bmcServices = {
35     // {bit position for service, service object path}
36     {rmcpServiceBitPos,
37      "/xyz/openbmc_project/control/service/phosphor_2dipmi_2dnet"},
38     {webServiceBitPos, "/xyz/openbmc_project/control/service/bmcweb"},
39     {solServiceBitPos, "/xyz/openbmc_project/control/service/obmc_2dconsole"},
40     {kvmServiceBitPos, "/xyz/openbmc_project/control/service/start_2dipkvm"},
41 };
42 
43 static constexpr uint16_t maskBit15 = 0xF000;
44 
45 static constexpr const char* objectManagerIntf =
46     "org.freedesktop.DBus.ObjectManager";
47 static constexpr const char* dBusPropIntf = "org.freedesktop.DBus.Properties";
48 static constexpr const char* serviceConfigBasePath =
49     "/xyz/openbmc_project/control/service";
50 static constexpr const char* serviceConfigAttrIntf =
51     "xyz.openbmc_project.Control.Service.Attributes";
52 static constexpr const char* getMgdObjMethod = "GetManagedObjects";
53 static constexpr const char* propMasked = "Masked";
54 
55 std::string getServiceConfigMgrName()
56 {
57     static std::string serviceCfgMgr{};
58     if (serviceCfgMgr.empty())
59     {
60         try
61         {
62             auto sdbusp = getSdBus();
63             serviceCfgMgr = ipmi::getService(*sdbusp, objectManagerIntf,
64                                              serviceConfigBasePath);
65         }
66         catch (const sdbusplus::exception::SdBusError& e)
67         {
68             serviceCfgMgr.clear();
69             phosphor::logging::log<phosphor::logging::level::ERR>(
70                 "Error: In fetching disabling service manager name");
71             return serviceCfgMgr;
72         }
73     }
74     return serviceCfgMgr;
75 }
76 
77 static inline void checkAndThrowError(boost::system::error_code& ec,
78                                       const std::string& msg)
79 {
80     if (ec)
81     {
82         std::string msgToLog = ec.message() + (msg.empty() ? "" : " - " + msg);
83         phosphor::logging::log<phosphor::logging::level::ERR>(msgToLog.c_str());
84         throw sdbusplus::exception::SdBusError(-EIO, msgToLog.c_str());
85     }
86     return;
87 }
88 
89 static inline bool getEnabledValue(const DbusInterfaceMap& intfMap)
90 {
91     for (const auto& intf : intfMap)
92     {
93         if (intf.first == serviceConfigAttrIntf)
94         {
95             auto it = intf.second.find(propMasked);
96             if (it == intf.second.end())
97             {
98                 phosphor::logging::log<phosphor::logging::level::ERR>(
99                     "Error: in getting Masked property value");
100                 throw sdbusplus::exception::SdBusError(
101                     -EIO, "ERROR in reading Masked property value");
102             }
103             // return !Masked value
104             return !std::get<bool>(it->second);
105         }
106     }
107     return false;
108 }
109 
110 ipmi::RspType<> setBmcControlServices(boost::asio::yield_context yield,
111                                       uint8_t state, uint16_t serviceValue)
112 {
113     constexpr uint16_t servicesRsvdMask = 0x3F97;
114     constexpr uint8_t enableService = 0x1;
115 
116     if ((state > enableService) || (serviceValue & servicesRsvdMask) ||
117         !serviceValue)
118     {
119         return ipmi::responseInvalidFieldRequest();
120     }
121     try
122     {
123         auto sdbusp = getSdBus();
124         boost::system::error_code ec;
125         auto objectMap = sdbusp->yield_method_call<ObjectValueTree>(
126             yield, ec, getServiceConfigMgrName().c_str(), serviceConfigBasePath,
127             objectManagerIntf, getMgdObjMethod);
128         checkAndThrowError(ec, "GetMangagedObjects for service cfg failed");
129 
130         for (const auto& services : bmcServices)
131         {
132             // services.first holds the bit position of the service, check
133             // whether it has to be updated.
134             const uint16_t serviceMask = 1 << services.first;
135             if (!(serviceValue & serviceMask))
136             {
137                 continue;
138             }
139             for (const auto& obj : objectMap)
140             {
141                 if (boost::algorithm::starts_with(obj.first.str,
142                                                   services.second))
143                 {
144                     if (state != getEnabledValue(obj.second))
145                     {
146                         ec.clear();
147                         sdbusp->yield_method_call<>(
148                             yield, ec, getServiceConfigMgrName().c_str(),
149                             obj.first.str, dBusPropIntf, "Set",
150                             serviceConfigAttrIntf, propMasked,
151                             std::variant<bool>(!state));
152                         checkAndThrowError(ec, "Set Masked property failed");
153                         // Multiple instances may be present, so continue
154                     }
155                 }
156             }
157         }
158     }
159     catch (sdbusplus::exception::SdBusError& e)
160     {
161         return ipmi::responseUnspecifiedError();
162     }
163     return ipmi::responseSuccess();
164 }
165 
166 ipmi::RspType<uint16_t> getBmcControlServices(boost::asio::yield_context yield)
167 {
168     uint16_t serviceValue = 0;
169     try
170     {
171         auto sdbusp = getSdBus();
172         boost::system::error_code ec;
173         auto objectMap = sdbusp->yield_method_call<ObjectValueTree>(
174             yield, ec, getServiceConfigMgrName().c_str(), serviceConfigBasePath,
175             objectManagerIntf, getMgdObjMethod);
176         checkAndThrowError(ec, "GetMangagedObjects for service cfg failed");
177 
178         for (const auto& services : bmcServices)
179         {
180             for (const auto& obj : objectMap)
181             {
182                 if (boost::algorithm::starts_with(obj.first.str,
183                                                   services.second))
184                 {
185                     serviceValue |= getEnabledValue(obj.second)
186                                     << services.first;
187                     break;
188                 }
189             }
190         }
191     }
192     catch (sdbusplus::exception::SdBusError& e)
193     {
194         return ipmi::responseUnspecifiedError();
195     }
196     // Bit 14 should match bit 15 as single service maintains Video & USB
197     // redirection
198     serviceValue |= (serviceValue & maskBit15) >> 1;
199     return ipmi::responseSuccess(serviceValue);
200 }
201 
202 void register_netfn_bmc_control_functions()
203 {
204     ipmi::registerHandler(ipmi::prioOpenBmcBase, netfnIntcOEMGeneral,
205                           static_cast<ipmi_cmd_t>(
206                               IPMINetFnIntelOemGeneralCmds::controlBmcServices),
207                           ipmi::Privilege::Admin, setBmcControlServices);
208 
209     ipmi::registerHandler(
210         ipmi::prioOpenBmcBase, netfnIntcOEMGeneral,
211         static_cast<ipmi_cmd_t>(
212             IPMINetFnIntelOemGeneralCmds::getBmcServiceStatus),
213         ipmi::Privilege::User, getBmcControlServices);
214 }
215 } // namespace ipmi
216