| /* |
| // Copyright (c) 2018 Intel Corporation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <commandutils.hpp> |
| #include <ipmid/api.hpp> |
| #include <ipmid/utils.hpp> |
| #include <oemcommands.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <sdbusplus/message/types.hpp> |
| #include <smbiosmdrv2handler.hpp> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| |
| #include <cstdint> |
| #include <fstream> |
| #include <string> |
| #include <vector> |
| |
| std::unique_ptr<MDRV2> mdrv2 = nullptr; |
| static constexpr const uint8_t ccOemInvalidChecksum = 0x85; |
| static constexpr size_t dataInfoSize = 16; |
| static constexpr const uint8_t ccStorageLeak = 0xC4; |
| |
| static void register_netfn_smbiosmdrv2_functions() __attribute__((constructor)); |
| |
| int MDRV2::agentLookup(const uint16_t& agentId) |
| { |
| int agentIndex = -1; |
| |
| if (lastAgentId == agentId) |
| { |
| return lastAgentIndex; |
| } |
| |
| if (agentId == smbiosAgentId) |
| { |
| return firstAgentIndex; |
| } |
| |
| return agentIndex; |
| } |
| |
| int MDRV2::sdplusMdrv2GetProperty(const std::string& name, |
| ipmi::DbusVariant& value, |
| const std::string& service) |
| { |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| sdbusplus::message_t method = |
| bus->new_method_call(service.c_str(), mdrv2Path, dbusProperties, "Get"); |
| method.append(mdrv2Interface, name); |
| |
| sdbusplus::message_t reply = bus->call(method); |
| |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(value); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error get property, sdbusplus call failed", |
| phosphor::logging::entry("ERROR=%s", e.what())); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int MDRV2::syncDirCommonData(uint8_t idIndex, uint32_t size, |
| const std::string& service) |
| { |
| std::vector<uint32_t> commonData; |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| sdbusplus::message_t method = |
| bus->new_method_call(service.c_str(), mdrv2Path, mdrv2Interface, |
| "SynchronizeDirectoryCommonData"); |
| method.append(idIndex, size); |
| |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(commonData); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error sync dir common data with service", |
| phosphor::logging::entry("ERROR=%s", e.what())); |
| return -1; |
| } |
| |
| if (commonData.size() < syncDirCommonSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error sync dir common data - data length invalid"); |
| return -1; |
| } |
| smbiosDir.dir[idIndex].common.dataSetSize = commonData.at(0); |
| smbiosDir.dir[idIndex].common.dataVersion = commonData.at(1); |
| smbiosDir.dir[idIndex].common.timestamp = commonData.at(2); |
| |
| return 0; |
| } |
| |
| int MDRV2::findDataId(const uint8_t* dataInfo, const size_t& len, |
| const std::string& service) |
| { |
| int idIndex = -1; |
| |
| if (dataInfo == nullptr) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error dataInfo, input is null point"); |
| return -1; |
| } |
| |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| sdbusplus::message_t method = bus->new_method_call( |
| service.c_str(), mdrv2Path, mdrv2Interface, "FindIdIndex"); |
| std::vector<uint8_t> info; |
| info.resize(len); |
| std::copy(dataInfo, dataInfo + len, info.data()); |
| method.append(info); |
| |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(idIndex); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error find id index", |
| phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("SERVICE=%s", service.c_str()), |
| phosphor::logging::entry("PATH=%s", mdrv2Path)); |
| return -1; |
| } |
| |
| return idIndex; |
| } |
| |
| uint16_t MDRV2::getSessionHandle(Mdr2DirStruct* dir) |
| { |
| if (dir == NULL) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Empty dir point"); |
| return 0; |
| } |
| dir->sessionHandle++; |
| if (dir->sessionHandle == 0) |
| { |
| dir->sessionHandle = 1; |
| } |
| |
| return dir->sessionHandle; |
| } |
| |
| int MDRV2::findLockHandle(const uint16_t& lockHandle) |
| { |
| int idIndex = -1; |
| |
| for (int index = 0; index < smbiosDir.dirEntries; index++) |
| { |
| if (lockHandle == smbiosDir.dir[index].lockHandle) |
| { |
| return index; |
| } |
| } |
| |
| return idIndex; |
| } |
| |
| bool MDRV2::smbiosIsUpdating(uint8_t index) |
| { |
| if (index >= maxDirEntries) |
| { |
| return false; |
| } |
| if (smbiosDir.dir[index].stage == MDR2SMBIOSStatusEnum::mdr2Updating) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| uint32_t MDRV2::calcChecksum32(uint8_t* buf, uint32_t len) |
| { |
| uint32_t sum = 0; |
| |
| if (buf == nullptr) |
| { |
| return invalidChecksum; |
| } |
| |
| for (uint32_t index = 0; index < len; index++) |
| { |
| sum += buf[index]; |
| } |
| |
| return sum; |
| } |
| |
| /** @brief implements mdr2 agent status command |
| * @param agentId |
| * @param dirVersion |
| * |
| * @returns IPMI completion code plus response data |
| * - mdrVersion |
| * - agentVersion |
| * - dirVersion |
| * - dirEntries |
| * - dataRequest |
| */ |
| ipmi::RspType<uint8_t, uint8_t, uint8_t, uint8_t, uint8_t> |
| mdr2AgentStatus(uint16_t agentId, uint8_t dirVersion) |
| { |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| constexpr uint8_t mdrVersion = mdr2Version; |
| constexpr uint8_t agentVersion = smbiosAgentVersion; |
| uint8_t dirVersionResp = mdrv2->smbiosDir.dirVersion; |
| uint8_t dirEntries = mdrv2->smbiosDir.dirEntries; |
| uint8_t dataRequest; |
| |
| if (mdrv2->smbiosDir.remoteDirVersion != dirVersion) |
| { |
| mdrv2->smbiosDir.remoteDirVersion = dirVersion; |
| dataRequest = |
| static_cast<uint8_t>(DirDataRequestEnum::dirDataRequested); |
| } |
| else |
| { |
| dataRequest = |
| static_cast<uint8_t>(DirDataRequestEnum::dirDataNotRequested); |
| } |
| |
| return ipmi::responseSuccess(mdrVersion, agentVersion, dirVersionResp, |
| dirEntries, dataRequest); |
| } |
| |
| /** @brief implements mdr2 get directory command |
| * @param agentId |
| * @param dirIndex |
| * @returns IPMI completion code plus response data |
| * - dataOut |
| */ |
| ipmi::RspType<std::vector<uint8_t>> mdr2GetDir(uint16_t agentId, |
| uint8_t dirIndex) |
| { |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| ipmi::DbusVariant value = static_cast<uint8_t>(0); |
| if (0 != mdrv2->sdplusMdrv2GetProperty("DirectoryEntries", value, service)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error getting DirEnries"); |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (std::get<uint8_t>(value) == 0) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error getting directory entries", |
| phosphor::logging::entry("VALUE=%x", std::get<uint8_t>(value))); |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (dirIndex > std::get<uint8_t>(value)) |
| { |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| sdbusplus::message_t method = bus->new_method_call( |
| service.c_str(), mdrv2Path, mdrv2Interface, "GetDirectoryInformation"); |
| |
| method.append(dirIndex); |
| |
| std::vector<uint8_t> dataOut; |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(dataOut); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error get dir", phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("SERVICE=%s", service.c_str()), |
| phosphor::logging::entry("PATH=%s", mdrv2Path)); |
| return ipmi::responseResponseError(); |
| } |
| |
| constexpr size_t getDirRespSize = 6; |
| if (dataOut.size() < getDirRespSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error get dir, response length invalid"); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| if (dataOut.size() > MAX_IPMI_BUFFER) // length + completion code should no |
| // more than MAX_IPMI_BUFFER |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Data length send from service is invalid"); |
| return ipmi::responseResponseError(); |
| } |
| |
| return ipmi::responseSuccess(dataOut); |
| } |
| |
| /** @brief implements mdr2 send directory info command |
| * @param agentId |
| * @param dirVersion |
| * @param dirIndex |
| * @param returnedEntries |
| * @param remainingEntries |
| * @param dataInfo |
| * dataInfo is 32 Bytes in size and contains below parameters |
| * - dataInfo, size, dataSetSize, dataVersion, timestamp |
| * |
| * @returns IPMI completion code plus response data |
| * - bool |
| */ |
| |
| ipmi::RspType<bool> mdr2SendDir(uint16_t agentId, uint8_t dirVersion, |
| uint8_t dirIndex, uint8_t returnedEntries, |
| uint8_t remainingEntries, |
| std::vector<uint8_t> dataInfo) |
| { |
| if ((static_cast<size_t>(returnedEntries) * dataInfoSize) != |
| dataInfo.size()) |
| { |
| return ipmi::responseReqDataLenInvalid(); |
| } |
| |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| if ((dirIndex + returnedEntries) > maxDirEntries) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Too many directory entries"); |
| return ipmi::response(ccStorageLeak); |
| } |
| |
| sdbusplus::message_t method = bus->new_method_call( |
| service.c_str(), mdrv2Path, mdrv2Interface, "SendDirectoryInformation"); |
| method.append(dirVersion, dirIndex, returnedEntries, remainingEntries, |
| dataInfo); |
| |
| bool terminate = false; |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(terminate); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error send dir", phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("SERVICE=%s", service.c_str()), |
| phosphor::logging::entry("PATH=%s", mdrv2Path)); |
| return ipmi::responseResponseError(); |
| } |
| |
| return ipmi::responseSuccess(terminate); |
| } |
| |
| /** @brief implements mdr2 get data info command |
| * @param agentId |
| * @param dataInfo |
| * |
| * @returns IPMI completion code plus response data |
| * - response - mdrVersion, data info, validFlag, |
| * dataLength, dataVersion, timeStamp |
| */ |
| ipmi::RspType<std::vector<uint8_t>> |
| mdr2GetDataInfo(uint16_t agentId, std::vector<uint8_t> dataInfo) |
| { |
| constexpr size_t getDataInfoReqSize = 16; |
| |
| if (dataInfo.size() < getDataInfoReqSize) |
| { |
| return ipmi::responseReqDataLenInvalid(); |
| } |
| |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| int idIndex = mdrv2->findDataId(dataInfo.data(), dataInfo.size(), service); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| sdbusplus::message_t method = bus->new_method_call( |
| service.c_str(), mdrv2Path, mdrv2Interface, "GetDataInformation"); |
| |
| method.append(static_cast<uint8_t>(idIndex)); |
| |
| std::vector<uint8_t> res; |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(res); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error get data info", |
| phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("SERVICE=%s", service.c_str()), |
| phosphor::logging::entry("PATH=%s", mdrv2Path)); |
| return ipmi::responseResponseError(); |
| } |
| |
| if (res.size() != sizeof(MDRiiGetDataInfoResponse)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Get data info response length not invalid"); |
| return ipmi::responseResponseError(); |
| } |
| |
| return ipmi::responseSuccess(res); |
| } |
| |
| /** @brief implements mdr2 data info offer command |
| * @param agentId - Offer a agent ID to get the "Data Set ID" |
| * |
| * @returns IPMI completion code plus response data |
| * - dataOut - data Set Id |
| */ |
| ipmi::RspType<std::vector<uint8_t>> mdr2DataInfoOffer(uint16_t agentId) |
| { |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| sdbusplus::message_t method = bus->new_method_call( |
| service.c_str(), mdrv2Path, mdrv2Interface, "GetDataOffer"); |
| |
| std::vector<uint8_t> dataOut; |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(dataOut); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error send data info offer", |
| phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("SERVICE=%s", service.c_str()), |
| phosphor::logging::entry("PATH=%s", mdrv2Path)); |
| return ipmi::responseResponseError(); |
| } |
| |
| constexpr size_t respInfoSize = 16; |
| if (dataOut.size() != respInfoSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error send data info offer, return length invalid"); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| return ipmi::responseSuccess(dataOut); |
| } |
| |
| /** @brief implements mdr2 send data info command |
| * @param agentId |
| * @param dataInfo |
| * @param validFlag |
| * @param dataLength |
| * @param dataVersion |
| * @param timeStamp |
| * |
| * @returns IPMI completion code plus response data |
| * - bool |
| */ |
| ipmi::RspType<bool> mdr2SendDataInfo(uint16_t agentId, |
| std::array<uint8_t, dataInfoSize> dataInfo, |
| uint8_t validFlag, uint32_t dataLength, |
| uint32_t dataVersion, uint32_t timeStamp) |
| { |
| if (dataLength > smbiosTableStorageSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Requested data length is out of SMBIOS Table storage size."); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| int idIndex = mdrv2->findDataId(dataInfo.data(), dataInfo.size(), service); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| sdbusplus::message_t method = bus->new_method_call( |
| service.c_str(), mdrv2Path, mdrv2Interface, "SendDataInformation"); |
| |
| method.append((uint8_t)idIndex, validFlag, dataLength, dataVersion, |
| timeStamp); |
| |
| bool entryChanged = true; |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(entryChanged); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error send data info", |
| phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("SERVICE=%s", service.c_str()), |
| phosphor::logging::entry("PATH=%s", mdrv2Path)); |
| return ipmi::responseResponseError(); |
| } |
| |
| return ipmi::responseSuccess(entryChanged); |
| } |
| |
| /** |
| @brief This command is MDR related get data block command. |
| |
| @param - agentId |
| @param - lockHandle |
| @param - xferOffset |
| @param - xferLength |
| |
| @return on success |
| - xferLength |
| - checksum |
| - data |
| **/ |
| ipmi::RspType<uint32_t, // xferLength |
| uint32_t, // Checksum |
| std::vector<uint8_t> // data |
| > |
| mdr2GetDataBlock(uint16_t agentId, uint16_t lockHandle, uint32_t xferOffset, |
| uint32_t xferLength) |
| { |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| int idIndex = mdrv2->findLockHandle(lockHandle); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| if (xferOffset >= mdrv2->smbiosDir.dir[idIndex].common.size) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Offset is outside of range."); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| size_t outSize = (xferLength > mdrv2->smbiosDir.dir[idIndex].xferSize) |
| ? mdrv2->smbiosDir.dir[idIndex].xferSize |
| : xferLength; |
| if (outSize > UINT_MAX - xferOffset) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Out size and offset are out of range"); |
| return ipmi::responseParmOutOfRange(); |
| } |
| if ((xferOffset + outSize) > mdrv2->smbiosDir.dir[idIndex].common.size) |
| { |
| outSize = mdrv2->smbiosDir.dir[idIndex].common.size - xferOffset; |
| } |
| |
| uint32_t respXferLength = outSize; |
| |
| if (respXferLength > xferLength) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Get data block unexpected error."); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| if ((xferOffset + outSize) > |
| UINT_MAX - |
| reinterpret_cast<size_t>(mdrv2->smbiosDir.dir[idIndex].dataStorage)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Input data to calculate checksum is out of range"); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| uint32_t u32Checksum = mdrv2->calcChecksum32( |
| mdrv2->smbiosDir.dir[idIndex].dataStorage + xferOffset, outSize); |
| if (u32Checksum == invalidChecksum) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Get data block failed - invalid checksum"); |
| return ipmi::response(ccOemInvalidChecksum); |
| } |
| std::vector<uint8_t> data(outSize); |
| |
| std::copy(&mdrv2->smbiosDir.dir[idIndex].dataStorage[xferOffset], |
| &mdrv2->smbiosDir.dir[idIndex].dataStorage[xferOffset + outSize], |
| data.begin()); |
| |
| return ipmi::responseSuccess(respXferLength, u32Checksum, data); |
| } |
| |
| /** @brief implements mdr2 send data block command |
| * @param agentId |
| * @param lockHandle |
| * @param xferOffset |
| * @param xferLength |
| * @param checksum |
| * |
| * @returns IPMI completion code |
| */ |
| ipmi::RspType<> mdr2SendDataBlock(uint16_t agentId, uint16_t lockHandle, |
| uint32_t xferOffset, uint32_t xferLength, |
| uint32_t checksum) |
| { |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| int idIndex = mdrv2->findLockHandle(lockHandle); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| if (mdrv2->smbiosIsUpdating(idIndex)) |
| { |
| if (xferOffset > UINT_MAX - xferLength) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Offset and length are out of range"); |
| return ipmi::responseParmOutOfRange(); |
| } |
| if (((xferOffset + xferLength) > |
| mdrv2->smbiosDir.dir[idIndex].maxDataSize) || |
| ((xferOffset + xferLength) > |
| mdrv2->smbiosDir.dir[idIndex].common.dataSetSize)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Send data block Invalid offset/length"); |
| return ipmi::responseReqDataLenExceeded(); |
| } |
| if (reinterpret_cast<size_t>( |
| mdrv2->smbiosDir.dir[idIndex].dataStorage) > |
| UINT_MAX - xferOffset) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Offset is out of range"); |
| return ipmi::responseParmOutOfRange(); |
| } |
| uint8_t* destAddr = |
| mdrv2->smbiosDir.dir[idIndex].dataStorage + xferOffset; |
| uint8_t* sourceAddr = reinterpret_cast<uint8_t*>(mdrv2->area->vPtr); |
| uint32_t calcChecksum = mdrv2->calcChecksum32(sourceAddr, xferLength); |
| if (calcChecksum != checksum) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Send data block Invalid checksum"); |
| return ipmi::response(ccOemInvalidChecksum); |
| } |
| else |
| { |
| if (reinterpret_cast<size_t>(sourceAddr) > UINT_MAX - xferLength) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Length is out of range"); |
| return ipmi::responseParmOutOfRange(); |
| } |
| std::copy(sourceAddr, sourceAddr + xferLength, destAddr); |
| } |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Send data block failed, other data is updating"); |
| return ipmi::responseDestinationUnavailable(); |
| } |
| |
| return ipmi::responseSuccess(); |
| } |
| |
| bool MDRV2::storeDatatoFlash(MDRSMBIOSHeader* mdrHdr, uint8_t* data) |
| { |
| std::ofstream smbiosFile(mdrType2File, |
| std::ios_base::binary | std::ios_base::trunc); |
| if (!smbiosFile.good()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Write data from flash error - Open MDRV2 table file failure"); |
| return false; |
| } |
| |
| try |
| { |
| smbiosFile.write(reinterpret_cast<char*>(mdrHdr), |
| sizeof(MDRSMBIOSHeader)); |
| smbiosFile.write(reinterpret_cast<char*>(data), mdrHdr->dataSize); |
| } |
| catch (const std::ofstream::failure& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Write data from flash error - write data error", |
| phosphor::logging::entry("ERROR=%s", e.what())); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void SharedMemoryArea::Initialize(uint32_t addr, uint32_t areaSize) |
| { |
| int memDriver = 0; |
| |
| // open mem driver for the system memory access |
| memDriver = open("/dev/vgasharedmem", O_RDONLY); |
| if (memDriver < 0) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Cannot access mem driver"); |
| throw std::system_error(EIO, std::generic_category()); |
| } |
| |
| // map the system memory |
| vPtr = mmap(NULL, // where to map to: don't mind |
| areaSize, // how many bytes ? |
| PROT_READ, // want to read and write |
| MAP_SHARED, // no copy on write |
| memDriver, // handle to /dev/mem |
| (physicalAddr & pageMask)); // hopefully the Text-buffer :-) |
| |
| close(memDriver); |
| if (vPtr == MAP_FAILED) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Failed to map share memory"); |
| throw std::system_error(EIO, std::generic_category()); |
| } |
| size = areaSize; |
| physicalAddr = addr; |
| } |
| |
| bool MDRV2::smbiosUnlock(uint8_t index) |
| { |
| bool ret = false; |
| switch (smbiosDir.dir[index].stage) |
| { |
| case MDR2SMBIOSStatusEnum::mdr2Updating: |
| smbiosDir.dir[index].stage = MDR2SMBIOSStatusEnum::mdr2Updated; |
| smbiosDir.dir[index].lock = MDR2DirLockEnum::mdr2DirUnlock; |
| |
| timer->stop(); |
| smbiosDir.dir[index].lockHandle = 0; |
| ret = true; |
| break; |
| |
| case MDR2SMBIOSStatusEnum::mdr2Updated: |
| case MDR2SMBIOSStatusEnum::mdr2Loaded: |
| smbiosDir.dir[index].lock = MDR2DirLockEnum::mdr2DirUnlock; |
| |
| timer->stop(); |
| |
| smbiosDir.dir[index].lockHandle = 0; |
| ret = true; |
| break; |
| |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| bool MDRV2::smbiosTryLock(uint8_t flag, uint8_t index, uint16_t* session, |
| uint16_t timeout) |
| { |
| bool ret = false; |
| uint32_t u32Status = 0; |
| |
| if (timeout == 0) |
| { |
| timeout = defaultTimeout; |
| } |
| std::chrono::microseconds usec(timeout * sysClock); |
| |
| switch (smbiosDir.dir[index].stage) |
| { |
| case MDR2SMBIOSStatusEnum::mdr2Updating: |
| if (smbiosDir.dir[index].lock != MDR2DirLockEnum::mdr2DirLock) |
| { |
| smbiosDir.dir[index].lock = MDR2DirLockEnum::mdr2DirLock; |
| timer->start(usec); |
| lockIndex = index; |
| |
| *session = getSessionHandle(&smbiosDir); |
| smbiosDir.dir[index].lockHandle = *session; |
| ret = true; |
| } |
| break; |
| case MDR2SMBIOSStatusEnum::mdr2Init: |
| if (flag) |
| { |
| smbiosDir.dir[index].stage = MDR2SMBIOSStatusEnum::mdr2Updating; |
| smbiosDir.dir[index].lock = MDR2DirLockEnum::mdr2DirUnlock; |
| timer->start(usec); |
| lockIndex = index; |
| |
| *session = getSessionHandle(&smbiosDir); |
| smbiosDir.dir[index].lockHandle = *session; |
| ret = true; |
| } |
| break; |
| |
| case MDR2SMBIOSStatusEnum::mdr2Updated: |
| case MDR2SMBIOSStatusEnum::mdr2Loaded: |
| if (smbiosDir.dir[index].lock != MDR2DirLockEnum::mdr2DirLock) |
| { |
| if (flag) |
| { |
| smbiosDir.dir[index].stage = |
| MDR2SMBIOSStatusEnum::mdr2Updating; |
| smbiosDir.dir[index].lock = MDR2DirLockEnum::mdr2DirUnlock; |
| } |
| else |
| { |
| smbiosDir.dir[index].lock = MDR2DirLockEnum::mdr2DirLock; |
| } |
| |
| timer->start(usec); |
| lockIndex = index; |
| |
| *session = getSessionHandle(&smbiosDir); |
| smbiosDir.dir[index].lockHandle = *session; |
| ret = true; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| return ret; |
| } |
| |
| void MDRV2::timeoutHandler() |
| { |
| smbiosUnlock(lockIndex); |
| mdrv2->area.reset(nullptr); |
| } |
| |
| /** @brief implements mdr2 lock data command |
| * @param agentId |
| * @param dataInfo |
| * @param timeout |
| * |
| * @returns IPMI completion code plus response data |
| * - mdr2Version |
| * - session |
| * - dataLength |
| * - xferAddress |
| * - xferLength |
| */ |
| ipmi::RspType<uint8_t, // mdr2Version |
| uint16_t, // session |
| uint32_t, // dataLength |
| uint32_t, // xferAddress |
| uint32_t // xferLength |
| > |
| mdr2LockData(uint16_t agentId, std::array<uint8_t, dataInfoSize> dataInfo, |
| uint16_t timeout) |
| { |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| |
| int idIndex = mdrv2->findDataId(dataInfo.data(), dataInfo.size(), service); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| uint16_t session = 0; |
| if (!mdrv2->smbiosTryLock(0, idIndex, &session, timeout)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Lock Data failed - cannot lock idIndex"); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| |
| uint32_t dataLength = mdrv2->smbiosDir.dir[idIndex].common.size; |
| uint32_t xferAddress = mdrv2->smbiosDir.dir[idIndex].xferBuff; |
| uint32_t xferLength = mdrv2->smbiosDir.dir[idIndex].xferSize; |
| |
| return ipmi::responseSuccess(mdr2Version, session, dataLength, xferAddress, |
| xferLength); |
| } |
| |
| /** @brief implements mdr2 unlock data command |
| * @param agentId |
| * @param lockHandle |
| * |
| * @returns IPMI completion code |
| */ |
| ipmi::RspType<> mdr2UnlockData(uint16_t agentId, uint16_t lockHandle) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>("unlock data"); |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| int idIndex = mdrv2->findLockHandle(lockHandle); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| if (!mdrv2->smbiosUnlock(idIndex)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unlock Data failed - cannot unlock idIndex"); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| |
| return ipmi::responseSuccess(); |
| } |
| |
| /** |
| @brief This command is executed after POST BIOS to get the session info. |
| |
| @param - agentId, dataInfo, dataLength, xferAddress, xferLength, timeout. |
| |
| @return xferStartAck and session on success. |
| **/ |
| ipmi::RspType<uint8_t, uint16_t> |
| cmd_mdr2_data_start(uint16_t agentId, std::array<uint8_t, 16> dataInfo, |
| uint32_t dataLength, uint32_t xferAddress, |
| uint32_t xferLength, uint16_t timeout) |
| { |
| uint16_t session = 0; |
| |
| if (dataLength > smbiosTableStorageSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Requested data length is out of SMBIOS Table storage size."); |
| return ipmi::responseParmOutOfRange(); |
| } |
| if ((xferLength + xferAddress) > mdriiSMSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid data address and size"); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| int idIndex = mdrv2->findDataId(dataInfo.data(), dataInfo.size(), service); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| if (mdrv2->smbiosTryLock(1, idIndex, &session, timeout)) |
| { |
| try |
| { |
| mdrv2->area = |
| std::make_unique<SharedMemoryArea>(xferAddress, xferLength); |
| } |
| catch (const std::system_error& e) |
| { |
| mdrv2->smbiosUnlock(idIndex); |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to access share memory", |
| phosphor::logging::entry("ERROR=%s", e.what())); |
| return ipmi::responseUnspecifiedError(); |
| } |
| mdrv2->smbiosDir.dir[idIndex].common.size = dataLength; |
| mdrv2->smbiosDir.dir[idIndex].lockHandle = session; |
| if (-1 == |
| mdrv2->syncDirCommonData( |
| idIndex, mdrv2->smbiosDir.dir[idIndex].common.size, service)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unable to sync data to service"); |
| return ipmi::responseResponseError(); |
| } |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Canot lock smbios"); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| static constexpr uint8_t xferStartAck = 1; |
| |
| return ipmi::responseSuccess(xferStartAck, session); |
| } |
| |
| /** |
| @brief This command is executed to close the session. |
| |
| @param - agentId, lockHandle. |
| |
| @return completion code on success. |
| **/ |
| ipmi::RspType<> cmd_mdr2_data_done(uint16_t agentId, uint16_t lockHandle) |
| { |
| |
| if (mdrv2 == nullptr) |
| { |
| mdrv2 = std::make_unique<MDRV2>(); |
| } |
| |
| int agentIndex = mdrv2->agentLookup(agentId); |
| if (agentIndex == -1) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Unknown agent id", phosphor::logging::entry("ID=%x", agentId)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| int idIndex = mdrv2->findLockHandle(lockHandle); |
| |
| if ((idIndex < 0) || (idIndex >= maxDirEntries)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Data ID", phosphor::logging::entry("IDINDEX=%x", idIndex)); |
| return ipmi::responseParmOutOfRange(); |
| } |
| |
| if (!mdrv2->smbiosUnlock(idIndex)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Send data done failed - cannot unlock idIndex"); |
| return ipmi::responseDestinationUnavailable(); |
| } |
| |
| mdrv2->area.reset(nullptr); |
| MDRSMBIOSHeader mdr2Smbios; |
| mdr2Smbios.mdrType = mdrTypeII; |
| mdr2Smbios.dirVer = mdrv2->smbiosDir.dir[0].common.dataVersion; |
| mdr2Smbios.timestamp = mdrv2->smbiosDir.dir[0].common.timestamp; |
| mdr2Smbios.dataSize = mdrv2->smbiosDir.dir[0].common.size; |
| |
| if (access(smbiosPath, 0) == -1) |
| { |
| int flag = mkdir(smbiosPath, S_IRWXU); |
| if (flag != 0) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "create folder failed for writting smbios file"); |
| } |
| } |
| if (!mdrv2->storeDatatoFlash( |
| &mdr2Smbios, mdrv2->smbiosDir.dir[smbiosDirIndex].dataStorage)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "MDR2 Store data to flash failed"); |
| return ipmi::responseDestinationUnavailable(); |
| } |
| bool status = false; |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| std::string service = ipmi::getService(*bus, mdrv2Interface, mdrv2Path); |
| sdbusplus::message_t method = bus->new_method_call( |
| service.c_str(), mdrv2Path, mdrv2Interface, "AgentSynchronizeData"); |
| |
| try |
| { |
| sdbusplus::message_t reply = bus->call(method); |
| reply.read(status); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Error Sync data with service", |
| phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("SERVICE=%s", service.c_str()), |
| phosphor::logging::entry("PATH=%s", mdrv2Path)); |
| return ipmi::responseResponseError(); |
| } |
| |
| if (!status) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Sync data with service failure"); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| return ipmi::responseSuccess(); |
| } |
| |
| static void register_netfn_smbiosmdrv2_functions(void) |
| { |
| // MDR V2 Command |
| // <Get MDRII Status Command> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIIAgentStatus, |
| ipmi::Privilege::Operator, mdr2AgentStatus); |
| |
| // <Get MDRII Directory Command> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIIGetDir, |
| ipmi::Privilege::Operator, mdr2GetDir); |
| |
| // <Send MDRII Directory Command> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIISendDir, |
| ipmi::Privilege::Operator, mdr2SendDir); |
| |
| // <Get MDRII Data Info Command> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIIGetDataInfo, |
| ipmi::Privilege::Operator, mdr2GetDataInfo); |
| |
| // <Send MDRII Info Offer> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIISendDataInfoOffer, |
| ipmi::Privilege::Operator, mdr2DataInfoOffer); |
| |
| // <Send MDRII Data Info> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIISendDataInfo, |
| ipmi::Privilege::Operator, mdr2SendDataInfo); |
| |
| // <Get MDRII Data Block Command> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIIGetDataBlock, |
| ipmi::Privilege::Operator, mdr2GetDataBlock); |
| |
| // <Send MDRII Data Block> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIISendDataBlock, |
| ipmi::Privilege::Operator, mdr2SendDataBlock); |
| |
| // <Lock MDRII Data Command> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIILockData, |
| ipmi::Privilege::Operator, mdr2LockData); |
| |
| // <Unlock MDRII Data Command> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIIUnlockData, |
| ipmi::Privilege::Operator, mdr2UnlockData); |
| |
| // <Send MDRII Data Start> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIIDataStart, |
| ipmi::Privilege::Operator, cmd_mdr2_data_start); |
| |
| // <Send MDRII Data Done> |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, |
| ipmi::intel::app::cmdMdrIIDataDone, |
| ipmi::Privilege::Operator, cmd_mdr2_data_done); |
| } |