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