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