1 /*
2 // Copyright (c) 2017 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 <host-ipmid/ipmid-api.h>
18 
19 #include <boost/container/flat_map.hpp>
20 #include <commandutils.hpp>
21 #include <iostream>
22 #include <phosphor-logging/log.hpp>
23 #include <sdbusplus/message/types.hpp>
24 #include <sdbusplus/timer.hpp>
25 #include <storagecommands.hpp>
26 
27 namespace ipmi
28 {
29 
30 namespace storage
31 {
32 
33 constexpr static const size_t maxFruSdrNameSize = 16;
34 using ManagedObjectType = boost::container::flat_map<
35     sdbusplus::message::object_path,
36     boost::container::flat_map<
37         std::string, boost::container::flat_map<std::string, DbusVariant>>>;
38 using ManagedEntry = std::pair<
39     sdbusplus::message::object_path,
40     boost::container::flat_map<
41         std::string, boost::container::flat_map<std::string, DbusVariant>>>;
42 
43 constexpr static const char* fruDeviceServiceName = "com.intel.FruDevice";
44 
45 static std::vector<uint8_t> fruCache;
46 static uint8_t cacheBus = 0xFF;
47 static uint8_t cacheAddr = 0XFF;
48 
49 std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
50 
51 // we unfortunately have to build a map of hashes in case there is a
52 // collision to verify our dev-id
53 boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
54 
55 static sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
56 
57 bool writeFru()
58 {
59     sdbusplus::message::message writeFru = dbus.new_method_call(
60         fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
61         "xyz.openbmc_project.FruDeviceManager", "WriteFru");
62     writeFru.append(cacheBus, cacheAddr, fruCache);
63     try
64     {
65         sdbusplus::message::message writeFruResp = dbus.call(writeFru);
66     }
67     catch (sdbusplus::exception_t&)
68     {
69         // todo: log sel?
70         phosphor::logging::log<phosphor::logging::level::ERR>(
71             "error writing fru");
72         return false;
73     }
74     return true;
75 }
76 
77 ipmi_ret_t replaceCacheFru(uint8_t devId)
78 {
79     static uint8_t lastDevId = 0xFF;
80 
81     bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
82     if (lastDevId == devId && timerRunning)
83     {
84         return IPMI_CC_OK; // cache already up to date
85     }
86     // if timer is running, stop it and writeFru manually
87     else if (timerRunning)
88     {
89         cacheTimer->stop();
90         writeFru();
91     }
92 
93     sdbusplus::message::message getObjects = dbus.new_method_call(
94         fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
95         "GetManagedObjects");
96     ManagedObjectType frus;
97     try
98     {
99         sdbusplus::message::message resp = dbus.call(getObjects);
100         resp.read(frus);
101     }
102     catch (sdbusplus::exception_t&)
103     {
104         phosphor::logging::log<phosphor::logging::level::ERR>(
105             "replaceCacheFru: error getting managed objects");
106         return IPMI_CC_RESPONSE_ERROR;
107     }
108 
109     deviceHashes.clear();
110 
111     // hash the object paths to create unique device id's. increment on
112     // collision
113     std::hash<std::string> hasher;
114     for (const auto& fru : frus)
115     {
116         auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
117         if (fruIface == fru.second.end())
118         {
119             continue;
120         }
121 
122         auto busFind = fruIface->second.find("BUS");
123         auto addrFind = fruIface->second.find("ADDRESS");
124         if (busFind == fruIface->second.end() ||
125             addrFind == fruIface->second.end())
126         {
127             phosphor::logging::log<phosphor::logging::level::INFO>(
128                 "fru device missing Bus or Address",
129                 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
130             continue;
131         }
132 
133         uint8_t fruBus =
134             sdbusplus::message::variant_ns::get<uint32_t>(busFind->second);
135         uint8_t fruAddr =
136             sdbusplus::message::variant_ns::get<uint32_t>(addrFind->second);
137 
138         uint8_t fruHash = 0;
139         if (fruBus != 0 || fruAddr != 0)
140         {
141             fruHash = hasher(fru.first.str);
142             // can't be 0xFF based on spec, and 0 is reserved for baseboard
143             if (fruHash == 0 || fruHash == 0xFF)
144             {
145                 fruHash = 1;
146             }
147         }
148         std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
149 
150         bool emplacePassed = false;
151         while (!emplacePassed)
152         {
153             auto resp = deviceHashes.emplace(fruHash, newDev);
154             emplacePassed = resp.second;
155             if (!emplacePassed)
156             {
157                 fruHash++;
158                 // can't be 0xFF based on spec, and 0 is reserved for
159                 // baseboard
160                 if (fruHash == 0XFF)
161                 {
162                     fruHash = 0x1;
163                 }
164             }
165         }
166     }
167     auto deviceFind = deviceHashes.find(devId);
168     if (deviceFind == deviceHashes.end())
169     {
170         return IPMI_CC_SENSOR_INVALID;
171     }
172 
173     fruCache.clear();
174     sdbusplus::message::message getRawFru = dbus.new_method_call(
175         fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
176         "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
177     cacheBus = deviceFind->second.first;
178     cacheAddr = deviceFind->second.second;
179     getRawFru.append(cacheBus, cacheAddr);
180     try
181     {
182         sdbusplus::message::message getRawResp = dbus.call(getRawFru);
183         getRawResp.read(fruCache);
184     }
185     catch (sdbusplus::exception_t&)
186     {
187         lastDevId = 0xFF;
188         cacheBus = 0xFF;
189         cacheAddr = 0xFF;
190         return IPMI_CC_RESPONSE_ERROR;
191     }
192 
193     lastDevId = devId;
194     return IPMI_CC_OK;
195 }
196 
197 ipmi_ret_t getFruSdrCount(size_t& count)
198 {
199     ipmi_ret_t ret = replaceCacheFru(0);
200     if (ret != IPMI_CC_OK)
201     {
202         return ret;
203     }
204     count = deviceHashes.size();
205     return IPMI_CC_OK;
206 }
207 
208 ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
209 {
210     ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
211     if (ret != IPMI_CC_OK)
212     {
213         return ret;
214     }
215     if (deviceHashes.size() < index)
216     {
217         return IPMI_CC_INVALID_FIELD_REQUEST;
218     }
219     auto device = deviceHashes.begin() + index;
220     uint8_t& bus = device->second.first;
221     uint8_t& address = device->second.second;
222 
223     ManagedObjectType frus;
224 
225     sdbusplus::message::message getObjects = dbus.new_method_call(
226         fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
227         "GetManagedObjects");
228     try
229     {
230         sdbusplus::message::message resp = dbus.call(getObjects);
231         resp.read(frus);
232     }
233     catch (sdbusplus::exception_t&)
234     {
235         return IPMI_CC_RESPONSE_ERROR;
236     }
237     boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
238     auto fru =
239         std::find_if(frus.begin(), frus.end(),
240                      [bus, address, &fruData](ManagedEntry& entry) {
241                          auto findFruDevice =
242                              entry.second.find("xyz.openbmc_project.FruDevice");
243                          if (findFruDevice == entry.second.end())
244                          {
245                              return false;
246                          }
247                          fruData = &(findFruDevice->second);
248                          auto findBus = findFruDevice->second.find("BUS");
249                          auto findAddress =
250                              findFruDevice->second.find("ADDRESS");
251                          if (findBus == findFruDevice->second.end() ||
252                              findAddress == findFruDevice->second.end())
253                          {
254                              return false;
255                          }
256                          if (sdbusplus::message::variant_ns::get<uint32_t>(
257                                  findBus->second) != bus)
258                          {
259                              return false;
260                          }
261                          if (sdbusplus::message::variant_ns::get<uint32_t>(
262                                  findAddress->second) != address)
263                          {
264                              return false;
265                          }
266                          return true;
267                      });
268     if (fru == frus.end())
269     {
270         return IPMI_CC_RESPONSE_ERROR;
271     }
272     std::string name;
273     auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
274     auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
275     if (findProductName != fruData->end())
276     {
277         name = sdbusplus::message::variant_ns::get<std::string>(
278             findProductName->second);
279     }
280     else if (findBoardName != fruData->end())
281     {
282         name = sdbusplus::message::variant_ns::get<std::string>(
283             findBoardName->second);
284     }
285     else
286     {
287         name = "UNKNOWN";
288     }
289     if (name.size() > maxFruSdrNameSize)
290     {
291         name = name.substr(0, maxFruSdrNameSize);
292     }
293     size_t sizeDiff = maxFruSdrNameSize - name.size();
294 
295     resp.header.record_id_lsb = 0x0; // calling code is to implement these
296     resp.header.record_id_msb = 0x0;
297     resp.header.sdr_version = ipmiSdrVersion;
298     resp.header.record_type = 0x11; // FRU Device Locator
299     resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
300     resp.key.deviceAddress = 0x20;
301     resp.key.fruID = device->first;
302     resp.key.accessLun = 0x80; // logical / physical fru device
303     resp.key.channelNumber = 0x0;
304     resp.body.reserved = 0x0;
305     resp.body.deviceType = 0x10;
306     resp.body.entityID = 0x0;
307     resp.body.entityInstance = 0x1;
308     resp.body.oem = 0x0;
309     resp.body.deviceIDLen = name.size();
310     name.copy(resp.body.deviceID, name.size());
311 
312     return IPMI_CC_OK;
313 }
314 } // namespace storage
315 } // namespace ipmi