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